@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,49 +1,56 @@
1
1
  import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
+ import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
2
3
  import type { IEsphomeConfig } from './esphome.types.js';
3
- import { esphomeDefaultNativeApiPort } from './esphome.types.js';
4
+ import { captureEsphomeConfig, esphomeData } from './esphome.validation.js';
4
5
 
5
6
  export class EsphomeConfigFlow implements IConfigFlow<IEsphomeConfig> {
7
+ private readonly scope: NativeIntegrationScope;
8
+
9
+ constructor(ownerArg: NativeIntegrationOwner = new NativeIntegrationOwner('esphome')) {
10
+ this.scope = ownerArg.createFlowScope();
11
+ }
12
+
6
13
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IEsphomeConfig>> {
7
- void contextArg;
14
+ const context = this.scope.admit(contextArg);
15
+ // Discovery offers endpoint hints; its metadata, home fields and credentials are never copied.
16
+ const hints: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
17
+ for (const key of ['host', 'port', 'name']) {
18
+ const field = Object.getOwnPropertyDescriptor(candidateArg, key);
19
+ if (field && (!field.enumerable || !('value' in field))) throw new TypeError('ESPHome discovery hints must be ordinary data fields.');
20
+ if (field) hints[key] = field.value as unknown;
21
+ }
22
+ const hint = Object.freeze(hints);
8
23
  return {
9
24
  kind: 'form',
10
25
  title: 'Connect ESPHome',
11
- description: 'Connect directly to the native API of an ESPHome 2025.10 or newer device. Use its 32-byte base64 encryption key when encryption is configured.',
26
+ description: 'Confirm the selected native endpoint, node name and MAC address. Provision credentials outside device configuration.',
12
27
  fields: [
13
28
  { name: 'host', label: 'Host', type: 'text', required: true },
14
29
  { name: 'port', label: 'Native API port', type: 'number' },
15
- { name: 'encryptionKey', label: 'Encryption key', type: 'password' },
30
+ { name: 'deviceName', label: 'Selected native node name', type: 'text', required: true },
31
+ { name: 'expectedMacAddress', label: 'Selected device MAC address', type: 'text', required: true },
32
+ { name: 'expectedModel', label: 'Selected model (optional)', type: 'text' },
33
+ { name: 'credentialKey', label: 'Credential reference (optional)', type: 'text' },
34
+ { name: 'credentialSource', label: 'Credential source', type: 'select', options: [{ label: 'External provider', value: 'provider' }, { label: 'Encrypted store', value: 'store' }] },
16
35
  ],
17
- submit: async (valuesArg) => ({
18
- kind: 'done',
19
- title: 'ESPHome configured',
20
- config: {
21
- host: this.stringValue(valuesArg.host) || candidateArg.host || '',
22
- port: this.numberValue(valuesArg.port) || candidateArg.port || esphomeDefaultNativeApiPort,
23
- name: candidateArg.name,
24
- deviceName: this.stringValue(candidateArg.metadata?.deviceName),
25
- encryptionKey: this.stringValue(valuesArg.encryptionKey) || this.stringValue(candidateArg.metadata?.encryptionKey),
26
- noisePsk: this.stringValue(candidateArg.metadata?.noisePsk),
27
- deviceInfo: {
28
- name: this.stringValue(candidateArg.metadata?.deviceName) || candidateArg.name,
29
- friendlyName: candidateArg.name,
30
- macAddress: candidateArg.macAddress,
31
- manufacturer: candidateArg.manufacturer,
32
- model: candidateArg.model,
33
- host: candidateArg.host,
34
- port: candidateArg.port || esphomeDefaultNativeApiPort,
35
- apiEncryptionSupported: Boolean(candidateArg.metadata?.encryptionRequired),
36
- },
37
- },
38
- }),
36
+ submit: async (valuesArg) => {
37
+ const values = esphomeData(valuesArg, ['homeId', 'host', 'port', 'deviceName', 'expectedMacAddress', 'expectedModel', 'credentialKey', 'credentialSource'], 'ESPHome submitted configuration');
38
+ if (values.homeId !== undefined && values.homeId !== context.homeId) throw new Error('ESPHome form home does not match the selected host home.');
39
+ const config = captureEsphomeConfig({
40
+ homeId: context.homeId, host: (values.host ?? hint.host) as string, port: (values.port ?? hint.port) as number | undefined,
41
+ name: hint.name as string | undefined, deviceName: values.deviceName as string, expectedMacAddress: values.expectedMacAddress as string,
42
+ expectedModel: values.expectedModel as string | undefined, credentialKey: values.credentialKey as string | undefined,
43
+ credentialSource: values.credentialSource as 'provider' | 'store' | undefined,
44
+ });
45
+ return this.scope.run(context, config.homeId, async (leaseArg) => {
46
+ leaseArg.assertOpen();
47
+ if (config.credentialSource === 'store' && !leaseArg.context.configStore) throw new Error('ESPHome store credential references require a scoped NoSqlConfigStore.');
48
+ return { kind: 'done', title: 'ESPHome configured', config };
49
+ });
50
+ },
39
51
  };
40
52
  }
41
53
 
42
- private stringValue(valueArg: unknown): string | undefined {
43
- return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
44
- }
45
-
46
- private numberValue(valueArg: unknown): number | undefined {
47
- return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : undefined;
48
- }
54
+ public destroy(): Promise<void> { return this.scope.close(); }
55
+ public retryCleanup(): Promise<void> { return this.scope.retryCleanup(); }
49
56
  }
