@homebridge-plugins/homebridge-eufy-security 4.7.2 → 5.0.0-beta.0

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.
Files changed (471) hide show
  1. package/AUTHORS +4 -0
  2. package/CODE_OF_CONDUCT.md +30 -0
  3. package/CONTRIBUTING.md +77 -0
  4. package/README.md +152 -49
  5. package/SECURITY.md +30 -0
  6. package/config.schema.json +92 -3
  7. package/dist/account/ownership.js +290 -0
  8. package/dist/account/ownership.js.map +1 -0
  9. package/dist/account/persistence.js +274 -0
  10. package/dist/account/persistence.js.map +1 -0
  11. package/dist/account/temporary-authentication.js +348 -0
  12. package/dist/account/temporary-authentication.js.map +1 -0
  13. package/dist/configuration.js +167 -0
  14. package/dist/configuration.js.map +1 -0
  15. package/dist/device/member-evidence.js +66 -0
  16. package/dist/device/member-evidence.js.map +1 -0
  17. package/dist/device/member-trust.js +114 -0
  18. package/dist/device/member-trust.js.map +1 -0
  19. package/dist/device/snapshot.js +190 -0
  20. package/dist/device/snapshot.js.map +1 -0
  21. package/dist/diagnostics.js +2218 -0
  22. package/dist/diagnostics.js.map +1 -0
  23. package/dist/homekit/adapter.js +2 -0
  24. package/dist/homekit/adapter.js.map +1 -0
  25. package/dist/homekit/adapters/battery.js +208 -0
  26. package/dist/homekit/adapters/battery.js.map +1 -0
  27. package/dist/homekit/adapters/camera-controls.js +527 -0
  28. package/dist/homekit/adapters/camera-controls.js.map +1 -0
  29. package/dist/homekit/adapters/camera-streaming.js +1515 -0
  30. package/dist/homekit/adapters/camera-streaming.js.map +1 -0
  31. package/dist/homekit/adapters/contact.js +138 -0
  32. package/dist/homekit/adapters/contact.js.map +1 -0
  33. package/dist/homekit/adapters/doorbell.js +57 -0
  34. package/dist/homekit/adapters/doorbell.js.map +1 -0
  35. package/dist/homekit/adapters/information.js +55 -0
  36. package/dist/homekit/adapters/information.js.map +1 -0
  37. package/dist/homekit/adapters/lock.js +489 -0
  38. package/dist/homekit/adapters/lock.js.map +1 -0
  39. package/dist/homekit/adapters/motion.js +100 -0
  40. package/dist/homekit/adapters/motion.js.map +1 -0
  41. package/dist/homekit/adapters/registry.js +26 -0
  42. package/dist/homekit/adapters/registry.js.map +1 -0
  43. package/dist/homekit/adapters/security-system.js +425 -0
  44. package/dist/homekit/adapters/security-system.js.map +1 -0
  45. package/dist/homekit/adapters/siren.js +136 -0
  46. package/dist/homekit/adapters/siren.js.map +1 -0
  47. package/dist/homekit/adapters/smart-light.js +662 -0
  48. package/dist/homekit/adapters/smart-light.js.map +1 -0
  49. package/dist/homekit/coverage-matrix.js +119 -0
  50. package/dist/homekit/coverage-matrix.js.map +1 -0
  51. package/dist/homekit/device-control.js +175 -0
  52. package/dist/homekit/device-control.js.map +1 -0
  53. package/dist/homekit/reconciler.js +319 -0
  54. package/dist/homekit/reconciler.js.map +1 -0
  55. package/dist/homekit/representation.js +23 -0
  56. package/dist/homekit/representation.js.map +1 -0
  57. package/dist/index.js +4 -9
  58. package/dist/index.js.map +1 -1
  59. package/dist/media/contracts.js +2 -0
  60. package/dist/media/contracts.js.map +1 -0
  61. package/dist/media/last-successful-image.js +208 -0
  62. package/dist/media/last-successful-image.js.map +1 -0
  63. package/dist/media/live-stream.js +1003 -0
  64. package/dist/media/live-stream.js.map +1 -0
  65. package/dist/media/recording.js +560 -0
  66. package/dist/media/recording.js.map +1 -0
  67. package/dist/media/session-budget.js +39 -0
  68. package/dist/media/session-budget.js.map +1 -0
  69. package/dist/media/snapshot.js +406 -0
  70. package/dist/media/snapshot.js.map +1 -0
  71. package/dist/media/station-live-sessions.js +101 -0
  72. package/dist/media/station-live-sessions.js.map +1 -0
  73. package/dist/platform.js +97 -809
  74. package/dist/platform.js.map +1 -1
  75. package/dist/runtime/channel.js +375 -0
  76. package/dist/runtime/channel.js.map +1 -0
  77. package/dist/runtime/owner.js +682 -0
  78. package/dist/runtime/owner.js.map +1 -0
  79. package/dist/runtime/sdk-client.js +211 -0
  80. package/dist/runtime/sdk-client.js.map +1 -0
  81. package/dist/runtime/tracker.js +211 -0
  82. package/dist/runtime/tracker.js.map +1 -0
  83. package/dist/settings.js +1 -43
  84. package/dist/settings.js.map +1 -1
  85. package/dist/storage.js +59 -0
  86. package/dist/storage.js.map +1 -0
  87. package/dist/ui/dashboard.js +181 -0
  88. package/dist/ui/dashboard.js.map +1 -0
  89. package/dist/ui/device-image.js +22 -0
  90. package/dist/ui/device-image.js.map +1 -0
  91. package/dist/ui/runtime-channel-client.js +305 -0
  92. package/dist/ui/runtime-channel-client.js.map +1 -0
  93. package/dist/ui/server.js +259 -0
  94. package/dist/ui/server.js.map +1 -0
  95. package/homebridge-ui/public/app.css +1902 -0
  96. package/homebridge-ui/public/assets/devices/clean/clean-T1250.webp +0 -0
  97. package/homebridge-ui/public/assets/devices/clean/clean-T2070.webp +0 -0
  98. package/homebridge-ui/public/assets/devices/clean/clean-T2080.webp +0 -0
  99. package/homebridge-ui/public/assets/devices/clean/clean-T2081.webp +0 -0
  100. package/homebridge-ui/public/assets/devices/clean/clean-T2103.webp +0 -0
  101. package/homebridge-ui/public/assets/devices/clean/clean-T2117.webp +0 -0
  102. package/homebridge-ui/public/assets/devices/clean/clean-T2118.webp +0 -0
  103. package/homebridge-ui/public/assets/devices/clean/clean-T2119.webp +0 -0
  104. package/homebridge-ui/public/assets/devices/clean/clean-T211A.webp +0 -0
  105. package/homebridge-ui/public/assets/devices/clean/clean-T2120.webp +0 -0
  106. package/homebridge-ui/public/assets/devices/clean/clean-T2123.webp +0 -0
  107. package/homebridge-ui/public/assets/devices/clean/clean-T2128.webp +0 -0
  108. package/homebridge-ui/public/assets/devices/clean/clean-T2130.webp +0 -0
  109. package/homebridge-ui/public/assets/devices/clean/clean-T2132.webp +0 -0
  110. package/homebridge-ui/public/assets/devices/clean/clean-T2150.webp +0 -0
  111. package/homebridge-ui/public/assets/devices/clean/clean-T2181.webp +0 -0
  112. package/homebridge-ui/public/assets/devices/clean/clean-T2182.webp +0 -0
  113. package/homebridge-ui/public/assets/devices/clean/clean-T2190.webp +0 -0
  114. package/homebridge-ui/public/assets/devices/clean/clean-T2192.webp +0 -0
  115. package/homebridge-ui/public/assets/devices/clean/clean-T2193.webp +0 -0
  116. package/homebridge-ui/public/assets/devices/clean/clean-T2194.webp +0 -0
  117. package/homebridge-ui/public/assets/devices/clean/clean-T2210.webp +0 -0
  118. package/homebridge-ui/public/assets/devices/clean/clean-T2250.webp +0 -0
  119. package/homebridge-ui/public/assets/devices/clean/clean-T2251.webp +0 -0
  120. package/homebridge-ui/public/assets/devices/clean/clean-T2252.webp +0 -0
  121. package/homebridge-ui/public/assets/devices/clean/clean-T2253.webp +0 -0
  122. package/homebridge-ui/public/assets/devices/clean/clean-T2254.webp +0 -0
  123. package/homebridge-ui/public/assets/devices/clean/clean-T2255.webp +0 -0
  124. package/homebridge-ui/public/assets/devices/clean/clean-T2256.webp +0 -0
  125. package/homebridge-ui/public/assets/devices/clean/clean-T2257.webp +0 -0
  126. package/homebridge-ui/public/assets/devices/clean/clean-T2258.webp +0 -0
  127. package/homebridge-ui/public/assets/devices/clean/clean-T2259.webp +0 -0
  128. package/homebridge-ui/public/assets/devices/clean/clean-T2261.webp +0 -0
  129. package/homebridge-ui/public/assets/devices/clean/clean-T2262.webp +0 -0
  130. package/homebridge-ui/public/assets/devices/clean/clean-T2266.webp +0 -0
  131. package/homebridge-ui/public/assets/devices/clean/clean-T2267.webp +0 -0
  132. package/homebridge-ui/public/assets/devices/clean/clean-T2268.webp +0 -0
  133. package/homebridge-ui/public/assets/devices/clean/clean-T2270.webp +0 -0
  134. package/homebridge-ui/public/assets/devices/clean/clean-T2272.webp +0 -0
  135. package/homebridge-ui/public/assets/devices/clean/clean-T2273.webp +0 -0
  136. package/homebridge-ui/public/assets/devices/clean/clean-T2276.webp +0 -0
  137. package/homebridge-ui/public/assets/devices/clean/clean-T2277.webp +0 -0
  138. package/homebridge-ui/public/assets/devices/clean/clean-T2278.webp +0 -0
  139. package/homebridge-ui/public/assets/devices/clean/clean-T2280.webp +0 -0
  140. package/homebridge-ui/public/assets/devices/clean/clean-T2292.webp +0 -0
  141. package/homebridge-ui/public/assets/devices/clean/clean-T2320.webp +0 -0
  142. package/homebridge-ui/public/assets/devices/clean/clean-T2351.webp +0 -0
  143. package/homebridge-ui/public/assets/devices/clean/clean-T2352.webp +0 -0
  144. package/homebridge-ui/public/assets/devices/clean/clean-T2353.webp +0 -0
  145. package/homebridge-ui/public/assets/devices/life/life-T8L00.webp +0 -0
  146. package/homebridge-ui/public/assets/devices/life/life-T8L01.webp +0 -0
  147. package/homebridge-ui/public/assets/devices/life/life-T8L02.webp +0 -0
  148. package/homebridge-ui/public/assets/devices/life/life-T8L04.webp +0 -0
  149. package/homebridge-ui/public/assets/devices/life/life-T8L10.webp +0 -0
  150. package/homebridge-ui/public/assets/devices/life/life-T8L20.webp +0 -0
  151. package/homebridge-ui/public/assets/devices/life/life-T8L30.webp +0 -0
  152. package/homebridge-ui/public/assets/devices/life/life-T8L40.webp +0 -0
  153. package/homebridge-ui/public/assets/devices/mower/mower-T2801.webp +0 -0
  154. package/homebridge-ui/public/assets/devices/mower/mower-T280B.webp +0 -0
  155. package/homebridge-ui/public/assets/devices/mower/mower-T2880.webp +0 -0
  156. package/homebridge-ui/public/assets/devices/security/security-T7400.webp +0 -0
  157. package/homebridge-ui/public/assets/devices/security/security-T7401.webp +0 -0
  158. package/homebridge-ui/public/assets/devices/security/security-T8001.webp +0 -0
  159. package/homebridge-ui/public/assets/devices/security/security-T8002.webp +0 -0
  160. package/homebridge-ui/public/assets/devices/security/security-T8010.webp +0 -0
  161. package/homebridge-ui/public/assets/devices/security/security-T8021.webp +0 -0
  162. package/homebridge-ui/public/assets/devices/security/security-T8023.webp +0 -0
  163. package/homebridge-ui/public/assets/devices/security/security-T8025.webp +0 -0
  164. package/homebridge-ui/public/assets/devices/security/security-T8030.webp +0 -0
  165. package/homebridge-ui/public/assets/devices/security/security-T8110.webp +0 -0
  166. package/homebridge-ui/public/assets/devices/security/security-T8111.webp +0 -0
  167. package/homebridge-ui/public/assets/devices/security/security-T8112.webp +0 -0
  168. package/homebridge-ui/public/assets/devices/security/security-T8113.webp +0 -0
  169. package/homebridge-ui/public/assets/devices/security/security-T8114.webp +0 -0
  170. package/homebridge-ui/public/assets/devices/security/security-T8122.webp +0 -0
  171. package/homebridge-ui/public/assets/devices/security/security-T8123.webp +0 -0
  172. package/homebridge-ui/public/assets/devices/security/security-T8124.webp +0 -0
  173. package/homebridge-ui/public/assets/devices/security/security-T8130.webp +0 -0
  174. package/homebridge-ui/public/assets/devices/security/security-T8131.webp +0 -0
  175. package/homebridge-ui/public/assets/devices/security/security-T8134.webp +0 -0
  176. package/homebridge-ui/public/assets/devices/security/security-T8140.webp +0 -0
  177. package/homebridge-ui/public/assets/devices/security/security-T8141.webp +0 -0
  178. package/homebridge-ui/public/assets/devices/security/security-T8150.webp +0 -0
  179. package/homebridge-ui/public/assets/devices/security/security-T8151.webp +0 -0
  180. package/homebridge-ui/public/assets/devices/security/security-T8152.webp +0 -0
  181. package/homebridge-ui/public/assets/devices/security/security-T8153.webp +0 -0
  182. package/homebridge-ui/public/assets/devices/security/security-T8160.webp +0 -0
  183. package/homebridge-ui/public/assets/devices/security/security-T8161.webp +0 -0
  184. package/homebridge-ui/public/assets/devices/security/security-T8162.webp +0 -0
  185. package/homebridge-ui/public/assets/devices/security/security-T8170.webp +0 -0
  186. package/homebridge-ui/public/assets/devices/security/security-T8171.webp +0 -0
  187. package/homebridge-ui/public/assets/devices/security/security-T8172.webp +0 -0
  188. package/homebridge-ui/public/assets/devices/security/security-T8173.webp +0 -0
  189. package/homebridge-ui/public/assets/devices/security/security-T8200.webp +0 -0
  190. package/homebridge-ui/public/assets/devices/security/security-T8200X.webp +0 -0
  191. package/homebridge-ui/public/assets/devices/security/security-T8201.webp +0 -0
  192. package/homebridge-ui/public/assets/devices/security/security-T8203.webp +0 -0
  193. package/homebridge-ui/public/assets/devices/security/security-T8210.webp +0 -0
  194. package/homebridge-ui/public/assets/devices/security/security-T8213.webp +0 -0
  195. package/homebridge-ui/public/assets/devices/security/security-T8214.webp +0 -0
  196. package/homebridge-ui/public/assets/devices/security/security-T8222.webp +0 -0
  197. package/homebridge-ui/public/assets/devices/security/security-T8400.webp +0 -0
  198. package/homebridge-ui/public/assets/devices/security/security-T8401.webp +0 -0
  199. package/homebridge-ui/public/assets/devices/security/security-T8410.webp +0 -0
  200. package/homebridge-ui/public/assets/devices/security/security-T8411.webp +0 -0
  201. package/homebridge-ui/public/assets/devices/security/security-T8414.webp +0 -0
  202. package/homebridge-ui/public/assets/devices/security/security-T8416.webp +0 -0
  203. package/homebridge-ui/public/assets/devices/security/security-T8417.webp +0 -0
  204. package/homebridge-ui/public/assets/devices/security/security-T8419.webp +0 -0
  205. package/homebridge-ui/public/assets/devices/security/security-T8419N.webp +0 -0
  206. package/homebridge-ui/public/assets/devices/security/security-T8420.webp +0 -0
  207. package/homebridge-ui/public/assets/devices/security/security-T8420X.webp +0 -0
  208. package/homebridge-ui/public/assets/devices/security/security-T8422.webp +0 -0
  209. package/homebridge-ui/public/assets/devices/security/security-T8423.webp +0 -0
  210. package/homebridge-ui/public/assets/devices/security/security-T8424.webp +0 -0
  211. package/homebridge-ui/public/assets/devices/security/security-T8425.webp +0 -0
  212. package/homebridge-ui/public/assets/devices/security/security-T8441.webp +0 -0
  213. package/homebridge-ui/public/assets/devices/security/security-T8442.webp +0 -0
  214. package/homebridge-ui/public/assets/devices/security/security-T8452.webp +0 -0
  215. package/homebridge-ui/public/assets/devices/security/security-T84A0.webp +0 -0
  216. package/homebridge-ui/public/assets/devices/security/security-T84A1.webp +0 -0
  217. package/homebridge-ui/public/assets/devices/security/security-T8500.webp +0 -0
  218. package/homebridge-ui/public/assets/devices/security/security-T8502.webp +0 -0
  219. package/homebridge-ui/public/assets/devices/security/security-T8503.webp +0 -0
  220. package/homebridge-ui/public/assets/devices/security/security-T8504.webp +0 -0
  221. package/homebridge-ui/public/assets/devices/security/security-T8506.webp +0 -0
  222. package/homebridge-ui/public/assets/devices/security/security-T8510.webp +0 -0
  223. package/homebridge-ui/public/assets/devices/security/security-T8510P.webp +0 -0
  224. package/homebridge-ui/public/assets/devices/security/security-T8520.webp +0 -0
  225. package/homebridge-ui/public/assets/devices/security/security-T8520P.webp +0 -0
  226. package/homebridge-ui/public/assets/devices/security/security-T8530.webp +0 -0
  227. package/homebridge-ui/public/assets/devices/security/security-T8531.webp +0 -0
  228. package/homebridge-ui/public/assets/devices/security/security-T85D0.webp +0 -0
  229. package/homebridge-ui/public/assets/devices/security/security-T85L0.webp +0 -0
  230. package/homebridge-ui/public/assets/devices/security/security-T85P0.webp +0 -0
  231. package/homebridge-ui/public/assets/devices/security/security-T85V0.webp +0 -0
  232. package/homebridge-ui/public/assets/devices/security/security-T8600.webp +0 -0
  233. package/homebridge-ui/public/assets/devices/security/security-T86P2.webp +0 -0
  234. package/homebridge-ui/public/assets/devices/security/security-T8790.webp +0 -0
  235. package/homebridge-ui/public/assets/devices/security/security-T87B0.webp +0 -0
  236. package/homebridge-ui/public/assets/devices/security/security-T87B2.webp +0 -0
  237. package/homebridge-ui/public/assets/devices/security/security-T8900.webp +0 -0
  238. package/homebridge-ui/public/assets/devices/security/security-T8910.webp +0 -0
  239. package/homebridge-ui/public/assets/devices/security/security-T8920.webp +0 -0
  240. package/homebridge-ui/public/assets/devices/security/security-T8960.webp +0 -0
  241. package/homebridge-ui/public/assets/devices/security/security-T8B0.webp +0 -0
  242. package/homebridge-ui/public/assets/devices/security/security-T8E00.webp +0 -0
  243. package/homebridge-ui/public/assets/devices/security/security-T8N00.webp +0 -0
  244. package/homebridge-ui/public/assets/devices/security/security-T8W11C.webp +0 -0
  245. package/homebridge-ui/public/assets/devices/security/security-T90F0.webp +0 -0
  246. package/homebridge-ui/public/assets/devices/security/security-T90M0.webp +0 -0
  247. package/homebridge-ui/public/assets/devices/security/security-T90R0.webp +0 -0
  248. package/homebridge-ui/public/assets/devices/security/security-T90S0.webp +0 -0
  249. package/homebridge-ui/public/assets/icons/inventory.svg +1 -1
  250. package/homebridge-ui/public/assets/icons/troubleshoot.svg +1 -0
  251. package/homebridge-ui/public/assets/icons/warning.svg +1 -1
  252. package/homebridge-ui/public/assets/logo-dark.svg +10 -0
  253. package/homebridge-ui/public/assets/logo.svg +10 -0
  254. package/homebridge-ui/public/i18n/en.json +218 -0
  255. package/homebridge-ui/public/i18n/fr.json +218 -0
  256. package/homebridge-ui/public/index.html +377 -28
  257. package/homebridge-ui/public/js/app.js +1065 -0
  258. package/homebridge-ui/public/js/dashboard.js +385 -0
  259. package/homebridge-ui/public/js/legacy-settings.js +37 -0
  260. package/homebridge-ui/public/js/profile-wizard.js +68 -0
  261. package/homebridge-ui/server.js +2 -1393
  262. package/i18n/runtime/en.json +100 -0
  263. package/media/Snapshot-Unavailable.jpg +0 -0
  264. package/media/Snapshot-black.jpg +0 -0
  265. package/media/camera-disabled.jpg +0 -0
  266. package/media/camera-offline.jpg +0 -0
  267. package/package.json +29 -24
  268. package/.claude/CLAUDE.md +0 -175
  269. package/.claude/PRD.md +0 -241
  270. package/.claude/docs/debug-recording-lessons.md +0 -91
  271. package/.claude/docs/hksv-recording-fix.md +0 -160
  272. package/.claude/skills/architect/SKILL.md +0 -76
  273. package/.claude/skills/developer/SKILL.md +0 -59
  274. package/.claude/skills/new-device-support/SKILL.md +0 -212
  275. package/.claude/skills/new-device-support/check-device.mjs +0 -363
  276. package/.claude/skills/new-device-support/map-properties.mjs +0 -450
  277. package/.claude/skills/new-device-support/verify-device.mjs +0 -272
  278. package/.claude/skills/planner/SKILL.md +0 -100
  279. package/.claude/skills/qa/SKILL.md +0 -79
  280. package/.claude/skills/support/SKILL.md +0 -175
  281. package/.prettierignore +0 -7
  282. package/.prettierrc.json +0 -7
  283. package/FUNDING.yml +0 -1
  284. package/dist/accessories/AutoSyncStationAccessory.js +0 -157
  285. package/dist/accessories/AutoSyncStationAccessory.js.map +0 -1
  286. package/dist/accessories/BaseAccessory.js +0 -261
  287. package/dist/accessories/BaseAccessory.js.map +0 -1
  288. package/dist/accessories/CameraAccessory.js +0 -459
  289. package/dist/accessories/CameraAccessory.js.map +0 -1
  290. package/dist/accessories/Device.js +0 -65
  291. package/dist/accessories/Device.js.map +0 -1
  292. package/dist/accessories/EntrySensorAccessory.js +0 -48
  293. package/dist/accessories/EntrySensorAccessory.js.map +0 -1
  294. package/dist/accessories/GarageDoorAccessory.js +0 -160
  295. package/dist/accessories/GarageDoorAccessory.js.map +0 -1
  296. package/dist/accessories/LockAccessory.js +0 -142
  297. package/dist/accessories/LockAccessory.js.map +0 -1
  298. package/dist/accessories/MotionSensorAccessory.js +0 -48
  299. package/dist/accessories/MotionSensorAccessory.js.map +0 -1
  300. package/dist/accessories/SmartDropAccessory.js +0 -141
  301. package/dist/accessories/SmartDropAccessory.js.map +0 -1
  302. package/dist/accessories/StationAccessory.js +0 -373
  303. package/dist/accessories/StationAccessory.js.map +0 -1
  304. package/dist/config.js +0 -26
  305. package/dist/config.js.map +0 -1
  306. package/dist/controller/DebugRecordingManager.js +0 -402
  307. package/dist/controller/DebugRecordingManager.js.map +0 -1
  308. package/dist/controller/LocalLivestreamManager.js +0 -371
  309. package/dist/controller/LocalLivestreamManager.js.map +0 -1
  310. package/dist/controller/recordingDelegate.js +0 -252
  311. package/dist/controller/recordingDelegate.js.map +0 -1
  312. package/dist/controller/snapshotDelegate.js +0 -348
  313. package/dist/controller/snapshotDelegate.js.map +0 -1
  314. package/dist/controller/streamingDelegate.js +0 -389
  315. package/dist/controller/streamingDelegate.js.map +0 -1
  316. package/dist/interfaces.js +0 -2
  317. package/dist/interfaces.js.map +0 -1
  318. package/dist/media/Snapshot-Unavailable.png +0 -0
  319. package/dist/media/Snapshot-Unavailable.xcf +0 -0
  320. package/dist/media/Snapshot-black.png +0 -0
  321. package/dist/media/camera-disabled.png +0 -0
  322. package/dist/media/camera-offline.png +0 -0
  323. package/dist/media/media/Snapshot-Unavailable.png +0 -0
  324. package/dist/media/media/Snapshot-Unavailable.xcf +0 -0
  325. package/dist/media/media/Snapshot-black.png +0 -0
  326. package/dist/media/media/camera-disabled.png +0 -0
  327. package/dist/media/media/camera-offline.png +0 -0
  328. package/dist/utils/Talkback.js +0 -162
  329. package/dist/utils/Talkback.js.map +0 -1
  330. package/dist/utils/accessoriesStore.js +0 -210
  331. package/dist/utils/accessoriesStore.js.map +0 -1
  332. package/dist/utils/configTypes.js +0 -38
  333. package/dist/utils/configTypes.js.map +0 -1
  334. package/dist/utils/ffmpeg.js +0 -1111
  335. package/dist/utils/ffmpeg.js.map +0 -1
  336. package/dist/utils/interfaces.js +0 -8
  337. package/dist/utils/interfaces.js.map +0 -1
  338. package/dist/utils/utils.js +0 -224
  339. package/dist/utils/utils.js.map +0 -1
  340. package/dist/version.js +0 -2
  341. package/dist/version.js.map +0 -1
  342. package/homebridge-eufy-security.png +0 -0
  343. package/homebridge-ui/diagnosticsCrypto.js +0 -112
  344. package/homebridge-ui/public/app.js +0 -237
  345. package/homebridge-ui/public/assets/devices/4g_lte_cam_s330_large.png +0 -0
  346. package/homebridge-ui/public/assets/devices/4g_lte_starlight_large.jpg +0 -0
  347. package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C30.png +0 -0
  348. package/homebridge-ui/public/assets/devices/BATTERY_DOORBELL_C31.png +0 -0
  349. package/homebridge-ui/public/assets/devices/batterydoorbell1080p_large.jpg +0 -0
  350. package/homebridge-ui/public/assets/devices/batterydoorbell2k_large.png +0 -0
  351. package/homebridge-ui/public/assets/devices/batterydoorbell2kdual_large.jpg +0 -0
  352. package/homebridge-ui/public/assets/devices/batterydoorbell_e340_large.png +0 -0
  353. package/homebridge-ui/public/assets/devices/eufy-security-client.png +0 -0
  354. package/homebridge-ui/public/assets/devices/eufyCamS4_large.png +0 -0
  355. package/homebridge-ui/public/assets/devices/eufycam2_large.png +0 -0
  356. package/homebridge-ui/public/assets/devices/eufycam2c_large.jpg +0 -0
  357. package/homebridge-ui/public/assets/devices/eufycam2cpro_large.jpg +0 -0
  358. package/homebridge-ui/public/assets/devices/eufycam2pro_large.jpg +0 -0
  359. package/homebridge-ui/public/assets/devices/eufycam3_large.jpg +0 -0
  360. package/homebridge-ui/public/assets/devices/eufycam3c_large.jpg +0 -0
  361. package/homebridge-ui/public/assets/devices/eufycam3pro_large.png +0 -0
  362. package/homebridge-ui/public/assets/devices/eufycam_large.jpg +0 -0
  363. package/homebridge-ui/public/assets/devices/eufycame330_large.jpg +0 -0
  364. package/homebridge-ui/public/assets/devices/flood_freeze_sensor_e20_t90F0_large.png +0 -0
  365. package/homebridge-ui/public/assets/devices/floodlight2_large.jpg +0 -0
  366. package/homebridge-ui/public/assets/devices/floodlight2pro_large.jpg +0 -0
  367. package/homebridge-ui/public/assets/devices/floodlight_large.jpg +0 -0
  368. package/homebridge-ui/public/assets/devices/floodlightcame340_large.jpg +0 -0
  369. package/homebridge-ui/public/assets/devices/garage_camera_t8452_large.png +0 -0
  370. package/homebridge-ui/public/assets/devices/homebase2_large.png +0 -0
  371. package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
  372. package/homebridge-ui/public/assets/devices/homebase_large.jpg +0 -0
  373. package/homebridge-ui/public/assets/devices/homebasemini_large.png +0 -0
  374. package/homebridge-ui/public/assets/devices/indoorcamC210_large.png +0 -0
  375. package/homebridge-ui/public/assets/devices/indoorcamC220_large.png +0 -0
  376. package/homebridge-ui/public/assets/devices/indoorcamC220_t8419N_large.png +0 -0
  377. package/homebridge-ui/public/assets/devices/indoorcamE30_large.png +0 -0
  378. package/homebridge-ui/public/assets/devices/indoorcamc120_large.png +0 -0
  379. package/homebridge-ui/public/assets/devices/indoorcammini_large.jpg +0 -0
  380. package/homebridge-ui/public/assets/devices/indoorcamp24_large.png +0 -0
  381. package/homebridge-ui/public/assets/devices/indoorcams350_large.png +0 -0
  382. package/homebridge-ui/public/assets/devices/keypad_large.png +0 -0
  383. package/homebridge-ui/public/assets/devices/minibase_chime_T8023_large.jpg +0 -0
  384. package/homebridge-ui/public/assets/devices/motionsensor_large.png +0 -0
  385. package/homebridge-ui/public/assets/devices/nvr_s4_max_T8N00_large.png +0 -0
  386. package/homebridge-ui/public/assets/devices/pir_sensor_e20_large.png +0 -0
  387. package/homebridge-ui/public/assets/devices/poe_bullet_ptz_cam_s4_T8E00_large.png +0 -0
  388. package/homebridge-ui/public/assets/devices/sensor_large.png +0 -0
  389. package/homebridge-ui/public/assets/devices/siren_sensor_e20_large.png +0 -0
  390. package/homebridge-ui/public/assets/devices/smartdrop_t8790_large.png +0 -0
  391. package/homebridge-ui/public/assets/devices/smartlock_t8500_large.png +0 -0
  392. package/homebridge-ui/public/assets/devices/smartlock_t8500_wifibridge_large.jpg +0 -0
  393. package/homebridge-ui/public/assets/devices/smartlock_t8503_large.png +0 -0
  394. package/homebridge-ui/public/assets/devices/smartlock_t8504_large.jpg +0 -0
  395. package/homebridge-ui/public/assets/devices/smartlock_t8510P_t8520P_large.png +0 -0
  396. package/homebridge-ui/public/assets/devices/smartlock_t85D0_large.png +0 -0
  397. package/homebridge-ui/public/assets/devices/smartlock_t85L0_large.png +0 -0
  398. package/homebridge-ui/public/assets/devices/smartlock_t85P0_large.png +0 -0
  399. package/homebridge-ui/public/assets/devices/smartlock_t85V0_large.png +0 -0
  400. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8502_large.png +0 -0
  401. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8506_large.png +0 -0
  402. package/homebridge-ui/public/assets/devices/smartlock_touch_and_wifi_t8520_large.png +0 -0
  403. package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_large.png +0 -0
  404. package/homebridge-ui/public/assets/devices/smartlock_touch_t8510_wifibridge_large.jpg +0 -0
  405. package/homebridge-ui/public/assets/devices/smartlock_video_t8530_large.png +0 -0
  406. package/homebridge-ui/public/assets/devices/smartlock_video_t8531_large.png +0 -0
  407. package/homebridge-ui/public/assets/devices/smartlockwifibridge_t8021_large.jpg +0 -0
  408. package/homebridge-ui/public/assets/devices/smartsafe_s10_t7400_large.png +0 -0
  409. package/homebridge-ui/public/assets/devices/smartsafe_s12_t7401_large.png +0 -0
  410. package/homebridge-ui/public/assets/devices/smarttrack_card_t87B2_large.png +0 -0
  411. package/homebridge-ui/public/assets/devices/smarttrack_link_t87B0_large.png +0 -0
  412. package/homebridge-ui/public/assets/devices/smoke_alarm_e10_t90S0_large.png +0 -0
  413. package/homebridge-ui/public/assets/devices/solocamc210_large.jpg +0 -0
  414. package/homebridge-ui/public/assets/devices/solocamc35_large.png +0 -0
  415. package/homebridge-ui/public/assets/devices/solocame20_large.jpg +0 -0
  416. package/homebridge-ui/public/assets/devices/solocame30_large.png +0 -0
  417. package/homebridge-ui/public/assets/devices/solocame40_large.jpg +0 -0
  418. package/homebridge-ui/public/assets/devices/solocame42_large.png +0 -0
  419. package/homebridge-ui/public/assets/devices/solocaml20_large.jpg +0 -0
  420. package/homebridge-ui/public/assets/devices/solocams220_large.jpg +0 -0
  421. package/homebridge-ui/public/assets/devices/solocams340_large.png +0 -0
  422. package/homebridge-ui/public/assets/devices/solocams40_large.jpg +0 -0
  423. package/homebridge-ui/public/assets/devices/soloindoorcamc24_large.jpg +0 -0
  424. package/homebridge-ui/public/assets/devices/solooutdoorcamc22_large.png +0 -0
  425. package/homebridge-ui/public/assets/devices/solooutdoorcamc24_large.jpg +0 -0
  426. package/homebridge-ui/public/assets/devices/unknown.png +0 -0
  427. package/homebridge-ui/public/assets/devices/walllight_s100_large.png +0 -0
  428. package/homebridge-ui/public/assets/devices/walllight_s120_large.png +0 -0
  429. package/homebridge-ui/public/assets/devices/water_freeze_sensor_t8920_large.png +0 -0
  430. package/homebridge-ui/public/assets/devices/wireddoorbell1080p_large.jpg +0 -0
  431. package/homebridge-ui/public/assets/devices/wireddoorbell2k_large.png +0 -0
  432. package/homebridge-ui/public/assets/devices/wireddoorbelldual_large.jpg +0 -0
  433. package/homebridge-ui/public/assets/icons/attach.svg +0 -1
  434. package/homebridge-ui/public/assets/icons/battery_0.svg +0 -1
  435. package/homebridge-ui/public/assets/icons/battery_1.svg +0 -1
  436. package/homebridge-ui/public/assets/icons/battery_2.svg +0 -1
  437. package/homebridge-ui/public/assets/icons/battery_3.svg +0 -1
  438. package/homebridge-ui/public/assets/icons/battery_4.svg +0 -1
  439. package/homebridge-ui/public/assets/icons/battery_5.svg +0 -1
  440. package/homebridge-ui/public/assets/icons/battery_6.svg +0 -1
  441. package/homebridge-ui/public/assets/icons/copy.svg +0 -1
  442. package/homebridge-ui/public/assets/icons/delete.svg +0 -1
  443. package/homebridge-ui/public/assets/icons/download.svg +0 -1
  444. package/homebridge-ui/public/assets/icons/info.svg +0 -1
  445. package/homebridge-ui/public/assets/icons/refresh.svg +0 -1
  446. package/homebridge-ui/public/assets/icons/satellite_alt.svg +0 -1
  447. package/homebridge-ui/public/assets/icons/solar_power.svg +0 -1
  448. package/homebridge-ui/public/components/device-card.js +0 -174
  449. package/homebridge-ui/public/components/guard-modes.js +0 -88
  450. package/homebridge-ui/public/components/number-input.js +0 -121
  451. package/homebridge-ui/public/components/select.js +0 -73
  452. package/homebridge-ui/public/components/toggle.js +0 -68
  453. package/homebridge-ui/public/services/api.js +0 -250
  454. package/homebridge-ui/public/services/config.js +0 -144
  455. package/homebridge-ui/public/style.css +0 -818
  456. package/homebridge-ui/public/utils/countries.js +0 -73
  457. package/homebridge-ui/public/utils/device-images.js +0 -102
  458. package/homebridge-ui/public/utils/helpers.js +0 -115
  459. package/homebridge-ui/public/views/dashboard.js +0 -226
  460. package/homebridge-ui/public/views/device-detail.js +0 -654
  461. package/homebridge-ui/public/views/diagnostics.js +0 -402
  462. package/homebridge-ui/public/views/login.js +0 -650
  463. package/homebridge-ui/public/views/recordings.js +0 -319
  464. package/homebridge-ui/public/views/settings.js +0 -197
  465. package/homebridge-ui/public/views/unsupported-detail.js +0 -296
  466. package/media/Snapshot-Unavailable.png +0 -0
  467. package/media/Snapshot-Unavailable.xcf +0 -0
  468. package/media/Snapshot-black.png +0 -0
  469. package/media/camera-disabled.png +0 -0
  470. package/media/camera-offline.png +0 -0
  471. 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 { DEFAULT_CONFIG_VALUES } from './config.js';
