@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,55 +1,60 @@
1
+ import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
1
2
  import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
2
- import type { ISoundtouchConfig } from './soundtouch.types.js';
3
- import { soundtouchDefaultDlnaPort, soundtouchDefaultPort } from './soundtouch.types.js';
3
+ import { SoundtouchClient } from './soundtouch.classes.client.js';
4
+ import { createSoundtouchDiscoveryDescriptor } from './soundtouch.discovery.js';
5
+ import type { ISoundtouchConfig, ISoundtouchHomeOptions } from './soundtouch.types.js';
6
+ import { normalizeSoundtouchConfig, soundtouchKeys, soundtouchRecord, soundtouchText } from './soundtouch.validation.js';
4
7
 
5
8
  export class SoundtouchConfigFlow implements IConfigFlow<ISoundtouchConfig> {
9
+ readonly #owner: NativeIntegrationOwner;
10
+ readonly #scope: NativeIntegrationScope;
11
+ readonly #standalone: boolean;
12
+ public constructor(optionsArg: ISoundtouchHomeOptions | NativeIntegrationOwner = {}) {
13
+ this.#standalone = !(optionsArg instanceof NativeIntegrationOwner);
14
+ this.#owner = optionsArg instanceof NativeIntegrationOwner ? optionsArg : new NativeIntegrationOwner('soundtouch', optionsArg);
15
+ this.#scope = this.#owner.createFlowScope();
16
+ }
6
17
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<ISoundtouchConfig>> {
7
- void contextArg;
18
+ // Capture host authority synchronously before the matcher or any submission can await.
19
+ const context = this.#scope.admit(contextArg);
20
+ const matched = await createSoundtouchDiscoveryDescriptor().getValidators()[0].validate(candidateArg, {});
21
+ if (!matched.matched || !matched.candidate) return { kind: 'error', error: matched.reason };
22
+ const candidate = matched.candidate;
8
23
  return {
9
- kind: 'form',
10
- title: 'Connect Bose SoundTouch',
11
- description: candidateArg.source === 'manual'
12
- ? 'Configure a local Bose SoundTouch speaker.'
13
- : 'Confirm or adjust the discovered local Bose SoundTouch speaker.',
24
+ kind: 'form', title: 'Connect selected Bose SoundTouch speaker',
25
+ description: 'Acknowledge the plaintext LAN connection and choose an absolute integer volume ceiling. The host home and selected endpoint/deviceID/type stay fixed.',
14
26
  fields: [
15
- { name: 'host', label: 'Host', type: 'text', required: true },
16
- { name: 'port', label: 'Port', type: 'number' },
17
- { name: 'name', label: 'Name', type: 'text' },
27
+ { name: 'allowUnencryptedLan', label: 'Acknowledge unauthenticated plaintext LAN', type: 'boolean', required: true },
28
+ { name: 'volumeLimit', label: 'Maximum absolute volume (0–100)', type: 'number', required: true },
29
+ { name: 'name', label: 'Local display label', type: 'text' },
18
30
  ],
19
31
  submit: async (valuesArg) => {
20
- const host = stringValue(valuesArg.host) || candidateArg.host;
21
- if (!host) {
22
- return { kind: 'error', title: 'SoundTouch host required', error: 'SoundTouch setup requires a host.' };
32
+ try {
33
+ const values = soundtouchRecord(valuesArg, 'config flow values');
34
+ soundtouchKeys(values, ['homeId', 'allowUnencryptedLan', 'volumeLimit', 'name', 'requestTimeoutMs', 'maxPendingOperations', 'maxResponseBytes']);
35
+ if (values.homeId !== undefined && soundtouchText(values.homeId, 'submitted homeId', 256) !== context.homeId) {
36
+ throw new TypeError('SoundTouch submitted home conflicts with the captured host home.');
37
+ }
38
+ const config = normalizeSoundtouchConfig({ ...values, homeId: context.homeId,
39
+ host: candidate.host, port: candidate.port, deviceId: candidate.id, type: candidate.metadata?.type });
40
+ return await this.#scope.run(context, config.homeId, async (leaseArg) => {
41
+ const client = new SoundtouchClient(config, { ...leaseArg.context, abortSignal: leaseArg.signal });
42
+ let cleanupAttempted = false;
43
+ leaseArg.own(async () => {
44
+ if (cleanupAttempted) return client.retryCleanup();
45
+ cleanupAttempted = true;
46
+ await client.destroy();
47
+ });
48
+ await client.getSnapshot(leaseArg.signal);
49
+ leaseArg.assertOpen();
50
+ return { kind: 'done' as const, title: 'Selected SoundTouch speaker verified', config };
51
+ });
52
+ } catch (errorArg) {
53
+ return { kind: 'error', error: errorArg instanceof Error ? errorArg.message : 'SoundTouch selected setup failed.' };
23
54
  }
24
- return {
25
- kind: 'done',
26
- title: 'Bose SoundTouch configured',
27
- config: {
28
- host,
29
- port: numberValue(valuesArg.port) || candidateArg.port || soundtouchDefaultPort,
30
- dlnaPort: soundtouchDefaultDlnaPort,
31
- name: stringValue(valuesArg.name) || candidateArg.name,
32
- deviceId: candidateArg.id,
33
- model: candidateArg.model,
34
- macAddress: candidateArg.macAddress,
35
- },
36
- };
37
55
  },
38
56
  };
39
57
  }
