@homebridge-plugins/homebridge-eufy-security 4.7.2 → 5.0.0-beta.1
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/AUTHORS +4 -0
- package/CODE_OF_CONDUCT.md +30 -0
- package/CONTRIBUTING.md +77 -0
- package/README.md +152 -49
- package/SECURITY.md +30 -0
- package/config.schema.json +92 -3
- package/dist/account/ownership.js +290 -0
- package/dist/account/ownership.js.map +1 -0
- package/dist/account/persistence.js +274 -0
- package/dist/account/persistence.js.map +1 -0
- package/dist/account/temporary-authentication.js +348 -0
- package/dist/account/temporary-authentication.js.map +1 -0
- package/dist/configuration.js +167 -0
- package/dist/configuration.js.map +1 -0
- package/dist/device/member-evidence.js +66 -0
- package/dist/device/member-evidence.js.map +1 -0
- package/dist/device/member-trust.js +114 -0
- package/dist/device/member-trust.js.map +1 -0
- package/dist/device/snapshot.js +190 -0
- package/dist/device/snapshot.js.map +1 -0
- package/dist/diagnostics.js +2218 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/homekit/adapter.js +2 -0
- package/dist/homekit/adapter.js.map +1 -0
- package/dist/homekit/adapters/battery.js +208 -0
- package/dist/homekit/adapters/battery.js.map +1 -0
- package/dist/homekit/adapters/camera-controls.js +527 -0
- package/dist/homekit/adapters/camera-controls.js.map +1 -0
- package/dist/homekit/adapters/camera-streaming.js +1515 -0
- package/dist/homekit/adapters/camera-streaming.js.map +1 -0
- package/dist/homekit/adapters/contact.js +138 -0
- package/dist/homekit/adapters/contact.js.map +1 -0
- package/dist/homekit/adapters/doorbell.js +57 -0
- package/dist/homekit/adapters/doorbell.js.map +1 -0
- package/dist/homekit/adapters/information.js +55 -0
- package/dist/homekit/adapters/information.js.map +1 -0
- package/dist/homekit/adapters/lock.js +489 -0
- package/dist/homekit/adapters/lock.js.map +1 -0
- package/dist/homekit/adapters/motion.js +100 -0
- package/dist/homekit/adapters/motion.js.map +1 -0
- package/dist/homekit/adapters/registry.js +26 -0
- package/dist/homekit/adapters/registry.js.map +1 -0
- package/dist/homekit/adapters/security-system.js +425 -0
- package/dist/homekit/adapters/security-system.js.map +1 -0
- package/dist/homekit/adapters/siren.js +136 -0
- package/dist/homekit/adapters/siren.js.map +1 -0
- package/dist/homekit/adapters/smart-light.js +662 -0
- package/dist/homekit/adapters/smart-light.js.map +1 -0
- package/dist/homekit/coverage-matrix.js +119 -0
- package/dist/homekit/coverage-matrix.js.map +1 -0
- package/dist/homekit/device-control.js +175 -0
- package/dist/homekit/device-control.js.map +1 -0
- package/dist/homekit/reconciler.js +319 -0
- package/dist/homekit/reconciler.js.map +1 -0
- package/dist/homekit/representation.js +23 -0
- package/dist/homekit/representation.js.map +1 -0
- package/dist/index.js +4 -9
- package/dist/index.js.map +1 -1
- package/dist/media/contracts.js +2 -0
- package/dist/media/contracts.js.map +1 -0
- package/dist/media/last-successful-image.js +208 -0
- package/dist/media/last-successful-image.js.map +1 -0
- package/dist/media/live-stream.js +1003 -0
- package/dist/media/live-stream.js.map +1 -0
- package/dist/media/recording.js +560 -0
- package/dist/media/recording.js.map +1 -0
- package/dist/media/session-budget.js +39 -0
- package/dist/media/session-budget.js.map +1 -0
- package/dist/media/snapshot.js +406 -0
- package/dist/media/snapshot.js.map +1 -0
- package/dist/media/station-live-sessions.js +101 -0
- package/dist/media/station-live-sessions.js.map +1 -0
- package/dist/platform.js +97 -809
- package/dist/platform.js.map +1 -1
- package/dist/runtime/channel.js +375 -0
- package/dist/runtime/channel.js.map +1 -0
- package/dist/runtime/owner.js +682 -0
- package/dist/runtime/owner.js.map +1 -0
- package/dist/runtime/sdk-client.js +211 -0
- package/dist/runtime/sdk-client.js.map +1 -0
- package/dist/runtime/tracker.js +211 -0
- package/dist/runtime/tracker.js.map +1 -0
- package/dist/settings.js +1 -43
- package/dist/settings.js.map +1 -1
- package/dist/storage.js +59 -0
- package/dist/storage.js.map +1 -0
- package/dist/ui/dashboard.js +181 -0
- package/dist/ui/dashboard.js.map +1 -0
- package/dist/ui/device-image.js +22 -0
- package/dist/ui/device-image.js.map +1 -0
- package/dist/ui/runtime-channel-client.js +305 -0
- package/dist/ui/runtime-channel-client.js.map +1 -0
- package/dist/ui/server.js +259 -0
- package/dist/ui/server.js.map +1 -0
- package/homebridge-ui/public/app.css +1902 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T1250.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2070.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2080.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2081.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2103.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2117.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2118.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2119.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T211A.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2120.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2123.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2128.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2130.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2132.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2150.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2181.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2182.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2190.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2192.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2193.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2194.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2210.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2250.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2251.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2252.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2253.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2254.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2255.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2256.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2257.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2258.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2259.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2261.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2262.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2266.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2267.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2268.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2270.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2272.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2273.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2276.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2277.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2278.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2280.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2292.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2320.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2351.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2352.webp +0 -0
- package/homebridge-ui/public/assets/devices/clean/clean-T2353.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L00.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L01.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L02.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L04.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L10.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L20.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L30.webp +0 -0
- package/homebridge-ui/public/assets/devices/life/life-T8L40.webp +0 -0
- package/homebridge-ui/public/assets/devices/mower/mower-T2801.webp +0 -0
- package/homebridge-ui/public/assets/devices/mower/mower-T280B.webp +0 -0
- package/homebridge-ui/public/assets/devices/mower/mower-T2880.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T7400.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T7401.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8001.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8002.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8010.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8021.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8023.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8025.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8030.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8110.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8111.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8112.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8113.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8114.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8122.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8123.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8124.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8130.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8131.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8134.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8140.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8141.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8150.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8151.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8152.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8153.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8160.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8161.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8162.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8170.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8171.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8172.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8173.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8200.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8200X.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8201.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8203.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8210.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8213.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8214.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8222.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8400.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8401.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8410.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8411.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8414.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8416.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8417.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8419.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8419N.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8420.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8420X.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8422.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8423.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8424.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8425.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8441.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8442.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8452.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T84A0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T84A1.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8500.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8502.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8503.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8504.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8506.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8510.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8510P.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8520.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8520P.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8530.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8531.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85D0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85L0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85P0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T85V0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8600.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T86P2.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8790.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T87B0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T87B2.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8900.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8910.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8920.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8960.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8B0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8E00.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8N00.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T8W11C.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90F0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90M0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90R0.webp +0 -0
- package/homebridge-ui/public/assets/devices/security/security-T90S0.webp +0 -0
- package/homebridge-ui/public/assets/icons/inventory.svg +1 -1
- package/homebridge-ui/public/assets/icons/troubleshoot.svg +1 -0
- package/homebridge-ui/public/assets/icons/warning.svg +1 -1
- package/homebridge-ui/public/assets/logo-dark.svg +10 -0
- package/homebridge-ui/public/assets/logo.svg +10 -0
- package/homebridge-ui/public/i18n/en.json +218 -0
- package/homebridge-ui/public/i18n/fr.json +218 -0
- package/homebridge-ui/public/index.html +377 -28
- package/homebridge-ui/public/js/app.js +1065 -0
- package/homebridge-ui/public/js/dashboard.js +385 -0
- package/homebridge-ui/public/js/legacy-settings.js +37 -0
- package/homebridge-ui/public/js/profile-wizard.js +68 -0
- package/homebridge-ui/server.js +2 -1393
- package/i18n/runtime/en.json +100 -0
- package/media/Snapshot-Unavailable.jpg +0 -0
- package/media/Snapshot-black.jpg +0 -0
- package/media/camera-disabled.jpg +0 -0
- package/media/camera-offline.jpg +0 -0
- package/package.json +29 -24
- package/.claude/CLAUDE.md +0 -175
- package/.claude/PRD.md +0 -241
- package/.claude/docs/debug-recording-lessons.md +0 -91
- package/.claude/docs/hksv-recording-fix.md +0 -160
- package/.claude/skills/architect/SKILL.md +0 -76
- package/.claude/skills/developer/SKILL.md +0 -59
- package/.claude/skills/new-device-support/SKILL.md +0 -212
- package/.claude/skills/new-device-support/check-device.mjs +0 -363
- package/.claude/skills/new-device-support/map-properties.mjs +0 -450
- package/.claude/skills/new-device-support/verify-device.mjs +0 -272
- package/.claude/skills/planner/SKILL.md +0 -100
- package/.claude/skills/qa/SKILL.md +0 -79
- package/.claude/skills/support/SKILL.md +0 -175
- package/.prettierignore +0 -7
- package/.prettierrc.json +0 -7
- package/FUNDING.yml +0 -1
- package/dist/accessories/AutoSyncStationAccessory.js +0 -157
- package/dist/accessories/AutoSyncStationAccessory.js.map +0 -1
- package/dist/accessories/BaseAccessory.js +0 -261
- package/dist/accessories/BaseAccessory.js.map +0 -1
- package/dist/accessories/CameraAccessory.js +0 -459
- package/dist/accessories/CameraAccessory.js.map +0 -1
- package/dist/accessories/Device.js +0 -65
- package/dist/accessories/Device.js.map +0 -1
- package/dist/accessories/EntrySensorAccessory.js +0 -48
- package/dist/accessories/EntrySensorAccessory.js.map +0 -1
- package/dist/accessories/GarageDoorAccessory.js +0 -160
- package/dist/accessories/GarageDoorAccessory.js.map +0 -1
- package/dist/accessories/LockAccessory.js +0 -142
- package/dist/accessories/LockAccessory.js.map +0 -1
- package/dist/accessories/MotionSensorAccessory.js +0 -48
- package/dist/accessories/MotionSensorAccessory.js.map +0 -1
- package/dist/accessories/SmartDropAccessory.js +0 -141
- package/dist/accessories/SmartDropAccessory.js.map +0 -1
- package/dist/accessories/StationAccessory.js +0 -373
- package/dist/accessories/StationAccessory.js.map +0 -1
- package/dist/config.js +0 -26
- package/dist/config.js.map +0 -1
- package/dist/controller/DebugRecordingManager.js +0 -402
- package/dist/controller/DebugRecordingManager.js.map +0 -1
- package/dist/controller/LocalLivestreamManager.js +0 -371
- package/dist/controller/LocalLivestreamManager.js.map +0 -1
- package/dist/controller/recordingDelegate.js +0 -252
- package/dist/controller/recordingDelegate.js.map +0 -1
- package/dist/controller/snapshotDelegate.js +0 -348
- package/dist/controller/snapshotDelegate.js.map +0 -1
- package/dist/controller/streamingDelegate.js +0 -389
- package/dist/controller/streamingDelegate.js.map +0 -1
- package/dist/interfaces.js +0 -2
- package/dist/interfaces.js.map +0 -1
- package/dist/media/Snapshot-Unavailable.png +0 -0
- package/dist/media/Snapshot-Unavailable.xcf +0 -0
- package/dist/media/Snapshot-black.png +0 -0
- package/dist/media/camera-disabled.png +0 -0
- package/dist/media/camera-offline.png +0 -0
- package/dist/media/media/Snapshot-Unavailable.png +0 -0
- package/dist/media/media/Snapshot-Unavailable.xcf +0 -0
- package/dist/media/media/Snapshot-black.png +0 -0
- package/dist/media/media/camera-disabled.png +0 -0
- package/dist/media/media/camera-offline.png +0 -0
- package/dist/utils/Talkback.js +0 -162
- package/dist/utils/Talkback.js.map +0 -1
- package/dist/utils/accessoriesStore.js +0 -210
- package/dist/utils/accessoriesStore.js.map +0 -1
- package/dist/utils/configTypes.js +0 -38
- package/dist/utils/configTypes.js.map +0 -1
- package/dist/utils/ffmpeg.js +0 -1111
- package/dist/utils/ffmpeg.js.map +0 -1
- package/dist/utils/interfaces.js +0 -8
- package/dist/utils/interfaces.js.map +0 -1
- package/dist/utils/utils.js +0 -224
- package/dist/utils/utils.js.map +0 -1
- package/dist/version.js +0 -2
- package/dist/version.js.map +0 -1
- package/homebridge-eufy-security.png +0 -0
- package/homebridge-ui/diagnosticsCrypto.js +0 -112
- package/homebridge-ui/public/app.js +0 -237
- package/homebridge-ui/public/assets/devices/4g_lte_cam_s330_large.png +0 -0
- package/homebridge-ui/public/assets/devices/4g_lte_starlight_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C30.png +0 -0
- package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C31.png +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell1080p_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell2k_large.png +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell2kdual_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/batterydoorbell_e340_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufy-security-client.png +0 -0
- package/homebridge-ui/public/assets/devices/eufyCamS4_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2c_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2cpro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam2pro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3c_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
- package/homebridge-ui/public/assets/devices/eufycam_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/eufycame330_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/flood_freeze_sensor_e20_t90F0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/floodlight2_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight2pro_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlight_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/floodlightcame340_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
- package/homebridge-ui/public/assets/devices/homebase_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/homebasemini_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamC220_t8419N_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamc120_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcammini_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/indoorcamp24_large.png +0 -0
- package/homebridge-ui/public/assets/devices/indoorcams350_large.png +0 -0
- package/homebridge-ui/public/assets/devices/keypad_large.png +0 -0
- package/homebridge-ui/public/assets/devices/minibase_chime_T8023_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/motionsensor_large.png +0 -0
- package/homebridge-ui/public/assets/devices/nvr_s4_max_T8N00_large.png +0 -0
- package/homebridge-ui/public/assets/devices/pir_sensor_e20_large.png +0 -0
- package/homebridge-ui/public/assets/devices/poe_bullet_ptz_cam_s4_T8E00_large.png +0 -0
- package/homebridge-ui/public/assets/devices/sensor_large.png +0 -0
- package/homebridge-ui/public/assets/devices/siren_sensor_e20_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartdrop_t8790_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8500_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8500_wifibridge_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8503_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8504_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t8510P_t8520P_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85D0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85L0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85P0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_t85V0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8502_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8506_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8520_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_wifibridge_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_video_t8530_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlock_video_t8531_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartlockwifibridge_t8021_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/smartsafe_s10_t7400_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smartsafe_s12_t7401_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smarttrack_card_t87B2_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smarttrack_link_t87B0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/smoke_alarm_e10_t90S0_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocamc210_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame20_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocame40_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocame42_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocaml20_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocams220_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solocams340_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solocams40_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/soloindoorcamc24_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/solooutdoorcamc22_large.png +0 -0
- package/homebridge-ui/public/assets/devices/solooutdoorcamc24_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/unknown.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s100_large.png +0 -0
- package/homebridge-ui/public/assets/devices/walllight_s120_large.png +0 -0
- package/homebridge-ui/public/assets/devices/water_freeze_sensor_t8920_large.png +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbell1080p_large.jpg +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbell2k_large.png +0 -0
- package/homebridge-ui/public/assets/devices/wireddoorbelldual_large.jpg +0 -0
- package/homebridge-ui/public/assets/icons/attach.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_0.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_1.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_2.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_3.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_4.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_5.svg +0 -1
- package/homebridge-ui/public/assets/icons/battery_6.svg +0 -1
- package/homebridge-ui/public/assets/icons/copy.svg +0 -1
- package/homebridge-ui/public/assets/icons/delete.svg +0 -1
- package/homebridge-ui/public/assets/icons/download.svg +0 -1
- package/homebridge-ui/public/assets/icons/info.svg +0 -1
- package/homebridge-ui/public/assets/icons/refresh.svg +0 -1
- package/homebridge-ui/public/assets/icons/satellite_alt.svg +0 -1
- package/homebridge-ui/public/assets/icons/solar_power.svg +0 -1
- package/homebridge-ui/public/components/device-card.js +0 -174
- package/homebridge-ui/public/components/guard-modes.js +0 -88
- package/homebridge-ui/public/components/number-input.js +0 -121
- package/homebridge-ui/public/components/select.js +0 -73
- package/homebridge-ui/public/components/toggle.js +0 -68
- package/homebridge-ui/public/services/api.js +0 -250
- package/homebridge-ui/public/services/config.js +0 -144
- package/homebridge-ui/public/style.css +0 -818
- package/homebridge-ui/public/utils/countries.js +0 -73
- package/homebridge-ui/public/utils/device-images.js +0 -102
- package/homebridge-ui/public/utils/helpers.js +0 -115
- package/homebridge-ui/public/views/dashboard.js +0 -226
- package/homebridge-ui/public/views/device-detail.js +0 -654
- package/homebridge-ui/public/views/diagnostics.js +0 -402
- package/homebridge-ui/public/views/login.js +0 -650
- package/homebridge-ui/public/views/recordings.js +0 -319
- package/homebridge-ui/public/views/settings.js +0 -197
- package/homebridge-ui/public/views/unsupported-detail.js +0 -296
- package/media/Snapshot-Unavailable.png +0 -0
- package/media/Snapshot-Unavailable.xcf +0 -0
- package/media/Snapshot-black.png +0 -0
- package/media/camera-disabled.png +0 -0
- package/media/camera-offline.png +0 -0
- package/scripts/decrypt-diagnostics.mjs +0 -257
package/dist/platform.js
CHANGED
|
@@ -1,821 +1,109 @@
|
|
|
1
|
+
import { ffmpegPathSource, parseConfig } from './configuration.js';
|
|
2
|
+
import { createDiagnosticLogger, DiagnosticConditions, recordFfmpegEnvironment, reportAdaptationNotice, reportDiscardedV4Settings, reportHomeKitEvent, unconfirmedWriteCondition, reportInvalidSnapshotCache, } from './diagnostics.js';
|
|
3
|
+
import { deviceSnapshotLabel } from './device/snapshot.js';
|
|
4
|
+
import { HomeKitReconciler } from './homekit/reconciler.js';
|
|
5
|
+
import { FfmpegLiveMedia, resolveFfmpegIdentity } from './media/live-stream.js';
|
|
6
|
+
import { FfmpegRecordingMedia } from './media/recording.js';
|
|
7
|
+
import { PersistedLastSuccessfulImages } from './media/last-successful-image.js';
|
|
8
|
+
import { SnapshotAcquisition } from './media/snapshot.js';
|
|
9
|
+
import { DeclaredMediaSessionBudget } from './media/session-budget.js';
|
|
10
|
+
import { StationLiveSessions } from './media/station-live-sessions.js';
|
|
11
|
+
import { RuntimeOwner } from './runtime/owner.js';
|
|
12
|
+
import { createPersistedSdkClient } from './runtime/sdk-client.js';
|
|
1
13
|
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
accessoriesStore;
|
|
47
|
-
constructor(hblog, config, api) {
|
|
48
|
-
this.config = config;
|
|
49
|
-
this.api = api;
|
|
50
|
-
this.eufyPath = this.api.user.storagePath() + '/eufysecurity';
|
|
51
|
-
if (!fs.existsSync(this.eufyPath)) {
|
|
52
|
-
fs.mkdirSync(this.eufyPath);
|
|
53
|
-
}
|
|
54
|
-
// Identify what we're running on so we can take advantage of hardware-specific features.
|
|
55
|
-
this.probeHwOs();
|
|
56
|
-
this.initConfig(config);
|
|
57
|
-
this.configureLogger();
|
|
58
|
-
// Initialize debug recording manager when debugLivestream is enabled.
|
|
59
|
-
if (this.config.debugLivestream) {
|
|
60
|
-
this.debugRecordingManager = new DebugRecordingManager(log, this.eufyPath);
|
|
61
|
-
}
|
|
62
|
-
this.initSetup();
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Initializes the configuration object with default values where properties are not provided.
|
|
66
|
-
* If a property is provided in the config object, it overrides the default value.
|
|
67
|
-
* Additionally, certain numeric properties are parsed to ensure they are of the correct type.
|
|
68
|
-
* @param config - Partial configuration object with user-provided values.
|
|
69
|
-
*/
|
|
70
|
-
initConfig(config) {
|
|
71
|
-
// Assigns the provided config object to this.config, casting it to the EufySecurityPlatformConfig type.
|
|
72
|
-
this.config = config;
|
|
73
|
-
// Iterates over each key in the DEFAULT_CONFIG_VALUES object.
|
|
74
|
-
Object.keys(DEFAULT_CONFIG_VALUES).forEach((key) => {
|
|
75
|
-
// Checks if the corresponding property in the config object is undefined or null.
|
|
76
|
-
// If it is, assigns the default value from DEFAULT_CONFIG_VALUES to it.
|
|
77
|
-
this.config[key] ??= DEFAULT_CONFIG_VALUES[key];
|
|
78
|
-
});
|
|
79
|
-
// List of properties that need to be parsed as numeric values
|
|
80
|
-
const numericProperties = [
|
|
81
|
-
'CameraMaxLivestreamDuration',
|
|
82
|
-
'pollingIntervalMinutes',
|
|
83
|
-
'hkHome',
|
|
84
|
-
'hkAway',
|
|
85
|
-
'hkNight',
|
|
86
|
-
'hkOff',
|
|
87
|
-
];
|
|
88
|
-
// Iterate over each property in the config object
|
|
89
|
-
Object.entries(this.config).forEach(([key, value]) => {
|
|
90
|
-
// Check if the property is one of the numeric properties
|
|
91
|
-
if (numericProperties.includes(key)) {
|
|
92
|
-
// Parse the value to ensure it is of the correct type (number)
|
|
93
|
-
this.config[key] = typeof value === 'string' ? parseInt(value) : value;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Configures the logging mechanism for the plugin.
|
|
99
|
-
*/
|
|
100
|
-
configureLogger() {
|
|
101
|
-
// Define options for logging
|
|
102
|
-
const logOptions = {
|
|
103
|
-
name: '[EufySecurity]', // Name prefix for log messages
|
|
104
|
-
prettyLogTemplate: '[{{mm}}/{{dd}}/{{yyyy}}, {{hh}}:{{MM}}:{{ss}}]\t{{name}}\t{{logLevelName}}\t', // Template for pretty log output
|
|
105
|
-
prettyErrorTemplate: '\n{{errorName}} {{errorMessage}}\nerror stack:\n{{errorStack}}', // Template for pretty error output
|
|
106
|
-
prettyErrorStackTemplate: ' • {{fileName}}\t{{method}}\n\t{{fileNameWithLine}}', // Template for error stack trace
|
|
107
|
-
prettyErrorParentNamesSeparator: '', // Separator for parent names in error messages
|
|
108
|
-
prettyErrorLoggerNameDelimiter: '\t', // Delimiter for logger name in error messages
|
|
109
|
-
stylePrettyLogs: true, // Enable styling for logs
|
|
110
|
-
minLevel: 3, // Minimum log level to display (3 corresponds to INFO)
|
|
111
|
-
prettyLogTimeZone: 'local', // Time zone for log timestamps
|
|
112
|
-
prettyLogStyles: {
|
|
113
|
-
// Styles for different log elements
|
|
114
|
-
logLevelName: {
|
|
115
|
-
// Styles for log level names
|
|
116
|
-
'*': ['bold', 'black', 'bgWhiteBright', 'dim'], // Default style
|
|
117
|
-
SILLY: ['bold', 'white'], // Style for SILLY level
|
|
118
|
-
TRACE: ['bold', 'whiteBright'], // Style for TRACE level
|
|
119
|
-
DEBUG: ['bold', 'green'], // Style for DEBUG level
|
|
120
|
-
INFO: ['bold', 'blue'], // Style for INFO level
|
|
121
|
-
WARN: ['bold', 'yellow'], // Style for WARN level
|
|
122
|
-
ERROR: ['bold', 'red'], // Style for ERROR level
|
|
123
|
-
FATAL: ['bold', 'redBright'], // Style for FATAL level
|
|
124
|
-
},
|
|
125
|
-
dateIsoStr: 'gray', // Style for ISO date strings
|
|
126
|
-
filePathWithLine: 'white', // Style for file paths with line numbers
|
|
127
|
-
name: 'green', // Style for logger names
|
|
128
|
-
nameWithDelimiterPrefix: ['white', 'bold'], // Style for logger names with delimiter prefix
|
|
129
|
-
nameWithDelimiterSuffix: ['white', 'bold'], // Style for logger names with delimiter suffix
|
|
130
|
-
errorName: ['bold', 'bgRedBright', 'whiteBright'], // Style for error names
|
|
131
|
-
fileName: ['yellow'], // Style for file names
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
// Modify log options if detailed logging is enabled
|
|
135
|
-
if (this.config.enableDetailedLogging) {
|
|
136
|
-
logOptions.name = `[EufySecurity-${LIB_VERSION}]`; // Modify logger name with plugin version
|
|
137
|
-
logOptions.prettyLogTemplate =
|
|
138
|
-
'[{{mm}}/{{dd}}/{{yyyy}} {{hh}}:{{MM}}:{{ss}}]\t{{name}}\t{{logLevelName}}\t[{{fileNameWithLine}}]\t'; // Modify log template
|
|
139
|
-
logOptions.minLevel = 2; // Adjust minimum log level
|
|
140
|
-
}
|
|
141
|
-
// Initialize the global logger with the configured options
|
|
142
|
-
initLog(logOptions);
|
|
143
|
-
// Configures log streams for various log files
|
|
144
|
-
configureLogStreams(this.eufyPath, this.config.omitLogFiles);
|
|
145
|
-
}
|
|
146
|
-
// This function is responsible for identifying the hardware and operating system environment the application is running on.
|
|
147
|
-
probeHwOs() {
|
|
148
|
-
// Start off with a generic identifier.
|
|
149
|
-
this._hostSystem = 'generic';
|
|
150
|
-
// Take a look at the platform we're on for an initial hint of what we are.
|
|
151
|
-
switch (platform) {
|
|
152
|
-
// The beloved macOS.
|
|
153
|
-
case 'darwin':
|
|
154
|
-
// For macOS, we check the CPU model to determine if it's an Apple CPU or an Intel CPU.
|
|
155
|
-
this._hostSystem = 'macOS.' + (os.cpus()[0].model.includes('Apple') ? 'Apple' : 'Intel');
|
|
156
|
-
break;
|
|
157
|
-
// The indomitable Linux.
|
|
158
|
-
case 'linux':
|
|
159
|
-
// Let's further see if we're a small, but scrappy, Raspberry Pi.
|
|
160
|
-
try {
|
|
161
|
-
// As of the 4.9 kernel, Raspberry Pi prefers to be identified using this method and has deprecated cpuinfo.
|
|
162
|
-
const systemId = readFileSync('/sys/firmware/devicetree/base/model', { encoding: 'utf8' });
|
|
163
|
-
// Check if it's a Raspberry Pi 4.
|
|
164
|
-
if (/Raspberry Pi (Compute Module )?4/.test(systemId)) {
|
|
165
|
-
// If it's a Pi 4, we identify the system as running Raspbian.
|
|
166
|
-
this._hostSystem = 'raspbian';
|
|
14
|
+
import { resolveStorageRoot } from './storage.js';
|
|
15
|
+
export function createEufyPlatform(clientFactory, shutdownTimeoutMs = 10_000, signalTarget) {
|
|
16
|
+
return class EufyPlatform {
|
|
17
|
+
runtime;
|
|
18
|
+
cachedAccessories = [];
|
|
19
|
+
reconciler;
|
|
20
|
+
unconfirmedWrites;
|
|
21
|
+
constructor(log, config, api) {
|
|
22
|
+
const configuredConfig = parseConfig(config);
|
|
23
|
+
const storageRoot = api.user ? resolveStorageRoot(api.user.storagePath()) : undefined;
|
|
24
|
+
const diagnosticLog = createDiagnosticLogger(log, storageRoot);
|
|
25
|
+
const diagnostics = new DiagnosticConditions(diagnosticLog, (serial) => this.cachedAccessories.find((accessory) => {
|
|
26
|
+
const context = accessory.context;
|
|
27
|
+
return context.homebridgeEufy?.version === 1 && context.homebridgeEufy.serial === serial;
|
|
28
|
+
})?.displayName ?? deviceSnapshotLabel(this.runtime.currentRegistry()?.snapshot, serial));
|
|
29
|
+
const adaptationDiagnostics = {
|
|
30
|
+
report: (notice) => reportAdaptationNotice(diagnosticLog, notice),
|
|
31
|
+
};
|
|
32
|
+
const liveMedia = configuredConfig.ffmpegPath
|
|
33
|
+
? new FfmpegLiveMedia(configuredConfig.ffmpegPath, adaptationDiagnostics)
|
|
34
|
+
: undefined;
|
|
35
|
+
const recordingMedia = configuredConfig.ffmpegPath
|
|
36
|
+
? new FfmpegRecordingMedia(configuredConfig.ffmpegPath, adaptationDiagnostics)
|
|
37
|
+
: undefined;
|
|
38
|
+
const mediaBudget = new DeclaredMediaSessionBudget(configuredConfig.maxConcurrentMediaSessions);
|
|
39
|
+
const stationLiveSessions = new StationLiveSessions();
|
|
40
|
+
const snapshotMedia = new SnapshotAcquisition(storageRoot
|
|
41
|
+
? new PersistedLastSuccessfulImages(storageRoot, () => reportInvalidSnapshotCache(diagnosticLog))
|
|
42
|
+
: undefined, mediaBudget, undefined, undefined, stationLiveSessions);
|
|
43
|
+
if (configuredConfig.discardedV4Settings.length > 0 && !configuredConfig.discardedV4Acknowledged) {
|
|
44
|
+
reportDiscardedV4Settings(diagnosticLog);
|
|
45
|
+
}
|
|
46
|
+
this.runtime = new RuntimeOwner(diagnosticLog, configuredConfig, clientFactory, {
|
|
47
|
+
storageRoot,
|
|
48
|
+
shutdownTimeoutMs,
|
|
49
|
+
});
|
|
50
|
+
this.runtime.subscribeState((state) => diagnostics.reportRuntimeState(state));
|
|
51
|
+
const signals = ['SIGHUP', 'SIGINT', 'SIGTERM'];
|
|
52
|
+
let listeningForSignals = Boolean(signalTarget);
|
|
53
|
+
const stop = () => {
|
|
54
|
+
if (listeningForSignals) {
|
|
55
|
+
listeningForSignals = false;
|
|
56
|
+
for (const signal of signals) {
|
|
57
|
+
signalTarget?.off(signal, stop);
|
|
167
58
|
}
|
|
168
59
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
60
|
+
this.reconciler?.stop();
|
|
61
|
+
void this.runtime.stop().finally(() => diagnosticLog.flush?.());
|
|
62
|
+
};
|
|
63
|
+
for (const signal of signals) {
|
|
64
|
+
signalTarget?.on(signal, stop);
|
|
65
|
+
}
|
|
66
|
+
this.unconfirmedWrites ??= this.runtime.subscribeUnconfirmedWrites((write) => {
|
|
67
|
+
const condition = unconfirmedWriteCondition(write.property);
|
|
68
|
+
if (condition) {
|
|
69
|
+
diagnostics.reportHomeKit(condition, [write.serial]);
|
|
172
70
|
}
|
|
173
|
-
break;
|
|
174
|
-
default:
|
|
175
|
-
// We aren't trying to solve for every system type.
|
|
176
|
-
// If the platform doesn't match macOS or Linux, we keep the generic identifier.
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
// Utility to return the hardware environment we're on.
|
|
181
|
-
get hostSystem() {
|
|
182
|
-
return this._hostSystem;
|
|
183
|
-
}
|
|
184
|
-
initSetup() {
|
|
185
|
-
log.debug('plugin data store:', this.eufyPath);
|
|
186
|
-
log.debug('OS is', this.hostSystem);
|
|
187
|
-
log.debug('Using bropats @homebridge-eufy-security/eufy-security-client library in version ', libVersion);
|
|
188
|
-
// Probe ffmpeg capabilities early so warnings show at boot.
|
|
189
|
-
hasFdkAac();
|
|
190
|
-
probeHardwareEncoder(this.hostSystem);
|
|
191
|
-
// Log the final configuration object for debugging purposes
|
|
192
|
-
log.debug('The config is:', this.config);
|
|
193
|
-
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number);
|
|
194
|
-
const nativePKCS1Restored = nodeMajor > 24 || (nodeMajor === 24 && nodeMinor >= 5);
|
|
195
|
-
if (nativePKCS1Restored && this.config.enableEmbeddedPKCS1Support) {
|
|
196
|
-
log.info('Native PKCS1 padding support available (Node.js >= 24.5.0) — ignoring enableEmbeddedPKCS1Support config option');
|
|
197
|
-
}
|
|
198
|
-
const eufyConfig = {
|
|
199
|
-
username: this.config.username,
|
|
200
|
-
password: this.config.password,
|
|
201
|
-
country: this.config.country,
|
|
202
|
-
trustedDeviceName: this.config.deviceName,
|
|
203
|
-
language: 'en',
|
|
204
|
-
persistentDir: this.eufyPath,
|
|
205
|
-
p2pConnectionSetup: 0,
|
|
206
|
-
pollingIntervalMinutes: this.config.pollingIntervalMinutes,
|
|
207
|
-
enableEmbeddedPKCS1Support: nativePKCS1Restored ? false : this.config.enableEmbeddedPKCS1Support,
|
|
208
|
-
eventDurationSeconds: 10,
|
|
209
|
-
logging: {
|
|
210
|
-
level: this.config.enableDetailedLogging ? LogLevel.Debug : LogLevel.Info,
|
|
211
|
-
},
|
|
212
|
-
};
|
|
213
|
-
this.api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, async () => {
|
|
214
|
-
await this.pluginSetup(eufyConfig);
|
|
215
|
-
});
|
|
216
|
-
this.api.on("shutdown" /* APIEvent.SHUTDOWN */, async () => {
|
|
217
|
-
await this.pluginShutdown();
|
|
218
|
-
});
|
|
219
|
-
log.debug('Finished booting!');
|
|
220
|
-
}
|
|
221
|
-
async pluginSetup(eufyConfig) {
|
|
222
|
-
let connected = false;
|
|
223
|
-
for (let attempt = 1; attempt <= EufySecurityPlatform.MAX_CONNECT_ATTEMPTS; attempt++) {
|
|
224
|
-
// If a fatal handler (captcha/tfa) triggered shutdown during a previous attempt, stop
|
|
225
|
-
if (this.already_shutdown) {
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
if (attempt > 1) {
|
|
229
|
-
log.warn(`Connection attempt ${attempt}/${EufySecurityPlatform.MAX_CONNECT_ATTEMPTS}...`);
|
|
230
|
-
}
|
|
231
|
-
connected = await this.attemptConnect(eufyConfig);
|
|
232
|
-
if (connected || this.already_shutdown) {
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
if (attempt < EufySecurityPlatform.MAX_CONNECT_ATTEMPTS) {
|
|
236
|
-
log.warn(`Retrying in ${EufySecurityPlatform.CONNECT_RETRY_DELAY_SEC} seconds...`);
|
|
237
|
-
await this.delay(EufySecurityPlatform.CONNECT_RETRY_DELAY_SEC * 1000);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (!connected) {
|
|
241
|
-
if (!this.already_shutdown) {
|
|
242
|
-
log.error(`All ${EufySecurityPlatform.MAX_CONNECT_ATTEMPTS} connection attempts failed. Shutting down plugin.`);
|
|
243
|
-
await this.pluginShutdown();
|
|
244
|
-
}
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
log.info('Connected to Eufy. Waiting for stations and devices to be discovered...');
|
|
248
|
-
if (this.config.CameraMaxLivestreamDuration > 86400) {
|
|
249
|
-
this.config.CameraMaxLivestreamDuration = 86400;
|
|
250
|
-
log.warn('Your maximum livestream duration value is too large. Since this can cause problems it was reset to 86400 seconds (1 day maximum).');
|
|
251
|
-
}
|
|
252
|
-
this.eufyClient.setCameraMaxLivestreamDuration(this.config.CameraMaxLivestreamDuration);
|
|
253
|
-
log.debug(`CameraMaxLivestreamDuration: ${this.eufyClient.getCameraMaxLivestreamDuration()}`);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Attempts a single connection to the Eufy cloud service.
|
|
257
|
-
* Initializes the client, sets up event handlers, and connects.
|
|
258
|
-
* @returns true if connected successfully, false otherwise.
|
|
259
|
-
*/
|
|
260
|
-
async attemptConnect(eufyConfig) {
|
|
261
|
-
try {
|
|
262
|
-
this.eufyClient = await EufySecurity.initialize(eufyConfig, this.config.enableDetailedLogging ? tsLogger : undefined);
|
|
263
|
-
// Each camera adds listeners (livestream, talkback) on top of the base ones.
|
|
264
|
-
// Raise limit to prevent MaxListenersExceededWarning in Node 22+.
|
|
265
|
-
this.eufyClient.setMaxListeners(30);
|
|
266
|
-
this.eufyClient.on('station added', this.stationAdded.bind(this));
|
|
267
|
-
this.eufyClient.on('station removed', this.stationRemoved.bind(this));
|
|
268
|
-
this.eufyClient.on('device added', this.deviceAdded.bind(this));
|
|
269
|
-
this.eufyClient.on('device removed', this.deviceRemoved.bind(this));
|
|
270
|
-
// Initialise the accessories store for UI consumption
|
|
271
|
-
this.accessoriesStore = new AccessoriesStore(this.eufyClient, this.config, this.eufyPath);
|
|
272
|
-
this.eufyClient.on('push connect', () => {
|
|
273
|
-
log.debug('Push Connected!');
|
|
274
71
|
});
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
log.debug('Connected!');
|
|
280
|
-
});
|
|
281
|
-
this.eufyClient.on('close', () => {
|
|
282
|
-
log.debug('Closed!');
|
|
283
|
-
});
|
|
284
|
-
this.eufyClient.on('connection error', (error) => {
|
|
285
|
-
log.warn(`Connection error: ${error}`);
|
|
286
|
-
});
|
|
287
|
-
this.eufyClient.once('captcha request', async () => {
|
|
288
|
-
log.error(`
|
|
289
|
-
***************************
|
|
290
|
-
***** WARNING MESSAGE *****
|
|
291
|
-
***************************
|
|
292
|
-
Important Notice: CAPTCHA Required
|
|
293
|
-
Your account requires CAPTCHA verification to log in.
|
|
294
|
-
The plugin will now shut down.
|
|
295
|
-
Please open the Homebridge UI, go to the homebridge-eufy-security plugin settings, and complete the CAPTCHA to reconnect.
|
|
296
|
-
***************************
|
|
297
|
-
`);
|
|
298
|
-
await this.pluginShutdown();
|
|
299
|
-
});
|
|
300
|
-
this.eufyClient.on('tfa request', async () => {
|
|
301
|
-
log.error(`
|
|
302
|
-
***************************
|
|
303
|
-
***** WARNING MESSAGE *****
|
|
304
|
-
***************************
|
|
305
|
-
Attention: Two-Factor Authentication (2FA) Required
|
|
306
|
-
Your account requires 2FA verification to log in.
|
|
307
|
-
The plugin will now shut down.
|
|
308
|
-
Please open the Homebridge UI, go to the homebridge-eufy-security plugin settings, and complete the 2FA verification to reconnect.
|
|
309
|
-
***************************
|
|
310
|
-
`);
|
|
311
|
-
await this.pluginShutdown();
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
catch (e) {
|
|
315
|
-
log.error(`Error while setup: ${e}`);
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
await this.eufyClient.connect();
|
|
320
|
-
log.debug('EufyClient connected ' + this.eufyClient?.isConnected?.());
|
|
321
|
-
}
|
|
322
|
-
catch (e) {
|
|
323
|
-
log.error(`Error authenticating Eufy: ${e}`);
|
|
324
|
-
try {
|
|
325
|
-
this.eufyClient.close();
|
|
326
|
-
}
|
|
327
|
-
catch {
|
|
328
|
-
/* ignore cleanup errors */
|
|
329
|
-
}
|
|
330
|
-
return false;
|
|
331
|
-
}
|
|
332
|
-
if (!this.eufyClient?.isConnected?.()) {
|
|
333
|
-
log.error("Not connected can't continue!");
|
|
334
|
-
try {
|
|
335
|
-
this.eufyClient.close();
|
|
336
|
-
}
|
|
337
|
-
catch {
|
|
338
|
-
/* ignore cleanup errors */
|
|
339
|
-
}
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Generates a UUID based on the given identifier and station flag.
|
|
346
|
-
* @param identifier The unique identifier.
|
|
347
|
-
* @param isStation Flag indicating whether the identifier belongs to a station.
|
|
348
|
-
* @returns The generated UUID.
|
|
349
|
-
*/
|
|
350
|
-
generateUUID(identifier, isStation) {
|
|
351
|
-
// Add prefix 's_' if it's a station identifier, otherwise, no prefix.
|
|
352
|
-
const prefix = isStation ? 's1_' : 'd1_';
|
|
353
|
-
// Generate UUID based on the prefix + identifier.
|
|
354
|
-
return HAP.uuid.generate(prefix + identifier);
|
|
355
|
-
}
|
|
356
|
-
async delay(ms) {
|
|
357
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Defines an accessory for a device or station.
|
|
361
|
-
*
|
|
362
|
-
* @param deviceContainer The container holding information about the device or station.
|
|
363
|
-
* @param isStation A boolean indicating whether the container represents a station.
|
|
364
|
-
* @returns A tuple containing the created or cached accessory and a boolean indicating whether the accessory was cached.
|
|
365
|
-
*/
|
|
366
|
-
defineAccessory(deviceContainer, isStation) {
|
|
367
|
-
// Generate UUID for the accessory based on device's unique identifier and whether it's a station
|
|
368
|
-
const uuid = this.generateUUID(deviceContainer.deviceIdentifier.uniqueId, isStation);
|
|
369
|
-
// Check if the accessory is already cached
|
|
370
|
-
const cachedAccessory = this.accessories.find((accessory) => accessory.UUID === uuid);
|
|
371
|
-
// If the accessory is cached, remove it from the accessories array
|
|
372
|
-
if (cachedAccessory) {
|
|
373
|
-
this.accessories.splice(this.accessories.indexOf(cachedAccessory), 1);
|
|
374
|
-
}
|
|
375
|
-
// Determine if the device is a camera
|
|
376
|
-
const isCamera = deviceContainer.eufyDevice instanceof Device ? deviceContainer.eufyDevice.isCamera() : false;
|
|
377
|
-
// Create a new accessory if not cached, otherwise use the cached one
|
|
378
|
-
const accessory = cachedAccessory ||
|
|
379
|
-
new this.api.platformAccessory(deviceContainer.deviceIdentifier.displayName, uuid, isCamera ? 17 /* HAP.Categories.CAMERA */ : 11 /* HAP.Categories.SECURITY_SYSTEM */);
|
|
380
|
-
// Store device information in accessory context
|
|
381
|
-
accessory.context['device'] = deviceContainer.deviceIdentifier;
|
|
382
|
-
accessory.displayName = deviceContainer.deviceIdentifier.displayName;
|
|
383
|
-
return [accessory, !!cachedAccessory];
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Adds or updates an accessory for a device or station.
|
|
387
|
-
*
|
|
388
|
-
* @param deviceContainer The container holding information about the device or station.
|
|
389
|
-
* @param isStation A boolean indicating whether the container represents a station.
|
|
390
|
-
*/
|
|
391
|
-
async addOrUpdateAccessory(deviceContainer, isStation) {
|
|
392
|
-
try {
|
|
393
|
-
// Define the accessory and check if it already exists
|
|
394
|
-
const [accessory, isExist] = this.defineAccessory(deviceContainer, isStation);
|
|
395
|
-
// Register the accessory based on whether it's a station or device
|
|
396
|
-
try {
|
|
397
|
-
if (isStation) {
|
|
398
|
-
this.register_station(accessory, deviceContainer);
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
this.register_device(accessory, deviceContainer);
|
|
72
|
+
api.on('didFinishLaunching', () => {
|
|
73
|
+
const ffmpegPath = configuredConfig.ffmpegPath;
|
|
74
|
+
if (ffmpegPath && storageRoot) {
|
|
75
|
+
void resolveFfmpegIdentity(ffmpegPath, ffmpegPathSource(ffmpegPath)).then((identity) => recordFfmpegEnvironment(storageRoot, identity));
|
|
402
76
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (isExist) {
|
|
408
|
-
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
409
|
-
}
|
|
410
|
-
throw error;
|
|
411
|
-
}
|
|
412
|
-
// Add the accessory's UUID to activeAccessoryIds if it's not already present
|
|
413
|
-
if (this.activeAccessoryIds.indexOf(accessory.UUID) === -1) {
|
|
414
|
-
this.activeAccessoryIds.push(accessory.UUID);
|
|
415
|
-
}
|
|
416
|
-
// Update or register the accessory with the platform
|
|
417
|
-
if (isExist) {
|
|
418
|
-
this.api.updatePlatformAccessories([accessory]);
|
|
419
|
-
log.info(`Updating existing accessory: ${accessory.displayName}`);
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
423
|
-
log.info(`Registering new accessory: ${accessory.displayName}`);
|
|
424
|
-
}
|
|
425
|
-
// Track the accessory so we can unregister it on hot-remove
|
|
426
|
-
this.registeredAccessories.set(accessory.UUID, accessory);
|
|
427
|
-
}
|
|
428
|
-
catch (error) {
|
|
429
|
-
// Log any errors that occur during accessory addition or update
|
|
430
|
-
log.error(`Error in ${isStation ? 'stationAdded' : 'deviceAdded'}:`, error);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
async stationAdded(station) {
|
|
434
|
-
try {
|
|
435
|
-
if (this.config.ignoreStations.includes(station.getSerial())) {
|
|
436
|
-
log.debug(`${station.getName()}: Station ignored`);
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
const rawStation = station.getRawStation();
|
|
440
|
-
if (rawStation.member.member_type !== UserType.ADMIN) {
|
|
441
|
-
await this.pluginShutdown();
|
|
442
|
-
log.error(`
|
|
443
|
-
#########################
|
|
444
|
-
######### ERROR #########
|
|
445
|
-
#########################
|
|
446
|
-
You're not using a guest admin account with this plugin! You must use a guest admin account!
|
|
447
|
-
Please look here for more details:
|
|
448
|
-
https://github.com/homebridge-plugins/homebridge-eufy-security/wiki/Create-a-dedicated-admin-account-for-Homebridge-Eufy-Security-Plugin
|
|
449
|
-
#########################
|
|
450
|
-
`);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (this.initialDiscoveryComplete) {
|
|
454
|
-
// Hot-add: process immediately
|
|
455
|
-
await this.hotAddStation(station);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
// Store station for batch processing later
|
|
459
|
-
this.pendingStations.push(station);
|
|
460
|
-
log.debug(`${station.getName()}: Station queued for processing`);
|
|
461
|
-
this.resetDiscoveryDebounce();
|
|
462
|
-
}
|
|
463
|
-
catch (error) {
|
|
464
|
-
log.error(`Error in stationAdded:, ${error}`);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
async deviceAdded(device) {
|
|
468
|
-
try {
|
|
469
|
-
if (this.config.ignoreDevices.includes(device.getSerial())) {
|
|
470
|
-
log.debug(`${device.getName()}: Device ignored`);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
// Check if the device is a keypad and ignore it from the start
|
|
474
|
-
const deviceType = device.getDeviceType();
|
|
475
|
-
if (Device.isKeyPad(deviceType)) {
|
|
476
|
-
log.warn(`${device.getName()}: The keypad is ignored as it serves no purpose in this plugin. You can ignore this message.`);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
if (this.initialDiscoveryComplete) {
|
|
480
|
-
// Hot-add: process immediately
|
|
481
|
-
await this.hotAddDevice(device);
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
// Store device for batch processing later
|
|
485
|
-
this.pendingDevices.push(device);
|
|
486
|
-
log.debug(`${device.getName()}: Device queued for processing`);
|
|
487
|
-
this.resetDiscoveryDebounce();
|
|
488
|
-
}
|
|
489
|
-
catch (error) {
|
|
490
|
-
log.error(`Error in deviceAdded: ${error}`);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
async stationRemoved(station) {
|
|
494
|
-
const serial = station.getSerial();
|
|
495
|
-
const name = station.getName();
|
|
496
|
-
log.info(`Station removed event received: ${name} (${serial})`);
|
|
497
|
-
const uuid = this.generateUUID(serial, true);
|
|
498
|
-
this.removeAccessoryByUUID(uuid, name);
|
|
499
|
-
}
|
|
500
|
-
async deviceRemoved(device) {
|
|
501
|
-
const serial = device.getSerial();
|
|
502
|
-
const name = device.getName();
|
|
503
|
-
log.info(`Device removed event received: ${name} (${serial})`);
|
|
504
|
-
const uuid = this.generateUUID(serial, false);
|
|
505
|
-
this.removeAccessoryByUUID(uuid, name);
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* Unregisters an accessory from HomeKit by its UUID.
|
|
509
|
-
* Used by hot-remove handlers to cleanly remove devices/stations at runtime.
|
|
510
|
-
*/
|
|
511
|
-
removeAccessoryByUUID(uuid, displayName) {
|
|
512
|
-
const accessory = this.registeredAccessories.get(uuid);
|
|
513
|
-
if (!accessory) {
|
|
514
|
-
log.debug(`No registered accessory found for UUID ${uuid} (${displayName}) — may have been ignored or already removed`);
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
try {
|
|
518
|
-
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
|
|
519
|
-
this.registeredAccessories.delete(uuid);
|
|
520
|
-
const idx = this.activeAccessoryIds.indexOf(uuid);
|
|
521
|
-
if (idx !== -1) {
|
|
522
|
-
this.activeAccessoryIds.splice(idx, 1);
|
|
523
|
-
}
|
|
524
|
-
log.info(`Successfully removed accessory: ${displayName}`);
|
|
525
|
-
// Refresh the accessories store so the UI reflects the change
|
|
526
|
-
this.accessoriesStore?.persistNow();
|
|
527
|
-
}
|
|
528
|
-
catch (error) {
|
|
529
|
-
log.error(`Error removing accessory ${displayName}: ${error}`);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Hot-adds a single device after initial discovery is complete.
|
|
534
|
-
* Processes it immediately without debouncing.
|
|
535
|
-
*/
|
|
536
|
-
async hotAddDevice(device) {
|
|
537
|
-
const deviceType = device.getDeviceType();
|
|
538
|
-
if (!Device.isSupported(deviceType)) {
|
|
539
|
-
log.warn(`[HOT-ADD] "${device.getName()}" (type ${deviceType}) is unsupported — skipping`);
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
const deviceContainer = {
|
|
543
|
-
deviceIdentifier: {
|
|
544
|
-
uniqueId: device.getSerial(),
|
|
545
|
-
displayName: 'DEVICE ' + device.getName().replace(/[^a-zA-Z0-9]/g, ''),
|
|
546
|
-
type: deviceType,
|
|
547
|
-
},
|
|
548
|
-
eufyDevice: device,
|
|
549
|
-
};
|
|
550
|
-
log.info(`[HOT-ADD] Adding new device: ${device.getName()} (${device.getSerial()})`);
|
|
551
|
-
await this.addOrUpdateAccessory(deviceContainer, false);
|
|
552
|
-
// Refresh the accessories store so the UI reflects the change
|
|
553
|
-
this.accessoriesStore?.persistNow();
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Hot-adds a single station after initial discovery is complete.
|
|
557
|
-
* Processes it immediately without debouncing.
|
|
558
|
-
*/
|
|
559
|
-
async hotAddStation(station) {
|
|
560
|
-
const stationType = station.getDeviceType();
|
|
561
|
-
const stationSerial = station.getSerial();
|
|
562
|
-
const isKnownStation = Device.isStation(stationType);
|
|
563
|
-
if (!isKnownStation && !Device.isSupported(stationType)) {
|
|
564
|
-
log.warn(`[HOT-ADD] "${station.getName()}" (type ${stationType}) is unsupported — skipping`);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const stationContainer = {
|
|
568
|
-
deviceIdentifier: {
|
|
569
|
-
uniqueId: stationSerial,
|
|
570
|
-
displayName: 'STATION ' + station.getName().replace(/[^a-zA-Z0-9]/g, ''),
|
|
571
|
-
type: stationType,
|
|
572
|
-
},
|
|
573
|
-
eufyDevice: station,
|
|
574
|
-
};
|
|
575
|
-
log.info(`[HOT-ADD] Adding new station: ${station.getName()} (${stationSerial})`);
|
|
576
|
-
await this.addOrUpdateAccessory(stationContainer, true);
|
|
577
|
-
// Refresh the accessories store so the UI reflects the change
|
|
578
|
-
this.accessoriesStore?.persistNow();
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Processes pending stations and devices in batch.
|
|
582
|
-
* Only creates station entities if:
|
|
583
|
-
* 1. It's a hub/base station (Device.isStation) OR
|
|
584
|
-
* 2. It has at least one device attached
|
|
585
|
-
*
|
|
586
|
-
* All discovered devices are processed since they are already verified by bropat/eufy-security-client.
|
|
587
|
-
*/
|
|
588
|
-
/**
|
|
589
|
-
* Resets the discovery debounce timer.
|
|
590
|
-
* Each time a station or device is emitted, the timer restarts.
|
|
591
|
-
* Processing begins once no new events arrive for DISCOVERY_DEBOUNCE_SEC seconds.
|
|
592
|
-
*/
|
|
593
|
-
resetDiscoveryDebounce() {
|
|
594
|
-
if (this.discoveryDebounceTimeout) {
|
|
595
|
-
clearTimeout(this.discoveryDebounceTimeout);
|
|
596
|
-
}
|
|
597
|
-
const delaySec = EufySecurityPlatform.DISCOVERY_DEBOUNCE_SEC;
|
|
598
|
-
log.debug(`Discovery debounce reset — will process in ${delaySec}s if no more devices arrive ` +
|
|
599
|
-
`(${this.pendingStations.length} station(s), ${this.pendingDevices.length} device(s) queued)`);
|
|
600
|
-
this.discoveryDebounceTimeout = setTimeout(async () => {
|
|
601
|
-
await this.processPendingDevices();
|
|
602
|
-
this.cleanCachedAccessories();
|
|
603
|
-
}, delaySec * 1000);
|
|
604
|
-
}
|
|
605
|
-
async processPendingDevices() {
|
|
606
|
-
log.debug(`[PROCESSING START] Processing ${this.pendingStations.length} stations and ${this.pendingDevices.length} devices`);
|
|
607
|
-
if (this.pendingStations.length === 0 || this.pendingDevices.length === 0) {
|
|
608
|
-
log.warn(`[DISCOVERY WARNING] Discovery finished with ${this.pendingStations.length} station(s) and ${this.pendingDevices.length} devices(s). ` +
|
|
609
|
-
'If this is unexpected, please verify your Eufy account has devices and the credentials used are for a guest admin account.');
|
|
610
|
-
}
|
|
611
|
-
// Build set of stations that have at least one device
|
|
612
|
-
const stationsWithDevices = new Set();
|
|
613
|
-
// Create all queued devices (they are already verified by bropat/eufy-security-client)
|
|
614
|
-
log.debug(`[DEVICES PROCESSING] Starting to process ${this.pendingDevices.length} devices`);
|
|
615
|
-
for (const device of this.pendingDevices) {
|
|
616
|
-
stationsWithDevices.add(device.getStationSerial());
|
|
617
|
-
try {
|
|
618
|
-
const deviceType = device.getDeviceType();
|
|
619
|
-
// Skip devices that the client declares as unsupported
|
|
620
|
-
if (!Device.isSupported(deviceType)) {
|
|
621
|
-
log.warn(`[DEVICE SKIP] "${device.getName()}" (type ${deviceType}) is unsupported by eufy-security-client — skipping accessory creation`);
|
|
622
|
-
continue;
|
|
77
|
+
const accessoryStore = createAccessoryStore(api);
|
|
78
|
+
if (accessoryStore) {
|
|
79
|
+
this.reconciler ??= new HomeKitReconciler(this.runtime, accessoryStore, (diagnostic, affectedDeviceIds = []) => diagnostics.reportHomeKit(diagnostic, affectedDeviceIds), this.cachedAccessories, (trace) => reportHomeKitEvent(diagnosticLog, trace), configuredConfig.entityPreferences, liveMedia, snapshotMedia, recordingMedia, mediaBudget, stationLiveSessions);
|
|
80
|
+
this.reconciler.start();
|
|
623
81
|
}
|
|
624
|
-
|
|
625
|
-
deviceIdentifier: {
|
|
626
|
-
uniqueId: device.getSerial(),
|
|
627
|
-
displayName: 'DEVICE ' + device.getName().replace(/[^a-zA-Z0-9]/g, ''),
|
|
628
|
-
type: deviceType,
|
|
629
|
-
},
|
|
630
|
-
eufyDevice: device,
|
|
631
|
-
};
|
|
632
|
-
log.debug(`[DEVICE] Processing: ${deviceContainer.deviceIdentifier.displayName}`);
|
|
633
|
-
await this.addOrUpdateAccessory(deviceContainer, false);
|
|
634
|
-
log.debug(`[DEVICE] Completed: ${deviceContainer.deviceIdentifier.displayName}`);
|
|
635
|
-
}
|
|
636
|
-
catch (error) {
|
|
637
|
-
log.error(`[DEVICE ERROR] Error processing device "${device.getName()}": ${error}`);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
log.debug(`[DEVICES COMPLETE] All ${this.pendingDevices.length} devices processed`);
|
|
641
|
-
// Create queued stations that should be created
|
|
642
|
-
log.debug(`[STATIONS PROCESSING] Starting to process ${this.pendingStations.length} stations`);
|
|
643
|
-
let stationsSkipped = 0;
|
|
644
|
-
for (const station of this.pendingStations) {
|
|
645
|
-
const stationType = station.getDeviceType();
|
|
646
|
-
const stationSerial = station.getSerial();
|
|
647
|
-
// Hub/base stations (type 0, HB3, etc.) are always valid — they don't
|
|
648
|
-
// appear in DeviceProperties so Device.isSupported() returns false for them.
|
|
649
|
-
// For standalone cameras acting as their own station, fall back to Device.isSupported().
|
|
650
|
-
const isKnownStation = Device.isStation(stationType);
|
|
651
|
-
if (!isKnownStation && !Device.isSupported(stationType)) {
|
|
652
|
-
log.warn(`[STATION SKIP] "${station.getName()}" (type ${stationType}) is unsupported by eufy-security-client — skipping accessory creation`);
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
// Create if: it's a hub/base station OR it has devices
|
|
656
|
-
const shouldCreate = isKnownStation || stationsWithDevices.has(stationSerial);
|
|
657
|
-
if (!shouldCreate) {
|
|
658
|
-
stationsSkipped++;
|
|
659
|
-
log.debug(`[STATION SKIP] "${station.getName()}" has no devices and will be skipped`);
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
662
|
-
try {
|
|
663
|
-
const deviceContainer = {
|
|
664
|
-
deviceIdentifier: {
|
|
665
|
-
uniqueId: stationSerial,
|
|
666
|
-
displayName: 'STATION ' + station.getName().replace(/[^a-zA-Z0-9]/g, ''),
|
|
667
|
-
type: stationType,
|
|
668
|
-
},
|
|
669
|
-
eufyDevice: station,
|
|
670
|
-
};
|
|
671
|
-
log.debug(`[STATION] Processing: ${deviceContainer.deviceIdentifier.displayName}`);
|
|
672
|
-
await this.addOrUpdateAccessory(deviceContainer, true);
|
|
673
|
-
log.debug(`[STATION] Completed: ${deviceContainer.deviceIdentifier.displayName}`);
|
|
674
|
-
}
|
|
675
|
-
catch (error) {
|
|
676
|
-
log.error(`[STATION ERROR] Error processing station "${station.getName()}": ${error}`);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
log.debug(`[STATIONS COMPLETE] ${this.pendingStations.length - stationsSkipped} stations created, ${stationsSkipped} skipped`);
|
|
680
|
-
// Persist the discovered accessories for the UI (immediate, no debounce)
|
|
681
|
-
this.accessoriesStore?.persistNow();
|
|
682
|
-
// Clear pending queues
|
|
683
|
-
this.pendingStations = [];
|
|
684
|
-
this.pendingDevices = [];
|
|
685
|
-
// Mark initial discovery as complete — subsequent events will be processed immediately
|
|
686
|
-
this.initialDiscoveryComplete = true;
|
|
687
|
-
log.info(`[PROCESSING COMPLETE] All pending devices and stations processed`);
|
|
688
|
-
}
|
|
689
|
-
async pluginShutdown() {
|
|
690
|
-
// Ensure a single shutdown to prevent corruption of the persistent file.
|
|
691
|
-
// This also enables captcha through the GUI and prevents repeated captcha or 2FA prompts upon plugin restart.
|
|
692
|
-
if (this.already_shutdown) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
this.already_shutdown = true;
|
|
696
|
-
if (this.discoveryDebounceTimeout) {
|
|
697
|
-
clearTimeout(this.discoveryDebounceTimeout);
|
|
698
|
-
}
|
|
699
|
-
this.accessoriesStore?.cancelPending();
|
|
700
|
-
try {
|
|
701
|
-
if (this.eufyClient?.isConnected?.()) {
|
|
702
|
-
this.eufyClient.close();
|
|
703
|
-
}
|
|
704
|
-
log.info('Finished shutdown!');
|
|
705
|
-
}
|
|
706
|
-
catch (e) {
|
|
707
|
-
log.error(`Error while shutdown: ${e}`);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* This function is invoked when homebridge restores cached accessories from disk at startup.
|
|
712
|
-
* It should be used to setup event handlers for characteristics and update respective values.
|
|
713
|
-
*/
|
|
714
|
-
configureAccessory(accessory) {
|
|
715
|
-
log.debug(`Loading accessory from cache: ${accessory.displayName}`);
|
|
716
|
-
// add the restored accessory to the accessories cache so we can track if it has already been registered
|
|
717
|
-
this.accessories.push(accessory);
|
|
718
|
-
}
|
|
719
|
-
cleanCachedAccessories() {
|
|
720
|
-
if (this.config.cleanCache) {
|
|
721
|
-
log.debug('Looking for old cached accessories that seem to be outdated...');
|
|
722
|
-
let num = 0;
|
|
723
|
-
const staleAccessories = this.accessories.filter((item) => {
|
|
724
|
-
return this.activeAccessoryIds.indexOf(item.UUID) === -1;
|
|
725
|
-
});
|
|
726
|
-
staleAccessories.forEach((staleAccessory) => {
|
|
727
|
-
log.info(`Removing cached accessory ${staleAccessory.UUID} ${staleAccessory.displayName}`);
|
|
728
|
-
num++;
|
|
729
|
-
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [staleAccessory]);
|
|
82
|
+
void this.runtime.start();
|
|
730
83
|
});
|
|
731
|
-
|
|
732
|
-
log.info('Removed ' + num + ' cached accessories');
|
|
733
|
-
}
|
|
734
|
-
else {
|
|
735
|
-
log.info('No outdated cached accessories found.');
|
|
736
|
-
}
|
|
84
|
+
api.on('shutdown', stop);
|
|
737
85
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
log.debug(accessory.displayName + ' UUID:' + accessory.UUID);
|
|
741
|
-
const type = container.deviceIdentifier.type;
|
|
742
|
-
const station = container.eufyDevice;
|
|
743
|
-
if (!Device.isStation(type)) {
|
|
744
|
-
// Standalone Lock or Doorbell doesn't have Security Control
|
|
745
|
-
if (Device.isDoorbell(type) || Device.isLock(type)) {
|
|
746
|
-
throw new Error(`looks station but it's not could imply some errors! Type: ${type}. You can ignore this message.`);
|
|
747
|
-
}
|
|
86
|
+
configureAccessory(accessory) {
|
|
87
|
+
this.cachedAccessories.push(accessory);
|
|
748
88
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}
|
|
768
|
-
if (Device.isSmartDrop(type)) {
|
|
769
|
-
log.debug(accessory.displayName + ' isSmartDrop!');
|
|
770
|
-
new SmartDropAccessory(this, accessory, device);
|
|
771
|
-
}
|
|
772
|
-
if (Device.isLock(type)) {
|
|
773
|
-
log.debug(accessory.displayName + ' isLock!');
|
|
774
|
-
new LockAccessory(this, accessory, device);
|
|
775
|
-
}
|
|
776
|
-
if (Device.isCamera(type) || Device.isLockWifiVideo(type)) {
|
|
777
|
-
log.debug(accessory.displayName + ' isCamera!');
|
|
778
|
-
new CameraAccessory(this, accessory, device);
|
|
779
|
-
// Register garage door(s) as separate accessories for individual naming
|
|
780
|
-
if (device.isGarageCamera()) {
|
|
781
|
-
this.registerGarageDoors(device, container);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
registerGarageDoors(device, container) {
|
|
786
|
-
const cameraConfig = this.config.cameras?.find((e) => e.serialNumber === device.getSerial()) ?? {};
|
|
787
|
-
const doors = [
|
|
788
|
-
{ doorId: 1, prop: PropertyName.DeviceDoor1Open, enableKey: 'enableDoor1', nameKey: 'door1Name' },
|
|
789
|
-
{ doorId: 2, prop: PropertyName.DeviceDoor2Open, enableKey: 'enableDoor2', nameKey: 'door2Name' },
|
|
790
|
-
];
|
|
791
|
-
for (const door of doors) {
|
|
792
|
-
const enabled = cameraConfig[door.enableKey] ?? door.doorId === 1;
|
|
793
|
-
if (!enabled || !device.hasProperty(door.prop))
|
|
794
|
-
continue;
|
|
795
|
-
const doorName = cameraConfig[door.nameKey] || `Garage Door ${door.doorId}`;
|
|
796
|
-
const doorSerial = device.getSerial() + `_door_${door.doorId}`;
|
|
797
|
-
const uuid = HAP.uuid.generate('d1_' + doorSerial);
|
|
798
|
-
const cachedAccessory = this.accessories.find((a) => a.UUID === uuid);
|
|
799
|
-
if (cachedAccessory) {
|
|
800
|
-
this.accessories.splice(this.accessories.indexOf(cachedAccessory), 1);
|
|
801
|
-
}
|
|
802
|
-
const doorAccessory = cachedAccessory || new this.api.platformAccessory(doorName, uuid);
|
|
803
|
-
doorAccessory.context['device'] = container.deviceIdentifier;
|
|
804
|
-
doorAccessory.displayName = doorName;
|
|
805
|
-
new GarageDoorAccessory(this, doorAccessory, device, door.doorId);
|
|
806
|
-
if (this.activeAccessoryIds.indexOf(doorAccessory.UUID) === -1) {
|
|
807
|
-
this.activeAccessoryIds.push(doorAccessory.UUID);
|
|
808
|
-
}
|
|
809
|
-
if (cachedAccessory) {
|
|
810
|
-
this.api.updatePlatformAccessories([doorAccessory]);
|
|
811
|
-
log.info(`Updating existing accessory: ${doorAccessory.displayName}`);
|
|
812
|
-
}
|
|
813
|
-
else {
|
|
814
|
-
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [doorAccessory]);
|
|
815
|
-
log.info(`Registering new accessory: ${doorAccessory.displayName}`);
|
|
816
|
-
}
|
|
817
|
-
this.registeredAccessories.set(doorAccessory.UUID, doorAccessory);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createAccessoryStore(api) {
|
|
92
|
+
if (!api.hap ||
|
|
93
|
+
!api.platformAccessory ||
|
|
94
|
+
!api.registerPlatformAccessories ||
|
|
95
|
+
!api.updatePlatformAccessories ||
|
|
96
|
+
!api.unregisterPlatformAccessories) {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
hap: api.hap,
|
|
101
|
+
generateUuid: (input) => api.hap.uuid.generate(input),
|
|
102
|
+
createAccessory: (name, uuid) => new api.platformAccessory(name, uuid),
|
|
103
|
+
register: (accessories) => api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories),
|
|
104
|
+
update: (accessories) => api.updatePlatformAccessories(accessories),
|
|
105
|
+
unregister: (accessories) => api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, accessories),
|
|
106
|
+
};
|
|
820
107
|
}
|
|
108
|
+
export const EufyPlatform = createEufyPlatform(createPersistedSdkClient, 10_000, process);
|
|
821
109
|
//# sourceMappingURL=platform.js.map
|