@@ -1,18 +1,20 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
3
4
  import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
4
5
  import { EsphomeClient } from './esphome.classes.client.js';
5
6
  import { EsphomeConfigFlow } from './esphome.classes.configflow.js';
6
7
  import { createEsphomeDiscoveryDescriptor } from './esphome.discovery.js';
7
8
  import { EsphomeMapper } from './esphome.mapper.js';
8
- import type { IEsphomeConfig } from './esphome.types.js';
9
+ import type { IEsphomeConfig, IEsphomeIntegrationOptions, TEsphomeCredentials } from './esphome.types.js';
10
+ import { captureEsphomeConfig, captureEsphomeCredentials, esphomeData } from './esphome.validation.js';
9
11
 
10
12
  export class EsphomeIntegration extends BaseIntegration<IEsphomeConfig> {
11
13
  public readonly domain = 'esphome';
12
14
  public readonly displayName = 'ESPHome';
13
15
  public readonly status = 'control-runtime' as const;
14
16
  public readonly discoveryDescriptor = createEsphomeDiscoveryDescriptor();
15
- public readonly configFlow = new EsphomeConfigFlow();
17
+ public readonly configFlow: EsphomeConfigFlow;
16
18
  public readonly metadata = {
17
19
  integrationType: 'device',
18
20
  iotClass: 'local_push',
@@ -21,35 +23,61 @@ export class EsphomeIntegration extends BaseIntegration<IEsphomeConfig> {
21
23
  minimumFirmwareVersion: '2025.10',
22
24
  zeroconf: ['_esphomelib._tcp.local.'],
23
25
  };
24
- private readonly runtimes = new Set<EsphomeRuntime>();
25
- private readonly pendingClients = new Set<EsphomeClient>();
26
- private destroyed = false;
26
+ private readonly owner: NativeIntegrationOwner;
27
+ private readonly resolveCredentials?: IEsphomeIntegrationOptions['resolveCredentials'];
28
+
29
+ constructor(optionsArg: IEsphomeIntegrationOptions = {}) {
30
+ super();
31
+ const options = esphomeData(optionsArg, ['homeId', 'capacity', 'resolveCredentials'], 'ESPHome integration options');
32
+ if (options.resolveCredentials !== undefined && typeof options.resolveCredentials !== 'function') throw new TypeError('ESPHome resolveCredentials must be an asynchronous callback.');
33
+ this.resolveCredentials = options.resolveCredentials as IEsphomeIntegrationOptions['resolveCredentials'];
34
+ this.owner = new NativeIntegrationOwner(this.domain, { homeId: options.homeId as string | undefined, capacity: options.capacity as number | undefined });
35
+ this.configFlow = new EsphomeConfigFlow(this.owner);
36
+ }
27
37
 
28
38
  public async setup(configArg: IEsphomeConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
29
- if (this.destroyed) throw new Error('ESPHome integration was destroyed.');
30
- const client = new EsphomeClient(configArg);
31
- this.pendingClients.add(client);
32
- try {
33
- await client.connectLive(contextArg.abortSignal);
34
- if (this.destroyed) throw new Error('ESPHome integration was destroyed during setup.');
35
- const runtime = new EsphomeRuntime(client, () => this.runtimes.delete(runtime));
36
- this.runtimes.add(runtime);
39
+ const config = captureEsphomeConfig(configArg);
40
+ return this.owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
41
+ const timeout = config.initialSnapshotTimeoutMs ?? 15000;
42
+ const monotonicDeadline = performance.now() + timeout;
43
+ const signal = AbortSignal.any([leaseArg.signal, AbortSignal.timeout(timeout)]);
44
+ let credentials: TEsphomeCredentials = {};
45
+ if (config.credentialKey !== undefined) {
46
+ const request = Object.freeze({ capturedHomeId: leaseArg.context.homeId, credentialKey: config.credentialKey, signal, monotonicDeadline });
47
+ // Setup is retained before the deferred callback. Adopt its real completion, never an abort race.
48
+ const provision = Promise.resolve().then(() => {
49
+ signal.throwIfAborted();
50
+ if (config.credentialSource === 'store') {
51
+ if (!leaseArg.context.configStore) throw new Error('ESPHome store credential references require a scoped NoSqlConfigStore.');
52
+ return leaseArg.context.configStore.get<TEsphomeCredentials>(request.credentialKey);
53
+ }
54
+ if (!this.resolveCredentials) throw new Error('ESPHome provider credential references require resolveCredentials.');
55
+ return this.resolveCredentials(request);
56
+ });
57
+ try { credentials = captureEsphomeCredentials(await provision); }
58
+ catch { throw new Error('ESPHome credentials could not be resolved.'); }
59
+ }
60
+ leaseArg.assertOpen();
61
+ signal.throwIfAborted();
62
+ const client = new EsphomeClient(config, undefined, credentials);
63
+ let cleanupAttempted = false;
64
+ leaseArg.own(() => {
65
+ if (cleanupAttempted) return client.retryCleanup();
66
+ cleanupAttempted = true;
67
+ return client.destroy();
68
+ });
69
+ await client.connectLive(signal);
70
+ leaseArg.assertOpen();
71
+ signal.throwIfAborted();
72
+ const runtime = new EsphomeRuntime(client, leaseArg);
73
+ leaseArg.promoteToRuntime();
37
74
  return runtime;
38
- } catch (error) {
39
- await client.destroy();
40
- throw error;
41
- } finally {
42
- this.pendingClients.delete(client);
43
- }
75
+ });
44
76
  }
