@smarthome.exchange/integrations 1.0.0 → 3.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 (529) hide show
  1. package/changelog.md +84 -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 +81 -0
  8. package/dist_ts/core/classes.nativeintegrationowner.js +273 -0
  9. package/dist_ts/core/classes.runtimemanager.d.ts +2 -0
  10. package/dist_ts/core/classes.runtimemanager.js +11 -3
  11. package/dist_ts/core/index.d.ts +2 -0
  12. package/dist_ts/core/index.js +3 -1
  13. package/dist_ts/core/types.d.ts +16 -0
  14. package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
  15. package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
  16. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
  17. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
  18. package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
  19. package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
  20. package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
  21. package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
  22. package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
  23. package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
  24. package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
  25. package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
  26. package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
  27. package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
  28. package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
  29. package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
  30. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
  31. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
  32. package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
  33. package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
  34. package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
  35. package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
  36. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
  37. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
  38. package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
  39. package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
  40. package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
  41. package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
  42. package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
  43. package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
  44. package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
  45. package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
  46. package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
  47. package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
  48. package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
  49. package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
  50. package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
  51. package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
  52. package/dist_ts/integrations/daikin/daikin.types.js +1 -1
  53. package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
  54. package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
  55. package/dist_ts/integrations/deconz/deconz.classes.client.d.ts +37 -29
  56. package/dist_ts/integrations/deconz/deconz.classes.client.js +521 -294
  57. package/dist_ts/integrations/deconz/deconz.classes.configflow.d.ts +6 -1
  58. package/dist_ts/integrations/deconz/deconz.classes.configflow.js +63 -33
  59. package/dist_ts/integrations/deconz/deconz.classes.integration.d.ts +4 -3
  60. package/dist_ts/integrations/deconz/deconz.classes.integration.js +85 -40
  61. package/dist_ts/integrations/deconz/deconz.mapper.d.ts +1 -0
  62. package/dist_ts/integrations/deconz/deconz.mapper.js +24 -6
  63. package/dist_ts/integrations/deconz/deconz.types.d.ts +43 -6
  64. package/dist_ts/integrations/deconz/deconz.validation.d.ts +7 -1
  65. package/dist_ts/integrations/deconz/deconz.validation.js +215 -4
  66. package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
  67. package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
  68. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
  69. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
  70. package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
  71. package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
  72. package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
  73. package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
  74. package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
  75. package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
  76. package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
  77. package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
  78. package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
  79. package/dist_ts/integrations/esphome/esphome.classes.client.d.ts +19 -7
  80. package/dist_ts/integrations/esphome/esphome.classes.client.js +183 -89
  81. package/dist_ts/integrations/esphome/esphome.classes.configflow.d.ts +5 -2
  82. package/dist_ts/integrations/esphome/esphome.classes.configflow.js +43 -33
  83. package/dist_ts/integrations/esphome/esphome.classes.integration.d.ts +8 -6
  84. package/dist_ts/integrations/esphome/esphome.classes.integration.js +71 -43
  85. package/dist_ts/integrations/esphome/esphome.discovery.js +4 -12
  86. package/dist_ts/integrations/esphome/esphome.mapper.d.ts +2 -2
  87. package/dist_ts/integrations/esphome/esphome.mapper.js +6 -4
  88. package/dist_ts/integrations/esphome/esphome.types.d.ts +31 -6
  89. package/dist_ts/integrations/esphome/esphome.types.js +1 -1
  90. package/dist_ts/integrations/esphome/esphome.validation.d.ts +7 -0
  91. package/dist_ts/integrations/esphome/esphome.validation.js +66 -0
  92. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.d.ts +32 -4
  93. package/dist_ts/integrations/homeassistant/homeassistant.classes.client.js +345 -116
  94. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.d.ts +8 -5
  95. package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.js +34 -8
  96. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.d.ts +7 -4
  97. package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.js +44 -56
  98. package/dist_ts/integrations/homeassistant/homeassistant.types.d.ts +12 -1
  99. package/dist_ts/integrations/hue/hue.classes.client.d.ts +6 -1
  100. package/dist_ts/integrations/hue/hue.classes.client.js +120 -45
  101. package/dist_ts/integrations/hue/hue.classes.configflow.js +16 -7
  102. package/dist_ts/integrations/hue/hue.classes.integration.d.ts +2 -0
  103. package/dist_ts/integrations/hue/hue.classes.integration.js +103 -30
  104. package/dist_ts/integrations/hue/hue.validation.d.ts +1 -0
  105. package/dist_ts/integrations/hue/hue.validation.js +14 -4
  106. package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
  107. package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
  108. package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
  109. package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
  110. package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
  111. package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
  112. package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
  113. package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
  114. package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
  115. package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
  116. package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
  117. package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
  118. package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
  119. package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
  120. package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
  121. package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
  122. package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
  123. package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
  124. package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
  125. package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
  126. package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
  127. package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
  128. package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
  129. package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
  130. package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
  131. package/dist_ts/integrations/mqtt/mqtt.classes.client.d.ts +15 -2
  132. package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
  133. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
  134. package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
  135. package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
  136. package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
  137. package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
  138. package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
  139. package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
  140. package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
  141. package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
  142. package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
  143. package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -0
  144. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
  145. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
  146. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
  147. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
  148. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
  149. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
  150. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
  151. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
  152. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
  153. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
  154. package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
  155. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
  156. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
  157. package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
  158. package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
  159. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
  160. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
  161. package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
  162. package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
  163. package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
  164. package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
  165. package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
  166. package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
  167. package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
  168. package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
  169. package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
  170. package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
  171. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
  172. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
  173. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
  174. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
  175. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
  176. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
  177. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
  178. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
  179. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
  180. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
  181. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
  182. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
  183. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
  184. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
  185. package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
  186. package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
  187. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
  188. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
  189. package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
  190. package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
  191. package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
  192. package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
  193. package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
  194. package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
  195. package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
  196. package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
  197. package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
  198. package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
  199. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.d.ts +4 -2
  200. package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
  201. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
  202. package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
  203. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
  204. package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
  205. package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
  206. package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
  207. package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
  208. package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
  209. package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
  210. package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
  211. package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
  212. package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
  213. package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
  214. package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
  215. package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
  216. package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
  217. package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
  218. package/dist_ts/integrations/shelly/shelly.types.js +11 -2
  219. package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
  220. package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
  221. package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
  222. package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
  223. package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
  224. package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
  225. package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
  226. package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
  227. package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
  228. package/dist_ts/integrations/sonos/sonos.config.js +23 -2
  229. package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
  230. package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
  231. package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
  232. package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
  233. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
  234. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
  235. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
  236. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
  237. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
  238. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
  239. package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
  240. package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
  241. package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
  242. package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
  243. package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
  244. package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
  245. package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
  246. package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
  247. package/dist_ts/integrations/tasmota/tasmota.classes.integration.d.ts +5 -0
  248. package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
  249. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
  250. package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
  251. package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
  252. package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
  253. package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
  254. package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
  255. package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
  256. package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
  257. package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
  258. package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
  259. package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
  260. package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
  261. package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
  262. package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
  263. package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
  264. package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
  265. package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
  266. package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
  267. package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
  268. package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
  269. package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
  270. package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
  271. package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
  272. package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
  273. package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
  274. package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
  275. package/dist_ts/integrations/webostv/webostv.types.js +1 -1
  276. package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
  277. package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
  278. package/dist_ts/integrations/wiz/index.d.ts +1 -0
  279. package/dist_ts/integrations/wiz/index.js +2 -1
  280. package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
  281. package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
  282. package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
  283. package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
  284. package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
  285. package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
  286. package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
  287. package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
  288. package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
  289. package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
  290. package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
  291. package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
  292. package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
  293. package/dist_ts/integrations/yamaha/index.d.ts +1 -0
  294. package/dist_ts/integrations/yamaha/index.js +2 -1
  295. package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
  296. package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
  297. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
  298. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
  299. package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
  300. package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
  301. package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
  302. package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
  303. package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
  304. package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
  305. package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
  306. package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
  307. package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
  308. package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
  309. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
  310. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
  311. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
  312. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
  313. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
  314. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
  315. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
  316. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
  317. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
  318. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
  319. package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
  320. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
  321. package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
  322. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
  323. package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
  324. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
  325. package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
  326. package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
  327. package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
  328. package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
  329. package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
  330. package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
  331. package/dist_ts/plugins.d.ts +2 -1
  332. package/dist_ts/plugins.js +3 -2
  333. package/package.json +9 -9
  334. package/readme.md +539 -8
  335. package/ts/00_commitinfo_data.ts +1 -1
  336. package/ts/core/classes.configstore.ts +84 -27
  337. package/ts/core/classes.nativehomeadmission.ts +96 -0
  338. package/ts/core/classes.nativeintegrationowner.ts +302 -0
  339. package/ts/core/classes.runtimemanager.ts +11 -2
  340. package/ts/core/index.ts +2 -0
  341. package/ts/core/types.ts +18 -0
  342. package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
  343. package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
  344. package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
  345. package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
  346. package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
  347. package/ts/integrations/braviatv/braviatv.types.ts +53 -173
  348. package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
  349. package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
  350. package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
  351. package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
  352. package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
  353. package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
  354. package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
  355. package/ts/integrations/broadlink/broadlink.types.ts +48 -8
  356. package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
  357. package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
  358. package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
  359. package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
  360. package/ts/integrations/daikin/daikin.discovery.ts +94 -346
  361. package/ts/integrations/daikin/daikin.mapper.ts +83 -166
  362. package/ts/integrations/daikin/daikin.types.ts +27 -48
  363. package/ts/integrations/daikin/daikin.validation.ts +51 -0
  364. package/ts/integrations/daikin/readme.md +83 -0
  365. package/ts/integrations/deconz/deconz.classes.client.ts +348 -252
  366. package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
  367. package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
  368. package/ts/integrations/deconz/deconz.mapper.ts +22 -5
  369. package/ts/integrations/deconz/deconz.types.ts +49 -6
  370. package/ts/integrations/deconz/deconz.validation.ts +177 -4
  371. package/ts/integrations/deconz/readme.md +15 -1
  372. package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
  373. package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
  374. package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
  375. package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
  376. package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
  377. package/ts/integrations/denonavr/denonavr.types.ts +48 -111
  378. package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
  379. package/ts/integrations/esphome/esphome.classes.client.ts +179 -94
  380. package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
  381. package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
  382. package/ts/integrations/esphome/esphome.discovery.ts +3 -12
  383. package/ts/integrations/esphome/esphome.mapper.ts +9 -7
  384. package/ts/integrations/esphome/esphome.types.ts +35 -6
  385. package/ts/integrations/esphome/esphome.validation.ts +66 -0
  386. package/ts/integrations/esphome/readme.md +20 -6
  387. package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
  388. package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
  389. package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
  390. package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
  391. package/ts/integrations/homeassistant/readme.md +22 -6
  392. package/ts/integrations/hue/hue.classes.client.ts +110 -41
  393. package/ts/integrations/hue/hue.classes.configflow.ts +12 -6
  394. package/ts/integrations/hue/hue.classes.integration.ts +92 -26
  395. package/ts/integrations/hue/hue.validation.ts +13 -3
  396. package/ts/integrations/hue/readme.md +3 -3
  397. package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
  398. package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
  399. package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
  400. package/ts/integrations/kodi/kodi.discovery.ts +49 -28
  401. package/ts/integrations/kodi/kodi.mapper.ts +52 -10
  402. package/ts/integrations/kodi/kodi.types.ts +33 -58
  403. package/ts/integrations/kodi/kodi.validation.ts +60 -0
  404. package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
  405. package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
  406. package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
  407. package/ts/integrations/modbus/modbus.discovery.ts +38 -106
  408. package/ts/integrations/modbus/modbus.mapper.ts +64 -467
  409. package/ts/integrations/modbus/modbus.types.ts +64 -381
  410. package/ts/integrations/modbus/modbus.validation.ts +119 -0
  411. package/ts/integrations/mqtt/mqtt.classes.client.ts +136 -19
  412. package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
  413. package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
  414. package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
  415. package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
  416. package/ts/integrations/mqtt/mqtt.types.ts +52 -0
  417. package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
  418. package/ts/integrations/mqtt/readme.md +12 -3
  419. package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
  420. package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
  421. package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
  422. package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
  423. package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
  424. package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
  425. package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
  426. package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
  427. package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
  428. package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
  429. package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
  430. package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
  431. package/ts/integrations/onkyo/onkyo.types.ts +55 -110
  432. package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
  433. package/ts/integrations/onkyo/readme.md +126 -0
  434. package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
  435. package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
  436. package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
  437. package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
  438. package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
  439. package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
  440. package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
  441. package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
  442. package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
  443. package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
  444. package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
  445. package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
  446. package/ts/integrations/pioneer/pioneer.types.ts +87 -85
  447. package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
  448. package/ts/integrations/samsungtv/readme.md +11 -5
  449. package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
  450. package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
  451. package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
  452. package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
  453. package/ts/integrations/shelly/readme.md +22 -7
  454. package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
  455. package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
  456. package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
  457. package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
  458. package/ts/integrations/shelly/shelly.mapper.ts +19 -20
  459. package/ts/integrations/shelly/shelly.rpc.ts +2 -0
  460. package/ts/integrations/shelly/shelly.types.ts +50 -2
  461. package/ts/integrations/shelly/shelly.validation.ts +76 -0
  462. package/ts/integrations/sonos/readme.md +117 -38
  463. package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
  464. package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
  465. package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
  466. package/ts/integrations/sonos/sonos.config.ts +24 -2
  467. package/ts/integrations/sonos/sonos.mapper.ts +13 -8
  468. package/ts/integrations/sonos/sonos.matchers.ts +2 -2
  469. package/ts/integrations/sonos/sonos.types.ts +30 -1
  470. package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
  471. package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
  472. package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
  473. package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
  474. package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
  475. package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
  476. package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
  477. package/ts/integrations/tasmota/readme.md +16 -5
  478. package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
  479. package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
  480. package/ts/integrations/tasmota/tasmota.config.ts +17 -0
  481. package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
  482. package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
  483. package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
  484. package/ts/integrations/tasmota/tasmota.types.ts +43 -2
  485. package/ts/integrations/tuya/readme.md +52 -10
  486. package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
  487. package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
  488. package/ts/integrations/tuya/tuya.mapper.ts +1 -1
  489. package/ts/integrations/tuya/tuya.types.ts +4 -0
  490. package/ts/integrations/tuya/tuya.validation.ts +5 -8
  491. package/ts/integrations/webostv/readme.md +115 -22
  492. package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
  493. package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
  494. package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
  495. package/ts/integrations/webostv/webostv.mapper.ts +4 -4
  496. package/ts/integrations/webostv/webostv.types.ts +18 -3
  497. package/ts/integrations/webostv/webostv.validation.ts +22 -1
  498. package/ts/integrations/wiz/index.ts +1 -0
  499. package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
  500. package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
  501. package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
  502. package/ts/integrations/wiz/wiz.discovery.ts +48 -291
  503. package/ts/integrations/wiz/wiz.mapper.ts +81 -754
  504. package/ts/integrations/wiz/wiz.types.ts +24 -224
  505. package/ts/integrations/wiz/wiz.validation.ts +73 -0
  506. package/ts/integrations/yamaha/index.ts +1 -0
  507. package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
  508. package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
  509. package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
  510. package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
  511. package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
  512. package/ts/integrations/yamaha/yamaha.types.ts +77 -97
  513. package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
  514. package/ts/integrations/zigbee2mqtt/readme.md +22 -27
  515. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
  516. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
  517. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
  518. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
  519. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
  520. package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
  521. package/ts/integrations/zwave_js/readme.md +42 -9
  522. package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
  523. package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
  524. package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
  525. package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
  526. package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
  527. package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
  528. package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
  529. package/ts/plugins.ts +2 -1
