@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,764 +1,91 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import type { IIntegrationEntity, IIntegrationEvent, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
3
- import type {
4
- IWizButtonRecord,
5
- IWizClientCommand,
6
- IWizConfig,
7
- IWizDeviceFeatures,
8
- IWizDeviceInfo,
9
- IWizEntityRecord,
10
- IWizEvent,
11
- IWizManualEntry,
12
- IWizPilotPatch,
13
- IWizPilotState,
14
- IWizSensorRecord,
15
- IWizSnapshot,
16
- IWizSnapshotDevice,
17
- } from './wiz.types.js';
18
- import { wizDefaultPort } from './wiz.types.js';
19
-
20
- const wizScenes: Array<{ id: number; name: string }> = [
21
- { id: 35, name: 'Alarm' },
22
- { id: 10, name: 'Bedtime' },
23
- { id: 29, name: 'Candlelight' },
24
- { id: 27, name: 'Christmas' },
25
- { id: 6, name: 'Cozy' },
26
- { id: 13, name: 'Cool white' },
27
- { id: 26, name: 'Club' },
28
- { id: 12, name: 'Daylight' },
29
- { id: 33, name: 'Diwali' },
30
- { id: 23, name: 'Deep dive' },
31
- { id: 22, name: 'Fall' },
32
- { id: 5, name: 'Fireplace' },
33
- { id: 7, name: 'Forest' },
34
- { id: 15, name: 'Focus' },
35
- { id: 30, name: 'Golden white' },
36
- { id: 28, name: 'Halloween' },
37
- { id: 24, name: 'Jungle' },
38
- { id: 25, name: 'Mojito' },
39
- { id: 14, name: 'Night light' },
40
- { id: 1, name: 'Ocean' },
41
- { id: 4, name: 'Party' },
42
- { id: 31, name: 'Pulse' },
43
- { id: 8, name: 'Pastel colors' },
44
- { id: 19, name: 'Plantgrowth' },
45
- { id: 2, name: 'Romance' },
46
- { id: 16, name: 'Relax' },
47
- { id: 36, name: 'Snowy sky' },
48
- { id: 3, name: 'Sunset' },
49
- { id: 20, name: 'Spring' },
50
- { id: 21, name: 'Summer' },
51
- { id: 32, name: 'Steampunk' },
52
- { id: 17, name: 'True colors' },
53
- { id: 18, name: 'TV time' },
54
- { id: 34, name: 'White' },
55
- { id: 9, name: 'Wake-up' },
56
- { id: 11, name: 'Warm white' },
57
- { id: 1000, name: 'Rhythm' },
58
- ];
59
-
60
- const wizSceneNamesById = new Map(wizScenes.map((sceneArg) => [sceneArg.id, sceneArg.name]));
61
- const wizSceneIdsByName = new Map(wizScenes.map((sceneArg) => [sceneArg.name.toLowerCase(), sceneArg.id]));
62
-
63
- const wizButtonSources: Record<string, string> = {
64
- wfa1: 'on',
65
- wfa2: 'off',
66
- wfa3: 'night',
67
- wfa8: 'decrease_brightness',
68
- wfa9: 'increase_brightness',
69
- wfa16: '1',
70
- wfa17: '2',
71
- wfa18: '3',
72
- wfa19: '4',
73
- };
74
-
75
- const pilotPatchKeys = new Set(['state', 'sceneId', 'temp', 'dimming', 'r', 'g', 'b', 'c', 'w', 'speed', 'ratio', 'fanState', 'fanMode', 'fanSpeed', 'fanRevrs']);
2
+ import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IServiceCallRequest } from '../../core/types.js';
3
+ import type { IWizSnapshot, TWizControl } from './wiz.types.js';
4
+ import { wizControl, wizRecord } from './wiz.validation.js';
76
5
 
