@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
@@ -1,448 +1,159 @@
1
- import type { IDenonavrConfig, IDenonavrReceiverInfo, IDenonavrSnapshot, IDenonavrZoneState, TDenonavrCommand, TDenonavrZone } from './denonavr.types.js';
2
-
3
- const zoneNumbers: Record<TDenonavrZone, number> = { Main: 1, Zone2: 2, Zone3: 3 };
4
- const statusPaths: Record<TDenonavrZone, string> = {
5
- Main: '/goform/formMainZone_MainZoneXmlStatus.xml',
6
- Zone2: '/goform/formZone2_Zone2XmlStatus.xml',
7
- Zone3: '/goform/formZone3_Zone3XmlStatus.xml',
8
- };
9
- const sourcePrefixes: Record<TDenonavrZone, string> = { Main: 'SI', Zone2: 'Z2', Zone3: 'Z3' };
10
- const volumeUpCommands: Record<TDenonavrZone, string> = { Main: 'MVUP', Zone2: 'Z2UP', Zone3: 'Z3UP' };
11
- const volumeDownCommands: Record<TDenonavrZone, string> = { Main: 'MVDOWN', Zone2: 'Z2DOWN', Zone3: 'Z3DOWN' };
12
-
13
- const defaultSourceMap: Record<string, string> = {
14
- 'TV AUDIO': 'TV',
15
- TV: 'TV',
16
- 'Blu-ray': 'BD',
17
- 'BLU-RAY': 'BD',
18
- BD: 'BD',
19
- 'CBL/SAT': 'SAT/CBL',
20
- 'SAT/CBL': 'SAT/CBL',
21
- DVD: 'DVD',
22
- 'Media Player': 'MPLAY',
23
- 'MEDIA PLAYER': 'MPLAY',
24
- MPLAY: 'MPLAY',
25
- GAME: 'GAME',
26
- AUX: 'AUX1',
27
- AUX1: 'AUX1',
28
- CD: 'CD',
29
- PHONO: 'PHONO',
30
- Tuner: 'TUNER',
31
- TUNER: 'TUNER',
32
- NETWORK: 'NET',
33
- NET: 'NET',
34
- Bluetooth: 'BT',
35
- BT: 'BT',
36
- 'iPod/USB': 'USB/IPOD',
37
- USB: 'USB/IPOD',
38
- 'USB/IPOD': 'USB/IPOD',
39
- 'Internet Radio': 'IRADIO',
40
- IRADIO: 'IRADIO',
41
- 'Media Server': 'SERVER',
42
- SERVER: 'SERVER',
43
- Favorites: 'FAVORITES',
44
- FAVORITES: 'FAVORITES',
45
- Spotify: 'SPOTIFY',
46
- SpotifyConnect: 'SPOTIFY',
47
- 'Spotify Connect': 'SPOTIFY',
48
- };
1
+ import * as plugins from '../../plugins.js';
2
+ import type { IIntegrationSetupContext } from '../../core/types.js';
3
+ import { NativeHomeAdmission, type INativeAdmissionContext } from '../../core/classes.nativehomeadmission.js';
4
+ import type { IDenonavrConfig, IDenonavrEffectResult, IDenonavrSnapshot, TDenonavrControl } from './denonavr.types.js';
5
+ import { normalizeDenonavrConfig, validateDenonavrControl } from './denonavr.validation.js';
49
6
 
