@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,48 +1,53 @@
1
- import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
- import type { IShellyConfig } from './shelly.types.js';
1
+ import type { IConfigFlow, IConfigFlowStep, IDiscoveryCandidate, INativeConfigFlowContext } from '../../core/types.js';
2
+ import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
3
+ import type { IShellyConfig, IShellyIntegrationOptions } from './shelly.types.js';
3
4
  import { ShellyClient } from './shelly.classes.client.js';
5
+ import { normalizeShellyConfig, normalizeShellyOptions, shellyConfigFields, shellyData } from './shelly.validation.js';
4
6
 
5
7
  export class ShellyConfigFlow implements IConfigFlow<IShellyConfig> {
6
- public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IShellyConfig>> {
7
- void contextArg;
8
- return {
9
- kind: 'form',
10
- title: 'Connect Shelly',
11
- description: 'Configure the local Shelly HTTP endpoint. Password is only needed when device authentication is enabled.',
8
+ readonly #scope: NativeIntegrationScope;
9
+ readonly #options: Readonly<IShellyIntegrationOptions>;
10
+ constructor(optionsArg: IShellyIntegrationOptions = {}, scopeArg?: NativeIntegrationScope) {
11
+ this.#options = normalizeShellyOptions(optionsArg);
12
+ this.#scope = scopeArg ?? new NativeIntegrationOwner('shelly', { homeId: this.#options.homeId, capacity: this.#options.capacity }).createFlowScope();
13
+ }
14
+ public async start(candidateArg: IDiscoveryCandidate, contextArg: INativeConfigFlowContext): Promise<IConfigFlowStep<IShellyConfig>> {
15
+ const candidate = shellyData(candidateArg);
16
+ if (candidate.integrationDomain !== undefined && candidate.integrationDomain !== 'shelly') return { kind: 'error', error: 'Candidate does not belong to Shelly.' };
17
+ const hints = { host: candidate.host, port: candidate.port };
18
+ const admitted = this.#scope.admit(contextArg);
19
+ return { kind: 'form', title: 'Connect selected Shelly Gen2/3',
20
+ description: 'Confirm the reported id, MAC, model and generation for the host-selected home. Supply only a caller credential reference; output controls require explicit enablement.',
12
21
  fields: [
13
22
  { name: 'host', label: 'Host', type: 'text', required: true },
14
23
  { name: 'port', label: 'Port', type: 'number' },
15
- { name: 'protocol', label: 'Protocol', type: 'select', options: [
16
- { label: 'HTTP', value: 'http' },
17
- { label: 'HTTPS', value: 'https' },
18
- ] },
19
- { name: 'username', label: 'Username', type: 'text' },
20
- { name: 'password', label: 'Password', type: 'password' },
24
+ { name: 'protocol', label: 'Protocol', type: 'select', options: [{ label: 'HTTP', value: 'http' }, { label: 'HTTPS', value: 'https' }] },
25
+ { name: 'expectedDeviceId', label: 'Exact observed device id', type: 'text', required: true },
26
+ { name: 'expectedMacAddress', label: 'Exact observed MAC (12 hex digits)', type: 'text', required: true },
27
+ { name: 'expectedModel', label: 'Exact observed model', type: 'text', required: true },
28
+ { name: 'expectedGeneration', label: 'Observed generation (2 or 3)', type: 'number', required: true },
29
+ { name: 'credentialRef', label: 'Caller password reference (if authentication is enabled)', type: 'text' },
30
+ { name: 'allowUnencryptedLan', label: 'Allow unencrypted selected LAN HTTP', type: 'boolean' },
31
+ { name: 'allowControl', label: 'Enable absolute switch output controls', type: 'boolean' },
21
32
  ],
22
- submit: async (valuesArg) => {
23
- if (valuesArg.protocol !== undefined && !['http', 'https'].includes(String(valuesArg.protocol)) ||
24
- valuesArg.host !== undefined && typeof valuesArg.host !== 'string' ||
25
- valuesArg.port !== undefined && typeof valuesArg.port !== 'number' ||
26
- valuesArg.username !== undefined && valuesArg.username !== 'admin' ||
27
- valuesArg.password !== undefined && typeof valuesArg.password !== 'string') {
28
- return { kind: 'error', error: 'Invalid Shelly connection settings.' };
29
- }
30
- const config: IShellyConfig = {
31
- host: typeof valuesArg.host === 'string' ? valuesArg.host : candidateArg.host ?? '',
32
- port: typeof valuesArg.port === 'number' ? valuesArg.port : candidateArg.port,
33
- protocol: valuesArg.protocol === 'https' ? 'https' : 'http',
34
- username: 'admin',
35
- password: typeof valuesArg.password === 'string' ? valuesArg.password : undefined,
36
- };
37
- let client: ShellyClient | undefined;
33
+ submit: async (inputArg) => {
38
34
  try {
39
- client = new ShellyClient(config);
40
- await client.getSnapshot();
41
- return { kind: 'done', title: 'Shelly configured', config };
42
- } catch {
43
- return { kind: 'error', error: 'Could not validate the Shelly Gen2/3 connection and credentials.' };
44
- } finally { await client?.destroy(); }
45
- },
46
- };
35
+ const values = shellyData(inputArg, shellyConfigFields);
36
+ if (values.homeId !== undefined && values.homeId !== admitted.homeId) throw new Error('Shelly submitted home conflicts with the host-selected home.');
37
+ const config = normalizeShellyConfig({ ...values, homeId: admitted.homeId, host: values.host ?? hints.host, port: values.port ?? hints.port });
38
+ return await this.#scope.run(admitted, config.homeId, async (leaseArg) => {
39
+ const client = new ShellyClient(config, { ...leaseArg.context, abortSignal: leaseArg.signal }, this.#options);
40
+ leaseArg.own(() => client.retryCleanup());
41
+ await client.getSnapshot();
42
+ leaseArg.assertOpen();
43
+ return { kind: 'done', title: 'Selected Shelly admitted', config };
44
+ });
45
+ } catch (errorArg) {
46
+ if (errorArg instanceof AggregateError) throw errorArg;
47
+ return { kind: 'error', error: 'Could not validate selected Shelly home, identity, capabilities and credential reference.' };
48
+ }
49
+ } };
47
50
  }
