@smarthome.exchange/integrations 1.0.0 → 2.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 (294) hide show
  1. package/changelog.md +49 -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 +79 -0
  8. package/dist_ts/core/classes.nativeintegrationowner.js +269 -0
  9. package/dist_ts/core/index.d.ts +2 -0
  10. package/dist_ts/core/index.js +3 -1
  11. package/dist_ts/core/types.d.ts +14 -0
  12. package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
  13. package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
  14. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
  15. package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
  16. package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
  17. package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
  18. package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
  19. package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
  20. package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
  21. package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
  22. package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
  23. package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
  24. package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
  25. package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
  26. package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
  27. package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
  28. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
  29. package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
  30. package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
  31. package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
  32. package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
  33. package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
  34. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
  35. package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
  36. package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
  37. package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
  38. package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
  39. package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
  40. package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
  41. package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
  42. package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
  43. package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
  44. package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
  45. package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
  46. package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
  47. package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
  48. package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
  49. package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
  50. package/dist_ts/integrations/daikin/daikin.types.js +1 -1
  51. package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
  52. package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
  53. package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
  54. package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
  55. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
  56. package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
  57. package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
  58. package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
  59. package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
  60. package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
  61. package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
  62. package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
  63. package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
  64. package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
  65. package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
  66. package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
  67. package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
  68. package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
  69. package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
  70. package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
  71. package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
  72. package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
  73. package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
  74. package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
  75. package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
  76. package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
  77. package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
  78. package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
  79. package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
  80. package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
  81. package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
  82. package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
  83. package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
  84. package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
  85. package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
  86. package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
  87. package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
  88. package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
  89. package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
  90. package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
  91. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
  92. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
  93. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
  94. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
  95. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
  96. package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
  97. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
  98. package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
  99. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
  100. package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
  101. package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
  102. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
  103. package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
  104. package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
  105. package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
  106. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
  107. package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
  108. package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
  109. package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
  110. package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
  111. package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
  112. package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
  113. package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
  114. package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
  115. package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
  116. package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
  117. package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
  118. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
  119. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
  120. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
  121. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
  122. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
  123. package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
  124. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
  125. package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
  126. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
  127. package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
  128. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
  129. package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
  130. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
  131. package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
  132. package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
  133. package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
  134. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
  135. package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
  136. package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
  137. package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
  138. package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
  139. package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
  140. package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
  141. package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
  142. package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
  143. package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
  144. package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
  145. package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
  146. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
  147. package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
  148. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
  149. package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
  150. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
  151. package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
  152. package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
  153. package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
  154. package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
  155. package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
  156. package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
  157. package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
  158. package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
  159. package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
  160. package/dist_ts/integrations/wiz/index.d.ts +1 -0
  161. package/dist_ts/integrations/wiz/index.js +2 -1
  162. package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
  163. package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
  164. package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
  165. package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
  166. package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
  167. package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
  168. package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
  169. package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
  170. package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
  171. package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
  172. package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
  173. package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
  174. package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
  175. package/dist_ts/integrations/yamaha/index.d.ts +1 -0
  176. package/dist_ts/integrations/yamaha/index.js +2 -1
  177. package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
  178. package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
  179. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
  180. package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
  181. package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
  182. package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
  183. package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
  184. package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
  185. package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
  186. package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
  187. package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
  188. package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
  189. package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
  190. package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
  191. package/package.json +3 -3
  192. package/readme.md +514 -2
  193. package/ts/00_commitinfo_data.ts +1 -1
  194. package/ts/core/classes.configstore.ts +84 -27
  195. package/ts/core/classes.nativehomeadmission.ts +96 -0
  196. package/ts/core/classes.nativeintegrationowner.ts +297 -0
  197. package/ts/core/index.ts +2 -0
  198. package/ts/core/types.ts +16 -0
  199. package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
  200. package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
  201. package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
  202. package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
  203. package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
  204. package/ts/integrations/braviatv/braviatv.types.ts +53 -173
  205. package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
  206. package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
  207. package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
  208. package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
  209. package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
  210. package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
  211. package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
  212. package/ts/integrations/broadlink/broadlink.types.ts +48 -8
  213. package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
  214. package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
  215. package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
  216. package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
  217. package/ts/integrations/daikin/daikin.discovery.ts +94 -346
  218. package/ts/integrations/daikin/daikin.mapper.ts +83 -166
  219. package/ts/integrations/daikin/daikin.types.ts +27 -48
  220. package/ts/integrations/daikin/daikin.validation.ts +51 -0
  221. package/ts/integrations/daikin/readme.md +83 -0
  222. package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
  223. package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
  224. package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
  225. package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
  226. package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
  227. package/ts/integrations/denonavr/denonavr.types.ts +48 -111
  228. package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
  229. package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
  230. package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
  231. package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
  232. package/ts/integrations/kodi/kodi.discovery.ts +49 -28
  233. package/ts/integrations/kodi/kodi.mapper.ts +52 -10
  234. package/ts/integrations/kodi/kodi.types.ts +33 -58
  235. package/ts/integrations/kodi/kodi.validation.ts +60 -0
  236. package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
  237. package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
  238. package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
  239. package/ts/integrations/modbus/modbus.discovery.ts +38 -106
  240. package/ts/integrations/modbus/modbus.mapper.ts +64 -467
  241. package/ts/integrations/modbus/modbus.types.ts +64 -381
  242. package/ts/integrations/modbus/modbus.validation.ts +119 -0
  243. package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
  244. package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
  245. package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
  246. package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
  247. package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
  248. package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
  249. package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
  250. package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
  251. package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
  252. package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
  253. package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
  254. package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
  255. package/ts/integrations/onkyo/onkyo.types.ts +55 -110
  256. package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
  257. package/ts/integrations/onkyo/readme.md +126 -0
  258. package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
  259. package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
  260. package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
  261. package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
  262. package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
  263. package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
  264. package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
  265. package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
  266. package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
  267. package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
  268. package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
  269. package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
  270. package/ts/integrations/pioneer/pioneer.types.ts +87 -85
  271. package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
  272. package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
  273. package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
  274. package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
  275. package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
  276. package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
  277. package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
  278. package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
  279. package/ts/integrations/wiz/index.ts +1 -0
  280. package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
  281. package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
  282. package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
  283. package/ts/integrations/wiz/wiz.discovery.ts +48 -291
  284. package/ts/integrations/wiz/wiz.mapper.ts +81 -754
  285. package/ts/integrations/wiz/wiz.types.ts +24 -224
  286. package/ts/integrations/wiz/wiz.validation.ts +73 -0
  287. package/ts/integrations/yamaha/index.ts +1 -0
  288. package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
  289. package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
  290. package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
  291. package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
  292. package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
  293. package/ts/integrations/yamaha/yamaha.types.ts +77 -97
  294. package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