58
+ public destroy(): Promise<void> { return this.#standalone ? this.#owner.close() : this.#scope.close(); }
59
+ public retryCleanup(): Promise<void> { return this.#standalone ? this.#owner.retryCleanup() : this.#scope.retryCleanup(); }
40
60
  }
41
-
42
- const stringValue = (valueArg: unknown): string | undefined => {
43
- return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
44
- };
45
-
46
- const numberValue = (valueArg: unknown): number | undefined => {
47
- if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
48
- return valueArg;
49
- }
50
- if (typeof valueArg === 'string' && valueArg.trim()) {
51
- const parsed = Number(valueArg);
52
- return Number.isFinite(parsed) ? parsed : undefined;
53
- }
54
- return undefined;
55
- };
@@ -1,178 +1,123 @@
1
- import type * as shxInterfaces from '@smarthome.exchange/interfaces';
1
+ import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
- import type { IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
3
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
4
+ import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
4
5
  import { SoundtouchClient } from './soundtouch.classes.client.js';
5
6
  import { SoundtouchConfigFlow } from './soundtouch.classes.configflow.js';
6
7
  import { createSoundtouchDiscoveryDescriptor } from './soundtouch.discovery.js';
7
8
  import { SoundtouchMapper } from './soundtouch.mapper.js';
8
- import type { ISoundtouchCommandRequest, ISoundtouchConfig, TSoundtouchCommand } from './soundtouch.types.js';
9
+ import type { ISoundtouchConfig, ISoundtouchHomeOptions, ISoundtouchSnapshot } from './soundtouch.types.js';
10
+ import { soundtouchProfile } from './soundtouch.types.js';
11
+ import { normalizeSoundtouchConfig, soundtouchBoolean, soundtouchInteger, soundtouchKeys, soundtouchRecord } from './soundtouch.validation.js';
9
12
 
10
13
  export class SoundtouchIntegration extends BaseIntegration<ISoundtouchConfig> {
11
14
  public readonly domain = 'soundtouch';
12
15
  public readonly displayName = 'Bose SoundTouch';
13
16
  public readonly status = 'control-runtime' as const;
17
+ public readonly metadata = soundtouchProfile.metadata;
14
18
  public readonly discoveryDescriptor = createSoundtouchDiscoveryDescriptor();
15
- public readonly configFlow = new SoundtouchConfigFlow();
16
- public readonly metadata = {
17
- source: 'home-assistant/core',
18
- upstreamPath: 'homeassistant/components/soundtouch',
19
- upstreamDomain: 'soundtouch',
20
- integrationType: 'device',
21
- iotClass: 'local_polling',
22
- requirements: ['libsoundtouch==0.8'],
23
- dependencies: [],
24
- afterDependencies: [],
25
- codeowners: ['@kroimon'],
26
- configFlow: true,
27
- documentation: 'https://www.home-assistant.io/integrations/soundtouch',
28
- zeroconf: ['_soundtouch._tcp.local.'],
29
- };
19
+ public readonly configFlow: SoundtouchConfigFlow;
20
+ readonly #owner: NativeIntegrationOwner;
30
21
 
31
- public async setup(configArg: ISoundtouchConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
32
- void contextArg;
33
- return new SoundtouchRuntime(new SoundtouchClient(configArg));
22
+ public constructor(optionsArg: ISoundtouchHomeOptions = {}) {
23
+ super();
24
+ this.#owner = new NativeIntegrationOwner('soundtouch', optionsArg);
25
+ this.configFlow = new SoundtouchConfigFlow(this.#owner);
34
26
  }
35
-
36
- public async destroy(): Promise<void> {}
27
+ public setup(configArg: ISoundtouchConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
28
+ const config = normalizeSoundtouchConfig(configArg);
29
+ return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
30
+ const client = new SoundtouchClient(config, { ...leaseArg.context, abortSignal: leaseArg.signal });
31
+ let cleanupAttempted = false;
32
+ leaseArg.own(async () => {
33
+ if (cleanupAttempted) return client.retryCleanup();
34
+ cleanupAttempted = true;
35
+ await client.destroy();
36
+ });
37
+ const snapshot = await client.getSnapshot(leaseArg.signal);
38
+ leaseArg.assertOpen();
39
+ const runtime = new SoundtouchRuntime(client, snapshot, leaseArg);
40
+ leaseArg.promoteToRuntime();
41
+ return runtime;
42
+ });
43
+ }
44
+ public destroy(): Promise<void> { return this.#owner.close(); }
45
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
37
46
  }
38
-
39
47
  export class HomeAssistantSoundtouchIntegration extends SoundtouchIntegration {}
40
48
 
41
49
  class SoundtouchRuntime implements IIntegrationRuntime {
42
- public domain = 'soundtouch';
43
-
44
- constructor(private readonly client: SoundtouchClient) {}
45
-
46
- public async devices(): Promise<shxInterfaces.data.IDeviceDefinition[]> {
47
- return SoundtouchMapper.toDevices(await this.client.getSnapshot());
50
+ public readonly domain = 'soundtouch';
51
+ readonly #deviceId: string;
52
+ readonly #entityId: string;
53
+ constructor(private readonly client: SoundtouchClient, snapshotArg: ISoundtouchSnapshot, private readonly lease: INativeSetupLease) {
54
+ this.#deviceId = SoundtouchMapper.deviceId(snapshotArg);
55
+ this.#entityId = SoundtouchMapper.mediaEntityId(snapshotArg);
56
+ }
57
+ public isHealthy(): boolean { return !this.lease.signal.aborted && this.client.isHealthy(); }
58
+ public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
59
+ this.lease.assertOpen(); return SoundtouchMapper.toDevices(await this.client.getSnapshot(signalArg));
48
60
  }
49
-
50
61
  public async entities(): Promise<IIntegrationEntity[]> {
51
- return SoundtouchMapper.toEntities(await this.client.getSnapshot());
62
+ this.lease.assertOpen(); return SoundtouchMapper.toEntities(await this.client.getSnapshot());
63
+ }
64
+ public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
65
+ this.lease.assertOpen();
66
+ if (typeof handlerArg !== 'function') throw new TypeError('SoundTouch runtime subscriber must be a function.');
67
+ const unsubscribe = this.client.subscribe((eventArg) => {
68
+ handlerArg({ type: eventArg.type === 'error' ? 'error' : 'state_changed', integrationDomain: 'soundtouch',
69
+ deviceId: this.#deviceId, entityId: this.#entityId, timestamp: Date.now(),
70
+ data: eventArg.type === 'snapshot' ? { snapshot: eventArg.snapshot, entity: SoundtouchMapper.toEntities(eventArg.snapshot)[0] }
71
+ : eventArg.type === 'control' ? { observation: eventArg.observation, effectOutcome: 'indeterminate' }
72
+ : { message: eventArg.error.message } });
73
+ }, signalArg);
74
+ return async () => unsubscribe();
52
75
  }
53
-
54
76
  public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
55
77
  try {
56
- if (requestArg.domain === 'media_player') {
57
- return await this.callMediaPlayerService(requestArg);
78
+ this.lease.assertOpen();
79
+ const request = soundtouchRecord(requestArg, 'service request');
80
+ soundtouchKeys(request, ['domain', 'service', 'target', 'data']);
81
+ if (typeof request.domain !== 'string' || typeof request.service !== 'string') throw new TypeError('SoundTouch service domain and name must be strings.');
82
+ const target = soundtouchRecord(request.target, 'service target'); soundtouchKeys(target, ['deviceId', 'entityId']);
83
+ if (target.deviceId === undefined && target.entityId === undefined
84
+ || target.deviceId !== undefined && target.deviceId !== this.#deviceId
85
+ || target.entityId !== undefined && target.entityId !== this.#entityId) throw new TypeError('SoundTouch services require an exact owned home/device or speaker entity target.');
86
+ const data = request.data === undefined ? {} : soundtouchRecord(request.data, 'service data');
87
+ if (request.domain === 'soundtouch' && ['status', 'snapshot', 'refresh'].includes(request.service)) {
88
+ soundtouchKeys(data, []); return { success: true, data: await this.client.getSnapshot() };
58
89
  }
59
- if (requestArg.domain === 'soundtouch') {
60
- return await this.callSoundtouchService(requestArg);
90
+ if (request.domain !== 'soundtouch' && request.domain !== 'media_player') throw new TypeError('SoundTouch service domain is unsupported.');
91
+ if (request.service === 'set_volume' || request.service === 'volume_set') {
92
+ soundtouchKeys(data, ['volume']); return observed(await this.client.setVolume(soundtouchInteger(data.volume, 0, 100, 'absolute volume')));
61
93
  }
62
- return { success: false, error: `Unsupported SoundTouch service domain: ${requestArg.domain}` };
63
- } catch (errorArg) {
64
- return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
65
- }
66
- }
67
-
68
- public async destroy(): Promise<void> {
69
- await this.client.destroy();
70
- }
71
-
72
- private async callMediaPlayerService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
73
- const command = this.commandFromMediaService(requestArg.service);
74
- if (!command) {
75
- return { success: false, error: `Unsupported SoundTouch media_player service: ${requestArg.service}` };
76
- }
77
- const result = await this.client.execute(this.commandRequest(command, requestArg));
78
- return { success: true, data: result };
79
- }
80
-
81
- private async callSoundtouchService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
82
- if (requestArg.service === 'play_preset' || requestArg.service === 'select_preset') {
83
- const presetId = this.stringData(requestArg, 'preset_id') || this.stringData(requestArg, 'preset') || this.stringData(requestArg, 'presetId') || this.numberStringData(requestArg, 'preset');
84
- const presetName = this.stringData(requestArg, 'preset_name') || this.stringData(requestArg, 'presetName') || this.stringData(requestArg, 'name');
85
- const result = await this.client.execute({ command: 'play_preset', presetId, presetName });
86
- return { success: true, data: result };
87
- }
88
- if (requestArg.service === 'play' || requestArg.service === 'pause' || requestArg.service === 'media_play' || requestArg.service === 'media_pause' || requestArg.service === 'volume_set' || requestArg.service === 'volume_mute' || requestArg.service === 'select_source' || requestArg.service === 'play_media') {
89
- return this.callMediaPlayerService({ ...requestArg, domain: 'media_player' });
90
- }
91
- return { success: false, error: `Unsupported SoundTouch service: ${requestArg.service}` };
92
- }
93
-
94
- private commandFromMediaService(serviceArg: string): TSoundtouchCommand | undefined {
95
- if (serviceArg === 'turn_on') {
96
- return 'turn_on';
97
- }
98
- if (serviceArg === 'turn_off') {
99
- return 'turn_off';
100
- }
101
- if (serviceArg === 'media_play' || serviceArg === 'play') {
102
- return 'play';
103
- }
104
- if (serviceArg === 'media_pause' || serviceArg === 'pause') {
105
- return 'pause';
106
- }
107
- if (serviceArg === 'media_play_pause' || serviceArg === 'play_pause') {
108
- return 'play_pause';
109
- }
110
- if (serviceArg === 'media_previous_track' || serviceArg === 'previous_track') {
111
- return 'previous_track';
112
- }
113
- if (serviceArg === 'media_next_track' || serviceArg === 'next_track') {
114
- return 'next_track';
115
- }
116
- if (serviceArg === 'volume_up') {
117
- return 'volume_up';
118
- }
119
- if (serviceArg === 'volume_down') {
120
- return 'volume_down';
121
- }
122
- if (serviceArg === 'volume_set' || serviceArg === 'set_volume') {
123
- return 'set_volume';
124
- }
125
- if (serviceArg === 'volume_mute' || serviceArg === 'mute') {
126
- return 'mute';
127
- }
128
- if (serviceArg === 'select_source' || serviceArg === 'select_input') {
129
- return 'select_source';
130
- }
131
- if (serviceArg === 'play_media') {
132
- return 'play_media';
133
- }
134
- return undefined;
135
- }
136
-
137
- private commandRequest(commandArg: TSoundtouchCommand, requestArg: IServiceCallRequest): ISoundtouchCommandRequest {
138
- const request: ISoundtouchCommandRequest = { command: commandArg };
139
- if (commandArg === 'set_volume') {
140
- request.volumeLevel = this.numberData(requestArg, 'volume_level') ?? this.numberData(requestArg, 'volumeLevel');
141
- request.volume = this.numberData(requestArg, 'volume');
142
- }
143
- if (commandArg === 'mute') {
144
- request.muted = this.boolData(requestArg, 'is_volume_muted') ?? this.boolData(requestArg, 'mute') ?? this.boolData(requestArg, 'muted');
145
- }
146
- if (commandArg === 'select_source') {
147
- request.source = this.stringData(requestArg, 'source') || this.stringData(requestArg, 'input');
148
- }
149
- if (commandArg === 'play_media') {
150
- request.mediaId = this.stringData(requestArg, 'media_content_id') || this.stringData(requestArg, 'mediaId') || this.stringData(requestArg, 'uri') || this.numberStringData(requestArg, 'media_content_id');
151
- request.mediaType = this.stringData(requestArg, 'media_content_type') || this.stringData(requestArg, 'mediaType');
152
- request.url = this.stringData(requestArg, 'url');
153
- request.presetId = this.stringData(requestArg, 'preset_id') || this.stringData(requestArg, 'presetId') || this.numberStringData(requestArg, 'preset');
154
- request.presetName = this.stringData(requestArg, 'preset_name') || this.stringData(requestArg, 'presetName');
155
- }
156
- return request;
157
- }
158
-
159
- private stringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
160
- const value = requestArg.data?.[keyArg];
161
- return typeof value === 'string' && value ? value : undefined;
162
- }
163
-
164
- private numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
165
- const value = requestArg.data?.[keyArg];
166
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
94
+ if (request.service === 'set_mute' || request.service === 'volume_mute') {
95
+ soundtouchKeys(data, ['muted']); return observed(await this.client.setMuted(soundtouchBoolean(data.muted)));
96
+ }
97
+ throw new TypeError('SoundTouch supports status reads and explicit absolute volume/mute only.');
98
+ } catch (errorArg) { return failed(errorArg); }
167
99
  }