77
6
  export class WizMapper {
78
- public static readonly sceneNames = wizScenes.map((sceneArg) => sceneArg.name);
79
-
80
- public static toSnapshot(configArg: IWizConfig, connectedArg?: boolean, eventsArg: IWizEvent[] = []): IWizSnapshot {
81
- const source = configArg.snapshot;
82
- const devices: IWizSnapshotDevice[] = [
83
- ...(source?.devices || []),
84
- ...(configArg.devices || []),
85
- ];
86
-
87
- for (const entry of configArg.manualEntries || []) {
88
- if (entry.snapshot) {
89
- devices.push(...entry.snapshot.devices);
90
- } else {
91
- devices.push(this.deviceFromManualEntry(entry));
92
- }
93
- }
94
-
95
- if (!devices.length && (configArg.host || configArg.mac || configArg.name || configArg.deviceInfo || configArg.pilot)) {
96
- devices.push({
97
- host: configArg.host,
98
- port: configArg.port || wizDefaultPort,
99
- mac: configArg.mac || configArg.deviceInfo?.mac || configArg.pilot?.mac,
100
- name: configArg.name || configArg.deviceInfo?.name,
101
- deviceInfo: configArg.deviceInfo,
102
- pilot: configArg.pilot,
103
- available: connectedArg ?? Boolean(configArg.pilot || source?.connected),
104
- });
105
- }
106
-
107
- return {
108
- connected: connectedArg ?? source?.connected ?? false,
109
- host: configArg.host || source?.host,
110
- port: configArg.port || source?.port || wizDefaultPort,
111
- devices,
112
- events: [...(source?.events || []), ...(configArg.events || []), ...eventsArg],
113
- };
114
- }
7
+ public static deviceId(snapshotArg: IWizSnapshot): string { return `wiz:${encodeURIComponent(snapshotArg.homeId)}:${snapshotArg.info.macAddress}`; }
8
+ public static entityId(snapshotArg: IWizSnapshot): string { return `${this.deviceId(snapshotArg)}:control`; }
115
9
 
116
10
  public static toDevices(snapshotArg: IWizSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
117
- const updatedAt = new Date().toISOString();
118
- return snapshotArg.devices.map((deviceArg) => this.toDevice(deviceArg, updatedAt));
119
- }
120
-
121
- public static toEntities(snapshotArg: IWizSnapshot): IIntegrationEntity[] {
122
- const entities: IIntegrationEntity[] = [];
123
- const usedIds = new Map<string, number>();
124
-
125
- for (const device of snapshotArg.devices) {
126
- const info = this.deviceInfo(device);
127
- const pilot = device.pilot || {};
128
- const features = this.features(device);
129
- const deviceId = this.deviceId(device);
130
- const baseName = this.deviceName(device);
131
- const isSocket = this.isSocket(device);
132
- const hasLight = !isSocket && features.light !== false;
133
-
134
- if (hasLight) {
135
- entities.push(this.entity('light', baseName, deviceId, this.uniqueId('light', device), pilot.state ? 'on' : 'off', usedIds, {
136
- ...this.baseAttributes(device),
137
- brightness: pilot.dimming,
138
- brightness255: this.percentToByte(pilot.dimming),
139
- colorTemperatureKelvin: pilot.temp,
140
- rgbColor: this.rgb(pilot),
141
- sceneId: this.sceneId(pilot),
142
- effect: this.sceneName(pilot),
143
- effectList: this.sceneNames,
144
- writable: true,
145
- }, device.available !== false));
146
- }
147
-
148
- if (isSocket || features.switch) {
149
- entities.push(this.entity('switch', baseName, deviceId, this.uniqueId('switch', device), pilot.state ? 'on' : 'off', usedIds, {
150
- ...this.baseAttributes(device),
151
- writable: true,
152
- }, device.available !== false));
153
- }
154
-
155
- if (features.fan || pilot.fanState !== undefined) {
156
- entities.push(this.entity('fan', `${baseName} Fan`, deviceId, this.uniqueId('fan', device), pilot.fanState ? 'on' : 'off', usedIds, {
157
- ...this.baseAttributes(device),
158
- percentage: this.fanPercentage(device, pilot),
159
- fanMode: pilot.fanMode,
160
- fanSpeed: pilot.fanSpeed,
161
- fanReverse: pilot.fanRevrs,
162
- writable: true,
163
- }, device.available !== false));
164
- }
165
-
166
- if (typeof pilot.rssi === 'number') {
167
- entities.push(this.sensorEntity(device, { key: 'rssi', name: `${baseName} RSSI`, value: pilot.rssi, unit: 'dBm', deviceClass: 'signal_strength' }, usedIds));
168
- }
169
- if (typeof pilot.pc === 'number' || features.power) {
170
- entities.push(this.sensorEntity(device, { key: 'power', name: `${baseName} Power`, value: typeof pilot.pc === 'number' ? pilot.pc / 1000 : undefined, unit: 'W', deviceClass: 'power' }, usedIds));
171
- }
172
- if (features.occupancy || pilot.src === 'pir') {
173
- entities.push(this.sensorEntity(device, { key: 'occupancy', name: `${baseName} Occupancy`, platform: 'binary_sensor', value: pilot.src === 'pir' ? pilot.state : undefined, deviceClass: 'occupancy' }, usedIds));
174
- }
175
-
176
- for (const sensor of device.sensors || []) {
177
- entities.push(this.sensorEntity(device, sensor, usedIds));
178
- }
179
-
180
- if (features.effect || pilot.speed !== undefined) {
181
- entities.push(this.numberEntity(device, 'effect_speed', `${baseName} Effect Speed`, pilot.speed, usedIds, { min: 10, max: 200, step: 1, wizPilotKey: 'speed' }));
182
- }
183
- if (features.dualHead || pilot.ratio !== undefined) {
184
- entities.push(this.numberEntity(device, 'dual_head_ratio', `${baseName} Dual Head Ratio`, pilot.ratio, usedIds, { min: 0, max: 100, step: 1, wizPilotKey: 'ratio' }));
185
- }
186
- if (features.effect || this.sceneId(pilot) !== undefined) {
187
- entities.push(this.entity('select', `${baseName} Effect`, deviceId, `${this.uniqueId('select', device)}_effect`, this.sceneName(pilot) || 'None', usedIds, {
188
- ...this.baseAttributes(device),
189
- options: this.sceneNames,
190
- wizPilotKey: 'sceneId',
191
- writable: true,
192
- }, device.available !== false));
193
- }
194
-
195
- for (const button of this.buttons(device)) {
196
- entities.push(this.buttonEntity(device, button, usedIds));
197
- }
198
-
199
- for (const entity of device.entities || []) {
200
- entities.push(this.explicitEntity(device, entity, usedIds));
201
- }
202
- }
203
-
204
- return entities;
205
- }
206
-
207
- public static toIntegrationEvent(eventArg: IWizEvent): IIntegrationEvent {
208
- return {
209
- type: eventArg.type === 'command_failed' || eventArg.type === 'error' ? 'error' : 'state_changed',
210
- integrationDomain: 'wiz',
211
- deviceId: eventArg.deviceId,
212
- entityId: eventArg.entityId,
213
- data: eventArg,
214
- timestamp: eventArg.timestamp || Date.now(),
215
- };
216
- }
217
-
218
- public static commandForService(snapshotArg: IWizSnapshot, requestArg: IServiceCallRequest): IWizClientCommand | undefined {
219
- if (requestArg.domain === 'wiz' && requestArg.service === 'set_pilot' && this.isRecord(requestArg.data?.payload)) {
220
- const targetEntity = this.findTargetEntity(snapshotArg, requestArg);
221
- return {
222
- type: 'setPilot',
223
- service: requestArg.service,
224
- deviceId: targetEntity?.deviceId || requestArg.target.deviceId,
225
- entityId: targetEntity?.id || requestArg.target.entityId,
226
- payload: requestArg.data.payload as IWizPilotPatch,
227
- target: requestArg.target,
228
- };
229
- }
230
-
231
- const target = this.findTargetEntity(snapshotArg, requestArg);
232
- if (!target) {
233
- return undefined;
234
- }
235
- const device = snapshotArg.devices.find((deviceArg) => this.deviceId(deviceArg) === target.deviceId);
236
- const payload = this.payloadForService(target, requestArg, device);
237
- if (!payload || !Object.keys(payload).length) {
238
- return undefined;
239
- }
240
- return {
241
- type: 'setPilot',
242
- service: requestArg.service,
243
- deviceId: target.deviceId,
244
- entityId: target.id,
245
- payload,
246
- target: requestArg.target,
247
- };
248
- }
249
-
250
- public static sceneIdFromName(valueArg: string): number | undefined {
251
- return wizSceneIdsByName.get(valueArg.toLowerCase());
252
- }
253
-
254
- private static toDevice(deviceArg: IWizSnapshotDevice, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
255
- const info = this.deviceInfo(deviceArg);
256
- const pilot = deviceArg.pilot || {};
257
- const featuresInfo = this.features(deviceArg);
258
- const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
259
- { id: 'availability', capability: 'sensor', name: 'Availability', readable: true, writable: false },
260
- ];
261
- const state: plugins.shxInterfaces.data.IDeviceState[] = [
262
- { featureId: 'availability', value: deviceArg.available === false ? 'offline' : 'online', updatedAt: updatedAtArg },
263
- ];
264
- const isSocket = this.isSocket(deviceArg);
265
- const hasLight = !isSocket && featuresInfo.light !== false;
266
-
267
- if (hasLight) {
268
- features.push({ id: 'on', capability: 'light', name: 'Power', readable: true, writable: true });
269
- this.pushDeviceState(state, 'on', pilot.state ?? false, updatedAtArg);
270
- if (featuresInfo.brightness !== false || typeof pilot.dimming === 'number') {
271
- features.push({ id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable: true, unit: '%' });
272
- this.pushDeviceState(state, 'brightness', pilot.dimming, updatedAtArg);
273
- }
274
- if (featuresInfo.colorTemp || typeof pilot.temp === 'number') {
275
- features.push({ id: 'color_temperature', capability: 'light', name: 'Color Temperature', readable: true, writable: true, unit: 'K' });
276
- this.pushDeviceState(state, 'color_temperature', pilot.temp, updatedAtArg);
277
- }
278
- if (featuresInfo.color || this.rgb(pilot)) {
279
- features.push({ id: 'rgb', capability: 'light', name: 'RGB Color', readable: true, writable: true });
280
- this.pushDeviceState(state, 'rgb', this.rgbRecord(pilot), updatedAtArg);
281
- }
282
- if (featuresInfo.effect || this.sceneId(pilot) !== undefined) {
283
- features.push({ id: 'effect', capability: 'light', name: 'Effect', readable: true, writable: true });
284
- this.pushDeviceState(state, 'effect', this.sceneName(pilot) || null, updatedAtArg);
285
- }
286
- }
287
-
288
- if (isSocket || featuresInfo.switch) {
289
- features.push({ id: 'switch', capability: 'switch', name: 'Power', readable: true, writable: true });
290
- this.pushDeviceState(state, 'switch', pilot.state ?? false, updatedAtArg);
291
- }
292
-
293
- if (featuresInfo.fan || pilot.fanState !== undefined) {
294
- features.push({ id: 'fan', capability: 'fan', name: 'Fan', readable: true, writable: true });
295
- this.pushDeviceState(state, 'fan', Boolean(pilot.fanState), updatedAtArg);
296
- features.push({ id: 'fan_speed', capability: 'fan', name: 'Fan Speed', readable: true, writable: true, unit: '%' });
297
- this.pushDeviceState(state, 'fan_speed', this.fanPercentage(deviceArg, pilot), updatedAtArg);
298
- }
299
-
300
- if (typeof pilot.rssi === 'number') {
301
- features.push({ id: 'rssi', capability: 'sensor', name: 'RSSI', readable: true, writable: false, unit: 'dBm' });
302
- this.pushDeviceState(state, 'rssi', pilot.rssi, updatedAtArg);
303
- }
304
- if (typeof pilot.pc === 'number' || featuresInfo.power) {
305
- features.push({ id: 'power', capability: 'energy', name: 'Power', readable: true, writable: false, unit: 'W' });
306
- this.pushDeviceState(state, 'power', typeof pilot.pc === 'number' ? pilot.pc / 1000 : undefined, updatedAtArg);
307
- }
308
- if (featuresInfo.occupancy || pilot.src === 'pir') {
309
- features.push({ id: 'occupancy', capability: 'sensor', name: 'Occupancy', readable: true, writable: false });
310
- this.pushDeviceState(state, 'occupancy', pilot.src === 'pir' ? Boolean(pilot.state) : undefined, updatedAtArg);
311
- }
312
-
313
- for (const sensor of deviceArg.sensors || []) {
314
- features.push({ id: sensor.key, capability: 'sensor', name: sensor.name || this.title(sensor.key), readable: true, writable: Boolean(sensor.writable), unit: sensor.unit });
315
- this.pushDeviceState(state, sensor.key, this.deviceStateValue(sensor.value), updatedAtArg);
316
- }
317
- for (const button of this.buttons(deviceArg)) {
318
- features.push({ id: `button_${button.key}`, capability: 'switch', name: button.name || this.title(button.key), readable: true, writable: true });
319
- this.pushDeviceState(state, `button_${button.key}`, String(button.value ?? button.lastPressedAt ?? 'idle'), updatedAtArg);
320
- }
321
-
322
- return {
323
- id: this.deviceId(deviceArg),
324
- integrationDomain: 'wiz',
325
- name: this.deviceName(deviceArg),
326
- protocol: 'unknown',
327
- manufacturer: info.manufacturer || 'WiZ',
328
- model: info.model || info.moduleName || String(info.bulbType || 'WiZ device'),
329
- online: deviceArg.available !== false,
330
- features,
331
- state,
332
- metadata: {
333
- ...deviceArg.metadata,
334
- protocol: 'wiz-udp-json',
335
- host: deviceArg.host || info.host,
336
- port: deviceArg.port || info.port || wizDefaultPort,
337
- mac: this.mac(deviceArg),
338
- moduleName: info.moduleName,
339
- fwVersion: info.fwVersion,
340
- typeId: info.typeId,
341
- features: featuresInfo,
342
- },
343
- };
344
- }
345
-
346
- private static payloadForService(entityArg: IIntegrationEntity, requestArg: IServiceCallRequest, deviceArg?: IWizSnapshotDevice): IWizPilotPatch | undefined {
347
- if (requestArg.service === 'turn_off') {
348
- return entityArg.platform === 'fan' || requestArg.domain === 'fan' ? { fanState: 0 } : { state: false };
349
- }
350
- if (requestArg.service === 'turn_on') {
351
- const payload: IWizPilotPatch = entityArg.platform === 'fan' || requestArg.domain === 'fan' ? { fanState: 1 } : { state: true };
352
- this.applyServiceData(payload, requestArg, entityArg, deviceArg);
353
- return payload;
354
- }
355
- if (requestArg.service === 'set_percentage' || requestArg.service === 'set_brightness') {
356
- const percentage = this.percentageFromData(requestArg.data, requestArg.service);
357
- if (percentage === undefined) {
358
- return undefined;
359
- }
360
- if (entityArg.platform === 'fan' || requestArg.domain === 'fan') {
361
- return percentage <= 0 ? { fanState: 0 } : { fanState: 1, fanSpeed: this.percentageToFanSpeed(deviceArg, percentage) };
362
- }
363
- return percentage <= 0 ? { state: false } : { state: true, dimming: this.clamp(Math.round(percentage), 10, 100) };
364
- }
365
- if (requestArg.service === 'set_value') {
366
- return this.setValuePayload(entityArg, requestArg);
367
- }
368
- if (requestArg.service === 'select_option' || requestArg.service === 'select_effect') {
369
- const option = this.stringFromData(requestArg.data, ['option', 'effect', 'value']);
370
- const sceneId = typeof option === 'string' ? this.sceneIdFromName(option) : undefined;
371
- return sceneId === undefined ? undefined : { state: true, sceneId };
372
- }
373
- if (requestArg.domain === 'fan') {
374
- if (requestArg.service === 'set_direction') {
375
- const direction = this.stringFromData(requestArg.data, ['direction']);
376
- return direction ? { fanRevrs: direction === 'reverse' ? 1 : 0 } : undefined;
377
- }
378
- if (requestArg.service === 'set_preset_mode') {
379
- const presetMode = this.stringFromData(requestArg.data, ['preset_mode', 'presetMode']);
380
- return presetMode === 'breeze' ? { fanMode: 2 } : undefined;
381
- }
382
- }
383
- return undefined;
384
- }
385
-
386
- private static applyServiceData(payloadArg: IWizPilotPatch, requestArg: IServiceCallRequest, entityArg: IIntegrationEntity, deviceArg?: IWizSnapshotDevice): void {
387
- const brightness = this.percentageFromData(requestArg.data, 'turn_on');
388
- if (brightness !== undefined && entityArg.platform !== 'fan' && requestArg.domain !== 'fan') {
389
- payloadArg.dimming = this.clamp(Math.round(brightness), 10, 100);
390
- }
391
- if (brightness !== undefined && (entityArg.platform === 'fan' || requestArg.domain === 'fan')) {
392
- payloadArg.fanSpeed = this.percentageToFanSpeed(deviceArg, brightness);
393
- }
394
- const kelvin = this.kelvinFromData(requestArg.data);
395
- if (kelvin !== undefined) {
396
- payloadArg.temp = kelvin;
397
- }
398
- const rgb = this.rgbFromData(requestArg.data, 'rgb_color') || this.rgbFromData(requestArg.data, 'rgb');
399
- if (rgb) {
400
- payloadArg.r = rgb[0];
401
- payloadArg.g = rgb[1];
402
- payloadArg.b = rgb[2];
403
- }
404
- const rgbw = this.rgbFromData(requestArg.data, 'rgbw_color');
405
- if (rgbw) {
406
- payloadArg.r = rgbw[0];
407
- payloadArg.g = rgbw[1];
408
- payloadArg.b = rgbw[2];
409
- payloadArg.w = rgbw[3];
410
- }
411
- const rgbww = this.rgbFromData(requestArg.data, 'rgbww_color');
412
- if (rgbww) {
413
- payloadArg.r = rgbww[0];
414
- payloadArg.g = rgbww[1];
415
- payloadArg.b = rgbww[2];
416
- payloadArg.c = rgbww[3];
417
- payloadArg.w = rgbww[4];
418
- }
419
- const effect = this.stringFromData(requestArg.data, ['effect', 'option']);
420
- const sceneId = effect ? this.sceneIdFromName(effect) : this.numberFromData(requestArg.data, ['sceneId', 'scene_id']);
421
- if (sceneId !== undefined) {
422
- payloadArg.sceneId = sceneId;
423
- }
424
- const speed = this.numberFromData(requestArg.data, ['speed']);
425
- if (speed !== undefined) {
426
- payloadArg.speed = this.clamp(Math.round(speed), 10, 200);
427
- }
428
- }
429
-
430
- private static setValuePayload(entityArg: IIntegrationEntity, requestArg: IServiceCallRequest): IWizPilotPatch | undefined {
431
- const value = this.valueFromData(requestArg.data, ['value', 'state']);
432
- const field = this.stringFromData(requestArg.data, ['field', 'attribute', 'key']) || (typeof entityArg.attributes?.wizPilotKey === 'string' ? entityArg.attributes.wizPilotKey : undefined);
433
- if (value === undefined || !field || !pilotPatchKeys.has(field)) {
434
- return undefined;
435
- }
436
- if (field === 'speed') {
437
- return typeof value === 'number' ? { speed: this.clamp(Math.round(value), 10, 200) } : undefined;
438
- }
439
- if (field === 'ratio') {
440
- return typeof value === 'number' ? { ratio: this.clamp(Math.round(value), 0, 100) } : undefined;
441
- }
442
- if (field === 'sceneId' && typeof value === 'string') {
443
- const sceneId = this.sceneIdFromName(value);
444
- return sceneId === undefined ? undefined : { state: true, sceneId };
445
- }
446
- return { [field]: value } as IWizPilotPatch;
447
- }
448
-
449
- private static findTargetEntity(snapshotArg: IWizSnapshot, requestArg: IServiceCallRequest): IIntegrationEntity | undefined {
450
- const entities = this.toEntities(snapshotArg);
451
- if (requestArg.target.entityId) {
452
- return entities.find((entityArg) => entityArg.id === requestArg.target.entityId || entityArg.uniqueId === requestArg.target.entityId);
453
- }
454
- if (requestArg.target.deviceId) {
455
- return entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId && entityArg.platform === requestArg.domain)
456
- || entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId && Boolean(entityArg.attributes?.writable))
457
- || entities.find((entityArg) => entityArg.deviceId === requestArg.target.deviceId);
458
- }
459
- return entities.find((entityArg) => entityArg.platform === requestArg.domain && Boolean(entityArg.attributes?.writable));
460
- }
461
-
462
- private static deviceFromManualEntry(entryArg: IWizManualEntry): IWizSnapshotDevice {
463
- return {
464
- id: entryArg.id,
465
- host: entryArg.host,
466
- port: entryArg.port || wizDefaultPort,
467
- mac: entryArg.mac || entryArg.macAddress || entryArg.deviceInfo?.mac,
468
- name: entryArg.name || entryArg.deviceInfo?.name,
469
- deviceInfo: {
470
- ...entryArg.deviceInfo,
471
- host: entryArg.host,
472
- port: entryArg.port || wizDefaultPort,
473
- mac: entryArg.mac || entryArg.macAddress || entryArg.deviceInfo?.mac,
474
- name: entryArg.name || entryArg.deviceInfo?.name,
475
- manufacturer: entryArg.manufacturer || entryArg.deviceInfo?.manufacturer,
476
- model: entryArg.model || entryArg.deviceInfo?.model,
477
- },
478
- pilot: entryArg.pilot,
479
- available: Boolean(entryArg.pilot),
480
- metadata: entryArg.metadata,
11
+ const pilot = snapshotArg.info.observedState;
12
+ const capabilities = snapshotArg.info.capabilities;
13
+ const features: plugins.shxInterfaces.data.IDeviceFeature[] = [];
14
+ const state: plugins.shxInterfaces.data.IDeviceState[] = [];
15
+ const add = (idArg: string, nameArg: string, capabilityArg: plugins.shxInterfaces.data.TDeviceCapability,
16
+ valueArg: plugins.shxInterfaces.data.TDeviceStateValue | undefined, writableArg: boolean, unitArg?: string): void => {
17
+ if (valueArg === undefined) return;
18
+ features.push({ id: idArg, name: nameArg, capability: capabilityArg, readable: true, writable: writableArg, unit: unitArg });
19
+ state.push({ featureId: idArg, value: valueArg, updatedAt: snapshotArg.observedAt });
481
20
  };
21
+ add('power', 'Power', 'switch', pilot.power, capabilities.power);
22
+ add('brightness', 'Brightness', 'light', pilot.brightness, capabilities.brightness, '%');
23
+ add('rgb', 'RGB', 'light', pilot.rgb ? `${pilot.rgb.red},${pilot.rgb.green},${pilot.rgb.blue}` : undefined, capabilities.rgb);
24
+ add('color_temperature', 'Observed colour temperature', 'sensor', pilot.colorTemperature, false, 'K');
25
+ add('scene_id', 'Observed scene ID', 'sensor', pilot.sceneId, false);
26
+ return [{ id: this.deviceId(snapshotArg), integrationDomain: 'wiz', protocol: 'udp', manufacturer: 'WiZ',
27
+ name: snapshotArg.name ?? snapshotArg.info.moduleName ?? 'WiZ', model: snapshotArg.info.moduleName,
28
+ features, state, online: true, metadata: {
29
+ homeId: snapshotArg.homeId, macAddress: snapshotArg.info.macAddress, host: snapshotArg.host, port: snapshotArg.port,
30
+ firmwareVersion: snapshotArg.info.firmwareVersion, deviceClass: snapshotArg.info.deviceClass,
31
+ observedAt: snapshotArg.observedAt, networkAuthenticated: false, physicalStateConfirmed: false,
32
+ } }];
482
33
  }
483
34
 
484
- private static explicitEntity(deviceArg: IWizSnapshotDevice, entityArg: IWizEntityRecord, usedIdsArg: Map<string, number>): IIntegrationEntity {
485
- const platform = this.corePlatform(entityArg.platform);
486
- const name = entityArg.name || this.deviceName(deviceArg);
487
- return this.entity(platform, name, this.deviceId(deviceArg), entityArg.uniqueId || `${this.uniqueId(platform, deviceArg)}_${entityArg.key || this.slug(name)}`, this.entityState(entityArg.state, platform), usedIdsArg, {
488
- ...this.baseAttributes(deviceArg),
489
- ...entityArg.attributes,
490
- wizPilotKey: entityArg.key,
491
- writable: entityArg.writable,
492
- }, entityArg.available !== false && deviceArg.available !== false, entityArg.entityId);
493
- }
494
-
495
- private static sensorEntity(deviceArg: IWizSnapshotDevice, sensorArg: IWizSensorRecord, usedIdsArg: Map<string, number>): IIntegrationEntity {
496
- const platform = sensorArg.platform || 'sensor';
497
- const state = platform === 'binary_sensor' ? sensorArg.value ? 'on' : 'off' : sensorArg.value ?? 'unknown';
498
- return this.entity(platform, sensorArg.name || `${this.deviceName(deviceArg)} ${this.title(sensorArg.key)}`, this.deviceId(deviceArg), `${this.uniqueId(platform, deviceArg)}_${this.slug(sensorArg.key)}`, state, usedIdsArg, {
499
- ...this.baseAttributes(deviceArg),
500
- wizSensorKey: sensorArg.key,
501
- wizPilotKey: sensorArg.writable ? sensorArg.key : undefined,
502
- deviceClass: sensorArg.deviceClass,
503
- unit: sensorArg.unit,
504
- writable: sensorArg.writable,
505
- }, sensorArg.available !== false && deviceArg.available !== false);
506
- }
507
-
508
- private static numberEntity(deviceArg: IWizSnapshotDevice, keyArg: string, nameArg: string, valueArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>): IIntegrationEntity {
509
- return this.entity('number', nameArg, this.deviceId(deviceArg), `${this.uniqueId('number', deviceArg)}_${this.slug(keyArg)}`, valueArg ?? 'unknown', usedIdsArg, {
510
- ...this.baseAttributes(deviceArg),
511
- ...attributesArg,
512
- writable: true,
513
- }, deviceArg.available !== false);
514
- }
515
-
516
- private static buttonEntity(deviceArg: IWizSnapshotDevice, buttonArg: IWizButtonRecord, usedIdsArg: Map<string, number>): IIntegrationEntity {
517
- const name = buttonArg.name || `${this.deviceName(deviceArg)} ${this.title(buttonArg.key)}`;
518
- return this.entity('button', name, this.deviceId(deviceArg), `${this.uniqueId('button', deviceArg)}_${this.slug(buttonArg.key)}`, buttonArg.value ?? 'idle', usedIdsArg, {
519
- ...this.baseAttributes(deviceArg),
520
- wizButtonKey: buttonArg.key,
521
- lastPressedAt: buttonArg.lastPressedAt,
522
- writable: true,
523
- }, buttonArg.available !== false && deviceArg.available !== false);
524
- }
525
-
526
- private static entity(platformArg: TEntityPlatform, nameArg: string, deviceIdArg: string, uniqueIdArg: string, stateArg: unknown, usedIdsArg: Map<string, number>, attributesArg: Record<string, unknown>, availableArg: boolean, explicitEntityIdArg?: string): IIntegrationEntity {
527
- return {
528
- id: explicitEntityIdArg || this.entityId(platformArg, nameArg, usedIdsArg),
529
- uniqueId: uniqueIdArg,
530
- integrationDomain: 'wiz',
531
- deviceId: deviceIdArg,
532
- platform: platformArg,
533
- name: nameArg,
534
- state: stateArg,
535
- attributes: attributesArg,
536
- available: availableArg,
537
- };
538
- }
539
-
540
- private static buttons(deviceArg: IWizSnapshotDevice): IWizButtonRecord[] {
541
- const buttons = [...(deviceArg.buttons || [])];
542
- const source = typeof deviceArg.pilot?.src === 'string' ? deviceArg.pilot.src : undefined;
543
- if (source && wizButtonSources[source]) {
544
- buttons.push({ key: wizButtonSources[source], name: `${this.deviceName(deviceArg)} Button ${this.title(wizButtonSources[source])}`, value: source });
545
- }
546
- return buttons;
547
- }
548
-
549
- private static deviceInfo(deviceArg: IWizSnapshotDevice): IWizDeviceInfo {
550
- return {
551
- ...deviceArg.deviceInfo,
552
- id: deviceArg.deviceInfo?.id || deviceArg.id,
553
- host: deviceArg.host || deviceArg.deviceInfo?.host,
554
- port: deviceArg.port || deviceArg.deviceInfo?.port || wizDefaultPort,
555
- mac: deviceArg.mac || deviceArg.deviceInfo?.mac || deviceArg.pilot?.mac,
556
- name: deviceArg.name || deviceArg.deviceInfo?.name,
557
- };
558
- }
559
-
560
- private static features(deviceArg: IWizSnapshotDevice): IWizDeviceFeatures {
561
- const info = this.deviceInfo(deviceArg);
562
- const pilot = deviceArg.pilot || {};
563
- const lowerType = String(info.bulbType || info.model || info.moduleName || '').toLowerCase();
564
- const isSocket = this.isSocket(deviceArg);
565
- return {
566
- light: !isSocket,
567
- switch: isSocket,
568
- brightness: typeof pilot.dimming === 'number' || !isSocket,
569
- color: ['r', 'g', 'b'].every((keyArg) => typeof pilot[keyArg] === 'number') || lowerType.includes('rgb'),
570
- colorTemp: typeof pilot.temp === 'number' || lowerType.includes('tw') || lowerType.includes('tunable'),
571
- effect: typeof pilot.sceneId === 'number' || typeof pilot.schdPsetId === 'number' || info.features?.effect,
572
- fan: typeof pilot.fanState === 'number' || info.features?.fan,
573
- power: typeof pilot.pc === 'number' || info.powerMonitoring || info.features?.power,
574
- occupancy: pilot.src === 'pir' || info.features?.occupancy,
575
- button: Boolean(deviceArg.buttons?.length) || Boolean(pilot.src && wizButtonSources[String(pilot.src)]) || info.features?.button,
576
- dualHead: typeof pilot.ratio === 'number' || info.features?.dualHead,
577
- ...info.features,
578
- };
579
- }
580
-
581
- private static isSocket(deviceArg: IWizSnapshotDevice): boolean {
582
- const info = this.deviceInfo(deviceArg);
583
- const text = [info.bulbType, info.model, info.moduleName, info.typeId].filter((valueArg) => valueArg !== undefined).join(' ').toLowerCase();
584
- return info.isSocket === true || info.features?.switch === true || text.includes('socket') || text.includes('plug');
585
- }
586
-
587
- private static deviceId(deviceArg: IWizSnapshotDevice): string {
588
- return `wiz.device.${this.slug(deviceArg.id || this.mac(deviceArg) || deviceArg.host || this.deviceName(deviceArg))}`;
589
- }
590
-
591
- private static uniqueId(platformArg: string, deviceArg: IWizSnapshotDevice): string {
592
- return `wiz_${platformArg}_${this.slug(this.mac(deviceArg) || deviceArg.id || deviceArg.host || this.deviceName(deviceArg))}`;
593
- }
594
-
595
- private static deviceName(deviceArg: IWizSnapshotDevice): string {
596
- const info = this.deviceInfo(deviceArg);
597
- return info.name || deviceArg.name || (this.mac(deviceArg) ? `WiZ ${this.shortMac(this.mac(deviceArg))}` : 'WiZ device');
598
- }
599
-
600
- private static mac(deviceArg: IWizSnapshotDevice): string | undefined {
601
- return this.normalizeMac(deviceArg.mac || deviceArg.deviceInfo?.mac || deviceArg.pilot?.mac);
602
- }
603
-
604
- private static baseAttributes(deviceArg: IWizSnapshotDevice): Record<string, unknown> {
605
- const info = this.deviceInfo(deviceArg);
606
- return {
607
- wizDeviceId: this.deviceId(deviceArg),
608
- wizHost: info.host,
609
- wizPort: info.port || wizDefaultPort,
610
- wizMac: this.mac(deviceArg),
611
- moduleName: info.moduleName,
612
- fwVersion: info.fwVersion,
613
- model: info.model,
614
- };
615
- }
616
-
617
- private static sceneId(pilotArg: IWizPilotState): number | undefined {
618
- if (typeof pilotArg.schdPsetId === 'number') {
619
- return 1000;
620
- }
621
- return typeof pilotArg.sceneId === 'number' && pilotArg.sceneId > 0 ? pilotArg.sceneId : undefined;
622
- }
623
-
624
- private static sceneName(pilotArg: IWizPilotState): string | undefined {
625
- const sceneId = this.sceneId(pilotArg);
626
- return sceneId === undefined ? undefined : wizSceneNamesById.get(sceneId) || `Scene ${sceneId}`;
627
- }
628
-
629
- private static fanPercentage(deviceArg: IWizSnapshotDevice, pilotArg: IWizPilotState): number | undefined {
630
- if (typeof pilotArg.fanSpeed !== 'number') {
631
- return undefined;
632
- }
633
- const max = this.deviceInfo(deviceArg).fanSpeedRange || 6;
634
- return this.clamp(Math.round(pilotArg.fanSpeed / max * 100), 0, 100);
635
- }
636
-
637
- private static percentageToFanSpeed(deviceArg: IWizSnapshotDevice | undefined, percentageArg: number): number {
638
- const max = deviceArg ? this.deviceInfo(deviceArg).fanSpeedRange || 6 : 6;
639
- return this.clamp(Math.ceil(this.clamp(percentageArg, 0, 100) / 100 * max), 1, max);
640
- }
641
-
642
- private static percentToByte(valueArg: unknown): number | undefined {
643
- return typeof valueArg === 'number' ? this.clamp(Math.round(valueArg / 100 * 255), 0, 255) : undefined;
644
- }
645
-
646
- private static percentageFromData(dataArg: Record<string, unknown> | undefined, serviceArg: string): number | undefined {
647
- const pct = this.numberFromData(dataArg, ['percentage', 'brightness_pct']);
648
- if (pct !== undefined) {
649
- return this.clamp(pct, 0, 100);
650
- }
651
- const brightness = this.numberFromData(dataArg, serviceArg === 'set_brightness' ? ['brightness', 'value'] : ['brightness']);
652
- return brightness === undefined ? undefined : this.clamp(Math.round(brightness / 255 * 100), 0, 100);
653
- }
654
-
655
- private static kelvinFromData(dataArg: Record<string, unknown> | undefined): number | undefined {
656
- const direct = this.numberFromData(dataArg, ['color_temp_kelvin', 'kelvin', 'temp', 'color_temperature']);
657
- if (direct !== undefined) {
658
- return Math.round(direct);
659
- }
660
- const mired = this.numberFromData(dataArg, ['color_temp', 'color_temp_mired']);
661
- return mired && mired > 0 ? Math.round(1000000 / mired) : undefined;
662
- }
663
-
664
- private static rgb(pilotArg: IWizPilotState): number[] | undefined {
665
- return typeof pilotArg.r === 'number' && typeof pilotArg.g === 'number' && typeof pilotArg.b === 'number'
666
- ? [pilotArg.r, pilotArg.g, pilotArg.b]
667
- : undefined;
668
- }
669
-
670
- private static rgbRecord(pilotArg: IWizPilotState): Record<string, unknown> | undefined {
671
- const rgb = this.rgb(pilotArg);
672
- return rgb ? { r: rgb[0], g: rgb[1], b: rgb[2], c: pilotArg.c, w: pilotArg.w } : undefined;
673
- }
674
-
675
- private static rgbFromData(dataArg: Record<string, unknown> | undefined, keyArg: string): number[] | undefined {
676
- const value = dataArg?.[keyArg];
677
- if (!Array.isArray(value) || value.length < 3) {
678
- return undefined;
679
- }
680
- const numbers = value.map((valueArg) => typeof valueArg === 'number' && Number.isFinite(valueArg) ? this.clamp(Math.round(valueArg), 0, 255) : undefined);
681
- return numbers.every((valueArg) => valueArg !== undefined) ? numbers as number[] : undefined;
682
- }
683
-
684
- private static valueFromData(dataArg: Record<string, unknown> | undefined, keysArg: string[]): unknown {
685
- for (const key of keysArg) {
686
- if (dataArg && key in dataArg) {
687
- return dataArg[key];
688
- }
689
- }
690
- return undefined;
691
- }
692
-
693
- private static numberFromData(dataArg: Record<string, unknown> | undefined, keysArg: string[]): number | undefined {
694
- const value = this.valueFromData(dataArg, keysArg);
695
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
696
- }
697
-
698
- private static stringFromData(dataArg: Record<string, unknown> | undefined, keysArg: string[]): string | undefined {
699
- const value = this.valueFromData(dataArg, keysArg);
700
- return typeof value === 'string' && value.trim() ? value.trim() : undefined;
701
- }
702
-
703
- private static corePlatform(platformArg: TEntityPlatform | string): TEntityPlatform {
704
- const platform = platformArg.toLowerCase();
705
- const allowed: TEntityPlatform[] = ['light', 'switch', 'sensor', 'binary_sensor', 'button', 'climate', 'cover', 'fan', 'media_player', 'number', 'select', 'text', 'update'];
706
- return allowed.includes(platform as TEntityPlatform) ? platform as TEntityPlatform : 'sensor';
707
- }
708
-
709
- private static entityState(valueArg: unknown, platformArg: TEntityPlatform): unknown {
710
- if (platformArg === 'light' || platformArg === 'switch' || platformArg === 'fan' || platformArg === 'binary_sensor') {
711
- return typeof valueArg === 'boolean' ? valueArg ? 'on' : 'off' : valueArg ?? 'unknown';
712
- }
713
- return valueArg ?? 'unknown';
714
- }
715
-
716
- private static pushDeviceState(stateArg: plugins.shxInterfaces.data.IDeviceState[], featureIdArg: string, valueArg: unknown, updatedAtArg: string): void {
717
- if (valueArg === undefined) {
718
- return;
719
- }
720
- stateArg.push({ featureId: featureIdArg, value: this.deviceStateValue(valueArg), updatedAt: updatedAtArg });
721
- }
722
-
723
- private static deviceStateValue(valueArg: unknown): plugins.shxInterfaces.data.TDeviceStateValue {
724
- if (typeof valueArg === 'string' || typeof valueArg === 'number' || typeof valueArg === 'boolean' || valueArg === null || this.isRecord(valueArg)) {
725
- return valueArg;
726
- }
727
- return valueArg === undefined ? null : String(valueArg);
728
- }
729
-
730
- private static entityId(platformArg: TEntityPlatform, nameArg: string, usedIdsArg: Map<string, number>): string {
731
- const base = `${platformArg}.${this.slug(nameArg)}`;
732
- const count = usedIdsArg.get(base) || 0;
733
- usedIdsArg.set(base, count + 1);
734
- return count ? `${base}_${count + 1}` : base;
735
- }
736
-
737
- private static shortMac(valueArg?: string): string {
738
- return (valueArg || '').replace(/[^a-fA-F0-9]/g, '').slice(-6).toUpperCase();
739
- }
740
-
741
- private static normalizeMac(valueArg?: string): string | undefined {
742
- if (!valueArg) {
743
- return undefined;
744
- }
745
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
746
- return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
747
- }
748
-
749
- private static title(valueArg: string): string {
750
- return valueArg.replace(/_/g, ' ').replace(/\b\w/g, (matchArg) => matchArg.toUpperCase());
751
- }
752
-
753
- private static slug(valueArg: string): string {
754
- return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'wiz';
755
- }
756
-
757
- private static clamp(valueArg: number, minArg: number, maxArg: number): number {
758
- return Math.max(minArg, Math.min(maxArg, valueArg));
759
- }
760
-
761
- private static isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
762
- return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
35
+ public static toEntities(snapshotArg: IWizSnapshot): IIntegrationEntity[] {
36
+ const pilot = snapshotArg.info.observedState;
37
+ return [{ id: this.entityId(snapshotArg), uniqueId: this.entityId(snapshotArg), deviceId: this.deviceId(snapshotArg), integrationDomain: 'wiz',
38
+ platform: snapshotArg.info.deviceClass === 'socket' ? 'switch' : snapshotArg.info.deviceClass === 'unknown' ? 'sensor' : 'light',
39
+ name: snapshotArg.name ?? snapshotArg.info.moduleName ?? 'WiZ', state: pilot.power === undefined ? null : pilot.power ? 'on' : 'off', available: true,
40
+ attributes: { homeId: snapshotArg.homeId, writable: Object.values(snapshotArg.info.capabilities).some(Boolean),
41
+ capabilities: snapshotArg.info.capabilities, brightness: pilot.brightness, rgb: pilot.rgb,
42
+ colorTemperature: pilot.colorTemperature, sceneId: pilot.sceneId, observedAt: snapshotArg.observedAt,
43
+ networkAuthenticated: false, physicalStateConfirmed: false },
44
+ }];
45
+ }
46
+
47
+ public static controlForService(snapshotArg: IWizSnapshot, requestArg: IServiceCallRequest): TWizControl {
48
+ const request = wizRecord(requestArg, ['domain', 'service', 'target', 'data']);
49
+ const target = wizRecord(request.target, ['deviceId', 'entityId']);
50
+ if (target.deviceId === undefined && target.entityId === undefined ||
51
+ target.deviceId !== undefined && target.deviceId !== this.deviceId(snapshotArg) ||
52
+ target.entityId !== undefined && target.entityId !== this.entityId(snapshotArg)) throw new TypeError('WiZ requires an exact owned home/device/entity target.');
53
+ const domain = snapshotArg.info.deviceClass === 'socket' ? 'switch' : 'light';
54
+ if (request.domain !== domain) throw new TypeError('WiZ service domain must match the observed device class.');
55
+ if (request.service === 'turn_on' || request.service === 'turn_off') {
56
+ if (request.data !== undefined) wizRecord(request.data, []);
57
+ return this.requireCapability(snapshotArg, wizControl({ feature: 'power', value: request.service === 'turn_on' }));
58
+ }
59
+ if (request.service === 'set_brightness') {
60
+ const data = wizRecord(request.data, ['brightness']);
61
+ return this.requireCapability(snapshotArg, wizControl({ feature: 'brightness', value: data.brightness }));
62
+ }
63
+ if (request.service === 'set_rgb') {
64
+ const data = wizRecord(request.data, ['rgb']);
65
+ return this.requireCapability(snapshotArg, wizControl({ feature: 'rgb', value: data.rgb }));
66
+ }
67
+ throw new TypeError('WiZ supports only explicit power, brightness and RGB services.');
68
+ }
69
+
70
+ public static controlForFeature(snapshotArg: IWizSnapshot, requestArg: IDeviceFeatureWriteRequest): TWizControl {
71
+ const request = wizRecord(requestArg, ['device', 'feature', 'value']);
72
+ const device = wizRecord(request.device);
73
+ const feature = wizRecord(request.feature);
74
+ const metadata = wizRecord(device.metadata);
75
+ if (device.id !== this.deviceId(snapshotArg) || device.integrationDomain !== 'wiz' || metadata.homeId !== snapshotArg.homeId) {
76
+ throw new TypeError('WiZ feature write must target the exact configured home and device.');
77
+ }
78
+ let value = request.value;
79
+ if (feature.id === 'rgb') {
80
+ if (typeof value !== 'string' || !/^(0|[1-9]\d{0,2}),(0|[1-9]\d{0,2}),(0|[1-9]\d{0,2})$/.test(value)) throw new TypeError('WiZ RGB features require three exact decimal bytes.');
81
+ const channels = value.split(',').map(Number);
82
+ value = { red: channels[0], green: channels[1], blue: channels[2] };
83
+ }
84
+ return this.requireCapability(snapshotArg, wizControl({ feature: feature.id, value }));
85
+ }
86
+
87
+ private static requireCapability(snapshotArg: IWizSnapshot, controlArg: TWizControl): TWizControl {
88
+ if (!snapshotArg.info.capabilities[controlArg.feature]) throw new TypeError('WiZ control is unsupported by the observed device.');
89
+ return controlArg;
763
90
  }
764
91
  }