45
77
 
46
- public async destroy(): Promise<void> {
47
- this.destroyed = true;
48
- await Promise.all([
49
- ...[...this.runtimes].map((runtimeArg) => runtimeArg.destroy()),
50
- ...[...this.pendingClients].map((clientArg) => clientArg.destroy()),
51
- ]);
52
- }
78
+ public destroy(): Promise<void> { return this.owner.close(); }
79
+
80
+ public retryCleanup(): Promise<void> { return this.owner.retryCleanup(); }
53
81
  }
54
82
 
55
83
  export class HomeAssistantEsphomeIntegration extends EsphomeIntegration {}
@@ -57,22 +85,25 @@ export class HomeAssistantEsphomeIntegration extends EsphomeIntegration {}
57
85
  export class EsphomeRuntime implements IIntegrationRuntime {
58
86
  public readonly domain = 'esphome';
59
87
 
60
- constructor(private readonly client: EsphomeClient, private readonly onDestroy?: () => void) {}
88
+ constructor(private readonly client: EsphomeClient, private readonly lease?: INativeSetupLease) {}
61
89
 
62
90
  public isHealthy(): boolean {
63
91
  return this.client.isHealthy();
64
92
  }
65
93
 
66
94
  public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
95
+ this.lease?.assertOpen();
67
96
  signalArg?.throwIfAborted();
68
97
  return EsphomeMapper.toDevices(await this.client.getSnapshot());
69
98
  }
70
99
 
71
100
  public async entities(): Promise<IIntegrationEntity[]> {
101
+ this.lease?.assertOpen();
72
102
  return EsphomeMapper.toEntities(await this.client.getSnapshot());
73
103
  }
74
104
 
75
105
  public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
106
+ this.lease?.assertOpen();
76
107
  signalArg?.throwIfAborted();
77
108
  const unsubscribe = this.client.onEvent((eventArg) => {
78
109
  handlerArg({
@@ -83,14 +114,12 @@ export class EsphomeRuntime implements IIntegrationRuntime {
83
114
  data: eventArg.data,
84
115
  timestamp: eventArg.timestamp || Date.now(),
85
116
  });
86
- });
87
- const cleanup = () => { unsubscribe(); signalArg?.removeEventListener('abort', cleanup); };
88
- signalArg?.addEventListener('abort', cleanup, { once: true });
89
- if (signalArg?.aborted) cleanup();
90
- return async () => cleanup();
117
+ }, signalArg);
118
+ return async () => unsubscribe();
91
119
  }
92
120
 
93
121
  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
122
+ this.lease?.assertOpen();
94
123
  if (!requestArg.target.entityId && !requestArg.target.deviceId) return { success: false, error: 'ESPHome service calls require an explicit entity or device target.' };
95
124
  const command = EsphomeMapper.commandForService(await this.client.getSnapshot(), requestArg);
96
125
  if (!command) return { success: false, error: `ESPHome service ${requestArg.domain}.${requestArg.service} has no unique supported native command target.` };