168
-
169
- private numberStringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
170
- const value = this.numberData(requestArg, keyArg);
171
- return typeof value === 'number' ? String(value) : undefined;
100
+ public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
101
+ try {
102
+ this.lease.assertOpen();
103
+ const request = soundtouchRecord(requestArg, 'feature write'); soundtouchKeys(request, ['device', 'feature', 'value']);
104
+ const device = soundtouchRecord(request.device, 'feature device');
105
+ const feature = soundtouchRecord(request.feature, 'feature');
106
+ if (device.id !== this.#deviceId || device.integrationDomain !== 'soundtouch') throw new TypeError('SoundTouch feature device is outside this home runtime.');
107
+ if ((feature.id !== 'volume' && feature.id !== 'mute') || feature.capability !== 'media') throw new TypeError('SoundTouch feature is not an admitted absolute media control.');
108
+ const value = feature.id === 'volume' ? soundtouchInteger(request.value, 0, 100, 'absolute volume') : soundtouchBoolean(request.value);
109
+ return observed(await this.client.writeFeature(feature.id, value, signalArg));
110
+ } catch (errorArg) { return failed(errorArg); }
172
111
  }
112
+ public destroy(): Promise<void> { return this.lease.close(); }
113
+ }
173
114
 
174
- private boolData(requestArg: IServiceCallRequest, keyArg: string): boolean | undefined {
175
- const value = requestArg.data?.[keyArg];
176
- return typeof value === 'boolean' ? value : undefined;
177
- }
115
+ function observed(observationArg: plugins.ecobridgeDeviceManager.IBoseSoundTouchControlResult): IServiceCallResult {
116
+ return { success: false, data: { ...observationArg, effectOutcome: 'indeterminate' } };
117
+ }
118
+ function failed(errorArg: unknown): IServiceCallResult {
119
+ return { success: false, error: errorArg instanceof Error ? errorArg.message : 'SoundTouch operation failed.', data: {
120
+ effectOutcome: errorArg instanceof plugins.ecobridgeDeviceManager.BoseSoundTouchProtocolError ? errorArg.effectOutcome : 'not-sent',
121
+ code: errorArg instanceof plugins.ecobridgeDeviceManager.BoseSoundTouchProtocolError ? errorArg.code : 'INVALID_OPERATION',
122
+ } };
178
123
  }
