@switchbot/homebridge-switchbot 5.0.0-beta.11 → 5.0.0-beta.111
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/e2e-verification.md +36 -0
- package/.github/copilot-instructions.md +39 -0
- package/.github/workflows/ci.yml +32 -0
- package/.github/workflows/manual-e2e.yml +115 -0
- package/.github/workflows/release.yml +0 -4
- package/CHANGELOG.md +35 -0
- package/E2E-VERIFICATION.md +121 -0
- package/MIGRATION.md +44 -0
- package/README.md +56 -3
- package/config.schema.json +170 -14787
- package/dist/device-types.d.ts +31 -0
- package/dist/device-types.d.ts.map +1 -0
- package/dist/device-types.js +216 -0
- package/dist/device-types.js.map +1 -0
- package/dist/deviceFactory.d.ts +13 -0
- package/dist/deviceFactory.d.ts.map +1 -0
- package/dist/deviceFactory.js +120 -0
- package/dist/deviceFactory.js.map +1 -0
- package/dist/devices/deviceBase.d.ts +50 -0
- package/dist/devices/deviceBase.d.ts.map +1 -0
- package/dist/devices/deviceBase.js +119 -0
- package/dist/devices/deviceBase.js.map +1 -0
- package/dist/devices/genericDevice.d.ts +298 -0
- package/dist/devices/genericDevice.d.ts.map +1 -0
- package/dist/devices/genericDevice.js +1067 -0
- package/dist/devices/genericDevice.js.map +1 -0
- package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
- package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/config.js +104 -0
- package/dist/homebridge-ui/endpoints/config.js.map +1 -0
- package/dist/homebridge-ui/endpoints/credentials.d.ts +6 -0
- package/dist/homebridge-ui/endpoints/credentials.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/credentials.js +65 -0
- package/dist/homebridge-ui/endpoints/credentials.js.map +1 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/devices.js +351 -0
- package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
- package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
- package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/discovery.js +171 -0
- package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
- package/dist/homebridge-ui/public/css/styles.css +254 -0
- package/dist/homebridge-ui/public/index.html +112 -247
- package/dist/homebridge-ui/public/js/api.d.ts +23 -0
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/api.js +188 -0
- package/dist/homebridge-ui/public/js/api.js.map +1 -0
- package/dist/homebridge-ui/public/js/api.ts +215 -0
- package/dist/homebridge-ui/public/js/app.d.ts +2 -0
- package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/app.js +1815 -0
- package/dist/homebridge-ui/public/js/app.js.map +7 -0
- package/dist/homebridge-ui/public/js/app.ts +22 -0
- package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
- package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/constants.js +2 -0
- package/dist/homebridge-ui/public/js/constants.js.map +1 -0
- package/dist/homebridge-ui/public/js/constants.ts +1 -0
- package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
- package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/credentials.js +96 -0
- package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
- package/dist/homebridge-ui/public/js/credentials.ts +100 -0
- package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
- package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/devices-delete.js +206 -0
- package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
- package/dist/homebridge-ui/public/js/devices-delete.ts +236 -0
- package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/devices.js +90 -0
- package/dist/homebridge-ui/public/js/devices.js.map +1 -0
- package/dist/homebridge-ui/public/js/devices.ts +98 -0
- package/dist/homebridge-ui/public/js/discovery.d.ts +3 -0
- package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/discovery.js +244 -0
- package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
- package/dist/homebridge-ui/public/js/discovery.ts +290 -0
- package/dist/homebridge-ui/public/js/logger.d.ts +6 -0
- package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/logger.js +12 -0
- package/dist/homebridge-ui/public/js/logger.js.map +1 -0
- package/dist/homebridge-ui/public/js/logger.ts +11 -0
- package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
- package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/modal.js +26 -0
- package/dist/homebridge-ui/public/js/modal.js.map +1 -0
- package/dist/homebridge-ui/public/js/modal.ts +28 -0
- package/dist/homebridge-ui/public/js/modals.d.ts +7 -0
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/modals.js +323 -0
- package/dist/homebridge-ui/public/js/modals.js.map +1 -0
- package/dist/homebridge-ui/public/js/modals.ts +367 -0
- package/dist/homebridge-ui/public/js/render.d.ts +65 -0
- package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/render.js +499 -0
- package/dist/homebridge-ui/public/js/render.js.map +1 -0
- package/dist/homebridge-ui/public/js/render.ts +579 -0
- package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
- package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/toast.js +29 -0
- package/dist/homebridge-ui/public/js/toast.js.map +1 -0
- package/dist/homebridge-ui/public/js/toast.ts +37 -0
- package/dist/homebridge-ui/public/js/types.d.ts +23 -0
- package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/types.js +2 -0
- package/dist/homebridge-ui/public/js/types.js.map +1 -0
- package/dist/homebridge-ui/public/js/types.ts +26 -0
- package/dist/homebridge-ui/server.js +11 -41
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/homebridge-ui/utils/config-parser.d.ts +35 -0
- package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
- package/dist/homebridge-ui/utils/config-parser.js +87 -0
- package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
- package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
- package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
- package/dist/homebridge-ui/utils/device-migration.js +111 -0
- package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
- package/dist/homebridge-ui/utils/logger.d.ts +7 -0
- package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
- package/dist/homebridge-ui/utils/logger.js +17 -0
- package/dist/homebridge-ui/utils/logger.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -65
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +35 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +1527 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +10 -249
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +5 -30
- package/dist/settings.js.map +1 -1
- package/dist/switchbotClient.d.ts +32 -0
- package/dist/switchbotClient.d.ts.map +1 -0
- package/dist/switchbotClient.js +207 -0
- package/dist/switchbotClient.js.map +1 -0
- package/dist/utils.d.ts +39 -50
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +40 -688
- package/dist/utils.js.map +1 -1
- package/docs/assets/highlight.css +14 -0
- package/docs/assets/icons.js +1 -1
- package/docs/assets/icons.svg +1 -1
- package/docs/assets/main.js +2 -2
- package/docs/assets/style.css +3 -3
- package/docs/index.html +77 -13
- package/docs/variables/default.html +1 -1
- package/eslint.config.js +7 -8
- package/nodemon.json +2 -2
- package/package.json +35 -32
- package/scripts/build-ui.js +36 -0
- package/scripts/e2e/README.md +25 -0
- package/scripts/e2e/curtain-e2e.sh +70 -0
- package/scripts/e2e/fan-e2e.sh +75 -0
- package/scripts/e2e/light-advanced-e2e.sh +97 -0
- package/scripts/e2e/light-e2e.sh +75 -0
- package/scripts/e2e/list-accessories.sh +19 -0
- package/scripts/e2e/lock-e2e.sh +65 -0
- package/scripts/free-dev-ports.mjs +105 -0
- package/scripts/generate-matter-maps.js +60 -0
- package/scripts/run-e2e-local.sh +14 -0
- package/scripts/sync-device-types.mjs +28 -0
- package/src/device-types.ts +230 -0
- package/src/deviceFactory.ts +163 -0
- package/src/devices/deviceBase.ts +141 -0
- package/src/devices/genericDevice.ts +998 -0
- package/src/homebridge-ui/endpoints/config.ts +123 -0
- package/src/homebridge-ui/endpoints/credentials.ts +74 -0
- package/src/homebridge-ui/endpoints/devices.ts +383 -0
- package/src/homebridge-ui/endpoints/discovery.ts +191 -0
- package/src/homebridge-ui/public/css/styles.css +254 -0
- package/src/homebridge-ui/public/index.html +112 -247
- package/src/homebridge-ui/public/js/api.ts +215 -0
- package/src/homebridge-ui/public/js/app.ts +22 -0
- package/src/homebridge-ui/public/js/constants.ts +1 -0
- package/src/homebridge-ui/public/js/credentials.ts +100 -0
- package/src/homebridge-ui/public/js/devices-delete.ts +236 -0
- package/src/homebridge-ui/public/js/devices.ts +98 -0
- package/src/homebridge-ui/public/js/discovery.ts +290 -0
- package/src/homebridge-ui/public/js/logger.ts +11 -0
- package/src/homebridge-ui/public/js/modal.ts +28 -0
- package/src/homebridge-ui/public/js/modals.ts +367 -0
- package/src/homebridge-ui/public/js/render.ts +579 -0
- package/src/homebridge-ui/public/js/toast.ts +37 -0
- package/src/homebridge-ui/public/js/types.ts +26 -0
- package/src/homebridge-ui/server.ts +11 -43
- package/src/homebridge-ui/utils/config-parser.ts +108 -0
- package/src/homebridge-ui/utils/device-migration.ts +144 -0
- package/src/homebridge-ui/utils/logger.ts +17 -0
- package/src/index.ts +4 -66
- package/src/platform.ts +1539 -0
- package/src/settings.ts +12 -277
- package/src/switchbotClient.ts +216 -0
- package/src/utils.ts +46 -713
- package/switchbot-homebridge-switchbot-5.0.0.tgz +0 -0
- package/test/accessory-restore.spec.ts +73 -0
- package/test/device-mapping.spec.ts +37 -0
- package/test/deviceFactory.spec.ts +18 -0
- package/test/e2e/run-e2e.spec.ts +50 -0
- package/test/fan-swing.spec.ts +29 -0
- package/test/helpers/matter-harness.ts +53 -0
- package/test/lock-users.spec.ts +44 -0
- package/test/matter-childbridge.spec.ts +59 -0
- package/test/matter-descriptors.spec.ts +97 -0
- package/test/matter-device-state.spec.ts +101 -0
- package/test/matter-integration.spec.ts +111 -0
- package/test/platform.integration.spec.ts +55 -0
- package/test/switchbot-client-debounce.spec.ts +131 -0
- package/test/switchbot-client-openapi.spec.ts +56 -0
- package/test/switchbotClient.spec.ts +10 -0
- package/test/utils.spec.ts +36 -0
- package/todo/COMPLETED.md +229 -0
- package/todo/README.md +59 -0
- package/todo/TODO.md +176 -0
- package/todo/mockups/filtering-sorting.svg +112 -0
- package/todo/mockups/modal-switchbot-theme.svg +66 -0
- package/todo/mockups/signal-strength-visualization.svg +94 -0
- package/vitest.config.ts +7 -0
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -15847
- package/coverage/coverage-final.json +0 -42
- package/coverage/docs/assets/dmt/dmt-component-data.js.html +0 -85
- package/coverage/docs/assets/dmt/dmt-components.js.html +0 -286
- package/coverage/docs/assets/dmt/index.html +0 -131
- package/coverage/docs/assets/hierarchy.js.html +0 -85
- package/coverage/docs/assets/icons.js.html +0 -136
- package/coverage/docs/assets/index.html +0 -146
- package/coverage/docs/assets/main.js.html +0 -265
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -191
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/coverage/src/device/blindtilt.ts.html +0 -3238
- package/coverage/src/device/bot.ts.html +0 -2803
- package/coverage/src/device/ceilinglight.ts.html +0 -2338
- package/coverage/src/device/colorbulb.ts.html +0 -2824
- package/coverage/src/device/contact.ts.html +0 -1465
- package/coverage/src/device/curtain.ts.html +0 -2869
- package/coverage/src/device/device.ts.html +0 -2500
- package/coverage/src/device/fan.ts.html +0 -2242
- package/coverage/src/device/hub.ts.html +0 -1408
- package/coverage/src/device/humidifier.ts.html +0 -2116
- package/coverage/src/device/index.html +0 -416
- package/coverage/src/device/iosensor.ts.html +0 -1375
- package/coverage/src/device/lightstrip.ts.html +0 -2617
- package/coverage/src/device/lock.ts.html +0 -1963
- package/coverage/src/device/meter.ts.html +0 -1372
- package/coverage/src/device/meterplus.ts.html +0 -1384
- package/coverage/src/device/meterpro.ts.html +0 -1618
- package/coverage/src/device/motion.ts.html +0 -1264
- package/coverage/src/device/plug.ts.html +0 -1372
- package/coverage/src/device/relayswitch.ts.html +0 -2284
- package/coverage/src/device/robotvacuumcleaner.ts.html +0 -1810
- package/coverage/src/device/waterdetector.ts.html +0 -1294
- package/coverage/src/homebridge-ui/index.html +0 -116
- package/coverage/src/homebridge-ui/server.ts.html +0 -229
- package/coverage/src/index.html +0 -161
- package/coverage/src/index.ts.html +0 -124
- package/coverage/src/irdevice/airconditioner.ts.html +0 -1687
- package/coverage/src/irdevice/airpurifier.ts.html +0 -844
- package/coverage/src/irdevice/camera.ts.html +0 -475
- package/coverage/src/irdevice/fan.ts.html +0 -766
- package/coverage/src/irdevice/index.html +0 -251
- package/coverage/src/irdevice/irdevice.ts.html +0 -1117
- package/coverage/src/irdevice/light.ts.html +0 -826
- package/coverage/src/irdevice/other.ts.html +0 -2458
- package/coverage/src/irdevice/tv.ts.html +0 -1222
- package/coverage/src/irdevice/vacuumcleaner.ts.html +0 -466
- package/coverage/src/irdevice/waterheater.ts.html +0 -469
- package/coverage/src/platform.ts.html +0 -8776
- package/coverage/src/settings.ts.html +0 -934
- package/coverage/src/utils.ts.html +0 -2092
- package/dist/devices-hap/airpurifier.d.ts +0 -54
- package/dist/devices-hap/airpurifier.d.ts.map +0 -1
- package/dist/devices-hap/airpurifier.js +0 -527
- package/dist/devices-hap/airpurifier.js.map +0 -1
- package/dist/devices-hap/blindtilt.d.ts +0 -90
- package/dist/devices-hap/blindtilt.d.ts.map +0 -1
- package/dist/devices-hap/blindtilt.js +0 -974
- package/dist/devices-hap/blindtilt.js.map +0 -1
- package/dist/devices-hap/bot.d.ts +0 -102
- package/dist/devices-hap/bot.d.ts.map +0 -1
- package/dist/devices-hap/bot.js +0 -811
- package/dist/devices-hap/bot.js.map +0 -1
- package/dist/devices-hap/ceilinglight.d.ts +0 -85
- package/dist/devices-hap/ceilinglight.d.ts.map +0 -1
- package/dist/devices-hap/ceilinglight.js +0 -701
- package/dist/devices-hap/ceilinglight.js.map +0 -1
- package/dist/devices-hap/colorbulb.d.ts +0 -88
- package/dist/devices-hap/colorbulb.d.ts.map +0 -1
- package/dist/devices-hap/colorbulb.js +0 -881
- package/dist/devices-hap/colorbulb.js.map +0 -1
- package/dist/devices-hap/contact.d.ts +0 -44
- package/dist/devices-hap/contact.d.ts.map +0 -1
- package/dist/devices-hap/contact.js +0 -409
- package/dist/devices-hap/contact.js.map +0 -1
- package/dist/devices-hap/curtain.d.ts +0 -73
- package/dist/devices-hap/curtain.d.ts.map +0 -1
- package/dist/devices-hap/curtain.js +0 -869
- package/dist/devices-hap/curtain.js.map +0 -1
- package/dist/devices-hap/device.d.ts +0 -98
- package/dist/devices-hap/device.d.ts.map +0 -1
- package/dist/devices-hap/device.js +0 -749
- package/dist/devices-hap/device.js.map +0 -1
- package/dist/devices-hap/fan.d.ts +0 -69
- package/dist/devices-hap/fan.d.ts.map +0 -1
- package/dist/devices-hap/fan.js +0 -649
- package/dist/devices-hap/fan.js.map +0 -1
- package/dist/devices-hap/hub.d.ts +0 -37
- package/dist/devices-hap/hub.d.ts.map +0 -1
- package/dist/devices-hap/hub.js +0 -392
- package/dist/devices-hap/hub.js.map +0 -1
- package/dist/devices-hap/humidifier.d.ts +0 -68
- package/dist/devices-hap/humidifier.d.ts.map +0 -1
- package/dist/devices-hap/humidifier.js +0 -628
- package/dist/devices-hap/humidifier.js.map +0 -1
- package/dist/devices-hap/iosensor.d.ts +0 -42
- package/dist/devices-hap/iosensor.d.ts.map +0 -1
- package/dist/devices-hap/iosensor.js +0 -382
- package/dist/devices-hap/iosensor.js.map +0 -1
- package/dist/devices-hap/lightstrip.d.ts +0 -79
- package/dist/devices-hap/lightstrip.d.ts.map +0 -1
- package/dist/devices-hap/lightstrip.js +0 -797
- package/dist/devices-hap/lightstrip.js.map +0 -1
- package/dist/devices-hap/lock.d.ts +0 -53
- package/dist/devices-hap/lock.d.ts.map +0 -1
- package/dist/devices-hap/lock.js +0 -561
- package/dist/devices-hap/lock.js.map +0 -1
- package/dist/devices-hap/meter.d.ts +0 -37
- package/dist/devices-hap/meter.d.ts.map +0 -1
- package/dist/devices-hap/meter.js +0 -379
- package/dist/devices-hap/meter.js.map +0 -1
- package/dist/devices-hap/meterplus.d.ts +0 -42
- package/dist/devices-hap/meterplus.d.ts.map +0 -1
- package/dist/devices-hap/meterplus.js +0 -384
- package/dist/devices-hap/meterplus.js.map +0 -1
- package/dist/devices-hap/meterpro.d.ts +0 -43
- package/dist/devices-hap/meterpro.d.ts.map +0 -1
- package/dist/devices-hap/meterpro.js +0 -468
- package/dist/devices-hap/meterpro.js.map +0 -1
- package/dist/devices-hap/motion.d.ts +0 -42
- package/dist/devices-hap/motion.d.ts.map +0 -1
- package/dist/devices-hap/motion.js +0 -345
- package/dist/devices-hap/motion.js.map +0 -1
- package/dist/devices-hap/plug.d.ts +0 -49
- package/dist/devices-hap/plug.d.ts.map +0 -1
- package/dist/devices-hap/plug.js +0 -395
- package/dist/devices-hap/plug.js.map +0 -1
- package/dist/devices-hap/relayswitch.d.ts +0 -96
- package/dist/devices-hap/relayswitch.d.ts.map +0 -1
- package/dist/devices-hap/relayswitch.js +0 -642
- package/dist/devices-hap/relayswitch.js.map +0 -1
- package/dist/devices-hap/robotvacuumcleaner.d.ts +0 -54
- package/dist/devices-hap/robotvacuumcleaner.d.ts.map +0 -1
- package/dist/devices-hap/robotvacuumcleaner.js +0 -523
- package/dist/devices-hap/robotvacuumcleaner.js.map +0 -1
- package/dist/devices-hap/waterdetector.d.ts +0 -41
- package/dist/devices-hap/waterdetector.d.ts.map +0 -1
- package/dist/devices-hap/waterdetector.js +0 -356
- package/dist/devices-hap/waterdetector.js.map +0 -1
- package/dist/devices-matter/BaseMatterAccessory.d.ts +0 -63
- package/dist/devices-matter/BaseMatterAccessory.d.ts.map +0 -1
- package/dist/devices-matter/BaseMatterAccessory.js +0 -100
- package/dist/devices-matter/BaseMatterAccessory.js.map +0 -1
- package/dist/devices-matter/ColorLightAccessory.d.ts +0 -20
- package/dist/devices-matter/ColorLightAccessory.d.ts.map +0 -1
- package/dist/devices-matter/ColorLightAccessory.js +0 -95
- package/dist/devices-matter/ColorLightAccessory.js.map +0 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts +0 -18
- package/dist/devices-matter/ColorTemperatureLightAccessory.d.ts.map +0 -1
- package/dist/devices-matter/ColorTemperatureLightAccessory.js +0 -78
- package/dist/devices-matter/ColorTemperatureLightAccessory.js.map +0 -1
- package/dist/devices-matter/ContactSensorAccessory.d.ts +0 -12
- package/dist/devices-matter/ContactSensorAccessory.d.ts.map +0 -1
- package/dist/devices-matter/ContactSensorAccessory.js +0 -34
- package/dist/devices-matter/ContactSensorAccessory.js.map +0 -1
- package/dist/devices-matter/DimmableLightAccessory.d.ts +0 -58
- package/dist/devices-matter/DimmableLightAccessory.d.ts.map +0 -1
- package/dist/devices-matter/DimmableLightAccessory.js +0 -167
- package/dist/devices-matter/DimmableLightAccessory.js.map +0 -1
- package/dist/devices-matter/DoorLockAccessory.d.ts +0 -14
- package/dist/devices-matter/DoorLockAccessory.d.ts.map +0 -1
- package/dist/devices-matter/DoorLockAccessory.js +0 -50
- package/dist/devices-matter/DoorLockAccessory.js.map +0 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.d.ts +0 -21
- package/dist/devices-matter/ExtendedColorLightAccessory.d.ts.map +0 -1
- package/dist/devices-matter/ExtendedColorLightAccessory.js +0 -107
- package/dist/devices-matter/ExtendedColorLightAccessory.js.map +0 -1
- package/dist/devices-matter/FanAccessory.d.ts +0 -16
- package/dist/devices-matter/FanAccessory.d.ts.map +0 -1
- package/dist/devices-matter/FanAccessory.js +0 -81
- package/dist/devices-matter/FanAccessory.js.map +0 -1
- package/dist/devices-matter/HumiditySensorAccessory.d.ts +0 -12
- package/dist/devices-matter/HumiditySensorAccessory.d.ts.map +0 -1
- package/dist/devices-matter/HumiditySensorAccessory.js +0 -34
- package/dist/devices-matter/HumiditySensorAccessory.js.map +0 -1
- package/dist/devices-matter/LeakSensorAccessory.d.ts +0 -12
- package/dist/devices-matter/LeakSensorAccessory.d.ts.map +0 -1
- package/dist/devices-matter/LeakSensorAccessory.js +0 -33
- package/dist/devices-matter/LeakSensorAccessory.js.map +0 -1
- package/dist/devices-matter/LightSensorAccessory.d.ts +0 -12
- package/dist/devices-matter/LightSensorAccessory.d.ts.map +0 -1
- package/dist/devices-matter/LightSensorAccessory.js +0 -34
- package/dist/devices-matter/LightSensorAccessory.js.map +0 -1
- package/dist/devices-matter/OccupancySensorAccessory.d.ts +0 -12
- package/dist/devices-matter/OccupancySensorAccessory.d.ts.map +0 -1
- package/dist/devices-matter/OccupancySensorAccessory.js +0 -39
- package/dist/devices-matter/OccupancySensorAccessory.js.map +0 -1
- package/dist/devices-matter/OnOffLightAccessory.d.ts +0 -38
- package/dist/devices-matter/OnOffLightAccessory.d.ts.map +0 -1
- package/dist/devices-matter/OnOffLightAccessory.js +0 -118
- package/dist/devices-matter/OnOffLightAccessory.js.map +0 -1
- package/dist/devices-matter/OnOffOutletAccessory.d.ts +0 -12
- package/dist/devices-matter/OnOffOutletAccessory.d.ts.map +0 -1
- package/dist/devices-matter/OnOffOutletAccessory.js +0 -40
- package/dist/devices-matter/OnOffOutletAccessory.js.map +0 -1
- package/dist/devices-matter/OnOffSwitchAccessory.d.ts +0 -14
- package/dist/devices-matter/OnOffSwitchAccessory.d.ts.map +0 -1
- package/dist/devices-matter/OnOffSwitchAccessory.js +0 -42
- package/dist/devices-matter/OnOffSwitchAccessory.js.map +0 -1
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts +0 -68
- package/dist/devices-matter/RoboticVacuumAccessory.d.ts.map +0 -1
- package/dist/devices-matter/RoboticVacuumAccessory.js +0 -334
- package/dist/devices-matter/RoboticVacuumAccessory.js.map +0 -1
- package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts +0 -11
- package/dist/devices-matter/SmokeCOAlarmAccessory.d.ts.map +0 -1
- package/dist/devices-matter/SmokeCOAlarmAccessory.js +0 -49
- package/dist/devices-matter/SmokeCOAlarmAccessory.js.map +0 -1
- package/dist/devices-matter/TemperatureSensorAccessory.d.ts +0 -12
- package/dist/devices-matter/TemperatureSensorAccessory.d.ts.map +0 -1
- package/dist/devices-matter/TemperatureSensorAccessory.js +0 -36
- package/dist/devices-matter/TemperatureSensorAccessory.js.map +0 -1
- package/dist/devices-matter/ThermostatAccessory.d.ts +0 -19
- package/dist/devices-matter/ThermostatAccessory.d.ts.map +0 -1
- package/dist/devices-matter/ThermostatAccessory.js +0 -95
- package/dist/devices-matter/ThermostatAccessory.js.map +0 -1
- package/dist/devices-matter/VenetianBlindAccessory.d.ts +0 -19
- package/dist/devices-matter/VenetianBlindAccessory.d.ts.map +0 -1
- package/dist/devices-matter/VenetianBlindAccessory.js +0 -99
- package/dist/devices-matter/VenetianBlindAccessory.js.map +0 -1
- package/dist/devices-matter/WindowBlindAccessory.d.ts +0 -17
- package/dist/devices-matter/WindowBlindAccessory.d.ts.map +0 -1
- package/dist/devices-matter/WindowBlindAccessory.js +0 -80
- package/dist/devices-matter/WindowBlindAccessory.js.map +0 -1
- package/dist/devices-matter/custom/PowerStripAccessory.d.ts +0 -97
- package/dist/devices-matter/custom/PowerStripAccessory.d.ts.map +0 -1
- package/dist/devices-matter/custom/PowerStripAccessory.js +0 -265
- package/dist/devices-matter/custom/PowerStripAccessory.js.map +0 -1
- package/dist/devices-matter/custom/index.d.ts +0 -8
- package/dist/devices-matter/custom/index.d.ts.map +0 -1
- package/dist/devices-matter/custom/index.js +0 -8
- package/dist/devices-matter/custom/index.js.map +0 -1
- package/dist/devices-matter/index.d.ts +0 -29
- package/dist/devices-matter/index.d.ts.map +0 -1
- package/dist/devices-matter/index.js +0 -28
- package/dist/devices-matter/index.js.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
- package/dist/index.test.js +0 -19
- package/dist/index.test.js.map +0 -1
- package/dist/irdevice/airconditioner.d.ts +0 -61
- package/dist/irdevice/airconditioner.d.ts.map +0 -1
- package/dist/irdevice/airconditioner.js +0 -472
- package/dist/irdevice/airconditioner.js.map +0 -1
- package/dist/irdevice/airpurifier.d.ts +0 -50
- package/dist/irdevice/airpurifier.d.ts.map +0 -1
- package/dist/irdevice/airpurifier.js +0 -213
- package/dist/irdevice/airpurifier.js.map +0 -1
- package/dist/irdevice/camera.d.ts +0 -32
- package/dist/irdevice/camera.d.ts.map +0 -1
- package/dist/irdevice/camera.js +0 -107
- package/dist/irdevice/camera.js.map +0 -1
- package/dist/irdevice/fan.d.ts +0 -36
- package/dist/irdevice/fan.d.ts.map +0 -1
- package/dist/irdevice/fan.js +0 -200
- package/dist/irdevice/fan.js.map +0 -1
- package/dist/irdevice/irdevice.d.ts +0 -68
- package/dist/irdevice/irdevice.d.ts.map +0 -1
- package/dist/irdevice/irdevice.js +0 -298
- package/dist/irdevice/irdevice.js.map +0 -1
- package/dist/irdevice/light.d.ts +0 -36
- package/dist/irdevice/light.d.ts.map +0 -1
- package/dist/irdevice/light.js +0 -206
- package/dist/irdevice/light.js.map +0 -1
- package/dist/irdevice/other.d.ts +0 -57
- package/dist/irdevice/other.d.ts.map +0 -1
- package/dist/irdevice/other.js +0 -778
- package/dist/irdevice/other.js.map +0 -1
- package/dist/irdevice/tv.d.ts +0 -45
- package/dist/irdevice/tv.d.ts.map +0 -1
- package/dist/irdevice/tv.js +0 -327
- package/dist/irdevice/tv.js.map +0 -1
- package/dist/irdevice/vacuumcleaner.d.ts +0 -28
- package/dist/irdevice/vacuumcleaner.d.ts.map +0 -1
- package/dist/irdevice/vacuumcleaner.js +0 -104
- package/dist/irdevice/vacuumcleaner.js.map +0 -1
- package/dist/irdevice/waterheater.d.ts +0 -30
- package/dist/irdevice/waterheater.d.ts.map +0 -1
- package/dist/irdevice/waterheater.js +0 -105
- package/dist/irdevice/waterheater.js.map +0 -1
- package/dist/platform-hap.d.ts +0 -149
- package/dist/platform-hap.d.ts.map +0 -1
- package/dist/platform-hap.js +0 -2861
- package/dist/platform-hap.js.map +0 -1
- package/dist/platform-matter.d.ts +0 -120
- package/dist/platform-matter.d.ts.map +0 -1
- package/dist/platform-matter.js +0 -984
- package/dist/platform-matter.js.map +0 -1
- package/dist/verifyconfig.test.d.ts +0 -2
- package/dist/verifyconfig.test.d.ts.map +0 -1
- package/dist/verifyconfig.test.js +0 -167
- package/dist/verifyconfig.test.js.map +0 -1
- package/src/custom.d.ts +0 -7
- package/src/devices-hap/airpurifier.ts +0 -563
- package/src/devices-hap/blindtilt.ts +0 -1049
- package/src/devices-hap/bot.ts +0 -900
- package/src/devices-hap/ceilinglight.ts +0 -742
- package/src/devices-hap/colorbulb.ts +0 -904
- package/src/devices-hap/contact.ts +0 -457
- package/src/devices-hap/curtain.ts +0 -944
- package/src/devices-hap/device.ts +0 -811
- package/src/devices-hap/fan.ts +0 -711
- package/src/devices-hap/hub.ts +0 -439
- package/src/devices-hap/humidifier.ts +0 -669
- package/src/devices-hap/iosensor.ts +0 -427
- package/src/devices-hap/lightstrip.ts +0 -836
- package/src/devices-hap/lock.ts +0 -620
- package/src/devices-hap/meter.ts +0 -426
- package/src/devices-hap/meterplus.ts +0 -430
- package/src/devices-hap/meterpro.ts +0 -522
- package/src/devices-hap/motion.ts +0 -390
- package/src/devices-hap/plug.ts +0 -423
- package/src/devices-hap/relayswitch.ts +0 -727
- package/src/devices-hap/robotvacuumcleaner.ts +0 -568
- package/src/devices-hap/waterdetector.ts +0 -400
- package/src/devices-matter/BaseMatterAccessory.ts +0 -131
- package/src/devices-matter/ColorLightAccessory.ts +0 -110
- package/src/devices-matter/ColorTemperatureLightAccessory.ts +0 -92
- package/src/devices-matter/ContactSensorAccessory.ts +0 -41
- package/src/devices-matter/DimmableLightAccessory.ts +0 -192
- package/src/devices-matter/DoorLockAccessory.ts +0 -60
- package/src/devices-matter/ExtendedColorLightAccessory.ts +0 -123
- package/src/devices-matter/FanAccessory.ts +0 -95
- package/src/devices-matter/HumiditySensorAccessory.ts +0 -41
- package/src/devices-matter/LeakSensorAccessory.ts +0 -40
- package/src/devices-matter/LightSensorAccessory.ts +0 -41
- package/src/devices-matter/OccupancySensorAccessory.ts +0 -48
- package/src/devices-matter/OnOffLightAccessory.ts +0 -133
- package/src/devices-matter/OnOffOutletAccessory.ts +0 -46
- package/src/devices-matter/OnOffSwitchAccessory.ts +0 -51
- package/src/devices-matter/RoboticVacuumAccessory.ts +0 -407
- package/src/devices-matter/SmokeCOAlarmAccessory.ts +0 -59
- package/src/devices-matter/TemperatureSensorAccessory.ts +0 -43
- package/src/devices-matter/ThermostatAccessory.ts +0 -110
- package/src/devices-matter/VenetianBlindAccessory.ts +0 -115
- package/src/devices-matter/WindowBlindAccessory.ts +0 -92
- package/src/devices-matter/custom/PowerStripAccessory.ts +0 -309
- package/src/devices-matter/custom/index.ts +0 -8
- package/src/devices-matter/index.ts +0 -29
- package/src/index.test.ts +0 -24
- package/src/irdevice/airconditioner.ts +0 -533
- package/src/irdevice/airpurifier.ts +0 -252
- package/src/irdevice/camera.ts +0 -129
- package/src/irdevice/fan.ts +0 -226
- package/src/irdevice/irdevice.ts +0 -344
- package/src/irdevice/light.ts +0 -246
- package/src/irdevice/other.ts +0 -790
- package/src/irdevice/tv.ts +0 -378
- package/src/irdevice/vacuumcleaner.ts +0 -126
- package/src/irdevice/waterheater.ts +0 -127
- package/src/platform-hap.ts +0 -2997
- package/src/platform-matter.ts +0 -1110
- package/src/verifyconfig.test.ts +0 -197
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createPlatformProxy } from '../src/utils'
|
|
3
|
+
import { makeFakeApiWithMatter, makeFakeApiWithoutMatter, makeFakeMatterApi } from './helpers/matter-harness'
|
|
4
|
+
import SwitchBotHAPPlatform, { SwitchBotMatterPlatform } from '../src/platform'
|
|
5
|
+
|
|
6
|
+
// Expanded Matter integration harness tests
|
|
7
|
+
|
|
8
|
+
describe('Matter integration harness', () => {
|
|
9
|
+
it('normalizes Color Bulb and Motion Sensor to Matter-supported categories', async () => {
|
|
10
|
+
const matterApi = makeFakeMatterApi()
|
|
11
|
+
const api = makeFakeApiWithMatter(matterApi)
|
|
12
|
+
|
|
13
|
+
const platform = new SwitchBotMatterPlatform(console as any, {
|
|
14
|
+
devices: [
|
|
15
|
+
{ id: 'cb-1', type: 'Color Bulb', name: 'Color Bulb 1' },
|
|
16
|
+
{ id: 'ms-1', type: 'Motion Sensor', name: 'Motion Sensor 1' },
|
|
17
|
+
],
|
|
18
|
+
enableMatter: true,
|
|
19
|
+
preferMatter: true,
|
|
20
|
+
} as any, api as any)
|
|
21
|
+
|
|
22
|
+
await (platform as any).registerMatterAccessories()
|
|
23
|
+
|
|
24
|
+
const regs = matterApi.getRegistered()
|
|
25
|
+
expect(regs.length).toBeGreaterThanOrEqual(2)
|
|
26
|
+
const ids = regs.map((r: any) => r?.context?.deviceId)
|
|
27
|
+
expect(ids).toContain('cb-1')
|
|
28
|
+
expect(ids).toContain('ms-1')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('uses conformant window covering type for curtain Matter registration', async () => {
|
|
32
|
+
const matterApi = makeFakeMatterApi()
|
|
33
|
+
const api = makeFakeApiWithMatter(matterApi)
|
|
34
|
+
|
|
35
|
+
const platform = new SwitchBotMatterPlatform(console as any, {
|
|
36
|
+
devices: [{ id: 'curtain-1', type: 'curtain', name: 'Curtain 1' }],
|
|
37
|
+
enableMatter: true,
|
|
38
|
+
preferMatter: true,
|
|
39
|
+
} as any, api as any)
|
|
40
|
+
|
|
41
|
+
await (platform as any).registerMatterAccessories()
|
|
42
|
+
|
|
43
|
+
const regs = matterApi.getRegistered()
|
|
44
|
+
expect(regs.length).toBeGreaterThanOrEqual(1)
|
|
45
|
+
const wcType = regs[0]?.clusters?.windowCovering?.type
|
|
46
|
+
expect(wcType).toBe(0)
|
|
47
|
+
expect(wcType).not.toBe(4)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('registers Matter accessories when Matter API available', async () => {
|
|
51
|
+
// Instantiate the real Matter platform directly to test its registration path
|
|
52
|
+
const matterApi = makeFakeMatterApi()
|
|
53
|
+
const api = makeFakeApiWithMatter(matterApi)
|
|
54
|
+
|
|
55
|
+
const platform = new SwitchBotMatterPlatform(console as any, { devices: [{ id: 'm-1', type: 'light', name: 'L1' }], enableMatter: true, preferMatter: true } as any, api as any)
|
|
56
|
+
|
|
57
|
+
// Call register path directly
|
|
58
|
+
if (typeof (platform as any).registerMatterAccessories === 'function') {
|
|
59
|
+
await (platform as any).registerMatterAccessories()
|
|
60
|
+
} else if (typeof (platform as any).loadDevices === 'function') {
|
|
61
|
+
await (platform as any).loadDevices()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const regs = matterApi.getRegistered()
|
|
65
|
+
expect(regs.length).toBeGreaterThanOrEqual(1)
|
|
66
|
+
// The platform registers serialized accessories; ensure deviceId is present in context
|
|
67
|
+
expect(regs[0]).toHaveProperty('context')
|
|
68
|
+
expect(regs[0].context).toHaveProperty('deviceId', 'm-1')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('falls back to HAP when Matter API unavailable', async () => {
|
|
72
|
+
// Directly instantiate the HAP platform to exercise HAP fallback
|
|
73
|
+
const api = makeFakeApiWithoutMatter()
|
|
74
|
+
const platform = new SwitchBotHAPPlatform(console as any, { devices: [{ id: 'h-1', type: 'light', name: 'H1' }], enableMatter: true, preferMatter: true } as any, api as any)
|
|
75
|
+
|
|
76
|
+
// call loadDevices for HAP flow
|
|
77
|
+
if (typeof (platform as any).loadDevices === 'function') await (platform as any).loadDevices()
|
|
78
|
+
|
|
79
|
+
// Platform should have an accessories map populated
|
|
80
|
+
const accMap = (platform as any).accessories
|
|
81
|
+
expect(accMap).toBeDefined()
|
|
82
|
+
const keys = Array.from(accMap.keys())
|
|
83
|
+
expect(keys.length).toBeGreaterThanOrEqual(1)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('reuses restored Matter accessories instead of registering duplicates', async () => {
|
|
87
|
+
const matterApi = makeFakeMatterApi()
|
|
88
|
+
const api = makeFakeApiWithMatter(matterApi)
|
|
89
|
+
|
|
90
|
+
const platform = new SwitchBotMatterPlatform(console as any, { devices: [{ id: 'r-1', type: 'light', name: 'R1' }], enableMatter: true, preferMatter: true } as any, api as any)
|
|
91
|
+
|
|
92
|
+
// Simulate a restored accessory present before registration
|
|
93
|
+
const restored = { uuid: matterApi.uuid.generate('r-1'), displayName: 'Restored R1', context: { deviceId: 'r-1' } }
|
|
94
|
+
if (typeof (platform as any).configureMatterAccessory === 'function') {
|
|
95
|
+
await (platform as any).configureMatterAccessory(restored)
|
|
96
|
+
} else if (typeof (platform as any).configureAccessory === 'function') {
|
|
97
|
+
await (platform as any).configureAccessory(restored)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Now call register path
|
|
101
|
+
if (typeof (platform as any).registerMatterAccessories === 'function') await (platform as any).registerMatterAccessories()
|
|
102
|
+
else if (typeof (platform as any).loadDevices === 'function') {
|
|
103
|
+
await (platform as any).loadDevices()
|
|
104
|
+
if (typeof (platform as any).registerMatterAccessories === 'function') await (platform as any).registerMatterAccessories()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const accs = (platform as any).accessories
|
|
108
|
+
const found = Array.from(accs.values()).some((a: any) => a && (a.uuid === restored.uuid || a.UUID === restored.uuid))
|
|
109
|
+
expect(found).toBeTruthy()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { SwitchBotHAPPlatform } from '../src/platform'
|
|
3
|
+
|
|
4
|
+
// Minimal mock logger
|
|
5
|
+
const log = {
|
|
6
|
+
info: () => {},
|
|
7
|
+
warn: () => {},
|
|
8
|
+
error: () => {},
|
|
9
|
+
debug: () => {},
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('platform integration (HAP)', () => {
|
|
13
|
+
it('registers HAP accessories using Homebridge API', async () => {
|
|
14
|
+
const registered: any[] = []
|
|
15
|
+
|
|
16
|
+
// Minimal hap stub
|
|
17
|
+
const hap = {
|
|
18
|
+
uuid: { generate: (s: string) => `uuid-${s}` },
|
|
19
|
+
Service: { Switch: 'Switch', Lightbulb: 'Lightbulb' },
|
|
20
|
+
Characteristic: { On: 'On', Brightness: 'Brightness' },
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Simple platformAccessory constructor
|
|
24
|
+
function PlatformAccessory(name: string, uuid: string) {
|
|
25
|
+
this.displayName = name
|
|
26
|
+
this.UUID = uuid
|
|
27
|
+
this.services = []
|
|
28
|
+
this.getService = (type: any) => this.services.find((s: any) => s.type === type)
|
|
29
|
+
this.addService = (type: any) => {
|
|
30
|
+
const s: any = { type, characteristics: {} }
|
|
31
|
+
this.services.push(s)
|
|
32
|
+
return s
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const api: any = {
|
|
37
|
+
hap,
|
|
38
|
+
platformAccessory: PlatformAccessory as any,
|
|
39
|
+
registerPlatformAccessories: (_p: string, _n: string, accs: any[]) => registered.push(...accs),
|
|
40
|
+
on: (_ev: string, cb: Function) => { /* do not auto-run */ },
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const config: any = { devices: [{ id: 'dev1', type: 'light', name: 'L1' }] }
|
|
44
|
+
|
|
45
|
+
const platform = new SwitchBotHAPPlatform(log as any, config, api)
|
|
46
|
+
|
|
47
|
+
// call loadDevices directly to control flow
|
|
48
|
+
await (platform as any).loadDevices()
|
|
49
|
+
|
|
50
|
+
// Expect accessory created and registered
|
|
51
|
+
expect(registered.length).toBeGreaterThanOrEqual(1)
|
|
52
|
+
const keys = Array.from((platform as any).accessories.keys())
|
|
53
|
+
expect(keys.some((k: string) => k.includes('dev1'))).toBeTruthy()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { SwitchBotClient } from '../src/switchbotClient'
|
|
3
|
+
|
|
4
|
+
describe('SwitchBotClient debounce/coalesce', () => {
|
|
5
|
+
let origFetch: any
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
origFetch = (global as any).fetch
|
|
9
|
+
vi.useFakeTimers()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
(global as any).fetch = origFetch
|
|
14
|
+
vi.restoreAllMocks()
|
|
15
|
+
vi.useRealTimers()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('coalesces rapid writes for the same device (last write wins)', async () => {
|
|
19
|
+
const respBody = { status: 'ok' }
|
|
20
|
+
const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
|
|
21
|
+
;(global as any).fetch = fetchMock
|
|
22
|
+
|
|
23
|
+
// Set a short debounce in config to keep the test fast
|
|
24
|
+
const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
|
|
25
|
+
await c.init()
|
|
26
|
+
|
|
27
|
+
const body1 = { command: 'one' }
|
|
28
|
+
const body2 = { command: 'two' }
|
|
29
|
+
|
|
30
|
+
const p1 = c.setDeviceState('d-debounce', body1)
|
|
31
|
+
const p2 = c.setDeviceState('d-debounce', body2)
|
|
32
|
+
|
|
33
|
+
// Fast-forward timers past debounce window
|
|
34
|
+
vi.advanceTimersByTime(20)
|
|
35
|
+
// Allow pending microtasks to resolve
|
|
36
|
+
await Promise.resolve()
|
|
37
|
+
await Promise.resolve()
|
|
38
|
+
|
|
39
|
+
const [r1, r2] = await Promise.all([p1, p2])
|
|
40
|
+
expect(r1).toEqual(respBody)
|
|
41
|
+
expect(r2).toEqual(respBody)
|
|
42
|
+
|
|
43
|
+
// Only one network call should have been made
|
|
44
|
+
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
45
|
+
const calledBody = JSON.parse(fetchMock.mock.calls[0][1].body)
|
|
46
|
+
expect(calledBody).toEqual(body2)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('does not coalesce writes for different devices', async () => {
|
|
50
|
+
const respBody = { status: 'ok' }
|
|
51
|
+
const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
|
|
52
|
+
;(global as any).fetch = fetchMock
|
|
53
|
+
|
|
54
|
+
const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
|
|
55
|
+
await c.init()
|
|
56
|
+
|
|
57
|
+
const p1 = c.setDeviceState('d-A', { command: 'a' })
|
|
58
|
+
const p2 = c.setDeviceState('d-B', { command: 'b' })
|
|
59
|
+
|
|
60
|
+
vi.advanceTimersByTime(20)
|
|
61
|
+
await Promise.resolve()
|
|
62
|
+
await Promise.resolve()
|
|
63
|
+
|
|
64
|
+
await Promise.all([p1, p2])
|
|
65
|
+
|
|
66
|
+
expect(fetchMock).toHaveBeenCalledTimes(2)
|
|
67
|
+
const b1 = JSON.parse(fetchMock.mock.calls[0][1].body)
|
|
68
|
+
const b2 = JSON.parse(fetchMock.mock.calls[1][1].body)
|
|
69
|
+
expect(b1).toEqual({ command: 'a' })
|
|
70
|
+
expect(b2).toEqual({ command: 'b' })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('coalesces multiple rapid writes (three) and resolves all promises with last response', async () => {
|
|
74
|
+
const respBody = { status: 'ok', val: 'last' }
|
|
75
|
+
const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
|
|
76
|
+
;(global as any).fetch = fetchMock
|
|
77
|
+
|
|
78
|
+
const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
|
|
79
|
+
await c.init()
|
|
80
|
+
|
|
81
|
+
const p1 = c.setDeviceState('d-debounce', { command: 'one' })
|
|
82
|
+
const p2 = c.setDeviceState('d-debounce', { command: 'two' })
|
|
83
|
+
const p3 = c.setDeviceState('d-debounce', { command: 'three' })
|
|
84
|
+
|
|
85
|
+
vi.advanceTimersByTime(20)
|
|
86
|
+
await Promise.resolve()
|
|
87
|
+
await Promise.resolve()
|
|
88
|
+
|
|
89
|
+
const [r1, r2, r3] = await Promise.all([p1, p2, p3])
|
|
90
|
+
expect(r1).toEqual(respBody)
|
|
91
|
+
expect(r2).toEqual(respBody)
|
|
92
|
+
expect(r3).toEqual(respBody)
|
|
93
|
+
|
|
94
|
+
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
95
|
+
const calledBody = JSON.parse(fetchMock.mock.calls[0][1].body)
|
|
96
|
+
expect(calledBody).toEqual({ command: 'three' })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('separate writes outside debounce window produce multiple network calls', async () => {
|
|
100
|
+
const respBody1 = { status: 'ok', seq: 1 }
|
|
101
|
+
const respBody2 = { status: 'ok', seq: 2 }
|
|
102
|
+
const fetchMock = vi.fn()
|
|
103
|
+
.mockImplementationOnce(async () => ({ json: async () => respBody1 }))
|
|
104
|
+
.mockImplementationOnce(async () => ({ json: async () => respBody2 }))
|
|
105
|
+
;(global as any).fetch = fetchMock
|
|
106
|
+
|
|
107
|
+
const c = new SwitchBotClient({ openApiToken: 't', writeDebounceMs: 10 } as any)
|
|
108
|
+
await c.init()
|
|
109
|
+
|
|
110
|
+
const p1 = c.setDeviceState('d-debounce', { command: 'one' })
|
|
111
|
+
vi.advanceTimersByTime(20)
|
|
112
|
+
await Promise.resolve()
|
|
113
|
+
await Promise.resolve()
|
|
114
|
+
|
|
115
|
+
const p2 = c.setDeviceState('d-debounce', { command: 'two' })
|
|
116
|
+
vi.advanceTimersByTime(20)
|
|
117
|
+
await Promise.resolve()
|
|
118
|
+
await Promise.resolve()
|
|
119
|
+
|
|
120
|
+
const r1 = await p1
|
|
121
|
+
const r2 = await p2
|
|
122
|
+
|
|
123
|
+
expect(fetchMock).toHaveBeenCalledTimes(2)
|
|
124
|
+
const b1 = JSON.parse(fetchMock.mock.calls[0][1].body)
|
|
125
|
+
const b2 = JSON.parse(fetchMock.mock.calls[1][1].body)
|
|
126
|
+
expect(b1).toEqual({ command: 'one' })
|
|
127
|
+
expect(b2).toEqual({ command: 'two' })
|
|
128
|
+
expect(r1).toEqual(respBody1)
|
|
129
|
+
expect(r2).toEqual(respBody2)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { SwitchBotClient } from '../src/switchbotClient'
|
|
3
|
+
|
|
4
|
+
describe('SwitchBotClient OpenAPI fallback', () => {
|
|
5
|
+
let origFetch: any
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
origFetch = (global as any).fetch
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
(global as any).fetch = origFetch
|
|
13
|
+
vi.restoreAllMocks()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('throws when no client and no token', async () => {
|
|
17
|
+
const c = new SwitchBotClient({} as any)
|
|
18
|
+
await c.init()
|
|
19
|
+
await expect(c.getDevice('nope')).rejects.toThrow('No SwitchBot client available')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('uses OpenAPI when token provided for getDevice', async () => {
|
|
23
|
+
const fakeResp = { body: { device: { id: 'd1', state: 'ok' } } }
|
|
24
|
+
const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => fakeResp }))
|
|
25
|
+
;(global as any).fetch = fetchMock
|
|
26
|
+
|
|
27
|
+
const c = new SwitchBotClient({ openApiToken: 'token-123' } as any)
|
|
28
|
+
await c.init()
|
|
29
|
+
const res = await c.getDevice('d1')
|
|
30
|
+
expect(res).toEqual(fakeResp)
|
|
31
|
+
expect(fetchMock).toHaveBeenCalledWith('https://api.switch-bot.com/v1.0/devices/d1', { headers: { Authorization: 'token-123' } })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('posts commands to OpenAPI for setDeviceState', async () => {
|
|
35
|
+
const respBody = { status: 'ok' }
|
|
36
|
+
const fetchMock = vi.fn(async (url: string, opts: any) => ({ json: async () => respBody }))
|
|
37
|
+
;(global as any).fetch = fetchMock
|
|
38
|
+
|
|
39
|
+
const c = new SwitchBotClient({ openApiToken: 't' } as any)
|
|
40
|
+
await c.init()
|
|
41
|
+
const body = { command: 'turnOn', parameter: 'default', commandType: 'command' }
|
|
42
|
+
const out = await c.setDeviceState('d2', body)
|
|
43
|
+
expect(out).toEqual(respBody)
|
|
44
|
+
expect(fetchMock).toHaveBeenCalledWith('https://api.switch-bot.com/v1.0/devices/d2/commands', expect.objectContaining({ method: 'POST' }))
|
|
45
|
+
const calledBody = JSON.parse((fetchMock.mock.calls[0][1].body))
|
|
46
|
+
expect(calledBody).toEqual(body)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('propagates fetch errors', async () => {
|
|
50
|
+
const fetchMock = vi.fn(async () => { throw new Error('network') })
|
|
51
|
+
;(global as any).fetch = fetchMock
|
|
52
|
+
const c = new SwitchBotClient({ openApiToken: 't' } as any)
|
|
53
|
+
await c.init()
|
|
54
|
+
await expect(c.getDevice('x')).rejects.toThrow('network')
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { SwitchBotClient } from '../src/switchbotClient'
|
|
3
|
+
|
|
4
|
+
describe('SwitchBotClient', () => {
|
|
5
|
+
it('init does not throw and getDevice throws when no token/client', async () => {
|
|
6
|
+
const c = new SwitchBotClient({})
|
|
7
|
+
await c.init()
|
|
8
|
+
await expect(c.getDevice('nope')).rejects.toThrow()
|
|
9
|
+
})
|
|
10
|
+
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createPlatformProxy } from '../src/utils'
|
|
3
|
+
|
|
4
|
+
describe('createPlatformProxy', () => {
|
|
5
|
+
it('returns Matter platform when preferMatter, enabled, and Matter API is available', () => {
|
|
6
|
+
class HAP {}
|
|
7
|
+
class Matter {}
|
|
8
|
+
const ProxyCtor: any = createPlatformProxy(HAP, Matter)
|
|
9
|
+
const api = {
|
|
10
|
+
isMatterAvailable: () => true,
|
|
11
|
+
isMatterEnabled: () => true,
|
|
12
|
+
}
|
|
13
|
+
const impl = new (ProxyCtor as any)(console, { preferMatter: true, enableMatter: true }, api)
|
|
14
|
+
expect(impl).toBeInstanceOf(Matter)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('returns HAP platform when preferMatter is false', () => {
|
|
18
|
+
class HAP {}
|
|
19
|
+
class Matter {}
|
|
20
|
+
const ProxyCtor: any = createPlatformProxy(HAP, Matter)
|
|
21
|
+
const impl = new (ProxyCtor as any)(console, { preferMatter: false, enableMatter: true }, undefined)
|
|
22
|
+
expect(impl).toBeInstanceOf(HAP)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('returns HAP platform when Matter API is unavailable', () => {
|
|
26
|
+
class HAP {}
|
|
27
|
+
class Matter {}
|
|
28
|
+
const ProxyCtor: any = createPlatformProxy(HAP, Matter)
|
|
29
|
+
const api = {
|
|
30
|
+
isMatterAvailable: () => false,
|
|
31
|
+
isMatterEnabled: () => false,
|
|
32
|
+
}
|
|
33
|
+
const impl = new (ProxyCtor as any)(console, { preferMatter: true, enableMatter: true }, api)
|
|
34
|
+
expect(impl).toBeInstanceOf(HAP)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Completed TODOs
|
|
2
|
+
|
|
3
|
+
This file tracks all completed features and improvements for the SwitchBot Homebridge Plugin.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Code Refactoring & Architecture
|
|
8
|
+
|
|
9
|
+
### Server-Side Refactoring ✅ COMPLETED
|
|
10
|
+
|
|
11
|
+
- [x] **Split `server.ts` into modular structure**
|
|
12
|
+
- [x] Created `src/homebridge-ui/endpoints/discovery.ts` - Discovery endpoint
|
|
13
|
+
- [x] Created `src/homebridge-ui/endpoints/config.ts` - Config management endpoint
|
|
14
|
+
- [x] Created `src/homebridge-ui/endpoints/devices.ts` - Device CRUD operations
|
|
15
|
+
- [x] Created `src/homebridge-ui/endpoints/credentials.ts` - Credentials endpoint
|
|
16
|
+
- [x] Created `src/homebridge-ui/utils/config-parser.ts` - Config parsing helpers
|
|
17
|
+
- [x] Created `src/homebridge-ui/utils/logger.ts` - Logging utility
|
|
18
|
+
- [x] Updated `src/homebridge-ui/server.ts` - Main entry point (imports and registers endpoints)
|
|
19
|
+
|
|
20
|
+
**Status:** Server endpoints are fully modularized and organized by concern.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Bug Fixes
|
|
25
|
+
|
|
26
|
+
### Matter Window Covering Conformance ✅ COMPLETED
|
|
27
|
+
|
|
28
|
+
- [x] **Fix Matter window covering Drapery enum validation error**
|
|
29
|
+
- [x] **Issue:** Matter accessory registration failed for Curtain devices
|
|
30
|
+
- [x] **Error:** `Conformance "LF & !TL": Matter does not allow enum value Drapery (ID 4) here`
|
|
31
|
+
- [x] **Related Runtime Failure:** `[Matter/Server] Failed to register Matter accessory ... [endpoint-behaviors] Behaviors have errors` with rollback from Matter transaction initialization
|
|
32
|
+
- [x] **Root Cause:** `windowCovering.type` used `Drapery` for lift-only curtain mappings
|
|
33
|
+
- [x] **Fix:** Updated curtain Matter default to a compliant lift-only covering enum and added regression coverage
|
|
34
|
+
|
|
35
|
+
**Status:** Curtain-type Matter accessories now register without the Drapery conformance failure.
|
|
36
|
+
|
|
37
|
+
### Invalid Device Type Validation ✅ COMPLETED
|
|
38
|
+
|
|
39
|
+
- [x] **Fix invalid device type in config schema validation**
|
|
40
|
+
- [x] **Issue:** Config validation rejects devices with invalid `configDeviceType` values (e.g., "Lock Vision Pro" is not in valid enum)
|
|
41
|
+
- [x] **Error:** "Value is not accepted. Valid values: ..." error shown in Homebridge UI
|
|
42
|
+
- [x] **Root Cause:** Device types in existing configs may not match current DEVICE_TYPES enum (legacy names, typos, or renamed devices)
|
|
43
|
+
- [x] **Affected Devices:** Any device with `configDeviceType` not matching DEVICE_TYPES enum
|
|
44
|
+
- [x] **Solution:** Implemented device type validation migration and auto-correction
|
|
45
|
+
|
|
46
|
+
**Implementation:**
|
|
47
|
+
- Added `normalizeDeviceType()` — Validates and maps invalid types to valid equivalents
|
|
48
|
+
- Added `isValidDeviceType()` — Checks if device type is in DEVICE_TYPES enum
|
|
49
|
+
- Added `getValidDeviceTypes()` — Returns set of all valid device types
|
|
50
|
+
- Created `src/homebridge-ui/utils/device-migration.ts` — Migration utilities for batch validation
|
|
51
|
+
- Updated `src/homebridge-ui/endpoints/config.ts` — Device loading now validates types and warns about invalid ones
|
|
52
|
+
- Added migration mapping: `"Lock Vision Pro"` → `"Keypad Vision Pro"` (and others)
|
|
53
|
+
- Config endpoint returns `validationWarnings` with invalid device types and suggestions
|
|
54
|
+
|
|
55
|
+
**Status:** Device type validation is now automatic. Invalid types are detected and suggestions provided to users. Auto-correction mappings handle legacy/typo device names.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### Client-Side Refactoring
|
|
60
|
+
|
|
61
|
+
#### Phase 1: Quick Wins
|
|
62
|
+
|
|
63
|
+
##### Extract DEVICE_TYPES constant ✅ COMPLETED
|
|
64
|
+
|
|
65
|
+
- [x] Created `src/device-types.ts` as single source of truth
|
|
66
|
+
- [x] Updated `src/homebridge-ui/public/js/constants.ts` to import from shared source
|
|
67
|
+
- [x] Added `DEVICE_TYPE_NORMALIZATION_MAP` for plugin type lookups
|
|
68
|
+
- [x] Updated `src/deviceFactory.ts` to use normalized type mappings
|
|
69
|
+
- [x] Created `scripts/sync-device-types.mjs` to auto-sync config.schema.json
|
|
70
|
+
- [x] Integrated sync into build process (runs before tsc)
|
|
71
|
+
|
|
72
|
+
**Status:** Device types now single-sourced with auto-sync. Config schema, UI dropdown, and plugin type handling all stay aligned automatically.
|
|
73
|
+
|
|
74
|
+
##### Extract CSS styles ✅ COMPLETED
|
|
75
|
+
|
|
76
|
+

|
|
77
|
+
|
|
78
|
+
- [x] Created `src/homebridge-ui/public/css/styles.css`
|
|
79
|
+
- [x] Moved all inline styles from `src/homebridge-ui/public/index.html` (2.3kb)
|
|
80
|
+
- [x] Updated HTML to `<link rel="stylesheet" href="css/styles.css">`
|
|
81
|
+
- [x] Verified build copies CSS to dist/
|
|
82
|
+
- [x] Tested in browser to confirm styles still work
|
|
83
|
+
- [x] Added SwitchBot red color variables and theme
|
|
84
|
+
- [x] Enhanced modal styling with red accent borders
|
|
85
|
+
- [x] Improved button styling with hover animations
|
|
86
|
+
|
|
87
|
+
**Status:** CSS now externalized and properly linked. Build copies files correctly to dist/. Modal uses SwitchBot red theme with Homebridge-like appearance.
|
|
88
|
+
|
|
89
|
+
#### Phase 2: JavaScript Modularization ✅ COMPLETED
|
|
90
|
+
|
|
91
|
+
- [x] Created `src/homebridge-ui/public/js/api.ts` - API client functions
|
|
92
|
+
- [x] Created `src/homebridge-ui/public/js/discovery.ts` - Discovery UI rendering
|
|
93
|
+
- [x] Created `src/homebridge-ui/public/js/modals.ts` - Modal management
|
|
94
|
+
- [x] Created `src/homebridge-ui/public/js/credentials.ts` - Credentials UI
|
|
95
|
+
- [x] Created `src/homebridge-ui/public/js/toast.ts` & `modal.ts` - UI helpers
|
|
96
|
+
- [x] Created `src/homebridge-ui/public/js/app.ts` - Main app init
|
|
97
|
+
- [x] Updated `src/homebridge-ui/public/index.html` to use bundled app.js
|
|
98
|
+
|
|
99
|
+
**Status:** UI is modularized in TypeScript. Build bundles into app.js (47.0kb). Direct TypeScript imports provide type safety.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Discovery System Enhancements
|
|
104
|
+
|
|
105
|
+
### Signal Strength Visualization ✅ COMPLETED
|
|
106
|
+
|
|
107
|
+

|
|
108
|
+
|
|
109
|
+
- [x] Implement RSSI color-coding system:
|
|
110
|
+
- [x] Green: > -60 dBm (excellent)
|
|
111
|
+
- [x] Yellow: -60 to -75 dBm (good)
|
|
112
|
+
- [x] Orange: -75 to -85 dBm (fair)
|
|
113
|
+
- [x] Red: < -85 dBm (poor/unreliable)
|
|
114
|
+
- [x] Add signal strength bars visual indicator (4-bar system)
|
|
115
|
+
- [x] Add tooltip explaining signal quality
|
|
116
|
+
|
|
117
|
+
**Implementation:**
|
|
118
|
+
- Added `getRssiSignalQuality()` — Determines RSSI level, color, description, and bar count
|
|
119
|
+
- Added `renderSignalBars()` — Visual 4-bar indicator (filled based on signal strength)
|
|
120
|
+
- Added `renderSignalQualityBadge()` — Color-coded badge showing quality level (Excellent/Good/Fair/Poor)
|
|
121
|
+
- Updated `renderDiscoveredDevices()` — Displays bars and badges next to device names for BLE/wireless devices
|
|
122
|
+
- Tooltips show detailed signal info on hover
|
|
123
|
+
|
|
124
|
+
**Status:** Signal visualization now displays for all discovered devices with RSSI data. Color-coded bars and badges provide instant visual feedback about connection quality.
|
|
125
|
+
|
|
126
|
+
### Filtering & Sorting ✅ COMPLETED
|
|
127
|
+
|
|
128
|
+

|
|
129
|
+
|
|
130
|
+
- [x] Add filter buttons: "All | BLE Only | API Only | Both | IR Devices"
|
|
131
|
+
- [x] Add sort dropdown: "Signal Strength | Name | Type | Connection"
|
|
132
|
+
- [x] Add search box to filter by name/ID/type (real-time filtering)
|
|
133
|
+
- [x] Persist filter/sort preferences in localStorage
|
|
134
|
+
|
|
135
|
+
**Implementation:**
|
|
136
|
+
- Added `filterDevices()` — Filters by connection type (BLE/API/Both/IR) and search query (matches name/id/type/model)
|
|
137
|
+
- Added `sortDevices()` — Sorts by name (alphabetical), signal strength (RSSI descending), device type, or connection type
|
|
138
|
+
- Added `getDiscoveryPreferences()` & `setDiscoveryPreferences()` — Persist user preferences in localStorage
|
|
139
|
+
- Updated `discovery.ts` — Creates filter buttons, sort dropdown, and search input dynamically
|
|
140
|
+
- All filtering/sorting is client-side (instant, no API calls)
|
|
141
|
+
- Search is debounced (300ms) for smooth typing
|
|
142
|
+
- Filter buttons highlight active selection
|
|
143
|
+
- Status shows "Showing X of Y device(s)" when filtered
|
|
144
|
+
|
|
145
|
+
**Status:** Full filtering and sorting implemented with localStorage persistence. Discovery page now shows:
|
|
146
|
+
- Filter bar with 5 buttons (All/BLE/API/Both/IR)
|
|
147
|
+
- Sort dropdown with 4 options (Name/Signal Strength/Type/Connection)
|
|
148
|
+
- Real-time search box
|
|
149
|
+
- All preferences saved and restored on next discovery
|
|
150
|
+
|
|
151
|
+
### One-Click Device Import ✅ COMPLETED
|
|
152
|
+
|
|
153
|
+
- [x] Added "Add to Config" button for each discovered device
|
|
154
|
+
- [x] Added auto-populated import modal with discovered name/type defaults
|
|
155
|
+
- [x] Added BLE MAC pre-fill in import flow and persistence to config on single add
|
|
156
|
+
- [x] Added confirmation toast/status messaging on successful import
|
|
157
|
+
|
|
158
|
+
**Implementation:**
|
|
159
|
+
- Updated discovery row action in `render.ts` to use explicit "Add to Config" CTA
|
|
160
|
+
- Added `importDiscoveredDevice()` modal in `modals.ts` for prefilled import settings
|
|
161
|
+
- Extended single-add client payload in `api.ts` to include optional `address`, `model`, and `rssi`
|
|
162
|
+
- Extended `/add-device` endpoint in `devices.ts` to persist discovered metadata (MAC/model/RSSI)
|
|
163
|
+
|
|
164
|
+
**Status:** One-click import now supports prefilled configuration and BLE MAC carry-through for direct connection setup.
|
|
165
|
+
|
|
166
|
+
### Duplicate Detection ✅ COMPLETED
|
|
167
|
+
|
|
168
|
+
- [x] Check if discovered device ID already exists in config
|
|
169
|
+
- [x] Show badge: "✓ Already Added" (green) or "➕ New Device" (blue)
|
|
170
|
+
- [x] Disable/hide "Add" button for already-added devices
|
|
171
|
+
- [x] Add "View in Config" link for existing devices
|
|
172
|
+
|
|
173
|
+
**Implementation:**
|
|
174
|
+
- Discovery view now fetches configured device IDs and tags discovered rows as existing/new
|
|
175
|
+
- Existing devices show `Already Added` state with disabled add action
|
|
176
|
+
- Existing devices include a `View in Config` action that scrolls and highlights the configured entry
|
|
177
|
+
|
|
178
|
+
**Status:** Duplicate devices are clearly identified at discovery time, preventing accidental re-adds and improving navigation to existing config entries.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Bug Fixes & Technical Debt
|
|
183
|
+
|
|
184
|
+
### Fix lint & lint:fix scripts ✅ COMPLETED
|
|
185
|
+
|
|
186
|
+
- [x] Update ESLint configuration to include `src/homebridge-ui/**/*.ts` files
|
|
187
|
+
- [x] Add HTML linting for `src/homebridge-ui/**/*.html` files
|
|
188
|
+
- [x] Installed `eslint-plugin-html` for inline JavaScript in HTML
|
|
189
|
+
- [x] Update `npm run lint` script to lint both plugin and UI code
|
|
190
|
+
- [x] Update `npm run lint:fix` script to auto-fix both plugin and UI code
|
|
191
|
+
- [x] Fixed existing ESLint errors (regex patterns, multi-statement lines)
|
|
192
|
+
- [x] Lint now passes successfully
|
|
193
|
+
- [x] Test that `npm run lint` runs successfully without errors
|
|
194
|
+
|
|
195
|
+
**Changes Made:**
|
|
196
|
+
- Installed `eslint-plugin-html` package
|
|
197
|
+
- Updated `eslint.config.js` to import and configure HTML plugin
|
|
198
|
+
- Added `html: true` to formatters in ESLint config
|
|
199
|
+
- Updated package.json scripts: `lint` and `lint:fix` now include `src/homebridge-ui/**/*.html`
|
|
200
|
+
- Moved regex patterns to module scope in `server.ts` and `genericDevice.ts`
|
|
201
|
+
- Split multi-statement lines to comply with `style/max-statements-per-line` rule
|
|
202
|
+
- All lint errors fixed - `npm run lint` passes with 0 errors
|
|
203
|
+
|
|
204
|
+
### Fix TypeScript compilation errors in discovery endpoint ✅ COMPLETED
|
|
205
|
+
|
|
206
|
+
- [x] Fixed: Changed `discover({ duration: 5000 })` to `discover({ timeout: 5000 })`
|
|
207
|
+
- [x] Fixed: Changed `switchbot.getDevices()` to use `switchbot.getAPIClient()?.getDevices()`
|
|
208
|
+
- [x] Reviewed node-switchbot v4.0.0-beta.3 API documentation and type definitions
|
|
209
|
+
- [x] Updated discovery implementation to use correct API types (APIDevice, APIInfraredRemote)
|
|
210
|
+
- [x] Fixed property names: `deviceId`, `deviceName`, `deviceType`, `remoteType`
|
|
211
|
+
- [x] Removed references to non-existent properties (`id`, `name`, `type`, `model`, `deviceModel`)
|
|
212
|
+
|
|
213
|
+
**Changes Made:**
|
|
214
|
+
- Updated BLE discovery to use `timeout` parameter instead of `duration`
|
|
215
|
+
- Changed OpenAPI access to use `switchbot.getAPIClient()?.getDevices()`
|
|
216
|
+
- Simplified response parsing: `apiData.deviceList` and `apiData.infraredRemoteList` (no `body` wrapper)
|
|
217
|
+
- Fixed all property accesses to match APIDevice and APIInfraredRemote interfaces
|
|
218
|
+
- Build now succeeds with 0 TypeScript errors
|
|
219
|
+
- All 39 tests still pass
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Notes
|
|
224
|
+
|
|
225
|
+
- All completed features have been tested and verified working
|
|
226
|
+
- Bundle size increased from 35.8kb → 47.0kb with new features
|
|
227
|
+
- Zero TypeScript compilation errors
|
|
228
|
+
- All lint checks passing
|
|
229
|
+
- UI mockups available in `todo/mockups/` directory
|