7
+ /** The published protocol owns wire behavior; this facade owns home admission and caller work. */
50
8
  export class DenonavrClient {
51
- constructor(private readonly config: IDenonavrConfig) {}
52
-
53
- public async getSnapshot(): Promise<IDenonavrSnapshot> {
54
- if (this.config.snapshot) {
55
- return this.config.snapshot;
56
- }
57
- const receiverInfo = await this.getReceiverInfo();
58
- const zones = await this.getZones();
59
- return { receiverInfo, zones, lastUpdated: new Date().toISOString() };
9
+ public readonly config: ReturnType<typeof normalizeDenonavrConfig>;
10
+ private readonly protocol: plugins.ecobridgeDeviceManager.DenonProtocol;
11
+ private readonly lifetime = new AbortController();
12
+ private readonly admission = new NativeHomeAdmission('denonavr');
13
+ private readonly context: INativeAdmissionContext;
14
+ private operationTail: Promise<void> = Promise.resolve();
15
+ private pendingOperations = 0;
16
+ private startPromise?: Promise<IDenonavrSnapshot>;
17
+ private snapshot?: IDenonavrSnapshot;
18
+ private healthy = false;
19
+ private closePromise?: Promise<void>;
20
+ private cleanupPromise?: Promise<void>;
21
+ private retryPromise?: Promise<void>;
22
+ private cleanupFailed = false;
23
+
24
+ public constructor(config: IDenonavrConfig, context: IIntegrationSetupContext) {
25
+ this.config = normalizeDenonavrConfig(config);
26
+ this.context = this.admission.admit(context, this.config.homeId);
27
+ this.protocol = new plugins.ecobridgeDeviceManager.DenonProtocol({
28
+ address: this.config.host, descriptionUrl: this.config.descriptionUrl, expectedUdn: this.config.expectedUdn,
29
+ expectedModel: this.config.model, maximumVolumeDb: this.config.maximumVolumeDb, port: this.config.port,
30
+ timeoutMs: this.config.timeoutMs, maxPendingOperations: this.config.maxPendingOperations, maxDescriptionBytes: this.config.maxDescriptionBytes,
31
+ });
60
32
  }
61
33
 
62
- public async getReceiverInfo(): Promise<IDenonavrReceiverInfo> {
63
- if (this.config.receiverInfo) {
64
- return { ...this.manualReceiverInfo(), ...this.config.receiverInfo };
65
- }
66
- if (!this.config.host) {
67
- return this.manualReceiverInfo();
68
- }
34
+ public isHealthy(): boolean { return !this.lifetime.signal.aborted && this.healthy && this.protocol.isStarted; }
69
35
 
70
- const descriptions = await this.tryDescriptionXml();
71
- if (descriptions) {
72
- return { ...this.manualReceiverInfo(), ...descriptions };
36
+ public start(signal?: AbortSignal): Promise<IDenonavrSnapshot> {
37
+ if (this.lifetime.signal.aborted) return Promise.reject(this.lifetime.signal.reason);
38
+ if (!this.startPromise) {
39
+ this.startPromise = this.run(async (operationSignal, timeoutMs) => this.observe(await this.protocol.start({ signal: operationSignal, timeoutMs })),
40
+ signal, false).catch((error: unknown) => {
41
+ this.lifetime.abort(error);
42
+ throw error;
43
+ });
73
44
  }
74
-
75
- const deviceInfo = await this.tryDeviceInfoXml();
76
- return { ...this.manualReceiverInfo(), ...deviceInfo };
45
+ return this.startPromise;
77
46
  }
78
47
 
79
- public async getZones(): Promise<IDenonavrZoneState[]> {
80
- if (this.config.zones) {
81
- return this.config.zones;
82
- }
83
- if (!this.config.host) {
84
- return [{ zone: 'Main', name: this.config.name || 'Main Zone', power: 'OFF', state: 'off', available: false }];
85
- }
86
-
87
- const zones: TDenonavrZone[] = ['Main'];
88
- if (this.config.zone2) {
89
- zones.push('Zone2');
90
- }
91
- if (this.config.zone3) {
92
- zones.push('Zone3');
93
- }
94
-
95
- return Promise.all(zones.map((zoneArg) => this.getZoneState(zoneArg)));
48
+ public getObservedSnapshot(): IDenonavrSnapshot {
49
+ this.assertOpen();
50
+ if (!this.snapshot) throw new Error('DenonAVR has no admitted observation.');
51
+ return this.snapshot;
96
52
  }
97
53
 
98
- public async execute(requestArg: { command: TDenonavrCommand; zone?: TDenonavrZone; source?: string; volumeLevel?: number; volumeDb?: number; muted?: boolean; path?: string }): Promise<string | undefined> {
99
- const zone = requestArg.zone || 'Main';
100
- if (requestArg.command === 'turn_on') {
101
- return this.command(`/goform/formiPhoneAppPower.xml?${zoneNumbers[zone]}+PowerOn`);
102
- }
103
- if (requestArg.command === 'turn_off') {
104
- return this.command(`/goform/formiPhoneAppPower.xml?${zoneNumbers[zone]}+PowerStandby`);
105
- }
106
- if (requestArg.command === 'volume_up') {
107
- return this.direct(volumeUpCommands[zone]);
108
- }
109
- if (requestArg.command === 'volume_down') {
110
- return this.direct(volumeDownCommands[zone]);
111
- }
112
- if (requestArg.command === 'set_volume') {
113
- const volumeDb = typeof requestArg.volumeDb === 'number' ? requestArg.volumeDb : this.volumeLevelToDb(requestArg.volumeLevel ?? 0);
114
- return this.command(`/goform/formiPhoneAppVolume.xml?${zoneNumbers[zone]}+${volumeDb.toFixed(1)}`);
115
- }
116
- if (requestArg.command === 'mute') {
117
- return this.command(`/goform/formiPhoneAppMute.xml?${zoneNumbers[zone]}+${requestArg.muted ? 'MuteOn' : 'MuteOff'}`);
118
- }
119
- if (requestArg.command === 'select_source') {
120
- if (!requestArg.source) {
121
- throw new Error('Denon AVR select_source requires a source.');
122
- }
123
- return this.direct(`${sourcePrefixes[zone]}${this.toSourceCode(requestArg.source, zone)}`);
124
- }
125
- if (requestArg.command === 'play') {
126
- return this.direct('NS9A');
127
- }
128
- if (requestArg.command === 'pause') {
129
- return this.direct('NS9B');
130
- }
131
- if (requestArg.command === 'stop') {
132
- return this.direct('NS9C');
133
- }
134
- if (requestArg.command === 'play_pause') {
135
- const snapshot = await this.getSnapshot();
136
- const zoneState = snapshot.zones.find((zoneArg) => zoneArg.zone === zone);
137
- return this.direct(zoneState?.state === 'playing' ? 'NS9B' : 'NS9A');
138
- }
139
- if (requestArg.command === 'previous_track') {
140
- await this.netaudioCommand('CurUp');
141
- return undefined;
142
- }
143
- if (requestArg.command === 'next_track') {
144
- await this.netaudioCommand('CurDown');
145
- return undefined;
146
- }
147
- if (requestArg.command === 'get_command') {
148
- if (!requestArg.path) {
149
- throw new Error('Denon AVR get_command requires a path.');
150
- }
151
- return this.command(requestArg.path);
152
- }
153
- throw new Error(`Unsupported Denon AVR command: ${requestArg.command}`);
54
+ public getSnapshot(signal?: AbortSignal): Promise<IDenonavrSnapshot> {
55
+ return this.run(async (operationSignal, timeoutMs) => this.observe(await this.protocol.readState({ signal: operationSignal, timeoutMs })), signal);
154
56
  }
155
57
 
156
- public async destroy(): Promise<void> {}
157
-
158
- private async getZoneState(zoneArg: TDenonavrZone): Promise<IDenonavrZoneState> {
159
- const [statusXml, mainXml] = await Promise.all([
160
- this.fetchText(statusPaths[zoneArg]).catch(() => ''),
161
- zoneArg === 'Main' ? this.fetchText('/goform/formMainZone_MainZoneXml.xml').catch(() => '') : Promise.resolve(''),
162
- ]);
163
- const xml = `${statusXml}\n${mainXml}`;
164
- const source = this.firstContainerValue(xml, ['InputFuncSelect', 'InputFunc']) || undefined;
165
- const volumeDb = this.numberValue(this.firstContainerValue(xml, ['MasterVolume', 'Volume']));
166
- const muted = this.boolOnOff(this.firstContainerValue(xml, ['Mute']));
167
- const media = await this.getMediaInfo(source);
168
- const power = this.firstContainerValue(xml, ['ZonePower', 'Power']) || undefined;
169
- return {
170
- zone: zoneArg,
171
- name: zoneArg === 'Main' ? (this.config.name || this.firstContainerValue(mainXml, ['FriendlyName']) || 'Main Zone') : zoneArg,
172
- power,
173
- state: this.stateFromPower(power, source, media),
174
- volumeDb,
175
- volumeLevel: typeof volumeDb === 'number' ? this.volumeDbToLevel(volumeDb) : undefined,
176
- muted,
177
- source,
178
- sourceList: this.readInputList(mainXml),
179
- sourceMap: this.config.sourceMap,
180
- soundModeRaw: this.firstContainerValue(xml, ['selectSurround', 'SurrMode']) || undefined,
181
- soundMode: this.firstContainerValue(xml, ['selectSurround', 'SurrMode']) || undefined,
182
- ecoMode: this.firstContainerValue(xml, ['ECOMode']) || undefined,
183
- media,
184
- available: Boolean(statusXml || mainXml),
185
- };
186
- }
187
-
188
- private async getMediaInfo(sourceArg: string | undefined) {
189
- const source = sourceArg?.toLowerCase() || '';
190
- if (['internet radio', 'media server', 'network', 'net', 'bluetooth', 'bt', 'spotify', 'spotifyconnect', 'ipod/usb', 'usb/ipod'].includes(source)) {
191
- const xml = await this.fetchText('/goform/formNetAudio_StatusXml.xml').catch(() => '');
192
- const lines = this.readSzLines(xml);
193
- return {
194
- title: lines[1],
195
- artist: lines[2],
196
- album: lines[4],
197
- imageUrl: this.config.host ? `http://${this.config.host}:${this.port()}/img/album%20art_S.png` : undefined,
198
- contentType: 'music',
199
- };
200
- }
201
- if (source === 'tuner') {
202
- const xml = await this.fetchText('/goform/formTuner_TunerXml.xml').catch(() => '');
203
- return {
204
- band: this.firstContainerValue(xml, ['Band']) || undefined,
205
- frequency: this.firstContainerValue(xml, ['Frequency']) || undefined,
206
- contentType: 'channel',
207
- };
208
- }
209
- if (source === 'hd radio' || source === 'hdradio') {
210
- const xml = await this.fetchText('/goform/formTuner_HdXml.xml').catch(() => '');
211
- return {
212
- title: this.firstContainerValue(xml, ['Title']) || undefined,
213
- artist: this.firstContainerValue(xml, ['Artist']) || undefined,
214
- album: this.firstContainerValue(xml, ['Album']) || undefined,
215
- band: this.firstContainerValue(xml, ['Band']) || undefined,
216
- frequency: this.firstContainerValue(xml, ['Frequency']) || undefined,
217
- station: this.firstContainerValue(xml, ['StationNameSh']) || undefined,
218
- contentType: 'music',
219
- };
220
- }
221
- return undefined;
222
- }
223
-
224
- private async tryDescriptionXml(): Promise<IDenonavrReceiverInfo | undefined> {
225
- const attempts = [
226
- { port: this.config.descriptionPort || this.config.port || 8080, path: '/description.xml' },
227
- { port: 60006, path: '/upnp/desc/aios_device/aios_device.xml' },
228
- { port: 8080, path: '/description.xml' },
229
- { port: 80, path: '/description.xml' },
230
- ];
231
- const seen = new Set<string>();
232
- for (const attempt of attempts) {
233
- const key = `${attempt.port}${attempt.path}`;
234
- if (seen.has(key)) {
235
- continue;
236
- }
237
- seen.add(key);
238
- const xml = await this.fetchText(attempt.path, attempt.port).catch(() => '');
239
- if (!xml) {
240
- continue;
58
+ public setControl(value: TDenonavrControl, signal?: AbortSignal): Promise<IDenonavrEffectResult> {
59
+ const control = validateDenonavrControl(value);
60
+ if (control.feature === 'volume_db' && control.value > this.config.maximumVolumeDb) throw new TypeError('DenonAVR volume exceeds the owner ceiling.');
61
+ return this.run(async (operationSignal, timeoutMs) => {
62
+ const options = { signal: operationSignal, timeoutMs };
63
+ const result = control.feature === 'power' ? await this.protocol.setPower(control.value, options)
64
+ : control.feature === 'muted' ? await this.protocol.setMute(control.value, options)
65
+ : control.feature === 'volume_db' ? await this.protocol.setVolumeDb(control.value, options)
66
+ : await this.protocol.setInput(control.value, options);
67
+ try {
68
+ const snapshot = this.observe(result.observation);
69
+ return Object.freeze({ dispatch: result.dispatch, outcome: result.outcome, snapshot });
70
+ } catch {
71
+ throw new plugins.ecobridgeDeviceManager.DenonProtocolError('DenonAVR observation could not be admitted after dispatch.', 'identity-mismatch', 'indeterminate');
241
72
  }
242
- const manufacturer = this.readXmlTag(xml, 'manufacturer');
243
- const modelName = this.readXmlTag(xml, 'modelName');
244
- const serialNumber = this.readXmlTag(xml, 'serialNumber');
245
- if (manufacturer || modelName || serialNumber) {
246
- return {
247
- host: this.config.host,
248
- port: this.port(),
249
- friendlyName: this.readXmlTag(xml, 'friendlyName'),
250
- name: this.readXmlTag(xml, 'friendlyName'),
251
- manufacturer,
252
- modelName,
253
- modelNumber: this.readXmlTag(xml, 'modelNumber'),
254
- serialNumber,
255
- presentationUrl: this.readXmlTag(xml, 'presentationURL'),
256
- };
257
- }
258
- }
259
- return undefined;
73
+ }, signal);
260
74
  }
261
75
 
262
- private async tryDeviceInfoXml(): Promise<IDenonavrReceiverInfo | undefined> {
263
- const xml = await this.fetchText('/goform/Deviceinfo.xml').catch(() => '');
264
- if (!xml) {
265
- return undefined;
76
+ public destroy(): Promise<void> {
77
+ if (!this.closePromise) {
78
+ this.healthy = false;
79
+ this.closePromise = Promise.resolve().then(() => this.drainCleanup());
80
+ this.cleanupPromise = this.closePromise;
81
+ this.lifetime.abort(new Error('DenonAVR client is closed.'));
266
82
  }
267
- return {
268
- host: this.config.host,
269
- port: this.port(),
270
- manufacturer: this.readXmlTag(xml, 'Manufacturer') || this.config.manufacturer,
271
- modelName: this.readXmlTag(xml, 'ModelName') || this.config.model,
272
- serialNumber: this.readXmlTag(xml, 'SerialNumber') || this.config.serialNumber,
273
- receiverType: this.readXmlTag(xml, 'CommApiVers') ? 'avr-x' : this.config.receiverType,
274
- };
83
+ return this.closePromise;
275
84
  }
276
85
 
277
- private manualReceiverInfo(): IDenonavrReceiverInfo {
278
- return {
279
- host: this.config.host,
280
- port: this.config.port || 80,
281
- name: this.config.name,
282
- friendlyName: this.config.name,
283
- manufacturer: this.config.manufacturer || 'Denon',
284
- modelName: this.config.model,
285
- serialNumber: this.config.serialNumber,
286
- receiverType: this.config.receiverType,
287
- receiverPort: this.config.port || 80,
288
- };
289
- }
290
-
291
- private async command(pathArg: string): Promise<string> {
292
- return this.fetchText(pathArg.startsWith('/') ? pathArg : `/${pathArg}`);
86
+ /** Explicit cleanup retry never starts, reads or dispatches receiver work. */
87
+ public retryCleanup(): Promise<void> {
88
+ if (!this.closePromise) return this.destroy();
89
+ if (!this.cleanupFailed) return this.cleanupPromise ?? this.closePromise;
90
+ if (!this.retryPromise) {
91
+ this.retryPromise = Promise.resolve().then(() => this.drainCleanup());
92
+ this.cleanupPromise = this.retryPromise;
93
+ void this.retryPromise.then(() => { this.retryPromise = undefined; }, () => { this.retryPromise = undefined; });
94
+ }
95
+ return this.retryPromise;
293
96
  }
294
97
 
295
- private async direct(commandArg: string): Promise<string> {
296
- return this.command(`/goform/formiPhoneAppDirect.xml?${encodeURIComponent(commandArg).replace(/%2F/g, '/').replace(/%20/g, '%20')}`);
98
+ private async drainCleanup(): Promise<void> {
99
+ const results = await Promise.allSettled([Promise.resolve().then(() => this.protocol.close()), this.operationTail, this.startPromise]);
100
+ this.snapshot = undefined;
101
+ const closure = results[0];
102
+ this.cleanupFailed = closure.status === 'rejected';
103
+ if (closure.status === 'rejected') throw closure.reason;
297
104
  }
298
105
 
299
- private async netaudioCommand(commandArg: 'CurUp' | 'CurDown'): Promise<void> {
300
- if (!this.config.host) {
301
- throw new Error('Denon AVR host is required for local HTTP commands.');
302
- }
303
- const body = new URLSearchParams({
304
- cmd0: `PutNetAudioCommand/${commandArg}`,
305
- cmd1: 'aspMainZone_WebUpdateStatus/',
306
- ZoneName: 'MAIN ZONE',
106
+ private observe(state: plugins.ecobridgeDeviceManager.IDenonState): IDenonavrSnapshot {
107
+ this.assertOpen();
108
+ this.snapshot = Object.freeze({
109
+ homeId: this.config.homeId, host: this.config.host, port: this.config.port,
110
+ name: this.config.name ?? state.identity.name, identity: Object.freeze({ ...state.identity }),
111
+ power: state.power, muted: state.muted, volumeDb: state.volumeDb, input: state.input,
112
+ observedInputs: Object.freeze([...this.protocol.observedInputs]), maximumVolumeDb: this.config.maximumVolumeDb, observedAt: state.observedAt,
307
113
  });
308
- const response = await globalThis.fetch(`${this.baseUrl()}/NetAudio/index.put.asp`, {
309
- method: 'POST',
310
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
311
- body,
114
+ this.healthy = true;
115
+ return this.snapshot;
116
+ }
117
+
118
+ private assertOpen(): void {
119
+ this.lifetime.signal.throwIfAborted();
120
+ this.admission.admit(this.context, this.config.homeId);
121
+ }
122
+
123
+ /** Adapter FIFO validates home scope when work actually runs; one deadline includes its queue. */
124
+ private run<TResult>(execute: (signal: AbortSignal, timeoutMs: number) => Promise<TResult>, signal?: AbortSignal, started = true): Promise<TResult> {
125
+ try {
126
+ this.assertOpen();
127
+ if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('DenonAVR cancellation requires an AbortSignal.');
128
+ signal?.throwIfAborted();
129
+ if (started && !this.protocol.isStarted) throw new Error('DenonAVR client has not started.');
130
+ if (this.pendingOperations >= this.config.maxPendingOperations) throw new Error('DenonAVR operation capacity reached.');
131
+ } catch (error) { return Promise.reject(error); }
132
+ const deadline = performance.now() + this.config.timeoutMs;
133
+ const controller = new AbortController();
134
+ const combined = AbortSignal.any([this.lifetime.signal, controller.signal,
135
+ ...(this.context.abortSignal ? [this.context.abortSignal] : []), ...(signal ? [signal] : [])]);
136
+ const timer = setTimeout(() => controller.abort(new plugins.ecobridgeDeviceManager.DenonProtocolError('DenonAVR operation deadline expired.', 'timeout')), this.config.timeoutMs);
137
+ this.pendingOperations++;
138
+ let rejectWaiting: () => void = () => {};
139
+ const waitingCancelled = new Promise<never>((_resolve, reject) => {
140
+ rejectWaiting = () => reject(combined.reason);
141
+ combined.addEventListener('abort', rejectWaiting, { once: true });
312
142
  });
313
- if (!response.ok) {
314
- throw new Error(`Denon AVR NetAudio command failed with HTTP ${response.status}: ${await response.text()}`);
315
- }
316
- }
317
-
318
- private async fetchText(pathArg: string, portArg = this.port()): Promise<string> {
319
- if (!this.config.host) {
320
- throw new Error('Denon AVR host is required when snapshot data is not provided.');
321
- }
322
- const response = await globalThis.fetch(`${this.baseUrl(portArg)}${pathArg}`);
323
- const text = await response.text();
324
- if (!response.ok) {
325
- throw new Error(`Denon AVR request ${pathArg} failed with HTTP ${response.status}: ${text}`);
326
- }
327
- return text;
328
- }
329
-
330
- private stateFromPower(powerArg: string | undefined, sourceArg: string | undefined, mediaArg: unknown): string {
331
- if (!powerArg || powerArg === 'OFF' || powerArg === 'STANDBY') {
332
- return 'off';
333
- }
334
- if (mediaArg && sourceArg) {
335
- return 'playing';
336
- }
337
- return 'on';
338
- }
339
-
340
- private toSourceCode(sourceArg: string, zoneArg: TDenonavrZone): string {
341
- const zone = this.config.zones?.find((itemArg) => itemArg.zone === zoneArg);
342
- const sourceMap = { ...defaultSourceMap, ...this.config.sourceMap, ...zone?.sourceMap };
343
- return sourceMap[sourceArg] || sourceMap[sourceArg.toUpperCase()] || sourceArg.toUpperCase().replace(/\s+/g, '');
344
- }
345
-
346
- private volumeLevelToDb(valueArg: number): number {
347
- return Math.max(-80, Math.min(18, valueArg * 100 - 80));
348
- }
349
-
350
- private volumeDbToLevel(valueArg: number): number {
351
- return Math.max(0, Math.min(1, (valueArg + 80) / 100));
352
- }
353
-
354
- private numberValue(valueArg: string | undefined): number | undefined {
355
- if (!valueArg || valueArg === '--') {
356
- return undefined;
357
- }
358
- const numberValue = Number(valueArg);
359
- return Number.isFinite(numberValue) ? numberValue : undefined;
360
- }
361
-
362
- private boolOnOff(valueArg: string | undefined): boolean | undefined {
363
- if (!valueArg) {
364
- return undefined;
365
- }
366
- const value = valueArg.toLowerCase();
367
- if (value === 'on') {
368
- return true;
369
- }
370
- if (value === 'off') {
371
- return false;
372
- }
373
- return undefined;
374
- }
375
-
376
- private readInputList(xmlArg: string): string[] | undefined {
377
- const container = this.readContainer(xmlArg, 'InputFuncList');
378
- if (!container) {
379
- return undefined;
380
- }
381
- const values = this.readAllTags(container, 'value');
382
- return values.length ? values : undefined;
383
- }
384
-
385
- private readSzLines(xmlArg: string): Record<number, string> {
386
- const lines: Record<number, string> = {};
387
- const container = this.readContainer(xmlArg, 'szLine') || '';
388
- const values = this.readAllTags(container, 'value');
389
- values.forEach((valueArg, indexArg) => {
390
- lines[indexArg] = valueArg;
143
+ const operation = this.operationTail.then(async () => {
144
+ combined.removeEventListener('abort', rejectWaiting);
145
+ combined.throwIfAborted();
146
+ this.assertOpen();
147
+ const remaining = Math.floor(deadline - performance.now());
148
+ if (remaining < 1) throw new plugins.ecobridgeDeviceManager.DenonProtocolError('DenonAVR queued operation expired.', 'timeout');
149
+ return execute(combined, remaining);
150
+ }).catch((error: unknown) => { this.healthy = false; throw error; }).finally(() => {
151
+ combined.removeEventListener('abort', rejectWaiting);
152
+ clearTimeout(timer);
153
+ this.pendingOperations--;
391
154
  });
392
- return lines;
393
- }
394
-
395
- private firstContainerValue(xmlArg: string, tagsArg: string[]): string | undefined {
396
- for (const tag of tagsArg) {
397
- const container = this.readContainer(xmlArg, tag);
398
- if (container) {
399
- return this.readXmlTag(container, 'value') || this.stripTags(container).trim() || undefined;
400
- }
401
- const value = this.readXmlTag(xmlArg, tag);
402
- if (value) {
403
- return value;
404
- }
405
- }
406
- return undefined;
407
- }
408
-
409
- private readContainer(xmlArg: string, tagArg: string): string | undefined {
410
- const escapedTag = tagArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
411
- const regex = new RegExp(`<(?:[A-Za-z0-9_]+:)?${escapedTag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/(?:[A-Za-z0-9_]+:)?${escapedTag}>`, 'i');
412
- return regex.exec(xmlArg)?.[1]?.trim();
413
- }
414
-
415
- private readXmlTag(xmlArg: string, tagArg: string): string | undefined {
416
- const escapedTag = tagArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
417
- const regex = new RegExp(`<(?:[A-Za-z0-9_]+:)?${escapedTag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/(?:[A-Za-z0-9_]+:)?${escapedTag}>`, 'i');
418
- const match = regex.exec(xmlArg);
419
- return match?.[1] ? this.unescapeXml(this.stripTags(match[1]).trim()) : undefined;
420
- }
421
-
422
- private readAllTags(xmlArg: string, tagArg: string): string[] {
423
- const escapedTag = tagArg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
424
- const regex = new RegExp(`<(?:[A-Za-z0-9_]+:)?${escapedTag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/(?:[A-Za-z0-9_]+:)?${escapedTag}>`, 'gi');
425
- const values: string[] = [];
426
- let match: RegExpExecArray | null;
427
- while ((match = regex.exec(xmlArg))) {
428
- values.push(this.unescapeXml(this.stripTags(match[1]).trim()));
429
- }
430
- return values.filter(Boolean);
431
- }
432
-
433
- private stripTags(valueArg: string): string {
434
- return valueArg.replace(/<[^>]+>/g, '');
435
- }
436
-
437
- private port(): number {
438
- return this.config.port || 80;
439
- }
440
-
441
- private baseUrl(portArg = this.port()): string {
442
- return `http://${this.config.host}:${portArg}`;
443
- }
444
-
445
- private unescapeXml(valueArg: string): string {
446
- return valueArg.replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&gt;/g, '>').replace(/&lt;/g, '<').replace(/&amp;/g, '&');
155
+ this.operationTail = operation.then(() => {}, () => {});
156
+ if (combined.aborted) rejectWaiting();
157
+ return Promise.race([operation, waitingCancelled]);
447
158
  }
448
159
  }