51
+ public destroy(): Promise<void> { return this.#scope.close(); }
52
+ public retryCleanup(): Promise<void> { return this.#scope.retryCleanup(); }
48
53
  }
@@ -1,99 +1,73 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
- import type {
4
- IDeviceFeatureWriteRequest,
5
- IIntegrationEntity,
6
- IIntegrationEvent,
7
- IIntegrationRuntime,
8
- IIntegrationSetupContext,
9
- IServiceCallRequest,
10
- IServiceCallResult,
11
- } from '../../core/types.js';
3
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
4
+ import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime,
5
+ INativeIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
12
6
  import { ShellyClient } from './shelly.classes.client.js';
13
7
  import { ShellyConfigFlow } from './shelly.classes.configflow.js';
14
8
  import { createShellyDiscoveryDescriptor } from './shelly.discovery.js';
15
9
  import { ShellyMapper } from './shelly.mapper.js';
16
- import type { IShellyConfig, IShellySnapshot, TShellyClientEvent } from './shelly.types.js';
10
+ import { normalizeShellyConfig, normalizeShellyOptions, shellyData } from './shelly.validation.js';
11
+ import { ShellyOperationError, type IShellyConfig, type IShellyIntegrationOptions, type IShellySnapshot,
12
+ type TShellyClientEvent } from './shelly.types.js';
17
13
 
18
14
  export class ShellyIntegration extends BaseIntegration<IShellyConfig> {
19
15
  public readonly domain = 'shelly';
20
16
  public readonly displayName = 'Shelly';
21
17
  public readonly status = 'control-runtime' as const;
22
18
  public readonly discoveryDescriptor = createShellyDiscoveryDescriptor();
23
- public readonly configFlow = new ShellyConfigFlow();
24
- public readonly metadata = {
25
- source: 'home-assistant/core',
26
- upstreamPath: 'homeassistant/components/shelly',
27
- upstreamDomain: 'shelly',
28
- integrationType: 'device',
29
- iotClass: 'local_push',
30
- qualityScale: 'platinum',
31
- };
32
- private readonly runtimes = new Set<ShellyRuntime>();
33
- private readonly pendingClients = new Set<ShellyClient>();
34
- private destroyed = false;
19
+ public readonly configFlow: ShellyConfigFlow;
20
+ public readonly metadata = { source: 'native-typescript', upstreamPath: 'homeassistant/components/shelly',
21
+ upstreamDomain: 'shelly', integrationType: 'device', iotClass: 'local_push', qualityScale: 'platinum' };
22
+ readonly #owner: NativeIntegrationOwner;
23
+ readonly #options: Readonly<IShellyIntegrationOptions>;
35
24
 
36
- public async setup(configArg: IShellyConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
37
- if (this.destroyed) throw new Error('Shelly integration has been destroyed.');
38
- const client = new ShellyClient(configArg, contextArg.abortSignal, contextArg.logger);
39
- this.pendingClients.add(client);
40
- try {
41
- const snapshot = await client.getSnapshot();
42
- if (this.destroyed) throw new Error('Shelly integration has been destroyed.');
43
- const runtime = new ShellyRuntime(client, snapshot, () => this.runtimes.delete(runtime));
44
- this.runtimes.add(runtime);
45
- this.pendingClients.delete(client);
46
- return runtime;
47
- } catch (errorArg) {
48
- try { await client.destroy(); }
49
- catch (cleanupErrorArg) { throw new AggregateError([errorArg, cleanupErrorArg], 'Shelly setup and cleanup failed.'); }
50
- this.pendingClients.delete(client);
51
- throw errorArg;
52
- }
25
+ public constructor(optionsArg: IShellyIntegrationOptions = {}) {
26
+ super();
27
+ this.#options = normalizeShellyOptions(optionsArg);
28
+ this.#owner = new NativeIntegrationOwner('shelly', { homeId: this.#options.homeId, capacity: this.#options.capacity });
29
+ this.configFlow = new ShellyConfigFlow(this.#options, this.#owner.createFlowScope());
53
30
  }
54
-
55
- public async destroy(): Promise<void> {
56
- this.destroyed = true;
57
- const results = await Promise.allSettled([
58
- ...[...this.runtimes].map((runtimeArg) => runtimeArg.destroy()),
59
- ...[...this.pendingClients].map(async (clientArg) => {
60
- await clientArg.destroy();
61
- this.pendingClients.delete(clientArg);
62
- }),
63
- ]);
64
- const errors = results.flatMap((resultArg) => resultArg.status === 'rejected' ? [resultArg.reason as unknown] : []);
65
- if (errors.length) throw new AggregateError(errors, 'Shelly integration cleanup failed.');
31
+ public setup(configArg: IShellyConfig, contextArg: INativeIntegrationSetupContext): Promise<IIntegrationRuntime> {
32
+ try {
33
+ const config = normalizeShellyConfig(configArg);
34
+ return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
35
+ const client = new ShellyClient(config, { ...leaseArg.context, abortSignal: leaseArg.signal }, this.#options);
36
+ leaseArg.own(() => client.retryCleanup());
37
+ const snapshot = await client.getSnapshot();
38
+ leaseArg.assertOpen();
39
+ const runtime = new ShellyRuntime(client, snapshot, leaseArg);
40
+ leaseArg.promoteToRuntime();
41
+ return runtime;
42
+ });
43
+ } catch (errorArg) { return Promise.reject(errorArg); }
66
44
  }
45
+ public destroy(): Promise<void> { return this.#owner.close(); }
46
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
67
47
  }
68
-
69
48
  export class HomeAssistantShellyIntegration extends ShellyIntegration {}
70
49
 
71
50
  class ShellyRuntime implements IIntegrationRuntime {
72
51
  public readonly domain = 'shelly';
73
-
74
- constructor(private readonly client: ShellyClient, private snapshot: IShellySnapshot, private readonly onDestroyed: () => void) {}
75
-
76
- public isHealthy(): boolean { return this.client.isHealthy(); }
77
-
78
- public async devices(abortSignalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
79
- this.snapshot = await this.client.getSnapshot(abortSignalArg);
80
- return ShellyMapper.toDevices(this.snapshot).map((deviceArg) => ({
81
- ...deviceArg,
82
- online: this.isHealthy(),
52
+ constructor(private readonly client: ShellyClient, private snapshot: IShellySnapshot, private readonly lease: INativeSetupLease) {}
53
+ public isHealthy(): boolean { return !this.lease.signal.aborted && this.client.isHealthy(); }
54
+ public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
55
+ this.lease.assertOpen();
56
+ this.snapshot = await this.client.getSnapshot(signalArg);
57
+ return ShellyMapper.toDevices(this.snapshot).map((deviceArg) => ({ ...deviceArg, online: this.isHealthy(),
83
58
  state: deviceArg.state.map((stateArg) => stateArg.featureId === 'connectivity' ?
84
- { ...stateArg, value: this.isHealthy() ? 'online' : 'offline' } : stateArg),
85
- }));
59
+ { ...stateArg, value: this.isHealthy() ? 'online' : 'offline' } : stateArg) }));
86
60
  }
87
-
88
61
  public async entities(): Promise<IIntegrationEntity[]> {
62
+ this.lease.assertOpen();
89
63
  this.snapshot = await this.client.getSnapshot();
90
64
  return ShellyMapper.toEntities(this.snapshot).map((entityArg) => ({ ...entityArg, available: this.isHealthy() }));
91
65
  }
92
-
93
- public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, abortSignalArg?: AbortSignal): Promise<() => Promise<void>> {
66
+ public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
67
+ this.lease.assertOpen();
94
68
  let previousEntities = ShellyMapper.toEntities(this.snapshot);
95
69
  return this.client.subscribe((eventArg: TShellyClientEvent) => {
96
- const deviceId = ShellyMapper.toDevices(this.snapshot)[0].id;
70
+ const deviceId = ShellyMapper.deviceId(this.snapshot);
97
71
  const emit = (event: Omit<IIntegrationEvent, 'integrationDomain' | 'deviceId' | 'timestamp'>): void =>
98
72
  handlerArg({ ...event, integrationDomain: this.domain, deviceId, timestamp: eventArg.timestamp });
99
73
  if (eventArg.type === 'status') {
@@ -103,54 +77,61 @@ class ShellyRuntime implements IIntegrationRuntime {
103
77
  const previous = previousEntities.find((candidateArg) => candidateArg.id === entity.id);
104
78
  if (JSON.stringify(previous) !== JSON.stringify(entity)) emit({ type: previous ? 'state_changed' : 'entity_added', entityId: entity.id, data: { entity } });
105
79
  }
106
- for (const previous of previousEntities) {
107
- if (!entities.some((entityArg) => entityArg.id === previous.id)) emit({ type: 'entity_removed', entityId: previous.id });
108
- }
80
+ for (const previous of previousEntities) if (!entities.some((entityArg) => entityArg.id === previous.id)) emit({ type: 'entity_removed', entityId: previous.id });
109
81
  previousEntities = entities;
110
- } else if (eventArg.type === 'availability') {
111
- emit({ type: 'availability_changed', data: { available: eventArg.available } });
112
- } else if (eventArg.type === 'error') {
113
- emit({ type: 'error', data: { message: eventArg.error.message } });
114
- } else {
115
- emit({ type: 'state_changed', data: { vendor: 'shelly', event: eventArg.event } });
116
- }
117
- }, abortSignalArg);
82
+ } else if (eventArg.type === 'availability') emit({ type: 'availability_changed', data: { available: eventArg.available } });
83
+ else if (eventArg.type === 'error') emit({ type: 'error', data: { message: eventArg.error.message } });
84
+ else emit({ type: 'state_changed', data: { vendor: 'shelly', event: eventArg.event } });
85
+ }, signalArg);
118
86
  }