3
- import { StationAccessory } from './accessories/StationAccessory.js';
4
- import { EntrySensorAccessory } from './accessories/EntrySensorAccessory.js';
5
- import { MotionSensorAccessory } from './accessories/MotionSensorAccessory.js';
6
- import { CameraAccessory } from './accessories/CameraAccessory.js';
7
- import { LockAccessory } from './accessories/LockAccessory.js';
8
- import { SmartDropAccessory } from './accessories/SmartDropAccessory.js';
9
- import { GarageDoorAccessory } from './accessories/GarageDoorAccessory.js';
10
- import { AutoSyncStationAccessory } from './accessories/AutoSyncStationAccessory.js';
11
- import { EufySecurity, Device, UserType, PropertyName, libVersion, LogLevel, } from 'eufy-security-client';
12
- import fs from 'fs';
13
- import os from 'node:os';
14
- import { platform } from 'node:process';
15
- import { readFileSync } from 'node:fs';
16
- import { initLog, log, tsLogger, HAP, configureLogStreams } from './utils/utils.js';
17
- import { hasFdkAac, probeHardwareEncoder } from './utils/ffmpeg.js';
18
- import { AccessoriesStore } from './utils/accessoriesStore.js';
19
- import { LIB_VERSION } from './version.js';
20
- import { DebugRecordingManager } from './controller/DebugRecordingManager.js';
21
- export class EufySecurityPlatform {
22
- config;
23
- api;
24
- eufyClient = {};
25
- /** Shared debug recording manager — created when debugLivestream is enabled. */
26
- debugRecordingManager = null;
27
- // this is used to track restored cached accessories
28
- accessories = [];
29
- already_shutdown = false;
30
- eufyPath;
31
- activeAccessoryIds = [];
32
- discoveryDebounceTimeout;
33
- pendingStations = [];
34
- pendingDevices = [];
35
- /** Seconds to wait after the last station/device event before processing. */
36
- static DISCOVERY_DEBOUNCE_SEC = 10;
37
- /** Maximum number of connection attempts before giving up. */
38
- static MAX_CONNECT_ATTEMPTS = 3;
39
- /** Seconds to wait between connection retry attempts. */
40
- static CONNECT_RETRY_DELAY_SEC = 30;
41
- /** Whether the initial batch discovery has finished. */
42
- initialDiscoveryComplete = false;
43
- /** Maps UUID → PlatformAccessory for every registered accessory (needed for hot-remove). */
44
- registeredAccessories = new Map();
45
- _hostSystem = '';
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
- catch {
170
- // Errors encountered while attempting to identify the system are ignored.
171
- // We prioritize getting system information through hints rather than comprehensive detection.
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
- this.eufyClient.on('push close', () => {
276
- log.debug('Push Closed!');
277
- });
278
- this.eufyClient.on('connect', () => {
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
- catch (error) {
405
- // Remove station or device accessories created prior to plugin upgrade,
406
- // which may have been subject to removal due to newly introduced logic.
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
- const deviceContainer = {
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
- if (num > 0) {
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
- register_station(accessory, container) {
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
- if (this.config.autoSyncStation) {
750
- new AutoSyncStationAccessory(this, accessory, station);
751
- }
752
- else {
753
- new StationAccessory(this, accessory, station);
754
- }
755
- }
756
- register_device(accessory, container) {
757
- log.debug(accessory.displayName + ' UUID:' + accessory.UUID);
758
- const device = container.eufyDevice;
759
- const type = container.deviceIdentifier.type;
760
- if (Device.isMotionSensor(type)) {
761
- log.debug(accessory.displayName + ' isMotionSensor!');
762
- new MotionSensorAccessory(this, accessory, device);
763
- }
764
- if (Device.isEntrySensor(type)) {
765
- log.debug(accessory.displayName + ' isEntrySensor!');
766
- new EntrySensorAccessory(this, accessory, device);
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