@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
@@ -1,4 +1,41 @@
1
1
  import type { IMqttConfig } from '../mqtt/mqtt.types.js';
2
+ import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
3
+
4
+ export interface ITasmotaBrokerIdentity {
5
+ readonly host: string;
6
+ readonly port: number;
7
+ readonly protocol: 'mqtt' | 'mqtts';
8
+ }
9
+
10
+ export interface ITasmotaCredential extends ITasmotaBrokerIdentity {
11
+ readonly version: 1;
12
+ readonly username?: string;
13
+ readonly password?: string;
14
+ readonly ca?: string;
15
+ readonly cert?: string;
16
+ readonly key?: string;
17
+ readonly servername?: string;
18
+ }
19
+
20
+ export interface ITasmotaCredentialRequest {
21
+ readonly homeId: string;
22
+ readonly credentialRef: string;
23
+ readonly broker: ITasmotaBrokerIdentity;
24
+ readonly signal: AbortSignal;
25
+ /** Absolute deadline on performance.now(), not wall-clock time. */
26
+ readonly deadlineMonotonicMs: number;
27
+ }
28
+
29
+ export type TTasmotaCredentialResolver = (requestArg: ITasmotaCredentialRequest) => Promise<ITasmotaCredential>;
30
+
31
+ export interface ITasmotaIntegrationOptions extends INativeIntegrationOwnerOptions {
32
+ /** Runtime-only embedding-owner permission. Defaults to false. */
33
+ readonly enableControls?: boolean;
34
+ readonly credentialResolver?: TTasmotaCredentialResolver;
35
+ }
36
+
37
+ export type TTasmotaAuthentication = { readonly mode: 'anonymous' }
38
+ | { readonly mode: 'credential-reference'; readonly credentialRef: string };
2
39
 
3
40
  export interface ITasmotaDeviceConfig {
4
41
  topic: string;
@@ -14,7 +51,9 @@ export interface ITasmotaDeviceConfig {
14
51
  }
15
52
 
16
53
  export interface IHomeAssistantTasmotaConfig {
17
- broker: IMqttConfig & { host: string };
54
+ readonly homeId: string;
55
+ broker: Omit<IMqttConfig, 'username' | 'password'> & { host: string };
56
+ readonly authentication: TTasmotaAuthentication;
18
57
  devices: ITasmotaDeviceConfig[];
19
58
  initialSnapshotTimeoutMs?: number;
20
59
  }
@@ -34,7 +73,9 @@ export interface ITasmotaSelectedDevice {
34
73
  }
35
74
 
36
75
  export interface ITasmotaConfig {
37
- broker: IMqttConfig & { host: string };
76
+ readonly homeId: string;
77
+ readonly authentication: TTasmotaAuthentication;
78
+ broker: Omit<IMqttConfig, 'username' | 'password'> & { host: string };
38
79
  devices: ITasmotaSelectedDevice[];
39
80
  initialSnapshotTimeoutMs: number;
40
81
  }
@@ -1,7 +1,7 @@
1
1
  # Tuya local protocol 3.4
2
2
 
3
3
  `TuyaIntegration` and the existing `HomeAssistantTuyaIntegration` export provide a
4
- native local runtime through published `@ecobridge.xyz/devicemanager` 4.2.0.
4
+ native local runtime through published `@ecobridge.xyz/devicemanager` 4.11.1.
5
5
  Setup requires an explicit home, device ID, literal private/link-local/loopback
6
6
  IPv4 endpoint, local key and this product's verified datapoint schema. Discovery
7
7
  has no broadcast probes and makes no claim to locate devices or obtain credentials.
@@ -33,11 +33,12 @@ const config: IHomeAssistantTuyaConfig = {
33
33
  { id: 108, name: 'Temperature', kind: 'sensor', valueType: 'integer', scale: 1, unit: '°C' },
34
34
  ],
35
35
  };
36
- const integration = new TuyaIntegration();
36
+ const integration = new TuyaIntegration({ homeId: 'home-one' });
37
+ const context = Object.freeze({ homeId: 'home-one', configStore: store });
37
38
  // One explicit provisioning action stores configuration and the opaque 16-byte
38
39
  // local key together, encrypted under the caller's key provider.