@@ -98,6 +127,7 @@ export class EsphomeRuntime implements IIntegrationRuntime {
98
127
  }
99
128
 
100
129
  public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
130
+ this.lease?.assertOpen();
101
131
  signalArg?.throwIfAborted();
102
132
  if (requestArg.device.integrationDomain !== this.domain) return { success: false, error: 'ESPHome feature writes require an ESPHome device target.' };
103
133
  const command = EsphomeMapper.commandForFeature(await this.client.getSnapshot(), requestArg.device.id, requestArg.feature.id, requestArg.value);
@@ -105,8 +135,5 @@ export class EsphomeRuntime implements IIntegrationRuntime {
105
135
  return this.client.sendCommand(command, signalArg);
106
136
  }
107
137
 
108
- public async destroy(): Promise<void> {
109
- await this.client.destroy();
110
- this.onDestroy?.();
111
- }
138
+ public destroy(): Promise<void> { return this.lease ? this.lease.close() : this.client.destroy(); }
112
139
  }
@@ -2,6 +2,7 @@ import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
2
2
  import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
3
3
  import type { IEsphomeManualEntry, IEsphomeMdnsRecord } from './esphome.types.js';
4
4
  import { esphomeDefaultNativeApiPort } from './esphome.types.js';
5
+ import { esphomeMac } from './esphome.validation.js';
5
6
 
6
7
  const esphomeMdnsTypes = new Set(['_esphomelib._tcp.local', '_esphome._tcp.local']);
7
8
 
@@ -37,7 +38,6 @@ export class EsphomeMdnsMatcher implements IDiscoveryMatcher<IEsphomeMdnsRecord>
37
38
  metadata: {
38
39
  mdnsName: recordArg.name,
39
40
  mdnsType: recordArg.type,
40
- txt,
41
41
  deviceName,
42
42
  encryptionRequired: Boolean(this.txt(txt, 'api_encryption')),
43
43
  },
@@ -63,13 +63,8 @@ export class EsphomeMdnsMatcher implements IDiscoveryMatcher<IEsphomeMdnsRecord>
63
63
  }
64
64
 
65
65
  private normalizeMac(valueArg?: string): string | undefined {
66
- if (!valueArg) {
67
- return undefined;
68
- }
69
- const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
70
- if (compact.length !== 12) {
71
- return valueArg.toLowerCase();
72
- }
66
+ const compact = esphomeMac(valueArg);
67
+ if (!compact) return undefined;
73
68
  return compact.match(/.{1,2}/g)?.join(':');
74
69
  }
75
70
  }
@@ -101,11 +96,7 @@ export class EsphomeManualMatcher implements IDiscoveryMatcher<IEsphomeManualEnt
101
96
  manufacturer: inputArg.manufacturer || 'ESPHome',
102
97
  model: inputArg.model || 'Native API device',
103
98
  metadata: {
104
- ...inputArg.metadata,
105
99
  deviceName: inputArg.deviceName,
106
- password: inputArg.password,
107
- encryptionKey: inputArg.encryptionKey,
108
- noisePsk: inputArg.noisePsk,
109
100
  },
110
101
  },
111
102
  };
@@ -1,6 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
3
- import type { IEsphomeAreaInfo, IEsphomeClientCommand, IEsphomeConfig, IEsphomeDeviceInfo, IEsphomeEntityDescriptor, IEsphomeEntityState, IEsphomeManualEntry, IEsphomeSnapshot } from './esphome.types.js';
3
+ import type { IEsphomeAreaInfo, IEsphomeClientCommand, IEsphomeMappingConfig, IEsphomeDeviceInfo, IEsphomeEntityDescriptor, IEsphomeEntityState, IEsphomeManualEntry, IEsphomeSnapshot } from './esphome.types.js';
4
4
  import { esphomeDefaultNativeApiPort } from './esphome.types.js';
5
5
 