119
-
120
- public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
121
- if (requestArg.domain !== 'switch' || !['turn_on', 'turn_off'].includes(requestArg.service)) return { success: false, error: 'Shelly supports switch.turn_on and switch.turn_off.' };
122
- if (requestArg.data && Object.keys(requestArg.data).length) return { success: false, error: 'Shelly switch services do not accept additional data.' };
123
- this.snapshot = await this.client.getSnapshot();
124
- const deviceId = ShellyMapper.toDevices(this.snapshot)[0].id;
125
- if (requestArg.target.deviceId !== undefined && requestArg.target.deviceId !== deviceId) return { success: false, error: 'Shelly service target does not belong to the connected device.' };
87
+ public async callService(inputArg: IServiceCallRequest): Promise<IServiceCallResult> {
88
+ this.lease.assertOpen();
89
+ let request: Record<string, unknown>;
90
+ let target: Record<string, unknown>;
91
+ try {
92
+ request = shellyData(inputArg, ['domain', 'service', 'target', 'data']);
93
+ target = shellyData(request.target, ['deviceId', 'entityId']);
94
+ if (request.domain !== 'switch' || !['turn_on', 'turn_off'].includes(String(request.service)) ||
95
+ request.data !== undefined && Object.keys(shellyData(request.data)).length) return { success: false, error: 'Shelly supports only absolute switch.turn_on and switch.turn_off without extra data.' };
96
+ } catch { return { success: false, error: 'Invalid Shelly service request.' }; }
97
+ const deviceId = ShellyMapper.deviceId(this.snapshot);
98
+ if (target.deviceId !== undefined && target.deviceId !== deviceId) return { success: false, error: 'Shelly service target does not belong to the connected home and device.' };
126
99
  const switches = ShellyMapper.toEntities(this.snapshot).filter((entityArg) => entityArg.platform === 'switch');
127
- const target = requestArg.target.entityId !== undefined ? switches.find((entityArg) => entityArg.id === requestArg.target.entityId) :
128
- requestArg.target.deviceId === deviceId && switches.length === 1 ? switches[0] : undefined;
129
- if (!target) return { success: false, error: 'Shelly service requires an exact switch entity or an unambiguous device target.' };
130
- const switchId = this.componentId(target.uniqueId);
131
- if (switchId === undefined) return { success: false, error: 'Shelly switch target has no component id.' };
132
- await this.client.setSwitchOutput(switchId, requestArg.service === 'turn_on');
133
- return { success: true };
100
+ const selected = target.entityId !== undefined ? switches.find((entityArg) => entityArg.id === target.entityId) :
101
+ target.deviceId === deviceId && switches.length === 1 ? switches[0] : undefined;
102
+ if (!selected) return { success: false, error: 'Shelly service requires an exact switch entity or an unambiguous device target.' };
103
+ const match = /\.switch_(\d+)$/.exec(selected.uniqueId);
104
+ if (!match) return { success: false, error: 'Shelly switch target has no component id.' };
105
+ return this.control(Number(match[1]), request.service === 'turn_on');
134
106
  }