39
- await integration.provisionLocalKey(config, configuredLocalKeyBytes, { configStore: store });
40
- const runtime = await integration.setup(config, { configStore: store });
40
+ await integration.provisionLocalKey(config, configuredLocalKeyBytes, context);
41
+ const runtime = await integration.setup(config, context);
41
42
  try {
42
43
  const [device] = await runtime.devices();
43
44
  const power = device.features.find((feature) => feature.id === 'dp_7');
@@ -52,6 +53,14 @@ try {
52
53
  }
53
54
  ```
54
55
 
56
+ The host must independently select `context.homeId` for provisioning and setup.
57
+ It must match the configuration home and the actual store scope; it is never
58
+ inferred from either. An optional constructor `homeId` constrains the instance.
59
+ Otherwise, the first valid synchronous admission fixes its home even if subsequent
60
+ I/O fails. Configuration, context fields and local-key bytes are captured before
61
+ asynchronous work. Accessors and overridden store-scope methods cannot grant
62
+ admission. The host still owns authenticated access to the selected home.
63
+
55
64
  The store must be an actual `NoSqlConfigStore` scoped to the exact configured home
56
65
  and `tuya` domain. Generic stores are rejected. Configuration and credentials use
57
66
  `device:<configured device ID>` in that encrypted scope; setup rejects a different
@@ -85,14 +94,38 @@ arrive. Failures retain `effectOutcome: 'not-sent'`, `'rejected'` or `'indetermi
85
94
  a transmitted effect with a lost/late response may already have happened. There is
86
95
  no retry, retransmission or reconnect. Lifetime/setup cancellation and feature-write
87
96
  signals are supported. `destroy()` is permanent, idempotent and drains setup and
88
- protocol operations; use a new integration for a new lifetime. Cached reads after
89
- shutdown remain marked unavailable with their original report timestamps.
97
+ protocol operations and admitted asynchronous subscriber continuations. A fulfilled
98
+ provisioning write remains fulfilled if cancellation or close arrives during its
99
+ persistence; close joins the actual write result and never replays it. Setup and
100
+ provisioning are reserved before store or protocol callbacks run. Destruction
101
+ publishes one stable close promise before abort callbacks run. Caller cancellation
102
+ also closes promoted runtime ownership and releases its slot after cleanup.
103
+ Use a new integration for a new lifetime. Cached reads after
104
+ shutdown remain unavailable and read-only with their original report timestamps.
90
105
 
91
- Subscriptions are bounded to 128 listeners, abortable and explicitly removable.
92
- Handlers must not block. Asynchronous handler work is caller-owned; handler failures
93
- are reported through the supplied logger using a generic credential-free message.
106
+ Subscriptions are bounded to 128 listeners and 128 outstanding callback
107
+ continuations, abortable and explicitly removable. Handlers must not block or
108
+ await destruction of their own runtime: destruction joins their continuation.
109
+ Returned promises and thenables are owned and joined even after listener removal.
110
+ Callback failures are retained through cleanup and logged with a generic,
111
+ credential-free message. Overflow closes the runtime and retains the failure.
94
112
  All listeners are removed during destruction.
95
- One integration owns at most 32 simultaneous setup/provisioning/runtime lifetimes.
113
+
114
+ The shared native owner accounts for pending setup/provisioning operations,
115
+ promoted runtimes and retained failed cleanup. Its default limit is 16; the optional
116
+ constructor `capacity` selects a positive safe-integer limit. A failed cleanup
117
+ retains its capacity slot and rejects destruction. `integration.retryCleanup()`
118
+ retries only failed cleanup handles and explicitly acknowledges already-drained
119
+ subscriber failures; it never reruns callbacks, provisioning, queries or controls,
120
+ and never reopens a session. It does not change the original rejected close promise.
121
+ A resource whose owning cleanup still rejects remains retained.
122
+
123
+ Per-DP provisioned `read-write` access continues to grant control. There is no
124
+ additional host control-permission API and no automatic preflight or post-write
125
+ readback. Those public-contract changes require a separate product decision, as
126
+ does a config flow. Protocol authentication proves possession of the local key;
127
+ identity fields are checked when present, but this is not independent hardware
128
+ attestation. Firmware qualification remains separate from fixture verification.
96
129
 
97
130
  Other protocol versions, cloud-only and sleeping devices, gateway children, BLE,
98
131
  Zigbee, raw/bitmap/enum mappings and arbitrary actions are unsupported. The shared
@@ -101,6 +134,15 @@ device protocol union currently has no Tuya member, so the generic device report
101
134
  loopback fixture tests cover the implementation; qualification against real product
102
135
  firmware requires separately authorized hardware access.
103
136
 
137
+ Fixture qualification uses `pnpm exec tstest test/tuya/ --verbose --logfile --timeout 60`:
138
+ `test.tuya.node.ts` contains 28 encrypted persistence/protocol cases and
139
+ `test.tuya.owner.node.ts` adds 22 admission, reentry, cancellation, capacity,
140
+ accepted-persistence, retained-cleanup and callback-continuation regressions.
141
+ The 50-case suite passes with no device or cloud account. Strict domain/test checks
142
+ use `strict: true`, `noImplicitAny: true` and `skipLibCheck: false`; production
143
+ build and the complete test type check are also required. This fixture evidence
144
+ does not establish physical acceptance for a particular product or firmware.
145
+
104
146
  Product types and permissions follow the vendor's [DP Model and Control Protocol](https://developer.tuya.com/en/docs/iot-device-dev/TuyaOS-iot_abi_dp_ctrl?id=Kcoglhn5r7ajr).
105
147
  LAN framing is implemented by the owning dependency, whose public API and wire
106
148
  contract are documented in its [readme](https://code.foss.global/ecobridge.xyz/devicemanager).
@@ -1,10 +1,11 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
+ import { NativeIntegrationOwner } from '../../core/classes.nativeintegrationowner.js';
3
4
  import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
4
5
  import type { IIntegrationRuntime, IIntegrationSetupContext } from '../../core/types.js';
5
6
  import { TuyaRuntime } from './tuya.classes.runtime.js';
6
- import type { IHomeAssistantTuyaConfig, ITuyaStoredConfiguration } from './tuya.types.js';
7
- import { normalizeTuyaConfig, scopedTuyaStore, tuyaCredentialId, tuyaKeys, tuyaRecord } from './tuya.validation.js';
7
+ import type { IHomeAssistantTuyaConfig, ITuyaIntegrationOptions, ITuyaStoredConfiguration } from './tuya.types.js';
8
+ import { normalizeTuyaConfig, tuyaSetupContext, tuyaCredentialId, tuyaKeys, tuyaRecord } from './tuya.validation.js';
8
9
 
9
10
  export class TuyaIntegration extends BaseIntegration<IHomeAssistantTuyaConfig> {
10
11
  public readonly domain = 'tuya';
@@ -12,118 +13,88 @@ export class TuyaIntegration extends BaseIntegration<IHomeAssistantTuyaConfig> {
12
13
  public readonly status = 'control-runtime' as const;
13
14
  public readonly discoveryDescriptor = new DiscoveryDescriptor({ integrationDomain: 'tuya', displayName: 'Tuya 3.4 local device' });
14
15
  public readonly metadata = Object.freeze({ source: 'native-typescript', integrationType: 'device', iotClass: 'local_push', protocolVersion: '3.4' });
15
- readonly #setups = new Set<Promise<unknown>>();
16
- readonly #controllers = new Set<AbortController>();
17
- readonly #runtimes = new Set<TuyaRuntime>();
18
- #destroyed = false;
19
- #destroy?: Promise<void>;
16
+ readonly #owner: NativeIntegrationOwner;
17
+
18
+ constructor(optionsArg: ITuyaIntegrationOptions = {}) {
19
+ super();
20
+ this.#owner = new NativeIntegrationOwner(this.domain, optionsArg);
21
+ }
20
22
 
21
23
  public provisionLocalKey(configArg: IHomeAssistantTuyaConfig, localKeyArg: Uint8Array, contextArg: IIntegrationSetupContext): Promise<void> {
22
- this.#assertOpen();
23
- this.#assertCapacity();
24
24
  const config = normalizeTuyaConfig(configArg);
25
- const store = scopedTuyaStore(config, contextArg);
25
+ const context = tuyaSetupContext(contextArg);
26
26
  if (!(localKeyArg instanceof Uint8Array) || localKeyArg.byteLength !== 16) throw new TypeError('Tuya local key must contain exactly 16 opaque bytes.');
27
27
  const copy = Buffer.from(localKeyArg);
28
- const controller = new AbortController();
29
- this.#controllers.add(controller);
30
- const signal = contextArg.abortSignal ? AbortSignal.any([controller.signal, contextArg.abortSignal]) : controller.signal;
31
- const operation = (async () => {
32
- try {
33
- signal.throwIfAborted();
34
- await store.initialize({ signal });
35
- this.#assertOpen();
36
- signal.throwIfAborted();
28
+ try {
29
+ return this.#owner.runOperation(context, config.homeId, async (leaseArg) => {
30
+ const store = leaseArg.context.configStore!;
31
+ await store.initialize({ signal: leaseArg.signal });
32
+ leaseArg.assertOpen();
37
33
  await store.set<ITuyaStoredConfiguration>(tuyaCredentialId(config.deviceId), { version: 1, config, localKey: copy.toString('base64') });
38
- this.#assertOpen();
39
- signal.throwIfAborted();
40
- } finally { copy.fill(0); this.#controllers.delete(controller); }
41
- })();
42
- return this.#track(operation);
34
+ // A fulfilled admitted write stays fulfilled even if close/cancellation arrives during persistence.
35
+ }).finally(() => copy.fill(0));
36
+ } catch (errorArg) { copy.fill(0); throw errorArg; }
43
37
  }
44
38
 
45
39
  public setup(configArg: IHomeAssistantTuyaConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
46
- this.#assertOpen();
47
- this.#assertCapacity();
48
40
  const config = normalizeTuyaConfig(configArg);
49
- const store = scopedTuyaStore(config, contextArg);
50
- const controller = new AbortController();
51
- this.#controllers.add(controller);
52
- const signal = contextArg.abortSignal ? AbortSignal.any([controller.signal, contextArg.abortSignal]) : controller.signal;
53
- const operation = (async () => {
54
- let protocol: plugins.ecobridgeDeviceManager.TuyaProtocol | undefined;
41
+ const context = tuyaSetupContext(contextArg);
42
+ return this.#owner.runSetup(context, config.homeId, async (leaseArg) => {
43
+ const store = leaseArg.context.configStore!;
44
+ const signal = leaseArg.signal;
45
+ await store.initialize({ signal });
46
+ leaseArg.assertOpen();
47
+ const stored = tuyaRecord(await store.get<unknown>(tuyaCredentialId(config.deviceId)), 'stored configuration');
48
+ tuyaKeys(stored, ['version', 'config', 'localKey']);
49
+ if (stored.version !== 1 || JSON.stringify(normalizeTuyaConfig(stored.config)) !== JSON.stringify(config)) {
50
+ throw new TypeError('Tuya configuration does not match its encrypted provisioned configuration.');
51
+ }
52
+ if (typeof stored.localKey !== 'string' || stored.localKey.length !== 24) throw new TypeError('Tuya stored local key is invalid.');
53
+ const key = Buffer.from(stored.localKey, 'base64');
54
+ let protocol: plugins.ecobridgeDeviceManager.TuyaProtocol;
55
+ let runtime: TuyaRuntime | undefined;
55
56
  let unsubscribeAdmission: (() => void) | undefined;
57
+ let cleanupAttempted = false;
56
58
  try {
57
- signal.throwIfAborted();
58
- await store.initialize({ signal });
59
- signal.throwIfAborted();
60
- const stored = tuyaRecord(await store.get<unknown>(tuyaCredentialId(config.deviceId)), 'stored configuration');
61
- tuyaKeys(stored, ['version', 'config', 'localKey']);
62
- if (stored.version !== 1 || JSON.stringify(normalizeTuyaConfig(stored.config)) !== JSON.stringify(config)) {
63
- throw new TypeError('Tuya configuration does not match its encrypted provisioned configuration.');
64
- }
65
- if (typeof stored.localKey !== 'string' || stored.localKey.length !== 24) throw new TypeError('Tuya stored local key is invalid.');
66
- const key = Buffer.from(stored.localKey, 'base64');
67
- try {
68
- if (key.byteLength !== 16 || key.toString('base64') !== stored.localKey) throw new TypeError('Tuya stored local key is invalid.');
69
- signal.throwIfAborted();
70
- protocol = new plugins.ecobridgeDeviceManager.TuyaProtocol({
71
- deviceId: config.deviceId, localKey: key, host: config.host, port: config.port, version: config.version,
72
- connectTimeoutMs: config.connectTimeoutMs, requestTimeoutMs: config.requestTimeoutMs,
73
- heartbeatIntervalMs: config.heartbeatIntervalMs, abortSignal: signal,
74
- });
75
- } finally { key.fill(0); }
76
- const reports: plugins.ecobridgeDeviceManager.ITuyaDatapointReport[] = [];
77
- let admissionOverflow = false;
78
- unsubscribeAdmission = protocol.subscribe((reportArg) => {
79
- if (reports.length >= 128) { admissionOverflow = true; controller.abort(); }
80
- else reports.push(reportArg);
59
+ if (key.byteLength !== 16 || key.toString('base64') !== stored.localKey) throw new TypeError('Tuya stored local key is invalid.');
60
+ leaseArg.assertOpen();
61
+ protocol = new plugins.ecobridgeDeviceManager.TuyaProtocol({
62
+ deviceId: config.deviceId, localKey: key, host: config.host, port: config.port, version: config.version,
63
+ connectTimeoutMs: config.connectTimeoutMs, requestTimeoutMs: config.requestTimeoutMs,
64
+ heartbeatIntervalMs: config.heartbeatIntervalMs, abortSignal: signal,
81
65
  });
82
- await protocol.connect(signal);
83
- await protocol.queryDatapoints(signal);
84
- if (admissionOverflow) throw new Error('Tuya admission report capacity exceeded.');
85
- signal.throwIfAborted();
86
- this.#assertOpen();
87
- unsubscribeAdmission();
88
- unsubscribeAdmission = undefined;
89
- const runtime = new TuyaRuntime(config, protocol, reports, () => {
90
- this.#runtimes.delete(runtime);
91
- this.#controllers.delete(controller);
92
- }, contextArg.logger);
93
- this.#runtimes.add(runtime);
94
- return runtime;
95
- } catch (errorArg) {
96
- controller.abort();
97
- unsubscribeAdmission?.();
98
- await protocol?.close();
99
- this.#controllers.delete(controller);
100
- throw errorArg;
101
- }
102
- })();
103
- return this.#track(operation);
104
- }
105
-
106
- public destroy(): Promise<void> {
107
- if (this.#destroy) return this.#destroy;
108
- this.#destroyed = true;
109
- for (const controller of this.#controllers) controller.abort();
110
- this.#destroy = (async () => {
111
- await Promise.allSettled([...this.#setups]);
112
- await Promise.all([...this.#runtimes].map((runtimeArg) => runtimeArg.destroy()));
113
- this.#controllers.clear();
114
- })();
115
- return this.#destroy;
66
+ // One composite handle transfers protocol and callback cleanup together to the runtime.
67
+ leaseArg.own(async () => {
68
+ const retry = cleanupAttempted;
69
+ cleanupAttempted = true;
70
+ unsubscribeAdmission?.();
71
+ unsubscribeAdmission = undefined;
72
+ if (runtime) await runtime.closeOwnedResources(retry);
73
+ else await protocol.close();
74
+ });
75
+ } finally { key.fill(0); }
76
+ const reports: plugins.ecobridgeDeviceManager.ITuyaDatapointReport[] = [];
77
+ let admissionOverflow = false;
78
+ unsubscribeAdmission = protocol.subscribe((reportArg) => {
79
+ if (reports.length >= 128) { admissionOverflow = true; void leaseArg.close().catch(() => undefined); }
80
+ else reports.push(reportArg);
81
+ });
82
+ await protocol.connect(signal);
83
+ await protocol.queryDatapoints(signal);
84
+ if (admissionOverflow) throw new Error('Tuya admission report capacity exceeded.');
85
+ leaseArg.assertOpen();
86
+ unsubscribeAdmission();
87
+ unsubscribeAdmission = undefined;
88
+ runtime = new TuyaRuntime(config, protocol, reports, leaseArg);
89
+ leaseArg.promoteToRuntime();
90
+ return runtime;
91
+ });
116
92
  }
117
93
 
118
- #assertOpen(): void { if (this.#destroyed) throw new Error('Tuya integration is permanently destroyed.'); }
94
+ public destroy(): Promise<void> { return this.#owner.close(); }
119
95
 
120
- #assertCapacity(): void { if (this.#controllers.size >= 32) throw new Error('Tuya integration lifetime capacity reached.'); }
121
-
122
- #track<TResult>(operationArg: Promise<TResult>): Promise<TResult> {
123
- this.#setups.add(operationArg);
124
- void operationArg.then(() => this.#setups.delete(operationArg), () => this.#setups.delete(operationArg));
125
- return operationArg;
126
- }
96
+ /** Retry retained cleanup only; never reopen a session or replay an accepted operation. */
97
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
127
98
  }
128
99
 
129
100
  export class HomeAssistantTuyaIntegration extends TuyaIntegration {}
@@ -1,47 +1,57 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, ILogger, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
2
+ import type { INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
3
+ import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
3
4
  import type { IHomeAssistantTuyaConfig, TTuyaDatapointMapping } from './tuya.types.js';
4
5
  import { tuyaDeviceId, tuyaDevices, tuyaEntities, tuyaEntityId, tuyaFeatureId, type ITuyaObservedDatapoint } from './tuya.mapper.js';
5
6
  import { tuyaKeys, tuyaRecord, tuyaValueMatches } from './tuya.validation.js';
6
7
 
8
+ interface ITuyaCleanupResource {
9
+ readonly cleanup: () => Promise<void>;
10
+ promise?: Promise<void>;
11
+ failed: boolean;
12
+ }
13
+
7
14
  export class TuyaRuntime implements IIntegrationRuntime {
8
15
  public readonly domain = 'tuya';
9
16
  readonly #config: IHomeAssistantTuyaConfig;
10
17
  readonly #protocol: plugins.ecobridgeDeviceManager.TuyaProtocol;
11
- readonly #onDestroyed: () => void;
12
- readonly #logger?: ILogger;
18
+ readonly #lease: INativeSetupLease;
13
19
  readonly #observed = new Map<number, ITuyaObservedDatapoint>();
14
20
  readonly #subscriptions = new Set<(eventArg: IIntegrationEvent) => void>();
15
21
  readonly #subscriptionCleanup = new Set<() => void>();
16
- readonly #unsubscribe: () => void;
17
- readonly #unsubscribeErrors: () => void;
22
+ readonly #resources: ITuyaCleanupResource[] = [];
23
+ readonly #callbacks = new Set<Promise<void>>();
24
+ #subscriberFailure = false;
18
25
  #closed = false;
19
26
  #destroy?: Promise<void>;
20
27
 
21
28
  constructor(configArg: IHomeAssistantTuyaConfig, protocolArg: plugins.ecobridgeDeviceManager.TuyaProtocol,
22
- reportsArg: readonly plugins.ecobridgeDeviceManager.ITuyaDatapointReport[], onDestroyedArg: () => void, loggerArg?: ILogger) {
29
+ reportsArg: readonly plugins.ecobridgeDeviceManager.ITuyaDatapointReport[], leaseArg: INativeSetupLease) {
23
30
  this.#config = configArg;
24
31
  this.#protocol = protocolArg;
25
- this.#onDestroyed = onDestroyedArg;
26
- this.#logger = loggerArg;
32
+ this.#lease = leaseArg;
33
+ this.#resources.push({ cleanup: () => this.#protocol.close(), failed: false });
27
34
  for (const report of reportsArg) this.#accept(report);
28
35
  if (this.#observed.size === 0) throw new Error('Tuya device reported none of the configured product datapoints.');
29
- this.#unsubscribe = this.#protocol.subscribe((report) => {
36
+ const unsubscribe = this.#protocol.subscribe((report) => {
37
+ if (this.#lease.signal.aborted || this.#destroy) return;
30
38
  try { this.#accept(report); }
31
39
  catch {
32
40
  this.#closed = true;
33
41
  this.#emit({ type: 'error', data: { code: 'schema-mismatch', message: 'Tuya reported data no longer matches the configured product schema.' } });
34
42
  this.#emit({ type: 'availability_changed', data: { available: false } });
35
- void this.destroy();
43
+ void this.destroy().catch(() => undefined);
36
44
  }
37
45
  });
38
- this.#unsubscribeErrors = this.#protocol.subscribeErrors((error) => {
46
+ this.#resources.push({ cleanup: async () => unsubscribe(), failed: false });
47
+ const unsubscribeErrors = this.#protocol.subscribeErrors((error) => {
39
48
  this.#emit({ type: 'error', data: { code: error.code, effectOutcome: error.effectOutcome } });
40
49
  if (!this.#protocol.connected) this.#emit({ type: 'availability_changed', data: { available: false } });
41
50
  });
51
+ this.#resources.push({ cleanup: async () => unsubscribeErrors(), failed: false });
42
52
  }
43
53
 
44
- public isHealthy(): boolean { return !this.#closed && this.#protocol.connected; }
54
+ public isHealthy(): boolean { return !this.#closed && !this.#lease.signal.aborted && this.#protocol.connected; }
45
55
 
46
56
  public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
47
57
  signalArg?.throwIfAborted();
@@ -53,6 +63,7 @@ export class TuyaRuntime implements IIntegrationRuntime {
53
63
  }
54
64
 
55
65
  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
66
+ this.#lease.assertOpen();
56
67
  if (this.#closed) throw new Error('Tuya runtime is permanently closed.');
57
68
  if (typeof handlerArg !== 'function') throw new TypeError('Tuya subscription handler must be a function.');
58
69
  signalArg?.throwIfAborted();
@@ -106,16 +117,37 @@ export class TuyaRuntime implements IIntegrationRuntime {
106
117
  } catch (errorArg) { return this.#failure(errorArg); }
107
118
  }
108
119
 
109
- public destroy(): Promise<void> {
110
- if (this.#destroy) return this.#destroy;
111
- this.#closed = true;
112
- this.#unsubscribe();
113
- this.#unsubscribeErrors();
114
- for (const cleanup of [...this.#subscriptionCleanup]) cleanup();
115
- this.#destroy = this.#protocol.close().then(() => this.#onDestroyed());
120
+ public destroy(): Promise<void> { return this.#lease.close(); }
121
+
122
+ /** Invoked by the retained owner handle, including explicit cleanup-only retry. */
123
+ public closeOwnedResources(retryArg = false): Promise<void> {
124
+ if (retryArg) return this.#drain(true);
125
+ if (!this.#destroy) {
126
+ this.#closed = true;
127
+ this.#destroy = Promise.resolve().then(() => this.#drain(false));
128
+ }
116
129
  return this.#destroy;
117
130
  }
118
131
 
132
+ async #drain(retryArg: boolean): Promise<void> {
133
+ for (const cleanup of [...this.#subscriptionCleanup]) cleanup();
134
+ // The first close already joined every admitted callback. Explicit retry acknowledges its
135
+ // retained failure; subscriber code is never invoked again as part of cleanup.
136
+ if (retryArg) this.#subscriberFailure = false;
137
+ const cleanup = this.#resources.map((resourceArg) => {
138
+ if (retryArg && resourceArg.failed) { resourceArg.promise = undefined; resourceArg.failed = false; }
139
+ resourceArg.promise ??= Promise.resolve().then(resourceArg.cleanup).catch(() => {
140
+ resourceArg.failed = true;
141
+ throw new Error('Tuya owned resource cleanup failed.');
142
+ });
143
+ return resourceArg.promise;
144
+ });
145
+ const [results] = await Promise.all([Promise.allSettled(cleanup), Promise.allSettled([...this.#callbacks])]);
146
+ const failures = results.filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected').map((resultArg) => resultArg.reason as unknown);
147
+ if (this.#subscriberFailure) failures.push(new Error('Tuya event subscriber failed.'));
148
+ if (failures.length) throw new AggregateError(failures, 'Tuya cleanup failed; ownership remains retained for explicit retry.');
149
+ }
150
+
119
151
  #accept(reportArg: plugins.ecobridgeDeviceManager.ITuyaDatapointReport): void {
120
152
  const changes: ITuyaObservedDatapoint[] = [];
121
153
  for (const mapping of this.#config.datapoints) {
@@ -134,6 +166,7 @@ export class TuyaRuntime implements IIntegrationRuntime {
134
166
  }
135
167
 
136
168
  async #write(mappingArg: TTuyaDatapointMapping, valueArg: unknown, signalArg?: AbortSignal): Promise<IServiceCallResult> {
169
+ this.#lease.assertOpen();
137
170
  if (!this.isHealthy()) throw new Error('Tuya session is unavailable; no automatic reconnect or retry is performed.');
138
171
  if (mappingArg.kind === 'sensor' || mappingArg.access !== 'read-write') throw new Error('Tuya datapoint is read-only.');
139
172
  if (!tuyaValueMatches(mappingArg, valueArg)) throw new TypeError('Tuya write value does not match its configured type/range/step.');
@@ -151,16 +184,29 @@ export class TuyaRuntime implements IIntegrationRuntime {
151
184
 
152
185
  #emit(eventArg: Omit<IIntegrationEvent, 'integrationDomain' | 'deviceId' | 'timestamp'>): void {
153
186
  for (const handler of [...this.#subscriptions]) {
154
- if (this.#destroy) return;
187
+ if (this.#destroy || this.#lease.signal.aborted) return;
188
+ if (this.#callbacks.size >= 128) {
189
+ this.#subscriberFailed();
190
+ void this.destroy().catch(() => undefined);
191
+ return;
192
+ }
193
+ // Reserve a continuation before entering foreign code, including synchronous close reentry.
194
+ let finish!: () => void;
195
+ const continuation = new Promise<void>((resolveArg) => { finish = resolveArg; });
196
+ this.#callbacks.add(continuation);
197
+ const settled = (): void => { this.#callbacks.delete(continuation); finish(); };
155
198
  try {
156
199
  const result: unknown = handler({ ...structuredClone(eventArg), integrationDomain: 'tuya', deviceId: tuyaDeviceId(this.#config), timestamp: Date.now() });
157
- if (result instanceof Promise) void result.catch(() => this.#subscriberFailed());
158
- } catch { this.#subscriberFailed(); }
200
+ if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
201
+ void Promise.resolve(result).then(settled, () => { this.#subscriberFailed(); settled(); });
202
+ } else settled();
203
+ } catch { this.#subscriberFailed(); settled(); }
159
204
  }
160
205
  }
161
206
 
162
207
  #subscriberFailed(): void {
163
- if (this.#destroy) return;
164
- this.#logger?.log('error', 'Tuya event subscriber failed.', { code: 'subscriber-error' });
208
+ this.#subscriberFailure = true;
209
+ try { this.#lease.context.logger?.log('error', 'Tuya event subscriber failed.', { code: 'subscriber-error' }); }
210
+ catch { /* Logging cannot detach or replace a retained subscriber failure. */ }
165
211
  }
166
212
  }
@@ -25,7 +25,7 @@ export const tuyaDevices = (configArg: IHomeAssistantTuyaConfig, observedArg: re
25
25
  protocol: 'unknown', manufacturer: 'Tuya', online: availableArg,
26
26
  features: observedArg.map(({ mapping }) => ({
27
27
  id: tuyaFeatureId(mapping), name: mapping.name, readable: true,
28
- writable: mapping.kind !== 'sensor' && mapping.access === 'read-write',
28
+ writable: availableArg && mapping.kind !== 'sensor' && mapping.access === 'read-write',
29
29
  capability: mapping.kind === 'switch' ? 'switch' : mapping.kind === 'brightness' ? 'light' : 'sensor',
30
30
  ...(mapping.kind === 'sensor' && mapping.unit !== undefined ? { unit: mapping.unit } : {}),
31
31
  })),
@@ -1,3 +1,7 @@
1
+ import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
2
+
3
+ export interface ITuyaIntegrationOptions extends INativeIntegrationOwnerOptions {}
4
+
1
5
  export type TTuyaDatapointAccess = 'read' | 'read-write';
2
6
 
3
7
  export interface ITuyaDatapointIdentity {
@@ -97,15 +97,12 @@ export const normalizeTuyaConfig = (valueArg: unknown): IHomeAssistantTuyaConfig
97
97
  });
98
98
  };
99
99
 
100
- export const scopedTuyaStore = (configArg: IHomeAssistantTuyaConfig, contextArg: IIntegrationSetupContext): NoSqlConfigStore => {
100
+ /** Capture data fields before admission; the shared owner verifies home and actual store scope. */
101
+ export const tuyaSetupContext = (contextArg: IIntegrationSetupContext): IIntegrationSetupContext => {
101
102
  const context = tuyaRecord(contextArg, 'setup context');
102
- tuyaKeys(context, ['configStore', 'abortSignal', 'logger']);
103
- const store = context.configStore;
104
- if (!(store instanceof NoSqlConfigStore)) throw new TypeError('Tuya requires an encrypted NoSqlConfigStore and caller-owned encryption key provider.');
105
- const scope = store.getScope();
106
- if (scope.homeId !== configArg.homeId || scope.integrationDomain !== 'tuya') throw new TypeError('Tuya encrypted store must belong to the exact configured home and integration.');
107
- if (context.abortSignal !== undefined && !(context.abortSignal instanceof AbortSignal)) throw new TypeError('Tuya setup cancellation must be an AbortSignal.');
108
- return store;
103
+ tuyaKeys(context, ['homeId', 'configStore', 'abortSignal', 'logger']);
104
+ if (!(context.configStore instanceof NoSqlConfigStore)) throw new TypeError('Tuya requires an encrypted NoSqlConfigStore and caller-owned encryption key provider.');
105
+ return Object.freeze(context) as IIntegrationSetupContext;
109
106
  };
110
107
 
111
108
  export const tuyaCredentialId = (deviceIdArg: string): string => `device:${deviceIdArg}`;