@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,462 +1,153 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import { SimpleLocalClient, SimpleLocalMapper, type IServiceCallRequest, type IServiceCallResult } from '../../core/index.js';
3
- import type { IOnkyoConfig, IOnkyoSnapshot } from './onkyo.types.js';
4
- import { onkyoDefaultName, onkyoDefaultPort, onkyoDefaultTimeoutMs, onkyoProfile } from './onkyo.types.js';
5
-
6
- interface IOnkyoReceiverState {
7
- power: 'on' | 'off' | 'unknown';
8
- volumeRaw?: number;
9
- volumeLevel?: number;
10
- muted?: boolean;
11
- sourceCode?: string;
12
- source?: string;
13
- listeningModeCode?: string;
14
- listeningMode?: string;
15
- }
16
-
17
- const eiscpHeaderSize = 16;
18
- const onkyoSourceCodes: Record<string, string> = {
19
- '00': 'VCR/DVR',
20
- '01': 'CBL/SAT',
21
- '02': 'Game',
22
- '03': 'Aux',
23
- '04': 'Game 2',
24
- '05': 'PC',
25
- '10': 'BD/DVD',
26
- '11': 'Stream Box',
27
- '12': 'TV',
28
- '22': 'Phono',
29
- '23': 'CD',
30
- '24': 'FM',
31
- '25': 'AM',
32
- '26': 'Tuner',
33
- '27': 'Music Server',
34
- '28': 'Internet Radio',
35
- '29': 'USB',
36
- '2B': 'NET',
37
- '2E': 'Bluetooth',
38
- '2F': 'USB DAC',
39
- '40': 'Universal Port',
40
- '44': 'Optical',
41
- '45': 'Coaxial',
42
- };
43
-
44
- const onkyoListeningModes: Record<string, string> = {
45
- '00': 'Stereo',
46
- '01': 'Direct',
47
- '02': 'Surround',
48
- '0C': 'All Channel Stereo',
49
- '0F': 'Mono',
50
- '11': 'Pure Audio',
51
- '40': 'Straight Decode',
52
- FF: 'Auto Surround',
53
- };
54
-
55
- const hdmiOutputCodes: Record<string, string> = {
56
- no: '00',
57
- analog: '00',
58
- yes: '01',
59
- out: '01',
60
- main: '01',
61
- 'out-sub': '02',
62
- sub: '03',
63
- hdbaset: '02',
64
- both: '03',
65
- up: 'UP',
66
- };
67
-
68
- export class OnkyoClient extends SimpleLocalClient<IOnkyoConfig> {
69
- private currentOnkyoSnapshot?: IOnkyoSnapshot;
70
-
71
- constructor(private readonly configArg: IOnkyoConfig) {
72
- super(onkyoProfile, configArg);
73
- }
74
-
75
- public async getSnapshot(forceRefreshArg = false): Promise<IOnkyoSnapshot> {
76
- if (!forceRefreshArg && this.currentOnkyoSnapshot) {
77
- return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
78
- }
79
-
80
- if (!forceRefreshArg && this.configArg.snapshot) {
81
- this.currentOnkyoSnapshot = await super.getSnapshot(false) as IOnkyoSnapshot;
82
- return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
83
- }
84
-
85
- if (this.configArg.client || this.configArg.snapshotProvider) {
86
- this.currentOnkyoSnapshot = await super.getSnapshot(forceRefreshArg) as IOnkyoSnapshot;
87
- return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
88
- }
89
-
90
- if (this.configArg.host && this.configArg.transport !== 'snapshot') {
91
- try {
92
- this.currentOnkyoSnapshot = await this.readEiscpSnapshot();
93
- } catch (errorArg) {
94
- this.currentOnkyoSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
95
- }
96
- return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
97
- }
98
-
99
- this.currentOnkyoSnapshot = await super.getSnapshot(forceRefreshArg) as IOnkyoSnapshot;
100
- return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
101
- }
102
-
103
- public async refresh(): Promise<IServiceCallResult> {
104
- this.currentOnkyoSnapshot = undefined;
105
- const snapshot = await this.getSnapshot(true);
106
- const success = snapshot.online && !snapshot.error;
107
- return { success, error: success ? undefined : snapshot.error, data: snapshot };
108
- }
109
-
110
- public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
111
- if (!this.configArg.host || this.configArg.transport === 'snapshot') {
112
- return super.execute(requestArg);
113
- }
114
-
115
- try {
116
- const data = await this.executeEiscpCommand(requestArg);
117
- this.currentOnkyoSnapshot = undefined;
118
- return { success: true, data };
119
- } catch (errorArg) {
120
- return { success: false, error: errorMessage(errorArg) };
121
- }
122
- }
123
-
124
- private async readEiscpSnapshot(): Promise<IOnkyoSnapshot> {
125
- const state = await this.queryReceiverState();
126
- const name = this.configArg.name || stringConfig(this.configArg, 'modelName') || onkyoDefaultName;
127
- const uniqueBase = this.configArg.uniqueId || this.configArg.deviceId || this.configArg.macAddress || `${this.host()}:${this.port()}`;
128
- const rawData = {
129
- state,
130
- sourceList: sourceList(this.configArg),
131
- volumeResolution: this.volumeResolution(),
132
- fetchedAt: new Date().toISOString(),
133
- };
134
-
135
- return {
136
- device: {
137
- id: uniqueBase,
138
- name,
139
- manufacturer: 'Onkyo / Pioneer',
140
- model: stringConfig(this.configArg, 'modelName') || stringConfig(this.configArg, 'model') || 'eISCP receiver',
141
- serialNumber: this.configArg.macAddress || this.configArg.deviceId,
142
- host: this.host(),
143
- port: this.port(),
144
- protocol: 'tcp',
145
- configurationUrl: `tcp://${this.host()}:${this.port()}`,
146
- },
147
- entities: [{
148
- id: 'media_player',
149
- uniqueId: `${onkyoProfile.domain}_${SimpleLocalMapper.slug(uniqueBase)}_media_player`,
150
- name,
151
- platform: 'media_player',
152
- state: state.power,
153
- available: state.power !== 'unknown',
154
- writable: true,
155
- attributes: {
156
- source: state.source,
157
- source_code: state.sourceCode,
158
- source_list: sourceList(this.configArg),
159
- volume_level: state.volumeLevel,
160
- volume_raw: state.volumeRaw,
161
- is_volume_muted: state.muted,
162
- sound_mode: state.listeningMode,
163
- sound_mode_code: state.listeningModeCode,
164
- },
165
- }],
166
- online: true,
167
- updatedAt: new Date().toISOString(),
168
- source: 'tcp',
169
- rawData,
170
- capabilities: {
171
- localRead: true,
172
- localControl: true,
173
- liveControlRequiresExecutor: false,
174
- },
175
- };
176
- }
177
-
178
- private async queryReceiverState(): Promise<IOnkyoReceiverState> {
179
- const powerValue = await this.queryValue('PWR');
180
- if (!powerValue) {
181
- throw new Error('Onkyo eISCP power query returned no usable PWR status.');
182
- }
183
- const [volumeValue, muteValue, sourceValue, listeningModeValue] = await Promise.all([
184
- optional(() => this.queryValue('MVL')),
185
- optional(() => this.queryValue('AMT')),
186
- optional(() => this.queryValue('SLI')),
187
- optional(() => this.queryValue('LMD')),
188
- ]);
189
- const volumeRaw = volumeValue ? Number.parseInt(volumeValue, 16) : undefined;
190
- const sourceCode = sourceValue?.toUpperCase();
191
- const listeningModeCode = listeningModeValue?.toUpperCase();
192
-
193
- return {
194
- power: powerValue === '01' ? 'on' : powerValue === '00' ? 'off' : 'unknown',
195
- volumeRaw: Number.isFinite(volumeRaw) ? volumeRaw : undefined,
196
- volumeLevel: Number.isFinite(volumeRaw) ? clamp(Number(((volumeRaw as number) / (this.volumeResolution() * (this.maxVolume() / 100))).toFixed(3)), 0, 1) : undefined,
197
- muted: muteValue === undefined ? undefined : muteValue === '01',
198
- sourceCode,
199
- source: sourceCode ? sourceName(sourceCode, this.configArg) : undefined,
200
- listeningModeCode,
201
- listeningMode: listeningModeCode ? onkyoListeningModes[listeningModeCode] || listeningModeCode : undefined,
202
- };
203
- }
204
-
205
- private async executeEiscpCommand(requestArg: IServiceCallRequest): Promise<unknown> {
206
- const service = requestArg.service;
207
- if (service === 'turn_on') {
208
- return { response: await this.sendCommand('PWR01') };
209
- }
210
- if (service === 'turn_off') {
211
- return { response: await this.sendCommand('PWR00') };
212
- }
213
- if (service === 'toggle') {
214
- const power = await this.queryValue('PWR');
215
- return { response: await this.sendCommand(power === '01' ? 'PWR00' : 'PWR01') };
216
- }
217
- if (service === 'volume_up') {
218
- return { response: await this.sendCommand('MVLUP') };
219
- }
220
- if (service === 'volume_down') {
221
- return { response: await this.sendCommand('MVLDOWN') };
222
- }
223
- if (service === 'volume_set' || service === 'set_volume' || service === 'set_volume_level') {
224
- const level = numberData(requestArg, 'volume_level') ?? numberData(requestArg, 'volumeLevel') ?? numberData(requestArg, 'level');
225
- if (level === undefined) {
226
- throw new Error('Onkyo volume_set requires data.volume_level.');
227
- }
228
- const raw = clamp(Math.round(level * (this.maxVolume() / 100) * this.volumeResolution()), 0, this.volumeResolution());
229
- return { response: await this.sendCommand(`MVL${hex(raw)}`), volumeRaw: raw };
230
- }
231
- if (service === 'volume_mute' || service === 'mute') {
232
- const muted = booleanData(requestArg, 'is_volume_muted') ?? booleanData(requestArg, 'mute') ?? booleanData(requestArg, 'muted');
233
- if (muted === undefined) {
234
- throw new Error('Onkyo volume_mute requires data.is_volume_muted or data.mute.');
235
- }
236
- return { response: await this.sendCommand(`AMT${muted ? '01' : '00'}`) };
237
- }
238
- if (service === 'media_play') {
239
- return { response: await this.sendCommand('NTCPLAY') };
240
- }
241
- if (service === 'media_pause') {
242
- return { response: await this.sendCommand('NTCPAUSE') };
243
- }
244
- if (service === 'media_stop') {
245
- return { response: await this.sendCommand('NTCSTOP') };
246
- }
247
- if (service === 'select_source' || service === 'set_source') {
248
- const source = stringData(requestArg, 'source');
249
- if (!source) {
250
- throw new Error('Onkyo select_source requires data.source.');
251
- }
252
- const code = sourceCode(source, this.configArg);
253
- if (!code) {
254
- throw new Error(`Unsupported Onkyo input source: ${source}.`);
255
- }
256
- return { response: await this.sendCommand(`SLI${code}`), source, sourceCode: code };
257
- }
258
- if (service === 'onkyo_select_hdmi_output' || service === 'select_hdmi_output') {
259
- const output = stringData(requestArg, 'hdmi_output') || stringData(requestArg, 'output');
260
- const code = output ? hdmiOutputCodes[normalizeKey(output)] : undefined;
261
- if (!code) {
262
- throw new Error('Onkyo HDMI output selection requires data.hdmi_output with a supported value.');
263
- }
264
- return { response: await this.sendCommand(`HDO${code}`), hdmiOutput: output };
265
- }
266
- throw new Error(`Unsupported Onkyo eISCP service: ${requestArg.domain}.${service}`);
267
- }
268
-
269
- private async queryValue(prefixArg: string): Promise<string | undefined> {
270
- const response = await this.sendCommand(`${prefixArg}QSTN`, prefixArg);
271
- return response.startsWith(prefixArg) ? response.slice(prefixArg.length) : undefined;
272
- }
273
-
274
- private async sendCommand(commandArg: string, responsePrefixArg = commandArg.slice(0, 3)): Promise<string> {
275
- const frame = buildEiscpFrame(commandArg);
276
- const host = this.host();
277
- const port = this.port();
278
- const timeoutMs = this.timeoutMs();
279
-
280
- return new Promise<string>((resolve, reject) => {
281
- const socket = plugins.net.createConnection({ host, port });
282
- let buffer = Buffer.alloc(0);
283
- let settled = false;
284
-
285
- const finish = (errorArg?: Error, responseArg?: string) => {
286
- if (settled) {
287
- return;
288
- }
289
- settled = true;
290
- socket.removeAllListeners();
291
- socket.destroy();
292
- if (errorArg) {
293
- reject(errorArg);
294
- return;
295
- }
296
- resolve(responseArg || '');
297
- };
298
-
299
- socket.setTimeout(timeoutMs, () => finish(new Error(`Onkyo eISCP request to ${host}:${port} timed out after ${timeoutMs}ms.`)));
300
- socket.on('connect', () => socket.write(frame));
301
- socket.on('data', (chunkArg) => {
302
- buffer = Buffer.concat([buffer, Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)]);
303
- const parsed = parseEiscpResponses(buffer);
304
- buffer = Buffer.from(parsed.remaining);
305
- const response = parsed.messages.find((messageArg) => messageArg.startsWith(responsePrefixArg));
306
- if (response) {
307
- finish(undefined, response);
308
- }
309
- });
310
- socket.on('end', () => finish(new Error(`Onkyo eISCP connection to ${host}:${port} closed before a response was received.`)));
311
- socket.on('close', () => finish(new Error(`Onkyo eISCP connection to ${host}:${port} closed before a response was received.`)));
312
- socket.on('error', (errorArg) => finish(errorArg));
2
+ import { NativeHomeAdmission } from '../../core/classes.nativehomeadmission.js';
3
+ import type { INativeIntegrationSetupContext } from '../../core/types.js';
4
+ import { OnkyoMapper } from './onkyo.mapper.js';
5
+ import type { IOnkyoConfig, IOnkyoSnapshot, TOnkyoClientEvent } from './onkyo.types.js';
6
+ import { normalizeOnkyoConfig, onkyoBoolean, onkyoInputId, onkyoInteger } from './onkyo.validation.js';
7
+
8
+ interface IOnkyoSubscriber {
9
+ handler: (eventArg: TOnkyoClientEvent) => void;
10
+ unsubscribe: () => void;
11
+ }
12
+
13
+ export class OnkyoClient {
14
+ readonly #config: ReturnType<typeof normalizeOnkyoConfig>;
15
+ readonly #homeId: string;
16
+ readonly #protocol: plugins.ecobridgeDeviceManager.OnkyoProtocol;
17
+ readonly #lifetime = new AbortController();
18
+ readonly #operations = new Set<Promise<unknown>>();
19
+ readonly #subscribers = new Set<IOnkyoSubscriber>();
20
+ readonly #parentSignal?: AbortSignal;
21
+ readonly #onParentAbort: () => void;
22
+ #healthy = false;
23
+ #closed = false;
24
+ #closePromise?: Promise<void>;
25
+
26
+ constructor(configArg: IOnkyoConfig, contextArg: INativeIntegrationSetupContext, signalArg?: AbortSignal) {
27
+ this.#config = normalizeOnkyoConfig(configArg);
28
+ const context = new NativeHomeAdmission('onkyo').admit(contextArg, this.#config.homeId);
29
+ this.#homeId = context.homeId;
30
+ if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Onkyo lifetime signal must be an AbortSignal.');
31
+ signalArg = signalArg && context.abortSignal ? AbortSignal.any([signalArg, context.abortSignal]) : signalArg ?? context.abortSignal;
32
+ this.#protocol = new plugins.ecobridgeDeviceManager.OnkyoProtocol({
33
+ address: this.#config.host, port: this.#config.port, expectedMacAddress: this.#config.macAddress, expectedModel: this.#config.model,
34
+ timeoutMs: this.#config.requestTimeoutMs, maxQueuedOperations: this.#config.maxPendingOperations,
313
35
  });
314
- }
315
-
316
- private host(): string {
317
- if (!this.configArg.host) {
318
- throw new Error('Onkyo eISCP requires config.host.');
319
- }
320
- return this.configArg.host;
321
- }
322
-
323
- private port(): number {
324
- return this.configArg.port || onkyoDefaultPort;
325
- }
326
-
327
- private timeoutMs(): number {
328
- return this.configArg.timeoutMs || onkyoDefaultTimeoutMs;
329
- }
330
-
331
- private volumeResolution(): number {
332
- return numberConfig(this.configArg, 'volumeResolution') || numberConfig(this.configArg, 'volume_resolution') || 50;
333
- }
334
-
335
- private maxVolume(): number {
336
- return numberConfig(this.configArg, 'maxVolume') || numberConfig(this.configArg, 'max_volume') || 100;
337
- }
338
-
339
- private createOfflineSnapshot(errorArg: string): IOnkyoSnapshot {
340
- return SimpleLocalMapper.toSnapshot({ profile: onkyoProfile, config: this.configArg, rawData: this.configArg.rawData, online: false, source: 'runtime', error: errorArg }) as IOnkyoSnapshot;
341
- }
342
- }
343
-
344
- function buildEiscpFrame(commandArg: string): Buffer {
345
- const data = Buffer.from(`!1${commandArg}\r`, 'ascii');
346
- const header = Buffer.alloc(eiscpHeaderSize);
347
- header.write('ISCP', 0, 'ascii');
348
- header.writeUInt32BE(eiscpHeaderSize, 4);
349
- header.writeUInt32BE(data.length, 8);
350
- header.writeUInt8(1, 12);
351
- return Buffer.concat([header, data]);
352
- }
353
-
354
- function parseEiscpResponses(bufferArg: Buffer): { messages: string[]; remaining: Buffer } {
355
- const messages: string[] = [];
356
- let offset = 0;
357
- while (bufferArg.length - offset >= eiscpHeaderSize) {
358
- if (bufferArg.subarray(offset, offset + 4).toString('ascii') !== 'ISCP') {
359
- offset++;
360
- continue;
361
- }
362
- const headerSize = bufferArg.readUInt32BE(offset + 4);
363
- const dataSize = bufferArg.readUInt32BE(offset + 8);
364
- const frameSize = headerSize + dataSize;
365
- if (bufferArg.length - offset < frameSize) {
366
- break;
367
- }
368
- const body = bufferArg.subarray(offset + headerSize, offset + frameSize).toString('ascii').replace(/[\u0000\u001a\r\n]+$/g, '');
369
- if (body.startsWith('!') && body.length >= 5) {
370
- messages.push(body.slice(2));
36
+ this.#parentSignal = signalArg;
37
+ // The terminal memo remains rejected for its owning lease or explicit caller.
38
+ this.#onParentAbort = () => { void this.destroy().catch(() => undefined); };
39
+ signalArg?.addEventListener('abort', this.#onParentAbort, { once: true });
40
+ if (signalArg?.aborted) this.#onParentAbort();
41
+ }
42
+
43
+ public isHealthy(): boolean { return this.#healthy && !this.#closed && !this.#protocol.isClosed; }
44
+ public getSnapshot(signalArg?: AbortSignal): Promise<IOnkyoSnapshot> {
45
+ return this.#run((signal) => this.#inspect(signal), signalArg);
46
+ }
47
+ public setPower(valueArg: boolean, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
48
+ const value = onkyoBoolean(valueArg, 'power');
49
+ return this.#run((signal) => this.#control('power', value, signal), signalArg);
50
+ }
51
+ public setMuted(valueArg: boolean, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
52
+ const value = onkyoBoolean(valueArg, 'mute');
53
+ return this.#run((signal) => this.#control('mute', value, signal), signalArg);
54
+ }
55
+ public setVolume(valueArg: number, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
56
+ const value = onkyoInteger(valueArg, 0, this.#config.volumeLimitRaw, 'raw volume');
57
+ return this.#run((signal) => this.#control('volume_raw', value, signal), signalArg);
58
+ }
59
+ public setInput(valueArg: string, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
60
+ const value = onkyoInputId(valueArg);
61
+ return this.#run((signal) => this.#control('input', value, signal), signalArg);
62
+ }
63
+ public writeFeature(idArg: string, valueArg: plugins.shxInterfaces.data.TDeviceStateValue, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
64
+ const value = idArg === 'power' || idArg === 'mute' ? onkyoBoolean(valueArg, idArg) :
65
+ idArg === 'volume_raw' ? onkyoInteger(valueArg, 0, this.#config.volumeLimitRaw, 'raw volume') :
66
+ idArg === 'input' ? onkyoInputId(valueArg) : undefined;
67
+ if (value === undefined) throw new TypeError('Onkyo feature is not writable.');
68
+ return this.#run(async (signal) => {
69
+ const current = OnkyoMapper.toDevices(await this.#inspect(signal))[0].features.find((featureArg) => featureArg.id === idArg);
70
+ if (!current?.writable) throw new TypeError('Onkyo current canonical feature is not writable.');
71
+ signal.throwIfAborted();
72
+ return this.#control(idArg, value, signal);
73
+ }, signalArg);
74
+ }
75
+ public subscribe(handlerArg: (eventArg: TOnkyoClientEvent) => void, signalArg?: AbortSignal): () => void {
76
+ if (this.#closed || this.#protocol.isClosed) throw new Error('Onkyo client is closed.');
77
+ if (typeof handlerArg !== 'function') throw new TypeError('Onkyo subscriber must be a function.');
78
+ if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Onkyo subscription signal must be an AbortSignal.');
79
+ signalArg?.throwIfAborted();
80
+ const subscriber: IOnkyoSubscriber = { handler: handlerArg, unsubscribe: () => {
81
+ signalArg?.removeEventListener('abort', subscriber.unsubscribe);
82
+ this.#subscribers.delete(subscriber);
83
+ } };
84
+ this.#subscribers.add(subscriber);
85
+ signalArg?.addEventListener('abort', subscriber.unsubscribe, { once: true });
86
+ return subscriber.unsubscribe;
87
+ }
88
+ public destroy(): Promise<void> {
89
+ if (this.#closePromise) return this.#closePromise;
90
+ this.#closed = true;
91
+ this.#healthy = false;
92
+ this.#parentSignal?.removeEventListener('abort', this.#onParentAbort);
93
+ for (const subscriber of this.#subscribers) subscriber.unsubscribe();
94
+ this.#closePromise = Promise.resolve().then(async () => {
95
+ const close = Promise.resolve().then(() => this.#protocol.close());
96
+ const results = await Promise.allSettled([close, ...this.#operations]);
97
+ if (results[0].status === 'rejected') throw results[0].reason;
98
+ });
99
+ this.#lifetime.abort(new Error('Onkyo client is destroyed.'));
100
+ return this.#closePromise;
101
+ }
102
+ async #inspect(signalArg: AbortSignal): Promise<IOnkyoSnapshot> {
103
+ const state = await this.#protocol.inspect({ signal: signalArg });
104
+ const snapshot: IOnkyoSnapshot = Object.freeze({ ...state, homeId: this.#homeId,
105
+ name: this.#config.name ?? `${state.receiver.model} main zone`, volumeLimitRaw: this.#config.volumeLimitRaw, online: true });
106
+ this.#emit({ type: 'snapshot', snapshot });
107
+ return snapshot;
108
+ }
109
+ async #control(idArg: string, valueArg: boolean | number | string, signalArg: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
110
+ const options = { signal: signalArg };
111
+ let observation: plugins.ecobridgeDeviceManager.IOnkyoControlObservation;
112
+ if (idArg === 'power' && typeof valueArg === 'boolean') observation = await this.#protocol.setPower(valueArg, options);
113
+ else if (idArg === 'mute' && typeof valueArg === 'boolean') observation = await this.#protocol.setMuted(valueArg, options);
114
+ else if (idArg === 'volume_raw' && typeof valueArg === 'number') observation = await this.#protocol.setVolume(valueArg, options);
115
+ else if (idArg === 'input' && typeof valueArg === 'string') observation = await this.#protocol.setInput(valueArg, options);
116
+ else throw new TypeError('Onkyo control requires an explicit supported value.');
117
+ this.#emit({ type: 'control', observation });
118
+ return observation;
119
+ }
120
+ #run<TResult>(workArg: (signalArg: AbortSignal) => Promise<TResult>, signalArg?: AbortSignal): Promise<TResult> {
121
+ if (this.#closed || this.#protocol.isClosed) return Promise.reject(new plugins.ecobridgeDeviceManager.OnkyoProtocolError('closed', 'Onkyo client is terminally closed.'));
122
+ if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) return Promise.reject(new TypeError('Onkyo operation signal must be an AbortSignal.'));
123
+ if (signalArg?.aborted) return Promise.reject(new plugins.ecobridgeDeviceManager.OnkyoProtocolError('cancelled', 'Onkyo operation was cancelled before admission.'));
124
+ if (this.#operations.size >= this.#config.maxPendingOperations) return Promise.reject(new plugins.ecobridgeDeviceManager.OnkyoProtocolError('capacity', 'Onkyo client operation capacity is full.'));
125
+ const deadline = new AbortController();
126
+ const signal = AbortSignal.any([this.#lifetime.signal, deadline.signal, ...(signalArg ? [signalArg] : [])]);
127
+ const timer = setTimeout(() => deadline.abort(), this.#config.requestTimeoutMs);
128
+ timer.unref();
129
+ const operation = Promise.resolve().then(() => { signal.throwIfAborted(); return workArg(signal); }).then((resultArg) => {
130
+ this.#healthy = !this.#closed && !signal.aborted && !this.#protocol.isClosed;
131
+ return resultArg;
132
+ }, (errorArg: unknown) => {
133
+ this.#healthy = false;
134
+ const failure = deadline.signal.aborted && errorArg instanceof plugins.ecobridgeDeviceManager.OnkyoProtocolError ?
135
+ new plugins.ecobridgeDeviceManager.OnkyoProtocolError('timeout', 'Onkyo consumer operation deadline exceeded.', errorArg.effectOutcome) : errorArg;
136
+ if (!this.#closed && failure instanceof Error) this.#emit({ type: 'error', error: failure });
137
+ throw failure;
138
+ }).finally(() => { clearTimeout(timer); this.#operations.delete(operation); });
139
+ this.#operations.add(operation);
140
+ return operation;
141
+ }
142
+ #emit(eventArg: TOnkyoClientEvent): void {
143
+ if (this.#closed) return;
144
+ for (const subscriber of [...this.#subscribers]) {
145
+ if (!this.#subscribers.has(subscriber)) continue;
146
+ try { subscriber.handler(structuredClone(eventArg)); }
147
+ catch {
148
+ subscriber.unsubscribe();
149
+ this.#emit({ type: 'error', error: new Error('Onkyo subscriber callback failed and was removed.') });
150
+ }
371
151
  }
372
- offset += frameSize;
373
- }
374
- return { messages, remaining: Buffer.from(bufferArg.subarray(offset)) };
375
- }
376
-
377
- async function optional<TValue>(callbackArg: () => Promise<TValue>): Promise<TValue | undefined> {
378
- try {
379
- return await callbackArg();
380
- } catch {
381
- return undefined;
382
- }
383
- }
384
-
385
- function sourceList(configArg: IOnkyoConfig): string[] {
386
- const configured = recordConfig(configArg, 'sourceMap') || recordConfig(configArg, 'inputSources') || recordConfig(configArg, 'input_sources');
387
- if (configured) {
388
- return Object.values(configured).filter((valueArg): valueArg is string => typeof valueArg === 'string' && valueArg.trim().length > 0);
389
152
  }
390
- return Object.values(onkyoSourceCodes);
391
- }
392
-
393
- function sourceName(codeArg: string, configArg: IOnkyoConfig): string {
394
- const configured = recordConfig(configArg, 'sourceMap') || recordConfig(configArg, 'inputSources') || recordConfig(configArg, 'input_sources');
395
- const configuredName = configured?.[codeArg];
396
- return typeof configuredName === 'string' && configuredName.trim() ? configuredName : onkyoSourceCodes[codeArg] || codeArg;
397
- }
398
-
399
- function sourceCode(sourceArg: string, configArg: IOnkyoConfig): string | undefined {
400
- const normalized = normalizeKey(sourceArg);
401
- const configured = recordConfig(configArg, 'sourceMap') || recordConfig(configArg, 'inputSources') || recordConfig(configArg, 'input_sources');
402
- const configuredMatch = configured ? Object.entries(configured).find(([codeArg, nameArg]) => normalizeKey(codeArg) === normalized || normalizeKey(String(nameArg)) === normalized) : undefined;
403
- if (configuredMatch) {
404
- return configuredMatch[0].toUpperCase();
405
- }
406
- return Object.entries(onkyoSourceCodes).find(([codeArg, nameArg]) => normalizeKey(codeArg) === normalized || normalizeKey(nameArg) === normalized)?.[0];
407
- }
408
-
409
- function normalizeKey(valueArg: string): string {
410
- return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '');
411
- }
412
-
413
- function hex(valueArg: number): string {
414
- return Math.round(valueArg).toString(16).toUpperCase().padStart(2, '0');
415
- }
416
-
417
- function clamp(valueArg: number, minArg: number, maxArg: number): number {
418
- return Math.max(minArg, Math.min(maxArg, valueArg));
419
- }
420
-
421
- function numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
422
- const value = requestArg.data?.[keyArg];
423
- if (typeof value === 'number' && Number.isFinite(value)) {
424
- return value;
425
- }
426
- if (typeof value === 'string' && value.trim() && Number.isFinite(Number(value))) {
427
- return Number(value);
428
- }
429
- return undefined;
430
- }
431
-
432
- function booleanData(requestArg: IServiceCallRequest, keyArg: string): boolean | undefined {
433
- const value = requestArg.data?.[keyArg];
434
- return typeof value === 'boolean' ? value : undefined;
435
- }
436
-
437
- function stringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
438
- const value = requestArg.data?.[keyArg];
439
- return typeof value === 'string' && value.trim() ? value.trim() : undefined;
440
- }
441
-
442
- function numberConfig(configArg: IOnkyoConfig, keyArg: string): number | undefined {
443
- const value = configArg[keyArg];
444
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
445
- }
446
-
447
- function stringConfig(configArg: IOnkyoConfig, keyArg: string): string | undefined {
448
- const value = configArg[keyArg];
449
- return typeof value === 'string' && value.trim() ? value.trim() : undefined;
450
- }
451
-
452
- function recordConfig(configArg: IOnkyoConfig, keyArg: string): Record<string, string> | undefined {
453
- const value = configArg[keyArg];
454
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
455
- return undefined;
456
- }
457
- return Object.fromEntries(Object.entries(value as Record<string, unknown>).filter(([, valueArg]) => typeof valueArg === 'string')) as Record<string, string>;
458
- }
459
-
460
- function errorMessage(errorArg: unknown): string {
461
- return errorArg instanceof Error ? errorArg.message : String(errorArg);
462
153
  }