@smarthome.exchange/integrations 1.0.0 → 2.0.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 (294) hide show
  1. package/changelog.md +49 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/classes.configstore.d.ts +2 -0
  4. package/dist_ts/core/classes.configstore.js +84 -27
  5. package/dist_ts/core/classes.nativehomeadmission.d.ts +19 -0
  6. package/dist_ts/core/classes.nativehomeadmission.js +84 -0
  7. package/dist_ts/core/classes.nativeintegrationowner.d.ts +79 -0
  8. package/dist_ts/core/classes.nativeintegrationowner.js +269 -0
  9. package/dist_ts/core/index.d.ts +2 -0
  10. package/dist_ts/core/index.js +3 -1
  11. package/dist_ts/core/types.d.ts +14 -0
  12. package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
  13. package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
  14. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
  15. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
  16. package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
  17. package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
  18. package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
  19. package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
  20. package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
  21. package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
  22. package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
  23. package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
  24. package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
  25. package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
  26. package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
  27. package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
  28. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
  29. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
  30. package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
  31. package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
  32. package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
  33. package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
  34. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
  35. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
  36. package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
  37. package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
  38. package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
  39. package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
  40. package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
  41. package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
  42. package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
  43. package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
  44. package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
  45. package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
  46. package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
  47. package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
  48. package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
  49. package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
  50. package/dist_ts/integrations/daikin/daikin.types.js +1 -1
  51. package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
  52. package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
  53. package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
  54. package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
  55. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
  56. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
  57. package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
  58. package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
  59. package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
  60. package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
  61. package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
  62. package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
  63. package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
  64. package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
  65. package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
  66. package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
  67. package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
  68. package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
  69. package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
  70. package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
  71. package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
  72. package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
  73. package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
  74. package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
  75. package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
  76. package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
  77. package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
  78. package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
  79. package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
  80. package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
  81. package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
  82. package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
  83. package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
  84. package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
  85. package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
  86. package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
  87. package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
  88. package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
  89. package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
  90. package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
  91. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
  92. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
  93. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
  94. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
  95. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
  96. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
  97. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
  98. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
  99. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
  100. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
  101. package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
  102. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
  103. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
  104. package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
  105. package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
  106. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
  107. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
  108. package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
  109. package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
  110. package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
  111. package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
  112. package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
  113. package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
  114. package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
  115. package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
  116. package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
  117. package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
  118. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
  119. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
  120. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
  121. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
  122. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
  123. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
  124. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
  125. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
  126. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
  127. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
  128. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
  129. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
  130. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
  131. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
  132. package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
  133. package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
  134. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
  135. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
  136. package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
  137. package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
  138. package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
  139. package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
  140. package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
  141. package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
  142. package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
  143. package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
  144. package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
  145. package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
  146. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
  147. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
  148. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
  149. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
  150. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
  151. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
  152. package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
  153. package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
  154. package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
  155. package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
  156. package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
  157. package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
  158. package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
  159. package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
  160. package/dist_ts/integrations/wiz/index.d.ts +1 -0
  161. package/dist_ts/integrations/wiz/index.js +2 -1
  162. package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
  163. package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
  164. package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
  165. package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
  166. package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
  167. package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
  168. package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
  169. package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
  170. package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
  171. package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
  172. package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
  173. package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
  174. package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
  175. package/dist_ts/integrations/yamaha/index.d.ts +1 -0
  176. package/dist_ts/integrations/yamaha/index.js +2 -1
  177. package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
  178. package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
  179. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
  180. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
  181. package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
  182. package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
  183. package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
  184. package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
  185. package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
  186. package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
  187. package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
  188. package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
  189. package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
  190. package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
  191. package/package.json +3 -3
  192. package/readme.md +514 -2
  193. package/ts/00_commitinfo_data.ts +1 -1
  194. package/ts/core/classes.configstore.ts +84 -27
  195. package/ts/core/classes.nativehomeadmission.ts +96 -0
  196. package/ts/core/classes.nativeintegrationowner.ts +297 -0
  197. package/ts/core/index.ts +2 -0
  198. package/ts/core/types.ts +16 -0
  199. package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
  200. package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
  201. package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
  202. package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
  203. package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
  204. package/ts/integrations/braviatv/braviatv.types.ts +53 -173
  205. package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
  206. package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
  207. package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
  208. package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
  209. package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
  210. package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
  211. package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
  212. package/ts/integrations/broadlink/broadlink.types.ts +48 -8
  213. package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
  214. package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
  215. package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
  216. package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
  217. package/ts/integrations/daikin/daikin.discovery.ts +94 -346
  218. package/ts/integrations/daikin/daikin.mapper.ts +83 -166
  219. package/ts/integrations/daikin/daikin.types.ts +27 -48
  220. package/ts/integrations/daikin/daikin.validation.ts +51 -0
  221. package/ts/integrations/daikin/readme.md +83 -0
  222. package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
  223. package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
  224. package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
  225. package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
  226. package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
  227. package/ts/integrations/denonavr/denonavr.types.ts +48 -111
  228. package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
  229. package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
  230. package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
  231. package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
  232. package/ts/integrations/kodi/kodi.discovery.ts +49 -28
  233. package/ts/integrations/kodi/kodi.mapper.ts +52 -10
  234. package/ts/integrations/kodi/kodi.types.ts +33 -58
  235. package/ts/integrations/kodi/kodi.validation.ts +60 -0
  236. package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
  237. package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
  238. package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
  239. package/ts/integrations/modbus/modbus.discovery.ts +38 -106
  240. package/ts/integrations/modbus/modbus.mapper.ts +64 -467
  241. package/ts/integrations/modbus/modbus.types.ts +64 -381
  242. package/ts/integrations/modbus/modbus.validation.ts +119 -0
  243. package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
  244. package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
  245. package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
  246. package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
  247. package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
  248. package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
  249. package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
  250. package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
  251. package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
  252. package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
  253. package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
  254. package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
  255. package/ts/integrations/onkyo/onkyo.types.ts +55 -110
  256. package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
  257. package/ts/integrations/onkyo/readme.md +126 -0
  258. package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
  259. package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
  260. package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
  261. package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
  262. package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
  263. package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
  264. package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
  265. package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
  266. package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
  267. package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
  268. package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
  269. package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
  270. package/ts/integrations/pioneer/pioneer.types.ts +87 -85
  271. package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
  272. package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
  273. package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
  274. package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
  275. package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
  276. package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
  277. package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
  278. package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
  279. package/ts/integrations/wiz/index.ts +1 -0
  280. package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
  281. package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
  282. package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
  283. package/ts/integrations/wiz/wiz.discovery.ts +48 -291
  284. package/ts/integrations/wiz/wiz.mapper.ts +81 -754
  285. package/ts/integrations/wiz/wiz.types.ts +24 -224
  286. package/ts/integrations/wiz/wiz.validation.ts +73 -0
  287. package/ts/integrations/yamaha/index.ts +1 -0
  288. package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
  289. package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
  290. package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
  291. package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
  292. package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
  293. package/ts/integrations/yamaha/yamaha.types.ts +77 -97
  294. package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
