diff --git a/.github/images/pcbway_delivery.png b/.github/images/pcbway_delivery.png deleted file mode 100644 index 171f81ed..00000000 Binary files a/.github/images/pcbway_delivery.png and /dev/null differ diff --git a/.github/images/pcbx_example.png b/.github/images/pcbx_example.png new file mode 100644 index 00000000..41d94441 Binary files /dev/null and b/.github/images/pcbx_example.png differ diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index e9f74965..7b7ec219 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -28,31 +28,34 @@ jobs: echo "VERSION=${VERSION}" >> $GITHUB_ENV - name: Update package.json version - if: github.ref == 'refs/heads/master' + if: github.ref_name == 'master' working-directory: docs run: | sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}\"/" package.json - name: Update package.json version - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') working-directory: docs run: | - sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}-dev\"/" package.json + TIMESTAMP=$(date +%d%H%M) + sed -i "s/\"version\": \".*\"/\"version\": \"${VERSION}-dev.${TIMESTAMP}\"/" package.json - name: Update example fw branch - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') working-directory: firmware/examples run: | - sed -i 's/@master/@dev/g' *.example.yaml + BRANCH_NAME=${{ github.ref_name }} + sed -i "s/@master/@${BRANCH_NAME}/g" *.example.yaml - name: Update docs branch - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') working-directory: docs run: | - sed -i 's/@master/@dev/g' */guide/getting-started.md - sed -i 's/master/dev/g' */guide/enclosure/3d-printing.md - sed -i 's/@master/@dev/g' */reference/schematics.md - sed -i 's/@master/@dev/g' */guide/firmware/minimal.example.yaml + BRANCH_NAME=${{ github.ref_name }} + sed -i "s/@master/@${BRANCH_NAME}/g" */guide/getting-started.md + sed -i "s/master/${BRANCH_NAME}/g" */guide/enclosure/3d-printing.md + sed -i "s/@master/@${BRANCH_NAME}/g" */reference/schematics.md + sed -i "s/@master/@${BRANCH_NAME}/g" */guide/firmware/minimal.example.yaml - name: Install dependencies working-directory: docs diff --git a/.github/workflows/build-esphome.yml b/.github/workflows/build-esphome.yml index 95385087..cd8606d4 100644 --- a/.github/workflows/build-esphome.yml +++ b/.github/workflows/build-esphome.yml @@ -22,6 +22,16 @@ on: required: true type: string default: esp32 + summary: + description: Summary of the release + required: false + type: string + default: Not available + url: + description: URL of the release + required: false + type: string + default: "https://github.com/AzonInc/Doorman/releases" jobs: prepare: @@ -49,16 +59,19 @@ jobs: - uses: actions/checkout@v4 - name: Change ESPHome project version to dev - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') run: | VERSION=$(awk '/project:/, /version:/' firmware/base.yaml | grep 'version:' | awk '{print $2}' | sed 's/[",]//g') - sed -i '/^ project:/,/^ platformio_options:/s/^ version: .*/ version: "'"$VERSION"'-dev"/' firmware/base.yaml + TIMESTAMP=$(date +%d%H%M) + sed -i '/^ project:/,/^ platformio_options:/s/^ version: .*/ version: "'"$VERSION"'-dev.'"$TIMESTAMP"'"/' firmware/base.yaml - - uses: esphome/build-action@v4.0.1 + - uses: esphome/build-action@v6.0.0 id: esphome-build with: yaml-file: ${{ matrix.file }} version: ${{ inputs.esphome_version }} + release-url: ${{ inputs.url }} + release-summary: ${{ inputs.summary }} complete-manifest: true - name: Change Manifest Firmware Name diff --git a/.github/workflows/build-nightly.yaml b/.github/workflows/build-nightly.yaml index 6d623fc1..0a21bf95 100644 --- a/.github/workflows/build-nightly.yaml +++ b/.github/workflows/build-nightly.yaml @@ -13,22 +13,22 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Compile Stock (ESP32-S3) Firmware - uses: esphome/build-action@v4.0.1 + - name: Build Doorman Stock for Home Assistant (ESP32-S3) Firmware + uses: esphome/build-action@v6.0.0 with: - yaml-file: firmware/doorman-stock.yaml + yaml-file: firmware/ha-doorman-stock.yaml version: latest complete-manifest: true - - name: Compile Nuki Bridge (ES32-S3) Firmware - uses: esphome/build-action@v4.0.1 + - name: Build Doorman Nuki Bridge for Home Assistant (ESP32-S3) Firmware + uses: esphome/build-action@v6.0.0 with: - yaml-file: firmware/doorman-nuki-bridge.yaml + yaml-file: firmware/ha-doorman-nuki-bridge.yaml version: latest complete-manifest: true - - name: Compile Component Test (ESP8266) Firmware - uses: esphome/build-action@v4.0.1 + - name: Build Component Test (ESP8266) Firmware + uses: esphome/build-action@v6.0.0 with: yaml-file: firmware/tc-bus-component-8266.yaml version: latest diff --git a/.github/workflows/bundle-assets.yml b/.github/workflows/bundle-assets.yml index 171ecf1a..031b9352 100644 --- a/.github/workflows/bundle-assets.yml +++ b/.github/workflows/bundle-assets.yml @@ -25,27 +25,27 @@ jobs: - name: Copy interactive BOM run: cp pcb/bom/ibom.html output/ - - - name: Download Doorman Stock (ESP32-S3) artifact + + - name: Download Doorman Stock for Home Assistant (ESP32-S3) artifact uses: actions/download-artifact@v4 with: name: firmware-doorman-stock path: output/firmware/release - - name: Download Doorman Nuki Bridge (ESP32-S3) artifact + - name: Download Doorman Nuki Bridge for Home Assistant (ESP32-S3) artifact uses: actions/download-artifact@v4 with: name: firmware-doorman-nuki-bridge path: output/firmware/release - name: Upload Pages artifact - if: github.ref == 'refs/heads/master' + if: github.ref_name == 'master' uses: actions/upload-pages-artifact@v3 with: path: output - name: Upload Assets Bundle - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') uses: actions/upload-artifact@v4 with: name: asset-bundle diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e1f3d2c7..3a7f0f34 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,60 +24,107 @@ jobs: name: Build Docs uses: ./.github/workflows/build-docs.yml + prepare-release: + runs-on: ubuntu-latest + name: Prepare release data + outputs: + release_summary: ${{ steps.generate-summary.outputs.summary }} + release_url: ${{ steps.generate-summary.outputs.url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate Release Data + id: generate-summary + run: | + GITHUB_REF_NAME=${{ github.ref_name }} + SHA=${{ github.sha }} + + URL="https://doorman.azon.ai/changelog/firmware" + + if [[ "${GITHUB_REF_NAME}" == "master" ]]; then + SUMMARY="Kindly review the release notes for further details, as there may be some breaking changes." + elif [[ "${GITHUB_REF_NAME}" == dev* ]]; then + SUMMARY="Please note this is an experimental update (${GITHUB_REF_NAME}) and may contain unstable features. Details: $(git log -1 --pretty=%B), ${SHA}" + URL="https://doorman-dev.surge.sh/changelog/firmware" + else + SUMMARY="" + fi + + # Encode special characters + SUMMARY="${SUMMARY//'%'/'%25'}" + SUMMARY="${SUMMARY//$'\n'/'%0A'}" + SUMMARY="${SUMMARY//$'\r'/'%0D'}" + + echo "url=$URL" >> $GITHUB_OUTPUT + echo "summary=$SUMMARY" >> $GITHUB_OUTPUT # Build stable firmware build-doorman-stock-stable-firmware: - if: github.ref == 'refs/heads/master' - name: Build Doorman Stock (ESP32-S3) Firmware (stable) + if: github.ref_name == 'master' + name: Build Doorman Stock for Home Assistant (ESP32-S3) Firmware (stable) uses: ./.github/workflows/build-esphome.yml + needs: prepare-release with: - files: firmware/doorman-stock.yaml + files: firmware/ha-doorman-stock.yaml name: AzonInc.Doorman - esphome_version: dev + esphome_version: latest directory_name: doorman-stock + summary: ${{ needs.prepare-release.outputs.release_summary }} + url: ${{ needs.prepare-release.outputs.release_url }} build-doorman-nuki-bridge-stable-firmware: - if: github.ref == 'refs/heads/master' - name: Build Doorman Nuki Bridge (ESP32-S3) Firmware (stable) + if: github.ref_name == 'master' + name: Build Doorman Nuki Bridge for Home Assistant (ESP32-S3) Firmware (stable) uses: ./.github/workflows/build-esphome.yml + needs: prepare-release with: - files: firmware/doorman-nuki-bridge.yaml + files: firmware/ha-doorman-nuki-bridge.yaml name: AzonInc.Doorman-Nuki-Bridge - esphome_version: dev + esphome_version: latest directory_name: doorman-nuki-bridge + summary: ${{ needs.prepare-release.outputs.release_summary }} + url: ${{ needs.prepare-release.outputs.release_url }} # Build dev firmware build-doorman-stock-dev-firmware: - if: github.ref != 'refs/heads/master' - name: Build Doorman Stock (ESP32-S3) Firmware (dev) + if: startsWith(github.ref_name, 'dev') + name: Build Doorman Stock for Home Assistant (ESP32-S3) Firmware (dev) uses: ./.github/workflows/build-esphome.yml + needs: prepare-release with: - files: firmware/doorman-stock.dev.yaml + files: firmware/ha-doorman-stock.dev.yaml name: AzonInc.Doorman - esphome_version: dev + esphome_version: latest directory_name: doorman-stock + summary: ${{ needs.prepare-release.outputs.release_summary }} + url: ${{ needs.prepare-release.outputs.release_url }} build-doorman-nuki-bridge-dev-firmware: - if: github.ref != 'refs/heads/master' - name: Build Doorman Nuki Bridge (ESP32-S3) Firmware (dev) + if: startsWith(github.ref_name, 'dev') + name: Build Doorman Nuki Bridge for Home Assistant (ESP32-S3) Firmware (dev) uses: ./.github/workflows/build-esphome.yml + needs: prepare-release with: - files: firmware/doorman-nuki-bridge.dev.yaml + files: firmware/ha-doorman-nuki-bridge.dev.yaml name: AzonInc.Doorman-Nuki-Bridge - esphome_version: dev + esphome_version: latest directory_name: doorman-nuki-bridge - + summary: ${{ needs.prepare-release.outputs.release_summary }} + url: ${{ needs.prepare-release.outputs.release_url }} # Bundle all assets for Pages bundle-assets-stable: - if: github.ref == 'refs/heads/master' + if: github.ref_name == 'master' name: Create asset bundle (stable) uses: ./.github/workflows/bundle-assets.yml needs: [build-docs, build-doorman-stock-stable-firmware, build-doorman-nuki-bridge-stable-firmware] bundle-assets-dev: - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') name: Create asset bundle (dev) uses: ./.github/workflows/bundle-assets.yml needs: [build-docs, build-doorman-stock-dev-firmware, build-doorman-nuki-bridge-dev-firmware] @@ -85,7 +132,7 @@ jobs: # Deployment job deploy-stable: - if: github.ref == 'refs/heads/master' + if: github.ref_name == 'master' name: Deploy to Github Pages (stable) environment: name: github-pages @@ -98,7 +145,7 @@ jobs: uses: actions/deploy-pages@v4 deploy-dev: - if: github.ref != 'refs/heads/master' + if: startsWith(github.ref_name, 'dev') name: Deploy to surge.sh (dev) environment: name: surge.sh diff --git a/.gitignore b/.gitignore index a2075ed5..9a90e762 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ TODOs.md *.timestamp-*.mjs __pycache__ .esphome -/*.yaml \ No newline at end of file +/*.yaml +.esphome/ +/secrets.yaml \ No newline at end of file diff --git a/README.md b/README.md index 5b29e98d..b7e7e498 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ The Doorman S3 isn't limited to these intercoms alone. With its integrated relay If your intercom isn't a TCS, Koch, Niko, Scantron or Jung model but operates on a 2-wire bus within the 14-24V DC range, you might be able to implement other protocols. Additionally, you can monitor the voltage levels on older intercoms (14-24V), allowing you to trigger specific actions based on the voltage readings. -A big thanks to [PCBWay](https://pcbway.com) for sponsoring this project! For more details on the manufacturing process, scroll down to the "Manufacturing" section. ## 🚀 Getting started @@ -56,17 +55,13 @@ The repository is organized into the following directories: ## 🛠️ Manufacturing -PCBWay Delivery +PCBX Delivery -I was unsure which PCB manufacturer to choose, but fortunately, PCBWay reached out and offered to sponsor the PCB fabrication. I couldn't be happier with the results—the quality is exceptional, especially with the vibrant colors. Everything arrived in pristine condition, and sourcing parts was hassle-free. They even included two extra unpopulated PCBs. +A huge thanks to [PCBX](https://www.pcbx.com/?mtm_campaign=review&mtm_kwd=BD&mtm_source=flo) for manufacturing and sponsoring 10 awesome PCBs for this project! Your support means a lot to us, and your prices are seriously hard to beat. We're thrilled to have you on board! -I handled the soldering of the ESP modules myself using a heating plate, as I already had ESP32 modules at home. +If you're looking for a reliable PCB manufacturer for your own project, definitely check out [PCBX](https://www.pcbx.com/?mtm_campaign=review&mtm_kwd=BD&mtm_source=flo). While their production might take a little longer than some others, the unbeatable prices and super-easy part sourcing make it absolutely worth it. They handle everything for you! -PCBWay made the entire process straightforward and responsive. A special thanks to Liam and Lynne for their support throughout, especially given the numerous changes I made. Their patience and assistance were greatly appreciated. - -If you're looking for a reliable, high-quality PCB manufacturer, I highly recommend [PCBWay](https://pcbway.com) :) - -You can find all the neccessary files [here](https://github.com/AzonInc/doorman/tree/master/pcb). +You can find all the necessary files for manufacturing the Doorman-S3 [here](https://github.com/AzonInc/doorman/tree/master/pcb). ## 🙌 Contributing If you'd like to contribute to the Doorman project, we welcome your involvement!\ diff --git a/components/tc_bus/__init__.py b/components/tc_bus/__init__.py index ee18500f..686c8d68 100644 --- a/components/tc_bus/__init__.py +++ b/components/tc_bus/__init__.py @@ -24,20 +24,30 @@ "TCBusReadMemoryAction", automation.Action ) +TCBusIdentifyAction = tc_bus_ns.class_( + "TCBusIdentifyAction", automation.Action +) + CommandData = tc_bus_ns.struct(f"CommandData") SettingData = tc_bus_ns.struct(f"SettingData") +ModelData = tc_bus_ns.struct(f"ModelData") +IdentifyCompleteTrigger = tc_bus_ns.class_("IdentifyCompleteTrigger", automation.Trigger.template()) +IdentifyTimeoutTrigger = tc_bus_ns.class_("IdentifyTimeoutTrigger", automation.Trigger.template()) ReadMemoryCompleteTrigger = tc_bus_ns.class_("ReadMemoryCompleteTrigger", automation.Trigger.template()) ReadMemoryTimeoutTrigger = tc_bus_ns.class_("ReadMemoryTimeoutTrigger", automation.Trigger.template()) + ReceivedCommandTrigger = tc_bus_ns.class_("ReceivedCommandTrigger", automation.Trigger.template()) SETTING_TYPE = tc_bus_ns.enum("SettingType") SETTING_TYPES = { "ringtone_floor_call": SETTING_TYPE.SETTING_RINGTONE_FLOOR_CALL, - "ringtone_door_call": SETTING_TYPE.SETTING_RINGTONE_DOOR_CALL, + "ringtone_entrance_door_call": SETTING_TYPE.SETTING_RINGTONE_ENTRANCE_DOOR_CALL, + "ringtone_second_entrance_door_call": SETTING_TYPE.SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL, "ringtone_internal_call": SETTING_TYPE.SETTING_RINGTONE_INTERNAL_CALL, "volume_ringtone": SETTING_TYPE.SETTING_VOLUME_RINGTONE, - "volume_handset": SETTING_TYPE.SETTING_VOLUME_HANDSET + "volume_handset_door": SETTING_TYPE.SETTING_VOLUME_HANDSET_DOOR, + "volume_handset_internal": SETTING_TYPE.SETTING_VOLUME_HANDSET_INTERNAL } COMMAND_TYPE = tc_bus_ns.enum("CommandType") @@ -54,6 +64,7 @@ "stop_talking_door_call": COMMAND_TYPE.COMMAND_TYPE_STOP_TALKING_DOOR_CALL, "stop_talking": COMMAND_TYPE.COMMAND_TYPE_STOP_TALKING, "open_door": COMMAND_TYPE.COMMAND_TYPE_OPEN_DOOR, + "open_door_long": COMMAND_TYPE.COMMAND_TYPE_OPEN_DOOR_LONG, "light": COMMAND_TYPE.COMMAND_TYPE_LIGHT, "door_opened": COMMAND_TYPE.COMMAND_TYPE_DOOR_OPENED, "door_closed": COMMAND_TYPE.COMMAND_TYPE_DOOR_CLOSED, @@ -75,17 +86,64 @@ CONF_MODELS = [ "None", - "TCS ISH1030", - "TCS ISH3030", - "TCS ISH3230", - "TCS ISH3340", - "TCS ISW3030", - "TCS ISW3230", + "TCS ISW3030 / Koch TC50 / Scantron Stilux", + "TCS ISW3130 / Koch TC50P", + "TCS ISW3230 / Koch TC50 GFA", + "TCS ISW3330 / Koch TC50 BW", "TCS ISW3340", - "TCS IVH3222", - "Koch TC50", - "Koch TCH50", - "Koch TCH50P" + "TCS ISW5010 / Koch TC60", + "TCS ISW5020", + "TCS ISW5030", + "TCS ISW5031", + "TCS ISW5033", + "TCS IVW511x / Koch VTC60 / Scantron VIVO", + "TCS IVW521x / Koch VTC60/2D", + "TCS ISW6031", + "TCS ISW7030 / Koch TC70", + "TCS IVW7510 / Koch VTC70", + "TCS ISH7030 / Koch TCH70", + "TCS IVH7510 / Koch VTCH70", + "TCS ISW6010", + "TCS IVW6511", + "TCS ISWM7000", + "TCS IVWM7000", + "TCS ISW4100 / Koch TC31", + "TCS IMM2100 / Koch TCE31", + "TCS IVW2210 / Koch Ecoos", + "TCS IVW2211 / Koch Ecoos", + "TCS IVW2212 / Koch Ecoos / Scantron SLIM60T", + "TCS VTC42V2", + "TCS TC40V2", + "TCS VTC40", + "TCS TC40", + "TCS TC2000", + "TCS TC20P", + "TCS TC20F", + "TCS ISH3022 / Koch TCH50P", + "TCS ISH3130 / Koch TCH50P / Scantron LuxPlus", + "TCS ISH3230 / Koch TCH50 GFA", + "TCS ISH3030 / Koch TCH50 / Scantron Lux2", + "TCS ISH1030 / Koch TTS25", + "TCS IMM1000 / Koch TCH30", + "TCS IMM1100 / Koch TCHE30", + "TCS IMM1300 / Koch VTCH30", + "TCS IMM1500", + "TCS IMM1310 / Koch VTCHE30", + "TCS IMM1110 / Koch TCHEE30", + "TCS IVH3222 / Koch VTCH50 / Scantron VLux", + "TCS IVH4222 / Koch VTCH50/2D", + "TCS IVW2220 / Koch Sky", + "TCS IVW2221 / Koch Sky R1.00", + "TCS IVW3011 / Koch Skyline Plus", + "TCS IVW3012 / Koch Skyline/Aldup", + "TCS VMH / Koch VMH", + "TCS VML / Koch VML", + "TCS VMF / Koch VMF", + "Jung TKIS", + "Jung TKISV", + "TCS CAIXXXX / Koch CAIXXXX", + "TCS CAI2000 / Koch Carus", + "TCS ISW42X0" ] CONF_RINGTONES = [ @@ -112,14 +170,17 @@ CONF_SERIAL_NUMBER = "serial_number" CONF_COMMAND = "command" +CONF_IS_LONG = "is_long" CONF_ADDRESS = "address" CONF_ADDRESS_LAMBDA = "address_lambda" CONF_PAYLOAD = "payload" CONF_PAYLOAD_LAMBDA = "payload_lambda" CONF_ON_COMMAND = "on_command" -CONF_ON_MEMORY = "on_read_memory_complete" -CONF_ON_MEMORY_TIMEOUT = "on_read_memory_timeout" +CONF_ON_READ_MEMORY_COMPLETE = "on_read_memory_complete" +CONF_ON_READ_MEMORY_TIMEOUT = "on_read_memory_timeout" +CONF_ON_IDENTIFY_COMPLETE = "on_identify_complete" +CONF_ON_IDENTIFY_TIMEOUT = "on_identify_timeout" CONF_PROGRAMMING_MODE = "programming_mode" @@ -139,16 +200,26 @@ def validate_config(config): cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReceivedCommandTrigger), } ), - cv.Optional(CONF_ON_MEMORY): automation.validate_automation( + cv.Optional(CONF_ON_READ_MEMORY_COMPLETE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadMemoryCompleteTrigger), } ), - cv.Optional(CONF_ON_MEMORY_TIMEOUT): automation.validate_automation( + cv.Optional(CONF_ON_READ_MEMORY_TIMEOUT): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadMemoryTimeoutTrigger), } ), + cv.Optional(CONF_ON_IDENTIFY_COMPLETE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(IdentifyCompleteTrigger), + } + ), + cv.Optional(CONF_ON_IDENTIFY_TIMEOUT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(IdentifyTimeoutTrigger), + } + ), } ) @@ -174,11 +245,19 @@ async def to_code(config): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(CommandData, "x")], conf) - for conf in config.get(CONF_ON_MEMORY, []): + for conf in config.get(CONF_ON_READ_MEMORY_COMPLETE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [(cg.std_vector.template(cg.uint8), "x")], conf) - for conf in config.get(CONF_ON_MEMORY_TIMEOUT, []): + for conf in config.get(CONF_ON_READ_MEMORY_TIMEOUT, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_IDENTIFY_COMPLETE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(ModelData, "x")], conf) + + for conf in config.get(CONF_ON_IDENTIFY_TIMEOUT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await automation.build_automation(trigger, [], conf) @@ -197,6 +276,7 @@ def validate(config): { cv.GenerateID(): cv.use_id(TCBusComponent), cv.Optional(CONF_COMMAND): cv.templatable(cv.hex_uint32_t), + cv.Optional(CONF_IS_LONG): cv.templatable(cv.boolean), cv.Optional(CONF_TYPE): cv.templatable(cv.enum(COMMAND_TYPES, upper=False)), cv.Optional(CONF_ADDRESS, default="0"): cv.templatable(cv.hex_uint8_t), cv.Optional(CONF_PAYLOAD, default="0"): cv.templatable(cv.hex_uint32_t), @@ -218,6 +298,10 @@ async def tc_bus_send_to_code(config, action_id, template_args, args): command_template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint32) cg.add(var.set_command(command_template_)) + if CONF_IS_LONG in config: + is_long_template_ = await cg.templatable(config[CONF_IS_LONG], args, cg.bool_) + cg.add(var.set_is_long(is_long_template_)) + if CONF_TYPE in config: type_template_ = await cg.templatable(config[CONF_TYPE], args, COMMAND_TYPE) cg.add(var.set_type(type_template_)) @@ -309,4 +393,23 @@ async def tc_bus_read_memory_to_code(config, action_id, template_args, args): serial_number_template_ = await cg.templatable(config[CONF_SERIAL_NUMBER], args, cg.uint32) cg.add(var.set_serial_number(serial_number_template_)) + return var + +@automation.register_action( + "tc_bus.identify", + TCBusIdentifyAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(TCBusComponent), + cv.Optional(CONF_SERIAL_NUMBER, default=0): cv.templatable(cv.hex_uint32_t) + } + ), +) +async def tc_bus_request_version_to_code(config, action_id, template_args, args): + parent = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_args, parent) + + serial_number_template_ = await cg.templatable(config[CONF_SERIAL_NUMBER], args, cg.uint32) + cg.add(var.set_serial_number(serial_number_template_)) + return var \ No newline at end of file diff --git a/components/tc_bus/automation.h b/components/tc_bus/automation.h index d4e2192d..270b8ac4 100644 --- a/components/tc_bus/automation.h +++ b/components/tc_bus/automation.h @@ -14,6 +14,7 @@ namespace esphome public: TCBusSendAction(TCBusComponent *parent) : parent_(parent) {} TEMPLATABLE_VALUE(uint32_t, command) + TEMPLATABLE_VALUE(bool, is_long) TEMPLATABLE_VALUE(CommandType, type) TEMPLATABLE_VALUE(uint8_t, address) TEMPLATABLE_VALUE(uint32_t, payload) @@ -27,7 +28,14 @@ namespace esphome } else { - this->parent_->send_command(this->command_.value(x...)); + if(this->is_long_.value(x...) == false) + { + this->parent_->send_command(this->command_.value(x...)); + } + else + { + this->parent_->send_command(this->command_.value(x...), true); + } } } @@ -76,6 +84,18 @@ namespace esphome TCBusComponent *parent_; }; + template class TCBusIdentifyAction : public Action + { + public: + TCBusIdentifyAction(TCBusComponent *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(uint32_t, serial_number) + + void play(Ts... x) { this->parent_->request_version(this->serial_number_.value(x...)); } + + protected: + TCBusComponent *parent_; + }; + class ReceivedCommandTrigger : public Trigger { public: explicit ReceivedCommandTrigger(TCBusComponent *parent) { @@ -96,5 +116,18 @@ namespace esphome parent->add_read_memory_timeout_callback([this]() { this->trigger(); }); } }; + + class IdentifyTimeoutTrigger : public Trigger<> { + public: + explicit IdentifyTimeoutTrigger(TCBusComponent *parent) { + parent->add_identify_timeout_callback([this]() { this->trigger(); }); + } + }; + class IdentifyCompleteTrigger : public Trigger { + public: + explicit IdentifyCompleteTrigger(TCBusComponent *parent) { + parent->add_identify_complete_callback([this](const ModelData &value) { this->trigger(value); }); + } + }; } // namespace tc_bus } // namespace esphome \ No newline at end of file diff --git a/components/tc_bus/number/__init__.py b/components/tc_bus/number/__init__.py index 5c6d1ea9..d3c14139 100644 --- a/components/tc_bus/number/__init__.py +++ b/components/tc_bus/number/__init__.py @@ -10,11 +10,13 @@ from .. import CONF_TC_ID, TCBusComponent, tc_bus_ns SerialNumberNumber = tc_bus_ns.class_("SerialNumberNumber", number.Number, cg.Component) -VolumeHandsetNumber = tc_bus_ns.class_("VolumeHandsetNumber", number.Number, cg.Component) +VolumeHandsetDoorCallNumber = tc_bus_ns.class_("VolumeHandsetDoorCallNumber", number.Number, cg.Component) +VolumeHandsetInternalCallNumber = tc_bus_ns.class_("VolumeHandsetInternalCallNumber", number.Number, cg.Component) VolumeRingtoneNumber = tc_bus_ns.class_("VolumeRingtoneNumber", number.Number, cg.Component) CONF_SERIAL_NUMBER = "serial_number" -CONF_VOLUME_HANDSET = "volume_handset" +CONF_VOLUME_HANDSET_DOOR_CALL = "volume_handset_door_call" +CONF_VOLUME_HANDSET_INTERNAL_CALL = "volume_handset_internal_call" CONF_VOLUME_RINGTONE = "volume_ringtone" CONFIG_SCHEMA = cv.Schema( @@ -25,8 +27,13 @@ entity_category=ENTITY_CATEGORY_CONFIG, icon="mdi:numeric" ).extend({ cv.Optional(CONF_MODE, default="BOX"): cv.enum(NUMBER_MODES, upper=True), }), - cv.Optional(CONF_VOLUME_HANDSET): number.number_schema( - VolumeHandsetNumber, + cv.Optional(CONF_VOLUME_HANDSET_DOOR_CALL): number.number_schema( + VolumeHandsetDoorCallNumber, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:volume-high" + ), + cv.Optional(CONF_VOLUME_HANDSET_INTERNAL_CALL): number.number_schema( + VolumeHandsetInternalCallNumber, entity_category=ENTITY_CATEGORY_CONFIG, icon="mdi:volume-high" ), @@ -49,12 +56,19 @@ async def to_code(config): await cg.register_parented(n, config[CONF_TC_ID]) cg.add(tc_bus_component.set_serial_number_number(n)) - if volume_handset := config.get(CONF_VOLUME_HANDSET): + if volume_handset_door_call := config.get(CONF_VOLUME_HANDSET_DOOR_CALL): + n = await number.new_number( + volume_handset_door_call, min_value=0, max_value=7, step=1 + ) + await cg.register_parented(n, config[CONF_TC_ID]) + cg.add(tc_bus_component.set_volume_handset_door_call_number(n)) + + if volume_handset_internal_call := config.get(CONF_VOLUME_HANDSET_INTERNAL_CALL): n = await number.new_number( - volume_handset, min_value=0, max_value=7, step=1 + volume_handset_internal_call, min_value=0, max_value=7, step=1 ) await cg.register_parented(n, config[CONF_TC_ID]) - cg.add(tc_bus_component.set_volume_handset_number(n)) + cg.add(tc_bus_component.set_volume_handset_internal_call_number(n)) if volume_ringtone := config.get(CONF_VOLUME_RINGTONE): n = await number.new_number( diff --git a/components/tc_bus/number/volume_handset_number.cpp b/components/tc_bus/number/volume_handset_number.cpp index 8eda63ec..5ff36ac9 100644 --- a/components/tc_bus/number/volume_handset_number.cpp +++ b/components/tc_bus/number/volume_handset_number.cpp @@ -3,11 +3,18 @@ namespace esphome { namespace tc_bus { -void VolumeHandsetNumber::control(float value) +void VolumeHandsetDoorCallNumber::control(float value) { this->publish_state(value); - this->parent_->update_setting(SETTING_VOLUME_HANDSET, value, 0); + this->parent_->update_setting(SETTING_VOLUME_HANDSET_DOOR_CALL, value, 0); } +void VolumeHandsetInternalCallNumber::control(float value) +{ + this->publish_state(value); + this->parent_->update_setting(SETTING_VOLUME_HANDSET_INTERNAL_CALL, value, 0); +} + + } // namespace tc_bus } // namespace esphome \ No newline at end of file diff --git a/components/tc_bus/number/volume_handset_number.h b/components/tc_bus/number/volume_handset_number.h index 00b60da7..345db2a3 100644 --- a/components/tc_bus/number/volume_handset_number.h +++ b/components/tc_bus/number/volume_handset_number.h @@ -6,13 +6,22 @@ namespace esphome { namespace tc_bus { -class VolumeHandsetNumber : public number::Number, public Parented { +class VolumeHandsetDoorCallNumber : public number::Number, public Parented { public: - VolumeHandsetNumber() = default; + VolumeHandsetDoorCallNumber() = default; protected: void control(float value) override; }; + +class VolumeHandsetInternalCallNumber : public number::Number, public Parented { + public: + VolumeHandsetInternalCallNumber() = default; + + protected: + void control(float value) override; + }; + } // namespace tc_bus } // namespace esphome \ No newline at end of file diff --git a/components/tc_bus/protocol.cpp b/components/tc_bus/protocol.cpp index 1ae3af49..507d581f 100644 --- a/components/tc_bus/protocol.cpp +++ b/components/tc_bus/protocol.cpp @@ -8,175 +8,192 @@ namespace esphome { namespace tc_bus { - uint32_t buildCommand(CommandType type, uint8_t address, uint32_t payload, uint32_t serial_number) + CommandData buildCommand(CommandType type, uint8_t address, uint32_t payload, uint32_t serial_number) { - uint32_t command = 0; + CommandData data{}; + data.command = 0; + data.type = type; + data.address = address; + data.payload = payload; + data.serial_number = serial_number; + data.is_long = true; switch (type) { case COMMAND_TYPE_DOOR_CALL: - command |= (0 << 28); // 0 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command |= (1 << 7); // 8 - command |= (address & 0x3F); // 0 + data.command |= (0 << 28); // 0 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command |= (1 << 7); // 8 + data.command |= (address & 0x3F); // 0 break; case COMMAND_TYPE_INTERNAL_CALL: - command |= (0 << 28); // 0 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command &= ~(1 << 7); // 0 - command |= (address & 0x3F); // 0 + data.command |= (0 << 28); // 0 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command &= ~(1 << 7); // 0 + data.command |= (address & 0x3F); // 0 break; case COMMAND_TYPE_FLOOR_CALL: - command |= (1 << 28); // 1 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command |= 0x41; // 41 + data.command |= (1 << 28); // 1 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command |= 0x41; // 41 break; case COMMAND_TYPE_START_TALKING_DOOR_CALL: - command |= (3 << 28); // 3 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command |= (1 << 7); // 8 - command |= (address & 0x3F); // 0 + data.command |= (3 << 28); // 3 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command |= (1 << 7); // 8 + data.command |= (address & 0x3F); // 0 break; case COMMAND_TYPE_START_TALKING: - command |= (3 << 28); // 3 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command &= ~(1 << 7); // 0 - command |= (address & 0x3F); // 0 + data.command |= (3 << 28); // 3 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command &= ~(1 << 7); // 0 + data.command |= (address & 0x3F); // 0 break; case COMMAND_TYPE_STOP_TALKING_DOOR_CALL: - command |= (3 << 12); // 3 - command |= (1 << 7); // 08 - command |= (address & 0x3F); // 0 + data.is_long = false; + data.command |= (3 << 12); // 3 + data.command |= (1 << 7); // 08 + data.command |= (address & 0x3F); // 0 break; case COMMAND_TYPE_STOP_TALKING: - command |= (3 << 12); // 3 - command &= ~(1 << 7); // 00 - command |= (address & 0x3F); // 0 + data.is_long = false; + data.command |= (3 << 12); // 3 + data.command &= ~(1 << 7); // 00 + data.command |= (address & 0x3F); // 0 break; case COMMAND_TYPE_OPEN_DOOR: - command |= (1 << 12); // 1 - command |= (1 << 8); // 1 - command |= (address & 0x3F); // 00 + data.is_long = false; + data.command |= (1 << 12); // 1 + data.command |= (1 << 8); // 1 + data.command |= (address & 0x3F); // 00 break; - /*case COMMAND_TYPE_OPEN_DOOR_LONG: + case COMMAND_TYPE_OPEN_DOOR_LONG: if(serial_number == 0) { - command |= (1 << 12); // 1 - command |= (1 << 8); // 1 - command |= (address & 0x3F); // 00 + data.is_long = false; + data.command |= (1 << 12); // 1 + data.command |= (1 << 8); // 1 + data.command |= (address & 0x3F); // 00 } else { - command |= (1 << 28); // 1 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command |= (1 << 7); // 8 - command |= (address & 0x3F); // 0 + data.command |= (1 << 28); // 1 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command |= (1 << 7); // 8 + data.command |= (address & 0x3F); // 0 } - break;*/ + break; case COMMAND_TYPE_LIGHT: - command |= (1 << 12); // 1 - command |= (2 << 8); // 2 + data.is_long = false; + data.command |= (1 << 12); // 1 + data.command |= (2 << 8); // 2 break; case COMMAND_TYPE_CONTROL_FUNCTION: - command |= (6 << 28); // 6 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command |= (payload & 0xFF); // 08 + data.command |= (6 << 28); // 6 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command |= (payload & 0xFF); // 08 break; case COMMAND_TYPE_REQUEST_VERSION: - command |= (5 << 28); // 5 - command |= ((serial_number & 0xFFFFF) << 8); // C30BA - command |= (0xC0 & 0xFF); // C0 + data.command |= (5 << 28); // 5 + data.command |= ((serial_number & 0xFFFFF) << 8); // C30BA + data.command |= (0xC0 & 0xFF); // C0 break; case COMMAND_TYPE_RESET: - command |= (5 << 12); // 5 - command |= (1 << 8); // 100 + data.is_long = false; + data.command |= (5 << 12); // 5 + data.command |= (1 << 8); // 100 break; case COMMAND_TYPE_SEARCH_DOORMAN_DEVICES: - command = 0x7FFF; + data.is_long = false; + data.command = 0x7FFF; break; case COMMAND_TYPE_FOUND_DOORMAN_DEVICE: - command |= (0x7F << 24); // 7F - command |= payload & 0xFFFFFF; // MAC address + data.command |= (0x7F << 24); // 7F + data.command |= payload & 0xFFFFFF; // MAC address break; case COMMAND_TYPE_SELECT_DEVICE_GROUP: - command |= (5 << 12); // 5 - command |= (8 << 8); // 80 - command |= (payload & 0xFF); // 0 + data.is_long = false; + data.command |= (5 << 12); // 5 + data.command |= (8 << 8); // 80 + data.command |= (payload & 0xFF); // 0 break; case COMMAND_TYPE_SELECT_DEVICE_GROUP_RESET: - command |= (5 << 12); // 5 - command |= (9 << 8); // 90 - command |= (payload & 0xFF); // 0 + data.is_long = false; + data.command |= (5 << 12); // 5 + data.command |= (9 << 8); // 90 + data.command |= (payload & 0xFF); // 0 break; case COMMAND_TYPE_SEARCH_DEVICES: - command |= (5 << 12); // 5 - command |= (2 << 8); // 20 + data.is_long = false; + data.command |= (5 << 12); // 5 + data.command |= (2 << 8); // 20 break; case COMMAND_TYPE_PROGRAMMING_MODE: - command |= (5 << 12); // 5 - command |= (0 << 8); // 0 - command |= (4 << 4); // 4 - command |= (payload & 0xF); // 0 / 1 + data.is_long = false; + data.command |= (5 << 12); // 5 + data.command |= (0 << 8); // 0 + data.command |= (4 << 4); // 4 + data.command |= (payload & 0xF); // 0 / 1 break; case COMMAND_TYPE_READ_MEMORY_BLOCK: - command |= (8 << 12); // 8 - command |= (4 << 8); // 4 - command |= ((address * 4) & 0xFF); // 00 + data.is_long = false; + data.command |= (8 << 12); // 8 + data.command |= (4 << 8); // 4 + data.command |= ((data.address * 4) & 0xFF); // 00 break; case COMMAND_TYPE_WRITE_MEMORY: - command |= (8 << 28); // 8 - command |= (2 << 24); // 2 - command |= (address & 0xFF) << 16; // start address - command |= payload & 0xFFFF; // ABCD payload + data.command |= (8 << 28); // 8 + data.command |= (2 << 24); // 2 + data.command |= (address & 0xFF) << 16; // start address + data.command |= payload & 0xFFFF; // ABCD payload break; case COMMAND_TYPE_SELECT_MEMORY_PAGE: - command |= (8 << 28); // 8 - command |= (1 << 24); // 1 - command |= (address & 0xF) << 20; // page - command |= serial_number & 0xFFFFF; + data.command |= (8 << 28); // 8 + data.command |= (1 << 24); // 1 + data.command |= (address & 0xF) << 20; // page + data.command |= serial_number & 0xFFFFF; break; default: break; } - return command; + return data; } - CommandData parseCommand(uint32_t command) + CommandData parseCommand(uint32_t command, bool is_long) { CommandData data{}; data.command = command; data.type = COMMAND_TYPE_UNKNOWN; data.address = 0; data.payload = 0; + data.is_long = is_long; - // Convert to HEX and determine length data.command_hex = str_upper_case(format_hex(command)); - data.length = (command >> 16 == 0) ? 16 : 32; - if (data.length == 32) + if (is_long) { data.serial_number = (command >> 8) & 0xFFFFF; // Serial (from bits 8 to 23) @@ -253,6 +270,7 @@ namespace esphome { data.type = COMMAND_TYPE_FOUND_DOORMAN_DEVICE; data.payload = command & 0xFFFFFF; // MAC Address + data.serial_number = 0; } break; @@ -270,6 +288,7 @@ namespace esphome data.type = COMMAND_TYPE_WRITE_MEMORY; data.address = (command >> 16) & 0xFF; data.payload = command & 0xFFFF; + data.serial_number = 0; break; } break; @@ -277,6 +296,8 @@ namespace esphome } else { + data.command_hex = data.command_hex.substr(4); + // For 16-bit commands, work on the lower 16 bits uint8_t first = (command >> 12) & 0xF; uint8_t second = (command >> 8) & 0xF; @@ -388,10 +409,12 @@ namespace esphome std::transform(str.begin(), str.end(), str.begin(), ::toupper); if (str == "RINGTONE_FLOOR_CALL") return SETTING_RINGTONE_FLOOR_CALL; - if (str == "RINGTONE_DOOR_CALL") return SETTING_RINGTONE_DOOR_CALL; + if (str == "RINGTONE_ENTRANCE_DOOR_CALL") return SETTING_RINGTONE_ENTRANCE_DOOR_CALL; + if (str == "RINGTONE_SECOND_ENTRANCE_DOOR_CALL") return SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL; if (str == "RINGTONE_INTERNAL_CALL") return SETTING_RINGTONE_INTERNAL_CALL; if (str == "VOLUME_RINGTONE") return SETTING_VOLUME_RINGTONE; - if (str == "VOLUME_HANDSET") return SETTING_VOLUME_HANDSET; + if (str == "VOLUME_HANDSET_DOOR_CALL") return SETTING_VOLUME_HANDSET_DOOR_CALL; + if (str == "VOLUME_HANDSET_INTERNAL_CALL") return SETTING_VOLUME_HANDSET_INTERNAL_CALL; return SETTING_UNKNOWN; } @@ -401,10 +424,12 @@ namespace esphome switch (type) { case SETTING_RINGTONE_FLOOR_CALL: return "RINGTONE_FLOOR_CALL"; - case SETTING_RINGTONE_DOOR_CALL: return "RINGTONE_DOOR_CALL"; + case SETTING_RINGTONE_ENTRANCE_DOOR_CALL: return "RINGTONE_ENTRANCE_DOOR_CALL"; + case SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL: return "RINGTONE_SECOND_ENTRANCE_DOOR_CALL"; case SETTING_RINGTONE_INTERNAL_CALL: return "RINGTONE_INTERNAL_CALL"; case SETTING_VOLUME_RINGTONE: return "VOLUME_RINGTONE"; - case SETTING_VOLUME_HANDSET: return "VOLUME_HANDSET"; + case SETTING_VOLUME_HANDSET_DOOR_CALL: return "VOLUME_HANDSET_DOOR_CALL"; + case SETTING_VOLUME_HANDSET_INTERNAL_CALL: return "VOLUME_HANDSET_INTERNAL_CALL"; default: return "UNKNOWN"; } } @@ -425,6 +450,7 @@ namespace esphome if (str == "STOP_TALKING_DOOR_CALL") return COMMAND_TYPE_STOP_TALKING_DOOR_CALL; if (str == "STOP_TALKING") return COMMAND_TYPE_STOP_TALKING; if (str == "OPEN_DOOR") return COMMAND_TYPE_OPEN_DOOR; + if (str == "OPEN_DOOR_LONG") return COMMAND_TYPE_OPEN_DOOR_LONG; if (str == "LIGHT") return COMMAND_TYPE_LIGHT; if (str == "DOOR_OPENED") return COMMAND_TYPE_DOOR_OPENED; if (str == "DOOR_CLOSED") return COMMAND_TYPE_DOOR_CLOSED; @@ -461,6 +487,7 @@ namespace esphome case COMMAND_TYPE_STOP_TALKING_DOOR_CALL: return "STOP_TALKING_DOOR_CALL"; case COMMAND_TYPE_STOP_TALKING: return "STOP_TALKING"; case COMMAND_TYPE_OPEN_DOOR: return "OPEN_DOOR"; + case COMMAND_TYPE_OPEN_DOOR_LONG: return "OPEN_DOOR_LONG"; case COMMAND_TYPE_LIGHT: return "LIGHT"; case COMMAND_TYPE_DOOR_OPENED: return "DOOR_OPENED"; case COMMAND_TYPE_DOOR_CLOSED: return "DOOR_CLOSED"; @@ -482,20 +509,171 @@ namespace esphome } } - Model string_to_model(std::string str) + Model string_to_model(const std::string& str) { - if (str == "TCS ISH1030") return MODEL_TCS_ISH1030; - if (str == "TCS ISH3030") return MODEL_TCS_ISH3030; - if (str == "TCS ISH3230") return MODEL_TCS_ISH3230; - if (str == "TCS ISH3340") return MODEL_TCS_ISH3340; - if (str == "TCS ISW3030") return MODEL_TCS_ISW3030; - if (str == "TCS ISW3230") return MODEL_TCS_ISW3230; - if (str == "TCS ISW3340") return MODEL_TCS_ISW3340; - if (str == "TCS IVH3222") return MODEL_TCS_IVH3222; - if (str == "Koch TC50") return MODEL_KOCH_TC50; - if (str == "Koch TCH50") return MODEL_KOCH_TCH50; - if (str == "Koch TCH50P") return MODEL_KOCH_TCH50P; + if (str == "TCS ISW3030 / Koch TC50 / Scantron Stilux") return MODEL_ISW3030; + if (str == "TCS ISW3130 / Koch TC50P") return MODEL_ISW3130; + if (str == "TCS ISW3230 / Koch TC50 GFA") return MODEL_ISW3230; + if (str == "TCS ISW3330 / Koch TC50 BW") return MODEL_ISW3330; + if (str == "TCS ISW3340") return MODEL_ISW3340; + if (str == "TCS ISW5010 / Koch TC60") return MODEL_ISW5010; + if (str == "TCS ISW5020") return MODEL_ISW5020; + if (str == "TCS ISW5030") return MODEL_ISW5030; + if (str == "TCS ISW5031") return MODEL_ISW5031; + if (str == "TCS ISW5033") return MODEL_ISW5033; + if (str == "TCS IVW511x / Koch VTC60 / Scantron VIVO") return MODEL_IVW511X; + if (str == "TCS IVW521x / Koch VTC60/2D") return MODEL_IVW521X; + if (str == "TCS ISW6031") return MODEL_ISW6031; + if (str == "TCS ISW7030 / Koch TC70") return MODEL_ISW7030; + if (str == "TCS IVW7510 / Koch VTC70") return MODEL_IVW7510; + if (str == "TCS ISH7030 / Koch TCH70") return MODEL_ISH7030; + if (str == "TCS IVH7510 / Koch VTCH70") return MODEL_IVH7510; + if (str == "TCS ISW6010") return MODEL_ISW6010; + if (str == "TCS IVW6511") return MODEL_IVW6511; + if (str == "TCS ISWM7000") return MODEL_ISWM7000; + if (str == "TCS IVWM7000") return MODEL_IVWM7000; + if (str == "TCS ISW4100 / Koch TC31") return MODEL_ISW4100; + if (str == "TCS IMM2100 / Koch TCE31") return MODEL_IMM2100; + if (str == "TCS IVW2210 / Koch Ecoos") return MODEL_IVW2210; + if (str == "TCS IVW2211 / Koch Ecoos") return MODEL_IVW2211; + if (str == "TCS IVW2212 / Koch Ecoos / Scantron SLIM60T") return MODEL_IVW2212; + if (str == "TCS VTC42V2") return MODEL_VTC42V2; + if (str == "TCS TC40V2") return MODEL_TC40V2; + if (str == "TCS VTC40") return MODEL_VTC40; + if (str == "TCS TC40") return MODEL_TC40; + if (str == "TCS TC2000") return MODEL_TC2000; + if (str == "TCS TC20P") return MODEL_TC20P; + if (str == "TCS TC20F") return MODEL_TC20F; + if (str == "TCS ISH3022 / Koch TCH50P") return MODEL_ISH3022; + if (str == "TCS ISH3130 / Koch TCH50P / Scantron LuxPlus") return MODEL_ISH3130; + if (str == "TCS ISH3230 / Koch TCH50 GFA") return MODEL_ISH3230; + if (str == "TCS ISH3030 / Koch TCH50 / Scantron Lux2") return MODEL_ISH3030; + if (str == "TCS ISH1030 / Koch TTS25") return MODEL_ISH1030; + if (str == "TCS IMM1000 / Koch TCH30") return MODEL_IMM1000; + if (str == "TCS IMM1100 / Koch TCHE30") return MODEL_IMM1100; + if (str == "TCS IMM1300 / Koch VTCH30") return MODEL_IMM1300; + if (str == "TCS IMM1500") return MODEL_IMM1500; + if (str == "TCS IMM1310 / Koch VTCHE30") return MODEL_IMM1310; + if (str == "TCS IMM1110 / Koch TCHEE30") return MODEL_IMM1110; + if (str == "TCS IVH3222 / Koch VTCH50 / Scantron VLux") return MODEL_IVH3222; + if (str == "TCS IVH4222 / Koch VTCH50/2D") return MODEL_IVH4222; + if (str == "TCS IVW2220 / Koch Sky") return MODEL_IVW2220; + if (str == "TCS IVW2221 / Koch Sky R1.00") return MODEL_IVW2221; + if (str == "TCS IVW3011 / Koch Skyline Plus") return MODEL_IVW3011; + if (str == "TCS IVW3012 / Koch Skyline/Aldup") return MODEL_IVW3012; + if (str == "TCS VMH / Koch VMH") return MODEL_VMH; + if (str == "TCS VML / Koch VML") return MODEL_VML; + if (str == "TCS VMF / Koch VMF") return MODEL_VMF; + if (str == "Jung TKIS") return MODEL_TKIS; + if (str == "Jung TKISV") return MODEL_TKISV; + if (str == "TCS CAIXXXX / Koch CAIXXXX") return MODEL_CAIXXXX; + if (str == "TCS CAI2000 / Koch Carus") return MODEL_CAI2000; + if (str == "TCS ISW42X0") return MODEL_ISW42X0; + + return MODEL_NONE; + } + Model identifier_string_to_model(const std::string& model_key, const uint8_t& hw_version, const uint32_t& fw_version) + { + if (model_key == "000") return MODEL_ISH3030; + else if (model_key == "010") return MODEL_ISW3030; + else if (model_key == "001") return MODEL_ISH3230; + else if (model_key == "011") return MODEL_ISW3230; + else if (model_key == "003") return MODEL_ISH3130; + else if (model_key == "013") return MODEL_ISW3130; + else if (model_key == "015") return MODEL_ISW3330; + else if (model_key == "002") return MODEL_ISH3022; + else if (model_key == "017") return MODEL_ISW3340; + else if (model_key == "800") return MODEL_IVH3222; + else if (model_key == "900") return MODEL_IVH4222; + else if (model_key == "B00") return MODEL_IMM1000; + else if (model_key == "200") return MODEL_ISW4100; + else if (model_key == "201") return MODEL_IMM2100; + else if (model_key == "020" || model_key == "021" || model_key == "022" || model_key == "023" || + model_key == "024" || model_key == "025" || model_key == "026" || model_key == "027") + return MODEL_ISW5010; + + else if (model_key == "030" || model_key == "031" || model_key == "032") + return MODEL_IVW511X; + + else if (model_key == "03A" || model_key == "03B" || model_key == "03C" || model_key == "03D" || model_key == "03F") + return MODEL_IVW521X; + + else if (model_key == "028" || model_key == "02B" || model_key == "02F") + return MODEL_ISW5020; + + else if (model_key == "068" || model_key == "06F") + return MODEL_ISW5030; + + else if (model_key == "068" || model_key == "06F") + return MODEL_ISW5031; + + else if (model_key == "060") return MODEL_ISW5033; + + else if (model_key == "070" || model_key == "071" || model_key == "072" || model_key == "073" || + model_key == "074" || model_key == "075" || model_key == "076" || model_key == "077") + return MODEL_ISW6031; + + else if (model_key == "080" || model_key == "081" || model_key == "082" || model_key == "083" || + model_key == "084" || model_key == "085" || model_key == "086" || model_key == "087") + return MODEL_ISW7030; + + else if (model_key == "088" || model_key == "089" || model_key == "08A" || model_key == "08B" || + model_key == "08C" || model_key == "08D" || model_key == "08E" || model_key == "08F") + return MODEL_IVW7510; + + else if (model_key == "180" || model_key == "181" || model_key == "182" || model_key == "183" || + model_key == "184" || model_key == "185" || model_key == "186" || model_key == "187") + return MODEL_ISH7030; + + else if (model_key == "188" || model_key == "189" || model_key == "18A" || model_key == "18B" || + model_key == "18C" || model_key == "18D" || model_key == "18E" || model_key == "18F") + return MODEL_IVH7510; + + else if (model_key == "078" || model_key == "079" || model_key == "07A" || model_key == "07B" || + model_key == "07C" || model_key == "07D" || model_key == "07E" || model_key == "07F") + return MODEL_ISW6010; + + else if (model_key == "058" || model_key == "059" || model_key == "05A" || model_key == "05B" || + model_key == "05C" || model_key == "05D" || model_key == "05E" || model_key == "05F") + return MODEL_IVW6511; + + else if (model_key == "C70" || model_key == "C71" || model_key == "C72" || model_key == "C73" || + model_key == "C74" || model_key == "C75" || model_key == "C76" || model_key == "C77") + return MODEL_ISW7030; + + else if (model_key == "C90" || model_key == "C91" || model_key == "C92" || model_key == "C93" || + model_key == "C94" || model_key == "C95" || model_key == "C96" || model_key == "C97") + return MODEL_ISWM7000; + + else if (model_key == "C80" || model_key == "C81" || model_key == "C82" || model_key == "C83" || + model_key == "C84" || model_key == "C85" || model_key == "C86" || model_key == "C87") + return MODEL_IVWM7000; + + else if (model_key == "800" || model_key == "805") return MODEL_IVW2210; + else if (model_key == "807") return MODEL_IVW2211; + else if (model_key == "80C") return MODEL_IVW2212; + else if (model_key == "810") return MODEL_IVW2220; + else if (model_key == "815") return MODEL_IVW2221; + else if (model_key == "820") return MODEL_IVW3011; + else if (model_key == "830") return MODEL_IVW3012; + else if (model_key == "C01") return MODEL_VMH; + else if (model_key == "C00") return MODEL_VML; + else if (model_key == "C02") return MODEL_VMF; + else if (model_key == "400") return MODEL_ISW42X0; + else if (model_key == "410") return MODEL_TKIS; + else if (model_key == "420") return MODEL_TKISV; + else if (model_key == "208") return MODEL_CAIXXXX; + else if (model_key == "809") return MODEL_CAI2000; + else if (model_key == "280") { + if(fw_version >= 512) return MODEL_VTC42V2; + else return MODEL_VTC40; + } + else if (model_key == "281") { + if(fw_version >= 512) return MODEL_TC40V2; + else return MODEL_TC40; + } + return MODEL_NONE; } @@ -503,22 +681,517 @@ namespace esphome { switch (model) { - case MODEL_TCS_ISH1030: return "TCS ISH1030"; - case MODEL_TCS_ISH3030: return "TCS ISH3030"; - case MODEL_TCS_ISH3230: return "TCS ISH3230"; - case MODEL_TCS_ISH3340: return "TCS ISH3340"; - case MODEL_TCS_ISW3030: return "TCS ISW3030"; - case MODEL_TCS_ISW3230: return "TCS ISW3230"; - case MODEL_TCS_ISW3340: return "TCS ISW3340"; - case MODEL_TCS_IVH3222: return "TCS IVH3222"; - case MODEL_KOCH_TC50: return "Koch TC50"; - case MODEL_KOCH_TCH50: return "Koch TCH50"; - case MODEL_KOCH_TCH50P: return "Koch TCH50P"; + case MODEL_ISW3030: return "TCS ISW3030 / Koch TC50 / Scantron Stilux"; + case MODEL_ISW3130: return "TCS ISW3130 / Koch TC50P"; + case MODEL_ISW3230: return "TCS ISW3230 / Koch TC50 GFA"; + case MODEL_ISW3330: return "TCS ISW3330 / Koch TC50 BW"; + case MODEL_ISW3340: return "TCS ISW3340"; + case MODEL_ISW5010: return "TCS ISW5010 / Koch TC60"; + case MODEL_ISW5020: return "TCS ISW5020"; + case MODEL_ISW5030: return "TCS ISW5030"; + case MODEL_ISW5031: return "TCS ISW5031"; + case MODEL_ISW5033: return "TCS ISW5033"; + case MODEL_IVW511X: return "TCS IVW511x / Koch VTC60 / Scantron VIVO"; + case MODEL_IVW521X: return "TCS IVW521x / Koch VTC60/2D"; + case MODEL_ISW6031: return "TCS ISW6031"; + case MODEL_ISW7030: return "TCS ISW7030 / Koch TC70"; + case MODEL_IVW7510: return "TCS IVW7510 / Koch VTC70"; + case MODEL_ISH7030: return "TCS ISH7030 / Koch TCH70"; + case MODEL_IVH7510: return "TCS IVH7510 / Koch VTCH70"; + case MODEL_ISW6010: return "TCS ISW6010"; + case MODEL_IVW6511: return "TCS IVW6511"; + case MODEL_ISWM7000: return "TCS ISWM7000"; + case MODEL_IVWM7000: return "TCS IVWM7000"; + case MODEL_ISW4100: return "TCS ISW4100 / Koch TC31"; + case MODEL_IMM2100: return "TCS IMM2100 / Koch TCE31"; + case MODEL_IVW2210: return "TCS IVW2210 / Koch Ecoos"; + case MODEL_IVW2211: return "TCS IVW2211 / Koch Ecoos"; + case MODEL_IVW2212: return "TCS IVW2212 / Koch Ecoos / Scantron SLIM60T"; + case MODEL_VTC42V2: return "TCS VTC42V2"; + case MODEL_TC40V2: return "TCS TC40V2"; + case MODEL_VTC40: return "TCS VTC40"; + case MODEL_TC40: return "TCS TC40"; + case MODEL_TC2000: return "TCS TC2000"; + case MODEL_TC20P: return "TCS TC20P"; + case MODEL_TC20F: return "TCS TC20F"; + case MODEL_ISH3022: return "TCS ISH3022"; + case MODEL_ISH3130: return "TCS ISH3130 / Koch TCH50P / Scantron LuxPlus"; + case MODEL_ISH3230: return "TCS ISH3230 / Koch TCH50 GFA"; + case MODEL_ISH3030: return "TCS ISH3030 / Koch TCH50 / Scantron Lux2"; + case MODEL_ISH1030: return "TCS ISH1030 / Koch TTS25"; + case MODEL_IMM1000: return "TCS IMM1000 / Koch TCH30"; + case MODEL_IMM1100: return "TCS IMM1100 / Koch TCHE30"; + case MODEL_IMM1300: return "TCS IMM1300 / Koch VTCH30"; + case MODEL_IMM1500: return "TCS IMM1500"; + case MODEL_IMM1310: return "TCS IMM1310 / Koch VTCHE30"; + case MODEL_IMM1110: return "TCS IMM1110 / Koch TCHEE30"; + case MODEL_IVH3222: return "TCS IVH3222 / Koch VTCH50 / Scantron VLux"; + case MODEL_IVH4222: return "TCS IVH4222 / Koch VTCH50/2D"; + case MODEL_IVW2220: return "TCS IVW2220 / Koch Sky"; + case MODEL_IVW2221: return "TCS IVW2221 / Koch Sky R1.00"; + case MODEL_IVW3011: return "TCS IVW3011 / Koch Skyline Plus"; + case MODEL_IVW3012: return "TCS IVW3012 / Koch Skyline/Aldup"; + case MODEL_VMH: return "TCS VMH / Koch VMH"; + case MODEL_VML: return "TCS VML / Koch VML"; + case MODEL_VMF: return "TCS VMF / Koch VMF"; + case MODEL_TKIS: return "Jung TKIS"; + case MODEL_TKISV: return "Jung TKISV"; + case MODEL_CAIXXXX: return "TCS CAIXXXX / Koch CAIXXXX"; + case MODEL_CAI2000: return "TCS CAI2000 / Koch Carus"; + case MODEL_ISW42X0: return "TCS ISW42X0"; default: return "None"; } } - uint8_t ringtone_to_int(std::string str) + ModelData getModelData(Model model) + { + ModelData modelData; + modelData.model = model; + + switch (model) { + // Category 1 + case MODEL_ISW3030: /* TC50 */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW3130: /* TC50P */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW3230: /* TC50 GFA */ + modelData.category = 1; + modelData.memory_size = 40; + break; + case MODEL_ISW3330: /* TC50 BW */ + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_ISW3340: + modelData.category = 1; + modelData.memory_size = 128; + break; + case MODEL_ISW5010: /* TC60 */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW5020: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW5030: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW5031: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW5033: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IVW511X: /* VTC60 */ + modelData.category = 1; + modelData.memory_size = 48; + break; + case MODEL_IVW521X: /* VTC60/2D */ + modelData.category = 1; + modelData.memory_size = 48; + break; + case MODEL_ISW6031: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW7030: /* TC70 */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IVW7510: /* VTC70 */ + modelData.category = 1; + modelData.memory_size = 48; + break; + case MODEL_ISH7030: /* TCH70 */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IVH7510: /* VTCH70 */ + modelData.category = 1; + modelData.memory_size = 48; + break; + case MODEL_ISW6010: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IVW6511: + modelData.category = 1; + modelData.memory_size = 48; + break; + case MODEL_ISWM7000: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IVWM7000: + modelData.category = 1; + modelData.memory_size = 48; + break; + case MODEL_ISW4100: /* TC31 */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IMM2100: /* TCE31 */ + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_IVW2210: /* Ecoos */ + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_IVW2211: /* Ecoos */ + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_IVW2212: /* Ecoos */ + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_VTC42V2: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_TC40V2: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_VTC40: + modelData.category = 1; + modelData.memory_size = 40; + break; + case MODEL_TC40: + modelData.category = 1; + modelData.memory_size = 40; + break; + case MODEL_TC2000: + modelData.category = 1; + modelData.memory_size = 16; + break; + case MODEL_TC20P: + modelData.category = 1; + modelData.memory_size = 16; + break; + case MODEL_TC20F: + modelData.category = 1; + modelData.memory_size = 16; + break; + case MODEL_IVW2220: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_IVW2221: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_IVW3011: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_IVW3012: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_TKIS: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_TKISV: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_CAI2000: + modelData.category = 1; + modelData.memory_size = 64; + break; + case MODEL_CAIXXXX: + modelData.category = 1; + modelData.memory_size = 32; + break; + case MODEL_ISW42X0: + modelData.category = 1; + modelData.memory_size = 40; + break; + + // Category 0 + case MODEL_ISH3022: /* TCH50P */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_ISH3130: /* TCH50P */ + modelData.category = 0; + modelData.memory_size = 40; + break; + case MODEL_ISH3230: /* TCH50 GFA */ + modelData.category = 0; + modelData.memory_size = 40; + break; + case MODEL_ISH3030: /* TCH50 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_ISH1030: /* TTS25 */ + modelData.category = 0; + modelData.memory_size = 16; + break; + case MODEL_IMM1000: /* TCH30 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IMM1100: /* TCHE30 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IMM1300: /* VTCH30 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IMM1500: + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IMM1310: /* VTCHE30 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IMM1110: /* TCHEE30 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IVH3222: /* VTCH50 */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_IVH4222: /* VTCH50/2D */ + modelData.category = 0; + modelData.memory_size = 32; + break; + case MODEL_VMH: + modelData.category = 0; + modelData.memory_size = 24; + break; + case MODEL_VML: + modelData.category = 0; + modelData.memory_size = 24; + break; + case MODEL_VMF: + modelData.category = 0; + modelData.memory_size = 24; + break; + + default: + break; + } + + return modelData; + } + + SettingCellData getSettingCellData(SettingType setting, Model model) + { + SettingCellData data; + + switch (model) { + case MODEL_ISH3030: + case MODEL_ISH3230: + case MODEL_ISW3030: + case MODEL_ISW3230: + case MODEL_ISW3340: + case MODEL_ISW3130: + case MODEL_ISW3330: + case MODEL_IVH4222: + switch (setting) + { + case SETTING_RINGTONE_ENTRANCE_DOOR_CALL: + data.index = 3; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_INTERNAL_CALL: + data.index = 6; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_FLOOR_CALL: + data.index = 9; + data.left_nibble = true; + break; + + case SETTING_VOLUME_RINGTONE: + data.index = 20; + data.left_nibble = false; + break; + + case SETTING_VOLUME_HANDSET_DOOR_CALL: + data.index = 21; + data.left_nibble = false; + break; + + default: break; + } + break; + + case MODEL_ISH1030: + case MODEL_IVH3222: + switch (setting) + { + case SETTING_RINGTONE_ENTRANCE_DOOR_CALL: + data.index = 3; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_INTERNAL_CALL: + data.index = 6; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_FLOOR_CALL: + data.index = 9; + data.left_nibble = true; + break; + + default: break; + } + break; + + case MODEL_IVW511X: + case MODEL_IVW521X: + // TASTA Video + switch (setting) + { + case SETTING_RINGTONE_ENTRANCE_DOOR_CALL: + data.index = 3; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_INTERNAL_CALL: + data.index = 6; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_FLOOR_CALL: + data.index = 9; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL: + data.index = 12; + data.left_nibble = true; + break; + + // Values: 0,2,4,6 + case SETTING_VOLUME_RINGTONE: + data.index = 20; + data.left_nibble = false; + break; + + // Values: 0,2,4,7 + case SETTING_VOLUME_HANDSET_DOOR_CALL: + data.index = 21; + data.left_nibble = false; + break; + + // Values: 0,2,4,7 + case SETTING_VOLUME_HANDSET_INTERNAL_CALL: + data.index = 21; + data.left_nibble = true; + break; + + default: break; + } + break; + + case MODEL_ISW5010: + case MODEL_ISW5020: + case MODEL_ISW5030: + case MODEL_ISW5031: + case MODEL_ISW5033: + // TASTA Audio + switch (setting) + { + case SETTING_RINGTONE_ENTRANCE_DOOR_CALL: + data.index = 3; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_INTERNAL_CALL: + data.index = 6; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_FLOOR_CALL: + data.index = 9; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL: + data.index = 12; + data.left_nibble = true; + break; + + // Values: 0,2,4,6 + case SETTING_VOLUME_RINGTONE: + data.index = 20; + data.left_nibble = false; + break; + + // Values: 0,2,4,7 + case SETTING_VOLUME_HANDSET_DOOR_CALL: + data.index = 21; + data.left_nibble = false; + break; + + // Values: 0,2,4,7 + case SETTING_VOLUME_HANDSET_INTERNAL_CALL: + data.index = 21; + data.left_nibble = true; + break; + + default: break; + } + break; + + case MODEL_IVW2210: + case MODEL_IVW2211: + case MODEL_IVW2212: + // ECOOS + switch (setting) + { + /*case SETTING_RINGTONE_ENTRANCE_DOOR_CALL: + data.index = 3; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_INTERNAL_CALL: + data.index = 6; + data.left_nibble = true; + break; + + case SETTING_RINGTONE_FLOOR_CALL: + data.index = 9; + data.left_nibble = true; + break; + + case SETTING_VOLUME_RINGTONE: + data.index = 20; + data.left_nibble = false; + break;*/ + + default: break; + } + break; + default: break; + } + + return data; + } + + uint8_t ringtone_to_int(const std::string& str) { if(str == "Ringtone 1") return 0; if(str == "Ringtone 2") return 1; diff --git a/components/tc_bus/protocol.h b/components/tc_bus/protocol.h index a3ed5fe8..4f097fe7 100644 --- a/components/tc_bus/protocol.h +++ b/components/tc_bus/protocol.h @@ -8,26 +8,77 @@ namespace esphome { enum Model { MODEL_NONE, - MODEL_TCS_ISH1030, - MODEL_TCS_ISH3030, - MODEL_TCS_ISH3230, - MODEL_TCS_ISH3340, - MODEL_TCS_ISW3030, - MODEL_TCS_ISW3230, - MODEL_TCS_ISW3340, - MODEL_TCS_IVH3222, - MODEL_KOCH_TC50, - MODEL_KOCH_TCH50, - MODEL_KOCH_TCH50P + MODEL_ISW3030, + MODEL_ISW3130, + MODEL_ISW3230, + MODEL_ISW3330, + MODEL_ISW3340, + MODEL_ISW5010, + MODEL_ISW5020, + MODEL_ISW5030, + MODEL_ISW5031, + MODEL_ISW5033, + MODEL_IVW511X, + MODEL_IVW521X, + MODEL_ISW6031, + MODEL_ISW7030, + MODEL_IVW7510, + MODEL_ISH7030, + MODEL_IVH7510, + MODEL_ISW6010, + MODEL_IVW6511, + MODEL_ISWM7000, + MODEL_IVWM7000, + MODEL_ISW4100, + MODEL_IMM2100, + MODEL_IVW2210, + MODEL_IVW2211, + MODEL_IVW2212, + MODEL_VTC42V2, + MODEL_TC40V2, + MODEL_VTC40, + MODEL_TC40, + MODEL_TC2000, + MODEL_TC20P, + MODEL_TC20F, + MODEL_ISH3340, + MODEL_ISH3022, + MODEL_ISH3130, + MODEL_ISW3022, + MODEL_ISH3230, + MODEL_ISH3030, + MODEL_ISH1030, + MODEL_IMM1000, + MODEL_IMM1100, + MODEL_IMM1300, + MODEL_IMM1500, + MODEL_IMM1310, + MODEL_IMM1110, + MODEL_IVH3222, + MODEL_IVH4222, + MODEL_IVW2220, + MODEL_IVW2221, + MODEL_IVW3011, + MODEL_IVW3012, + MODEL_VMH, + MODEL_VML, + MODEL_VMF, + MODEL_TKIS, + MODEL_TKISV, + MODEL_CAIXXXX, + MODEL_CAI2000, + MODEL_ISW42X0 }; enum SettingType { SETTING_UNKNOWN, SETTING_RINGTONE_FLOOR_CALL, - SETTING_RINGTONE_DOOR_CALL, + SETTING_RINGTONE_ENTRANCE_DOOR_CALL, + SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL, SETTING_RINGTONE_INTERNAL_CALL, SETTING_VOLUME_RINGTONE, - SETTING_VOLUME_HANDSET + SETTING_VOLUME_HANDSET_DOOR_CALL, + SETTING_VOLUME_HANDSET_INTERNAL_CALL }; enum CommandType { @@ -43,6 +94,7 @@ namespace esphome COMMAND_TYPE_STOP_TALKING_DOOR_CALL, COMMAND_TYPE_STOP_TALKING, COMMAND_TYPE_OPEN_DOOR, + COMMAND_TYPE_OPEN_DOOR_LONG, COMMAND_TYPE_LIGHT, COMMAND_TYPE_DOOR_OPENED, COMMAND_TYPE_DOOR_CLOSED, @@ -69,7 +121,7 @@ namespace esphome uint8_t address; uint32_t serial_number; uint32_t payload; - uint8_t length; + bool is_long; }; struct SettingCellData { @@ -77,9 +129,19 @@ namespace esphome bool left_nibble = false; }; + struct ModelData { + Model model = MODEL_NONE; + uint32_t firmware_version = 0; + uint8_t firmware_major = 0; + uint8_t firmware_minor = 0; + uint8_t firmware_patch = 0; + uint8_t hardware_version = 0; + uint8_t category = 0; + uint8_t memory_size = 0; + }; - uint32_t buildCommand(CommandType type, uint8_t address = 0, uint32_t payload = 0, uint32_t serial_number = 0); - CommandData parseCommand(uint32_t command); + CommandData buildCommand(CommandType type, uint8_t address = 0, uint32_t payload = 0, uint32_t serial_number = 0); + CommandData parseCommand(uint32_t command, bool is_long = true); const char* command_type_to_string(CommandType type); CommandType string_to_command_type(std::string str); @@ -87,10 +149,14 @@ namespace esphome const char* setting_type_to_string(SettingType type); SettingType string_to_setting_type(std::string str); - const char* model_to_string(Model model); - Model string_to_model(std::string str); + SettingCellData getSettingCellData(SettingType setting, Model model); + ModelData getModelData(Model model = MODEL_NONE); + + const char* model_to_string(Model model = MODEL_NONE); + Model string_to_model(const std::string& str); + Model identifier_string_to_model(const std::string& model_key, const uint8_t& hw_version = 0, const uint32_t& fw_version = 0); - uint8_t ringtone_to_int(std::string str); + uint8_t ringtone_to_int(const std::string& str); std::string int_to_ringtone(uint8_t ringtone); } // namespace tc_bus diff --git a/components/tc_bus/select/__init__.py b/components/tc_bus/select/__init__.py index 1abfbd16..ae747781 100644 --- a/components/tc_bus/select/__init__.py +++ b/components/tc_bus/select/__init__.py @@ -8,12 +8,14 @@ from .. import CONF_TC_ID, CONF_MODELS, CONF_RINGTONES, TCBusComponent, tc_bus_ns ModelSelect = tc_bus_ns.class_("ModelSelect", select.Select, cg.Component) -RingtoneDoorCallSelect = tc_bus_ns.class_("RingtoneDoorCallSelect", select.Select, cg.Component) +RingtoneEntranceDoorCallSelect = tc_bus_ns.class_("RingtoneEntranceDoorCallSelect", select.Select, cg.Component) +RingtoneSecondEntranceDoorCallSelect = tc_bus_ns.class_("RingtoneSecondEntranceDoorCallSelect", select.Select, cg.Component) RingtoneFloorCallSelect = tc_bus_ns.class_("RingtoneFloorCallSelect", select.Select, cg.Component) RingtoneInternalCallSelect = tc_bus_ns.class_("RingtoneInternalCallSelect", select.Select, cg.Component) CONF_MODEL = "model" -CONF_RINGTONE_DOOR_CALL = "ringtone_door_call" +CONF_RINGTONE_ENTRANCE_DOOR_CALL = "ringtone_entrance_door_call" +CONF_RINGTONE_SECOND_ENTRANCE_DOOR_CALL = "ringtone_second_entrance_door_call" CONF_RINGTONE_FLOOR_CALL = "ringtone_floor_call" CONF_RINGTONE_INTERNAL_CALL = "ringtone_internal_call" @@ -25,8 +27,13 @@ entity_category=ENTITY_CATEGORY_CONFIG, icon="mdi:doorbell-video" ), - cv.Optional(CONF_RINGTONE_DOOR_CALL): select.select_schema( - RingtoneDoorCallSelect, + cv.Optional(CONF_RINGTONE_ENTRANCE_DOOR_CALL): select.select_schema( + RingtoneEntranceDoorCallSelect, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:music" + ), + cv.Optional(CONF_RINGTONE_SECOND_ENTRANCE_DOOR_CALL): select.select_schema( + RingtoneSecondEntranceDoorCallSelect, entity_category=ENTITY_CATEGORY_CONFIG, icon="mdi:music" ), @@ -55,13 +62,21 @@ async def to_code(config): await cg.register_parented(sel, config[CONF_TC_ID]) cg.add(tc_bus_component.set_model_select(sel)) - if ringtone_door_call := config.get(CONF_RINGTONE_DOOR_CALL): + if ringtone_entrance_door_call := config.get(CONF_RINGTONE_ENTRANCE_DOOR_CALL): + sel = await select.new_select( + ringtone_entrance_door_call, + options=[CONF_RINGTONES], + ) + await cg.register_parented(sel, config[CONF_TC_ID]) + cg.add(tc_bus_component.set_ringtone_entrance_door_call_select(sel)) + + if ringtone_second_entrance_door_call := config.get(CONF_RINGTONE_SECOND_ENTRANCE_DOOR_CALL): sel = await select.new_select( - ringtone_door_call, + ringtone_second_entrance_door_call, options=[CONF_RINGTONES], ) await cg.register_parented(sel, config[CONF_TC_ID]) - cg.add(tc_bus_component.set_ringtone_door_call_select(sel)) + cg.add(tc_bus_component.set_ringtone_second_entrance_door_call_select(sel)) if ringtone_floor_call := config.get(CONF_RINGTONE_FLOOR_CALL): sel = await select.new_select( diff --git a/components/tc_bus/select/ringtone_door_call_select.cpp b/components/tc_bus/select/ringtone_door_call_select.cpp index a02c5106..09a88b45 100644 --- a/components/tc_bus/select/ringtone_door_call_select.cpp +++ b/components/tc_bus/select/ringtone_door_call_select.cpp @@ -4,11 +4,18 @@ namespace esphome { namespace tc_bus { -void RingtoneDoorCallSelect::control(const std::string &value) +void RingtoneEntranceDoorCallSelect::control(const std::string &value) { this->publish_state(value); uint8_t ringtone = ringtone_to_int(value); - this->parent_->update_setting(SETTING_RINGTONE_DOOR_CALL, ringtone, 0); + this->parent_->update_setting(SETTING_RINGTONE_ENTRANCE_DOOR_CALL, ringtone, 0); +} + +void RingtoneSecondEntranceDoorCallSelect::control(const std::string &value) +{ + this->publish_state(value); + uint8_t ringtone = ringtone_to_int(value); + this->parent_->update_setting(SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL, ringtone, 0); } } // namespace tc_bus diff --git a/components/tc_bus/select/ringtone_door_call_select.h b/components/tc_bus/select/ringtone_door_call_select.h index 4ff7e911..13930d06 100644 --- a/components/tc_bus/select/ringtone_door_call_select.h +++ b/components/tc_bus/select/ringtone_door_call_select.h @@ -6,9 +6,17 @@ namespace esphome { namespace tc_bus { -class RingtoneDoorCallSelect : public select::Select, public Parented { +class RingtoneEntranceDoorCallSelect : public select::Select, public Parented { public: - RingtoneDoorCallSelect() = default; + RingtoneEntranceDoorCallSelect() = default; + + protected: + void control(const std::string &value) override; +}; + +class RingtoneSecondEntranceDoorCallSelect : public select::Select, public Parented { + public: + RingtoneSecondEntranceDoorCallSelect() = default; protected: void control(const std::string &value) override; diff --git a/components/tc_bus/tc_bus.cpp b/components/tc_bus/tc_bus.cpp index cb7fe2a6..ce8d528f 100644 --- a/components/tc_bus/tc_bus.cpp +++ b/components/tc_bus/tc_bus.cpp @@ -42,7 +42,11 @@ namespace esphome static const uint8_t TCS_MSG_START_MS = 6; // a new message static const uint8_t TCS_ONE_BIT_MS = 4; // a 1-bit is 4ms long static const uint8_t TCS_ZERO_BIT_MS = 2; // a 0-bit is 2ms long + static const uint8_t TCS_SEND_WAIT_DURATION = 50; + static const uint8_t TCS_SEND_WAIT_TIMEOUT_MS = 100; + static const uint8_t TCS_SEND_MIN_DELAY_MS = 20; + static const uint8_t TCS_SEND_MAX_DELAY_MS = 50; TCBusComponent *global_tc_bus = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) @@ -54,8 +58,7 @@ namespace esphome TCBusSettings recovered; - if (!this->pref_.load(&recovered)) - { + if (!this->pref_.load(&recovered)) { recovered = {MODEL_NONE, 0}; } @@ -63,12 +66,14 @@ namespace esphome this->serial_number_ = recovered.serial_number; #ifdef USE_SELECT - if (this->model_select_ != nullptr) + if (this->model_select_ != nullptr) { this->model_select_->publish_state(model_to_string(this->model_)); + } #endif #ifdef USE_NUMBER - if (this->serial_number_number_ != nullptr) + if (this->serial_number_number_ != nullptr) { this->serial_number_number_->publish_state(this->serial_number_); + } #endif #if defined(USE_ESP_IDF) || (defined(USE_ARDUINO) && defined(ESP32)) @@ -82,14 +87,12 @@ namespace esphome ver[1] = value >> 8; ver[2] = value >> 16; - if(ver[0] > 0) - { + if(ver[0] > 0) { ESP_LOGI(TAG, "Doorman Hardware detected: V%i.%i.%i", ver[0], ver[1], ver[2]); this->hardware_version_str_ = "Doorman-S3 " + std::to_string(ver[0]) + "." + std::to_string(ver[1]) + "." + std::to_string(ver[2]); // Override GPIO - if(ver[0] == 1 && (ver[1] == 3 || ver[1] == 4 || ver[1] == 5)) - { + if(ver[0] == 1 && (ver[1] == 3 || ver[1] == 4 || ver[1] == 5)) { esp32::ESP32InternalGPIOPin *gpio_pin_rx_; gpio_pin_rx_ = new(esp32::ESP32InternalGPIOPin); gpio_pin_rx_->set_pin(static_cast(9)); @@ -111,8 +114,9 @@ namespace esphome #endif #ifdef USE_TEXT_SENSOR - if (this->hardware_version_text_sensor_ != nullptr) + if (this->hardware_version_text_sensor_ != nullptr) { this->hardware_version_text_sensor_->publish_state(this->hardware_version_str_); + } #endif this->rx_pin_->setup(); @@ -126,16 +130,66 @@ namespace esphome // Reset Sensors #ifdef USE_TEXT_SENSOR - if (this->bus_command_text_sensor_ != nullptr) + if (this->bus_command_text_sensor_ != nullptr) { this->bus_command_text_sensor_->publish_state(""); + } #endif #ifdef USE_BINARY_SENSOR - for (auto &listener : listeners_) - { + for (auto &listener : listeners_) { listener->turn_off(&listener->timer_); } #endif + + #ifdef TC_DEBUG_TIMING + this->set_interval("timing_debug", 5000, [this] { + this->rx_pin_->detach_interrupt(); + uint8_t index = this->store_.debug_buffer_index; + this->store_.debug_buffer_index = 0; + this->rx_pin_->attach_interrupt(TCBusComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); + + if(index > 0) + { + ESP_LOGI(TAG, "Timings:"); + for (uint8_t i = 0; i < index; i++) { + std::string timing_type = "unknown"; + uint32_t timing = this->store_.debug_buffer[i]; + if(timing == 0) + { + timing_type = "end"; + } + else if(timing == 1) + { + timing_type = "start"; + } + else if(timing == 99) + { + timing_type = "crc error"; + } + else if(timing >= 1000 && timing <= 2999) + { + timing_type = "0"; + } + else if(timing >= 3000 && timing <= 4999) + { + timing_type = "1"; + } + else if(timing >= 5000 && timing <= 6999) + { + timing_type = "start sequence"; + } + else if(timing >= 7000) + { + timing_type = "reset"; + } + + ESP_LOGI(TAG, "Microseconds: %i (%s)", timing, timing_type.c_str()); + } + } else { + ESP_LOGI(TAG, "Timings: No data available"); + } + }); + #endif } void TCBusComponent::save_settings() @@ -145,8 +199,7 @@ namespace esphome this->serial_number_ }; - if (!this->pref_.save(&settings)) - { + if (!this->pref_.save(&settings)) { ESP_LOGW(TAG, "Failed to save settings"); } } @@ -157,16 +210,17 @@ namespace esphome LOG_PIN(" Pin RX: ", this->rx_pin_); LOG_PIN(" Pin TX: ", this->tx_pin_); - if (strcmp(this->event_, "esphome.none") != 0) - { + if (strcmp(this->event_, "esphome.none") != 0) { ESP_LOGCONFIG(TAG, " Event: %s", this->event_); - } - else - { - ESP_LOGCONFIG(TAG, " Event: disabled"); + } else { + ESP_LOGCONFIG(TAG, " Event: Disabled"); } ESP_LOGCONFIG(TAG, " Hardware: %s", this->hardware_version_str_.c_str()); + + #ifdef TC_DEBUG_TIMING + ESP_LOGCONFIG(TAG, " Debug Timings: Enabled"); + #endif #ifdef USE_TEXT_SENSOR ESP_LOGCONFIG(TAG, "Text Sensors:"); @@ -180,60 +234,37 @@ namespace esphome #ifdef USE_BINARY_SENSOR // Turn off binary sensor after ... milliseconds uint32_t now_millis = millis(); - for (auto &listener : listeners_) - { - if (listener->timer_ && now_millis > listener->timer_) - { + for (auto &listener : listeners_) { + if (listener->timer_ && now_millis > listener->timer_) { listener->turn_off(&listener->timer_); } } #endif - // Cancel Reading memory - if(reading_memory_) - { - uint32_t now_millis = millis(); - - if(now_millis - reading_memory_timer_ >= 5000) - { - reading_memory_ = false; - reading_memory_count_ = 0; - reading_memory_timer_ = 0; - - ESP_LOGE(TAG, "Reading memory timed out!"); - this->read_memory_timeout_callback_.call(); - } - } - auto &s = this->store_; - if(s.s_cmdReady) - { - if(reading_memory_) - { + if(s.command_is_ready) { + if(reading_memory_) { ESP_LOGD(TAG, "Received 4 memory addresses %i to %i", (reading_memory_count_ * 4), (reading_memory_count_ * 4) + 4); // Save Data to memory Store - memory_buffer_.push_back((s.s_cmd >> 24) & 0xFF); - memory_buffer_.push_back((s.s_cmd >> 16) & 0xFF); - memory_buffer_.push_back((s.s_cmd >> 8) & 0xFF); - memory_buffer_.push_back(s.s_cmd & 0xFF); + memory_buffer_.push_back((s.command >> 24) & 0xFF); + memory_buffer_.push_back((s.command >> 16) & 0xFF); + memory_buffer_.push_back((s.command >> 8) & 0xFF); + memory_buffer_.push_back(s.command & 0xFF); // Next 4 Data Blocks reading_memory_count_++; - if(reading_memory_count_ == 6) - { + // Memory reading complete + if(reading_memory_count_ == reading_memory_max_) { // Turn off + this->cancel_timeout("wait_for_memory_reading"); reading_memory_ = false; - reading_memory_timer_ = 0; this->publish_settings(); - this->read_memory_complete_callback_.call(memory_buffer_); - } - else - { + } else { delay(20); // Request Data Blocks @@ -241,39 +272,98 @@ namespace esphome send_command(COMMAND_TYPE_READ_MEMORY_BLOCK, reading_memory_count_); } } - else - { - ESP_LOGD(TAG, "Received command %08X", s.s_cmd); - this->publish_command(s.s_cmd, true); + else if(identify_model_) { + std::string hex_result = str_upper_case(format_hex(s.command)); + if(hex_result.substr(4, 1) == "D") { + identify_model_ = false; + this->cancel_timeout("wait_for_identification"); + + ModelData device; + + device.category = 0; + device.memory_size = 0; + + // FW Version + device.firmware_version = std::stoi(hex_result.substr(5, 3)); + device.firmware_major = std::stoi(hex_result.substr(5, 1), nullptr, 16); + device.firmware_minor = std::stoi(hex_result.substr(6, 1), nullptr, 16); + device.firmware_patch = std::stoi(hex_result.substr(7, 1), nullptr, 16); + + // HW Version + device.hardware_version = std::stoi(hex_result.substr(0, 1)); + device.model = identifier_string_to_model(hex_result.substr(1, 3), device.hardware_version, device.firmware_version); + const char* hw_model = model_to_string(device.model); + + ESP_LOGD(TAG, "Identified Hardware: %s (version %i), Firmware: %i.%i.%i - %i", + hw_model, device.hardware_version, device.firmware_major, device.firmware_minor, device.firmware_patch, device.firmware_version); + + // Update Model + if(device.model != MODEL_NONE && device.model != this->model_) { + this->model_ = device.model; + #ifdef USE_SELECT + if (this->model_select_ != nullptr) { + this->model_select_->publish_state(hw_model); + } + #endif + this->save_settings(); + } + + this->identify_complete_callback_.call(device); + } else { + ESP_LOGE(TAG, "Invalid indentification response!"); + } + } + else { + if(s.command_is_long) { + ESP_LOGD(TAG, "Received 32-Bit command %08X", s.command); + } else { + ESP_LOGD(TAG, "Received 16-Bit command %04X", s.command); + } + this->publish_command(s.command, s.command_is_long, true); } - s.s_cmdReady = false; - s.s_cmd = 0; + s.command_is_ready = false; + s.command_is_long = false; + s.command = 0; } } void TCBusComponent::publish_settings() { - ESP_LOGD(TAG, "Handset volume %i", get_setting(SETTING_VOLUME_HANDSET)); - ESP_LOGD(TAG, "Ringtone volume %i", get_setting(SETTING_VOLUME_RINGTONE)); - ESP_LOGD(TAG, "Door Call Ringtone %i", get_setting(SETTING_RINGTONE_DOOR_CALL)); - ESP_LOGD(TAG, "Floor Call Ringtone %i", get_setting(SETTING_RINGTONE_FLOOR_CALL)); - ESP_LOGD(TAG, "Internal Call Ringtone %i", get_setting(SETTING_RINGTONE_INTERNAL_CALL)); + ESP_LOGD(TAG, "Handset volume (Door Call): %i", get_setting(SETTING_VOLUME_HANDSET_DOOR_CALL)); + ESP_LOGD(TAG, "Handset volume (Internal Call): %i", get_setting(SETTING_VOLUME_HANDSET_INTERNAL_CALL)); + ESP_LOGD(TAG, "Ringtone volume: %i", get_setting(SETTING_VOLUME_RINGTONE)); + + ESP_LOGD(TAG, "Entrance Door Call Ringtone: %i", get_setting(SETTING_RINGTONE_ENTRANCE_DOOR_CALL)); + ESP_LOGD(TAG, "Second Entrance Door Call Ringtone: %i", get_setting(SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL)); + ESP_LOGD(TAG, "Floor Call Ringtone: %i", get_setting(SETTING_RINGTONE_FLOOR_CALL)); + ESP_LOGD(TAG, "Internal Call Ringtone: %i", get_setting(SETTING_RINGTONE_INTERNAL_CALL)); #ifdef USE_SELECT - if (this->ringtone_door_call_select_ != nullptr) - this->ringtone_door_call_select_->publish_state(int_to_ringtone(get_setting(SETTING_RINGTONE_DOOR_CALL))); - if (this->ringtone_floor_call_select_ != nullptr) + if (this->ringtone_entrance_door_call_select_) { + this->ringtone_entrance_door_call_select_->publish_state(int_to_ringtone(get_setting(SETTING_RINGTONE_ENTRANCE_DOOR_CALL))); + } + if (this->ringtone_second_entrance_door_call_select_) { + this->ringtone_second_entrance_door_call_select_->publish_state(int_to_ringtone(get_setting(SETTING_RINGTONE_SECOND_ENTRANCE_DOOR_CALL))); + } + if (this->ringtone_floor_call_select_) { this->ringtone_floor_call_select_->publish_state(int_to_ringtone(get_setting(SETTING_RINGTONE_FLOOR_CALL))); - if (this->ringtone_internal_call_select_ != nullptr) + } + if (this->ringtone_internal_call_select_) { this->ringtone_internal_call_select_->publish_state(int_to_ringtone(get_setting(SETTING_RINGTONE_INTERNAL_CALL))); + } #endif #ifdef USE_NUMBER - if (this->volume_handset_number_ != nullptr) - this->volume_handset_number_->publish_state(get_setting(SETTING_VOLUME_HANDSET)); - if (this->volume_ringtone_number_ != nullptr) + if (this->volume_handset_door_call_number_) { + this->volume_handset_door_call_number_->publish_state(get_setting(SETTING_VOLUME_HANDSET_DOOR_CALL)); + } + if (this->volume_handset_internal_call_number_) { + this->volume_handset_internal_call_number_->publish_state(get_setting(SETTING_VOLUME_HANDSET_INTERNAL_CALL)); + } + if (this->volume_ringtone_number_) { this->volume_ringtone_number_->publish_state(get_setting(SETTING_VOLUME_RINGTONE)); + } #endif } @@ -284,179 +374,183 @@ namespace esphome } #endif - volatile uint32_t TCBusComponentStore::s_last_bit_change = 0; - volatile uint32_t TCBusComponentStore::s_cmd = 0; - volatile uint8_t TCBusComponentStore::s_cmdLength = 0; - volatile bool TCBusComponentStore::s_cmdReady = false; - - void bitSetIDF(uint32_t *variable, int bitPosition) { - *variable |= (1UL << bitPosition); - } - - uint8_t bitReadIDF(uint32_t variable, int bitPosition) { - return (variable >> bitPosition) & 0x01; - } + #ifdef TC_DEBUG_TIMING + volatile uint32_t TCBusComponentStore::debug_buffer[TIMING_DEBUG_BUFFER_SIZE]; + volatile uint8_t TCBusComponentStore::debug_buffer_index = 0; + #endif + volatile uint32_t TCBusComponentStore::last_bit_change = 0; + volatile uint32_t TCBusComponentStore::command = 0; + volatile bool TCBusComponentStore::command_is_long = false; + volatile bool TCBusComponentStore::command_is_ready = false; void IRAM_ATTR HOT TCBusComponentStore::gpio_intr(TCBusComponentStore *arg) { // Made by https://github.com/atc1441/TCSintercomArduino - static uint32_t curCMD; - static uint32_t usLast; - - static uint8_t curCRC; - static uint8_t calCRC; - static uint8_t curLength; - static uint8_t cmdIntReady; - static uint8_t curPos; + // Timing thresholds (in microseconds) + const uint32_t BIT_0_MIN = 1000, BIT_0_MAX = 2999; + const uint32_t BIT_1_MIN = 3000, BIT_1_MAX = 4999; + const uint32_t START_MIN = 5000, START_MAX = 6999; + const uint32_t RESET_MIN = 7000, RESET_MAX = 24000; + + static uint32_t curCMD = 0; // Current command being constructed + static uint32_t usLast = 0; // Last timestamp in microseconds + static uint8_t curCRC = 0; // CRC received in the data + static uint8_t calCRC = 1; // Calculated CRC (starts at 1) + static uint8_t curPos = 0; // Current position in the bit stream + static bool curIsLong = false; // 32 or 16 Bit Command + static bool cmdIntReady = false; // Command ready flag + + // Calculate time difference uint32_t usNow = micros(); uint32_t timeInUS = usNow - usLast; usLast = usNow; - uint8_t curBit = 4; + #ifdef TC_DEBUG_TIMING + if (arg->debug_buffer_index < TIMING_DEBUG_BUFFER_SIZE) { + arg->debug_buffer[arg->debug_buffer_index++] = timeInUS; + } + #endif - if (timeInUS >= 1000 && timeInUS <= 2999) - { + // Determine current bit based on time interval + uint8_t curBit = 4; // Default to undefined bit + if (timeInUS >= BIT_0_MIN && timeInUS <= BIT_0_MAX) { curBit = 0; - } - else if (timeInUS >= 3000 && timeInUS <= 4999) - { + } else if (timeInUS >= BIT_1_MIN && timeInUS <= BIT_1_MAX) { curBit = 1; - } - else if (timeInUS >= 5000 && timeInUS <= 6999) - { + } else if (timeInUS >= START_MIN && timeInUS <= START_MAX) { curBit = 2; - } - else if (timeInUS >= 7000 && timeInUS <= 24000) - { - curBit = 3; + } else if (timeInUS >= RESET_MIN) { + // Reset if a reset signal is detected + curPos = 0; + return; + } else { + // Invalid timing, reset the position curPos = 0; + return; } - if (curPos == 0) - { + // Save last bit timestamp + arg->last_bit_change = millis(); + + if (curPos == 0) { + // First bit after reset: expect start signal (bit 2) if (curBit == 2) { curPos++; + + #ifdef TC_DEBUG_TIMING + // END OF COMMAND + if (arg->debug_buffer_index < TIMING_DEBUG_BUFFER_SIZE) { + arg->debug_buffer[arg->debug_buffer_index++] = 1; + } + #endif } curCMD = 0; curCRC = 0; calCRC = 1; - curLength = 0; - } - else if (curBit == 0 || curBit == 1) - { - if (curPos == 1) - { - curLength = curBit; + curIsLong = false; + } else if (curBit == 0 || curBit == 1) { + // Process bits based on position + if (curPos == 1) { + // Second bit: command length (0 or 1) + curIsLong = curBit; curPos++; - } - else if (curPos >= 2 && curPos <= 17) - { + } else if (curPos >= 2 && curPos <= 17) { + // Bits 2-17: Command data (low 16 bits) if (curBit) { - #if defined(USE_ESP_IDF) - bitSetIDF(&curCMD, (curLength ? 33 : 17) - curPos); - #else - bitSet(curCMD, (curLength ? 33 : 17) - curPos); - #endif + BIT_SET(curCMD, (curIsLong ? 33 : 17) - curPos); } - calCRC ^= curBit; + calCRC ^= curBit; // Update CRC curPos++; - } - else if (curPos == 18) - { - if (curLength) + } else if (curPos == 18) { + // Bit 18: Either part of data (32-bit command) or CRC for 16-bit command + if (curIsLong) { if (curBit) { - #if defined(USE_ESP_IDF) - bitSetIDF(&curCMD, 33 - curPos); - #else - bitSet(curCMD, 33 - curPos); - #endif + BIT_SET(curCMD, 33 - curPos); } - - calCRC ^= curBit; + calCRC ^= curBit; // Update CRC curPos++; } else { - curCRC = curBit; - cmdIntReady = 1; + curCRC = curBit; // Save CRC for 16-bit command + cmdIntReady = true; } - } - else if (curPos >= 19 && curPos <= 33) - { + } else if (curPos >= 19 && curPos <= 33) { + // Bits 19-33: Remaining bits for 32-bit command if (curBit) { - #if defined(USE_ESP_IDF) - bitSetIDF(&curCMD, 33 - curPos); - #else - bitSet(curCMD, 33 - curPos); - #endif + BIT_SET(curCMD, 33 - curPos); } - - calCRC ^= curBit; + calCRC ^= curBit; // Update CRC curPos++; - } - else if (curPos == 34) - { + } else if (curPos == 34) { + // Bit 34: CRC for 32-bit command curCRC = curBit; - cmdIntReady = 1; + cmdIntReady = true; } - } - else - { + } else { + // Undefined bit, reset the position curPos = 0; } - // Save last bit timestamp - arg->s_last_bit_change = millis(); + // If the command is ready, validate CRC and save the command + if (cmdIntReady) { + cmdIntReady = false; - if (cmdIntReady) - { - cmdIntReady = 0; + if (curCRC == calCRC) { + arg->command_is_long = curIsLong ? true : false; + arg->command = curCMD; // Save the decoded command + arg->command_is_ready = true; // Indicate that a command is ready - if (curCRC == calCRC) + #ifdef TC_DEBUG_TIMING + // END OF COMMAND + if (arg->debug_buffer_index < TIMING_DEBUG_BUFFER_SIZE) { + arg->debug_buffer[arg->debug_buffer_index++] = 0; + } + #endif + } + else { - arg->s_cmdReady = true; - arg->s_cmd = curCMD; + #ifdef TC_DEBUG_TIMING + // CRC ERROR + if (arg->debug_buffer_index < TIMING_DEBUG_BUFFER_SIZE) { + arg->debug_buffer[arg->debug_buffer_index++] = 99; + } + #endif } + // Reset state curCMD = 0; curPos = 0; } } - void TCBusComponent::publish_command(uint32_t command, bool received) + void TCBusComponent::publish_command(uint32_t command, bool is_long, bool received) { // Get current TCS Serial Number uint32_t tcs_serial = this->serial_number_; // Parse Command - CommandData cmd_data = parseCommand(command); - ESP_LOGD(TAG, "[Parsed] Type: %s, Address: %i, Payload: %x, Serial: %i", command_type_to_string(cmd_data.type), cmd_data.address, cmd_data.payload, cmd_data.serial_number); + CommandData cmd_data = parseCommand(command, is_long); + ESP_LOGD(TAG, "[Parsed] Type: %s, Address: %i, Payload: %X, Serial: %i, Length: %i-bit", command_type_to_string(cmd_data.type), cmd_data.address, cmd_data.payload, cmd_data.serial_number, (is_long ? 32 : 16)); // Update Door Readiness Status - if (cmd_data.type == COMMAND_TYPE_START_TALKING_DOOR_CALL) - { + if (cmd_data.type == COMMAND_TYPE_START_TALKING_DOOR_CALL) { bool door_readiness_state = cmd_data.payload == 1; ESP_LOGD(TAG, "Door readiness: %s", YESNO(door_readiness_state)); - } - else if (cmd_data.type == COMMAND_TYPE_END_OF_DOOR_READINESS) - { + } else if (cmd_data.type == COMMAND_TYPE_END_OF_DOOR_READINESS) { ESP_LOGD(TAG, "Door readiness: %s", YESNO(false)); - } - else if (cmd_data.type == COMMAND_TYPE_PROGRAMMING_MODE) - { + } else if (cmd_data.type == COMMAND_TYPE_PROGRAMMING_MODE) { ESP_LOGD(TAG, "Programming Mode: %s", YESNO(cmd_data.payload == 1)); this->programming_mode_ = cmd_data.payload == 1; - } - else if (cmd_data.type == COMMAND_TYPE_SEARCH_DOORMAN_DEVICES) - { + } else if (cmd_data.type == COMMAND_TYPE_SEARCH_DOORMAN_DEVICES) { ESP_LOGD(TAG, "Replying to Doorman search request"); uint8_t mac[6]; @@ -468,9 +562,7 @@ namespace esphome mac_addr |= (mac[5] << 0); send_command(COMMAND_TYPE_FOUND_DOORMAN_DEVICE, 0, mac_addr, 0); - } - else if (cmd_data.type == COMMAND_TYPE_FOUND_DOORMAN_DEVICE) - { + } else if (cmd_data.type == COMMAND_TYPE_FOUND_DOORMAN_DEVICE) { uint8_t mac[3]; mac[0] = (cmd_data.payload >> 16) & 0xFF; mac[1] = (cmd_data.payload >> 8) & 0xFF; @@ -481,24 +573,22 @@ namespace esphome #ifdef USE_TEXT_SENSOR // Publish Command to Last Bus Command Sensor - if (this->bus_command_text_sensor_ != nullptr) + if (this->bus_command_text_sensor_ != nullptr) { this->bus_command_text_sensor_->publish_state(cmd_data.command_hex); + } #endif // If the command was received, notify the listeners - if (received) - { + if (received) { // Fire Callback this->received_command_callback_.call(cmd_data); #ifdef USE_BINARY_SENSOR // Fire Binary Sensors - for (auto &listener : listeners_) - { + for (auto &listener : listeners_) { // Listener Command lambda or command property when not available uint32_t listener_command = listener->command_.has_value() ? listener->command_.value() : 0; - if (listener->command_lambda_.has_value()) - { + if (listener->command_lambda_.has_value()) { auto optional_value = (*listener->command_lambda_)(); if (optional_value.has_value()) { listener_command = optional_value.value(); @@ -510,8 +600,7 @@ namespace esphome // Listener Address lambda or address property when not available uint8_t listener_address = listener->address_.has_value() ? listener->address_.value() : 0; - if (listener->address_lambda_.has_value()) - { + if (listener->address_lambda_.has_value()) { auto optional_value = (*listener->address_lambda_)(); if (optional_value.has_value()) { listener_address = optional_value.value(); @@ -520,8 +609,7 @@ namespace esphome // Listener payload lambda or payload property when not available uint32_t listener_payload = listener->payload_.has_value() ? listener->payload_.value() : 0; - if (listener->payload_lambda_.has_value()) - { + if (listener->payload_lambda_.has_value()) { auto optional_value = (*listener->payload_lambda_)(); if (optional_value.has_value()) { listener_payload = optional_value.value(); @@ -534,40 +622,30 @@ namespace esphome bool allow_publish = false; // Check if listener matches the command - if (listener_command != 0) - { - if (cmd_data.command == listener_command) - { + if (listener_command != 0) { + if (cmd_data.command == listener_command) { allow_publish = true; } - } - else if (cmd_data.type == listener_type && (cmd_data.address == listener_address || listener_address == 255) && (cmd_data.payload == listener_payload || listener_payload == 255)) - { - if (listener_serial_number != 0) - { - if (cmd_data.serial_number == listener_serial_number || listener_serial_number == 255) - { + } else if (cmd_data.type == listener_type && (cmd_data.address == listener_address || listener_address == 255) && (cmd_data.payload == listener_payload || listener_payload == 255)) { + if (listener_serial_number != 0) { + if (cmd_data.serial_number == listener_serial_number || listener_serial_number == 255) { allow_publish = true; } - } - else - { + } else { allow_publish = true; // Accept any serial number } } // Trigger listener binary sensor if match found - if (allow_publish) - { + if (allow_publish) { listener->turn_on(&listener->timer_, listener->auto_off_); } } #endif + #ifdef USE_API // Fire Home Assistant Event if event name is specified - if (strcmp(event_, "esphome.none") != 0) - { - #ifdef USE_API + if (strcmp(event_, "esphome.none") != 0) { auto capi = new esphome::api::CustomAPIDevice(); ESP_LOGD(TAG, "Send event to Home Assistant on %s", event_); capi->fire_homeassistant_event(event_, { @@ -577,8 +655,8 @@ namespace esphome {"payload", std::to_string(cmd_data.payload)}, {"serial_number", std::to_string(cmd_data.serial_number)} }); - #endif } + #endif } } @@ -589,44 +667,52 @@ namespace esphome // Get current TCS Serial Number uint32_t tcs_serial = this->serial_number_; - if(serial_number == 0) - { + if(serial_number == 0) { ESP_LOGV(TAG, "Serial Number is 0, use intercom serial number: %i", tcs_serial); serial_number = tcs_serial; } - uint32_t command = buildCommand(type, address, payload, serial_number); - if(command == 0) - { + CommandData command_data = buildCommand(type, address, payload, serial_number); + if(command_data.command == 0) { ESP_LOGW(TAG, "Sending commands of type %s is not supported!", command_type_to_string(type)); - } - else - { - send_command(command); + } else { + send_command(command_data.command, command_data.is_long); } } void TCBusComponent::send_command(uint32_t command) { - ESP_LOGD(TAG, "Sending command %08X", command); + // Determine length of command + // not so reliable as its based on the 32 bit integer itself + send_command(command, (command > 0xFFFF)); + } - if (this->sending) - { - ESP_LOGD(TAG, "Sending of command %i cancelled, another sending is in progress", command); + void TCBusComponent::send_command(uint32_t command, bool is_long) + { + if(is_long) { + ESP_LOGD(TAG, "Sending 32-bit command %08X", command); + } else { + ESP_LOGD(TAG, "Sending 16-bit command %04X", command); } - else - { + + if (this->sending) { + ESP_LOGD(TAG, "Sending of command %08X cancelled, another sending is in progress", command); + } else { // Prevent collisions - /*auto &s = this->store_; + std::srand(millis()); + uint32_t delay_time = std::rand() % (TCS_SEND_MAX_DELAY_MS - TCS_SEND_MIN_DELAY_MS + 1) + TCS_SEND_MIN_DELAY_MS; + uint32_t start_wait = millis(); - uint32_t msNow = millis(); - std::srand(msNow); + delay(delay_time); - delay(std::rand() % 101 + 50); // 50-150 - while((msNow - s.s_last_bit_change) < TCS_SEND_WAIT_DURATION) + while((millis() - this->store_.last_bit_change) < TCS_SEND_WAIT_DURATION) { - delay(std::rand() % 101 + 50); // 50-150 - }*/ + // Add timeout protection + if((millis() - start_wait) > TCS_SEND_WAIT_TIMEOUT_MS) { + break; + } + delay(delay_time); + } // Pause reading ESP_LOGV(TAG, "Pause reading"); @@ -635,10 +721,6 @@ namespace esphome // Source: https://github.com/atc1441/TCSintercomArduino this->sending = true; - // Determine message length and whether it's a long message - bool isLongMessage = (command > 0xFFFF); - int length = isLongMessage ? 32 : 16; - uint8_t checksm = 1; bool output_state = false; @@ -647,16 +729,14 @@ namespace esphome delay(TCS_MSG_START_MS); this->tx_pin_->digital_write(false); - delay(isLongMessage ? TCS_ONE_BIT_MS : TCS_ZERO_BIT_MS); + delay(is_long ? TCS_ONE_BIT_MS : TCS_ZERO_BIT_MS); int curBit = 0; - for (uint8_t i = length; i > 0; i--) - { - #if defined(USE_ESP_IDF) - curBit = bitReadIDF(command, i - 1); - #else - curBit = bitRead(command, i - 1); - #endif + uint8_t length = is_long ? 32 : 16; + + for (uint8_t i = length; i > 0; i--) { + curBit = BIT_READ(command, i - 1); + output_state = !output_state; this->tx_pin_->digital_write(output_state); delay(curBit ? TCS_ONE_BIT_MS : TCS_ZERO_BIT_MS); @@ -674,7 +754,7 @@ namespace esphome this->rx_pin_->attach_interrupt(TCBusComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE); // Publish received Command on Sensors, Events, etc. - this->publish_command(command, false); + this->publish_command(command, is_long, false); } } @@ -693,245 +773,229 @@ namespace esphome this->read_memory_timeout_callback_.add(std::move(callback)); } + void TCBusComponent::add_identify_complete_callback(std::function &&callback) + { + this->identify_complete_callback_.add(std::move(callback)); + } + + void TCBusComponent::add_identify_timeout_callback(std::function &&callback) + { + this->identify_timeout_callback_.add(std::move(callback)); + } + void TCBusComponent::set_programming_mode(bool enabled) { send_command(COMMAND_TYPE_PROGRAMMING_MODE, 0, enabled ? 1 : 0); } - void TCBusComponent::read_memory(uint32_t serial_number) + void TCBusComponent::request_version(uint32_t serial_number) { - if(serial_number == 0) - { + ESP_LOGD(TAG, "Identifying model of device with serial number: %i...", serial_number); + + if(serial_number == 0 && this->serial_number_ != 0) { serial_number = this->serial_number_; + } else { + ESP_LOGW(TAG, "Serial number is not set!"); + return; } - memory_buffer_.clear(); - reading_memory_count_ = 0; + this->cancel_timeout("wait_for_identification"); + + identify_model_ = true; + + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 0); // class 0 + delay(50); + send_command(COMMAND_TYPE_REQUEST_VERSION, 0, 0, serial_number); + delay(200); + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 1); // class 1 + delay(50); + send_command(COMMAND_TYPE_REQUEST_VERSION, 0, 0, serial_number); + + this->set_timeout("wait_for_identification", 600, [this]() { + identify_model_ = false; + this->identify_timeout_callback_.call(); + ESP_LOGE(TAG, "Reading version timed out!"); + }); + } + + void TCBusComponent::read_memory(uint32_t serial_number, Model model) + { + ESP_LOGD(TAG, "Reading EEPROM of %s (%i)...", model_to_string(model), serial_number); + + if(serial_number == 0 && this->serial_number_ != 0) { + serial_number = this->serial_number_; + } else { + ESP_LOGW(TAG, "Serial number is not set!"); + return; + } + + if(model == MODEL_NONE && this->model_ != MODEL_NONE) { + model = this->model_; + } else { + ESP_LOGW(TAG, "Model is not set!"); + return; + } + + this->cancel_timeout("wait_for_memory_reading"); reading_memory_ = false; - ESP_LOGD(TAG, "Select Indoor Stations"); - send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 0); // payload 0 = indoor stations + ModelData model_data = getModelData(model); + + ESP_LOGD(TAG, "Select device category"); + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, model_data.category); delay(50); - ESP_LOGD(TAG, "Select Memory Page %i of Serial Number %i", 0, serial_number); + ESP_LOGD(TAG, "Select memory page %i of serial number %i", 0, serial_number); send_command(COMMAND_TYPE_SELECT_MEMORY_PAGE, 0, 0, serial_number); delay(50); + memory_buffer_.clear(); reading_memory_ = true; + reading_memory_serial_number_ = serial_number; reading_memory_count_ = 0; - reading_memory_timer_ = millis(); + reading_memory_max_ = (model_data.memory_size / 4); + + this->set_timeout("wait_for_memory_reading", 5000, [this]() { + memory_buffer_.clear(); + reading_memory_ = false; + reading_memory_serial_number_ = 0; + reading_memory_count_ = 0; + reading_memory_max_ = 0; + + this->read_memory_timeout_callback_.call(); + ESP_LOGE(TAG, "Reading memory timed out!"); + }); ESP_LOGD(TAG, "Read 4 memory addresses %i to %i", (reading_memory_count_ * 4), (reading_memory_count_ * 4) + 4); send_command(COMMAND_TYPE_READ_MEMORY_BLOCK, reading_memory_count_); } - uint8_t TCBusComponent::get_setting(SettingType type) + uint8_t TCBusComponent::get_setting(SettingType type, Model model) { - if(memory_buffer_.size() == 0) - { + if(memory_buffer_.size() == 0) { + return 0; + } + + if(model == MODEL_NONE && this->model_ != MODEL_NONE) { + model = this->model_; + } else { + ESP_LOGW(TAG, "Model is not set!"); return 0; } // Get Setting Cell Data by Model - SettingCellData cellData = getSettingCellData(type); + SettingCellData cellData = getSettingCellData(type, model); - if(cellData.index != 0) - { + if(cellData.index != 0) { return cellData.left_nibble ? ((memory_buffer_[cellData.index] >> 4) & 0xF) : (memory_buffer_[cellData.index] & 0xF); - } - else - { - ESP_LOGV(TAG, "The setting %s is not available for model %s", setting_type_to_string(type), model_to_string(model_)); + } else { + ESP_LOGV(TAG, "The setting %s is not available for model %s", setting_type_to_string(type), model_to_string(model)); return 0; } } - bool TCBusComponent::update_setting(SettingType type, uint8_t new_value, uint32_t serial_number) + bool TCBusComponent::update_setting(SettingType type, uint8_t new_value, uint32_t serial_number, Model model) { - if(memory_buffer_.size() == 0) - { - ESP_LOGW(TAG, "Memory Buffer is empty! Please read memory first!"); + ESP_LOGD(TAG, "Write setting %s (%X) to EEPROM of %s (%i)...", setting_type_to_string(type), new_value, model_to_string(model), serial_number); + + if(memory_buffer_.size() == 0) { + ESP_LOGW(TAG, "Memory buffer is empty! Please read memory first!"); return false; } - if(serial_number == 0) - { + if(serial_number == 0 && this->serial_number_ != 0) { serial_number = this->serial_number_; + } else { + ESP_LOGW(TAG, "Serial number is not set!"); + return false; } - if(serial_number == 0) - { - ESP_LOGW(TAG, "Serial Number is not set!"); + if(model == MODEL_NONE && this->model_ != MODEL_NONE) { + model = this->model_; + } else { + ESP_LOGW(TAG, "Model is not set!"); return false; } uint8_t saved_nibble = 0; // Get Setting Cell Data by Model - SettingCellData cellData = getSettingCellData(type); + SettingCellData cellData = getSettingCellData(type, model); - if(cellData.index != 0) - { + if(cellData.index != 0) { // Apply new nibble and keep other nibble saved_nibble = (cellData.left_nibble ? memory_buffer_[cellData.index] : (memory_buffer_[cellData.index] >> 4)) & 0xF; memory_buffer_[cellData.index] = cellData.left_nibble ? ((new_value << 4) | (saved_nibble & 0xF)) : ((saved_nibble << 4) | (new_value & 0xF)); // Prepare Transmission - ESP_LOGD(TAG, "Select Indoor Stations"); - send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 0); // payload 0 = indoor stations + ESP_LOGD(TAG, "Select device category"); + ModelData model_data = getModelData(model); + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, model_data.category); delay(50); - ESP_LOGD(TAG, "Select Memory Page %i of Serial Number %i", 0, serial_number); + ESP_LOGD(TAG, "Select memory page %i of serial number %i", 0, serial_number); send_command(COMMAND_TYPE_SELECT_MEMORY_PAGE, 0, 0, serial_number); delay(50); // Transfer new settings value to memory uint16_t new_values = (memory_buffer_[cellData.index] << 8) | memory_buffer_[cellData.index + 1]; - send_command(COMMAND_TYPE_WRITE_MEMORY, cellData.index, new_values, serial_number); + send_command(COMMAND_TYPE_WRITE_MEMORY, cellData.index, new_values); + delay(50); + + // Reset + send_command(COMMAND_TYPE_RESET); return true; - } - else - { - ESP_LOGW(TAG, "The setting %s is not available for model %s", setting_type_to_string(type), model_to_string(model_)); + } else { + ESP_LOGV(TAG, "The setting %s is not available for model %s", setting_type_to_string(type), model_to_string(model)); return false; } } - SettingCellData TCBusComponent::getSettingCellData(SettingType setting) + bool TCBusComponent::write_memory(uint32_t serial_number, Model model) { - SettingCellData data; - - switch (model_) - { - case MODEL_TCS_ISH3030: - case MODEL_TCS_ISH3230: - case MODEL_TCS_ISH3340: - case MODEL_TCS_ISW3030: - case MODEL_TCS_ISW3230: - case MODEL_TCS_ISW3340: - case MODEL_KOCH_TC50: - case MODEL_KOCH_TCH50: - case MODEL_KOCH_TCH50P: - switch (setting) - { - case SETTING_RINGTONE_DOOR_CALL: - data.index = 3; - data.left_nibble = true; - break; - - case SETTING_RINGTONE_INTERNAL_CALL: - data.index = 6; - data.left_nibble = true; - break; - - case SETTING_RINGTONE_FLOOR_CALL: - data.index = 9; - data.left_nibble = true; - break; - - case SETTING_VOLUME_RINGTONE: - data.index = 20; - data.left_nibble = false; - break; - - case SETTING_VOLUME_HANDSET: - data.index = 21; - data.left_nibble = false; - break; - - default: break; - } - break; + ESP_LOGD(TAG, "Write memory buffer to EEPROM of %s (%i)...", model_to_string(model), serial_number); - case MODEL_TCS_ISH1030: - switch (setting) - { - case SETTING_RINGTONE_DOOR_CALL: - data.index = 3; - data.left_nibble = true; - break; - - case SETTING_RINGTONE_INTERNAL_CALL: - data.index = 6; - data.left_nibble = true; - break; - - case SETTING_RINGTONE_FLOOR_CALL: - data.index = 9; - data.left_nibble = true; - break; - - default: break; - } - break; - - case MODEL_TCS_IVH3222: - switch (setting) - { - case SETTING_RINGTONE_DOOR_CALL: - data.index = 3; - data.left_nibble = true; - break; - - // Is this really available? - // It's set to 0 in default eep - case SETTING_RINGTONE_INTERNAL_CALL: - data.index = 6; - data.left_nibble = true; - break; - - case SETTING_RINGTONE_FLOOR_CALL: - data.index = 9; - data.left_nibble = true; - break; - - default: break; - } - break; - default: break; - } - - return data; - } - - void TCBusComponent::write_memory(uint32_t serial_number) - { - if(memory_buffer_.size() == 0) - { - ESP_LOGW(TAG, "Memory Buffer is empty! Please read memory first!"); - return; + if(memory_buffer_.size() == 0) { + ESP_LOGW(TAG, "Memory buffer is empty! Please read memory first!"); + return false; } - if(serial_number == 0) - { + if(serial_number == 0 && this->serial_number_ != 0) { serial_number = this->serial_number_; + } else { + ESP_LOGW(TAG, "Serial number is not set!"); + return false; } - if(serial_number == 0) - { - ESP_LOGW(TAG, "Serial Number is not set!"); - return; + if(model == MODEL_NONE && this->model_ != MODEL_NONE) { + model = this->model_; + } else { + ESP_LOGW(TAG, "Model is not set!"); + return false; } // Prepare Transmission - ESP_LOGD(TAG, "Select Indoor Stations"); - send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, 0); // payload 0 = indoor stations + ESP_LOGD(TAG, "Select device category"); + ModelData model_data = getModelData(model); + send_command(COMMAND_TYPE_SELECT_DEVICE_GROUP, 0, model_data.category); delay(50); - ESP_LOGD(TAG, "Select Memory Page %i of Serial Number %i", 0, serial_number); + ESP_LOGD(TAG, "Select memory page %i of serial number %i", 0, serial_number); send_command(COMMAND_TYPE_SELECT_MEMORY_PAGE, 0, 0, serial_number); delay(50); // Transmit Memory - uint8_t address = 0; - for (size_t i = 0; i < memory_buffer_.size(); i += 2) - { - uint16_t new_value = (memory_buffer_[i] << 8) | memory_buffer_[i + 1]; - send_command(COMMAND_TYPE_WRITE_MEMORY, address, new_value, serial_number); - address = address + 2; + for (size_t address = 0; address < memory_buffer_.size(); address += 2) { + uint16_t new_value = (memory_buffer_[address] << 8) | memory_buffer_[address + 1]; + send_command(COMMAND_TYPE_WRITE_MEMORY, address, new_value); delay(50); } + + // Reset + send_command(COMMAND_TYPE_RESET); + + return true; } } // namespace tc_bus diff --git a/components/tc_bus/tc_bus.h b/components/tc_bus/tc_bus.h index 149ac25f..0643d221 100644 --- a/components/tc_bus/tc_bus.h +++ b/components/tc_bus/tc_bus.h @@ -31,6 +31,18 @@ namespace esphome { namespace tc_bus { + #ifdef TC_DEBUG_TIMING + static const uint8_t TIMING_DEBUG_BUFFER_SIZE = 255; + #endif + + #if defined(USE_ESP_IDF) + #define BIT_SET(var, pos) ((var) |= (1UL << (pos))) + #define BIT_READ(var, pos) ((var >> pos) & 0x01) + #else + #define BIT_SET(var, pos) bitSet((var), (pos)) + #define BIT_READ(var, pos) bitRead((var), (pos)) + #endif + #ifdef USE_BINARY_SENSOR class TCBusListener { @@ -74,10 +86,15 @@ namespace esphome { static void gpio_intr(TCBusComponentStore *arg); - static volatile uint32_t s_last_bit_change; - static volatile uint32_t s_cmd; - static volatile uint8_t s_cmdLength; - static volatile bool s_cmdReady; + #ifdef TC_DEBUG_TIMING + static volatile uint32_t debug_buffer[TIMING_DEBUG_BUFFER_SIZE]; + static volatile uint8_t debug_buffer_index; + #endif + + static volatile uint32_t last_bit_change; + static volatile uint32_t command; + static volatile bool command_is_long; + static volatile bool command_is_ready; ISRInternalGPIOPin rx_pin; }; @@ -96,15 +113,15 @@ namespace esphome #endif #ifdef USE_SELECT SUB_SELECT(model) - SUB_SELECT(ringtone_door_call) + SUB_SELECT(ringtone_entrance_door_call) + SUB_SELECT(ringtone_second_entrance_door_call) SUB_SELECT(ringtone_floor_call) SUB_SELECT(ringtone_internal_call) - SUB_SELECT(volume_handset) - SUB_SELECT(volume_ringtone) #endif #ifdef USE_NUMBER SUB_NUMBER(serial_number) - SUB_NUMBER(volume_handset) + SUB_NUMBER(volume_handset_door_call) + SUB_NUMBER(volume_handset_internal_call) SUB_NUMBER(volume_ringtone) #endif @@ -126,17 +143,19 @@ namespace esphome #endif void send_command(uint32_t command); + void send_command(uint32_t command, bool is_long); void send_command(CommandType type, uint8_t address = 0, uint32_t payload = 0, uint32_t serial_number = 0); void set_programming_mode(bool enabled); - void read_memory(uint32_t serial_number); + + void request_version(uint32_t serial_number); + void read_memory(uint32_t serial_number, Model model = MODEL_NONE); void request_memory_blocks(uint8_t start_address); - void write_memory(uint32_t serial_number = 0); + bool write_memory(uint32_t serial_number = 0, Model model = MODEL_NONE); - uint8_t get_setting(SettingType type); - bool update_setting(SettingType type, uint8_t new_value, uint32_t serial_number = 0); - SettingCellData getSettingCellData(SettingType setting); + uint8_t get_setting(SettingType type, Model model = MODEL_NONE); + bool update_setting(SettingType type, uint8_t new_value, uint32_t serial_number = 0, Model model = MODEL_NONE); - void publish_command(uint32_t command, bool fire_events); + void publish_command(uint32_t command, bool is_long, bool fire_events); void publish_settings(); void save_settings(); @@ -147,6 +166,10 @@ namespace esphome CallbackManager)> read_memory_complete_callback_{}; void add_read_memory_timeout_callback(std::function &&callback); CallbackManager read_memory_timeout_callback_{}; + void add_identify_complete_callback(std::function &&callback); + CallbackManager identify_complete_callback_{}; + void add_identify_timeout_callback(std::function &&callback); + CallbackManager identify_timeout_callback_{}; bool sending; @@ -171,10 +194,12 @@ namespace esphome std::string hardware_version_str_ = "Generic"; bool programming_mode_ = false; + bool identify_model_ = false; bool reading_memory_ = false; uint8_t reading_memory_count_ = 0; - uint32_t reading_memory_timer_ = 0; + uint8_t reading_memory_max_ = 0; + uint32_t reading_memory_serial_number_ = 0; std::vector memory_buffer_; ESPPreferenceObject pref_; diff --git a/docs/.vitepress/config/shared.mts b/docs/.vitepress/config/shared.mts index 0be884b6..97ebea28 100644 --- a/docs/.vitepress/config/shared.mts +++ b/docs/.vitepress/config/shared.mts @@ -1,6 +1,25 @@ + + +export default defineConfig({ + head: [ + // og:site_name, og:type, og:image:width, og:image:height, twitter:card go here + ], + + // … +}); + + import { createRequire } from 'module' import { defineConfig } from 'vitepress' import { search as deSearch } from './de.mts' +import { createTitle, normalize } from "vitepress/dist/client/shared.js"; + +const ORIGIN = "https://doorman.azon.ai"; +const FALLBACK_META_IMAGE = "doorman-og.jpg"; + +function href(path = "") { + return new URL(normalize(path), ORIGIN).href; +} const require = createRequire(import.meta.url) const pkg = require('../../package.json') @@ -63,7 +82,91 @@ export const shared = defineConfig({ ] ], - + transformPageData(pageData, ctx) { + let pageDescription = pageData.frontmatter?.description; + const pageHref = href(pageData.relativePath); + const pageImage = href( + pageData.frontmatter?.metaImage ?? FALLBACK_META_IMAGE, + ); + const pageTitle = createTitle(ctx.siteConfig.site, pageData); + + if (!pageDescription) { + pageDescription = ctx.siteConfig.site?.description; + + // If no page-specific description and not homepage, prepend the site title to the description + if (pageDescription && pageHref !== href()) { + pageDescription = [ctx.siteConfig.site?.title, pageDescription] + .filter((v) => Boolean(v)) + .join(": "); + } + } + + pageData.frontmatter.head ??= []; + + pageData.frontmatter.head.push( + [ + "meta", + { + property: "og:image", + content: pageImage, + }, + ], + [ + "meta", + { + name: "og:title", + content: pageTitle, + }, + ], + [ + "meta", + { + property: "og:title", + content: pageTitle, + }, + ], + [ + "meta", + { + property: "og:url", + content: pageHref, + }, + ], + [ + "meta", + { + name: "og:image", + content: pageImage, + }, + ], + [ + "meta", + { + name: "twitter:title", + content: pageTitle, + }, + ], + ); + + if (pageDescription) { + pageData.frontmatter.head.push( + [ + "meta", + { + name: "og:description", + content: pageDescription, + }, + ], + [ + "meta", + { + name: "twitter:description", + content: pageDescription, + }, + ], + ); + } + }, themeConfig: { diff --git a/docs/de/guide/automation/pattern-events.md b/docs/de/guide/automation/pattern-events.md index 205a6e21..4f19e3d0 100644 --- a/docs/de/guide/automation/pattern-events.md +++ b/docs/de/guide/automation/pattern-events.md @@ -11,16 +11,15 @@ Sieh dir die [fortgeschrittenen Beispiele](../firmware/stock-firmware#fortgeschr ## Klingelmuster +### Ereignis Sensoren +- Entrance Doorbell +- Second Entrance Doorbell +- Apartment Doorbell + ### Ereignistypen -- apartment_single -- apartment_double -- apartment_triple -- entrance_single -- entrance_double -- entrance_triple -- second_entrance_single -- second_entrance_double -- second_entrance_triple +- single +- double +- triple ### Beispiel-Automatisierung ::: details Tür automatisch öffnen, wenn die Eingangsklingel zweimal in einem bestimmten Muster gedrückt wird. @@ -31,9 +30,9 @@ description: "Öffne die Eingangstür, nachdem die Eingangsklingel zweimal gedr trigger: - platform: state entity_id: - - event.doorman_s3_doorbell_pattern + - event.doorman_s3_entrance_doorbell attribute: event_type - to: entrance_double + to: double condition: [] action: - service: button.press @@ -61,7 +60,7 @@ description: "Schalte den Ring-To-Open-Modus um, wenn du schnell dreimal den Tel trigger: - platform: state entity_id: - - event.doorman_s3_phone_pick_up_pattern + - event.doorman_s3_phone_pick_up attribute: event_type to: triple condition: [] @@ -73,4 +72,4 @@ action: entity_id: switch.doorman_s3_ring_to_open mode: single ``` -::: \ No newline at end of file +::: diff --git a/docs/de/guide/firmware/additions.md b/docs/de/guide/firmware/additions.md index ade3370b..919cf3d9 100644 --- a/docs/de/guide/firmware/additions.md +++ b/docs/de/guide/firmware/additions.md @@ -75,9 +75,9 @@ i2c: // [!code ++] // [!code focus] ## Fortgeschrittene Beispiele ### Home Assistant -::: details Sending Bus commands +::: details Bus Commands senden Mit Home Assistant kannst du Aktionen nutzen, um Commands über den Bus zu senden. -Benutze entweder `command` für reine 32 Bit Befehle oder `type`, `address`, `payload` und `serial_number` um Befehle über den Command Builder zu senden. +Benutze entweder `command` für hexadezimale Befehle oder `type`, `address`, `payload` und `serial_number` um Befehle über den Command Builder zu senden. > [!INFO] > Denk an das führende `0x` beim Senden eines Befehls mit der `command` Eigenschaft. Wenn du es weglässt, musst du den HEX-Befehl zuerst in eine Dezimalzahl umwandeln. @@ -92,7 +92,7 @@ data: serial_number: 0 ``` -32-Bit Befehle via `command`: +Hexadezimale Befehle via `command`: ```yaml service: esphome.doorman_s3_send_tc_command_raw data: @@ -159,7 +159,7 @@ Wenn du ein benutzerdefiniertes Klingelmuster erstellen möchtest, kannst du die # Türglocken-Muster-Event-Entity erweitern // [!code ++] // [!code focus] # Neues apartment_special-Eventtyp hinzufügen // [!code ++] // [!code focus] event: // [!code ++] // [!code focus] - - id: !extend doorbell_pattern // [!code ++] // [!code focus] + - id: !extend apartment_doorbell_pattern // [!code ++] // [!code focus] event_types: // [!code ++] // [!code focus] - "apartment_special" // [!code ++] // [!code focus] @@ -178,7 +178,7 @@ binary_sensor: // [!code ++] // [!code focus] then: // [!code ++] // [!code focus] - logger.log: "Besonderes Muster erkannt!" // [!code ++] // [!code focus] - event.trigger: // [!code ++] // [!code focus] - id: doorbell_pattern // [!code ++] // [!code focus] + id: apartment_doorbell_pattern // [!code ++] // [!code focus] # Den vorher definierten neuen Eventtyp hier verwenden // [!code ++] // [!code focus] event_type: apartment_special // [!code ++] // [!code focus] ``` @@ -222,4 +222,4 @@ binary_sensor: // [!code ++] // [!code focus] - tc_bus.send: // [!code ++] // [!code focus] type: "light" // [!code ++] // [!code focus] ``` -::: \ No newline at end of file +::: diff --git a/docs/de/guide/firmware/mqtt.md b/docs/de/guide/firmware/mqtt.md new file mode 100644 index 00000000..5e35d765 --- /dev/null +++ b/docs/de/guide/firmware/mqtt.md @@ -0,0 +1,68 @@ +## MQTT + +Bei Verwendung der MQTT-Firmware werden verschiedene Topics an deinen Broker gesendet. So funktioniert die Struktur der Topics und Steuerung. + +### Topic Struktur +Jede Entität veröffentlicht ihren Status auf einem Topic im folgenden Format: +``` +///state +``` + +Du kannst bestimmte Entitäten steuern, indem du einen Befehl an ein Topic mit folgendem Format sendest: +::: code-group +``` [Topic] +///command +``` +``` [Payload] +ON oder OFF oder was sonst unterstützt wird +``` +::: + +### Beispiel +Um die [Ring-To-Open](../automation/ring-to-open.md) Automatisierung zu aktivieren oder zu deaktivieren, sende ON oder OFF als Payload an dieses Topic: +::: code-group +``` [Topic] +doorman-s3/switch/ring_to_open/command +``` +``` [Payload] +ON +``` +::: + +### Spezielle Topics +Es gibt spezielle Topics, die erweiterte Befehle ermöglichen. + +#### Senden eines Commands (Hexadezimal) +Hier ist ein Beispiel um hexadezimale Commands (uint32) an den Bus zu senden: +::: code-group +``` [Topic] +doorman-s3/send_raw_command +``` +```json [Payload] +{ + "command": 0x1C30BA80 +} +``` +```json [Advanced Payload] +{ + "command": 0x1C30BA80, + "is_long": false +} +``` +::: + +#### Senden eines Commands (Command Builder) +Hier ist ein Beispiel um Commands via Command Builder an den Bus zu senden: +::: code-group +``` [Topic] +doorman-s3/send_command +``` +```json [Payload] +{ + "type": "open_door", + "address": 0, + "payload": 0, + "serial_number": 123456 +} +``` +::: \ No newline at end of file diff --git a/docs/de/guide/firmware/nuki-bridge-firmware.md b/docs/de/guide/firmware/nuki-bridge-firmware.md index aa936dd3..34b9524d 100644 --- a/docs/de/guide/firmware/nuki-bridge-firmware.md +++ b/docs/de/guide/firmware/nuki-bridge-firmware.md @@ -8,11 +8,12 @@ Es gibt mehrere Möglichkeiten, die Firmware zu aktualisieren: - HTTP OTA - Web Serial -Du kannst deinen Doorman über USB-C anschließen und auf den untenstehenden Button klicken, um die neueste Doorman Nuki-Bridge Firmware direkt über Web Serial zu installieren. +Du kannst deinen Doorman über USB-C anschließen und auf den untenstehenden Button klicken, um die neueste Doorman Nuki-Bridge Firmware (Home Assistant) direkt über Web Serial zu installieren. +Wenn du MQTT ohne Home Assistant nutzen möchtest, kannst du Doorman in dein ESPHome Dashboard aufnehmen und das untenstehende Beispiel `Minimale Nuki-Bridge Firmware (MQTT)` verwenden.
@@ -31,11 +32,17 @@ Du kannst deinen Doorman über USB-C anschließen und auf den untenstehenden But ## Firmware YAML -Dies ist die minimale ESPHome-Konfigurations-YAML-Datei. Vergiss nicht, den API-Schlüssel zu aktualisieren. +Dies ist die minimale ESPHome-Konfigurations-YAML-Datei für die Verwendung mit Home Assistant. Vergiss nicht, den API-Schlüssel zu aktualisieren. +::: details Minimale Nuki-Bridge Firmware (Home Assistant) +```yaml + +``` +::: -::: details Minimale Nuki-Bridge Firmware +Dies ist die minimale ESPHome-Konfigurations-YAML-Datei für die Verwendung mit MQTT. Vergiss nicht, die MQTT Broker Daten zu aktualisieren. +::: details Minimale Nuki-Bridge Firmware (MQTT) ```yaml - + ``` ::: @@ -57,4 +64,6 @@ Du kannst dein Gerät entweder über den `Nuki Unpair Device`-Button in Home Ass Falls dein Schloss bereits mit Doorman gekoppelt ist, drücke die `FLASH`- oder `PRG`-Taste auf der Doorman-Platine für 5 Sekunden, bis die RGB-Status-LED lila zu blinken beginnt. Dein Nuki Lock wird dann entkoppelt. Beachte, dass der Pairing-Modus nach 30 Sekunden abläuft. ::: - \ No newline at end of file + + + \ No newline at end of file diff --git a/docs/de/guide/firmware/stock-firmware.md b/docs/de/guide/firmware/stock-firmware.md index 17a066e7..e3039083 100644 --- a/docs/de/guide/firmware/stock-firmware.md +++ b/docs/de/guide/firmware/stock-firmware.md @@ -8,11 +8,12 @@ Es gibt mehrere Möglichkeiten, die Firmware zu aktualisieren: - HTTP OTA - Web Serial -Du kannst deinen Doorman über USB-C anschließen und auf den untenstehenden Button klicken, um die neueste Doorman Stock Firmware direkt über Web Serial zu installieren. +Du kannst deinen Doorman über USB-C anschließen und auf den untenstehenden Button klicken, um die neueste Doorman Stock Firmware (Home Assistant) direkt über Web Serial zu installieren. +Wenn du MQTT ohne Home Assistant nutzen möchtest, kannst du Doorman in dein ESPHome Dashboard aufnehmen und das untenstehende Beispiel `Minimale Stock Firmware (MQTT)` verwenden.
@@ -31,12 +32,20 @@ Du kannst deinen Doorman über USB-C anschließen und auf den untenstehenden But ## Firmware YAML -Dies ist die minimale ESPHome-Konfigurations-YAML-Datei. Vergiss nicht, den API-Schlüssel zu aktualisieren. +Dies ist die minimale ESPHome-Konfigurations-YAML-Datei für die Verwendung mit Home Assistant. Vergiss nicht, den API-Schlüssel zu aktualisieren. +::: details Minimale Stock Firmware (Home Assistant) +```yaml + +``` +::: -::: details Minimale Stock Firmware +Dies ist die minimale ESPHome-Konfigurations-YAML-Datei für die Verwendung mit MQTT. Vergiss nicht, die MQTT Broker Daten zu aktualisieren. +::: details Minimale Stock Firmware (MQTT) ```yaml - + ``` ::: - \ No newline at end of file + + + \ No newline at end of file diff --git a/docs/de/guide/getting-started.md b/docs/de/guide/getting-started.md index d78be976..9c14a4cf 100644 --- a/docs/de/guide/getting-started.md +++ b/docs/de/guide/getting-started.md @@ -9,16 +9,16 @@ Sofern du selbst ein PCB produzieren lassen hast, musst du zuerst die Firmware f **Vielen Dank, dass du Doorman verwendest! ❤️** ## Verkabelung -Öffne als Erstes das Gehäuse deiner Gegensprechanlage. In den meisten Fällen findest du dort eine Schraubklemme mit den Bezeichnungen `a`, `b`, `E` und `P`. +Öffne als Erstes das Gehäuse deiner Innenstation. In den meisten Fällen findest du dort eine Schraubklemme mit den Bezeichnungen `a`, `b`, `E` und `P`. -Schließe die `b`-Leitung (Ground) an einen der TC:BUS-Anschlüsse deines Doorman an und die `a`-Leitung (24V Bus) an den anderen TC:BUS-Anschluss deines Doorman. +Schließe die `b`-Leitung (Ground) an einen der TC:BUS-Anschlüsse deines Doorman an und die `a`-Leitung (24V Bus) an den anderen TC:BUS-Anschluss deines Doorman. Doorman ist wie jedes andere Gerät am Bus und wird **parallel angeschlossen**. ::: warning Hinweis -Standardmäßig versende ich Revision 1.5 mit einer Jumper-Kappe auf `BUS PWR`. Bitte entferne diese, sofern du Doorman nicht nach dem Schema `2-Draht-Modus über die Gegensprechanlage` anschließt. +Standardmäßig versende ich Revision 1.5 mit einer Jumper-Kappe auf `BUS PWR`. Bitte entferne diese, sofern du Doorman nicht nach dem Schema `2-Draht-Modus über die Innenstation` anschließt. ::: ### Stromversorgungsoptionen: -::: details 3-Draht-Modus über die Gegensprechanlage +::: details 3-Draht-Modus über die Innenstation Verbinde die `P`-Leitung (+24V) mit dem `P`-Terminal an deinem Doorman. > [!WARNING] @@ -42,7 +42,7 @@ Beispiel: ![2-wire external via usb](./images/2wire_power_usb_c.png){width=300px} ::: -::: details 2-Draht-Modus über die Gegensprechanlage +::: details 2-Draht-Modus über die Innenstation > [!DANGER] Wichtig > Die Nutzung der `a`-Bus-Leitung als Stromquelle bei älteren Hardware Revisionen als `1.5` führt zu einem lauten Piepton. Dieses Problem tritt wahrscheinlich aufgrund der Hochfrequenz-Schaltstromversorgung auf. > @@ -94,17 +94,21 @@ Du musst den Modus nicht manuell aktivieren; er wird bei jedem Neustart automati Gehe zum Bereich `Konfiguration` und schalte den `Setup Mode` ein, um die interaktive Einrichtung zu beginnen. ::: warning Bevor du weitermachst -Deine Gegensprechanlage muss angeschlossen und das Gehäuse verschlossen sein, damit die Einrichtung abgeschlossen werden kann. +Deine Innenstation muss angeschlossen und das Gehäuse verschlossen sein, damit die Einrichtung abgeschlossen werden kann. ::: 3. **Einrichtung durchführen:**\ Die RGB-Status-LED wird grün-türkis pulsieren. Drücke den Klingeltaster vor der Wohnung oder am Eingang. 4. **Abschluss der Einrichtung:**\ - Nach dem Drücken des Klingeltasters leuchtet die LED für 3 Sekunden durchgehend grün-türkis. Danach schaltet sich die LED aus, und die Einrichtung ist abgeschlossen. + Nach dem Drücken des Klingeltasters versucht das System, das Modell deiner Innenstation zu erkennen. Sobald die Modell-Erkennung erfolgreich war oder einen Timeout bekommt, leuchtet die LED für 3 Sekunden grün-türkis. Danach schaltet sich die LED aus, und die Einrichtung ist abgeschlossen. Wenn du mehrere Außenstationen hast, wird die Firmware versuchen, die zusätzliche Station automatisch zu erkennen. -Um die Erkennung der zweiten Türklingel und das Öffnen der zweiten Tür zu ermöglichen, musst du die zweite Türklingel einmal betätigen oder die zweite Tür einmal öffnen, damit die Adresse gespeichert wird. +Um die Erkennung der zweiten Türklingel und das Öffnen der zweiten Tür zu ermöglichen, musst du die zweite Türklingel einmal betätigen oder den physischen Entsperrknopf der zweiten Tür mindestens einmal betätigen, damit die Adresse gespeichert wird. + +::: tip Mehrere Innenstationen +Wenn du mehrere Innenstationen hast, wird's etwas tricky. Du musst dann eine eigene YAML-Konfiguration erstellen, damit alle zusammen funktionieren. Die Standard-Firmware kann nämlich nur mit einer Innenstation umgehen. +::: ## ESPHome adoption Wenn du die Firmware deines Doorman anpassen möchtest, kannst du diesen zu deinem [ESPHome-Dashboard](https://my.home-assistant.io/redirect/supervisor_ingress/?addon=5c53de3b_esphome) hinzufügen und deine angepasste [Stock](firmware/stock-firmware.md) oder [Nuki Bridge](firmware/nuki-bridge-firmware.md) Firmware flashen. diff --git a/docs/de/reference/entities.md b/docs/de/reference/entities.md index fdff4da7..7d4d0d08 100644 --- a/docs/de/reference/entities.md +++ b/docs/de/reference/entities.md @@ -87,12 +87,18 @@ Steuert die [Ring To Open](../guide/automation/ring-to-open) Automatisierung. ### Ring To Open: Confirmation Steuert die Einschaltbestätigung für die [Ring To Open](../guide/automation/ring-to-open) Automatisierung. +### Ring To Open: Display Status +Steuert die Status LED anzeige für die [Ring To Open](../guide/automation/ring-to-open) Automatisierung. + ### Relay Steuert das eingebaute Relais. ### Setup Mode Aktiviert oder deaktiviert den Modus für die [interaktive Einrichtung](../guide/getting-started#schritt-3-interaktive-einrichtung). +### Experimental Updates +Schaltet experimentelle Updates frei, damit du ganz einfach zwischen dem Master- und Dev-Zweig wechseln und die neuesten Änderungen ausprobieren kannst. + ### Nuki Pairing Mode Steuert den Nuki Kopplungsmodus. @@ -129,12 +135,6 @@ Controls the Nuki Smart Lock Auto Unlock Disable setting. ### Nuki Single Lock Controls the Nuki Smart Lock Single Lock setting. -### Nuki Daylight Saving Time -Controls the Nuki Smart Lock DST Mode setting. - -### Nuki Automatic Updates -Controls the Nuki Smart Lock Automatic Updates setting. - ## Buttons @@ -147,6 +147,12 @@ Controls the Nuki Smart Lock Automatic Updates setting. ### Turn on the Light Schaltet das Licht ein, indem der Befehl `light_button_command` auf dem Bus gesendet wird. +### Identify Indoor Station +Ermittelt das Modell der Innenstation und speichert es. +::: note Hinweis +Es werden nicht alle Modelle unterstützt, da ältere Modelle diese Funktion ggfs. nicht unterstützen. +::: + ### Read Memory Liest den internen speicher deiner Innenstation mit der angegebenen Seriennummer aus. @@ -174,6 +180,9 @@ Wenn du WiFi über das Captive Portal, Improv Serial oder Improv BLE konfigurier ### Serial Number Legt die Seriennummer der Innenstation für den Commandbuilder/parser fest. +### Entrance Door Station ID +Legt die ID der Außenstelle am Eingang fest. + ### Second Door Station ID Legt die ID der zweiten Außenstelle fest. @@ -187,8 +196,11 @@ Das Einstellen der Verzögerung auf das Maximum (60 Sekunden) führt dazu, dass ### Volume: Ringtone Legt die Lautstärke der Klingeltöne deiner Innenstation fest. -### Volume: Handset -Legt die Lautstärke des Hörers deiner Innenstation fest. +### Volume: Handset Door Call +Legt die Lautstärke des Hörers deiner Innenstation bei Türrufen fest. + +### Volume: Handset Internal Call +Legt die Lautstärke des Hörers deiner Innenstation bei Internrufen fest. ### Nuki LED: Brightness Controls the Nuki Smart Lock LED Brightness setting. @@ -218,28 +230,34 @@ Legt die auslösende Außenstelle für die [Ring To Open](../guide/automation/ri ### Intercom Model Legt das Modell deiner Innenstation fest. Finde mehr über die [unterstützten Modelle und Einstellungen](esphome-component#model-setting-availability) heraus. -### Ringtone: Door Call -Legt den Klingelton für Tür-Rufe deiner Innenstation fest. +### Ringtone: Entrance Door Call +Legt den Klingelton für Tür-Rufe (Eingang) deiner Innenstation fest. + +##### Optionen: +- Ringtone 1 ... 13 + +### Ringtone: Second Entrance Door Call +Legt den Klingelton für Tür-Rufe (Zweiter Eingang) deiner Innenstation fest. -##### Options: +##### Optionen: - Ringtone 1 ... 13 ### Ringtone: Floor Call Legt den Klingelton für Etagen-Rufe deiner Innenstation fest. -##### Options: +##### Optionen: - Ringtone 1 ... 13 ### Ringtone: Internal Call Legt den Klingelton für Intern-Rufe deiner Innenstation fest. -##### Options: +##### Optionen: - Ringtone 1 ... 13 ### Nuki Button: Single Press Action Controls the Nuki Smart Lock Single Button Press Action setting. -##### Options: +##### Optionen: - No Action - Intelligent - Unlock @@ -251,7 +269,7 @@ Controls the Nuki Smart Lock Single Button Press Action setting. ### Nuki Button: Double Press Action Controls the Nuki Smart Lock Double Button Press Action setting. -##### Options: +##### Optionen: - No Action - Intelligent - Unlock @@ -263,7 +281,7 @@ Controls the Nuki Smart Lock Double Button Press Action setting. ### Nuki Fob: Action 1 Controls the Nuki Smart Lock Fob Action 1 setting. -##### Options: +##### Optionen: - No Action - Unlock - Lock @@ -273,7 +291,7 @@ Controls the Nuki Smart Lock Fob Action 1 setting. ### Nuki Fob: Action 2 Controls the Nuki Smart Lock Fob Action 2 setting. -##### Options: +##### Optionen: - No Action - Unlock - Lock @@ -283,23 +301,17 @@ Controls the Nuki Smart Lock Fob Action 2 setting. ### Nuki Fob: Action 3 Controls the Nuki Smart Lock Fob Action 3 setting. -##### Options: +##### Optionen: - No Action - Unlock - Lock - Lock n Go - Intelligent -### Nuki Timezone -Controls the Nuki Smart Lock Timezone setting. - -##### Options: -Check out the nuki developer documentation. - ### Nuki Advertising Mode Controls the Nuki Smart Lock Advertising Mode setting. -##### Options: +##### Optionen: - Automatic - Normal - Slow @@ -314,21 +326,31 @@ Repräsentiert die Schlosseinheit für dein gekoppeltes Nuki Smart Lock. ## Ereignisse -### Doorbell Pattern -Wird ausgelöst, wenn ein Klingelmuster erkannt wird. Erfahre mehr über Musterereignisse [hier](../guide/automation/pattern-events). +### Entrance Doorbell +Wird ausgelöst, wenn ein Klingelmuster an der Eingangstür erkannt wird. Erfahre mehr über Musterereignisse [hier](../guide/automation/pattern-events). + +##### Ereignistypen +- single +- double +- triple + +### Second Entrance Doorbell +Wird ausgelöst, wenn ein Klingelmuster an der zweiten Eingangstür erkannt wird. Erfahre mehr über Musterereignisse [hier](../guide/automation/pattern-events). + +##### Ereignistypen +- single +- double +- triple + +### Apartment Doorbell +Wird ausgelöst, wenn ein Klingelmuster an der Wohnungstür erkannt wird. Erfahre mehr über Musterereignisse [hier](../guide/automation/pattern-events). ##### Ereignistypen -- apartment_single -- apartment_double -- apartment_triple -- entrance_single -- entrance_double -- entrance_triple -- second_entrance_single -- second_entrance_double -- second_entrance_triple - -### Phone pick up Pattern +- single +- double +- triple + +### Phone pick up Wird ausgelöst, wenn ein Abhebe-Muster des Telefons der Innenstation erkannt wird. Erfahre mehr über Musterereignisse [hier](../guide/automation/pattern-events). ##### Ereignistypen @@ -339,11 +361,8 @@ Wird ausgelöst, wenn ein Abhebe-Muster des Telefons der Innenstation erkannt wi ## Updates -### Firmware -Zeigt an, ob ein Update im stabilen Zweig verfügbar ist, und bietet eine Installation über das HTTP OTA-Updateverfahren an. - -### Firmware -Zeigt an, ob ein Update im Entwicklungszweig verfügbar ist, und bietet eine Installation über das HTTP OTA-Updateverfahren an. +### Firmware +Zeigt an, ob ein Update verfügbar ist, und bietet eine Installation über das HTTP OTA-Updateverfahren an. ## Lichter @@ -362,4 +381,4 @@ Eine kleine WS2812B RGB-LED auf der Doorman-Platine, die verwendet wird, um best - Home Assistant verbunden - Ring To Open ist aktiv - Nuki Kopplungsmodus ist aktiv -- Nuki erfolgreich gekoppelt \ No newline at end of file +- Nuki erfolgreich gekoppelt diff --git a/docs/de/reference/gpio.md b/docs/de/reference/gpio.md index aac8a2c2..5fabd8e5 100644 --- a/docs/de/reference/gpio.md +++ b/docs/de/reference/gpio.md @@ -8,6 +8,7 @@ Der Doorman-S3 nutzt bestimmte GPIO-Pins für spezielle Funktionen und bietet zu | GPIO02 | WS2812B RGB Status-LED | | GPIO08 | TC Bus TX - Kurzschluss des Busses zu Ground | | GPIO09 | TC Bus RX - Liest Bus-Daten / ADC-Eingang (mit integriertem Spannungsteiler - 1M+160K) | +| GPIO10 | Rev >= 1.5 - Verbunden mit GPIO09 - Alternativer ADC Eingang | | GPIO40 | Freier I/O | | GPIO41 | 10K Onboard-Widerstand für externen Button | | GPIO42 | Relais für analogen Türöffner oder Licht | diff --git a/docs/de/reference/schematics.md b/docs/de/reference/schematics.md index 018ef2b6..1e2f5cc3 100644 --- a/docs/de/reference/schematics.md +++ b/docs/de/reference/schematics.md @@ -13,4 +13,4 @@ Nutze die interaktiven Viewer unten, um PCB- und Schaltplan-Designs anzusehen. V ## Interactive BOM -Klicke [hier](../ibom.html), um den interaktiven BOM-Viewer zu öffnen. \ No newline at end of file +Klicke [hier](../ibom.html){target="_self"}, um den interaktiven BOM-Viewer zu öffnen. \ No newline at end of file diff --git a/docs/en/changelog/firmware.md b/docs/en/changelog/firmware.md index 80c57c13..093fcd78 100644 --- a/docs/en/changelog/firmware.md +++ b/docs/en/changelog/firmware.md @@ -1,6 +1,75 @@ # Release Notes & Changelog Welcome to the latest updates! Here's a breakdown of all the **new features**, **improvements**, and important **changes** you need to know. Be sure to check out the **Breaking Changes** section for any actions needed to keep everything running smoothly. +## 2025.2.0 +### 🚀 What's New? +- **Added a Switch to turn off the Status LED while Ring to Open is active** + If you don't want the Status LED to blink while Ring to Open is active, you can now easily turn it off. + +- **Automatic Model Detection** + The setup mode now attempts to automatically identify the indoor station model. However, this process is not compatible with all models, as some do not support automatic detection. + +- **Introduced a button to identify your indoor station model** + You can now effortlessly determine the correct model for your settings by simply pressing the "Identify Indoor Station" button, perfect for cases where you're unsure which model you own. + +- **Expand Support for Model Settings** + Implemented settings compatibility for TCS TASTA (Koch TC60) IVW5xxx and ISW5xxx models. + +### ✨ Improvements +- **Fix Parser Command Length** + Previously, the command length was not properly parsed, which occasionally led to 32-bit commands being misinterpreted. This issue has now been resolved. + +- **Configure Entrance Outdoor Station ID** + It is now feasible to replace the entrance outdoor station in the exceptional instances where non-default addresses are utilized. The setup mode will also set the entrance outdoor station address. + +- **Automatically Disable BLE Server When Not Needed** + The BLE Server is now automatically disabled once Wi-Fi is connected. Note: This behavior applies exclusively to the Stock Firmware. + +- **Fixed Memory Reading for Some Intercom Models** + The memory will now be correctly read from your indoor station. + +- **Automatic Intercom Memory Reading** + The intercom memory is now automatically read during boot and after model identification. Manual memory readings are still possible but no longer necessary. + +- **Experimental Update Switch** + Instead of having two separate update entities, there's now a single one that checks for updates based on a new switch. This switch lets you easily toggle experimental updates from the dev branch on or off. + +### 📝 Other Updates +- **Nuki Component** + The [ESPHome_nuki_lock](https://github.com/uriyacovy/ESPHome_nuki_lock) component now leverages Doorman-S3's PSRAM, potentially enhancing the overall performance. + +### 🚨 Breaking Changes +- **PSRAM Compatibility** + Some users, particularly those with Revision 1.4 PCBs, may encounter issues due to the newly added PSRAM component. This is because certain Revision 1.4 boards use the N16R2 variant of the ESP32S3, which requires a different configuration for proper PSRAM booting. + + For assistance, please contact me via [Discord](https://discord.gg/t2d34dvmBf) or open an issue on [GitHub](https://github.com/AzonInc/Doorman/issues). + +- **Hexadecimal Command-String Length changed** + With the command parser now fixed, the hexadecimal string representation has been updated to correctly display the [Last Bus Command](../reference/entities#last-bus-command) sensor. + +- **Separate Event entities** + [Skaronator](https://github.com/AzonInc/Doorman/pull/37) introduced separate event entities for each physical doorbell button. + This enhancement enables event tracking on a per-button basis, providing more granular and precise support for doorbell interactions. + You will need to adjust your automations if you previously used the Doorbell Pattern Event Entity. Additionally, the event types have been changed. + + 👉 **Check the [Entities](../reference/entities#events) for details!** + +- **Intercom Models Renamed** + As part of streamlining the models for each manufacturer, you may need to reconfigure your intercom model. + Now, you can also see the Koch and Scantron models. + + 👉 **Check the [Model Setting availability](../reference/esphome-component#model-setting-availability) for details!** + +- **Intercom Settings Updated** + To accommodate compatibility with new models, the settings `ringtone_door_call` and `volume_handset` have been renamed. + + 👉 **Refer to the [Setting Types](../reference/esphome-component#setting-types) for the updated names and additional settings!** + +## 2024.11.2 +### ✨ Improvements +- **Fixed open door command** + Use the short open door command instead of the long one (with serial number) as this seems to cause issues on some setups. + ## 2024.11.1 ### ✨ Improvements - **Fixed dev branch name** diff --git a/docs/en/guide/automation/pattern-events.md b/docs/en/guide/automation/pattern-events.md index 0230ef20..12fa67c0 100644 --- a/docs/en/guide/automation/pattern-events.md +++ b/docs/en/guide/automation/pattern-events.md @@ -11,16 +11,15 @@ Check out the [advanced examples](../firmware/stock-firmware#advanced-examples) ## Doorbell Pattern +### Event Sensors +- Entrance Doorbell +- Second Entrance Doorbell +- Apartment Doorbell + ### Event Types -- apartment_single -- apartment_double -- apartment_triple -- entrance_single -- entrance_double -- entrance_triple -- second_entrance_single -- second_entrance_double -- second_entrance_triple +- single +- double +- triple ### Example Automation ::: details Automatically open the door when the Entrance Doorbell is pressed twice in a specific pattern. @@ -31,9 +30,9 @@ description: "Open the entrance door after pressing the entrance doorbell two ti trigger: - platform: state entity_id: - - event.doorman_s3_doorbell_pattern + - event.doorman_s3_entrance_doorbell attribute: event_type - to: entrance_double + to: double condition: [] action: - service: button.press @@ -61,7 +60,7 @@ description: "Toggle Ring To Open Mode when you quickly pick up the phone 3 time trigger: - platform: state entity_id: - - event.doorman_s3_phone_pick_up_pattern + - event.doorman_s3_phone_pick_up attribute: event_type to: triple condition: [] @@ -73,4 +72,4 @@ action: entity_id: switch.doorman_s3_ring_to_open mode: single ``` -::: \ No newline at end of file +::: diff --git a/docs/en/guide/firmware/additions.md b/docs/en/guide/firmware/additions.md index 452d9884..5ea09db8 100644 --- a/docs/en/guide/firmware/additions.md +++ b/docs/en/guide/firmware/additions.md @@ -91,7 +91,7 @@ data: serial_number: 123456 ``` -32-Bit Commands via `command`: +Hexadecimal Commands via `command`: ```yaml service: esphome.doorman_s3_send_tc_command_raw data: @@ -158,7 +158,7 @@ If you want to create a custom doorbell pattern, you can easily extend the exist # Extend the doorbell_pattern event entity // [!code ++] // [!code focus] # Add a new apartment_special event type // [!code ++] // [!code focus] event: // [!code ++] // [!code focus] - - id: !extend doorbell_pattern // [!code ++] // [!code focus] + - id: !extend apartment_doorbell_pattern // [!code ++] // [!code focus] event_types: // [!code ++] // [!code focus] - "apartment_special" // [!code ++] // [!code focus] @@ -177,7 +177,7 @@ binary_sensor: // [!code ++] // [!code focus] then: // [!code ++] // [!code focus] - logger.log: "Special pattern detected!" // [!code ++] // [!code focus] - event.trigger: // [!code ++] // [!code focus] - id: doorbell_pattern // [!code ++] // [!code focus] + id: apartment_doorbell_pattern // [!code ++] // [!code focus] # Use the previously defined new event type here // [!code ++] // [!code focus] event_type: apartment_special // [!code ++] // [!code focus] ``` @@ -221,4 +221,4 @@ binary_sensor: // [!code ++] // [!code focus] - tc_bus.send: // [!code ++] // [!code focus] type: "light" // [!code ++] // [!code focus] ``` -::: \ No newline at end of file +::: diff --git a/docs/en/guide/firmware/mqtt.md b/docs/en/guide/firmware/mqtt.md new file mode 100644 index 00000000..536a3562 --- /dev/null +++ b/docs/en/guide/firmware/mqtt.md @@ -0,0 +1,68 @@ +## MQTT + +When using the MQTT firmware, various topics are published to your broker. Here's how the topic structure and controls work. + +### Topic Structure +Each entity publishes its state to a topic in the following format: +``` +///state +``` + +You can control certain entities by publishing a command to this topic format: +::: code-group +``` [Topic] +///command +``` +``` [Payload] +ON or OFF or whatever is supported +``` +::: + +### Example +To enable or disable the [Ring-To-Open](../automation/ring-to-open.md) automation, send `ON` or `OFF` as the payload to the topic: +::: code-group +``` [Topic] +doorman-s3/switch/ring_to_open/command +``` +``` [Payload] +ON +``` +::: + +### Special Topics +Certain special topics allow for advanced commands. + +#### Send a Command (Hexadecimal) +Here's an example of how to send a hexadecimal command (uint32) to the bus: +::: code-group +``` [Topic] +doorman-s3/send_raw_command +``` +```json [Payload] +{ + "command": 0x1C30BA80 +} +``` +```json [Advanced Payload] +{ + "command": 0x1C30BA80, + "is_long": false +} +``` +::: + +#### Send a Command (Command Builder) +Here's an example of how to use the command builder to send a command to the bus: +::: code-group +``` [Topic] +doorman-s3/send_command +``` +```json [Payload] +{ + "type": "open_door", + "address": 0, + "payload": 0, + "serial_number": 123456 +} +``` +::: \ No newline at end of file diff --git a/docs/en/guide/firmware/nuki-bridge-firmware.md b/docs/en/guide/firmware/nuki-bridge-firmware.md index 0d818591..625f589b 100644 --- a/docs/en/guide/firmware/nuki-bridge-firmware.md +++ b/docs/en/guide/firmware/nuki-bridge-firmware.md @@ -8,11 +8,12 @@ There are several ways to update the firmware: - HTTP OTA - Web Serial -You can connect your Doorman via USB-C and click the button below to install the latest Doorman Nuki Bridge Firmware directly through Web Serial. +You can connect your Doorman via USB-C and click the button below to install the latest Doorman Nuki Bridge Firmware (Home Assistant) directly through Web Serial. +To use the MQTT firmware, adopt Doorman into your ESPHome Dashboard and apply the MQTT Example Firmware YAML.
@@ -30,11 +31,17 @@ You can connect your Doorman via USB-C and click the button below to install the ## Firmware YAML -This is the minimal ESPHome configuration YAML file. Be sure to update the API key. +This is the minimal ESPHome configuration YAML file for use with Home Assistant. Be sure to update the API key. +::: details Minimal Nuki Bridge Firmware (Home Assistant) +```yaml + +``` +::: -::: details Minimal Nuki Bridge Firmware +This is the minimal ESPHome configuration YAML file for use with MQTT. Be sure to update the Broker details. +::: details Minimal Nuki Bridge Firmware (MQTT) ```yaml - + ``` ::: @@ -54,4 +61,6 @@ You can unpair your device using either the `Nuki Unpair Device` button in Home If your lock is already paired with Doorman, press the `FLASH` or `PRG` button on the Doorman PCB for 5 seconds until the RGB status LED starts flashing purple. Your Nuki Lock will then be unpaired. Note that the pairing mode will time out after 30 seconds. ::: - \ No newline at end of file + + + \ No newline at end of file diff --git a/docs/en/guide/firmware/stock-firmware.md b/docs/en/guide/firmware/stock-firmware.md index 087823b1..ba3df08d 100644 --- a/docs/en/guide/firmware/stock-firmware.md +++ b/docs/en/guide/firmware/stock-firmware.md @@ -8,11 +8,12 @@ There are several ways to update the firmware: - HTTP OTA - Web Serial -You can connect your Doorman via USB-C and click the button below to install the latest Doorman Stock Firmware directly through Web Serial. +You can connect your Doorman via USB-C and click the button below to install the latest Doorman Stock Firmware (Home Assistant) directly through Web Serial. +To use the MQTT firmware, adopt Doorman into your ESPHome Dashboard and apply the MQTT Example Firmware YAML.
@@ -30,12 +31,20 @@ You can connect your Doorman via USB-C and click the button below to install the ## Firmware YAML -This is the minimal ESPHome configuration YAML file. Be sure to update the API key. +This is the minimal ESPHome configuration YAML file for use with Home Assistant. Be sure to update the API key. +::: details Minimal Stock Firmware (Home Assistant) +```yaml + +``` +::: -::: details Minimal Stock Firmware +This is the minimal ESPHome configuration YAML file for use with MQTT. Be sure to update the Broker details. +::: details Minimal Stock Firmware (MQTT) ```yaml - + ``` ::: - \ No newline at end of file + + + \ No newline at end of file diff --git a/docs/en/guide/getting-started.md b/docs/en/guide/getting-started.md index ef2b3a72..8fb04649 100644 --- a/docs/en/guide/getting-started.md +++ b/docs/en/guide/getting-started.md @@ -8,16 +8,16 @@ Please note that these instructions are based on the pre-flashed Doorman PCB tha **Thank you so much for using Doorman! ❤️** ## Wiring -First, open your intercom enclosure. On most models, you will find a screw terminal labeled with `a`, `b`, `E`, and `P`. +First, open your indoor station enclosure. On most models, you will find a screw terminal labeled with `a`, `b`, `E`, and `P`. -Connect the `b` line (Ground) to one of the TC:BUS terminals on your Doorman, and connect the `a` line (24V Bus) to the other TC:BUS terminal on your Doorman. +Connect the `b` line (Ground) to one of the TC:BUS terminals on your Doorman, and connect the `a` line (24V Bus) to the other TC:BUS terminal on your Doorman. Doorman is just like any other device on the bus, **connected in parallel**. ::: warning Note -By default, I ship version 1.5 with a jumper cap on `BUS PWR`. Please remove this if you are not connecting Doorman in the `2-Wire Mode via intercom` configuration. +By default, I ship version 1.5 with a jumper cap on `BUS PWR`. Please remove this if you are not connecting Doorman in the `2-Wire Mode via indoor station` configuration. ::: ### Power supply options: -::: details 3-Wire Mode via intercom +::: details 3-Wire Mode via indoor station Connect the `P` line (+24V) to the `P` terminal on your Doorman. > [!WARNING] @@ -41,7 +41,7 @@ Example: ![2-wire external via usb](./images/2wire_power_usb_c.png){width=300px} ::: -::: details 2-Wire Mode via intercom +::: details 2-Wire Mode via indoor station > [!DANGER] Important Info > Using the `a`-bus line as a power source on revisions older than `1.5` results in a loud beeping noise. This issue is likely due to the high-frequency switching power supply. > @@ -50,7 +50,7 @@ Example: After connecting the `a` and `b` lines, you need to connect `BUS PWR` using a jumper cap. Example: -![2-wire via intercom jumper](./images/2wire_intercom.png){width=300px} +![2-wire via bus pwr jumper](./images/2wire_intercom.png){width=300px} ::: @@ -95,10 +95,14 @@ The indoor station must be connected, and the enclosure securely closed, to comp The RGB status LED will pulse green-turquoise. Press the doorbell button at your apartment or entrance. 4. **Complete the Setup:**\ - After pressing the doorbell button, the LED will stay green-turquoise for 3 seconds. Then, the LED will turn off, and the setup is complete. + After pressing the doorbell button, the system will attempt to detect your indoor station model. Once the model detection either succeeds or times out, the LED will remain green-turquoise for 3 seconds. Then, the LED will turn off, and the setup is complete. If you have multiple door stations, the firmware will attempt to automatically detect the additional station. -To enable detection of the second doorbell and the ability to unlock the second door, you must press the second doorbell or unlock the second door at least once to store its address. +To enable detection of the second doorbell and the ability to unlock the second door, you need to press the second doorbell or physically push the unlock button for the second door at least once to store its address. + +::: tip Multiple Indoor Stations +If you have multiple indoor stations, things become a bit more complex. You'll need to create a custom YAML configuration to integrate all the indoor stations. The default firmware only supports a single indoor station. +::: ## ESPHome adoption diff --git a/docs/en/reference/entities.md b/docs/en/reference/entities.md index f8e19cb9..5493d519 100644 --- a/docs/en/reference/entities.md +++ b/docs/en/reference/entities.md @@ -87,12 +87,18 @@ Controls the [Ring To Open](../guide/automation/ring-to-open) automation. ### Ring To Open: Confirmation Controls the turn-on confirmation for the [Ring To Open](../guide/automation/ring-to-open) automation. +### Ring To Open: Display Status +Controls the Status LED for the [Ring To Open](../guide/automation/ring-to-open) automation. + ### Relay Controls the built-in relay. ### Setup Mode Toggles the [Interactive Setup](../guide/getting-started#step-3-interactive-setup) Mode to easily setup your Doorman. +### Experimental Updates +Enables experimental updates, allowing you to easily switch between the master and dev branches to check for the latest changes. + ### Nuki Pairing Mode Controls the Nuki pairing mode. @@ -129,11 +135,6 @@ Controls the Nuki Smart Lock Auto Unlock Disable setting. ### Nuki Single Lock Controls the Nuki Smart Lock Single Lock setting. -### Nuki Daylight Saving Time -Controls the Nuki Smart Lock DST Mode setting. - -### Nuki Automatic Updates -Controls the Nuki Smart Lock Automatic Updates setting. ## Buttons @@ -146,6 +147,12 @@ Opens the second entrance door by sending a `open_door` command with the `addres ### Turn on the Light Turns on the light by sending a `light` command on the bus. +### Identify Indoor Station +Quickly identifies your indoor station model and saves it. +::: note +Not all models are supported as old models might not support this feature. +::: + ### Read Memory Reads the memory of the intercom phone with the specified serial number. @@ -172,6 +179,9 @@ If you configured WiFi using the captive portal, Improv Serial, or Improv BLE, t ### Serial Number Sets the indoor station serial number for the command builder/parser. +### Entrance Door Station ID +Sets the ID of the entrance outdoor station. + ### Second Door Station ID Sets the ID of the second outdoor station. @@ -184,15 +194,15 @@ Setting the delay to the maximum (60 seconds) will result in the automation gene ### Volume: Ringtone Sets the intercom phone ringtone volume. -### Volume: Handset -Sets the intercom phone handset volume. +### Volume: Handset Door Call +Sets the intercom phone handset door call volume. + +### Volume: Handset Internal Call +Sets the intercom phone handset internal call volume. ### Nuki LED: Brightness Controls the Nuki Smart Lock LED Brightness setting. -### Nuki Timezone: Offset -Controls the Nuki Smart Lock Timezone offset setting (Lock v1). - ### Nuki Security Pin Sets the Nuki Bridge Security Pin to authenticate against the Nuki Smart Lock. @@ -218,8 +228,14 @@ Sets the triggering door for the [Ring To Open](../guide/automation/ring-to-open ### Intercom Model Sets the intercom phone model. Check the [Supported Models and Settings](esphome-component#model-setting-availability) to see your options. -### Ringtone: Door Call -Sets the intercom phone ringtone for door calls. +### Ringtone: Entrance Door Call +Sets the intercom phone ringtone for entrance door calls. + +##### Options: +- Ringtone 1 ... 13 + +### Ringtone: Second Entrance Door Call +Sets the intercom phone ringtone for second entrance door calls. ##### Options: - Ringtone 1 ... 13 @@ -290,12 +306,6 @@ Controls the Nuki Smart Lock Fob Action 3 setting. - Lock n Go - Intelligent -### Nuki Timezone -Controls the Nuki Smart Lock Timezone setting. - -##### Options: -Check out the nuki developer documentation. - ### Nuki Advertising Mode Controls the Nuki Smart Lock Advertising Mode setting. @@ -314,21 +324,31 @@ Represents the lock entity for your paired Nuki smart lock. ## Events -### Doorbell Pattern -Triggers each time a doorbell pattern is detected. Learn more about pattern events [here](../guide/automation/pattern-events). +### Entrance Doorbell +Triggers each time a doorbell pattern is detected at the entrance. Learn more about pattern events [here](../guide/automation/pattern-events). + +##### Event Types +- single +- double +- triple + +### Second Entrance Doorbell +Triggers each time a doorbell pattern is detected at the second entrance. Learn more about pattern events [here](../guide/automation/pattern-events). + +##### Event Types +- single +- double +- triple + +### Apartment Doorbell +Triggers each time a doorbell pattern is detected at the apartment. Learn more about pattern events [here](../guide/automation/pattern-events). ##### Event Types -- apartment_single -- apartment_double -- apartment_triple -- entrance_single -- entrance_double -- entrance_triple -- second_entrance_single -- second_entrance_double -- second_entrance_triple - -### Phone pick up Pattern +- single +- double +- triple + +### Phone pick up Triggers each time a phone pick up pattern is detected. Learn more about pattern events [here](../guide/automation/pattern-events). ##### Event Types @@ -338,11 +358,8 @@ Triggers each time a phone pick up pattern is detected. Learn more about pattern ## Updates -### Firmware -Shows if an update on the stable branch is available and offers installation via the HTTP OTA update mechanism. - -### Firmware -Shows if an update on the development branch is available and offers installation via the HTTP OTA update mechanism. +### Firmware +Shows if an update is available and offers installation via the HTTP OTA update mechanism. ## Lights @@ -361,4 +378,4 @@ A small WS2812B RGB LED on the Doorman's PCB is used to indicate specific events - Home Assistant Connected - Ring To Open is Active - Nuki Pairing Mode is Active -- Nuki Paired Successfully \ No newline at end of file +- Nuki Paired Successfully diff --git a/docs/en/reference/esphome-component.md b/docs/en/reference/esphome-component.md index ea038725..c3038efc 100644 --- a/docs/en/reference/esphome-component.md +++ b/docs/en/reference/esphome-component.md @@ -23,16 +23,19 @@ The `tc_bus` hub component offers the following configuration options: | `on_command` | Defines actions to be triggered when a command is received from the intercom. Returns a `CommandData` structure as the `x` variable. | No | | | `on_read_memory_complete` | Defines actions to be triggered when the memory reading is complete. Returns a `std::vector` buffer as the `x` variable. | No | | | `on_read_memory_timeout` | Defines actions to be triggered when the memory reading times out. | No | | +| `on_identify_complete` | Defines actions to be triggered when the identification of the indoor station is complete. Returns a `ModelData` object as the `x` variable. | No | | +| `on_identify_timeout` | Defines actions to be triggered when the identification of the indoor station times out. | No | | ### Text Sensors The `tc_bus` Text Sensor component offers the following configuration options: -| Option | Description | Required | Default | -|------------------------|------------------------------------------------------------------------------------------------|----------|---------------| -| `serial_number` | Indoor Station Serial Number Input to set the serial number of the predefined indoor station. | No | | -| `volume_handset` | Handset Volume Select to set the handset volume of your indoor station | No | | -| `volume_ringtone` | Ringtone Volume Select to set the ringtone volume of your indoor station | No | | +| Option | Description | Required | Default | +|--------------------------------|---------------------------------------------------------------------------------------------------------|----------|---------------| +| `serial_number` | Indoor Station Serial Number Input to set the serial number of the predefined indoor station. | No | | +| `volume_handset_door_call` | Door Call Handset Volume Select to set the handset volume for door calls of your indoor station. | No | | +| `volume_handset_internal_call` | Internal Call Handset Volume Select to set the handset volume for internal calls of your indoor station.| No | | +| `volume_ringtone` | Ringtone Volume Select to set the ringtone volume of your indoor station. | No | | ### Number Inputs @@ -46,12 +49,13 @@ The `tc_bus` Number component offers the following configuration options: ### Select Inputs The `tc_bus` Select component offers the following configuration options: -| Option | Description | Required | Default | -|------------------------|------------------------------------------------------------------------------------------------|----------|---------------| -| `model` | Model Select to set the model of your indoor station (used to read and write settings). Take a look at the [supported models and settings](#model-setting-availability).| No | `None` | -| `ringtone_door_call` | Door Call Ringtone Select to set the door call ringtone of your indoor station | No | | -| `ringtone_floor_call` | Floor Call Ringtone Select to set the floor call ringtone of your indoor station | No | | -| `ringtone_internal_call` | Internal Call Ringtone Select to set the internal call ringtone of your indoor station | No | | +| Option | Description | Required | Default | +|--------------------------------------|----------------------------------------------------------------------------------------------------------------|----------|---------------| +| `model` | Model Select to set the model of your indoor station (used to read and write settings). Take a look at the [supported models and settings](#model-setting-availability).| No | `None` | +| `ringtone_entrance_door_call` | Entrance Door Call Ringtone Select to set the entrance door call ringtone of your indoor station. | No | | +| `ringtone_second_entrance_door_call` | Second Entrance Door Call Ringtone Select to set the second entrance door call ringtone of your indoor station.| No | | +| `ringtone_floor_call` | Floor Call Ringtone Select to set the floor call ringtone of your indoor station. | No | | +| `ringtone_internal_call` | Internal Call Ringtone Select to set the internal call ringtone of your indoor station. | No | | ### Binary Sensors @@ -112,6 +116,25 @@ on_read_memory_timeout: - logger.log: "Failed to read Memory" ``` +### Identification of Indoor Station Complete +The `on_identify_complete` callback of the `tc_bus` hub allows you to utilize the [ModelData](#model-data) structure, accessible as the `x` variable. + +```yaml +on_identify_complete: + - logger.log: "Completed identification!" + - lambda: |- + std::string hexString = str_upper_case(format_hex(x)); + ESP_LOGI("tcs_bus", "Memory Dump: %s", hexString.c_str()); +``` + +### Identification of Indoor Station Timeout +The `on_identify_timeout` callback of the `tc_bus` hub allows you to detect a failed identification of the indoor station. Most probably when a model is too old doesn't support this process. + +```yaml +on_identify_timeout: + - logger.log: "Failed to identify indoor station!" +``` + ## Actions ### Read Memory @@ -123,6 +146,18 @@ on_...: serial_number: 123456 ``` +### Identify Indoor Station +The `tc_bus.identify` action allows you to identify the model of any indoor station using the serial number. +::: tip Note +Not all models support this feature. For older indoor stations, you may need to select the model manually. +::: + +```yaml +on_...: + - tc_bus.identify: + serial_number: 123456 +``` + ### Set Programming Mode The `tc_bus.set_programming_mode` action allows you to enable or disable the programming mode of the control unit. @@ -150,6 +185,8 @@ You can send commands on the bus using the `tc_bus.send` action. ::: tip Note You can either use the `command` field to send a specific command or use the `type`, `address`, `payload`, and `serial_number` fields to create a more complex message. **Both cannot be used at the same time**. + +You can explicitly send a 32-bit command by using the optional `is_long` property, which is useful when the command begins with leading zeros. ::: #### Example 1: Sending a raw Command @@ -159,8 +196,16 @@ on_...: - tc_bus.send: command: 0x1A2B3C4D ``` +#### Example 2: Sending a raw Command with fixed size + +```yaml +on_...: + - tc_bus.send: + command: 0x00000280 + is_long: True +``` -#### Example 2: Sending a Command via Command Builder +#### Example 3: Sending a Command via Command Builder ```yaml on_...: @@ -230,7 +275,7 @@ Be sure to modify the command and event name as needed based on your configurati ## Example YAML Configuration -Here is an example configuration for the TCS Intercom component in ESPHome: +Here is an example configuration for the TC:BUS component in ESPHome: ```yaml external_components: @@ -317,13 +362,13 @@ button: name: "Read Handset volume" on_press: -lambda: |- - ESP_LOGD("TAG", "Handset volume: %i", id(tc_bus_intercom)->get_setting(SETTING_VOLUME_HANDSET)); + ESP_LOGD("TAG", "Handset volume: %i", id(tc_bus_intercom)->get_setting(SETTING_VOLUME_HANDSET_DOOR_CALL)); - platform: template name: "Set Handset volume" on_press: - tc_bus.update_setting: - type: volume_handset + type: volume_handset_door_call value: 7 ``` @@ -338,10 +383,25 @@ struct CommandData { uint8_t address; uint32_t serial_number; uint32_t payload; - uint8_t length; + bool is_long; }; ``` +## Model Data +The `ModelData` structure is used internally in the identification process. + +```c++ +struct ModelData { + Model model = MODEL_NONE; + uint32_t firmware_version = 0; + uint8_t firmware_major = 0; + uint8_t firmware_minor = 0; + uint8_t firmware_patch = 0; + uint8_t hardware_version = 0; + uint8_t category = 0; + uint8_t memory_size = 0; +}; +``` ## Command Types You can use command types in binary sensors and also when [sending commands](#sending-commands): @@ -355,6 +415,7 @@ You can use command types in binary sensors and also when [sending commands](#se - stop_talking_door_call - stop_talking - open_door +- open_door_long - light - door_opened - door_closed @@ -378,25 +439,76 @@ You can use command types in binary sensors and also when [sending commands](#se Here are the available setting types you can use to update the settings of your indoor station: - ringtone_floor_call -- ringtone_door_call +- ringtone_entrance_door_call +- ringtone_second_entrance_door_call - ringtone_internal_call - volume_ringtone -- volume_handset +- volume_handset_door_call +- volume_handset_internal_call ## Model Setting availability Here are the available settings for specific indoor station models: -| Model | Available settings | -|----------------|------------------------------------------------------------------------------------------------------------| -| TCS ISH1030 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call` | -| TCS ISH3030 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| TCS ISH3230 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| TCS ISH3340 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| TCS ISW3030 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| TCS ISW3230 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| TCS ISW3340 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| TCS IVH3222 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call` | -| Koch TC50 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| Koch TCH50 | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | -| Koch TCH50P | `ringtone_floor_call`, `ringtone_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset` | \ No newline at end of file +| Model | Available settings | +|------------------------------|------------------------------------------------------------------------------------------------------------| +| TCS ISH1030 / Koch TTS25 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call` | +| TCS ISH3030 / Koch TCH50 / Scantron Lux2 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISH3130 / Koch TCH50P / Scantron LuxPlus | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISH3230 / Koch TCH50 GFA | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISH3340 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISW3030 / Koch TC50 / Scantron Stilux | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISW3230 / Koch TC50 GFA | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISW3340 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISW3130 / Koch TC50P | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS IVH3222 / Koch VTCH50 / Scantron VLux | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call` | +| TCS IVH4222 / Koch VTCH50/2D | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call` | +| TCS ISW3330 / Koch TC50 BW | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call` | +| TCS ISW5010 / Koch TC60 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS ISW5020 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS ISW5030 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS ISW5031 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS ISW5033 | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS IVW511x / Koch VTC60/2D / Scantron VIVO | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS IVW521x | `ringtone_floor_call`, `ringtone_entrance_door_call`, `ringtone_second_entrance_door_call`, `ringtone_internal_call`, `volume_ringtone`, `volume_handset_door_call`, `volume_handset_internal_call` | +| TCS ISW6010 | Verification and implementation required | +| TCS ISW6031 | Verification and implementation required | +| TCS IVW6511 | Verification and implementation required | +| TCS ISW7030 / Koch TC70 | Verification and implementation required | +| TCS IVW7510 / Koch VTC70 | Verification and implementation required | +| TCS ISH7030 / Koch TCH70 | Verification and implementation required | +| TCS IVH7510 / Koch VTCH70 | Verification and implementation required | +| TCS ISWM7000 | Verification and implementation required | +| TCS IVWM7000 | Verification and implementation required | +| TCS ISW4100 / Koch TC31 | Verification and implementation required | +| TCS IMM2100 / Koch TCE31 | Verification and implementation required | +| TCS IVW2210 / Koch Ecoos | Verification and implementation required | +| TCS IVW2211 / Koch Ecoos | Verification and implementation required | +| TCS IVW2212 / Koch Ecoos / Scantron SLIM60T | Verification and implementation required | +| TCS VTC42V2 | Verification and implementation required | +| TCS TC40V2 | Verification and implementation required | +| TCS VTC40 | Verification and implementation required | +| TCS TC40 | Verification and implementation required | +| TCS TC2000 | Verification and implementation required | +| TCS TC20P | Verification and implementation required | +| TCS TC20F | Verification and implementation required | +| TCS ISH3022 | Verification and implementation required | +| TCS ISW3022 | Verification and implementation required | +| TCS IMM1000 / Koch TCH30 | Verification and implementation required | +| TCS IMM1100 / Koch TCHE30 | Verification and implementation required | +| TCS IMM1300 / Koch VTCH30 | Verification and implementation required | +| TCS IMM1310 / Koch VTCHE30 | Verification and implementation required | +| TCS IMM1110 / Koch TCHEE30 | Verification and implementation required | +| TCS IMM1500 | Verification and implementation required | +| TCS IVW2220 / Koch Sky | Verification and implementation required | +| TCS IVW2221 / Koch Sky R1.00 | Verification and implementation required | +| TCS IVW3011 / Koch Skyline Plus | Verification and implementation required | +| TCS IVW3012 / Koch Skyline/Aldup | Verification and implementation required | +| TCS VMH / Koch VMH | Verification and implementation required | +| TCS VML / Koch VML | Verification and implementation required | +| TCS VMF / Koch VMF | Verification and implementation required | +| Jung TKIS | Verification and implementation required | +| Jung TKISV | Verification and implementation required | +| TCS CAIXXXX / Koch CAIXXXX | Verification and implementation required | +| TCS CAI2000 / Koch Carus | Verification and implementation required | +| TCS ISW42X0 | Verification and implementation required | \ No newline at end of file diff --git a/docs/en/reference/gpio.md b/docs/en/reference/gpio.md index 92b65fd5..feeb1bd2 100644 --- a/docs/en/reference/gpio.md +++ b/docs/en/reference/gpio.md @@ -8,6 +8,7 @@ The Doorman-S3 uses certain GPIO pins for specific functions and provides additi | GPIO02 | WS2812B RGB Status LED | | GPIO08 | TC:BUS TX - Shorts the Bus to Ground | | GPIO09 | TC:BUS RX - Reads Bus Data / ADC input (with onboard Voltage Divider - 1M+160K) | +| GPIO10 | Rev >= 1.5 - Connected to GPIO09, alternative ADC input | | GPIO40 | Free I/O | | GPIO41 | 10K Onboard Resistor for External Button | | GPIO42 | Relay for Analog Door Opener or Light | diff --git a/docs/en/reference/schematics.md b/docs/en/reference/schematics.md index 7b608394..75b648fe 100644 --- a/docs/en/reference/schematics.md +++ b/docs/en/reference/schematics.md @@ -13,4 +13,4 @@ To view the PCB and schematic designs, use the interactive viewers below. If you ## Interactive BOM -Click [here](../ibom.html) to open the interactive BOM viewer. \ No newline at end of file +Click [here](../ibom.html){target="_self"} to open the interactive BOM viewer. \ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json index 83d4fdcf..2be9f227 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,12 +1,12 @@ { "name": "doorman-docs", - "version": "2024.8.0", + "version": "2024.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "doorman-docs", - "version": "2024.8.0", + "version": "2024.10.0", "dependencies": { "@tresjs/cientos": "^4.0.0", "@tresjs/core": "^4.2.7", @@ -24,41 +24,41 @@ "unplugin": "^1.12.2", "unplugin-vue-components": "^0.27.4", "vite-svg-loader": "^5.1.0", - "vitepress": "^1.3.2" + "vitepress": "^1.6.3" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.9.5" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "dev": true, "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "dev": true, "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "dev": true, "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -66,161 +66,193 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "dev": true, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", - "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", + "node_modules/@algolia/client-abtesting": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.20.0.tgz", + "integrity": "sha512-YaEoNc1Xf2Yk6oCfXXkZ4+dIPLulCx8Ivqj0OsdkHWnsI3aOJChY5qsfyHhDBNSOhqn2ilgHWxSfyZrjxBcAww==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/cache-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", - "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", - "dev": true - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", - "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", + "node_modules/@algolia/client-analytics": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.20.0.tgz", + "integrity": "sha512-CIT9ni0+5sYwqehw+t5cesjho3ugKQjPVy/iPiJvtJX4g8Cdb6je6SPt2uX72cf2ISiXCAX9U3cY0nN0efnRDw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-account": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", - "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", + "node_modules/@algolia/client-common": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.20.0.tgz", + "integrity": "sha512-iSTFT3IU8KNpbAHcBUJw2HUrPnMXeXLyGajmCL7gIzWOsYM4GabZDHXOFx93WGiXMti1dymz8k8R+bfHv1YZmA==", "dev": true, - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/transporter": "4.24.0" + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-analytics": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", - "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", + "node_modules/@algolia/client-insights": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.20.0.tgz", + "integrity": "sha512-w9RIojD45z1csvW1vZmAko82fqE/Dm+Ovsy2ElTsjFDB0HMAiLh2FO86hMHbEXDPz6GhHKgGNmBRiRP8dDPgJg==", "dev": true, "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "node_modules/@algolia/client-personalization": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.20.0.tgz", + "integrity": "sha512-p/hftHhrbiHaEcxubYOzqVV4gUqYWLpTwK+nl2xN3eTrSW9SNuFlAvUBFqPXSVBqc6J5XL9dNKn3y8OA1KElSQ==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-personalization": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", - "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "node_modules/@algolia/client-query-suggestions": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.20.0.tgz", + "integrity": "sha512-m4aAuis5vZi7P4gTfiEs6YPrk/9hNTESj3gEmGFgfJw3hO2ubdS4jSId1URd6dGdt0ax2QuapXufcrN58hPUcw==", "dev": true, "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.20.0.tgz", + "integrity": "sha512-KL1zWTzrlN4MSiaK1ea560iCA/UewMbS4ZsLQRPoDTWyrbDKVbztkPwwv764LAqgXk0fvkNZvJ3IelcK7DqhjQ==", "dev": true, "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/logger-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", - "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", - "dev": true + "node_modules/@algolia/ingestion": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.20.0.tgz", + "integrity": "sha512-shj2lTdzl9un4XJblrgqg54DoK6JeKFO8K8qInMu4XhE2JuB8De6PUuXAQwiRigZupbI0xq8aM0LKdc9+qiLQA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" + } }, - "node_modules/@algolia/logger-console": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", - "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "node_modules/@algolia/monitoring": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.20.0.tgz", + "integrity": "sha512-aF9blPwOhKtWvkjyyXh9P5peqmhCA1XxLBRgItT+K6pbT0q4hBDQrCid+pQZJYy4HFUKjB/NDDwyzFhj/rwKhw==", "dev": true, "dependencies": { - "@algolia/logger-common": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", - "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.20.0.tgz", + "integrity": "sha512-T6B/WPdZR3b89/F9Vvk6QCbt/wrLAtrGoL8z4qPXDFApQ8MuTFWbleN/4rHn6APWO3ps+BUePIEbue2rY5MlRw==", "dev": true, "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.20.0.tgz", + "integrity": "sha512-t6//lXsq8E85JMenHrI6mhViipUT5riNhEfCcvtRsTV+KIBpC6Od18eK864dmBhoc5MubM0f+sGpKOqJIlBSCg==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/requester-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", - "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", - "dev": true - }, - "node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "node_modules/@algolia/requester-fetch": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.20.0.tgz", + "integrity": "sha512-FHxYGqRY+6bgjKsK4aUsTAg6xMs2S21elPe4Y50GB0Y041ihvw41Vlwy2QS6K9ldoftX4JvXodbKTcmuQxywdQ==", "dev": true, "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/transporter": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", - "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", + "node_modules/@algolia/requester-node-http": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.20.0.tgz", + "integrity": "sha512-kmtQClq/w3vtPteDSPvaW9SPZL/xrIgMrxZyAgsFwrJk0vJxqyC5/hwHmrCraDnStnGSADnLpBf4SpZnwnkwWw==", "dev": true, "dependencies": { - "@algolia/cache-common": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@alvarosabu/utils": { @@ -695,31 +727,31 @@ } }, "node_modules/@docsearch/css": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", - "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", "dev": true }, "node_modules/@docsearch/js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.1.tgz", - "integrity": "sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", "dev": true, "dependencies": { - "@docsearch/react": "3.6.1", + "@docsearch/react": "3.8.2", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", - "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", "dev": true, "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.1", - "algoliasearch": "^4.19.1" + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 19.0.0", @@ -1144,6 +1176,15 @@ "@iconify/types": "*" } }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.22.tgz", + "integrity": "sha512-0UzThRMwHuOJfgpp+tyV/y2uEBLjFVrxC4igv9iWjSEQEBK4tNjWZNTRCBCYyv/FwWVYyKAsA8tZQ8vUYzvFnw==", + "dev": true, + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -1522,23 +1563,84 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.12.1.tgz", - "integrity": "sha512-biCz/mnkMktImI6hMfMX3H9kOeqsInxWEyCHbSlL8C/2TR1FqfmGxTLRNwYCKsyCyxWLbB8rEqXRVZuyxuLFmA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.2.0.tgz", + "integrity": "sha512-U+vpKdsQDWuX3fPTCkSc8XPX9dCaS+r+qEP1XhnU30yxRFo2OxHJmY2H5rO1q+v0zB5R2vobsxEFt5uPf31CGQ==", "dev": true, "dependencies": { - "@types/hast": "^3.0.4" + "@shikijs/engine-javascript": "2.2.0", + "@shikijs/engine-oniguruma": "2.2.0", + "@shikijs/types": "2.2.0", + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.2.0.tgz", + "integrity": "sha512-96SpZ4V3UVMtpSPR5QpmU395CNrQiRPszXK62m8gKR2HMA0653ruce7omS5eX6EyAyFSYHvBWtTuspiIsHpu4A==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.2.0", + "@shikijs/vscode-textmate": "^10.0.1", + "oniguruma-to-es": "^2.3.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.2.0.tgz", + "integrity": "sha512-wowCKwkvPFFMXFkiKK/a2vs5uTCc0W9+O9Xcu/oqFP6VoDFe14T8u/D+Rl4dCJJSOyeynP9mxNPJ82T5JHTNCw==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.2.0", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.2.0.tgz", + "integrity": "sha512-RSWLH3bnoyG6O1kZ2msh5jOkKKp8eENwyT30n62vUtXfp5cxkF/bpWPpO+p4+GAPhL2foBWR2kOerwkKG0HXlQ==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.2.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.2.0.tgz", + "integrity": "sha512-8Us9ZF2mV9kuh+4ySJ9MzrUDIpc2RIkRfKBZclkliW1z9a0PlGU2U7fCkItZZHpR5e4/ft5BzuO+GDqombC6Aw==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.2.0" } }, "node_modules/@shikijs/transformers": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.12.1.tgz", - "integrity": "sha512-zOpj/S2thBvnJV4Ty3EE8aRs/VqCbV+lgtEYeBRkPxTW22uLADEIZq0qjt5W2Rfy2KSu29e73nRyzp4PefjUTg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.2.0.tgz", + "integrity": "sha512-zrj7OcSKAh3KL4Jgv45aKS6lSPXZWq61/DyXJJ5gsBMUIE5Ojmnvmseit7H8zQ/xPQOgJP+XqEzy7utScv0N9w==", + "dev": true, + "dependencies": { + "@shikijs/core": "2.2.0", + "@shikijs/types": "2.2.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.2.0.tgz", + "integrity": "sha512-wkZZKs80NtW5Jp/7ONI1j7EdXSatX2BKMS7I01wliDa09gJKHkZyVqlEMRka/mjT5Qk9WgAyitoCKgGgbsP/9g==", "dev": true, "dependencies": { - "shiki": "1.12.1" + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" } }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", + "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", + "dev": true + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -1715,6 +1817,15 @@ "@types/mdurl": "^2" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", @@ -1756,9 +1867,9 @@ } }, "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "dev": true }, "node_modules/@types/web-bluetooth": { @@ -1771,6 +1882,12 @@ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.20.tgz", "integrity": "sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==" }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, "node_modules/@unocss/astro": { "version": "0.62.3", "resolved": "https://registry.npmjs.org/@unocss/astro/-/astro-0.62.3.tgz", @@ -2121,81 +2238,81 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.2.tgz", - "integrity": "sha512-nY9IwH12qeiJqumTCLJLE7IiNx7HZ39cbHaysEUd+Myvbz9KAqd2yq+U01Kab1R/H1BmiyM2ShTYlNH32Fzo3A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", "dev": true, "engines": { "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "node_modules/@vue/compiler-core": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.37.tgz", - "integrity": "sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.37", - "entities": "^5.0.0", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.37.tgz", - "integrity": "sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "dependencies": { - "@vue/compiler-core": "3.4.37", - "@vue/shared": "3.4.37" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.37.tgz", - "integrity": "sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==", - "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.37", - "@vue/compiler-dom": "3.4.37", - "@vue/compiler-ssr": "3.4.37", - "@vue/shared": "3.4.37", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.40", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.37.tgz", - "integrity": "sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "dependencies": { - "@vue/compiler-dom": "3.4.37", - "@vue/shared": "3.4.37" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/devtools-api": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.3.7.tgz", - "integrity": "sha512-kvjQ6nmsqTp7SrmpwI2G0MgbC4ys0bPsgQirHXJM8y1m7siQ5RnWQUHJVfyUrHNguCySW1cevAdIw87zrPTl9g==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.1.tgz", + "integrity": "sha512-Cexc8GimowoDkJ6eNelOPdYIzsu2mgNyp0scOQ3tiaYSb9iok6LOESSsJvHaI+ib3joRfqRJNLkHFjhNuWA5dg==", "dev": true, "dependencies": { - "@vue/devtools-kit": "^7.3.7" + "@vue/devtools-kit": "^7.7.1" } }, "node_modules/@vue/devtools-kit": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.7.tgz", - "integrity": "sha512-ktHhhjI4CoUrwdSUF5b/MFfjrtAtK8r4vhOkFyRN5Yp9kdXTwsRBYcwarHuP+wFPKf4/KM7DVBj2ELO8SBwdsw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.1.tgz", + "integrity": "sha512-yhZ4NPnK/tmxGtLNQxmll90jIIXdb2jAhPF76anvn5M/UkZCiLJy28bYgPIACKZ7FCosyKoaope89/RsFJll1w==", "dev": true, "dependencies": { - "@vue/devtools-shared": "^7.3.7", - "birpc": "^0.2.17", + "@vue/devtools-shared": "^7.7.1", + "birpc": "^0.2.19", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", @@ -2204,58 +2321,58 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.7.tgz", - "integrity": "sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.1.tgz", + "integrity": "sha512-BtgF7kHq4BHG23Lezc/3W2UhK2ga7a8ohAIAGJMBr4BkxUFzhqntQtCiuL1ijo2ztWnmusymkirgqUrXoQKumA==", "dev": true, "dependencies": { "rfdc": "^1.4.1" } }, "node_modules/@vue/reactivity": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.37.tgz", - "integrity": "sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dependencies": { - "@vue/shared": "3.4.37" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.37.tgz", - "integrity": "sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "dependencies": { - "@vue/reactivity": "3.4.37", - "@vue/shared": "3.4.37" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.37.tgz", - "integrity": "sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "dependencies": { - "@vue/reactivity": "3.4.37", - "@vue/runtime-core": "3.4.37", - "@vue/shared": "3.4.37", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.37.tgz", - "integrity": "sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "dependencies": { - "@vue/compiler-ssr": "3.4.37", - "@vue/shared": "3.4.37" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.4.37" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.37.tgz", - "integrity": "sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==" + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" }, "node_modules/@vueuse/core": { "version": "10.11.1", @@ -2297,14 +2414,14 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.11.1.tgz", - "integrity": "sha512-Y5hCGBguN+vuVYTZmdd/IMXLOdfS60zAmDmFYc4BKBcMUPZH1n4tdyDECCPjXm0bNT3ZRUy1xzTLGaUje8Xyaw==", + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.5.0.tgz", + "integrity": "sha512-HYLt8M6mjUfcoUOzyBcX2RjpfapIwHPBmQJtTmXOQW845Y/Osu9VuTJ5kPvnmWJ6IUa05WpblfOwZ+P0G4iZsQ==", "dev": true, "dependencies": { - "@vueuse/core": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" + "@vueuse/core": "12.5.0", + "@vueuse/shared": "12.5.0", + "vue": "^3.5.13" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -2312,16 +2429,16 @@ "peerDependencies": { "async-validator": "^4", "axios": "^1", - "change-case": "^4", - "drauu": "^0.3", + "change-case": "^5", + "drauu": "^0.4", "focus-trap": "^7", - "fuse.js": "^6", + "fuse.js": "^7", "idb-keyval": "^6", - "jwt-decode": "^3", + "jwt-decode": "^4", "nprogress": "^0.2", "qrcode": "^1.5", "sortablejs": "^1", - "universal-cookie": "^6" + "universal-cookie": "^7" }, "peerDependenciesMeta": { "async-validator": { @@ -2362,30 +2479,40 @@ } } }, - "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "node_modules/@vueuse/integrations/node_modules/@vueuse/core": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.5.0.tgz", + "integrity": "sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==", "dev": true, - "hasInstallScript": true, - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "12.5.0", + "@vueuse/shared": "12.5.0", + "vue": "^3.5.13" }, "funding": { "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/metadata": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.5.0.tgz", + "integrity": "sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations/node_modules/@vueuse/shared": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.5.0.tgz", + "integrity": "sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==", + "dev": true, + "dependencies": { + "vue": "^3.5.13" }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/metadata": { @@ -2445,26 +2572,27 @@ } }, "node_modules/algoliasearch": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", - "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", - "dev": true, - "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-account": "4.24.0", - "@algolia/client-analytics": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-personalization": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/recommend": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.20.0.tgz", + "integrity": "sha512-groO71Fvi5SWpxjI9Ia+chy0QBwT61mg6yxJV27f5YFf+Mw+STT75K6SHySpP8Co5LsCrtsbCH5dJZSRtkSKaQ==", + "dev": true, + "dependencies": { + "@algolia/client-abtesting": "5.20.0", + "@algolia/client-analytics": "5.20.0", + "@algolia/client-common": "5.20.0", + "@algolia/client-insights": "5.20.0", + "@algolia/client-personalization": "5.20.0", + "@algolia/client-query-suggestions": "5.20.0", + "@algolia/client-search": "5.20.0", + "@algolia/ingestion": "1.20.0", + "@algolia/monitoring": "1.20.0", + "@algolia/recommend": "5.20.0", + "@algolia/requester-browser-xhr": "5.20.0", + "@algolia/requester-fetch": "5.20.0", + "@algolia/requester-node-http": "5.20.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/ansi-colors": { @@ -2520,9 +2648,9 @@ } }, "node_modules/birpc": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.17.tgz", - "integrity": "sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==", + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/antfu" @@ -2654,6 +2782,16 @@ } ] }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2668,6 +2806,26 @@ "node": ">=4" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/cheerio": { "version": "1.0.0-rc.10", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", @@ -2750,6 +2908,16 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -2969,12 +3137,34 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/destr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", "dev": true }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -3056,10 +3246,16 @@ "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", "dev": true }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true + }, "node_modules/entities": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-5.0.0.tgz", - "integrity": "sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "engines": { "node": ">=0.12" }, @@ -3561,9 +3757,9 @@ } }, "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", + "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", "dev": true, "dependencies": { "tabbable": "^6.2.0" @@ -3691,12 +3887,58 @@ "node": ">=4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", + "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "dev": true }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -4041,6 +4283,27 @@ "speech-rule-engine": "^4.0.6" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -4085,6 +4348,95 @@ "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==", "dev": true }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4126,9 +4478,9 @@ } }, "node_modules/minisearch": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", - "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.1.tgz", + "integrity": "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==", "dev": true }, "node_modules/mitt": { @@ -4171,9 +4523,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -4259,6 +4611,17 @@ "ufo": "^1.5.3" } }, + "node_modules/oniguruma-to-es": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", + "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", + "dev": true, + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.1.1", + "regex-recursion": "^5.1.1" + } + }, "node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -4352,9 +4715,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -4380,9 +4743,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "funding": [ { "type": "opencollective", @@ -4398,8 +4761,8 @@ } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -4412,15 +4775,25 @@ "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" }, "node_modules/preact": { - "version": "10.23.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.1.tgz", - "integrity": "sha512-O5UdRsNh4vdZaTieWe3XOgSpdMAmkIYBCT3VhQDlKrzyCm8lUYsk0fmVEvoQQifoOjFRTaHZO69ylrzTW2BH+A==", + "version": "10.25.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", + "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4483,6 +4856,31 @@ "node": ">=8.10.0" } }, + "node_modules/regex": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", + "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", + "dev": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", + "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", + "dev": true, + "dependencies": { + "regex": "^5.1.1", + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4612,9 +5010,9 @@ ] }, "node_modules/search-insights": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.16.2.tgz", - "integrity": "sha512-+KrS5rnYlyWgzoCNJGsNPw7Vv+47Y7Ze7KZ+/9Xls+5BUugEbU2yv1n9JsQOqv+MLKYfg3bxI5K6tYJxXZY8FA==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "dev": true, "peer": true }, @@ -4628,12 +5026,18 @@ } }, "node_modules/shiki": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.12.1.tgz", - "integrity": "sha512-nwmjbHKnOYYAe1aaQyEBHvQymJgfm86ZSS7fT8OaPRr4sbAcBNz7PbfAikMEFSDQ6se2j2zobkXvVKcBOm0ysg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.2.0.tgz", + "integrity": "sha512-3uoZBmc+zpd2JOEeTvKP/vK5UVDDe8YiigkT9flq+MV5Z1MKFiUXfbLIvHfqcJ+V90StDiP1ckN97z1WlhC6cQ==", "dev": true, "dependencies": { - "@shikijs/core": "1.12.1", + "@shikijs/core": "2.2.0", + "@shikijs/engine-javascript": "2.2.0", + "@shikijs/engine-oniguruma": "2.2.0", + "@shikijs/langs": "2.2.0", + "@shikijs/themes": "2.2.0", + "@shikijs/types": "2.2.0", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, @@ -4689,6 +5093,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/speakingurl": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", @@ -4755,6 +5169,20 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strtok3": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", @@ -4773,9 +5201,9 @@ } }, "node_modules/superjson": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", - "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", "dev": true, "dependencies": { "copy-anything": "^3.0.2" @@ -4889,18 +5317,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/svgo/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -5106,6 +5522,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", @@ -5255,6 +5681,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unocss": { "version": "0.62.3", "resolved": "https://registry.npmjs.org/unocss/-/unocss-0.62.3.tgz", @@ -5398,10 +5892,38 @@ "node": ">=10" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "dependencies": { "esbuild": "^0.21.3", @@ -5470,27 +5992,29 @@ } }, "node_modules/vitepress": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.3.2.tgz", - "integrity": "sha512-6gvecsCuR6b1Cid4w19KQiQ02qkpgzFRqiG0v1ZBekGkrZCzsxdDD5y4WH82HRXAOhU4iZIpzA1CsWqs719rqA==", - "dev": true, - "dependencies": { - "@docsearch/css": "^3.6.0", - "@docsearch/js": "^3.6.0", - "@shikijs/core": "^1.10.3", - "@shikijs/transformers": "^1.10.3", - "@types/markdown-it": "^14.1.1", - "@vitejs/plugin-vue": "^5.0.5", - "@vue/devtools-api": "^7.3.5", - "@vue/shared": "^3.4.31", - "@vueuse/core": "^10.11.0", - "@vueuse/integrations": "^10.11.0", - "focus-trap": "^7.5.4", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.3.tgz", + "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", + "dev": true, + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", "mark.js": "8.11.1", - "minisearch": "^7.0.0", - "shiki": "^1.10.3", - "vite": "^5.3.3", - "vue": "^3.4.31" + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" }, "bin": { "vitepress": "bin/vitepress.js" @@ -5508,16 +6032,52 @@ } } }, + "node_modules/vitepress/node_modules/@vueuse/core": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.5.0.tgz", + "integrity": "sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "12.5.0", + "@vueuse/shared": "12.5.0", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vitepress/node_modules/@vueuse/metadata": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.5.0.tgz", + "integrity": "sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/vitepress/node_modules/@vueuse/shared": { + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.5.0.tgz", + "integrity": "sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==", + "dev": true, + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/vue": { - "version": "3.4.37", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.37.tgz", - "integrity": "sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "dependencies": { - "@vue/compiler-dom": "3.4.37", - "@vue/compiler-sfc": "3.4.37", - "@vue/runtime-dom": "3.4.37", - "@vue/server-renderer": "3.4.37", - "@vue/shared": "3.4.37" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" @@ -5652,6 +6212,16 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/docs/package.json b/docs/package.json index e5f84cf2..f8c5b122 100644 --- a/docs/package.json +++ b/docs/package.json @@ -21,7 +21,7 @@ "unplugin": "^1.12.2", "unplugin-vue-components": "^0.27.4", "vite-svg-loader": "^5.1.0", - "vitepress": "^1.3.2" + "vitepress": "^1.6.3" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "^4.9.5" diff --git a/enclosure/Top.stl b/enclosure/Top.stl index df7eb9d9..cb786345 100644 Binary files a/enclosure/Top.stl and b/enclosure/Top.stl differ diff --git a/firmware/.gitignore b/firmware/.gitignore new file mode 100644 index 00000000..d8b4157a --- /dev/null +++ b/firmware/.gitignore @@ -0,0 +1,5 @@ +# Gitignore settings for ESPHome +# This is an example and may include too much for your use-case. +# You can modify this file to suit your needs. +/.esphome/ +/secrets.yaml diff --git a/firmware/addons/debug-utilities.yaml b/firmware/addons/debug-utilities.yaml new file mode 100644 index 00000000..35a85db6 --- /dev/null +++ b/firmware/addons/debug-utilities.yaml @@ -0,0 +1,203 @@ +# Debug Utilities Addon + +globals: + - id: search_mode + type: int + restore_value: no + initial_value: "0" + + - id: as_list + type: std::vector + initial_value: '' + + - id: is0_list + type: std::vector + initial_value: '' + + - id: is1_list + type: std::vector + initial_value: '' + +debug: + update_interval: 5s + +text_sensor: + - platform: debug + reset_reason: + name: "Reset Reason" + id: debug_restart_reason + entity_category: DIAGNOSTIC + web_server: + sorting_group_id: sorting_group_diagnostic + +binary_sensor: + - platform: template + id: debug_bus_connected + name: Bus Connection Status + icon: "mdi:connection" + disabled_by_default: true + lambda: |- + if (id(debug_bus_voltage).state >= 3) { + return true; + } else { + return false; + } + entity_category: DIAGNOSTIC + web_server: + sorting_group_id: sorting_group_diagnostic + +sensor: + - platform: adc + id: debug_bus_voltage + name: Bus Voltage + pin: GPIO10 + update_interval: 1s + attenuation: 12dB + entity_category: DIAGNOSTIC + disabled_by_default: true + icon: "mdi:current-dc" + web_server: + sorting_group_id: sorting_group_diagnostic + sorting_weight: -5 + +text: + - platform: template + id: debug_send_command + mode: text + name: "Send Command" + icon: "mdi:send" + disabled_by_default: true + optimistic: true + set_action: + then: + - lambda: |- + x.erase(std::remove_if(x.begin(), x.end(), [](char c) { return !std::isxdigit(c); }), x.end()); + x.erase(0, x.find_first_not_of('0')); + x.resize(8); + unsigned long number = 0; + if(std::string(x.c_str()) != "") { + number = std::stoul(x.c_str(), nullptr, 16); + } + id(tc_bus_intercom)->send_command(number); + web_server: + sorting_group_id: sorting_group_system + +button: + - platform: template + id: debug_restart_control_unit + name: "System Restart" + icon: mdi:restart + disabled_by_default: true + on_press: + - tc_bus.send: + command: 0x5100 + web_server: + sorting_group_id: sorting_group_system + + - platform: template + id: debug_system_discovery + name: "System Discovery" + icon: "mdi:home-search" + disabled_by_default: true + on_press: + # Clear Lists + - lambda: |- + id(as_list).clear(); + id(is0_list).clear(); + id(is1_list).clear(); + - light.turn_on: + id: doorman_rgb_status_led + red: 0% + green: 100% + blue: 10% + effect: pulse + # Search Outdoor Stations + - logger.log: Search outdoor stations... + - tc_bus.send: + command: 0x5802 + - globals.set: + id: search_mode + value: "1" + - delay: 350ms + - tc_bus.send: + command: 0x5200 + # Wait until every outdoor station sent response + - delay: 5s + # Search Indoor Stations 0 + - logger.log: Search indoor stations (0)... + - globals.set: + id: search_mode + value: "2" + - tc_bus.send: + command: 0x5800 + - delay: 350ms + - tc_bus.send: + command: 0x5200 + # Wait until every indoor station sent response + - delay: 10s + # Search Indoor Stations 1 + - logger.log: Search indoor stations (1)... + - globals.set: + id: search_mode + value: "3" + - tc_bus.send: + command: 0x5801 + - delay: 350ms + - tc_bus.send: + command: 0x5200 + # Wait until every indoor station sent response + - delay: 10s + # Complete + - globals.set: + id: search_mode + value: "0" + - logger.log: + format: "Search complete! Found %i outdoor station(s), %i 0-indoor station(s) and %i 1-indoor station(s)." + args: [ 'id(as_list).size()', 'id(is0_list).size()', 'id(is1_list).size()'] + level: info + - lambda: |- + for (const auto& value : id(as_list)) + { + ESP_LOGI("DEBUG", "AS: %d", value); + } + + for (const auto& value : id(is0_list)) + { + ESP_LOGI("DEBUG", "IS0: %d", value); + } + + for (const auto& value : id(is1_list)) + { + ESP_LOGI("DEBUG", "IS1: %d", value); + } + - light.turn_on: + id: doorman_rgb_status_led + red: 0% + green: 100% + blue: 10% + effect: none + - delay: 3s + - script.execute: update_led + web_server: + sorting_group_id: sorting_group_system + +# Extend TC:BUS Component +tc_bus: + on_command: + - if: + condition: + - lambda: |- + return x.type == COMMAND_TYPE_FOUND_DEVICE && id(search_mode) != 0; + then: + - lambda: |- + std::vector& list = (id(search_mode) == 1 ? id(as_list) : (id(search_mode) == 2 ? id(is0_list) : id(is1_list))); + bool found = false; + for (const auto& s : list) { + if (s == x.serial_number) { + found = true; + break; + } + } + if (!found) { + list.push_back(x.serial_number); + } \ No newline at end of file diff --git a/firmware/addons/homeassistant.yaml b/firmware/addons/homeassistant.yaml new file mode 100644 index 00000000..4fcfce37 --- /dev/null +++ b/firmware/addons/homeassistant.yaml @@ -0,0 +1,54 @@ +# Home Assistant Addon + +esphome: + on_boot: + then: + - light.turn_on: + id: doorman_rgb_status_led + effect: pulse + red: 100% + green: 65% + blue: 0% + - wait_until: + condition: + wifi.connected: + - light.turn_on: + id: doorman_rgb_status_led + effect: slow_pulse + red: 0% + green: 22% + blue: 100% + - wait_until: + condition: + api.connected: + - light.turn_on: + id: doorman_rgb_status_led + effect: none + red: 0% + green: 22% + blue: 100% + color_brightness: 60% + - delay: 3s + - script.execute: update_led + +api: + reboot_timeout: 0s + actions: + - action: send_tc_command_raw + variables: + command: int + then: + - tc_bus.send: + command: !lambda 'return command;' + - action: send_tc_command + variables: + type: string + address: int + payload: int + serial_number: int + then: + - tc_bus.send: + type: !lambda 'return string_to_command_type(type);' + address: !lambda 'return address;' + payload: !lambda 'return payload;' + serial_number: !lambda 'return serial_number;' \ No newline at end of file diff --git a/firmware/addons/interactive-setup-old.yaml b/firmware/addons/interactive-setup-old.yaml deleted file mode 100644 index 8586d619..00000000 --- a/firmware/addons/interactive-setup-old.yaml +++ /dev/null @@ -1,192 +0,0 @@ -# WIP - -globals: - - id: search_mode - type: int - restore_value: no - initial_value: "0" - - - id: as_list - type: std::vector - initial_value: '' - - - id: is_list - type: std::vector - initial_value: '' - - - id: tcs_serial - type: int - restore_value: yes - initial_value: "0" - -text: - - platform: template - name: Input - id: input - mode: text - optimistic: true - -button: - - platform: template - name: Search Devices - on_press: - # Clear Lists - - lambda: |- - id(as_list).clear(); - id(is_list).clear(); - - light.turn_on: - id: doorman_rgb_status_led - red: 0% - green: 100% - blue: 10% - effect: pulse - - - - logger.log: Search outdoor stations... - - tc_bus.send: - command: 0x5802 - - globals.set: - id: search_mode - value: "1" - - delay: 350ms - - tc_bus.send: - command: 0x5200 - # Wait until every outdoor station sent response - - delay: 5s - - - - logger.log: Search indoor stations (0)... - - globals.set: - id: search_mode - value: "2" - - tc_bus.send: - command: 0x5800 - - delay: 350ms - - tc_bus.send: - command: 0x5200 - # Wait until every indoor station sent response - - delay: 10s - - - - logger.log: Search indoor stations (1)... - - tc_bus.send: - command: 0x5801 - - delay: 350ms - - tc_bus.send: - command: 0x5200 - # Wait until every indoor station sent response - - delay: 10s - - - - logger.log: - format: "Search complete! Found %i outdoor station(s) and %i indoor station(s)." - args: [ 'id(as_list).size()', 'id(is_list).size()'] - - globals.set: - id: search_mode - value: "3" - - light.turn_on: - id: doorman_rgb_status_led - red: 0% - green: 100% - blue: 10% - effect: none - - delay: 3s - - light.turn_on: - id: doorman_rgb_status_led - red: 10% - green: 100% - blue: 0% - effect: pulse - -tc_bus: - bus_command: - on_value: - - if: - condition: - - lambda: |- - std::string hex_command = std::string(x.c_str()); - std::string first(hex_command.begin(), hex_command.begin() + 1); - // Proceed on commands starting with 5 (Service response) - return first == "5"; - then: - - if: - condition: - - lambda: return id(search_mode) > 0 && id(search_mode) < 3; - then: - - lambda: |- - std::string hex_command = std::string(x.c_str()); - std::string serial(hex_command.begin() + 1, hex_command.begin() + 6); - int serial_int = std::stoi(serial, nullptr, 16); - std::vector& list = (id(search_mode) == 1 ? id(as_list) : id(is_list)); - bool found = false; - for (const auto& s : list) { - if (s == serial_int) { - found = true; - break; - } - } - if (!found) { - list.push_back(serial_int); - } - - if: - condition: - - lambda: |- - std::string hex_command = std::string(x.c_str()); - - std::string first(hex_command.begin(), hex_command.begin() + 1); - std::string serial(hex_command.begin() + 1, hex_command.begin() + 6); - std::string last(hex_command.begin() + 6, hex_command.begin() + 8); - - // Apartment Doorbell or Entrance Doorbell - return id(search_mode) == 3 && ((first == "1" && last == "41") || (first == "0" && serial != "00000" && (last == "80" || last == "81" || last == "82" || last == "83" || last == "84"))); - then: - - logger.log: Setup saving commands - # Save Commands - - lambda: |- - std::string hex_command = std::string(x.c_str()); - - std::string serial(hex_command.begin() + 1, hex_command.begin() + 6); - - // Save Serial Number - id(tcs_serial) = serial; - - // Entrance Doorbell - std::string entrance_doorbell_command_hex = "0" + serial + "80"; - unsigned long entrance_doorbell_number = std::stoul(entrance_doorbell_command_hex, nullptr, 16); - id(entrance_doorbell_command) = entrance_doorbell_number; - - // Open Door - std::string open_entrance_door_command_hex = "1" + serial + "80"; - unsigned long open_entrance_door_number = std::stoul(open_entrance_door_command_hex, nullptr, 16); - id(open_entrance_door_command) = open_entrance_door_number; - - // Theoretisch automatisch lernen nachdem einmalig die seriennummer gelernt wurde - // sofern ein code kommt mit 0 SN 8 X wo X nicht 0 ist soll der gespeichert werden als türöffner 2 - - // Apartment Doorbell - std::string apartment_doorbell_command_hex = "1" + serial + "41"; - unsigned long apartment_doorbell_number = std::stoul(apartment_doorbell_command_hex, nullptr, 16); - id(apartment_doorbell_command) = apartment_doorbell_number; - - // Pick up phone - std::string pick_up_phone_command_hex = "3" + serial + "00"; - unsigned long pick_up_phone_number = std::stoul(pick_up_phone_command_hex, nullptr, 16); - id(pick_up_phone_command) = pick_up_phone_number; - - // Function Button - std::string function_button_command_hex = "6" + serial + "08"; - unsigned long function_button_number = std::stoul(function_button_command_hex, nullptr, 16); - id(function_button_command) = function_button_number; - - # Setup complete - - globals.set: - id: search_mode - value: "0" - - light.turn_on: - id: doorman_rgb_status_led - red: 10% - green: 100% - blue: 0% - effect: none - - delay: 3s - - script.execute: update_led \ No newline at end of file diff --git a/firmware/addons/interactive-setup.yaml b/firmware/addons/interactive-setup.yaml index 2bb9361c..5784c5ec 100644 --- a/firmware/addons/interactive-setup.yaml +++ b/firmware/addons/interactive-setup.yaml @@ -9,6 +9,7 @@ switch: entity_category: CONFIG optimistic: true on_turn_on: + - logger.log: "Setup: Step 1 - Waiting for Door/Floor Call" - script.execute: update_led on_turn_off: - script.execute: update_led @@ -18,11 +19,25 @@ switch: # Extend TC:BUS Component tc_bus: + on_identify_complete: + - if: + condition: + - switch.is_on: doorman_setup_mode + then: + - script.execute: complete_setup + + on_identify_timeout: + - if: + condition: + - switch.is_on: doorman_setup_mode + then: + - script.execute: complete_setup + on_command: # If no second door station id is set, wait for one - if: condition: - - lambda: return id(second_door_station_id).state == 63 && (x.type == COMMAND_TYPE_DOOR_CALL || x.type == COMMAND_TYPE_OPEN_DOOR) && x.address != 0; + - lambda: return id(second_door_station_id).state == 63 && (x.type == COMMAND_TYPE_DOOR_CALL || x.type == COMMAND_TYPE_OPEN_DOOR) && x.address != id(entrance_door_station_id).state; then: - number.set: id: second_door_station_id @@ -34,27 +49,37 @@ tc_bus: - switch.is_on: doorman_setup_mode - lambda: "return x.type == COMMAND_TYPE_FLOOR_CALL || x.type == COMMAND_TYPE_DOOR_CALL;" then: + # Save Serial Number - number.set: id: serial_number value: !lambda "return x.serial_number;" + # Save Entrance Door Station address + - number.set: + id: entrance_door_station_id + value: !lambda "return x.address;" + # Start Identification + - delay: 200ms + - logger.log: "Setup: Step 2 - Identify Indoor Station" + - tc_bus.identify: - - switch.turn_off: doorman_setup_mode - - - light.turn_on: - id: doorman_rgb_status_led - red: 0% - green: 100% - blue: 10% - effect: none +script: + - id: complete_setup + then: + - switch.turn_off: doorman_setup_mode - - delay: 3s - - - script.execute: update_led + - light.turn_on: + id: doorman_rgb_status_led + red: 0% + green: 100% + blue: 10% + effect: none - - logger.log: Setup done + - delay: 3s + + - script.execute: update_led + - logger.log: "Setup: Complete" -script: - id: !extend update_led then: - if: diff --git a/firmware/addons/intercom-settings.yaml b/firmware/addons/intercom-settings.yaml index f862bd81..e99a7bda 100644 --- a/firmware/addons/intercom-settings.yaml +++ b/firmware/addons/intercom-settings.yaml @@ -1,5 +1,14 @@ # Intercom Settings Addon +esphome: + on_boot: + then: + - if: + condition: + - lambda: return id(serial_number).state != 0 && id(intercom_model).state != "None"; + then: + - tc_bus.read_memory: + # Extend TC:BUS Component tc_bus: on_read_memory_complete: @@ -11,6 +20,19 @@ tc_bus: on_read_memory_timeout: - logger.log: "Failed to read Memory" + on_identify_complete: + - logger.log: + format: "Identified Hardware: %s (version %i), Firmware: %i.%i.%i" + args: [ 'model_to_string(x.model)', 'x.hardware_version', 'x.firmware_major', 'x.firmware_minor', 'x.firmware_patch' ] + - if: + condition: + - lambda: return x.model != MODEL_NONE; + then: + - tc_bus.read_memory: + + on_identify_timeout: + - logger.log: "Unable to identify the Indoor Station, please select manually." + select: - platform: tc_bus model: @@ -22,9 +44,18 @@ select: web_server: sorting_group_id: sorting_group_intercom_settings - ringtone_door_call: - id: intercom_ringtone_door_call - name: "Ringtone: Door Call" + ringtone_entrance_door_call: + id: intercom_ringtone_entrance_door_call + name: "Ringtone: Entrance Door Call" + icon: "mdi:music" + entity_category: CONFIG + disabled_by_default: true + web_server: + sorting_group_id: sorting_group_intercom_settings + + ringtone_second_entrance_door_call: + id: intercom_ringtone_second_entrance_door_call + name: "Ringtone: Second Entrance Door Call" icon: "mdi:music" entity_category: CONFIG disabled_by_default: true @@ -60,9 +91,18 @@ number: web_server: sorting_group_id: sorting_group_intercom_settings - volume_handset: - id: intercom_volume_handset - name: "Volume: Handset" + volume_handset_door_call: + id: intercom_volume_handset_door_call + name: "Volume: Handset Door Call" + icon: "mdi:volume-high" + entity_category: CONFIG + disabled_by_default: true + web_server: + sorting_group_id: sorting_group_intercom_settings + + volume_handset_internal_call: + id: intercom_volume_handset_internal_call + name: "Volume: Handset Internal Call" icon: "mdi:volume-high" entity_category: CONFIG disabled_by_default: true @@ -80,4 +120,15 @@ button: entity_category: CONFIG disabled_by_default: true web_server: - sorting_group_id: sorting_group_intercom_settings \ No newline at end of file + sorting_group_id: sorting_group_intercom_settings + + - platform: template + id: identify_indoor_station + name: "Identify Indoor Station" + icon: "mdi:identifier" + entity_category: CONFIG + disabled_by_default: true + on_press: + - tc_bus.identify: + web_server: + sorting_group_id: sorting_group_intercom_settings diff --git a/firmware/addons/mqtt.yaml b/firmware/addons/mqtt.yaml new file mode 100644 index 00000000..61c8eb9b --- /dev/null +++ b/firmware/addons/mqtt.yaml @@ -0,0 +1,70 @@ +esphome: + on_boot: + then: + - light.turn_on: + id: doorman_rgb_status_led + effect: pulse + red: 100% + green: 65% + blue: 0% + - wait_until: + condition: + wifi.connected: + - light.turn_on: + id: doorman_rgb_status_led + effect: slow_pulse + red: 0% + green: 22% + blue: 100% + - wait_until: + condition: + mqtt.connected: + - light.turn_on: + id: doorman_rgb_status_led + effect: none + red: 0% + green: 22% + blue: 100% + color_brightness: 60% + - delay: 3s + - script.execute: update_led + +mqtt: + on_json_message: + - topic: doorman-s3/send_tc_command_raw + then: + - tc_bus.send: + command: !lambda |- + int command = 0; + if (x.containsKey("command")) + command = x["command"]; + return command; + is_long: !lambda |- + bool is_long = false; + if (x.containsKey("is_long")) + is_long = x["is_long"]; + return is_long; + + - topic: doorman-s3/send_tc_command + then: + - tc_bus.send: + type: !lambda |- + CommandType commandType = COMMAND_TYPE_UNKNOWN; + if (x.containsKey("type")) + commandType = string_to_command_type(x["type"]); + return commandType; + address: !lambda |- + int address = 0; + if (x.containsKey("address")) + address = x["address"]; + return address; + payload: !lambda |- + int payload = 0; + if (x.containsKey("payload")) + payload = x["payload"]; + return payload; + serial_number: !lambda |- + int serial_number = 0; + if (x.containsKey("serial_number")) + serial_number = x["serial_number"]; + return serial_number; \ No newline at end of file diff --git a/firmware/addons/nuki-bridge.yaml b/firmware/addons/nuki-bridge.yaml index 9efe033e..89416399 100644 --- a/firmware/addons/nuki-bridge.yaml +++ b/firmware/addons/nuki-bridge.yaml @@ -1,9 +1,3 @@ -esp32: - framework: - type: arduino - version: 2.0.16 - platform_version: 6.7.0 - external_components: - source: github://uriyacovy/ESPHome_nuki_lock refresh: 60s @@ -28,7 +22,24 @@ binary_sensor: id: nuki_smart_lock pairing_mode: true +number: + - platform: template + id: nuki_security_pin + name: "Nuki Security Pin" + icon: "mdi:shield-key" + disabled_by_default: true + mode: box + step: 1 + min_value: 0 + max_value: 65535 + set_action: + - nuki_lock.set_security_pin: + security_pin: !lambda return x; + web_server: + sorting_group_id: sorting_group_nuki_bridge_settings + sorting_weight: -31 + # Nuki Lock Bridge lock: - platform: nuki_lock @@ -156,22 +167,6 @@ lock: sorting_group_id: sorting_group_nuki_settings sorting_weight: -14 - security_pin: - id: nuki_security_pin - name: "Nuki Security Pin" - disabled_by_default: true - web_server: - sorting_group_id: sorting_group_nuki_bridge_settings - sorting_weight: -31 - - timezone_offset: - id: nuki_timezone_offset - name: "Nuki Timezone: Offset" - disabled_by_default: true - web_server: - sorting_group_id: sorting_group_nuki_settings - sorting_weight: 5 - night_mode_enabled: id: nuki_night_mode name: "Nuki Night Mode" @@ -228,14 +223,6 @@ lock: sorting_group_id: sorting_group_nuki_settings sorting_weight: -4 - dst_mode_enabled: - id: nuki_dst_mode - name: "Nuki Daylight Saving Time" - disabled_by_default: true - web_server: - sorting_group_id: sorting_group_nuki_settings - sorting_weight: -4 - auto_unlock_disabled: id: nuki_auto_unlock_disabled name: "Nuki Auto Unlock: Disable" @@ -268,14 +255,6 @@ lock: sorting_group_id: sorting_group_nuki_settings sorting_weight: 3 - timezone: - id: nuki_timezone - name: "Nuki Timezone" - disabled_by_default: true - web_server: - sorting_group_id: sorting_group_nuki_settings - sorting_weight: 4 - advertising_mode: id: nuki_advertising_mode name: "Nuki Advertising Mode" @@ -300,14 +279,6 @@ lock: sorting_group_id: sorting_group_nuki_bridge_settings sorting_weight: 11 - auto_update_enabled: - id: nuki_auto_update - name: "Nuki Automatic Updates" - disabled_by_default: true - web_server: - sorting_group_id: sorting_group_nuki_settings - sorting_weight: 12 - on_pairing_mode_on_action: - script.execute: update_led diff --git a/firmware/addons/pattern-events.yaml b/firmware/addons/pattern-events.yaml index 20d56bbf..ce602081 100644 --- a/firmware/addons/pattern-events.yaml +++ b/firmware/addons/pattern-events.yaml @@ -2,27 +2,47 @@ event: - platform: template - id: doorbell_pattern - name: "Doorbell Pattern" + id: entrance_doorbell_pattern + name: "Entrance Doorbell" icon: "mdi:doorbell" device_class: DOORBELL event_types: - - "apartment_single" - - "apartment_double" - - "apartment_triple" - - "entrance_single" - - "entrance_double" - - "entrance_triple" - - "second_entrance_single" - - "second_entrance_double" - - "second_entrance_triple" + - "single" + - "double" + - "triple" web_server: sorting_group_id: sorting_group_listeners sorting_weight: -30 + - platform: template + id: second_entrance_doorbell_pattern + name: "Second Entrance Doorbell" + icon: "mdi:doorbell" + device_class: DOORBELL + event_types: + - "single" + - "double" + - "triple" + web_server: + sorting_group_id: sorting_group_listeners + sorting_weight: -40 + + - platform: template + id: apartment_doorbell_pattern + name: "Apartment Doorbell" + icon: "mdi:doorbell" + device_class: DOORBELL + event_types: + - "single" + - "double" + - "triple" + web_server: + sorting_group_id: sorting_group_listeners + sorting_weight: -50 + - platform: template id: phone_pick_up_pattern - name: "Phone pick up Pattern" + name: "Phone pick up" icon: "mdi:phone-incoming-outgoing" event_types: - "single" @@ -41,8 +61,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: entrance_single + id: entrance_doorbell_pattern + event_type: single # Double press - timing: @@ -53,8 +73,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: entrance_double + id: entrance_doorbell_pattern + event_type: double # Triple press - timing: @@ -68,8 +88,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: entrance_triple + id: entrance_doorbell_pattern + event_type: triple - id: !extend second_entrance_doorbell @@ -80,8 +100,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: second_entrance_single + id: second_entrance_doorbell_pattern + event_type: single # Double press - timing: @@ -92,8 +112,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: second_entrance_double + id: second_entrance_doorbell_pattern + event_type: double # Triple press - timing: @@ -107,8 +127,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: second_entrance_triple + id: second_entrance_doorbell_pattern + event_type: triple - id: !extend apartment_doorbell @@ -119,8 +139,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: apartment_single + id: apartment_doorbell_pattern + event_type: single # Double press - timing: @@ -131,8 +151,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: apartment_double + id: apartment_doorbell_pattern + event_type: double # Triple press - timing: @@ -146,8 +166,8 @@ binary_sensor: - OFF for at least 2s then: - event.trigger: - id: doorbell_pattern - event_type: apartment_triple + id: apartment_doorbell_pattern + event_type: triple - id: !extend pick_up_phone @@ -186,4 +206,4 @@ binary_sensor: then: - event.trigger: id: phone_pick_up_pattern - event_type: triple \ No newline at end of file + event_type: triple diff --git a/firmware/addons/ring-to-open.yaml b/firmware/addons/ring-to-open.yaml index e3c98987..7ece0296 100644 --- a/firmware/addons/ring-to-open.yaml +++ b/firmware/addons/ring-to-open.yaml @@ -30,7 +30,7 @@ binary_sensor: - tc_bus.send: type: open_door - address: 0 + address: !lambda "return id(entrance_door_station_id).state;" - id: !extend second_entrance_doorbell on_press: @@ -124,6 +124,17 @@ switch: web_server: sorting_group_id: sorting_group_doorman_settings + - platform: template + id: doorman_ring_to_open_led_status + name: "Ring To Open: Display Status" + icon: "mdi:led-on" + restore_mode: RESTORE_DEFAULT_ON + optimistic: true + entity_category: CONFIG + disabled_by_default: true + web_server: + sorting_group_id: sorting_group_doorman_settings + select: - platform: template @@ -197,6 +208,7 @@ script: - if: condition: - switch.is_on: doorman_ring_to_open + - switch.is_on: doorman_ring_to_open_led_status then: - light.turn_on: id: doorman_rgb_status_led diff --git a/firmware/base.yaml b/firmware/base.yaml index 49ef4119..b5425952 100644 --- a/firmware/base.yaml +++ b/firmware/base.yaml @@ -15,8 +15,6 @@ esp32: board: esp32-s3-devkitc-1 variant: esp32s3 flash_size: 8MB - framework: - type: esp-idf # Essential ESPHome Configuration Options esphome: @@ -24,73 +22,25 @@ esphome: friendly_name: ${friendly_name} name_add_mac_suffix: false - min_version: "2024.11.0" + min_version: "2024.12.0" project: name: "AzonInc.Doorman" - version: "2024.11.2" + version: "2025.2.0" platformio_options: board_build.flash_mode: dio + build_flags: + - -DBOARD_HAS_PSRAM - on_boot: - then: - - light.turn_on: - id: doorman_rgb_status_led - effect: pulse - red: 100% - green: 65% - blue: 0% - - wait_until: - condition: - wifi.connected: - - light.turn_on: - id: doorman_rgb_status_led - effect: slow_pulse - red: 0% - green: 22% - blue: 100% - - wait_until: - condition: - api.connected: - - light.turn_on: - id: doorman_rgb_status_led - effect: none - red: 0% - green: 22% - blue: 100% - color_brightness: 60% - - delay: 3s - - script.execute: update_led +# Utilize PSRAM +psram: + mode: octal + speed: 80MHz # Enable logging logger: level: ${log_level} - logs: - select: INFO - number: INFO - -api: - reboot_timeout: 0s - actions: - - action: send_tc_command_raw - variables: - command: int - then: - - tc_bus.send: - command: !lambda 'return command;' - - action: send_tc_command - variables: - type: string - address: int - payload: int - serial_number: int - then: - - tc_bus.send: - type: !lambda 'return string_to_command_type(type);' - address: !lambda 'return address;' - payload: !lambda 'return payload;' - serial_number: !lambda 'return serial_number;' web_server: version: 3 @@ -119,6 +69,9 @@ web_server: - id: sorting_group_system name: "System" sorting_weight: -2 + - id: sorting_group_diagnostic + name: "Diagnostic" + sorting_weight: -1 ota: - platform: esphome @@ -130,25 +83,15 @@ http_request: update: - platform: http_request - id: update_http_request_stable + id: update_http_request icon: "mdi:update" - name: Firmware (stable) + name: Firmware source: https://doorman.azon.ai/firmware/release/doorman-stock/manifest.json disabled_by_default: false web_server: sorting_group_id: sorting_group_system sorting_weight: -4 - - platform: http_request - id: update_http_request_dev - icon: "mdi:update" - name: Firmware (dev) - source: https://doorman-dev.surge.sh/firmware/release/doorman-stock/manifest.json - disabled_by_default: true - web_server: - sorting_group_id: sorting_group_system - sorting_weight: -3 - wifi: ap: ssid: "Doorman-S3 Setup" @@ -181,6 +124,23 @@ number: sorting_group_id: sorting_group_intercom_settings sorting_weight: -20 + - platform: template + id: entrance_door_station_id + name: "Entrance Door Station ID" + mode : box + icon: "mdi:counter" + restore_value: true + initial_value: '0' + max_value: 63 + min_value: 0 + step: 1 + optimistic: true + disabled_by_default: true + entity_category: CONFIG + web_server: + sorting_group_id: sorting_group_intercom_settings + sorting_weight: -20 + - platform: template id: second_door_station_id name: "Second Door Station ID" @@ -205,8 +165,9 @@ text_sensor: name: "Hardware" icon: "mdi:router-wireless" disabled_by_default: true + entity_category: DIAGNOSTIC web_server: - sorting_group_id: sorting_group_system + sorting_group_id: sorting_group_diagnostic sorting_weight: -20 bus_command: id: last_bus_command @@ -216,6 +177,26 @@ text_sensor: sorting_weight: -99 switch: + - platform: template + id: dev_firmware + name: Experimental firmware + icon: "mdi:test-tube" + entity_category: CONFIG + optimistic: true + restore_mode: RESTORE_DEFAULT_OFF + on_turn_on: + - logger.log: "OTA update source set to dev" + - lambda: id(update_http_request).set_source_url("https://doorman-dev.surge.sh/firmware/release/doorman-stock/manifest.json"); + - component.update: update_http_request + on_turn_off: + - logger.log: "OTA update source set to master" + - lambda: id(update_http_request).set_source_url("https://doorman.azon.ai/firmware/release/doorman-stock/manifest.json"); + - component.update: update_http_request + disabled_by_default: true + web_server: + sorting_group_id: sorting_group_system + sorting_weight: -3 + # Preconfigured Relay Switch - platform: gpio name: Relay @@ -266,7 +247,7 @@ button: on_press: - tc_bus.send: type: open_door - address: 0 + address: !lambda "return id(entrance_door_station_id).state;" web_server: sorting_group_id: sorting_group_controls sorting_weight: -10 @@ -349,22 +330,10 @@ sensor: name: Uptime id: doorman_uptime disabled_by_default: true - web_server: - sorting_group_id: sorting_group_system - sorting_weight: -7 - - - platform: adc - id: bus_voltage - name: Bus Voltage - pin: GPIO10 - update_interval: 1s - attenuation: 12dB entity_category: DIAGNOSTIC - disabled_by_default: true - icon: "mdi:current-dc" web_server: - sorting_group_id: sorting_group_system - sorting_weight: -5 + sorting_group_id: sorting_group_diagnostic + sorting_weight: -7 binary_sensor: - platform: gpio @@ -388,6 +357,7 @@ binary_sensor: id: entrance_doorbell name: "Entrance Doorbell" type: door_call + address_lambda: !lambda "return id(entrance_door_station_id).state;" auto_off: 0.2s web_server: sorting_group_id: sorting_group_listeners diff --git a/firmware/doorman-nuki-bridge.dev.yaml b/firmware/doorman-nuki-bridge.dev.yaml index 3717a0cc..e09675c2 100644 --- a/firmware/doorman-nuki-bridge.dev.yaml +++ b/firmware/doorman-nuki-bridge.dev.yaml @@ -1,18 +1,3 @@ -# Helper File: Compile dev firmware -dashboard_import: - package_import_url: github://AzonInc/doorman/firmware/examples/nuki-bridge.example.yaml@dev - +# This file provides backwards compatibility (< 2025.1.0) packages: - device_base: !include doorman-nuki-bridge.yaml - -external_components: - - source: github://AzonInc/Doorman@dev - components: [ tc_bus ] - refresh: 60s - -update: - - id: !extend update_http_request_stable - disabled_by_default: true - - - id: !extend update_http_request_dev - disabled_by_default: false \ No newline at end of file + device_base: !include ha-doorman-nuki-bridge.dev.yaml \ No newline at end of file diff --git a/firmware/doorman-nuki-bridge.yaml b/firmware/doorman-nuki-bridge.yaml index 7125a50b..a0b95965 100644 --- a/firmware/doorman-nuki-bridge.yaml +++ b/firmware/doorman-nuki-bridge.yaml @@ -1,22 +1,3 @@ -dashboard_import: - package_import_url: github://AzonInc/doorman/firmware/examples/nuki-bridge.example.yaml@master - import_full_config: false - +# This file provides backwards compatibility (< 2025.1.0) packages: - device_base: !include base.yaml - pattern_events: !include addons/pattern-events.yaml - ring_to_open: !include addons/ring-to-open.yaml - memory_utils: !include addons/intercom-settings.yaml - addon_nuki_bridge: !include addons/nuki-bridge.yaml - interactive_setup: !include addons/interactive-setup.yaml - -esphome: - project: - name: "AzonInc.Doorman-Nuki-Bridge" - -update: - - id: !extend update_http_request_stable - source: https://doorman.azon.ai/firmware/release/doorman-nuki-bridge/manifest.json - - - id: !extend update_http_request_dev - source: https://doorman-dev.surge.sh/firmware/release/doorman-nuki-bridge/manifest.json \ No newline at end of file + device_base: !include ha-doorman-nuki-bridge.yaml \ No newline at end of file diff --git a/firmware/doorman-stock.dev.yaml b/firmware/doorman-stock.dev.yaml index 8aa289bc..52130460 100644 --- a/firmware/doorman-stock.dev.yaml +++ b/firmware/doorman-stock.dev.yaml @@ -1,18 +1,3 @@ -# Helper File: Compile dev firmware -dashboard_import: - package_import_url: github://AzonInc/doorman/firmware/examples/stock.example.yaml@dev - +# This file provides backwards compatibility (< 2025.1.0) packages: - device_base: !include doorman-stock.yaml - -external_components: - - source: github://AzonInc/Doorman@dev - components: [ tc_bus ] - refresh: 60s - -update: - - id: !extend update_http_request_stable - disabled_by_default: true - - - id: !extend update_http_request_dev - disabled_by_default: false \ No newline at end of file + device_base: !include ha-doorman-stock.dev.yaml \ No newline at end of file diff --git a/firmware/doorman-stock.yaml b/firmware/doorman-stock.yaml index 0c051da5..a063e686 100644 --- a/firmware/doorman-stock.yaml +++ b/firmware/doorman-stock.yaml @@ -1,13 +1,3 @@ -dashboard_import: - package_import_url: github://AzonInc/doorman/firmware/examples/stock.example.yaml@master - import_full_config: false - +# This file provides backwards compatibility (< 2025.1.0) packages: - device_base: !include base.yaml - pattern_events: !include addons/pattern-events.yaml - ring_to_open: !include addons/ring-to-open.yaml - memory_utils: !include addons/intercom-settings.yaml - interactive_setup: !include addons/interactive-setup.yaml - -esp32_improv: - authorizer: none \ No newline at end of file + device_base: !include ha-doorman-stock.yaml \ No newline at end of file diff --git a/firmware/examples/ha-nuki-bridge.example.dev.yaml b/firmware/examples/ha-nuki-bridge.example.dev.yaml new file mode 100644 index 00000000..76b6b136 --- /dev/null +++ b/firmware/examples/ha-nuki-bridge.example.dev.yaml @@ -0,0 +1,24 @@ +# Doorman S3 Nuki Bridge Firmware + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Nuki Bridge Firmware Config +packages: + AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/ha-doorman-nuki-bridge.dev.yaml@dev + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password \ No newline at end of file diff --git a/firmware/examples/ha-nuki-bridge.example.yaml b/firmware/examples/ha-nuki-bridge.example.yaml new file mode 100644 index 00000000..836bb63d --- /dev/null +++ b/firmware/examples/ha-nuki-bridge.example.yaml @@ -0,0 +1,24 @@ +# Doorman S3 Nuki Bridge Firmware + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Nuki Bridge Firmware Config +packages: + AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/ha-doorman-nuki-bridge.yaml@master + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password \ No newline at end of file diff --git a/firmware/examples/ha-stock.example.dev.yaml b/firmware/examples/ha-stock.example.dev.yaml new file mode 100644 index 00000000..53f4f3fe --- /dev/null +++ b/firmware/examples/ha-stock.example.dev.yaml @@ -0,0 +1,24 @@ +# Doorman S3 Stock Firmware + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Stock Firmware Config +packages: + AzonInc.Doorman: github://AzonInc/doorman/firmware/ha-doorman-stock.dev.yaml@dev + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password \ No newline at end of file diff --git a/firmware/examples/ha-stock.example.yaml b/firmware/examples/ha-stock.example.yaml new file mode 100644 index 00000000..2f6696dd --- /dev/null +++ b/firmware/examples/ha-stock.example.yaml @@ -0,0 +1,24 @@ +# Doorman S3 Stock Firmware + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Stock Firmware Config +packages: + AzonInc.Doorman: github://AzonInc/doorman/firmware/ha-doorman-stock.yaml@master + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password \ No newline at end of file diff --git a/firmware/examples/mqtt-nuki-bridge.example.dev.yaml b/firmware/examples/mqtt-nuki-bridge.example.dev.yaml new file mode 100644 index 00000000..022d4114 --- /dev/null +++ b/firmware/examples/mqtt-nuki-bridge.example.dev.yaml @@ -0,0 +1,29 @@ +# Doorman S3 Nuki Bridge Firmware (MQTT) + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Nuki Bridge Firmware Config +packages: + AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/mqtt-doorman-nuki-bridge.dev.yaml@dev + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +mqtt: + broker: 10.0.0.2 + username: "mqtt" + password: "password" \ No newline at end of file diff --git a/firmware/examples/mqtt-nuki-bridge.example.yaml b/firmware/examples/mqtt-nuki-bridge.example.yaml new file mode 100644 index 00000000..606dd354 --- /dev/null +++ b/firmware/examples/mqtt-nuki-bridge.example.yaml @@ -0,0 +1,29 @@ +# Doorman S3 Nuki Bridge Firmware (MQTT) + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Nuki Bridge Firmware Config +packages: + AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/mqtt-doorman-nuki-bridge.yaml@master + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +mqtt: + broker: 10.0.0.2 + username: "mqtt" + password: "password" \ No newline at end of file diff --git a/firmware/examples/mqtt-stock.example.dev.yaml b/firmware/examples/mqtt-stock.example.dev.yaml new file mode 100644 index 00000000..296b84f8 --- /dev/null +++ b/firmware/examples/mqtt-stock.example.dev.yaml @@ -0,0 +1,29 @@ +# Doorman S3 Stock Firmware (MQTT) + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Stock Firmware Config +packages: + AzonInc.Doorman: github://AzonInc/doorman/firmware/mqtt-doorman-stock.dev.yaml@dev + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +mqtt: + broker: 10.0.0.2 + username: "mqtt" + password: "password" \ No newline at end of file diff --git a/firmware/examples/mqtt-stock.example.yaml b/firmware/examples/mqtt-stock.example.yaml new file mode 100644 index 00000000..cb98bcf7 --- /dev/null +++ b/firmware/examples/mqtt-stock.example.yaml @@ -0,0 +1,29 @@ +# Doorman S3 Stock Firmware (MQTT) + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Stock Firmware Config +packages: + AzonInc.Doorman: github://AzonInc/doorman/firmware/mqtt-doorman-stock.yaml@master + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +mqtt: + broker: 10.0.0.2 + username: "mqtt" + password: "password" \ No newline at end of file diff --git a/firmware/examples/nuki-bridge.example.dev.yaml b/firmware/examples/nuki-bridge.example.dev.yaml new file mode 100644 index 00000000..76b6b136 --- /dev/null +++ b/firmware/examples/nuki-bridge.example.dev.yaml @@ -0,0 +1,24 @@ +# Doorman S3 Nuki Bridge Firmware + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Nuki Bridge Firmware Config +packages: + AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/ha-doorman-nuki-bridge.dev.yaml@dev + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password \ No newline at end of file diff --git a/firmware/examples/nuki-bridge.example.yaml b/firmware/examples/nuki-bridge.example.yaml index b0356b54..836bb63d 100644 --- a/firmware/examples/nuki-bridge.example.yaml +++ b/firmware/examples/nuki-bridge.example.yaml @@ -12,7 +12,7 @@ substitutions: # Import Doorman Nuki Bridge Firmware Config packages: - AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/doorman-nuki-bridge.yaml@master + AzonInc.Doorman-Nuki-Bridge: github://AzonInc/doorman/firmware/ha-doorman-nuki-bridge.yaml@master esphome: name: ${name} diff --git a/firmware/examples/stock.example.dev.yaml b/firmware/examples/stock.example.dev.yaml new file mode 100644 index 00000000..53f4f3fe --- /dev/null +++ b/firmware/examples/stock.example.dev.yaml @@ -0,0 +1,24 @@ +# Doorman S3 Stock Firmware + +# You can change a few options here. +substitutions: + name: "doorman-s3" + friendly_name: "Doorman S3" + # log_level: "ERROR" + # led_pin: "GPIO1" + # rgb_led_pin: "GPIO2" + # relay_pin: "GPIO42" + # external_button_pin: "GPIO41" + +# Import Doorman Stock Firmware Config +packages: + AzonInc.Doorman: github://AzonInc/doorman/firmware/ha-doorman-stock.dev.yaml@dev + +esphome: + name: ${name} + name_add_mac_suffix: false + friendly_name: ${friendly_name} + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password \ No newline at end of file diff --git a/firmware/examples/stock.example.yaml b/firmware/examples/stock.example.yaml index f653594f..2f6696dd 100644 --- a/firmware/examples/stock.example.yaml +++ b/firmware/examples/stock.example.yaml @@ -12,7 +12,7 @@ substitutions: # Import Doorman Stock Firmware Config packages: - AzonInc.Doorman: github://AzonInc/doorman/firmware/doorman-stock.yaml@master + AzonInc.Doorman: github://AzonInc/doorman/firmware/ha-doorman-stock.yaml@master esphome: name: ${name} diff --git a/firmware/ha-doorman-nuki-bridge.dev.yaml b/firmware/ha-doorman-nuki-bridge.dev.yaml new file mode 100644 index 00000000..74f23063 --- /dev/null +++ b/firmware/ha-doorman-nuki-bridge.dev.yaml @@ -0,0 +1,16 @@ +# Helper File: Compile dev firmware +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/ha-nuki-bridge.example.dev.yaml@dev + +packages: + device_base: !include ha-doorman-nuki-bridge.yaml + debug_utilities: !include addons/debug-utilities.yaml + +external_components: + - source: github://AzonInc/Doorman@dev + components: [ tc_bus ] + refresh: 60s + +switch: + - id: !extend dev_firmware + restore_mode: RESTORE_DEFAULT_ON \ No newline at end of file diff --git a/firmware/ha-doorman-nuki-bridge.yaml b/firmware/ha-doorman-nuki-bridge.yaml new file mode 100644 index 00000000..d5746d1e --- /dev/null +++ b/firmware/ha-doorman-nuki-bridge.yaml @@ -0,0 +1,22 @@ +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/ha-nuki-bridge.example.yaml@master + import_full_config: false + +packages: + device_base: !include base.yaml + homeassistant: !include addons/homeassistant.yaml + pattern_events: !include addons/pattern-events.yaml + ring_to_open: !include addons/ring-to-open.yaml + intercom_settings: !include addons/intercom-settings.yaml + addon_nuki_bridge: !include addons/nuki-bridge.yaml + interactive_setup: !include addons/interactive-setup.yaml + +esphome: + project: + name: "AzonInc.Doorman-Nuki-Bridge" + +esp32: + framework: + type: arduino + version: 2.0.16 + platform_version: 6.7.0 \ No newline at end of file diff --git a/firmware/ha-doorman-stock.dev.yaml b/firmware/ha-doorman-stock.dev.yaml new file mode 100644 index 00000000..5ce2d1dd --- /dev/null +++ b/firmware/ha-doorman-stock.dev.yaml @@ -0,0 +1,16 @@ +# Helper File: Compile dev firmware +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/ha-stock.example.dev.yaml@dev + +packages: + device_base: !include ha-doorman-stock.yaml + debug_utilities: !include addons/debug-utilities.yaml + +external_components: + - source: github://AzonInc/Doorman@dev + components: [ tc_bus ] + refresh: 60s + +switch: + - id: !extend dev_firmware + restore_mode: RESTORE_DEFAULT_ON \ No newline at end of file diff --git a/firmware/ha-doorman-stock.yaml b/firmware/ha-doorman-stock.yaml new file mode 100644 index 00000000..05a31a79 --- /dev/null +++ b/firmware/ha-doorman-stock.yaml @@ -0,0 +1,43 @@ +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/ha-stock.example.yaml@master + import_full_config: false + +packages: + device_base: !include base.yaml + homeassistant: !include addons/homeassistant.yaml + pattern_events: !include addons/pattern-events.yaml + ring_to_open: !include addons/ring-to-open.yaml + intercom_settings: !include addons/intercom-settings.yaml + interactive_setup: !include addons/interactive-setup.yaml + +esp32: + framework: + type: esp-idf + version: recommended + sdkconfig_options: + CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y" + CONFIG_ESP32S3_DATA_CACHE_64KB: "y" + CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y" + CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: "y" + CONFIG_ESP_DEFAULT_CPU_FREQ_240: "y" + CONFIG_ESP_DATA_CACHE_64KB: "y" + CONFIG_ESP_DATA_CACHE_LINE_64B: "y" + CONFIG_ESP_INSTRUCTION_CACHE_32KB: "y" + CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY: "y" + CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y" + CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y" + CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y" + +esp32_ble: + name: doorman-s3 + +esp32_improv: + authorizer: none + +wifi: + on_connect: + - delay: 5s # Gives time for improv results to be transmitted + - ble.disable: + + on_disconnect: + - ble.enable: \ No newline at end of file diff --git a/firmware/mqtt-doorman-nuki-bridge.dev.yaml b/firmware/mqtt-doorman-nuki-bridge.dev.yaml new file mode 100644 index 00000000..44c1b485 --- /dev/null +++ b/firmware/mqtt-doorman-nuki-bridge.dev.yaml @@ -0,0 +1,16 @@ +# Helper File: Compile dev firmware +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/mqtt-nuki-bridge.example.dev.yaml@dev + +packages: + device_base: !include mqtt-doorman-nuki-bridge.yaml + debug_utilities: !include addons/debug-utilities.yaml + +external_components: + - source: github://AzonInc/Doorman@dev + components: [ tc_bus ] + refresh: 60s + +switch: + - id: !extend dev_firmware + restore_mode: RESTORE_DEFAULT_ON \ No newline at end of file diff --git a/firmware/mqtt-doorman-nuki-bridge.yaml b/firmware/mqtt-doorman-nuki-bridge.yaml new file mode 100644 index 00000000..5fc0a63c --- /dev/null +++ b/firmware/mqtt-doorman-nuki-bridge.yaml @@ -0,0 +1,22 @@ +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/mqtt-nuki-bridge.example.yaml@master + import_full_config: false + +packages: + device_base: !include base.yaml + mqtt: !include addons/mqtt.yaml + pattern_events: !include addons/pattern-events.yaml + ring_to_open: !include addons/ring-to-open.yaml + intercom_settings: !include addons/intercom-settings.yaml + addon_nuki_bridge: !include addons/nuki-bridge.yaml + interactive_setup: !include addons/interactive-setup.yaml + +esphome: + project: + name: "AzonInc.Doorman-Nuki-Bridge" + +esp32: + framework: + type: arduino + version: 2.0.16 + platform_version: 6.7.0 \ No newline at end of file diff --git a/firmware/mqtt-doorman-stock.dev.yaml b/firmware/mqtt-doorman-stock.dev.yaml new file mode 100644 index 00000000..8a073e1d --- /dev/null +++ b/firmware/mqtt-doorman-stock.dev.yaml @@ -0,0 +1,16 @@ +# Helper File: Compile dev firmware +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/mqtt-stock.example.dev.yaml@dev + +packages: + device_base: !include mqtt-doorman-stock.yaml + debug_utilities: !include addons/debug-utilities.yaml + +external_components: + - source: github://AzonInc/Doorman@dev + components: [ tc_bus ] + refresh: 60s + +switch: + - id: !extend dev_firmware + restore_mode: RESTORE_DEFAULT_ON \ No newline at end of file diff --git a/firmware/mqtt-doorman-stock.yaml b/firmware/mqtt-doorman-stock.yaml new file mode 100644 index 00000000..e03576c8 --- /dev/null +++ b/firmware/mqtt-doorman-stock.yaml @@ -0,0 +1,73 @@ +dashboard_import: + package_import_url: github://AzonInc/doorman/firmware/examples/mqtt-stock.example.yaml@master + import_full_config: false + +packages: + device_base: !include base.yaml + mqtt: !include addons/mqtt.yaml + pattern_events: !include addons/pattern-events.yaml + ring_to_open: !include addons/ring-to-open.yaml + intercom_settings: !include addons/intercom-settings.yaml + interactive_setup: !include addons/interactive-setup.yaml + +esp32: + framework: + type: esp-idf + version: recommended + sdkconfig_options: + CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y" + CONFIG_ESP32S3_DATA_CACHE_64KB: "y" + CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y" + CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: "y" + CONFIG_ESP_DEFAULT_CPU_FREQ_240: "y" + CONFIG_ESP_DATA_CACHE_64KB: "y" + CONFIG_ESP_DATA_CACHE_LINE_64B: "y" + CONFIG_ESP_INSTRUCTION_CACHE_32KB: "y" + + CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY: "y" + CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP: "y" + + # Settings based on https://github.com/espressif/esp-adf/issues/297#issuecomment-783811702 + CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM: "16" + CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM: "512" + CONFIG_ESP32_WIFI_STATIC_TX_BUFFER: "y" + CONFIG_ESP32_WIFI_TX_BUFFER_TYPE: "0" + CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM: "8" + CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM: "32" + CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED: "y" + CONFIG_ESP32_WIFI_TX_BA_WIN: "16" + CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED: "y" + CONFIG_ESP32_WIFI_RX_BA_WIN: "32" + CONFIG_LWIP_MAX_ACTIVE_TCP: "16" + CONFIG_LWIP_MAX_LISTENING_TCP: "16" + CONFIG_TCP_MAXRTX: "12" + CONFIG_TCP_SYNMAXRTX: "6" + CONFIG_TCP_MSS: "1436" + CONFIG_TCP_MSL: "60000" + CONFIG_TCP_SND_BUF_DEFAULT: "65535" + CONFIG_TCP_WND_DEFAULT: "65535" # Adjusted from linked settings to avoid compilation error + CONFIG_TCP_RECVMBOX_SIZE: "512" + CONFIG_TCP_QUEUE_OOSEQ: "y" + CONFIG_TCP_OVERSIZE_MSS: "y" + CONFIG_LWIP_WND_SCALE: "y" + CONFIG_TCP_RCV_SCALE: "3" + CONFIG_LWIP_TCPIP_RECVMBOX_SIZE: "512" + + CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y" + CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y" + + CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y" + +esp32_ble: + name: doorman-s3 + +esp32_improv: + authorizer: none + +wifi: + on_connect: + - delay: 5s # Gives time for improv results to be transmitted + - ble.disable: + + on_disconnect: + - ble.enable: \ No newline at end of file diff --git a/firmware/tc-bus-component-8266.yaml b/firmware/tc-bus-component-8266.yaml index 1583f91c..b161267a 100644 --- a/firmware/tc-bus-component-8266.yaml +++ b/firmware/tc-bus-component-8266.yaml @@ -1,6 +1,7 @@ # Component Test esphome: name: test-tc-bus + friendly_name: TC BUS Component Test esp8266: board: esp01_1m @@ -30,6 +31,12 @@ tc_bus: ESP_LOGD("tcs_bus", "Memory Dump: %s", hexString.c_str()); on_read_memory_timeout: - logger.log: "Failed to read Memory" + on_identify_complete: + - logger.log: + format: "Setup: Identified Hardware: %s (version %i), Firmware: %i.%i.%i" + args: [ 'model_to_string(x.model)', 'x.hardware_version', 'x.firmware_major', 'x.firmware_minor', 'x.firmware_patch' ] + on_identify_timeout: + - logger.log: "Setup: Unable to identify the Indoor Station, please select manually." text_sensor: - platform: tc_bus @@ -42,6 +49,25 @@ number: - platform: tc_bus serial_number: name: "Serial Number" + volume_ringtone: + name: "Volume: Ringtone" + volume_handset_door_call: + name: "Volume: Handset Door Call" + volume_handset_internal_call: + name: "Volume: Handset Internal Call" + +select: + - platform: tc_bus + model: + name: "Intercom Model" + ringtone_entrance_door_call: + name: "Ringtone: Entrance Door Call" + ringtone_second_entrance_door_call: + name: "Ringtone: Second Entrance Door Call" + ringtone_floor_call: + name: "Ringtone: Floor Call" + ringtone_internal_call: + name: "Ringtone: Internal Call" button: - platform: template @@ -58,4 +84,12 @@ binary_sensor: id: entrance_doorbell name: "Entrance Doorbell" type: door_call - auto_off: 0.2s \ No newline at end of file + auto_off: 0.2s + - platform: template + name: "Read Memory" + on_press: + - tc_bus.read_memory: + - platform: template + name: "Identify Indoor Station" + on_press: + - tc_bus.identify: \ No newline at end of file diff --git a/pcb/doorman.kicad_sch b/pcb/doorman.kicad_sch index 7e0dcaca..fbfee172 100644 --- a/pcb/doorman.kicad_sch +++ b/pcb/doorman.kicad_sch @@ -6,7 +6,7 @@ (paper "A4") (title_block (title "Doorman S3") - (date "2024-10-13") + (date "2024-11-29") (rev "1.5") (comment 1 "TC:Bus Intercom Gateway") ) @@ -8937,7 +8937,7 @@ (exclude_from_sim no) (in_bom no) (on_board yes) - (dnp no) + (dnp yes) (uuid "07e1541a-bf8d-4a97-bdf0-b6d5baf7dd42") (property "Reference" "J7" (at 188.722 81.026 0) @@ -9181,7 +9181,7 @@ (hide yes) ) ) - (property "Datasheet" "https://assets.nexperia.com/documents/data-sheet/BC817_SER.pdf" + (property "Datasheet" "https://www.lcsc.com/datasheet/lcsc_datasheet_2407241028_Nexperia-BC817-25-215_C39828.pdf" (at 157.48 281.28 0) (effects (font @@ -9191,7 +9191,7 @@ (hide yes) ) ) - (property "Description" "45V 300mW 160@100mA,1V 500mA NPN SOT-23(TO-236) Bipolar Transistors - BJT ROHS" + (property "Description" "45V 250mW 160@100mA,1V 500mA NPN SOT-23 Bipolar (BJT) ROHS " (at 140.97 86.36 0) (effects (font @@ -9209,7 +9209,7 @@ (hide yes) ) ) - (property "MPN" "BC817-25" + (property "MPN" "BC817-25,215" (at 140.97 86.36 0) (effects (font @@ -9218,7 +9218,7 @@ (hide yes) ) ) - (property "LCSC" "C5184412" + (property "LCSC" "C39828" (at 140.97 86.36 0) (effects (font @@ -9489,7 +9489,7 @@ (exclude_from_sim no) (in_bom no) (on_board yes) - (dnp no) + (dnp yes) (uuid "2ac1a2d4-2176-4af7-88b4-95cb5edf6fd6") (property "Reference" "J3" (at 200.406 48.768 90) @@ -12047,7 +12047,7 @@ (hide yes) ) ) - (property "Datasheet" "~" + (property "Datasheet" "https://www.lcsc.com/datasheet/lcsc_datasheet_2301061530_DORABO-DB125-2-54-9P-GN-S_C918127.pdf" (at 170.18 139.7 0) (effects (font @@ -12056,7 +12056,7 @@ (hide yes) ) ) - (property "Description" "Generic screw terminal, single row, 01x09, script generated (kicad-library-utils/schlib/autogen/connector/)" + (property "Description" "1x9P -40℃~+105℃ 8A 130V Green 18~26 Direct Insert 2.54mm 0.5~1 1 9 Plugin,P=2.54mm Screw Terminal Blocks ROHS" (at 170.18 139.7 0) (effects (font @@ -13105,7 +13105,7 @@ (exclude_from_sim no) (in_bom no) (on_board yes) - (dnp no) + (dnp yes) (uuid "993fb52d-8981-4a0c-8ecc-8a3fae334f31") (property "Reference" "J6" (at 166.878 81.026 0) @@ -13663,7 +13663,7 @@ (exclude_from_sim no) (in_bom no) (on_board yes) - (dnp no) + (dnp yes) (uuid "a2ff12c4-3072-464f-b02b-14fdfd75b0ea") (property "Reference" "J5" (at 23.368 117.602 90) @@ -15433,7 +15433,7 @@ (exclude_from_sim no) (in_bom no) (on_board yes) - (dnp no) + (dnp yes) (uuid "f3623ce4-341f-4811-b089-a69ea4b87258") (property "Reference" "J4" (at 23.368 112.522 90) diff --git a/pcb/schematics/Doorman-REV1.5.pdf b/pcb/schematics/Doorman-REV1.5.pdf index fc487191..60fdc640 100644 Binary files a/pcb/schematics/Doorman-REV1.5.pdf and b/pcb/schematics/Doorman-REV1.5.pdf differ