@@ -0,0 +1,56 @@
1
+ import type { IPanasonicVieraConfig } from './panasonic_viera.types.js';
2
+
3
+ export function panasonicVieraRecord(valueArg: unknown, keysArg?: readonly string[]): Record<string, unknown> {
4
+ if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg) ||
5
+ ![Object.prototype, null].includes(Object.getPrototypeOf(valueArg))) {
6
+ throw new TypeError('Panasonic Viera input must be a plain data object.');
7
+ }
8
+ const result: Record<string, unknown> = {};
9
+ for (const key of Reflect.ownKeys(valueArg)) {
10
+ const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
11
+ if (typeof key !== 'string' || !descriptor || !('value' in descriptor) || !descriptor.enumerable ||
12
+ keysArg && !keysArg.includes(key)) throw new TypeError('Panasonic Viera input contains unsupported fields or accessors.');
13
+ Object.defineProperty(result, key, { value: descriptor.value, enumerable: true });
14
+ }
15
+ return result;
16
+ }
17
+
18
+ export function panasonicVieraText(valueArg: unknown, nameArg: string, maximumArg = 256): string {
19
+ if (typeof valueArg !== 'string' || !valueArg.trim() || valueArg.length > maximumArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
20
+ throw new TypeError(`Panasonic Viera ${nameArg} must be a bounded non-empty string without control characters.`);
21
+ }
22
+ return valueArg;
23
+ }
24
+
25
+ function optionalInteger(valueArg: unknown, minimumArg: number, maximumArg: number): number | undefined {
26
+ if (valueArg === undefined) return undefined;
27
+ if (typeof valueArg !== 'number' || !Number.isSafeInteger(valueArg) || valueArg < minimumArg || valueArg > maximumArg) {
28
+ throw new TypeError('Panasonic Viera numeric bound is invalid.');
29
+ }
30
+ return valueArg;
31
+ }
32
+
33
+ export function normalizePanasonicVieraConfig(valueArg: unknown): Readonly<IPanasonicVieraConfig> {
34
+ const config = panasonicVieraRecord(valueArg, ['homeId', 'host', 'descriptionUrl', 'expectedUdn', 'expectedModelName', 'allowUnencryptedLan', 'name', 'timeoutMs', 'maxResponseBytes']);
35
+ if (config.allowUnencryptedLan !== true) throw new TypeError('Panasonic Viera requires explicit allowUnencryptedLan: true.');
36
+ // The owning protocol validates the selected private address, exact description origin and UPnP identity.
37
+ return Object.freeze({
38
+ homeId: panasonicVieraText(config.homeId, 'homeId'),
39
+ host: panasonicVieraText(config.host, 'host'),
40
+ descriptionUrl: panasonicVieraText(config.descriptionUrl, 'descriptionUrl', 4096),
41
+ expectedUdn: panasonicVieraText(config.expectedUdn, 'expectedUdn'),
42
+ expectedModelName: panasonicVieraText(config.expectedModelName, 'expectedModelName'),
43
+ allowUnencryptedLan: true,
44
+ name: config.name === undefined ? undefined : panasonicVieraText(config.name, 'name'),
45
+ timeoutMs: optionalInteger(config.timeoutMs, 1, 60_000),
46
+ maxResponseBytes: optionalInteger(config.maxResponseBytes, 256, 1024 * 1024),
47
+ });
48
+ }
49
+
50
+ /** Cleanup failures remain observable after every admitted operation has settled. */
51
+ export async function drainPanasonicVieraCleanup(cleanupsArg: readonly Promise<unknown>[], operationsArg: readonly Promise<unknown>[], messageArg: string): Promise<void> {
52
+ const [cleanups] = await Promise.all([Promise.allSettled(cleanupsArg), Promise.allSettled(operationsArg)]);
53
+ const errors: unknown[] = cleanups.filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected').map((resultArg) => resultArg.reason);
54
+ if (errors.length === 1) throw errors[0];
55
+ if (errors.length > 1) throw new AggregateError(errors, messageArg);
56
+ }
@@ -1,462 +1,166 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import { SimpleLocalClient, SimpleLocalMapper, type ISimpleLocalEntitySnapshot, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
3
- import type { IPioneerConfig, IPioneerSnapshot } from './pioneer.types.js';
4
- import { pioneerDefaultName, pioneerDefaultPort, pioneerMaxSourceNumbers, pioneerMaxVolume, pioneerProfile } from './pioneer.types.js';
5
-
6
- export class PioneerClient extends SimpleLocalClient<IPioneerConfig> {
7
- private liveSnapshot?: IPioneerSnapshot;
8
-
9
- constructor(private readonly configArg: IPioneerConfig) {
10
- super(pioneerProfile, configArg);
11
- }
12
-
13
- public override async getSnapshot(forceRefreshArg = false): Promise<IPioneerSnapshot> {
14
- if (!forceRefreshArg && this.liveSnapshot) {
15
- return SimpleLocalMapper.clone(this.liveSnapshot);
16
- }
17
-
18
- if (this.shouldUseNativeTelnet(forceRefreshArg)) {
19
- try {
20
- this.liveSnapshot = await this.fetchSnapshot();
21
- } catch (errorArg) {
22
- this.liveSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
23
- }
24
- return SimpleLocalMapper.clone(this.liveSnapshot);
25
- }
26
-
27
- this.liveSnapshot = await super.getSnapshot(forceRefreshArg) as IPioneerSnapshot;
28
- return SimpleLocalMapper.clone(this.liveSnapshot);
29
- }
30
-
31
- public override async refresh(): Promise<IServiceCallResult> {
32
- this.liveSnapshot = undefined;
33
- const snapshot = await this.getSnapshot(true);
34
- const success = snapshot.online && !snapshot.error;
35
- return { success, error: success ? undefined : snapshot.error, data: snapshot };
2
+ import type { INativeIntegrationSetupContext } from '../../core/types.js';
3
+ import { NativeHomeAdmission, type INativeAdmissionContext } from '../../core/classes.nativehomeadmission.js';
4
+ import type { IPioneerConfig, IPioneerEffectResult, IPioneerSnapshot, TPioneerControl } from './pioneer.types.js';
5
+ import { normalizePioneerConfig, validatePioneerControl } from './pioneer.validation.js';
6
+
7
+ /** Home admission and caller work belong here; the published foundation owns the wire. */
8
+ export class PioneerClient {
9
+ public readonly config: ReturnType<typeof normalizePioneerConfig>;
10
+ private readonly protocol: plugins.ecobridgeDeviceManager.PioneerProtocol;
11
+ private readonly admission = new NativeHomeAdmission('pioneer');
12
+ private readonly context: INativeAdmissionContext;
13
+ private readonly lifetime = new AbortController();
14
+ private operationTail: Promise<void> = Promise.resolve();
15
+ private pendingOperations = 0;
16
+ private startPromise?: Promise<IPioneerSnapshot>;
17
+ private snapshot?: IPioneerSnapshot;
18
+ private closePromise?: Promise<void>;
19
+ private retryPromise?: Promise<void>;
20
+
21
+ public constructor(config: IPioneerConfig, context: INativeIntegrationSetupContext) {
22
+ this.config = normalizePioneerConfig(config);
23
+ this.context = this.admission.admit(context, this.config.homeId);
24
+ this.protocol = new plugins.ecobridgeDeviceManager.PioneerProtocol({
25
+ address: this.config.host, descriptionUrl: this.config.descriptionUrl, expectedUdn: this.config.expectedUdn,
26
+ expectedModel: this.config.model, allowUnencryptedLan: this.config.allowUnencryptedLan,
27
+ maximumVolumeRaw: this.config.maximumVolumeRaw, port: this.config.port, timeoutMs: this.config.timeoutMs,
28
+ maxPendingOperations: this.config.maxPendingOperations, maxDescriptionBytes: this.config.maxDescriptionBytes,
29
+ maxFrameBytes: this.config.maxFrameBytes,
30
+ });
36
31
  }
37
32
 
38
- public override async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
39
- if (this.configArg.client?.execute || this.configArg.commandExecutor || !this.endpoint()) {
40
- return super.execute(requestArg);
41
- }
42
-
43
- try {
44
- const command = await this.commandForService(requestArg);
45
- const endpoint = this.requireEndpoint();
46
- await sendPioneerCommand(endpoint, command, this.configArg.timeoutMs || 3000);
47
- this.liveSnapshot = undefined;
48
- return { success: true, data: { command } };
49
- } catch (errorArg) {
50
- return { success: false, error: errorMessage(errorArg) };
51
- }
33
+ public isHealthy(): boolean {
34
+ try { this.assertOpen(); return this.snapshot !== undefined; } catch { return false; }
52
35
  }
53
36
 
54
- private shouldUseNativeTelnet(forceRefreshArg: boolean): boolean {
55
- if (!this.endpoint() || this.configArg.client || this.configArg.snapshotProvider) {
56
- return false;
37
+ public start(signal?: AbortSignal): Promise<IPioneerSnapshot> {
38
+ try { this.assertOpen(); this.assertSignal(signal); } catch (error) { return Promise.reject(error); }
39
+ if (!this.startPromise) {
40
+ this.startPromise = this.run(async (operationSignal, timeoutMs) => this.observe(await this.protocol.inspect({ signal: operationSignal, timeoutMs })), signal, false)
41
+ .catch(async (error: unknown) => {
42
+ this.lifetime.abort(error);
43
+ try { await this.protocol.close(); }
44
+ catch (cleanupError) { throw new AggregateError([error, cleanupError], 'Pioneer startup and cleanup failed.'); }
45
+ throw error;
46
+ });
57
47
  }
58
- if (!forceRefreshArg && this.configArg.snapshot) {
59
- return false;
60
- }
61
- if (!forceRefreshArg && (this.configArg.rawData !== undefined || this.configArg.rawText !== undefined || this.configArg.entities?.length)) {
62
- return false;
63
- }
64
- return true;
48
+ return this.startPromise;
65
49
  }
66
50
 
67
- private async fetchSnapshot(): Promise<IPioneerSnapshot> {
68
- const endpoint = this.requireEndpoint();
69
- const connection = await PioneerTelnetConnection.connect(endpoint, this.configArg.timeoutMs || 3000);
70
- try {
71
- const commandTimeoutMs = this.configArg.commandTimeoutMs || 250;
72
- const powerRaw = await connection.request('?P', 'PWR', commandTimeoutMs);
73
- const volumeRaw = await connection.request('?V', 'VOL', commandTimeoutMs);
74
- const muteRaw = await connection.request('?M', 'MUT', commandTimeoutMs);
75
- const sourceNameToNumber = await this.sourceNameToNumber(connection, commandTimeoutMs);
76
- const sourceNumberToName = reverseSourceMap(sourceNameToNumber);
77
- const sourceRaw = await connection.request('?F', 'FN', commandTimeoutMs);
78
- const sourceNumber = sourceRaw?.slice(2);
79
- const source = sourceNumber ? sourceNumberToName[sourceNumber] || sourceNumber : undefined;
80
- const volume = volumeRaw ? numberValue(volumeRaw.slice(3)) : undefined;
81
- const state = pioneerPowerState(powerRaw);
82
- const name = this.configArg.name || pioneerDefaultName;
83
- const entity: ISimpleLocalEntitySnapshot = {
84
- id: 'media_player',
85
- uniqueId: `${pioneerProfile.domain}_${SimpleLocalMapper.slug(this.configArg.uniqueId || this.configArg.deviceId || endpoint.host)}_media_player`,
86
- name,
87
- platform: 'media_player',
88
- state,
89
- available: true,
90
- writable: true,
91
- attributes: cleanAttributes({
92
- powerRaw,
93
- volumeRaw,
94
- volume,
95
- volumeLevel: volume === undefined ? undefined : volume / pioneerMaxVolume,
96
- isVolumeMuted: muteRaw === undefined ? undefined : muteRaw === 'MUT0',
97
- muteRaw,
98
- source,
99
- sourceNumber,
100
- sourceList: Object.keys(sourceNameToNumber).length ? Object.keys(sourceNameToNumber).sort() : undefined,
101
- sources: Object.keys(sourceNameToNumber).length ? sourceNameToNumber : undefined,
102
- }),
103
- };
104
-
105
- return {
106
- device: {
107
- id: this.configArg.uniqueId || this.configArg.deviceId || `${endpoint.host}:${endpoint.port}`,
108
- name,
109
- manufacturer: 'Pioneer',
110
- model: stringValue(this.configArg.model) || 'Pioneer AVR',
111
- serialNumber: this.configArg.macAddress,
112
- host: endpoint.host,
113
- port: endpoint.port,
114
- protocol: 'telnet',
115
- },
116
- entities: [entity],
117
- online: true,
118
- updatedAt: new Date().toISOString(),
119
- source: 'tcp',
120
- rawData: {
121
- powerRaw,
122
- volumeRaw,
123
- muteRaw,
124
- sourceRaw,
125
- sources: sourceNameToNumber,
126
- },
127
- capabilities: {
128
- localRead: true,
129
- localControl: true,
130
- telnet: true,
131
- },
132
- };
133
- } finally {
134
- connection.close();
135
- }
51
+ public getObservedSnapshot(): IPioneerSnapshot {
52
+ this.assertOpen();
53
+ if (!this.snapshot) throw new Error('Pioneer has no admitted observation.');
54
+ return this.snapshot;
136
55
  }
137
56
 
138
- private async sourceNameToNumber(connectionArg: PioneerTelnetConnection, commandTimeoutMsArg: number): Promise<Record<string, string>> {
139
- const configured = recordValue(this.configArg.sources) || recordValue(this.configArg.metadata?.sources);
140
- if (configured) {
141
- return configured;
142
- }
143
-
144
- const sources: Record<string, string> = {};
145
- const limit = Math.max(0, Math.min(pioneerMaxSourceNumbers, this.configArg.sourceScanLimit ?? pioneerMaxSourceNumbers));
146
- for (let index = 0; index < limit; index++) {
147
- const response = await connectionArg.request(`?RGB${String(index).padStart(2, '0')}`, 'RGB', commandTimeoutMsArg);
148
- const source = parseSourceResponse(response);
149
- if (source) {
150
- sources[source.name] = source.number;
151
- }
152
- }
153
- return sources;
57
+ public getSnapshot(signal?: AbortSignal): Promise<IPioneerSnapshot> {
58
+ return this.run(async (operationSignal, timeoutMs) => this.observe(await this.protocol.inspect({ signal: operationSignal, timeoutMs })), signal);
154
59
  }
155
60
 
156
- private async commandForService(requestArg: IServiceCallRequest): Promise<string> {
157
- const data = requestArg.data || {};
158
- if (typeof data.command === 'string' && data.command.trim()) {
159
- return data.command.trim();
160
- }
161
- if (requestArg.service === 'turn_on') {
162
- return 'PO';
163
- }
164
- if (requestArg.service === 'turn_off') {
165
- return 'PF';
166
- }
167
- if (requestArg.service === 'volume_up') {
168
- return 'VU';
169
- }
170
- if (requestArg.service === 'volume_down') {
171
- return 'VD';
172
- }
173
- if (requestArg.service === 'volume_mute' || requestArg.service === 'mute_volume') {
174
- return boolValue(data.mute ?? data.is_volume_muted ?? data.muted ?? true) ? 'MO' : 'MF';
175
- }
176
- if (requestArg.service === 'set_volume_level' || requestArg.service === 'volume_set' || requestArg.service === 'set_level') {
177
- const requested = numberValue(data.volume_level ?? data.volume ?? data.level);
178
- const percentage = numberValue(data.percentage);
179
- const value = percentage !== undefined ? (percentage / 100) * pioneerMaxVolume : requested !== undefined && requested <= 1 ? requested * pioneerMaxVolume : requested;
180
- if (value === undefined) {
181
- throw new Error('Pioneer set_volume_level requires data.volume_level, data.volume, data.level, or data.percentage.');
182
- }
183
- return `${String(Math.max(0, Math.min(pioneerMaxVolume, Math.round(value)))).padStart(3, '0')}VL`;
184
- }
185
- if (requestArg.service === 'select_source') {
186
- const sourceNumber = stringValue(data.sourceNumber) || stringValue(data.source_number) || stringValue(data.input);
187
- if (sourceNumber && /^\d{2}$/.test(sourceNumber)) {
188
- return `${sourceNumber}FN`;
189
- }
190
- const source = stringValue(data.source) || stringValue(data.source_name);
191
- const sourceMap = recordValue(this.configArg.sources) || recordValue(this.configArg.metadata?.sources) || recordValue((await this.getSnapshot()).entities[0]?.attributes?.sources);
192
- const mapped = source ? sourceMap?.[source] : undefined;
193
- if (!mapped) {
194
- throw new Error('Pioneer select_source requires data.sourceNumber or data.source matching configured/discovered sources.');
61
+ public async setControl(input: TPioneerControl, signal?: AbortSignal): Promise<IPioneerEffectResult> {
62
+ const control = validatePioneerControl(input);
63
+ if (control.feature === 'volume_raw' && control.value > this.config.maximumVolumeRaw) throw new TypeError('Pioneer volume exceeds the owner ceiling.');
64
+ return this.run(async (operationSignal, timeoutMs) => {
65
+ const options = { signal: operationSignal, timeoutMs };
66
+ const result = control.feature === 'power' ? await this.protocol.setPower(control.value, options)
67
+ : control.feature === 'muted' ? await this.protocol.setMuted(control.value, options)
68
+ : control.feature === 'volume_raw' ? await this.protocol.setVolumeRaw(control.value, options)
69
+ : await this.protocol.setInput(control.value, options);
70
+ try {
71
+ return Object.freeze({ dispatch: result.dispatch, outcome: result.outcome,
72
+ commandAcknowledged: result.commandAcknowledged, causalityConfirmed: result.causalityConfirmed,
73
+ snapshot: this.observe(result.observation) });
74
+ } catch {
75
+ throw new plugins.ecobridgeDeviceManager.PioneerProtocolError('identity-mismatch', 'Pioneer observation could not be admitted after dispatch.', 'indeterminate');
195
76
  }
196
- return `${mapped}FN`;
197
- }
198
- throw new Error(`Unsupported Pioneer native service: ${requestArg.domain}.${requestArg.service}`);
77
+ }, signal);
199
78
  }
200
79
 
201
- private requireEndpoint(): IPioneerEndpoint {
202
- const endpoint = this.endpoint();
203
- if (!endpoint) {
204
- throw new Error('Pioneer Telnet requires config.host or config.url.');
80
+ public destroy(): Promise<void> {
81
+ if (!this.closePromise) {
82
+ // Publish the memo before protocol abort listeners can reenter this facade.
83
+ this.closePromise = Promise.resolve().then(() => this.drain());
84
+ this.lifetime.abort(new Error('Pioneer client is closed.'));
205
85
  }
206
- return endpoint;
86
+ return this.closePromise;
207
87
  }
208
88
 
209
- private endpoint(): IPioneerEndpoint | undefined {
210
- const rawUrl = stringValue(this.configArg.url) || stringValue(this.configArg.metadata?.url);
211
- if (rawUrl) {
212
- const parsed = parseUrl(rawUrl);
213
- if (parsed) {
214
- return { host: parsed.hostname, port: this.configArg.port || numberValue(parsed.port) || pioneerDefaultPort };
215
- }
216
- }
217
- const host = stringValue(this.configArg.host);
218
- if (!host) {
219
- return undefined;
89
+ /** Retries retained terminal cleanup only; never repeats protocol observations or effects. */
90
+ public retryCleanup(): Promise<void> {
91
+ const closed = this.closePromise;
92
+ if (!closed) return Promise.reject(new Error('Pioneer cleanup retry requires terminal destruction.'));
93
+ if (!this.retryPromise) {
94
+ const retry = Promise.resolve().then(async () => {
95
+ await closed.catch(() => undefined);
96
+ await this.drain();
97
+ });
98
+ this.retryPromise = retry;
99
+ const settled = (): void => { this.retryPromise = undefined; };
100
+ void retry.then(settled, settled);
220
101
  }
221
- const parsedHost = parseUrl(host);
222
- if (parsedHost) {
223
- return { host: parsedHost.hostname, port: this.configArg.port || numberValue(parsedHost.port) || pioneerDefaultPort };
224
- }
225
- const hostPort = splitHostPort(host);
226
- return { host: hostPort.host, port: this.configArg.port || hostPort.port || pioneerDefaultPort };
102
+ return this.retryPromise;
227
103
  }
228
104
 
229
- private createOfflineSnapshot(errorArg: string): IPioneerSnapshot {
230
- return SimpleLocalMapper.toSnapshot({ profile: pioneerProfile, config: this.configArg, online: false, source: 'runtime', error: errorArg }) as IPioneerSnapshot;
105
+ private async drain(): Promise<void> {
106
+ const results = await Promise.allSettled([this.protocol.close(), this.operationTail, this.startPromise]);
107
+ this.snapshot = undefined;
108
+ const closure = results[0];
109
+ if (closure.status === 'rejected') throw closure.reason;
231
110
  }
232
- }
233
-
234
- interface IPioneerEndpoint {
235
- host: string;
236
- port: number;
237
- }
238
-
239
- class PioneerTelnetConnection {
240
- private buffer = '';
241
- private lines: string[] = [];
242
- private waiters: Array<() => void> = [];
243
- private closed = false;
244
- private lastError?: Error;
245
111
 
246
- private constructor(private readonly socket: plugins.net.Socket) {
247
- socket.on('data', (chunkArg) => this.pushData(Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)));
248
- socket.on('close', () => {
249
- this.closed = true;
250
- this.notify();
251
- });
252
- socket.on('end', () => {
253
- this.closed = true;
254
- this.notify();
255
- });
256
- socket.on('error', (errorArg) => {
257
- this.lastError = errorArg;
258
- this.closed = true;
259
- this.notify();
112
+ private observe(state: plugins.ecobridgeDeviceManager.IPioneerState): IPioneerSnapshot {
113
+ this.assertOpen();
114
+ this.snapshot = Object.freeze({
115
+ homeId: this.config.homeId, host: this.config.host, port: this.config.port,
116
+ name: this.config.name ?? state.identity.name, identity: Object.freeze({ ...state.identity }),
117
+ power: state.power, muted: state.muted, volumeRaw: state.volumeRaw, inputId: state.inputId,
118
+ observedInputs: Object.freeze([...this.protocol.observedInputs]), maximumVolumeRaw: this.config.maximumVolumeRaw,
119
+ observedAt: state.observedAt,
260
120
  });
121
+ return this.snapshot;
261
122
  }
262
123
 
263
- public static async connect(endpointArg: IPioneerEndpoint, timeoutMsArg: number): Promise<PioneerTelnetConnection> {
264
- return new Promise<PioneerTelnetConnection>((resolve, reject) => {
265
- const socket = plugins.net.createConnection({ host: endpointArg.host, port: endpointArg.port });
266
- let settled = false;
267
- const finish = (connectionArg?: PioneerTelnetConnection, errorArg?: Error) => {
268
- if (settled) {
269
- return;
270
- }
271
- settled = true;
272
- socket.off('connect', onConnect);
273
- socket.off('error', onError);
274
- socket.off('timeout', onTimeout);
275
- if (errorArg) {
276
- socket.destroy();
277
- reject(errorArg);
278
- return;
279
- }
280
- resolve(connectionArg!);
281
- };
282
- const onConnect = () => finish(new PioneerTelnetConnection(socket));
283
- const onError = (errorArg: Error) => finish(undefined, errorArg);
284
- const onTimeout = () => finish(undefined, new Error(`Pioneer Telnet connection to ${endpointArg.host}:${endpointArg.port} timed out after ${timeoutMsArg}ms.`));
285
- socket.setTimeout(timeoutMsArg);
286
- socket.once('connect', onConnect);
287
- socket.once('error', onError);
288
- socket.once('timeout', onTimeout);
289
- });
124
+ private assertOpen(): void {
125
+ this.lifetime.signal.throwIfAborted();
126
+ if (this.protocol.isClosed) throw new plugins.ecobridgeDeviceManager.PioneerProtocolError('closed', 'Pioneer protocol is terminally closed.');
127
+ this.admission.admit(this.context, this.config.homeId);
290
128
  }
291
129
 
292
- public async request(commandArg: string, expectedPrefixArg: string, timeoutMsArg: number): Promise<string | undefined> {
293
- await this.write(`${commandArg}\r`);
294
- for (let index = 0; index < 3; index++) {
295
- const line = await this.readLine(timeoutMsArg);
296
- if (!line) {
297
- return undefined;
298
- }
299
- if (line.startsWith(expectedPrefixArg)) {
300
- return line;
301
- }
302
- }
303
- return undefined;
130
+ private assertSignal(signal?: AbortSignal): void {
131
+ if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('Pioneer cancellation requires an AbortSignal.');
132
+ signal?.throwIfAborted();
304
133
  }
305
134
 
306
- public async write(dataArg: string): Promise<void> {
307
- if (this.lastError) {
308
- throw this.lastError;
309
- }
310
- await new Promise<void>((resolve, reject) => {
311
- this.socket.write(dataArg, 'ascii', (errorArg) => errorArg ? reject(errorArg) : resolve());
135
+ /** Scope is checked when adapter work runs, before admission to the foundation's FIFO. */
136
+ private run<TResult>(execute: (signal: AbortSignal, timeoutMs: number) => Promise<TResult>, signal?: AbortSignal, started = true): Promise<TResult> {
137
+ try {
138
+ this.assertOpen(); this.assertSignal(signal);
139
+ if (started && !this.snapshot) throw new Error('Pioneer client has not started.');
140
+ if (this.pendingOperations >= this.config.maxPendingOperations) throw new Error('Pioneer operation capacity reached.');
141
+ } catch (error) { return Promise.reject(error); }
142
+ const deadline = performance.now() + this.config.timeoutMs;
143
+ const controller = new AbortController();
144
+ const combined = AbortSignal.any([this.lifetime.signal, controller.signal,
145
+ ...(this.context.abortSignal ? [this.context.abortSignal] : []), ...(signal ? [signal] : [])]);
146
+ const timer = setTimeout(() => controller.abort(new plugins.ecobridgeDeviceManager.PioneerProtocolError('timeout', 'Pioneer adapter deadline expired.')), this.config.timeoutMs);
147
+ this.pendingOperations++;
148
+ let rejectWaiting: () => void = () => {};
149
+ const waitingCancelled = new Promise<never>((_resolve, reject) => {
150
+ rejectWaiting = () => reject(combined.reason);
151
+ combined.addEventListener('abort', rejectWaiting, { once: true });
312
152
  });
313
- }
314
-
315
- public close(): void {
316
- this.closed = true;
317
- this.socket.destroy();
318
- this.notify();
319
- }
320
-
321
- private async readLine(timeoutMsArg: number): Promise<string | undefined> {
322
- if (this.lastError) {
323
- throw this.lastError;
324
- }
325
- if (this.lines.length) {
326
- return this.lines.shift();
327
- }
328
- if (this.closed) {
329
- return undefined;
330
- }
331
- return new Promise<string | undefined>((resolve, reject) => {
332
- let settled = false;
333
- const cleanup = () => {
334
- clearTimeout(timer);
335
- this.waiters = this.waiters.filter((waiterArg) => waiterArg !== waiter);
336
- };
337
- const done = (valueArg?: string, errorArg?: Error) => {
338
- if (settled) {
339
- return;
340
- }
341
- settled = true;
342
- cleanup();
343
- if (errorArg) {
344
- reject(errorArg);
345
- return;
346
- }
347
- resolve(valueArg);
348
- };
349
- const waiter = () => {
350
- if (this.lastError) {
351
- done(undefined, this.lastError);
352
- return;
353
- }
354
- if (this.lines.length) {
355
- done(this.lines.shift());
356
- return;
357
- }
358
- if (this.closed) {
359
- done(undefined);
360
- }
361
- };
362
- const timer = setTimeout(() => done(undefined), timeoutMsArg);
363
- this.waiters.push(waiter);
153
+ const operation = this.operationTail.then(async () => {
154
+ combined.removeEventListener('abort', rejectWaiting);
155
+ combined.throwIfAborted(); this.assertOpen();
156
+ const remaining = Math.floor(deadline - performance.now());
157
+ if (remaining < 1) throw new plugins.ecobridgeDeviceManager.PioneerProtocolError('timeout', 'Pioneer queued operation expired.');
158
+ return execute(combined, remaining);
159
+ }).finally(() => {
160
+ combined.removeEventListener('abort', rejectWaiting); clearTimeout(timer); this.pendingOperations--;
364
161
  });
365
- }
366
-
367
- private pushData(chunkArg: Buffer): void {
368
- this.buffer += chunkArg.toString('ascii');
369
- let match = /\r\n|\r|\n/.exec(this.buffer);
370
- while (match?.index !== undefined) {
371
- const line = this.buffer.slice(0, match.index).trim();
372
- this.buffer = this.buffer.slice(match.index + match[0].length);
373
- if (line) {
374
- this.lines.push(line);
375
- }
376
- match = /\r\n|\r|\n/.exec(this.buffer);
377
- }
378
- this.notify();
379
- }
380
-
381
- private notify(): void {
382
- const waiters = this.waiters.splice(0);
383
- for (const waiter of waiters) {
384
- waiter();
385
- }
162
+ this.operationTail = operation.then(() => {}, () => {});
163
+ if (combined.aborted) rejectWaiting();
164
+ return Promise.race([operation, waitingCancelled]);
386
165
  }
387
166
  }
388
-
389
- const sendPioneerCommand = async (endpointArg: IPioneerEndpoint, commandArg: string, timeoutMsArg: number): Promise<void> => {
390
- const connection = await PioneerTelnetConnection.connect(endpointArg, timeoutMsArg);
391
- try {
392
- await connection.write(`${commandArg}\r`);
393
- await delay(40);
394
- } finally {
395
- connection.close();
396
- }
397
- };
398
-
399
- const parseSourceResponse = (responseArg: string | undefined): { number: string; name: string } | undefined => {
400
- const match = responseArg ? /^RGB(\d{2})\s*(.+)$/.exec(responseArg) : undefined;
401
- const name = match?.[2]?.trim();
402
- return match && name ? { number: match[1], name } : undefined;
403
- };
404
-
405
- const pioneerPowerState = (valueArg: string | undefined): string => {
406
- if (valueArg === 'PWR0') {
407
- return 'on';
408
- }
409
- if (valueArg === 'PWR1' || valueArg === 'PWR2') {
410
- return 'off';
411
- }
412
- return 'unknown';
413
- };
414
-
415
- const reverseSourceMap = (sourcesArg: Record<string, string>): Record<string, string> => Object.fromEntries(Object.entries(sourcesArg).map(([nameArg, numberArg]) => [numberArg, nameArg]));
416
-
417
- const recordValue = (valueArg: unknown): Record<string, string> | undefined => {
418
- if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
419
- return undefined;
420
- }
421
- const entries = Object.entries(valueArg).filter((entryArg): entryArg is [string, string] => typeof entryArg[1] === 'string' && entryArg[1].trim().length > 0);
422
- return entries.length ? Object.fromEntries(entries) : undefined;
423
- };
424
-
425
- const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
426
-
427
- const numberValue = (valueArg: unknown): number | undefined => {
428
- if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
429
- return valueArg;
430
- }
431
- if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
432
- return Number(valueArg);
433
- }
434
- return undefined;
435
- };
436
-
437
- const boolValue = (valueArg: unknown): boolean => valueArg === true || valueArg === 'true' || valueArg === '1' || valueArg === 1;
438
-
439
- const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> | undefined => {
440
- const clean = Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
441
- return Object.keys(clean).length ? clean : undefined;
442
- };
443
-
444
- const parseUrl = (valueArg: string): URL | undefined => {
445
- if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
446
- return undefined;
447
- }
448
- try {
449
- return new URL(valueArg);
450
- } catch {
451
- return undefined;
452
- }
453
- };
454
-
455
- const splitHostPort = (valueArg: string): { host: string; port?: number } => {
456
- const match = /^([^:]+):(\d+)$/.exec(valueArg);
457
- return match ? { host: match[1], port: Number(match[2]) } : { host: valueArg };
458
- };
459
-
460
- const delay = async (msArg: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, msArg));
461
-
462
- const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);