@@ -1,137 +1,25 @@
1
1
  import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
2
- import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
3
- import type { ISoundtouchManualEntry, ISoundtouchMdnsRecord } from './soundtouch.types.js';
4
- import { soundtouchDefaultPort } from './soundtouch.types.js';
5
-
6
- const soundtouchDomain = 'soundtouch';
7
- const soundtouchMdnsType = '_soundtouch._tcp.local.';
8
-
9
- export class SoundtouchMdnsMatcher implements IDiscoveryMatcher<ISoundtouchMdnsRecord> {
10
- public id = 'soundtouch-mdns-match';
11
- public source = 'mdns' as const;
12
- public description = 'Recognize Bose SoundTouch zeroconf advertisements.';
13
-
14
- public async matches(recordArg: ISoundtouchMdnsRecord, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
15
- void contextArg;
16
- const type = recordArg.type || '';
17
- const txt = recordArg.txt || {};
18
- const matched = normalizeMdnsType(type) === normalizeMdnsType(soundtouchMdnsType);
19
- if (!matched) {
20
- return { matched: false, confidence: 'low', reason: 'mDNS record is not a SoundTouch advertisement.' };
21
- }
22
-
23
- const id = valueForKey(txt, 'deviceId') || valueForKey(txt, 'deviceID') || valueForKey(txt, 'id') || recordArg.name;
24
- return {
25
- matched: true,
26
- confidence: recordArg.host ? 'certain' : 'high',
27
- reason: 'mDNS record matches Bose SoundTouch zeroconf metadata.',
28
- normalizedDeviceId: id,
29
- candidate: {
30
- source: 'mdns',
31
- integrationDomain: soundtouchDomain,
32
- id,
33
- host: recordArg.host,
34
- port: recordArg.port || soundtouchDefaultPort,
35
- name: recordArg.name,
36
- manufacturer: 'Bose Corporation',
37
- model: valueForKey(txt, 'type') || valueForKey(txt, 'model'),
38
- macAddress: valueForKey(txt, 'mac') || valueForKey(txt, 'macAddress'),
39
- metadata: { mdnsName: recordArg.name, mdnsType: type, txt },
40
- },
41
- };
42
- }
43
- }
44
-
45
- export class SoundtouchManualMatcher implements IDiscoveryMatcher<ISoundtouchManualEntry> {
46
- public id = 'soundtouch-manual-match';
47
- public source = 'manual' as const;
48
- public description = 'Recognize manual Bose SoundTouch setup entries.';
49
-
50
- public async matches(inputArg: ISoundtouchManualEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
51
- void contextArg;
52
- const haystack = `${inputArg.name || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''}`.toLowerCase();
53
- const matched = Boolean(inputArg.host || inputArg.metadata?.soundtouch || haystack.includes('soundtouch') || haystack.includes('bose'));
54
- if (!matched) {
55
- return { matched: false, confidence: 'low', reason: 'Manual entry does not contain SoundTouch setup hints.' };
2
+ import type { IDiscoveryCandidate, IDiscoveryMatch } from '../../core/types.js';
3
+ import { readSoundtouchEndpoint, soundtouchKeys, soundtouchRecord, soundtouchText } from './soundtouch.validation.js';
4
+
5
+ function matchSoundtouchCandidate(valueArg: unknown): IDiscoveryMatch {
6
+ try {
7
+ const input = soundtouchRecord(valueArg, 'selected candidate'); soundtouchKeys(input, ['source', 'integrationDomain', 'id', 'host', 'port', 'name', 'metadata']);
8
+ if (input.integrationDomain !== 'soundtouch' || input.source !== 'manual' && input.source !== 'custom') {
9
+ return { matched: false, confidence: 'low', reason: 'SoundTouch requires an explicitly selected manual/custom candidate.' };
56
10
  }
57
-
58
- const id = inputArg.deviceId || inputArg.macAddress || inputArg.id || (inputArg.host ? `${inputArg.host}:${inputArg.port || soundtouchDefaultPort}` : undefined);
59
- return {
60
- matched: true,
61
- confidence: inputArg.host ? 'high' : 'medium',
62
- reason: 'Manual entry can start SoundTouch setup.',
63
- normalizedDeviceId: id,
64
- candidate: {
65
- source: 'manual',
66
- integrationDomain: soundtouchDomain,
67
- id,
68
- host: inputArg.host,
69
- port: inputArg.port || soundtouchDefaultPort,
70
- name: inputArg.name,
71
- manufacturer: normalizedManufacturer(inputArg.manufacturer),
72
- model: inputArg.model,
73
- macAddress: inputArg.macAddress,
74
- metadata: inputArg.metadata,
75
- },
76
- };
77
- }
11
+ const metadata = soundtouchRecord(input.metadata, 'candidate metadata'); soundtouchKeys(metadata, ['type']);
12
+ const endpoint = readSoundtouchEndpoint(input.host, input.id, metadata.type, input.port);
13
+ const candidate: IDiscoveryCandidate = Object.freeze({ source: input.source, integrationDomain: 'soundtouch', id: endpoint.deviceId,
14
+ host: endpoint.host, port: endpoint.port, ...(input.name === undefined ? {} : { name: soundtouchText(input.name, 'candidate name') }),
15
+ metadata: Object.freeze({ type: endpoint.type }) });
16
+ return { matched: true, confidence: 'medium', reason: 'Selected endpoint/deviceID/type; live identity is verified during setup.', candidate, normalizedDeviceId: endpoint.deviceId };
17
+ } catch (errorArg) { return { matched: false, confidence: 'low', reason: errorArg instanceof Error ? errorArg.message : 'Invalid SoundTouch candidate.' }; }
78
18
  }
79
-
80
- export class SoundtouchCandidateValidator implements IDiscoveryValidator {
81
- public id = 'soundtouch-candidate-validator';
82
- public description = 'Validate Bose SoundTouch candidates.';
83
-
84
- public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
85
- void contextArg;
86
- const metadata = candidateArg.metadata || {};
87
- const matched = candidateArg.integrationDomain === soundtouchDomain
88
- || includesSoundtouch(candidateArg.name)
89
- || includesSoundtouch(candidateArg.model)
90
- || includesBose(candidateArg.manufacturer)
91
- || Boolean(metadata.soundtouch)
92
- || normalizeMdnsType(stringMetadata(metadata.mdnsType) || '') === normalizeMdnsType(soundtouchMdnsType);
93
-
94
- return {
95
- matched,
96
- confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
97
- reason: matched ? 'Candidate has Bose SoundTouch metadata.' : 'Candidate is not SoundTouch.',
98
- candidate: matched ? { ...candidateArg, port: candidateArg.port || soundtouchDefaultPort } : undefined,
99
- normalizedDeviceId: candidateArg.id || candidateArg.macAddress,
100
- };
101
- }
19
+ /** Matching only; this subset performs no network discovery. */
20
+ export function createSoundtouchDiscoveryDescriptor(): DiscoveryDescriptor {
21
+ return new DiscoveryDescriptor({ integrationDomain: 'soundtouch', displayName: 'Bose SoundTouch' })
22
+ .addMatcher({ id: 'soundtouch-manual-match', source: 'manual', matches: async (inputArg) => matchSoundtouchCandidate(inputArg) })
23
+ .addMatcher({ id: 'soundtouch-custom-match', source: 'custom', matches: async (inputArg) => matchSoundtouchCandidate(inputArg) })
24
+ .addValidator({ id: 'soundtouch-selected-endpoint', validate: async (candidateArg) => matchSoundtouchCandidate(candidateArg) });
102
25
  }
103
-
104
- export const createSoundtouchDiscoveryDescriptor = (): DiscoveryDescriptor => {
105
- return new DiscoveryDescriptor({ integrationDomain: soundtouchDomain, displayName: 'Bose SoundTouch' })
106
- .addMatcher(new SoundtouchMdnsMatcher())
107
- .addMatcher(new SoundtouchManualMatcher())
108
- .addValidator(new SoundtouchCandidateValidator());
109
- };
110
-
111
- const valueForKey = (recordArg: Record<string, string | undefined> | undefined, keyArg: string): string | undefined => {
112
- if (!recordArg) {
113
- return undefined;
114
- }
115
- const lowerKey = keyArg.toLowerCase();
116
- for (const [key, value] of Object.entries(recordArg)) {
117
- if (key.toLowerCase() === lowerKey) {
118
- return value;
119
- }
120
- }
121
- return undefined;
122
- };
123
-
124
- const normalizeMdnsType = (valueArg: string): string => valueArg.toLowerCase().replace(/\.$/, '');
125
-
126
- const normalizedManufacturer = (valueArg: string | undefined): string => {
127
- if (!valueArg || valueArg.toLowerCase().includes('bose')) {
128
- return 'Bose Corporation';
129
- }
130
- return valueArg;
131
- };
132
-
133
- const includesSoundtouch = (valueArg: string | undefined): boolean => Boolean(valueArg?.toLowerCase().includes('soundtouch'));
134
-
135
- const includesBose = (valueArg: string | undefined): boolean => Boolean(valueArg?.toLowerCase().includes('bose'));
136
-
137
- const stringMetadata = (valueArg: unknown): string | undefined => typeof valueArg === 'string' ? valueArg : undefined;