@@ -0,0 +1,86 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { NoSqlConfigStore } from '../../core/classes.configstore.js';
3
+ import type { IYamahaConfig, TYamahaControl, TYamahaModel } from './yamaha.types.js';
4
+
5
+ export function yamahaRecord(value: unknown, allowed?: readonly string[]): Record<string, unknown> {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
7
+ throw new TypeError('Yamaha input must be a plain data object.');
8
+ }
9
+ const result: Record<string, unknown> = {};
10
+ for (const key of Reflect.ownKeys(value)) {
11
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
12
+ if (typeof key !== 'string' || (allowed && !allowed.includes(key)) || !descriptor || !('value' in descriptor) || !descriptor.enumerable) {
13
+ throw new TypeError('Yamaha input contains an unsupported field or accessor.');
14
+ }
15
+ Object.defineProperty(result, key, { value: descriptor.value, enumerable: true });
16
+ }
17
+ return result;
18
+ }
19
+
20
+ export function yamahaText(value: unknown, label: string, maximum = 256): string {
21
+ if (typeof value !== 'string' || !value || value.length > maximum || value !== value.trim() || /[\p{Cc}\p{Cs}]/u.test(value)) {
22
+ throw new TypeError(`Yamaha ${label} must be bounded text without control characters.`);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ export function yamahaOrigin(value: unknown): string {
28
+ const text = yamahaText(value, 'selected origin', 256);
29
+ const match = /^http:\/\/([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(?::([0-9]+))?\/?$/.exec(text);
30
+ if (!match || plugins.net.isIP(match[1]) !== 4) throw new TypeError('Yamaha requires a canonical literal private IPv4 HTTP origin.');
31
+ const octets = match[1].split('.').map(Number);
32
+ const [first, second] = octets;
33
+ if (!(first === 10 || first === 127 || (first === 172 && second >= 16 && second <= 31) ||
34
+ (first === 192 && second === 168) || (first === 169 && second === 254))) {
35
+ throw new TypeError('Yamaha requires a selected private, link-local or loopback IPv4 host.');
36
+ }
37
+ if (match[2] !== undefined && (!/^[1-9][0-9]{0,4}$/.test(match[2]) || Number(match[2]) > 65535)) {
38
+ throw new TypeError('Yamaha HTTP origin port is invalid.');
39
+ }
40
+ return new URL(text).origin;
41
+ }
42
+
43
+ export function yamahaDeviceId(value: unknown): string {
44
+ if (typeof value !== 'string' || !/^[\x21-\x7e]{12}$/.test(value)) throw new TypeError('Yamaha requires the exact 12-character device_id.');
45
+ return value;
46
+ }
47
+
48
+ export function yamahaModel(value: unknown): TYamahaModel {
49
+ if (value !== 'RX-V485') throw new TypeError('Yamaha model has no selected native profile.');
50
+ return value;
51
+ }
52
+
53
+ export function yamahaInteger(value: unknown, fallback?: number, minimum = Number.MIN_SAFE_INTEGER, maximum = Number.MAX_SAFE_INTEGER): number {
54
+ const result = value === undefined ? fallback : value;
55
+ if (typeof result !== 'number' || !Number.isSafeInteger(result) || result < minimum || result > maximum) throw new RangeError('Yamaha integer bound is invalid.');
56
+ return result;
57
+ }
58
+
59
+ export function normalizeYamahaConfig(value: unknown): Readonly<Required<Omit<IYamahaConfig, 'name'>> & Pick<IYamahaConfig, 'name'>> {
60
+ const config = yamahaRecord(value, ['homeId', 'origin', 'expectedDeviceId', 'model', 'allowUnencryptedHttp', 'maxVolume', 'name', 'timeoutMs', 'maxResponseBytes', 'maxPendingOperations']);
61
+ if (config.allowUnencryptedHttp !== true) throw new TypeError('Yamaha plaintext LAN trust must be acknowledged explicitly.');
62
+ return Object.freeze({
63
+ homeId: yamahaText(config.homeId, 'home ID'), origin: yamahaOrigin(config.origin), expectedDeviceId: yamahaDeviceId(config.expectedDeviceId),
64
+ model: yamahaModel(config.model), allowUnencryptedHttp: true, maxVolume: yamahaInteger(config.maxVolume),
65
+ ...(config.name === undefined ? {} : { name: yamahaText(config.name, 'display name', 128) }),
66
+ timeoutMs: yamahaInteger(config.timeoutMs, 5000, 1, 60000), maxResponseBytes: yamahaInteger(config.maxResponseBytes, 65536, 256, 1048576),
67
+ maxPendingOperations: yamahaInteger(config.maxPendingOperations, 16, 1, 128),
68
+ });
69
+ }
70
+
71
+ /** The authorized host owns the optional store and its immutable home/domain declaration. */
72
+ export function assertYamahaScope(store: NoSqlConfigStore | undefined, homeId: string): void {
73
+ if (store === undefined) return;
74
+ if (!(store instanceof NoSqlConfigStore)) throw new TypeError('Yamaha config store must be an actual NoSqlConfigStore.');
75
+ const scope = NoSqlConfigStore.prototype.getScope.call(store);
76
+ if (scope.homeId !== homeId || scope.integrationDomain !== 'yamaha') throw new Error('Yamaha config store must match the exact home and integration.');
77
+ }
78
+
79
+ export function validateYamahaControl(value: unknown): TYamahaControl {
80
+ const control = yamahaRecord(value, ['feature', 'value']);
81
+ if (control.feature === 'power' && typeof control.value === 'boolean') return { feature: 'power', value: control.value };
82
+ if (control.feature === 'muted' && typeof control.value === 'boolean') return { feature: 'muted', value: control.value };
83
+ if (control.feature === 'volume_raw') return { feature: 'volume_raw', value: yamahaInteger(control.value) };
84
+ if (control.feature === 'input' && typeof control.value === 'string' && /^[a-z][a-z0-9_]{0,63}$/.test(control.value)) return { feature: 'input', value: control.value };
85
+ throw new TypeError('Yamaha supports explicit power, mute, raw integer volume and current advertised input controls.');
86
+ }
@@ -1,40 +1,35 @@
1
- # Zigbee2MQTT
1
+ # Native Zigbee2MQTT
2
2
 
3
- `Zigbee2MqttIntegration` connects directly to a configured MQTT broker and Zigbee2MQTT base topic. It reuses the package's native `MqttClient`; it does not require Home Assistant or connect to the Zigbee coordinator itself.
3
+ The adapter connects to an explicitly selected MQTT broker and Zigbee2MQTT base topic through the existing native MQTT.js transport. SHX home selection is separate from the Zigbee coordinator, network, IEEE address and friendly name. Broker TLS authenticates the selected broker name; Zigbee identity/inventory observations do not prove a unique or cryptographically authenticated physical controller.
4
4
 
5
5
  ```typescript
6
- const integration = new Zigbee2MqttIntegration();
6
+ const integration = new Zigbee2MqttIntegration({
7
+ homeId: hostSelectedHomeId,
8
+ controls: [{ ieeeAddress: selectedLowercaseIeeeAddress, property: 'state' }],
9
+ resolveCredentials: async (request) => {
10
+ // The host resolver owns storage. It validates the admitted home and exact
11
+ // request.host/port/protocol/baseTopic before returning runtime-only material.
12
+ return retrieveSelectedBrokerCredentials(request);
13
+ },
14
+ });
7
15
  const runtime = await integration.setup({
8
- host: 'broker.example.internal',
9
- protocol: 'mqtts',
10
- port: 8883,
16
+ homeId: hostSelectedHomeId,
17
+ host: 'broker.internal', protocol: 'mqtts', port: 8883,
11
18
  baseTopic: 'home/zigbee2mqtt',
12
- username: brokerUsername,
13
- password: brokerPassword,
14
- }, { abortSignal: lifetimeController.signal });
15
-
16
- const [device] = await runtime.devices();
17
- const power = device?.features.find((feature) => feature.id === 'state');
18
- if (device?.online && power?.writable) {
19
- await runtime.writeFeature?.({ device, feature: power, value: true });
20
- }
21
- await runtime.destroy();
19
+ authentication: 'provider', credentialKey: 'broker-reference',
20
+ }, { homeId: hostSelectedHomeId, abortSignal: hostLifetime.signal });
22
21
  ```
23
22
 
24
- The broker host and base topic are required explicitly. There is no automatic broker selection, injected inventory or fixture configuration in production. `mqtts` validates the broker certificate with Node's trusted certificate authorities; disabling certificate verification is not a supported option. The runtime holds supplied credentials in memory and does not persist them. The application must store and retrieve integration configuration through the public `NoSqlConfigStore` backed by `@lossless.org/client/nosqldb`, using its home scope and external encryption-key provider.
23
+ Every setup and config-flow context requires an independently selected `homeId`; an optional constructor binding does not replace it. Config, form fields, discovery, store scope and network IDs cannot choose home. One instance binds once even if setup later fails. Canonical device IDs are `encodeURIComponent(homeId) + ':zigbee2mqtt.' + lowercaseIeeeAddress`; entity IDs append the exact exposed property. Close is terminal.
25
24
 
26
- Setup waits for received `BASE/bridge/devices` inventory and `BASE/bridge/state`. The inventory contains each device's actual `definition.exposes`; unsupported devices and coordinators cannot be operated. Device IDs use `zigbee2mqtt.0xIEEEADDRESS`, and feature IDs use the exact exposed property, including endpoint suffixes. Entity IDs combine those IDs. A friendly-name change keeps identity stable and discards state and availability learned through the former name.
25
+ `authentication` is required and explicitly selects `anonymous`, `provider` or `config-store`. Anonymous forbids a credential reference. Provider requires both an existing `credentialKey` and constructor-injected `resolveCredentials`; missing provider does not fall back to another source. Config-store requires a borrowed actual scoped `NoSqlConfigStore` and the reference. It reads an encrypted value shaped as `{ version: 1, host, port, protocol, baseTopic, credentials: { username?, password?, ca?, cert?, key?, servername? } }` and verifies exact selected broker/topic binding before use. Configuration and flow output contain references only; passwords, CA/client certificates and private keys are runtime-only strings. No filesystem credential paths are supported. Client cert/key must be supplied together, TLS material requires mqtts, and certificate/hostname verification remains enabled. Plain MQTT requires `allowUnencryptedLan: true`. No-store anonymous and provider paths are legitimate.
27
26
 
28
- Binary, numeric, enum and text exposes are mapped, including primitive features inside advertised light, switch, fan, cover, lock and climate containers. Access bit 1 grants reported state and bit 2 grants writes. Binary features use normalized boolean values, translated into the advertised `value_on` and `value_off`. Numeric writes respect advertised minimum, maximum and step, enum writes accept advertised values only, and text values are limited to 1024 characters. Composite/list values, colors, schedules, numeric presets outside the normal range and binary toggles are unsupported. Their count is included in device metadata. Raw ZCL read/write/command fields are never exposed as controls.
27
+ The resolver request includes captured home, reference, exact broker/topic, an AbortSignal and an absolute performance.now() setup deadline. Provider work is owned before invocation. Deadline/abort prevents late transport allocation; destruction joins the actual foreign promise even if it ignores cancellation. Providers and borrowed store/database/key-provider resources remain caller-owned. Cleanup retry never reruns a provider, setup, subscription or physical command.
29
28
 
30
- Device availability must be enabled in Zigbee2MQTT. An explicit received `{"state":"online"}` on a device's availability topic, current inventory, an online bridge and a ready MQTT connection are all required for control. Missing availability remains `unknown`; neither a sensor report nor a device announcement implies online status. Bridge shutdown and MQTT disconnect revoke admission and clear cached availability. Reconnect restores the owned subscription and requires freshly received inventory and bridge state before control resumes. Per-device state and availability reports received before the inventory are buffered within the configured limits and applied only to validated inventory members.
29
+ Primitive binary, numeric, enum and text controls are preserved, including advertised primitive children of light/switch/fan/cover/lock/climate. Constructor `controls` is an immutable finite exact lowercase IEEE/property allowlist. Default is no control permission. Expose write access alone does not grant owner permission; editable config cannot grant it. At dispatch, current canonical home/target/capability, expose type/access/range/step/enum, supported/non-disabled membership and authoritative availability must all agree. Complex colors, lists, schedules, arbitrary ZCL, groups, joining, pairing, binding, OTA, bridge configuration and /get are excluded.
31
30
 
32
- `zigbee2mqtt.set` accepts exactly one target, either `deviceId` or `entityId`, and exactly `data: { property, value }`. The entity target must correspond to that property. Feature writes check the target's domain and resolve its current canonical feature, so caller-provided writable flags do not grant capabilities. Every command publishes one JSON property to `BASE/0xIEEEADDRESS/set`, using QoS 0 and `retain: false`. Commands are not retried. Success means the MQTT transport accepted publication and returns `physicalOutcome: 'unconfirmed'`; only subsequent device reports change state. MQTT publication does not prove physical execution.
31
+ Each admitted write publishes exactly one property to BASE/IEEE/set with QoS0 and retain:false. Commands are never retried or optimistically applied. Success preserves `{ confirmation: 'mqtt-publish', physicalOutcome: 'unconfirmed' }`; publication callback loss reports uncertainty. Device state changes only from subsequent observations. Retained and pre-inventory action reports never replay physical events. Current-generation cache seeding and callbacks preserve fresh inventory/availability across reconnects; a stale historical snapshot cannot admit control.
33
32
 
34
- Subscriptions emit added/removed entities and devices, state changes and availability changes. Accepted live action reports are emitted once per report, including repeated identical actions; retained or pre-inventory action state does not replay physical events. Authoritative `device_leave` events remove a device immediately. Subscriber cancellation removes only that listener; a throwing listener is removed and produces an error for remaining subscribers. Runtime destruction and the setup context's lifetime abort cancel pending I/O, stop reconnect, remove callbacks and await socket cleanup.
33
+ The owner shares a default capacity of 16 across pending setup/flow work, live runtimes and retained cleanup failures. Existing device/payload/buffer/deadline/write/subscriber bounds remain. Async foreign subscriber work is additionally bounded at 256 pending callbacks and drained during closure. A failed subscriber is removed and other subscribers receive a sanitized error, preserving established isolation behavior. Integration `destroy()` drains flow/setup/runtime, actual providers, subscriber work, commands and transport; repeated/reentrant calls share a stable promise. `retryCleanup()` retries retained failed cleanup only. Runtime destruction closes its lease. Config-flow destruction is terminal for its scope.
35
34
 
36
- Connection, publication and inventory deadlines default to 7 seconds and are capped at 60 seconds. Inventory defaults to at most 2048 devices, 256 expose descriptors per device, 8 MiB per MQTT payload and 32 MiB total buffered MQTT data. Commands are limited to 64 in flight and listeners to 256. Unsupported bridge topics, including bridge configuration and logging, are never forwarded as entity attributes or errors.
37
-
38
- Groups, pairing, joining, binding, OTA, bridge configuration, network management, `/get` polling and Home Assistant discovery are outside this adapter. Tests use the real MQTT.js TCP parser against a local test broker; no hardware access is performed.
39
-
40
- Protocol references: [MQTT topics and messages](https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html), [exposes and access masks](https://www.zigbee2mqtt.io/guide/usage/exposes.html), and [device availability](https://www.zigbee2mqtt.io/guide/configuration/device-availability.html).
35
+ Verification uses real local MQTT.js TCP/TLS fixtures, including private CA/mutual client authentication and wrong broker-name denial. No hardware/private production broker acceptance has been established.
@@ -1,9 +1,13 @@
1
1
  import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
2
  import type { IZigbee2MqttConfig } from './zigbee2mqtt.types.js';
3
- import { zigbee2MqttConfig } from './zigbee2mqtt.validation.js';
3
+ import { zigbee2MqttConfig, zigbee2MqttRecord } from './zigbee2mqtt.validation.js';
4
+ import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
4
5
 
5
6
  export class Zigbee2MqttConfigFlow implements IConfigFlow<IZigbee2MqttConfig> {
6
- public async start(_candidateArg: IDiscoveryCandidate, _contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IZigbee2MqttConfig>> {
7
+ readonly #scope: NativeIntegrationScope;
8
+ constructor(ownerArg = new NativeIntegrationOwner('zigbee2mqtt')) { this.#scope = ownerArg.createFlowScope(); }
9
+ public async start(_candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IZigbee2MqttConfig>> {
10
+ const context = this.#scope.admit(contextArg);
7
11
  return {
8
12
  kind: 'form', title: 'Connect Zigbee2MQTT',
9
13
  description: 'Enter the broker and exact Zigbee2MQTT base topic. Enable device availability in Zigbee2MQTT before operating devices.',
@@ -12,15 +16,20 @@ export class Zigbee2MqttConfigFlow implements IConfigFlow<IZigbee2MqttConfig> {
12
16
  { name: 'baseTopic', label: 'Zigbee2MQTT base topic', type: 'text', required: true },
13
17
  { name: 'port', label: 'Broker port', type: 'number' },
14
18
  { name: 'protocol', label: 'Protocol', type: 'select', options: [{ label: 'MQTT', value: 'mqtt' }, { label: 'MQTTS', value: 'mqtts' }] },
15
- { name: 'username', label: 'Username', type: 'text' },
16
- { name: 'password', label: 'Password', type: 'password' },
19
+ { name: 'authentication', label: 'Credential source', type: 'select', required: true, options: [{ label: 'Anonymous', value: 'anonymous' }, { label: 'Caller provider', value: 'provider' }, { label: 'Encrypted config store', value: 'config-store' }] },
20
+ { name: 'credentialKey', label: 'Existing credential reference', type: 'text' },
21
+ { name: 'allowUnencryptedLan', label: 'Allow plaintext LAN MQTT', type: 'boolean' },
17
22
  ],
18
- submit: async (valuesArg) => {
23
+ submit: (valuesArg) => this.#scope.run(context, undefined, async () => {
19
24
  try {
20
- const config = zigbee2MqttConfig(valuesArg as unknown as IZigbee2MqttConfig);
25
+ const values = zigbee2MqttRecord(valuesArg);
26
+ if (!values || values.homeId !== undefined && values.homeId !== context.homeId) throw new Error('Conflicting home or invalid form data.');
27
+ const config = zigbee2MqttConfig({ ...values, homeId: context.homeId } as unknown as IZigbee2MqttConfig);
21
28
  return { kind: 'done', title: 'Zigbee2MQTT configured', config };
22
29
  } catch { return { kind: 'error', error: 'Enter a valid broker, base topic and supported explicit configuration fields.' }; }
23
- },
30
+ }),
24
31
  };
25
32
  }
33
+ public destroy(): Promise<void> { return this.#scope.close(); }
34
+ public retryCleanup(): Promise<void> { return this.#scope.retryCleanup(); }
26
35
  }
@@ -1,23 +1,70 @@
1
1
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
2
2
  import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
3
+ import { NativeIntegrationOwner } from '../../core/classes.nativeintegrationowner.js';
3
4
  import type { IIntegrationRuntime, IIntegrationSetupContext } from '../../core/types.js';
4
5
  import { Zigbee2MqttConfigFlow } from './zigbee2mqtt.classes.configflow.js';
5
6
  import { Zigbee2MqttRuntime } from './zigbee2mqtt.classes.runtime.js';
6
- import type { IZigbee2MqttConfig } from './zigbee2mqtt.types.js';
7
+ import { MqttSession } from '../mqtt/mqtt.classes.session.js';
8
+ import type { TMqttCredentialResolver } from '../mqtt/mqtt.types.js';
9
+ import type { IZigbee2MqttConfig, IZigbee2MqttIntegrationOptions } from './zigbee2mqtt.types.js';
10
+ import { zigbee2MqttConfig, zigbee2MqttCredentials, zigbee2MqttOptions, zigbee2MqttRecord } from './zigbee2mqtt.validation.js';
7
11
 
8
12
  export class Zigbee2MqttIntegration extends BaseIntegration<IZigbee2MqttConfig> {
9
13
  public readonly domain = 'zigbee2mqtt';
10
14
  public readonly displayName = 'Zigbee2MQTT';
11
15
  public readonly status = 'control-runtime' as const;
12
16
  public readonly discoveryDescriptor = new DiscoveryDescriptor({ integrationDomain: this.domain, displayName: this.displayName });
13
- public readonly configFlow = new Zigbee2MqttConfigFlow();
17
+ public readonly configFlow: Zigbee2MqttConfigFlow;
18
+ readonly #owner: NativeIntegrationOwner;
19
+ readonly #options: IZigbee2MqttIntegrationOptions;
20
+ readonly #controls: ReadonlySet<string>;
14
21
  public readonly metadata = { source: 'zigbee2mqtt', upstreamPath: 'Koenkk/zigbee2mqtt', iotClass: 'local_push' };
15
22
 
23
+ constructor(optionsArg: IZigbee2MqttIntegrationOptions = {}) {
24
+ super();
25
+ const { options, controls } = zigbee2MqttOptions(optionsArg);
26
+ this.#options = options;
27
+ this.#controls = controls;
28
+ this.#owner = new NativeIntegrationOwner(this.domain, { homeId: options.homeId, capacity: options.capacity });
29
+ this.configFlow = new Zigbee2MqttConfigFlow(this.#owner);
30
+ }
31
+
16
32
  public async setup(configArg: IZigbee2MqttConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
17
- const runtime = new Zigbee2MqttRuntime(configArg);
18
- await runtime.start(contextArg.abortSignal);
19
- return runtime;
33
+ const config = zigbee2MqttConfig(configArg);
34
+ return this.#owner.runSetup(contextArg, config.homeId, async (lease) => {
35
+ let resolver: TMqttCredentialResolver | undefined;
36
+ if (config.authentication === 'provider') {
37
+ const provider = this.#options.resolveCredentials;
38
+ if (!provider) throw new Error('Zigbee2MQTT provider mode requires an explicit caller resolver.');
39
+ resolver = async (request) => zigbee2MqttCredentials(config, await provider(Object.freeze({ ...request, host: config.host, port: config.port!, protocol: config.protocol!, baseTopic: config.baseTopic })));
40
+ } else if (config.authentication === 'config-store') {
41
+ const store = lease.context.configStore;
42
+ if (!store) throw new Error('Zigbee2MQTT config-store mode requires an admitted encrypted NoSqlConfigStore.');
43
+ resolver = async (request) => {
44
+ const value = zigbee2MqttRecord(await store.get<unknown>(request.credentialKey));
45
+ if (!value || Object.keys(value).some((key) => !['version', 'host', 'port', 'protocol', 'baseTopic', 'credentials'].includes(key)) ||
46
+ value.version !== 1 || value.host !== config.host || value.port !== config.port || value.protocol !== config.protocol || value.baseTopic !== config.baseTopic) {
47
+ throw new Error('Zigbee2MQTT encrypted credentials do not match the selected broker and base topic.');
48
+ }
49
+ return zigbee2MqttCredentials(config, value.credentials);
50
+ };
51
+ }
52
+ const session = new MqttSession({ ...config, discovery: false, birthMessage: false, willMessage: false, clean: true,
53
+ subscriptions: [config.baseTopic + '/#'], maxMessageTopics: (config.maxDevices ?? 2048) * 4 + 16 }, lease, resolver);
54
+ let runtime: Zigbee2MqttRuntime | undefined;
55
+ let attempts = 0;
56
+ const closeSession = (): Promise<void> => attempts++ === 0 ? session.close() : session.retryCleanup();
57
+ lease.own(() => runtime ? runtime.cleanup() : closeSession());
58
+ const client = await session.start();
59
+ lease.assertOpen();
60
+ runtime = new Zigbee2MqttRuntime(config, client, lease, closeSession, this.#controls);
61
+ await runtime.start();
62
+ lease.assertOpen();
63
+ lease.promoteToRuntime();
64
+ return runtime;
65
+ });
20
66
  }
21
67
 
22
- public async destroy(): Promise<void> {}
68
+ public destroy(): Promise<void> { return this.#owner.close(); }
69
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
23
70
  }
@@ -1,10 +1,11 @@
1
1
  import type * as plugins from '../../plugins.js';
2
2
  import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
3
- import { MqttClient } from '../mqtt/mqtt.classes.client.js';
3
+ import type { MqttClient } from '../mqtt/mqtt.classes.client.js';
4
4
  import type { IMqttMessage } from '../mqtt/mqtt.types.js';
5
5
  import { Zigbee2MqttMapper } from './zigbee2mqtt.mapper.js';
6
6
  import type { IZigbee2MqttConfig, IZigbee2MqttDevice, IZigbee2MqttReportedState, IZigbee2MqttSnapshot } from './zigbee2mqtt.types.js';
7
- import { zigbee2MqttConfig, zigbee2MqttRecord } from './zigbee2mqtt.validation.js';
7
+ import { zigbee2MqttRecord } from './zigbee2mqtt.validation.js';
8
+ import type { INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
8
9
 
9
10
  interface IZigbee2MqttListener {
10
11
  handler: (eventArg: IIntegrationEvent) => void;
@@ -14,7 +15,7 @@ interface IZigbee2MqttListener {
14
15
  export class Zigbee2MqttRuntime implements IIntegrationRuntime {
15
16
  public readonly domain = 'zigbee2mqtt';
16
17
  private readonly config: IZigbee2MqttConfig;
17
- private readonly client: MqttClient;
18
+ readonly #controls: ReadonlySet<string>;
18
19
  private readonly removeMessageHandler: () => void;
19
20
  private readonly removeConnectionHandler: () => void;
20
21
  private readonly handlers = new Set<IZigbee2MqttListener>();
@@ -34,17 +35,18 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
34
35
  private inventoryCurrent = false;
35
36
  private inventoryError?: Error;
36
37
  private destroyed = false;
37
- private destroyPromise?: Promise<void>;
38
38
  private activeWrites = 0;
39
- private removeSetupAbort?: () => void;
39
+ private readonly pendingHandlers = new Set<Promise<void>>();
40
+ private readonly callbackFailures = new Set<Error>();
41
+ private readonly ownerAbort = (): void => { this.destroyed = true; };
40
42
 
41
- constructor(configArg: IZigbee2MqttConfig) {
42
- this.config = zigbee2MqttConfig(configArg);
43
- this.client = new MqttClient({
44
- ...this.config, discovery: false, birthMessage: false, willMessage: false, clean: true,
45
- subscriptions: [this.config.baseTopic + '/#'],
46
- maxMessageTopics: (this.config.maxDevices ?? 2048) * 4 + 16,
47
- });
43
+ constructor(configArg: IZigbee2MqttConfig, private readonly client: MqttClient,
44
+ private readonly lease: INativeSetupLease, private readonly closeSession: () => Promise<void>,
45
+ controlsArg: ReadonlySet<string>) {
46
+ this.config = configArg;
47
+ this.#controls = new Set(controlsArg);
48
+ this.connected = client.connected;
49
+ lease.signal.addEventListener('abort', this.ownerAbort, { once: true });
48
50
  this.removeMessageHandler = this.client.onMessage((messageArg) => this.receive(messageArg));
49
51
  this.removeConnectionHandler = this.client.onConnectionState((stateArg) => {
50
52
  const previous = this.snapshot();
@@ -64,24 +66,16 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
64
66
  });
65
67
  }
66
68
 
67
- public async start(abortSignalArg?: AbortSignal): Promise<void> {
68
- this.assertOpen(abortSignalArg);
69
+ public async start(): Promise<void> {
70
+ this.assertOpen();
69
71
  if (this.started) throw new Error('Zigbee2MQTT runtime has already started.');
70
72
  this.started = true;
71
- if (abortSignalArg) {
72
- const abort = (): void => { void this.destroy().catch(() => this.emitError('Zigbee2MQTT cancellation cleanup failed.')); };
73
- abortSignalArg.addEventListener('abort', abort, { once: true });
74
- this.removeSetupAbort = () => abortSignalArg.removeEventListener('abort', abort);
75
- }
76
- try {
77
- await this.client.start(abortSignalArg);
78
- this.assertOpen(abortSignalArg);
79
- await this.waitForInventory(abortSignalArg);
80
- } catch (error) {
81
- try { await this.destroy(); }
82
- catch (cleanupError) { throw new AggregateError([error, cleanupError], 'Zigbee2MQTT setup and cleanup failed.'); }
83
- throw error;
73
+ // Handlers are installed before this synchronous current-generation snapshot. No await
74
+ // can let older cached observations overwrite a later callback or a reconnect.
75
+ for (const message of this.client.getCurrentSnapshot()) {
76
+ if (message.topic !== this.config.baseTopic + '/bridge/event') this.receive(message, true);
84
77
  }
78
+ await this.waitForInventory(this.lease.signal);
85
79
  }
86
80
 
87
81
  public isHealthy(): boolean {
@@ -128,6 +122,9 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
128
122
  !device || device.integrationDomain !== this.domain || typeof device.id !== 'string' || !feature || typeof feature.id !== 'string') {
129
123
  throw new Error('Zigbee2MQTT feature request requires a device owned by this domain.');
130
124
  }
125
+ const selected = this.findDevice(device.id);
126
+ const current = selected?.exposes.find((expose) => expose.property === feature.id);
127
+ if (!current || feature.capability !== current.capability) throw new Error('Zigbee2MQTT feature capability does not match the current target.');
131
128
  return await this.write(device.id, feature.id, request.value, abortSignalArg);
132
129
  } catch (error) { return this.failure(error); }
133
130
  }
@@ -149,26 +146,23 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
149
146
  selected = this.findDevice(target.entityId.slice(0, -(data.property.length + 1)));
150
147
  }
151
148
  if (!selected) throw new Error('Zigbee2MQTT target is not in the current inventory.');
152
- return await this.write(Zigbee2MqttMapper.deviceId(selected.ieeeAddress), data.property, data.value);
149
+ return await this.write(Zigbee2MqttMapper.deviceId(this.config.homeId, selected.ieeeAddress), data.property, data.value);
153
150
  } catch (error) { return this.failure(error); }
154
151
  }
155
152
 
156
- public destroy(): Promise<void> {
157
- if (!this.destroyPromise) this.destroyPromise = this.destroyConnection();
158
- return this.destroyPromise;
159
- }
153
+ public destroy(): Promise<void> { return this.lease.close(); }
160
154
 
161
155
  private async write(deviceIdArg: string, propertyArg: string, valueArg: unknown, abortSignalArg?: AbortSignal): Promise<IServiceCallResult> {
162
156
  this.assertOpen(abortSignalArg);
163
157
  const device = this.findDevice(deviceIdArg);
164
158
  const expose = device?.exposes.find((exposeArg) => exposeArg.property === propertyArg);
165
- if (!device || !expose || !Zigbee2MqttMapper.available(this.snapshot([device]), device)) throw new Error('Zigbee2MQTT target is unavailable or not exposed.');
159
+ if (!device || !expose || !Zigbee2MqttMapper.writable(this.snapshot([device]), device, expose) || !Zigbee2MqttMapper.available(this.snapshot([device]), device)) throw new Error('Zigbee2MQTT target is unavailable or not exposed.');
166
160
  const value = Zigbee2MqttMapper.commandValue(expose, valueArg);
167
161
  if (value === undefined) throw new Error('Zigbee2MQTT value is not permitted by the advertised expose.');
168
162
  if (this.activeWrites >= 64) throw new Error('Zigbee2MQTT command admission limit exceeded.');
169
163
  this.activeWrites++;
170
164
  try {
171
- const publication = this.client.publish({ topic: `${this.config.baseTopic}/${device.ieeeAddress}/set`, payload: { [expose.property]: value }, qos: 0, retain: false }, abortSignalArg);
165
+ const publication = this.client.publish({ topic: `${this.config.baseTopic}/${device.ieeeAddress}/set`, payload: { [expose.property]: value }, qos: 0, retain: false }, abortSignalArg ? AbortSignal.any([this.lease.signal, abortSignalArg]) : this.lease.signal);
172
166
  this.pendingWrites.add(publication);
173
167
  try { await publication; }
174
168
  catch { throw new Error('Zigbee2MQTT command publication failed; its physical outcome is unconfirmed.'); }
@@ -297,8 +291,8 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
297
291
  if (value === undefined) { states.delete(expose.property); this.emitError('Zigbee2MQTT reported a value outside its expose type.'); continue; }
298
292
  states.set(expose.property, { value, updatedAt: new Date(timestampArg).toISOString() });
299
293
  if (liveEventArg && (expose.property === 'action' || expose.property.startsWith('action_'))) {
300
- this.emit({ type: 'state_changed', integrationDomain: this.domain, deviceId: Zigbee2MqttMapper.deviceId(deviceArg.ieeeAddress),
301
- entityId: Zigbee2MqttMapper.entityId(deviceArg.ieeeAddress, expose.property), data: { value }, timestamp: timestampArg });
294
+ this.emit({ type: 'state_changed', integrationDomain: this.domain, deviceId: Zigbee2MqttMapper.deviceId(this.config.homeId, deviceArg.ieeeAddress),
295
+ entityId: Zigbee2MqttMapper.entityId(this.config.homeId, deviceArg.ieeeAddress, expose.property), data: { value }, timestamp: timestampArg });
302
296
  }
303
297
  }
304
298
  }
@@ -307,10 +301,10 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
307
301
  const next = this.snapshot(scopedDevicesArg);
308
302
  const previousDevices = new Map(previousArg.devices.map((deviceArg) => [deviceArg.ieeeAddress, deviceArg]));
309
303
  for (const device of next.devices) {
310
- if (!previousDevices.has(device.ieeeAddress)) this.emit({ type: 'device_added', integrationDomain: this.domain, deviceId: Zigbee2MqttMapper.deviceId(device.ieeeAddress), timestamp: Date.now() });
304
+ if (!previousDevices.has(device.ieeeAddress)) this.emit({ type: 'device_added', integrationDomain: this.domain, deviceId: Zigbee2MqttMapper.deviceId(this.config.homeId, device.ieeeAddress), timestamp: Date.now() });
311
305
  }
312
306
  for (const device of previousArg.devices) {
313
- if (!this.devicesByIeee.has(device.ieeeAddress)) this.emit({ type: 'device_removed', integrationDomain: this.domain, deviceId: Zigbee2MqttMapper.deviceId(device.ieeeAddress), timestamp: Date.now() });
307
+ if (!this.devicesByIeee.has(device.ieeeAddress)) this.emit({ type: 'device_removed', integrationDomain: this.domain, deviceId: Zigbee2MqttMapper.deviceId(this.config.homeId, device.ieeeAddress), timestamp: Date.now() });
314
308
  }
315
309
  const previous = new Map(Zigbee2MqttMapper.toEntities(previousArg).map((entityArg) => [entityArg.id, entityArg]));
316
310
  const current = new Map(Zigbee2MqttMapper.toEntities(next).map((entityArg) => [entityArg.id, entityArg]));
@@ -331,7 +325,7 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
331
325
  private snapshot(scopedDevicesArg?: IZigbee2MqttDevice[]): IZigbee2MqttSnapshot {
332
326
  const devices = scopedDevicesArg ?? [...this.devicesByIeee.values()];
333
327
  return {
334
- devices, connected: this.connected && !this.destroyed,
328
+ homeId: this.config.homeId, controls: this.#controls, devices, connected: this.connected && !this.destroyed,
335
329
  bridgeOnline: this.bridgeOnline, inventoryCurrent: this.inventoryCurrent,
336
330
  availability: new Map(devices.filter((deviceArg) => this.availability.has(deviceArg.ieeeAddress)).map((deviceArg) =>
337
331
  [deviceArg.ieeeAddress, this.availability.get(deviceArg.ieeeAddress) === true])),
@@ -340,7 +334,8 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
340
334
  }
341
335
 
342
336
  private findDevice(idArg: string): IZigbee2MqttDevice | undefined {
343
- return idArg.startsWith('zigbee2mqtt.') ? this.devicesByIeee.get(idArg.slice('zigbee2mqtt.'.length)) : undefined;
337
+ const prefix = `${encodeURIComponent(this.config.homeId)}:zigbee2mqtt.`;
338
+ return idArg.startsWith(prefix) ? this.devicesByIeee.get(idArg.slice(prefix.length)) : undefined;
344
339
  }
345
340
 
346
341
  private deviceForTopic(topicArg: string): IZigbee2MqttDevice | undefined {
@@ -370,37 +365,55 @@ export class Zigbee2MqttRuntime implements IIntegrationRuntime {
370
365
  if (this.inventoryReceived && this.bridgeReceived && this.client.connected) for (const waiter of [...this.readyWaiters]) waiter.resolve();
371
366
  }
372
367
 
373
- private async destroyConnection(): Promise<void> {
368
+ public async cleanup(): Promise<void> {
374
369
  this.destroyed = true;
375
- this.removeSetupAbort?.();
370
+ this.lease.signal.removeEventListener('abort', this.ownerAbort);
376
371
  this.removeMessageHandler();
377
372
  this.removeConnectionHandler();
378
373
  for (const waiter of [...this.readyWaiters]) waiter.reject(new Error('Zigbee2MQTT runtime closed.'));
379
- for (const cleanup of [...this.subscriptionCleanups]) await cleanup();
374
+ const cleanups = [...this.subscriptionCleanups];
380
375
  this.handlers.clear();
381
- await this.client.destroy();
382
- await Promise.allSettled([...this.pendingWrites]);
376
+ const results = await Promise.allSettled([
377
+ ...cleanups.map((cleanup) => Promise.resolve().then(cleanup)),
378
+ Promise.resolve().then(this.closeSession),
379
+ ...[...this.pendingWrites].map((write) => write.then(() => undefined, () => undefined)),
380
+ ...this.pendingHandlers,
381
+ ]);
383
382
  this.devicesByIeee.clear();
384
383
  this.devicesByName.clear();
385
384
  this.availability.clear();
386
385
  this.state.clear();
387
386
  this.pendingMessages.clear();
387
+ const failures = [...this.callbackFailures, ...results.flatMap((result) => result.status === 'rejected' ? [result.reason as unknown] : [])];
388
+ this.callbackFailures.clear();
389
+ if (failures.length) throw new AggregateError(failures, 'Zigbee2MQTT runtime cleanup failed; retry cleanup explicitly.');
388
390
  }
389
391
 
390
392
  private assertOpen(abortSignalArg?: AbortSignal): void {
391
393
  if (abortSignalArg?.aborted) throw new DOMException('Zigbee2MQTT operation aborted.', 'AbortError');
392
394
  if (this.destroyed) throw new Error('Zigbee2MQTT runtime closed.');
395
+ this.lease.assertOpen();
393
396
  }
394
397
 
395
398
  private emit(eventArg: IIntegrationEvent): void {
396
399
  for (const listener of [...this.handlers]) {
397
400
  if (!this.handlers.has(listener)) continue;
398
- try { listener.handler(structuredClone(eventArg)); }
399
- catch {
400
- // A consumer callback must not disconnect the transport shared by other subscribers.
401
+ if (this.pendingHandlers.size >= 256) {
401
402
  void listener.cleanup();
402
- this.emitError('Zigbee2MQTT subscriber callback failed.');
403
+ this.emitError('Zigbee2MQTT subscriber work limit exceeded.');
404
+ continue;
403
405
  }
406
+ const work = Promise.resolve().then(async () => {
407
+ if (!this.handlers.has(listener) || this.destroyed) return;
408
+ try { await listener.handler(structuredClone(eventArg)); }
409
+ catch {
410
+ await listener.cleanup();
411
+ if (this.destroyed) this.callbackFailures.add(new Error('Zigbee2MQTT subscriber callback failed while draining.'));
412
+ else this.emitError('Zigbee2MQTT subscriber callback failed.');
413
+ }
414
+ });
415
+ this.pendingHandlers.add(work);
416
+ void work.then(() => this.pendingHandlers.delete(work), () => this.pendingHandlers.delete(work));
404
417
  }
405
418
  }
406
419
 
@@ -1,18 +1,18 @@
1
1
  import type * as plugins from '../../plugins.js';
2
2
  import type { IIntegrationEntity } from '../../core/types.js';
3
3
  import type { IZigbee2MqttDevice, IZigbee2MqttExpose, IZigbee2MqttSnapshot, TZigbee2MqttPrimitive } from './zigbee2mqtt.types.js';
4
- import { zigbee2MqttPrimitive, zigbee2MqttRecord } from './zigbee2mqtt.validation.js';
4
+ import { zigbee2MqttControlKey, zigbee2MqttPrimitive, zigbee2MqttRecord } from './zigbee2mqtt.validation.js';
5
5
 
6
6
  const specificTypes = new Set(['light', 'switch', 'fan', 'cover', 'lock', 'climate']);
7
7
  const forbiddenProperties = new Set(['__proto__', 'constructor', 'prototype', 'read', 'write', 'command', 'commands', 'get', 'set']);
8
8
 
9
9
  export class Zigbee2MqttMapper {
10
- public static deviceId(ieeeAddressArg: string): string {
11
- return `zigbee2mqtt.${ieeeAddressArg}`;
10
+ public static deviceId(homeIdArg: string, ieeeAddressArg: string): string {
11
+ return `${encodeURIComponent(homeIdArg)}:zigbee2mqtt.${ieeeAddressArg}`;
12
12
  }
13
13
 
14
- public static entityId(ieeeAddressArg: string, propertyArg: string): string {
15
- return `${this.deviceId(ieeeAddressArg)}.${propertyArg}`;
14
+ public static entityId(homeIdArg: string, ieeeAddressArg: string, propertyArg: string): string {
15
+ return `${this.deviceId(homeIdArg, ieeeAddressArg)}.${propertyArg}`;
16
16
  }
17
17
 
18
18
  public static parseInventory(payloadArg: unknown, maxDevicesArg: number): IZigbee2MqttDevice[] {
@@ -54,12 +54,12 @@ export class Zigbee2MqttMapper {
54
54
 
55
55
  public static toDevices(snapshotArg: IZigbee2MqttSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
56
56
  return snapshotArg.devices.map((deviceArg) => ({
57
- id: this.deviceId(deviceArg.ieeeAddress), integrationDomain: 'zigbee2mqtt', name: deviceArg.friendlyName,
57
+ id: this.deviceId(snapshotArg.homeId, deviceArg.ieeeAddress), integrationDomain: 'zigbee2mqtt', name: deviceArg.friendlyName,
58
58
  manufacturer: deviceArg.manufacturer, model: deviceArg.model, protocol: 'zigbee',
59
59
  online: this.available(snapshotArg, deviceArg),
60
60
  features: deviceArg.exposes.map((exposeArg) => ({
61
61
  id: exposeArg.property, name: exposeArg.name, capability: exposeArg.capability,
62
- readable: exposeArg.readable, writable: exposeArg.writable && deviceArg.supported && !deviceArg.disabled,
62
+ readable: exposeArg.readable, writable: this.writable(snapshotArg, deviceArg, exposeArg),
63
63
  unit: exposeArg.unit,
64
64
  })),
65
65
  state: deviceArg.exposes.filter((exposeArg) => exposeArg.readable).flatMap((exposeArg) => {
@@ -67,6 +67,7 @@ export class Zigbee2MqttMapper {
67
67
  return state ? [{ featureId: exposeArg.property, value: state.value, updatedAt: state.updatedAt }] : [];
68
68
  }),
69
69
  metadata: {
70
+ homeId: snapshotArg.homeId,
70
71
  ieeeAddress: deviceArg.ieeeAddress, supported: deviceArg.supported, disabled: deviceArg.disabled,
71
72
  unsupportedExposes: deviceArg.unsupportedExposes,
72
73
  availability: snapshotArg.availability.has(deviceArg.ieeeAddress) ? snapshotArg.availability.get(deviceArg.ieeeAddress) ? 'online' : 'offline' : 'unknown',
@@ -76,18 +77,23 @@ export class Zigbee2MqttMapper {
76
77
 
77
78
  public static toEntities(snapshotArg: IZigbee2MqttSnapshot): IIntegrationEntity[] {
78
79
  return snapshotArg.devices.flatMap((deviceArg) => deviceArg.exposes.map((exposeArg) => ({
79
- id: this.entityId(deviceArg.ieeeAddress, exposeArg.property),
80
- uniqueId: this.entityId(deviceArg.ieeeAddress, exposeArg.property),
81
- integrationDomain: 'zigbee2mqtt', deviceId: this.deviceId(deviceArg.ieeeAddress),
80
+ id: this.entityId(snapshotArg.homeId, deviceArg.ieeeAddress, exposeArg.property),
81
+ uniqueId: this.entityId(snapshotArg.homeId, deviceArg.ieeeAddress, exposeArg.property),
82
+ integrationDomain: 'zigbee2mqtt', deviceId: this.deviceId(snapshotArg.homeId, deviceArg.ieeeAddress),
82
83
  name: `${deviceArg.friendlyName} ${exposeArg.name}`, platform: exposeArg.platform,
83
84
  available: this.available(snapshotArg, deviceArg),
84
85
  state: exposeArg.readable ? snapshotArg.state.get(deviceArg.ieeeAddress)?.get(exposeArg.property)?.value ?? null : null,
85
- attributes: { property: exposeArg.property, readable: exposeArg.readable, writable: exposeArg.writable && deviceArg.supported && !deviceArg.disabled,
86
+ attributes: { homeId: snapshotArg.homeId, property: exposeArg.property, readable: exposeArg.readable, writable: this.writable(snapshotArg, deviceArg, exposeArg),
86
87
  unit: exposeArg.unit, minimum: exposeArg.minimum, maximum: exposeArg.maximum, step: exposeArg.step,
87
88
  values: exposeArg.values ? [...exposeArg.values] : undefined },
88
89
  })));
89
90
  }
90
91
 
92
+ public static writable(snapshotArg: IZigbee2MqttSnapshot, deviceArg: IZigbee2MqttDevice, exposeArg: IZigbee2MqttExpose): boolean {
93
+ return snapshotArg.inventoryCurrent && exposeArg.writable && deviceArg.supported && !deviceArg.disabled &&
94
+ snapshotArg.controls.has(zigbee2MqttControlKey(deviceArg.ieeeAddress, exposeArg.property));
95
+ }
96
+
91
97
  public static available(snapshotArg: IZigbee2MqttSnapshot, deviceArg: IZigbee2MqttDevice): boolean {
92
98
  return snapshotArg.connected && snapshotArg.bridgeOnline && snapshotArg.inventoryCurrent &&
93
99
  deviceArg.supported && !deviceArg.disabled && snapshotArg.availability.get(deviceArg.ieeeAddress) === true;