@@ -1,80 +1,224 @@
1
- import type * as shxInterfaces from '@smarthome.exchange/interfaces';
1
+ import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
- import type { IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
3
+ import { ConsoleLogger } from '../../core/classes.logger.js';
4
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
5
+ import type { IDeviceFeatureWriteRequest, ILogger, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime,
6
+ IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
4
7
  import { WizClient } from './wiz.classes.client.js';
5
8
  import { WizConfigFlow } from './wiz.classes.configflow.js';
6
9
  import { createWizDiscoveryDescriptor } from './wiz.discovery.js';
7
10
  import { WizMapper } from './wiz.mapper.js';
8
- import type { IWizConfig } from './wiz.types.js';
11
+ import type { IWizConfig, IWizSnapshot, IWizWriteResult, TWizControl } from './wiz.types.js';
12
+ import { normalizeWizConfig } from './wiz.validation.js';
13
+
14
+ export interface IWizIntegrationOptions {
15
+ readonly homeId?: string;
16
+ readonly capacity?: number;
17
+ }
9
18
 
10
19
  export class WizIntegration extends BaseIntegration<IWizConfig> {
11
20
  public readonly domain = 'wiz';
12
21
  public readonly displayName = 'WiZ';
13
22
  public readonly status = 'control-runtime' as const;
14
23
  public readonly discoveryDescriptor = createWizDiscoveryDescriptor();
15
- public readonly configFlow = new WizConfigFlow();
24
+ public readonly configFlow: WizConfigFlow;
16
25
  public readonly metadata = {
17
- source: 'home-assistant/core',
18
- upstreamPath: 'homeassistant/components/wiz',
19
- upstreamDomain: 'wiz',
20
- integrationType: 'device',
21
- iotClass: 'local_push',
22
- requirements: ['pywizlight==0.6.3'],
23
- dependencies: ['network'],
24
- afterDependencies: [] as string[],
25
- codeowners: ['@sbidy', '@arturpragacz'],
26
- documentation: 'https://www.home-assistant.io/integrations/wiz',
27
- protocolDocumentation: 'https://docs.pro.wizconnected.com/#introduction',
28
- dhcp: [
29
- { registeredDevices: true },
30
- { macaddress: 'A8BB50*' },
31
- { macaddress: 'D8A011*' },
32
- { macaddress: '444F8E*' },
33
- { macaddress: '6C2990*' },
34
- { hostname: 'wiz_*' },
35
- ],
26
+ source: '@ecobridge.xyz/devicemanager', documentation: 'https://www.home-assistant.io/integrations/wiz',
27
+ integrationType: 'device', iotClass: 'local_polling', networkAuthenticated: false,
28
+ supportedControls: ['explicit power', 'observed brightness', 'observed RGB'],
36
29
  };
30
+ readonly #owner: NativeIntegrationOwner;
31
+
32
+ constructor(optionsArg: IWizIntegrationOptions = {}) {
33
+ super();
34
+ this.#owner = new NativeIntegrationOwner('wiz', optionsArg);
35
+ this.configFlow = new WizConfigFlow(this.#owner);
36
+ }
37
37
 
38
38
  public async setup(configArg: IWizConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
39
- void contextArg;
40
- return new WizRuntime(new WizClient(configArg));
39
+ const config = normalizeWizConfig(configArg);
40
+ return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
41
+ const client = new WizClient(config, { ...leaseArg.context, abortSignal: leaseArg.signal });
42
+ let runtime: WizRuntime | undefined;
43
+ let cleanupAttempts = 0;
44
+ const closeClient = (): Promise<void> => cleanupAttempts++ === 0 ? client.destroy() : client.retryCleanup();
45
+ leaseArg.own(() => runtime ? runtime.cleanup() : closeClient());
46
+ const snapshot = await client.getSnapshot(leaseArg.signal);
47
+ leaseArg.assertOpen();
48
+ runtime = new WizRuntime(client, snapshot, leaseArg.context.logger, leaseArg, closeClient);
49
+ leaseArg.promoteToRuntime();
50
+ return runtime;
51
+ });
41
52
  }
42
53
 
43
- public async destroy(): Promise<void> {}
54
+ public destroy(): Promise<void> { return this.#owner.close(); }
55
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
44
56
  }
45
57
 
46
58
  export class HomeAssistantWizIntegration extends WizIntegration {}
47
59
 
60
+ interface IWizSubscription {
61
+ handler(eventArg: IIntegrationEvent): void;
62
+ release(): void;
63
+ }
64
+
48
65
  class WizRuntime implements IIntegrationRuntime {
49
- public domain = 'wiz';
66
+ public readonly domain = 'wiz';
67
+ readonly #subscriptions = new Set<IWizSubscription>();
68
+ readonly #client: WizClient;
69
+ readonly #logger: ILogger;
70
+ readonly #lease: INativeSetupLease;
71
+ readonly #closeClient: () => Promise<void>;
72
+ readonly #ownerAbort = (): void => { this.markClosed(); };
73
+ #snapshot: IWizSnapshot;
74
+ #timer?: ReturnType<typeof setTimeout>;
75
+ #poll?: Promise<void>;
76
+ #pollController?: AbortController;
77
+ #closed = false;
50
78
 
51
- constructor(private readonly client: WizClient) {}
79
+ constructor(clientArg: WizClient, snapshotArg: IWizSnapshot, loggerArg: ILogger | undefined, leaseArg: INativeSetupLease, closeClientArg: () => Promise<void>) {
80
+ this.#client = clientArg; this.#snapshot = snapshotArg; this.#logger = loggerArg ?? new ConsoleLogger();
81
+ this.#lease = leaseArg; this.#closeClient = closeClientArg;
82
+ leaseArg.signal.addEventListener('abort', this.#ownerAbort, { once: true });
83
+ if (leaseArg.signal.aborted) this.#ownerAbort();
84
+ }
52
85
 
53
- public async devices(): Promise<shxInterfaces.data.IDeviceDefinition[]> {
54
- return WizMapper.toDevices(await this.client.getSnapshot());
86
+ public isHealthy(): boolean { return !this.#closed && this.#client.isHealthy(); }
87
+ public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> { return WizMapper.toDevices(await this.refresh(signalArg)); }
88
+ public async entities(): Promise<IIntegrationEntity[]> { return WizMapper.toEntities(await this.refresh()); }
89
+
90
+ public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
91
+ if (this.#closed) throw new Error('WiZ runtime is destroyed.');
92
+ if (typeof handlerArg !== 'function') throw new TypeError('WiZ subscription requires a handler.');
93
+ signalArg?.throwIfAborted();
94
+ const subscription: IWizSubscription = {
95
+ handler: handlerArg,
96
+ release: () => {
97
+ this.#subscriptions.delete(subscription);
98
+ signalArg?.removeEventListener('abort', subscription.release);
99
+ if (!this.#subscriptions.size) this.stopPolling();
100
+ },
101
+ };
102
+ try {
103
+ const snapshot = await this.#client.getSnapshot(signalArg);
104
+ if (this.#closed) throw new Error('WiZ runtime is destroyed.');
105
+ signalArg?.throwIfAborted();
106
+ this.#snapshot = snapshot;
107
+ handlerArg(this.observation(snapshot));
108
+ if (this.#closed) throw new Error('WiZ runtime was destroyed during initial subscription delivery.');
109
+ signalArg?.throwIfAborted();
110
+ this.#subscriptions.add(subscription);
111
+ signalArg?.addEventListener('abort', subscription.release, { once: true });
112
+ if (signalArg?.aborted) subscription.release();
113
+ this.schedulePolling();
114
+ } catch (errorArg) { subscription.release(); throw errorArg; }
115
+ return async () => {
116
+ subscription.release();
117
+ if (!this.#subscriptions.size) await this.#poll;
118
+ };
55
119
  }
56
120
 
57
- public async entities(): Promise<IIntegrationEntity[]> {
58
- return WizMapper.toEntities(await this.client.getSnapshot());
121
+ public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
122
+ let control: TWizControl;
123
+ try { control = WizMapper.controlForService(this.#snapshot, requestArg); }
124
+ catch (errorArg) { return this.admissionFailure(errorArg); }
125
+ return this.write(control);
59
126
  }
60
127
 
61
- public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void): Promise<() => Promise<void>> {
62
- const unsubscribe = this.client.onEvent((eventArg) => handlerArg(WizMapper.toIntegrationEvent(eventArg)));
63
- await this.client.getSnapshot();
64
- return async () => unsubscribe();
128
+ public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
129
+ let control: TWizControl;
130
+ try { control = WizMapper.controlForFeature(this.#snapshot, requestArg); }
131
+ catch (errorArg) { return this.admissionFailure(errorArg); }
132
+ return this.write(control, signalArg);
65
133
  }
66
134
 
67
- public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
68
- const snapshot = await this.client.getSnapshot();
69
- const command = WizMapper.commandForService(snapshot, requestArg);
70
- if (!command) {
71
- return { success: false, error: `WiZ service ${requestArg.domain}.${requestArg.service} has no native setPilot mapping for the target.` };
135
+ public destroy(): Promise<void> {
136
+ this.markClosed();
137
+ return this.#lease.close();
138
+ }
139
+
140
+ /** Called only by the owning lease, including explicit retained-cleanup retry. */
141
+ public async cleanup(): Promise<void> {
142
+ this.markClosed();
143
+ const results = await Promise.allSettled([Promise.resolve().then(this.#closeClient), this.#poll]);
144
+ const errors = results.flatMap((resultArg) => resultArg.status === 'rejected' ? [resultArg.reason] : []);
145
+ if (errors.length) throw new AggregateError(errors, 'WiZ runtime cleanup failed after draining polling.');
146
+ }
147
+
148
+ private markClosed(): void {
149
+ if (this.#closed) return;
150
+ this.#closed = true;
151
+ this.#lease.signal.removeEventListener('abort', this.#ownerAbort);
152
+ for (const subscription of [...this.#subscriptions]) subscription.release();
153
+ this.stopPolling();
154
+ }
155
+
156
+ private async refresh(signalArg?: AbortSignal): Promise<IWizSnapshot> {
157
+ if (this.#closed) throw new Error('WiZ runtime is destroyed.');
158
+ const snapshot = await this.#client.getSnapshot(signalArg);
159
+ if (this.#closed) throw new Error('WiZ runtime is destroyed.');
160
+ this.#snapshot = snapshot;
161
+ this.notify(this.observation(snapshot));
162
+ return snapshot;
163
+ }
164
+
165
+ private async write(controlArg: TWizControl, signalArg?: AbortSignal): Promise<IServiceCallResult> {
166
+ if (this.#closed) return this.admissionFailure(new Error('WiZ runtime is destroyed.'));
167
+ const data = await this.#client.write(controlArg, signalArg);
168
+ if (data.observedState && !this.#closed) {
169
+ this.#snapshot = Object.freeze({ ...this.#snapshot, observedAt: new Date().toISOString(),
170
+ info: Object.freeze({ ...this.#snapshot.info, observedState: data.observedState }) });
171
+ this.notify(this.observation(this.#snapshot));
72
172
  }
73
- const result = await this.client.sendCommand(command);
74
- return { success: result.success, error: result.error, data: result.data };
173
+ return this.writeResult(data);
174
+ }
175
+
176
+ private writeResult(dataArg: IWizWriteResult): IServiceCallResult {
177
+ return { success: dataArg.outcome === 'accepted' && dataArg.errorCode === undefined, data: dataArg,
178
+ error: dataArg.outcome === 'accepted' && dataArg.errorCode === undefined ? undefined : dataArg.outcome === 'accepted' ?
179
+ 'WiZ firmware accepted the operation, but observed readback failed; do not replay it.' :
180
+ `WiZ operation outcome is ${dataArg.outcome}; automatic replay is prohibited.` };
181
+ }
182
+
183
+ private admissionFailure(errorArg: unknown): IServiceCallResult {
184
+ return { success: false, error: errorArg instanceof Error ? errorArg.message : 'WiZ admission failed.',
185
+ data: { outcome: 'not-sent', physicalStateConfirmed: false } };
186
+ }
187
+
188
+ private observation(snapshotArg: IWizSnapshot): IIntegrationEvent {
189
+ return { type: 'state_changed', integrationDomain: 'wiz', deviceId: WizMapper.deviceId(snapshotArg),
190
+ entityId: WizMapper.entityId(snapshotArg), timestamp: Date.parse(snapshotArg.observedAt),
191
+ data: { device: WizMapper.toDevices(snapshotArg)[0], entity: WizMapper.toEntities(snapshotArg)[0] } };
192
+ }
193
+
194
+ private notify(eventArg: IIntegrationEvent): void {
195
+ for (const subscription of [...this.#subscriptions]) {
196
+ try { subscription.handler(eventArg); }
197
+ catch { subscription.release(); this.#logger.log('error', 'WiZ subscription handler failed and was removed.'); }
198
+ }
199
+ }
200
+
201
+ private schedulePolling(): void {
202
+ if (this.#closed || this.#timer || this.#poll || !this.#subscriptions.size || !this.#client.pollIntervalMs) return;
203
+ this.#timer = setTimeout(() => {
204
+ this.#timer = undefined;
205
+ const controller = new AbortController();
206
+ this.#pollController = controller;
207
+ this.#poll = this.refresh(controller.signal).then(() => undefined).catch((errorArg: unknown) => {
208
+ if (controller.signal.aborted || this.#closed) return;
209
+ this.notify({ type: 'availability_changed', integrationDomain: 'wiz', deviceId: WizMapper.deviceId(this.#snapshot),
210
+ timestamp: Date.now(), data: { available: false } });
211
+ this.#logger.log('error', 'WiZ polling did not obtain an authoritative observation.');
212
+ if (errorArg instanceof plugins.ecobridgeDeviceManager.WizProtocolError && errorArg.code === 'EXCHANGE_LIMIT') void this.destroy();
213
+ }).finally(() => {
214
+ this.#poll = undefined; this.#pollController = undefined; this.schedulePolling();
215
+ });
216
+ }, this.#client.pollIntervalMs);
75
217
  }
76
218
 
77
- public async destroy(): Promise<void> {
78
- await this.client.destroy();
219
+ private stopPolling(): void {
220
+ if (this.#timer) clearTimeout(this.#timer);
221
+ this.#timer = undefined;
222
+ this.#pollController?.abort(new Error('WiZ subscription polling stopped.'));
79
223
  }
80
224
  }
@@ -1,314 +1,71 @@
1
1
  import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
2
- import type {
3
- IDiscoveryCandidate,
4
- IDiscoveryContext,
5
- IDiscoveryMatch,
6
- IDiscoveryMatcher,
7
- IDiscoveryProbe,
8
- IDiscoveryProbeResult,
9
- IDiscoveryValidator,
10
- } from '../../core/types.js';
11
- import type { IWizManualEntry, IWizMdnsRecord, IWizUdpDiscoveryRecord, IWizUdpResponse } from './wiz.types.js';
12
- import { wizDefaultPort } from './wiz.types.js';
13
-
14
- const wizMacPrefixes = ['a8bb50', 'd8a011', '444f8e', '6c2990'];
15
- const wizUdpMethods = new Set(['getPilot', 'setPilot', 'syncPilot', 'syncSystemConfig', 'getSystemConfig', 'registration']);
16
-
17
- export class WizUdpDiscoveryProbe implements IDiscoveryProbe {
18
- public id = 'wiz-udp-discovery-probe';
19
- public source = 'custom' as const;
20
- public description = 'Discover WiZ devices by UDP registration broadcast on port 38899.';
21
-
22
- public async probe(contextArg: IDiscoveryContext): Promise<IDiscoveryProbeResult> {
23
- if (contextArg.abortSignal?.aborted) {
24
- return { candidates: [] };
25
- }
26
- return { candidates: await this.discover(1200) };
27
- }
28
-
29
- private async discover(timeoutMsArg: number): Promise<IDiscoveryCandidate[]> {
30
- const { createSocket } = await import('node:dgram');
31
- const message = Buffer.from(JSON.stringify({
32
- method: 'registration',
33
- params: {
34
- phoneMac: 'AAAAAAAAAAAA',
35
- register: false,
36
- phoneIp: '1.2.3.4',
37
- id: '1',
2
+ import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
3
+ import type { IWizManualEntry, IWizMdnsRecord, IWizUdpDiscoveryRecord } from './wiz.types.js';
4
+ import { wizAddress, wizInteger, wizMac, wizRecord, wizText } from './wiz.validation.js';
5
+
6
+ function hint(hostArg: unknown, macArg: unknown, portArg: unknown, nameArg: unknown, sourceArg: IDiscoveryCandidate['source']): IDiscoveryMatch {
7
+ try {
8
+ const host = wizAddress(hostArg);
9
+ const macAddress = wizMac(macArg);
10
+ return {
11
+ matched: true, confidence: 'medium', reason: 'Selected WiZ endpoint and MAC are unauthenticated setup hints; live admission is required.',
12
+ normalizedDeviceId: macAddress, candidate: {
13
+ source: sourceArg, integrationDomain: 'wiz', id: macAddress, host, macAddress,
14
+ port: wizInteger(portArg, 38899, 1, 65535), name: nameArg === undefined ? 'WiZ' : wizText(nameArg, 'name', 128),
15
+ manufacturer: 'WiZ', metadata: { authenticated: false },
38
16
  },
39
- }));
40
- const matcher = new WizUdpMatcher();
41
- const candidates: IDiscoveryCandidate[] = [];
42
-
43
- return new Promise((resolve) => {
44
- const socket = createSocket({ type: 'udp4', reuseAddr: true });
45
- const timer = setTimeout(() => {
46
- try {
47
- socket.close();
48
- } catch {
49
- // The discovery socket may already be closed after an OS error.
50
- }
51
- resolve(candidates);
52
- }, timeoutMsArg);
53
-
54
- socket.on('message', async (dataArg, remoteArg) => {
55
- let response: IWizUdpResponse<Record<string, unknown>> | undefined;
56
- try {
57
- response = JSON.parse(dataArg.toString('utf8')) as IWizUdpResponse<Record<string, unknown>>;
58
- } catch {
59
- return;
60
- }
61
- const match = await matcher.matches({ host: remoteArg.address, port: remoteArg.port, response });
62
- if (match.matched && match.candidate && !candidates.some((candidateArg) => candidateArg.id === match.candidate?.id || candidateArg.host === match.candidate?.host)) {
63
- candidates.push(match.candidate);
64
- }
65
- });
66
- socket.on('error', () => {
67
- clearTimeout(timer);
68
- try {
69
- socket.close();
70
- } catch {
71
- // Ignore discovery socket close races.
72
- }
73
- resolve(candidates);
74
- });
75
- socket.bind(() => {
76
- socket.setBroadcast(true);
77
- socket.send(message, wizDefaultPort, '255.255.255.255');
78
- setTimeout(() => socket.send(message, wizDefaultPort, '255.255.255.255'), 500);
79
- });
80
- });
81
- }
17
+ };
18
+ } catch { return { matched: false, confidence: 'low', reason: 'WiZ hints require an explicit local IPv4 endpoint and unicast MAC.' }; }
82
19
  }
83
20
 
84
21
  export class WizMdnsMatcher implements IDiscoveryMatcher<IWizMdnsRecord> {
85
- public id = 'wiz-mdns-match';
86
- public source = 'mdns' as const;
87
- public description = 'Recognize WiZ mDNS or hostname advertisements.';
88
-
22
+ public readonly id = 'wiz-mdns-match';
23
+ public readonly source = 'mdns' as const;
89
24
  public async matches(recordArg: IWizMdnsRecord): Promise<IDiscoveryMatch> {
90
- const txt = recordArg.txt || recordArg.properties || {};
91
- const type = this.normalizeType(recordArg.type);
92
- const host = recordArg.host || recordArg.hostname || recordArg.addresses?.[0];
93
- const mac = this.normalizeMac(this.txt(txt, 'mac') || this.txt(txt, 'macaddress') || this.txt(txt, 'mac_address'));
94
- const name = this.name(recordArg, txt);
95
- const text = [type, recordArg.name, recordArg.hostname, host, name, this.txt(txt, 'manufacturer'), this.txt(txt, 'model')]
96
- .filter((valueArg): valueArg is string => typeof valueArg === 'string')
97
- .join(' ')
98
- .toLowerCase();
99
- const matched = Boolean(mac && this.isWizMac(mac)) || /(^|[._-])wiz([._-]|$)/i.test(text) || text.includes('wizconnected');
100
- if (!matched) {
101
- return { matched: false, confidence: 'low', reason: 'mDNS record is not a WiZ advertisement.' };
102
- }
103
- return {
104
- matched: true,
105
- confidence: mac ? 'certain' : host ? 'high' : 'medium',
106
- reason: mac ? 'mDNS record contains a WiZ MAC address.' : 'mDNS record contains WiZ hostname or TXT metadata.',
107
- normalizedDeviceId: mac || recordArg.name || host,
108
- candidate: {
109
- source: 'mdns',
110
- integrationDomain: 'wiz',
111
- id: mac || recordArg.name || host,
112
- host,
113
- port: recordArg.port || wizDefaultPort,
114
- name: name || 'WiZ',
115
- manufacturer: 'WiZ',
116
- model: this.txt(txt, 'model') || this.txt(txt, 'moduleName') || 'WiZ device',
117
- macAddress: mac,
118
- metadata: {
119
- mdnsName: recordArg.name,
120
- mdnsType: recordArg.type,
121
- txt,
122
- },
123
- },
124
- };
125
- }
126
-
127
- private txt(txtArg: Record<string, string | undefined>, keyArg: string): string | undefined {
128
- return txtArg[keyArg] || txtArg[keyArg.toUpperCase()];
129
- }
130
-
131
- private name(recordArg: IWizMdnsRecord, txtArg: Record<string, string | undefined>): string | undefined {
132
- return this.txt(txtArg, 'name') || this.txt(txtArg, 'friendly_name') || recordArg.name?.split('._', 1)[0] || recordArg.hostname?.replace(/\.local\.?$/i, '');
133
- }
134
-
135
- private normalizeType(valueArg?: string): string {
136
- return (valueArg || '').toLowerCase().replace(/\.$/, '');
137
- }
138
-
139
- private normalizeMac(valueArg?: string): string | undefined {
140
- if (!valueArg) {
141
- return undefined;
142
- }
143
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
144
- return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
145
- }
146
-
147
- private isWizMac(valueArg: string): boolean {
148
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
149
- return wizMacPrefixes.some((prefixArg) => compact.startsWith(prefixArg));
25
+ try {
26
+ const value = wizRecord(recordArg, ['type', 'name', 'host', 'hostname', 'addresses', 'port', 'txt', 'properties']);
27
+ const txt = wizRecord(value.txt ?? value.properties ?? {});
28
+ return hint(value.host, txt.mac ?? txt.macaddress ?? txt.mac_address, value.port, value.name, 'mdns');
29
+ } catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ mDNS hint.' }; }
150
30
  }
151
31
  }
152
32
 
153
33
  export class WizUdpMatcher implements IDiscoveryMatcher<IWizUdpDiscoveryRecord> {
154
- public id = 'wiz-udp-match';
155
- public source = 'custom' as const;
156
- public description = 'Recognize WiZ UDP JSON discovery responses.';
157
-
34
+ public readonly id = 'wiz-udp-match';
35
+ public readonly source = 'custom' as const;
158
36
  public async matches(recordArg: IWizUdpDiscoveryRecord): Promise<IDiscoveryMatch> {
159
- const response = recordArg.response;
160
- const result = response?.result || recordArg.result || {};
161
- const method = response?.method || recordArg.method;
162
- const host = recordArg.host || recordArg.address || recordArg.ip || recordArg.ip_address;
163
- const mac = this.normalizeMac(recordArg.mac || recordArg.mac_address || this.stringValue(result.mac));
164
- const matched = Boolean(mac) || Boolean(method && wizUdpMethods.has(method)) || recordArg.metadata?.wiz === true;
165
- if (!matched) {
166
- return { matched: false, confidence: 'low', reason: 'UDP record is not a WiZ JSON response.' };
167
- }
168
- return {
169
- matched: true,
170
- confidence: mac && host ? 'certain' : mac || host ? 'high' : 'medium',
171
- reason: mac ? 'UDP response contains a WiZ MAC address.' : 'UDP response method matches WiZ JSON protocol.',
172
- normalizedDeviceId: mac || recordArg.name || host,
173
- candidate: {
174
- source: 'custom',
175
- integrationDomain: 'wiz',
176
- id: mac || recordArg.name || host,
177
- host,
178
- port: recordArg.port || wizDefaultPort,
179
- name: recordArg.name || (mac ? `WiZ ${this.shortMac(mac)}` : 'WiZ'),
180
- manufacturer: 'WiZ',
181
- model: 'WiZ UDP JSON device',
182
- macAddress: mac,
183
- metadata: {
184
- ...recordArg.metadata,
185
- discoveryProtocol: 'udp',
186
- method,
187
- result,
188
- },
189
- },
190
- };
191
- }
192
-
193
- private stringValue(valueArg: unknown): string | undefined {
194
- return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
195
- }
196
-
197
- private shortMac(valueArg?: string): string {
198
- return (valueArg || '').replace(/[^a-fA-F0-9]/g, '').slice(-6).toUpperCase();
199
- }
200
-
201
- private normalizeMac(valueArg?: string): string | undefined {
202
- if (!valueArg) {
203
- return undefined;
204
- }
205
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
206
- return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
37
+ try {
38
+ const value = wizRecord(recordArg, ['host', 'port', 'method', 'result', 'response']);
39
+ const response = value.response === undefined ? value : wizRecord(value.response, ['method', 'result']);
40
+ if (typeof response.method !== 'string' || !['getPilot', 'getSystemConfig', 'registration'].includes(response.method)) throw new TypeError('Unsupported WiZ hint method.');
41
+ const result = wizRecord(response.result);
42
+ return hint(value.host, result.mac, value.port, undefined, 'custom');
43
+ } catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ UDP hint.' }; }
207
44
  }
208
45
  }
209
46
 
210
47
  export class WizManualMatcher implements IDiscoveryMatcher<IWizManualEntry> {
211
- public id = 'wiz-manual-match';
212
- public source = 'manual' as const;
213
- public description = 'Recognize manual WiZ setup entries.';
214
-
215
- public async matches(inputArg: IWizManualEntry): Promise<IDiscoveryMatch> {
216
- const mac = this.normalizeMac(inputArg.mac || inputArg.macAddress || inputArg.deviceInfo?.mac);
217
- const text = [inputArg.name, inputArg.manufacturer, inputArg.model, inputArg.deviceInfo?.manufacturer, inputArg.deviceInfo?.model, inputArg.deviceInfo?.moduleName]
218
- .filter((valueArg): valueArg is string => typeof valueArg === 'string')
219
- .join(' ')
220
- .toLowerCase();
221
- const matched = Boolean(inputArg.host || mac || inputArg.metadata?.wiz || text.includes('wiz'));
222
- if (!matched) {
223
- return { matched: false, confidence: 'low', reason: 'Manual entry does not contain WiZ setup hints.' };
224
- }
225
- return {
226
- matched: true,
227
- confidence: inputArg.host && mac ? 'certain' : inputArg.host || mac ? 'high' : 'medium',
228
- reason: 'Manual entry can start WiZ setup.',
229
- normalizedDeviceId: mac || inputArg.id || inputArg.host,
230
- candidate: {
231
- source: 'manual',
232
- integrationDomain: 'wiz',
233
- id: inputArg.id || mac || inputArg.host,
234
- host: inputArg.host,
235
- port: inputArg.port || wizDefaultPort,
236
- name: inputArg.name || inputArg.deviceInfo?.name || (mac ? `WiZ ${this.shortMac(mac)}` : 'WiZ'),
237
- manufacturer: inputArg.manufacturer || inputArg.deviceInfo?.manufacturer || 'WiZ',
238
- model: inputArg.model || inputArg.deviceInfo?.model || inputArg.deviceInfo?.moduleName || 'WiZ device',
239
- macAddress: mac,
240
- metadata: {
241
- ...inputArg.metadata,
242
- discoveryProtocol: 'manual',
243
- deviceInfo: inputArg.deviceInfo,
244
- pilot: inputArg.pilot,
245
- snapshot: inputArg.snapshot,
246
- },
247
- },
248
- };
249
- }
250
-
251
- private shortMac(valueArg?: string): string {
252
- return (valueArg || '').replace(/[^a-fA-F0-9]/g, '').slice(-6).toUpperCase();
253
- }
254
-
255
- private normalizeMac(valueArg?: string): string | undefined {
256
- if (!valueArg) {
257
- return undefined;
258
- }
259
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
260
- return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
48
+ public readonly id = 'wiz-manual-match';
49
+ public readonly source = 'manual' as const;
50
+ public async matches(recordArg: IWizManualEntry): Promise<IDiscoveryMatch> {
51
+ try {
52
+ const value = wizRecord(recordArg, ['host', 'port', 'macAddress', 'name']);
53
+ return hint(value.host, value.macAddress, value.port, value.name, 'manual');
54
+ } catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ manual hint.' }; }
261
55
  }
262
56
  }
263
57
 
264
58
  export class WizCandidateValidator implements IDiscoveryValidator {
265
- public id = 'wiz-candidate-validator';
266
- public description = 'Validate WiZ candidates from mDNS, UDP, DHCP, and manual setup.';
267
-
59
+ public readonly id = 'wiz-candidate-validator';
268
60
  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
269
- const metadata = candidateArg.metadata || {};
270
- const mac = this.normalizeMac(candidateArg.macAddress);
271
- const compactMac = (mac || '').replace(/[^a-fA-F0-9]/g, '').toLowerCase();
272
- const name = (candidateArg.name || '').toLowerCase();
273
- const model = (candidateArg.model || '').toLowerCase();
274
- const manufacturer = (candidateArg.manufacturer || '').toLowerCase();
275
- const mdnsType = typeof metadata.mdnsType === 'string' ? metadata.mdnsType.toLowerCase() : '';
276
- const discoveryProtocol = typeof metadata.discoveryProtocol === 'string' ? metadata.discoveryProtocol : undefined;
277
- const macMatched = wizMacPrefixes.some((prefixArg) => compactMac.startsWith(prefixArg));
278
- const hostMatched = Boolean(candidateArg.host && /^wiz[_-]/i.test(candidateArg.host));
279
- const textMatched = name.includes('wiz') || model.includes('wiz') || manufacturer.includes('wiz') || mdnsType.includes('wiz');
280
- const matched = candidateArg.integrationDomain === 'wiz'
281
- || macMatched
282
- || hostMatched
283
- || textMatched
284
- || candidateArg.port === wizDefaultPort
285
- || metadata.wiz === true
286
- || discoveryProtocol === 'udp'
287
- || discoveryProtocol === 'manual';
288
- return {
289
- matched,
290
- confidence: matched && (candidateArg.integrationDomain === 'wiz' || macMatched) && candidateArg.host ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
291
- reason: matched ? 'Candidate has WiZ metadata or local UDP port information.' : 'Candidate is not WiZ.',
292
- candidate: matched ? candidateArg : undefined,
293
- normalizedDeviceId: mac || candidateArg.id || candidateArg.host,
294
- metadata: matched ? { macMatched, hostMatched, discoveryProtocol } : undefined,
295
- };
296
- }
297
-
298
- private normalizeMac(valueArg?: string): string | undefined {
299
- if (!valueArg) {
300
- return undefined;
301
- }
302
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
303
- return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
61
+ try {
62
+ const candidate = wizRecord(candidateArg);
63
+ if (candidate.integrationDomain !== undefined && candidate.integrationDomain !== 'wiz') return { matched: false, confidence: 'low', reason: 'Candidate belongs to another integration.' };
64
+ return hint(candidate.host, candidate.macAddress, candidate.port, candidate.name, candidateArg.source);
65
+ } catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ candidate.' }; }
304
66
  }
305
67
  }
306
68
 
307
- export const createWizDiscoveryDescriptor = (): DiscoveryDescriptor => {
308
- return new DiscoveryDescriptor({ integrationDomain: 'wiz', displayName: 'WiZ' })
309
- .addProbe(new WizUdpDiscoveryProbe())
310
- .addMatcher(new WizMdnsMatcher())
311
- .addMatcher(new WizUdpMatcher())
312
- .addMatcher(new WizManualMatcher())
313
- .addValidator(new WizCandidateValidator());
314
- };
69
+ /** Discovery consumes supplied advertisements only; it never broadcasts or provisions devices. */
70
+ export const createWizDiscoveryDescriptor = (): DiscoveryDescriptor => new DiscoveryDescriptor({ integrationDomain: 'wiz', displayName: 'WiZ' })
71
+ .addMatcher(new WizMdnsMatcher()).addMatcher(new WizUdpMatcher()).addMatcher(new WizManualMatcher()).addValidator(new WizCandidateValidator());