6
6
  const serviceDataFields = new Map<string, readonly string[]>([
@@ -18,11 +18,12 @@ const serviceDataFields = new Map<string, readonly string[]>([
18
18
  ]);
19
19
 
20
20
  export class EsphomeMapper {
21
- public static toSnapshot(configArg: IEsphomeConfig, connectedArg?: boolean, eventsArg: IEsphomeSnapshot['events'] = []): IEsphomeSnapshot {
21
+ public static toSnapshot(configArg: IEsphomeMappingConfig, connectedArg?: boolean, eventsArg: IEsphomeSnapshot['events'] = []): IEsphomeSnapshot {
22
22
  const source = configArg.snapshot;
23
23
  const manualEntry = configArg.manualEntries?.[0];
24
24
  const deviceInfo = this.deviceInfoFromConfig(configArg, source?.deviceInfo, manualEntry);
25
25
  return {
26
+ homeId: configArg.homeId ?? source?.homeId,
26
27
  host: configArg.host || source?.host || manualEntry?.host || deviceInfo.host,
27
28
  port: configArg.port || source?.port || manualEntry?.port || deviceInfo.port || esphomeDefaultNativeApiPort,
28
29
  connected: connectedArg ?? source?.connected ?? false,
@@ -216,7 +217,7 @@ export class EsphomeMapper {
216
217
  return `esphome_${this.slug(`${deviceIdentifier}_${entityArg.platform}_${objectId}_${entityArg.key}_${subDevice ?? 0}`)}`;
217
218
  }
218
219
 
219
- private static deviceInfoFromConfig(configArg: IEsphomeConfig, sourceArg?: IEsphomeDeviceInfo, manualEntryArg?: IEsphomeManualEntry): IEsphomeDeviceInfo {
220
+ private static deviceInfoFromConfig(configArg: IEsphomeMappingConfig, sourceArg?: IEsphomeDeviceInfo, manualEntryArg?: IEsphomeManualEntry): IEsphomeDeviceInfo {
220
221
  return {
221
222
  ...sourceArg,
222
223
  ...configArg.deviceInfo,
@@ -226,12 +227,12 @@ export class EsphomeMapper {
226
227
  model: configArg.deviceInfo?.model || sourceArg?.model || manualEntryArg?.model,
227
228
  host: configArg.host || sourceArg?.host || manualEntryArg?.host || configArg.deviceInfo?.host,
228
229
  port: configArg.port || sourceArg?.port || manualEntryArg?.port || configArg.deviceInfo?.port || esphomeDefaultNativeApiPort,
229
- usesPassword: configArg.deviceInfo?.usesPassword ?? sourceArg?.usesPassword ?? Boolean(configArg.password || manualEntryArg?.password),
230
- apiEncryptionSupported: configArg.deviceInfo?.apiEncryptionSupported ?? sourceArg?.apiEncryptionSupported ?? Boolean(configArg.encryptionKey || configArg.noisePsk || manualEntryArg?.encryptionKey || manualEntryArg?.noisePsk),
230
+ usesPassword: configArg.deviceInfo?.usesPassword ?? sourceArg?.usesPassword,
231
+ apiEncryptionSupported: configArg.deviceInfo?.apiEncryptionSupported ?? sourceArg?.apiEncryptionSupported,
231
232
  };
232
233
  }
233
234
 
234
- private static normalizeStates(statesArg: IEsphomeConfig['states'] | IEsphomeSnapshot['states'] | undefined): IEsphomeEntityState[] {
235
+ private static normalizeStates(statesArg: IEsphomeMappingConfig['states'] | IEsphomeSnapshot['states'] | undefined): IEsphomeEntityState[] {
235
236
  if (!statesArg) {
236
237
  return [];
237
238
  }
@@ -450,7 +451,8 @@ export class EsphomeMapper {
450
451
  }
451
452
 
452
453
  private static mainDeviceId(snapshotArg: IEsphomeSnapshot): string {
453
- return `esphome.device.${this.slug(this.compactDeviceIdentifier(snapshotArg.deviceInfo) || snapshotArg.deviceInfo.name || snapshotArg.host || 'configured')}`;
454
+ const scope = snapshotArg.homeId ? `home.${encodeURIComponent(snapshotArg.homeId)}.` : '';
455
+ return `${scope}esphome.device.${this.slug(this.compactDeviceIdentifier(snapshotArg.deviceInfo) || snapshotArg.deviceInfo.name || snapshotArg.host || 'configured')}`;
454
456
  }
455
457
 
456
458
  private static subDeviceId(snapshotArg: IEsphomeSnapshot, subDeviceIdArg: string | number): string {
@@ -1,4 +1,5 @@
1
1
  import type { TEntityPlatform } from '../../core/types.js';
2
+ import type * as plugins from '../../plugins.js';
2
3
 
3
4
  export const esphomeDefaultNativeApiPort = 6053;
4
5
 
@@ -16,13 +17,43 @@ export type TEsphomeEntityPlatform =
16
17
  | 'water_heater';
17
18
 
18
19
  export interface IEsphomeConfig {
20
+ readonly homeId: string;
21
+ readonly host: string;
22
+ readonly port?: number;
23
+ readonly deviceName: string;
24
+ readonly expectedMacAddress: string;
25
+ readonly expectedModel?: string;
26
+ readonly name?: string;
27
+ readonly credentialKey?: string;
28
+ readonly credentialSource?: 'provider' | 'store';
29
+ readonly initialSnapshotTimeoutMs?: number;
30
+ readonly commandTimeoutMs?: number;
31
+ }
32
+
33
+ /** Only the published native client's Noise option is supported; legacy passwords are not. */
34
+ export type TEsphomeCredentials = Readonly<Pick<plugins.esphomeNativeApi.EspHomeClientOptions, 'psk'>>;
35
+
36
+ export interface IEsphomeCredentialRequest {
37
+ readonly capturedHomeId: string;
38
+ readonly credentialKey: string;
39
+ readonly signal: AbortSignal;
40
+ /** Absolute milliseconds on performance.now(), including provisioning and native setup. */
41
+ readonly monotonicDeadline: number;
42
+ }
43
+
44
+ export interface IEsphomeIntegrationOptions {
45
+ readonly homeId?: string;
46
+ readonly capacity?: number;
47
+ readonly resolveCredentials?: (requestArg: IEsphomeCredentialRequest) => Promise<TEsphomeCredentials>;
48
+ }
49
+
50
+ /** Pure mapper input. This cannot be used for live setup. */
51
+ export interface IEsphomeMappingConfig {
52
+ homeId?: string;
19
53
  host?: string;
20
54
  port?: number;
21
55
  name?: string;
22
56
  deviceName?: string;
23
- password?: string;
24
- encryptionKey?: string;
25
- noisePsk?: string;
26
57
  initialSnapshotTimeoutMs?: number;
27
58
  commandTimeoutMs?: number;
28
59
  snapshot?: IEsphomeSnapshot;
@@ -151,6 +182,7 @@ export interface IEsphomeEntityState {
151
182
  }
152
183
 
153
184
  export interface IEsphomeSnapshot {
185
+ homeId?: string;
154
186
  host?: string;
155
187
  port: number;
156
188
  connected: boolean;
@@ -236,9 +268,6 @@ export interface IEsphomeManualEntry {
236
268
  deviceName?: string;
237
269
  model?: string;
238
270
  manufacturer?: string;
239
- password?: string;
240
- encryptionKey?: string;
241
- noisePsk?: string;
242
271
  metadata?: Record<string, unknown>;
243
272
  }
244
273
 
@@ -0,0 +1,66 @@
1
+ import type { IEsphomeConfig, TEsphomeCredentials } from './esphome.types.js';
2
+
3
+ export const esphomeData = (valueArg: unknown, fieldsArg: readonly string[], labelArg: string): Record<string, unknown> => {
4
+ if (!valueArg || typeof valueArg !== 'object' || (Object.getPrototypeOf(valueArg) !== Object.prototype && Object.getPrototypeOf(valueArg) !== null)) {
5
+ throw new TypeError(`${labelArg} must be a plain data object.`);
6
+ }
7
+ const captured: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
8
+ for (const key of Reflect.ownKeys(valueArg)) {
9
+ const field = Object.getOwnPropertyDescriptor(valueArg, key);
10
+ if (typeof key !== 'string' || !fieldsArg.includes(key) || !field || !field.enumerable || !('value' in field)) {
11
+ throw new TypeError(`${labelArg} contains an unsupported field or accessor.`);
12
+ }
13
+ captured[key] = field.value as unknown;
14
+ }
15
+ return captured;
16
+ };
17
+
18
+ export const esphomeIdentifier = (valueArg: unknown, labelArg: string): string => {
19
+ if (typeof valueArg !== 'string' || !valueArg || valueArg.trim() !== valueArg || valueArg.length > 256 || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
20
+ throw new TypeError(`${labelArg} must be a non-empty identifier without surrounding whitespace or control characters.`);
21
+ }
22
+ return valueArg;
23
+ };
24
+
25
+ export const esphomeTimeout = (valueArg: unknown, defaultArg: number): number => {
26
+ const value = valueArg ?? defaultArg;
27
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 1 || value > 2147483647) {
28
+ throw new TypeError('ESPHome timeouts must be integers from 1 through 2147483647.');
29
+ }
30
+ return value;
31
+ };
32
+
33
+ export const esphomeMac = (valueArg: unknown): string | undefined => {
34
+ if (typeof valueArg !== 'string' || !/^(?:[a-f\d]{12}|(?:[a-f\d]{2}:){5}[a-f\d]{2})$/i.test(valueArg)) return undefined;
35
+ return valueArg.replace(/:/g, '').toLowerCase();
36
+ };
37
+
38
+ export const captureEsphomeConfig = (valueArg: IEsphomeConfig): IEsphomeConfig => {
39
+ const value = esphomeData(valueArg, ['homeId', 'host', 'port', 'name', 'deviceName', 'expectedMacAddress', 'expectedModel', 'credentialKey', 'credentialSource', 'initialSnapshotTimeoutMs', 'commandTimeoutMs'], 'ESPHome configuration');
40
+ const host = esphomeIdentifier(value.host, 'ESPHome host');
41
+ if (/\s/.test(host)) throw new TypeError('ESPHome host must not contain whitespace.');
42
+ const port = value.port ?? 6053;
43
+ if (typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65535) throw new TypeError('ESPHome port must be an integer from 1 through 65535.');
44
+ const mac = esphomeMac(value.expectedMacAddress);
45
+ if (!mac) throw new TypeError('ESPHome expectedMacAddress must be a complete MAC address.');
46
+ if (value.credentialSource !== undefined && value.credentialSource !== 'provider' && value.credentialSource !== 'store') throw new TypeError('ESPHome credential source must be provider or store.');
47
+ if (value.credentialSource !== undefined && value.credentialKey === undefined) throw new TypeError('ESPHome credential source requires a credential reference.');
48
+ return Object.freeze({
49
+ homeId: esphomeIdentifier(value.homeId, 'ESPHome configuration homeId'), host, port,
50
+ deviceName: esphomeIdentifier(value.deviceName, 'ESPHome selected device name'), expectedMacAddress: mac,
51
+ expectedModel: value.expectedModel === undefined ? undefined : esphomeIdentifier(value.expectedModel, 'ESPHome selected model'),
52
+ name: value.name === undefined ? undefined : esphomeIdentifier(value.name, 'ESPHome display name'),
53
+ credentialKey: value.credentialKey === undefined ? undefined : esphomeIdentifier(value.credentialKey, 'ESPHome credential reference'),
54
+ credentialSource: value.credentialSource as 'provider' | 'store' | undefined,
55
+ initialSnapshotTimeoutMs: esphomeTimeout(value.initialSnapshotTimeoutMs, 15000), commandTimeoutMs: esphomeTimeout(value.commandTimeoutMs, 5000),
56
+ });
57
+ };
58
+
59
+ export const captureEsphomeCredentials = (valueArg: unknown): TEsphomeCredentials => {
60
+ const value = esphomeData(valueArg, ['psk'], 'ESPHome credential provider result');
61
+ const psk = value.psk;
62
+ if (psk !== undefined && psk !== null && (typeof psk !== 'string' || !/^[A-Za-z0-9+/]{43}=$/.test(psk) || Buffer.from(psk, 'base64').length !== 32 || Buffer.from(psk, 'base64').toString('base64') !== psk)) {
63
+ throw new TypeError('ESPHome Noise credentials require a canonical base64-encoded 32-byte key.');
64
+ }
65
+ return Object.freeze({ psk: psk as string | null | undefined });
66
+ };
@@ -10,15 +10,25 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
10
10
 
11
11
  Install the parent package with `pnpm add @smarthome.exchange/integrations`. The native client requires Node.js 22.20 or newer and ESPHome 2025.10 or newer. Configure one native API node per integration instance; its advertised logical sub-devices share that connection.
12
12
 
13
- Provide a host and an optional port, which defaults to `6053`. For an encrypted device, supply its canonical base64-encoded 32-byte key as `encryptionKey` or `noisePsk`. Conflicting keys, legacy API passwords, injected snapshots, and command executors are rejected during live setup. When a key is supplied, a plaintext connection is rejected before controls become available.
13
+ Provide `homeId`, a host, the selected native `deviceName`, and `expectedMacAddress`; the optional port defaults to `6053`. An optional `expectedModel` must match the model in the actual native device-info response. Setup checks these observed identity fields before publishing readiness, including after reconnect. Discovery supplies endpoint hints and does not prove identity or select the owning home. Home and device/entity routing identifiers include the independently admitted home. MAC/name checks are selection constraints, not authentication; use Noise encryption for authenticated transport.
14
+
15
+ The embedding host must independently supply the same `homeId` in setup and config-flow contexts. An optional constructor home constrains this choice and never substitutes for context. The first valid admission binds an otherwise unbound integration, even when setup fails. Construction performs no network or persistence work. Shared ownership defaults to sixteen pending operations, live runtimes and retained cleanup resources.
16
+
17
+ Configuration contains only an optional `credentialKey` reference. Resolve it through the constructor's borrowed asynchronous `resolveCredentials` callback, which receives the captured home, reference, cancellation signal and an absolute `performance.now()` deadline. Its result supports the published client's `{ psk }` option only: a canonical base64-encoded 32-byte Noise key, or no key for a plaintext endpoint. The entire setup deadline includes credential provisioning. Cancellation joins the actual provider promise and rejects late results before transport construction; an uncooperative provider can therefore delay shutdown. Provider errors are replaced with fixed diagnostics to avoid exposing secrets.
14
18
 
15
19
  ```typescript
16
20
  import { EsphomeIntegration } from '@smarthome.exchange/integrations';
17
21
 
18
- async function inspectNode(host: string, encryptionKey: string) {
19
- const integration = new EsphomeIntegration();
22
+ async function inspectNode(homeId: string, host: string, macAddress: string, resolveNoiseKey: (reference: string, signal: AbortSignal) => Promise<string>) {
23
+ const integration = new EsphomeIntegration({
24
+ resolveCredentials: async ({ credentialKey, signal }) => ({ psk: await resolveNoiseKey(credentialKey, signal) }),
25
+ });
20
26
  try {
21
- const runtime = await integration.setup({ host, encryptionKey }, {
27
+ const runtime = await integration.setup({
28
+ homeId, host, deviceName: 'selected_node', expectedMacAddress: macAddress,
29
+ credentialKey: 'selected-node-noise',
30
+ }, {
31
+ homeId,
22
32
  abortSignal: AbortSignal.timeout(15_000),
23
33
  });
24
34
  return await runtime.devices();
@@ -28,7 +38,9 @@ async function inspectNode(host: string, encryptionKey: string) {
28
38
  }
29
39
  ```
30
40
 
31
- Obtain encryption keys through the application's secret provisioning flow. Discovery metadata does not prove a device's identity or grant authority to control it. The caller remains responsible for home access and tool authorization.
41
+ Obtain encryption keys through the application's secret provisioning flow. Forms accept references, and discovery never copies passwords, keys or arbitrary TXT/manual metadata. Raw `encryptionKey`, `noisePsk`, legacy passwords, injected snapshots and command executors are rejected in live configuration. A provisioned PSK session that negotiates plaintext is rejected before controls become available. The caller remains responsible for authentication, home access and tool authorization.
42
+
43
+ For an explicitly selected encrypted store reference, set `credentialSource: 'store'` and supply an initialized actual `NoSqlConfigStore` scoped to this home and the `esphome` domain. The caller provisions `{ psk }` under `credentialKey` through the store's public API. The default source is the external provider; neither source falls through to the other on failure. Store reads use the store's configured operation timeout and expose no per-call cancellation option; shutdown joins their actual completion and fences late results. The store, database and encryption-key provider remain caller-owned. No store is needed for an external provider or plaintext setup.
32
44
 
33
45
  ## State and controls
34
46
 
@@ -38,7 +50,9 @@ Readable platforms are lights, switches, fans, covers, climate entities, numbers
38
50
 
39
51
  Use `runtime.writeFeature` with a device and feature returned by that runtime, or `runtime.callService` with an explicit entity or device target. A device target must select exactly one supported entity. Supplying both targets requires them to name the same device. Foreign integration devices, unrelated service domains, out-of-range cover positions, and unknown service settings are rejected. Service data must contain ordinary values without accessors or symbol fields. The `esphome` service domain can select a supported native entity; platform domains must match the selected entity.
40
52
 
41
- Each entity admits one pending command. Success requires a subsequent matching state report within `commandTimeoutMs`, which defaults to 5 seconds. A matching report confirms the observed state; the protocol does not supply a unique action receipt or exactly-once physical execution guarantee. Timeout or cancellation can follow a physical effect, so failed or interrupted commands must not be retried automatically. Destruction disconnects the node, removes subscriptions, and cancels setup and pending confirmations.
53
+ Each entity admits one pending command. Success requires a subsequent matching state report within `commandTimeoutMs`, which defaults to 5 seconds. A matching report confirms the observed state; the protocol does not supply a unique action receipt or exactly-once physical execution guarantee. Timeout or cancellation can follow a physical effect, so failed or interrupted commands must not be retried automatically. Destruction publishes one stable terminal promise, aborts owned work, disconnects the node, drains pending confirmations and all subscriptions, and retains cleanup failures. `integration.retryCleanup()` retries failed cleanup explicitly without reconnecting or replaying commands. Destruction never closes borrowed credential or persistence resources.
54
+
55
+ The published SDK's synchronous `disconnect()` cancels reconnection, disposes its owned transport and reports disconnect/health transitions. Fixture peers observe the resulting TCP close for plaintext and Noise sessions. The SDK does not expose an OS socket-close completion promise, and this adapter makes no stronger OS-close or guaranteed memory-zeroization claim.
42
56
 
43
57
  Verification uses real plaintext and Noise-encrypted local TCP fixtures plus the published native client's encoder/decoder and `MockTransport`. Real hardware behavior has not been verified in this lane.
44
58