135
-
136
- public async writeFeature(requestArg: IDeviceFeatureWriteRequest, abortSignalArg?: AbortSignal): Promise<IServiceCallResult> {
137
- this.snapshot = await this.client.getSnapshot(abortSignalArg);
138
- const device = ShellyMapper.toDevices(this.snapshot)[0];
139
- if (requestArg.device.id !== device.id || requestArg.device.integrationDomain !== this.domain) return { success: false, error: 'Shelly feature target does not belong to the connected device.' };
140
- const feature = device.features.find((featureArg) => featureArg.id === requestArg.feature.id);
107
+ public async writeFeature(inputArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
108
+ this.lease.assertOpen();
109
+ let request: Record<string, unknown>;
110
+ let deviceInput: Record<string, unknown>;
111
+ let featureInput: Record<string, unknown>;
112
+ try {
113
+ request = shellyData(inputArg, ['device', 'feature', 'value']);
114
+ deviceInput = shellyData(request.device);
115
+ featureInput = shellyData(request.feature);
116
+ } catch { return { success: false, error: 'Invalid Shelly feature request.' }; }
117
+ if (deviceInput.id !== ShellyMapper.deviceId(this.snapshot) || deviceInput.integrationDomain !== this.domain) return { success: false, error: 'Shelly feature target does not belong to the connected home and device.' };
118
+ const feature = ShellyMapper.toDevices(this.snapshot)[0].features.find((entryArg) => entryArg.id === featureInput.id);
141
119
  const match = feature && /^switch_(\d+)_output$/.exec(feature.id);
142
- if (!match || !feature.writable || feature.capability !== 'switch' || typeof requestArg.value !== 'boolean') return { success: false, error: 'Shelly writes require a writable switch output and boolean value.' };
143
- await this.client.setSwitchOutput(Number(match[1]), requestArg.value, abortSignalArg);
144
- return { success: true };
120
+ if (!match || !feature.writable || feature.capability !== 'switch' || featureInput.capability !== 'switch' || typeof request.value !== 'boolean') return { success: false, error: 'Shelly writes require a current writable switch output and boolean value.' };
121
+ return this.control(Number(match[1]), request.value, signalArg);
145
122
  }
146
-
147
- public async destroy(): Promise<void> {
148
- await this.client.destroy();
149
- this.onDestroyed();
150
- }
151
-
152
- private componentId(uniqueIdArg: string): number | undefined {
153
- const match = /_switch_(\d+)$/.exec(uniqueIdArg);
154
- return match ? Number(match[1]) : undefined;
123
+ public destroy(): Promise<void> { return this.lease.close(); }
124
+ private async control(idArg: number, onArg: boolean, signalArg?: AbortSignal): Promise<IServiceCallResult> {
125
+ try {
126
+ const receipt = await this.client.setSwitchOutput(idArg, onArg, signalArg);
127
+ this.snapshot = receipt.snapshot;
128
+ return { success: receipt.outcome === 'observed-target',
129
+ ...(receipt.outcome === 'indeterminate' ? { error: 'Shelly acknowledged the command, but independent reported output differs.' } : {}),
130
+ data: { ...receipt, devices: ShellyMapper.toDevices(receipt.snapshot), entities: ShellyMapper.toEntities(receipt.snapshot) } };
131
+ } catch (errorArg) {
132
+ const error = errorArg instanceof ShellyOperationError ? errorArg : undefined;
133
+ return { success: false, error: error?.message ?? 'Shelly control was not admitted.',
134
+ data: { effectOutcome: error?.effectOutcome ?? 'not-sent', commandAcknowledged: error?.commandAcknowledged ?? false, causalityConfirmed: false } };
135
+ }
155
136
  }
156
137
  }
@@ -28,6 +28,8 @@ export class ShellySubscription {
28
28
  private resolveStart?: () => void;
29
29
  private rejectStart?: (errorArg: Error) => void;
30
30
  private closePromise?: Promise<void>;
31
+ private stopPromise?: Promise<void>;
32
+ private retryPromise?: Promise<void>;
31
33
  private resolveClose?: () => void;
32
34
  private deadline?: ReturnType<typeof setTimeout>;
33
35
  private requestId = 1;
@@ -65,7 +67,26 @@ export class ShellySubscription {
65
67
  return this.startPromise;
66
68
  }
67
69
 
68
- public async stop(): Promise<void> {
70
+ public stop(): Promise<void> {
71
+ if (!this.stopPromise) {
72
+ // Publish before availability callbacks or synchronous socket/abort listeners.
73
+ this.stopPromise = Promise.resolve().then(() => this.drainClose());
74
+ this.stopping = true;
75
+ }
76
+ return this.stopPromise;
77
+ }
78
+
79
+ public retryCleanup(): Promise<void> {
80
+ if (!this.stopPromise) return this.stop();
81
+ if (!this.retryPromise) {
82
+ this.retryPromise = Promise.resolve().then(() => this.drainClose());
83
+ const settled = (): void => { this.retryPromise = undefined; };
84
+ void this.retryPromise.then(settled, settled);
85
+ }
86
+ return this.retryPromise;
87
+ }
88
+
89
+ private async drainClose(): Promise<void> {
69
90
  this.stopping = true;
70
91
  this.setHealthy(false);
71
92
  this.clearDeadline();
@@ -123,6 +144,7 @@ export class ShellySubscription {
123
144
  this.resolveStart?.();
124
145
  this.resolveStart = undefined;
125
146
  this.rejectStart = undefined;
147
+ this.options.password = undefined;
126
148
  } catch (errorArg) {
127
149
  this.fail(errorArg instanceof AuthenticationError || errorArg instanceof DeviceCommunicationError ?
128
150
  errorArg : new DeviceCommunicationError('Shelly returned an invalid WebSocket payload.'));
@@ -214,5 +236,6 @@ export class ShellySubscription {
214
236
  this.socket?.removeEventListener('close', this.handleClose);
215
237
  this.resolveClose?.();
216
238
  this.resolveClose = undefined;
239
+ this.options.password = undefined;
217
240
  }
218
241
  }
@@ -4,7 +4,7 @@ import type { IShellyDeviceConfig, IShellyDeviceInfo, IShellySnapshot, IShellySt
4
4
 
5
5
  export class ShellyMapper {
6
6
  public static toDevices(snapshotArg: IShellySnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
7
- const deviceId = this.deviceId(snapshotArg.deviceInfo, snapshotArg.status);
7
+ const deviceId = this.deviceId(snapshotArg);
8
8
  const updatedAt = new Date().toISOString();
9
9
  const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
10
10
  { id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
@@ -21,7 +21,8 @@ export class ShellyMapper {
21
21
  for (const switchComponent of this.switchComponents(snapshotArg.status)) {
22
22
  const prefix = `switch_${switchComponent.id}`;
23
23
  const name = this.componentName(snapshotArg.deviceConfig, `switch:${switchComponent.id}`, `Switch ${switchComponent.id}`);
24
- features.push({ id: `${prefix}_output`, capability: 'switch', name, readable: true, writable: true });
24
+ features.push({ id: `${prefix}_output`, capability: 'switch', name, readable: true,
25
+ writable: snapshotArg.controlEnabled && snapshotArg.methods.includes('Switch.Set') && snapshotArg.methods.includes('Switch.GetStatus') });
25
26
  state.push({ featureId: `${prefix}_output`, value: switchComponent.status.output ?? null, updatedAt });
26
27
  this.pushNumericFeature(features, state, `${prefix}_power`, `${name} power`, switchComponent.status.apower, 'W', updatedAt);
27
28
  this.pushNumericFeature(features, state, `${prefix}_voltage`, `${name} voltage`, switchComponent.status.voltage, 'V', updatedAt);
@@ -36,7 +37,7 @@ export class ShellyMapper {
36
37
  name: this.deviceName(snapshotArg.deviceInfo, snapshotArg.deviceConfig),
37
38
  protocol: 'http',
38
39
  manufacturer: 'Shelly',
39
- model: snapshotArg.deviceInfo.model || snapshotArg.deviceInfo.app,
40
+ model: snapshotArg.deviceInfo.model,
40
41
  online: true,
41
42
  features,
42
43
  state,
@@ -46,20 +47,21 @@ export class ShellyMapper {
46
47
  authEnabled: snapshotArg.deviceInfo.auth_en ?? snapshotArg.deviceInfo.auth,
47
48
  macAddress: snapshotArg.deviceInfo.mac || snapshotArg.status.sys?.mac,
48
49
  profile: snapshotArg.deviceInfo.profile,
50
+ homeId: snapshotArg.homeId,
49
51
  },
50
52
  }];
51
53
  }
52
54
 
53
55
  public static toEntities(snapshotArg: IShellySnapshot): IIntegrationEntity[] {
54
- const deviceId = this.deviceId(snapshotArg.deviceInfo, snapshotArg.status);
55
- const deviceSlug = this.slug(this.deviceName(snapshotArg.deviceInfo, snapshotArg.deviceConfig));
56
+ const deviceId = this.deviceId(snapshotArg);
57
+ const deviceSlug = `${encodeURIComponent(snapshotArg.homeId)}.${encodeURIComponent(snapshotArg.deviceInfo.id!)}`;
56
58
  const entities: IIntegrationEntity[] = [];
57
59
 
58
60
  for (const switchComponent of this.switchComponents(snapshotArg.status)) {
59
61
  const switchName = this.componentName(snapshotArg.deviceConfig, `switch:${switchComponent.id}`, `Switch ${switchComponent.id}`);
60
- const uniquePrefix = `shelly_${this.slug(deviceId)}_switch_${switchComponent.id}`;
62
+ const uniquePrefix = `${deviceId}.switch_${switchComponent.id}`;
61
63
  entities.push({
62
- id: `switch.${deviceSlug}_${switchComponent.id}`,
64
+ id: `switch.${deviceSlug}.${switchComponent.id}`,
63
65
  uniqueId: uniquePrefix,
64
66
  integrationDomain: 'shelly',
65
67
  deviceId,
@@ -72,11 +74,11 @@ export class ShellyMapper {
72
74
  },
73
75
  available: true,
74
76
  });
75
- this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_power`, `sensor.${deviceSlug}_${switchComponent.id}_power`, `${switchName} power`, switchComponent.status.apower, 'W');
76
- this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_voltage`, `sensor.${deviceSlug}_${switchComponent.id}_voltage`, `${switchName} voltage`, switchComponent.status.voltage, 'V');
77
- this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_current`, `sensor.${deviceSlug}_${switchComponent.id}_current`, `${switchName} current`, switchComponent.status.current, 'A');
78
- this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_energy`, `sensor.${deviceSlug}_${switchComponent.id}_energy`, `${switchName} energy`, switchComponent.status.aenergy?.total, 'Wh');
79
- this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_temperature`, `sensor.${deviceSlug}_${switchComponent.id}_temperature`, `${switchName} temperature`, switchComponent.status.temperature?.tC, 'C');
77
+ this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_power`, `sensor.${deviceSlug}.${switchComponent.id}.power`, `${switchName} power`, switchComponent.status.apower, 'W');
78
+ this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_voltage`, `sensor.${deviceSlug}.${switchComponent.id}.voltage`, `${switchName} voltage`, switchComponent.status.voltage, 'V');
79
+ this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_current`, `sensor.${deviceSlug}.${switchComponent.id}.current`, `${switchName} current`, switchComponent.status.current, 'A');
80
+ this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_energy`, `sensor.${deviceSlug}.${switchComponent.id}.energy`, `${switchName} energy`, switchComponent.status.aenergy?.total, 'Wh');
81
+ this.pushSensorEntity(entities, deviceId, `${uniquePrefix}_temperature`, `sensor.${deviceSlug}.${switchComponent.id}.temperature`, `${switchName} temperature`, switchComponent.status.temperature?.tC, 'C');
80
82
  }
81
83
 
82
84
  return entities;
@@ -91,7 +93,7 @@ export class ShellyMapper {
91
93
  unitArg: string,
92
94
  updatedAtArg: string
93
95
  ): void {
94
- if (typeof valueArg !== 'number') {
96
+ if (typeof valueArg !== 'number' || !Number.isFinite(valueArg)) {
95
97
  return;
96
98
  }
97
99
  featuresArg.push({ id: idArg, capability: 'sensor', name: nameArg, readable: true, writable: false, unit: unitArg });
@@ -107,7 +109,7 @@ export class ShellyMapper {
107
109
  valueArg: number | null | undefined,
108
110
  unitArg: string
109
111
  ): void {
110
- if (typeof valueArg !== 'number') {
112
+ if (typeof valueArg !== 'number' || !Number.isFinite(valueArg)) {
111
113
  return;
112
114
  }
113
115
  entitiesArg.push({
@@ -139,8 +141,9 @@ export class ShellyMapper {
139
141
  return components.sort((leftArg, rightArg) => leftArg.id - rightArg.id);
140
142
  }
141
143
 
142
- private static deviceId(deviceInfoArg: IShellyDeviceInfo, statusArg: IShellyStatus): string {
143
- return `shelly.device.${this.slug(deviceInfoArg.id || deviceInfoArg.mac || statusArg.sys?.mac || 'unknown')}`;
144
+ public static deviceId(snapshotArg: IShellySnapshot): string {
145
+ if (!snapshotArg.homeId || !snapshotArg.deviceInfo.id) throw new TypeError('Shelly mapping requires admitted home and observed device identity.');
146
+ return `shelly.device.${encodeURIComponent(snapshotArg.homeId)}.${encodeURIComponent(snapshotArg.deviceInfo.id)}`;
144
147
  }
145
148
 
146
149
  private static deviceName(deviceInfoArg: IShellyDeviceInfo, configArg?: IShellyDeviceConfig): string {
@@ -155,10 +158,6 @@ export class ShellyMapper {
155
158
  return fallbackArg;
156
159
  }
157
160
 
158
- private static slug(valueArg: string): string {
159
- return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'shelly';
160
- }
161
-
162
161
  private static isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
163
162
  return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
164
163
  }
@@ -29,6 +29,8 @@ export const parseShellyDeviceInfo = (valueArg: unknown): IShellyDeviceInfo => {
29
29
  if (valueArg.gen !== 2 && valueArg.gen !== 3) {
30
30
  throw new DeviceCommunicationError('Shelly integration supports generation 2 and 3 devices.');
31
31
  }
32
+ if (typeof valueArg.mac !== 'string' || !/^[0-9a-f]{12}$/i.test(valueArg.mac) ||
33
+ typeof valueArg.model !== 'string' || !valueArg.model || valueArg.model.length > 128) invalidPayload();
32
34
  for (const key of ['mac', 'model', 'ver', 'fw_id', 'app', 'profile']) {
33
35
  if (valueArg[key] !== undefined && typeof valueArg[key] !== 'string') invalidPayload();
34
36
  }
@@ -1,12 +1,57 @@
1
1
  export type TShellyProtocol = 'http' | 'https';
2
2
 
3
3
  export interface IShellyConfig {
4
+ homeId: string;
4
5
  host: string;
5
6
  port?: number;
6
7
  protocol?: TShellyProtocol;
7
- username?: string;
8
- password?: string;
8
+ expectedDeviceId: string;
9
+ expectedMacAddress: string;
10
+ expectedModel: string;
11
+ expectedGeneration: 2 | 3;
12
+ credentialRef?: string;
13
+ /** Absolute output controls are denied unless explicitly enabled. */
14
+ allowControl?: boolean;
15
+ allowUnencryptedLan?: boolean;
9
16
  requestTimeoutMs?: number;
17
+ maxPendingOperations?: number;
18
+ maxResponseBytes?: number;
19
+ }
20
+
21
+ export interface IShellyCredentialRequest {
22
+ readonly homeId: string;
23
+ readonly credentialRef: string;
24
+ readonly signal: AbortSignal;
25
+ /** Monotonic performance.now() deadline, including queue time. */
26
+ readonly deadline: number;
27
+ }
28
+
29
+ export type TShellyPasswordResolver = (requestArg: IShellyCredentialRequest) => Promise<string>;
30
+
31
+ export interface IShellyIntegrationOptions {
32
+ readonly homeId?: string;
33
+ readonly capacity?: number;
34
+ readonly resolvePassword?: TShellyPasswordResolver;
35
+ }
36
+
37
+ export type TShellyEffectOutcome = 'not-sent' | 'indeterminate';
38
+
39
+ export class ShellyOperationError extends Error {
40
+ constructor(messageArg: string, public readonly effectOutcome: TShellyEffectOutcome = 'not-sent',
41
+ public readonly commandAcknowledged = false) {
42
+ super(messageArg);
43
+ this.name = 'ShellyOperationError';
44
+ }
45
+ }
46
+
47
+ export interface IShellyEffectResult {
48
+ readonly componentId: number;
49
+ readonly requested: boolean;
50
+ readonly wasOn: boolean;
51
+ readonly commandAcknowledged: true;
52
+ readonly causalityConfirmed: false;
53
+ readonly outcome: 'observed-target' | 'indeterminate';
54
+ readonly snapshot: IShellySnapshot;
10
55
  }
11
56
 
12
57
  export interface IShellyDeviceInfo {
@@ -78,9 +123,12 @@ export interface IShellySwitchStatus {
78
123
  }
79
124
 
80
125
  export interface IShellySnapshot {
126
+ homeId: string;
81
127
  deviceInfo: IShellyDeviceInfo;
82
128
  status: IShellyStatus;
83
129
  deviceConfig?: IShellyDeviceConfig;
130
+ methods: readonly string[];
131
+ controlEnabled: boolean;
84
132
  }
85
133
 
86
134
  export interface IShellyComponentEvent {