@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,216 +1,135 @@
1
- import type * as shxInterfaces from '@smarthome.exchange/interfaces';
1
+ import * as plugins from '../../plugins.js';
2
2
  import { BaseIntegration } from '../../core/classes.baseintegration.js';
3
- import type { IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
3
+ import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
4
+ import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationRuntime, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
4
5
  import { KodiClient } from './kodi.classes.client.js';
5
6
  import { KodiConfigFlow } from './kodi.classes.configflow.js';
6
7
  import { createKodiDiscoveryDescriptor } from './kodi.discovery.js';
7
8
  import { KodiMapper } from './kodi.mapper.js';
8
- import type { IKodiConfig, TKodiMediaType } from './kodi.types.js';
9
+ import type { IKodiConfig, IKodiIntegrationOptions, IKodiNativeSnapshot, IKodiSetupContext } from './kodi.types.js';
10
+ import { kodiBoolean, kodiInteger, kodiKeys, kodiRecord, kodiSignal, normalizeKodiConfig } from './kodi.validation.js';
9
11
 
10
12
  export class KodiIntegration extends BaseIntegration<IKodiConfig> {
11
13
  public readonly domain = 'kodi';
12
14
  public readonly displayName = 'Kodi';
13
15
  public readonly status = 'control-runtime' as const;
14
16
  public readonly discoveryDescriptor = createKodiDiscoveryDescriptor();
15
- public readonly configFlow = new KodiConfigFlow();
17
+ public readonly configFlow: KodiConfigFlow;
16
18
  public readonly metadata = {
17
- source: 'home-assistant/core',
18
- upstreamPath: 'homeassistant/components/kodi',
19
- upstreamDomain: 'kodi',
20
- integrationType: 'service',
21
- iotClass: 'local_push',
22
- requirements: ['pykodi==0.2.7'],
23
- dependencies: [],
24
- afterDependencies: ['media_source'],
25
- codeowners: ['@OnFreund'],
26
- configFlow: true,
27
- documentation: 'https://www.home-assistant.io/integrations/kodi',
19
+ source: 'native-typescript', integrationType: 'device', iotClass: 'local_poll',
20
+ localApi: { status: 'Selected Kodi HTTP JSON-RPC API13 through devicemanager.',
21
+ implemented: ['fresh application observation', 'read-only default', 'explicit owner-trusted absolute volume and boolean mute', 'home-scoped endpoint targets', 'joined terminal lifetime'],
22
+ explicitUnsupported: ['unique or cryptographic server identity', 'arbitrary JSON-RPC', 'media transport', 'library and power operations', 'toggle or increment', 'notification transport', 'retry or replay', 'Home Assistant bridge'] },
28
23
  };
24
+ readonly #owner: NativeIntegrationOwner;
25
+ #getBasicCredentials?: IKodiIntegrationOptions['getBasicCredentials'];
26
+ #closePromise?: Promise<void>;
29
27
 
30
- public async setup(configArg: IKodiConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
31
- void contextArg;
32
- return new KodiRuntime(new KodiClient(configArg));
28
+ public constructor(options: IKodiIntegrationOptions = {}) {
29
+ super();
30
+ const value = kodiRecord(options, 'integration options');
31
+ kodiKeys(value, ['homeId', 'getBasicCredentials']);
32
+ if (value.getBasicCredentials !== undefined && typeof value.getBasicCredentials !== 'function') throw new TypeError('Kodi Basic provider must be a function.');
33
+ this.#owner = new NativeIntegrationOwner('kodi', { homeId: value.homeId as string | undefined });
34
+ this.#getBasicCredentials = value.getBasicCredentials as IKodiIntegrationOptions['getBasicCredentials'];
35
+ this.configFlow = new KodiConfigFlow(this.#owner);
33
36
  }
34
37
 
35
- public async destroy(): Promise<void> {}
38
+ public setup(config: IKodiConfig, context: IKodiSetupContext): Promise<IIntegrationRuntime> {
39
+ try {
40
+ const captured = normalizeKodiConfig(config);
41
+ return this.#owner.runSetup(context, captured.homeId, async (lease) => {
42
+ const client = new KodiClient(captured, { ...lease.context, abortSignal: lease.signal }, { getBasicCredentials: this.#getBasicCredentials });
43
+ lease.own(() => client.destroy());
44
+ const initial = await client.getSnapshot();
45
+ lease.assertOpen();
46
+ const runtime = new KodiRuntime(client, initial, lease);
47
+ lease.promoteToRuntime();
48
+ return runtime;
49
+ });
50
+ } catch (error) { return Promise.reject(error); }
51
+ }
52
+
53
+ public destroy(): Promise<void> {
54
+ if (!this.#closePromise) {
55
+ this.#closePromise = Promise.resolve().then(async () => {
56
+ try { await this.#owner.close(); }
57
+ finally { this.#getBasicCredentials = undefined; }
58
+ });
59
+ // Install the facade memo before synchronously closing admission or dispatching abort.
60
+ void this.#owner.close().catch(() => undefined);
61
+ }
62
+ return this.#closePromise;
63
+ }
64
+
65
+ public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
36
66
  }
37
67
 
38
68
  export class HomeAssistantKodiIntegration extends KodiIntegration {}
39
69
 
40
70
  class KodiRuntime implements IIntegrationRuntime {
41
- public domain = 'kodi';
42
-
43
- constructor(private readonly client: KodiClient) {}
71
+ public readonly domain = 'kodi';
72
+ readonly #deviceId: string;
73
+ readonly #entityId: string;
44
74
 
45
- public async devices(): Promise<shxInterfaces.data.IDeviceDefinition[]> {
46
- return KodiMapper.toDevices(await this.client.getSnapshot());
75
+ public constructor(private readonly client: KodiClient, initial: IKodiNativeSnapshot, private readonly lease: INativeSetupLease) {
76
+ this.#deviceId = KodiMapper.deviceId(initial);
77
+ this.#entityId = KodiMapper.entityId(initial);
47
78
  }
48
79
 
49
- public async entities(): Promise<IIntegrationEntity[]> {
50
- return KodiMapper.toEntities(await this.client.getSnapshot());
51
- }
80
+ public isHealthy(): boolean { return this.client.isHealthy(); }
81
+ public async devices(signal?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> { return KodiMapper.toNativeDevices(await this.client.getSnapshot(signal)); }
82
+ public async entities(): Promise<IIntegrationEntity[]> { return KodiMapper.toNativeEntities(await this.client.getSnapshot()); }
52
83
 
53
- public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
84
+ public async callService(input: IServiceCallRequest): Promise<IServiceCallResult> {
54
85
  try {
55
- if (requestArg.domain === 'media_player') {
56
- return await this.callMediaPlayerService(requestArg);
57
- }
58
- if (requestArg.domain === 'kodi') {
59
- return await this.callKodiService(requestArg);
86
+ const request = kodiRecord(input, 'service request');
87
+ kodiKeys(request, ['domain', 'service', 'target', 'data']);
88
+ this.assertTarget(request.target);
89
+ const data = request.data === undefined ? {} : kodiRecord(request.data, 'service data');
90
+ if (request.domain === 'kodi' && request.service === 'snapshot') {
91
+ kodiKeys(data, []); return { success: true, data: await this.client.getSnapshot() };
60
92
  }
61
- if (requestArg.domain === 'notify') {
62
- return await this.callNotifyService(requestArg);
93
+ if (request.domain === 'kodi' && request.service === 'set_volume') {
94
+ kodiKeys(data, ['volume']); return { success: true, data: await this.client.setVolume(kodiInteger(data.volume, 0, 100, 'absolute volume')) };
63
95
  }
64
- if (requestArg.domain === 'remote') {
65
- return await this.callRemoteService(requestArg);
96
+ if ((request.domain === 'kodi' && request.service === 'set_muted') || (request.domain === 'media_player' && request.service === 'volume_mute')) {
97
+ kodiKeys(data, ['muted']); return { success: true, data: await this.client.setMuted(kodiBoolean(data.muted)) };
66
98
  }
67
- return { success: false, error: `Unsupported Kodi service domain: ${requestArg.domain}` };
68
- } catch (errorArg) {
69
- return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
70
- }
71
- }
72
-
73
- public async destroy(): Promise<void> {
74
- await this.client.destroy();
99
+ throw new TypeError('Kodi service is unsupported; only snapshot, absolute volume and explicit mute are admitted.');
100
+ } catch (error) { return kodiFailure(error); }
75
101
  }
76
102
 
77
- private async callMediaPlayerService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
78
- if (requestArg.service === 'turn_on') {
79
- return { success: true, data: { event: 'kodi.turn_on', entityId: requestArg.target.entityId } };
80
- }
81
- if (requestArg.service === 'turn_off') {
82
- await this.client.quit();
83
- return { success: true };
84
- }
85
- if (requestArg.service === 'play' || requestArg.service === 'media_play') {
86
- await this.client.play();
87
- return { success: true };
88
- }
89
- if (requestArg.service === 'pause' || requestArg.service === 'media_pause') {
90
- await this.client.pause();
91
- return { success: true };
92
- }
93
- if (requestArg.service === 'play_pause' || requestArg.service === 'media_play_pause') {
94
- await this.client.playPause();
95
- return { success: true };
96
- }
97
- if (requestArg.service === 'stop' || requestArg.service === 'media_stop') {
98
- await this.client.stop();
99
- return { success: true };
100
- }
101
- if (requestArg.service === 'next_track' || requestArg.service === 'media_next_track') {
102
- await this.client.nextTrack();
103
- return { success: true };
104
- }
105
- if (requestArg.service === 'previous_track' || requestArg.service === 'media_previous_track') {
106
- await this.client.previousTrack();
107
- return { success: true };
108
- }
109
- if (requestArg.service === 'seek' || requestArg.service === 'media_seek') {
110
- const position = requestArg.data?.seek_position ?? requestArg.data?.position;
111
- if (typeof position !== 'number') {
112
- return { success: false, error: 'Kodi seek requires data.seek_position.' };
113
- }
114
- await this.client.seek(position);
115
- return { success: true };
116
- }
117
- if (requestArg.service === 'volume_set') {
118
- const level = requestArg.data?.volume_level;
119
- if (typeof level !== 'number') {
120
- return { success: false, error: 'Kodi volume_set requires data.volume_level.' };
121
- }
122
- await this.client.setVolumeLevel(level);
123
- return { success: true };
124
- }
125
- if (requestArg.service === 'volume_up' || requestArg.service === 'volume_down') {
126
- await this.client.stepVolume(requestArg.service === 'volume_up' ? 'increment' : 'decrement');
127
- return { success: true };
128
- }
129
- if (requestArg.service === 'volume_mute') {
130
- const muted = requestArg.data?.is_volume_muted ?? requestArg.data?.muted;
131
- if (typeof muted !== 'boolean') {
132
- return { success: false, error: 'Kodi volume_mute requires data.is_volume_muted.' };
133
- }
134
- await this.client.setMuted(muted);
135
- return { success: true };
136
- }
137
- if (requestArg.service === 'play_media') {
138
- const mediaId = requestArg.data?.media_content_id ?? requestArg.data?.uri;
139
- const mediaType = requestArg.data?.media_content_type ?? requestArg.data?.media_type ?? 'file';
140
- if ((typeof mediaId !== 'string' && typeof mediaId !== 'number') || mediaId === '') {
141
- return { success: false, error: 'Kodi play_media requires data.media_content_id or data.uri.' };
142
- }
143
- await this.client.playMedia(typeof mediaType === 'string' ? mediaType : 'file', mediaId);
144
- return { success: true };
145
- }
146
- return { success: false, error: `Unsupported Kodi media_player service: ${requestArg.service}` };
147
- }
148
-
149
- private async callKodiService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
150
- if (requestArg.service === 'call_method') {
151
- const method = requestArg.data?.method;
152
- if (typeof method !== 'string' || !method) {
153
- return { success: false, error: 'Kodi call_method requires data.method.' };
154
- }
155
- const params = this.callMethodParams(requestArg);
156
- return { success: true, data: await this.client.callMethod(method, params) };
157
- }
158
- if (requestArg.service === 'add_to_playlist') {
159
- const mediaType = requestArg.data?.media_type;
160
- const mediaId = requestArg.data?.media_id;
161
- if (typeof mediaType !== 'string' || (typeof mediaId !== 'string' && typeof mediaId !== 'number')) {
162
- return { success: false, error: 'Kodi add_to_playlist requires data.media_type and data.media_id.' };
163
- }
164
- await this.client.addToPlaylist(mediaType as TKodiMediaType, mediaId);
165
- return { success: true };
166
- }
167
- if (requestArg.service === 'show_notification') {
168
- return this.callNotifyService(requestArg);
169
- }
170
- return { success: false, error: `Unsupported Kodi service: ${requestArg.service}` };
103
+ public async writeFeature(input: IDeviceFeatureWriteRequest, signal?: AbortSignal): Promise<IServiceCallResult> {
104
+ try {
105
+ kodiSignal(signal);
106
+ const request = kodiRecord(input, 'feature write');
107
+ kodiKeys(request, ['device', 'feature', 'value']);
108
+ const device = kodiRecord(request.device, 'feature device');
109
+ const feature = kodiRecord(request.feature, 'feature');
110
+ if (device.id !== this.#deviceId || device.integrationDomain !== 'kodi') throw new TypeError('Kodi feature target is outside this home and selected endpoint.');
111
+ if (feature.capability !== 'media') throw new TypeError('Kodi feature capability differs from the canonical feature.');
112
+ if (feature.id === 'volume') return { success: true, data: await this.client.setVolume(kodiInteger(request.value, 0, 100, 'absolute volume'), signal) };
113
+ if (feature.id === 'muted') return { success: true, data: await this.client.setMuted(kodiBoolean(request.value), signal) };
114
+ throw new TypeError('Kodi feature is not writable.');
115
+ } catch (error) { return kodiFailure(error); }
171
116
  }
172
117
 
173
- private async callNotifyService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
174
- const message = requestArg.data?.message;
175
- if (typeof message !== 'string') {
176
- return { success: false, error: 'Kodi notification requires data.message.' };
177
- }
178
- const title = typeof requestArg.data?.title === 'string' ? requestArg.data.title : 'Home Assistant';
179
- const icon = typeof requestArg.data?.icon === 'string' ? requestArg.data.icon : 'info';
180
- const displayTime = typeof requestArg.data?.displaytime === 'number' ? requestArg.data.displaytime : 10000;
181
- await this.client.showNotification(title, message, icon, displayTime);
182
- return { success: true };
118
+ public destroy(): Promise<void> {
119
+ return this.lease.close();
183
120
  }
184
121
 
185
- private async callRemoteService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
186
- if (requestArg.service !== 'send_command') {
187
- return { success: false, error: `Unsupported Kodi remote service: ${requestArg.service}` };
188
- }
189
- const command = requestArg.data?.command;
190
- const commands = typeof command === 'string' ? [command] : Array.isArray(command) ? command.filter((itemArg): itemArg is string => typeof itemArg === 'string') : [];
191
- if (!commands.length) {
192
- return { success: false, error: 'Kodi remote.send_command requires data.command.' };
193
- }
194
- for (const item of commands) {
195
- await this.client.input(item);
196
- }
197
- return { success: true };
122
+ private assertTarget(input: unknown): void {
123
+ const target = kodiRecord(input, 'service target');
124
+ kodiKeys(target, ['deviceId', 'entityId']);
125
+ if (target.deviceId === undefined && target.entityId === undefined ||
126
+ target.deviceId !== undefined && target.deviceId !== this.#deviceId ||
127
+ target.entityId !== undefined && target.entityId !== this.#entityId) throw new TypeError('Kodi requires an exact owned device or entity target.');
198
128
  }
129
+ }
199
130
 
200
- private callMethodParams(requestArg: IServiceCallRequest): Record<string, unknown> | unknown[] | undefined {
201
- const explicitParams = requestArg.data?.params;
202
- if (Array.isArray(explicitParams)) {
203
- return explicitParams;
204
- }
205
- if (explicitParams && typeof explicitParams === 'object') {
206
- return explicitParams as Record<string, unknown>;
207
- }
208
- const params: Record<string, unknown> = {};
209
- for (const [key, value] of Object.entries(requestArg.data || {})) {
210
- if (key !== 'method') {
211
- params[key] = value;
212
- }
213
- }
214
- return Object.keys(params).length ? params : undefined;
215
- }
131
+ function kodiFailure(error: unknown): IServiceCallResult {
132
+ const protocolError = error instanceof plugins.ecobridgeDeviceManager.KodiProtocolError ? error : undefined;
133
+ return { success: false, error: protocolError?.message ?? 'Kodi operation was refused or failed.',
134
+ data: { code: protocolError?.code ?? 'invalid-operation', effectOutcome: protocolError?.effectOutcome ?? 'not-sent', ...(protocolError?.rpcErrorCode === undefined ? {} : { rpcErrorCode: protocolError.rpcErrorCode }) } };
216
135
  }
@@ -1,9 +1,9 @@
1
+ import * as plugins from '../../plugins.js';
1
2
  import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
2
3
  import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
3
4
  import type { IKodiManualEntry, IKodiMdnsRecord } from './kodi.types.js';
4
5
 
5
6
  const defaultPort = 8080;
6
- const defaultWsPort = 9090;
7
7
  const kodiMdnsType = '_xbmc-jsonrpc-h._tcp.local';
8
8
 
9
9
  export class KodiMdnsMatcher implements IDiscoveryMatcher<IKodiMdnsRecord> {
@@ -20,25 +20,33 @@ export class KodiMdnsMatcher implements IDiscoveryMatcher<IKodiMdnsRecord> {
20
20
  if (!matched) {
21
21
  return { matched: false, confidence: 'low', reason: 'mDNS record is not a Kodi JSON-RPC advertisement.' };
22
22
  }
23
+ let endpoint = kodiEndpoint(recordArg.host, recordArg.port);
24
+ for (const address of recordArg.addresses || []) {
25
+ if (endpoint) break;
26
+ endpoint = kodiEndpoint(address, recordArg.port);
27
+ }
28
+ if (!endpoint) {
29
+ return { matched: false, confidence: 'low', reason: 'Kodi advertisement does not contain a supported numeric HTTP endpoint.' };
30
+ }
23
31
  return {
24
32
  matched: true,
25
- confidence: uuid ? 'certain' : 'high',
26
- reason: 'mDNS record matches Kodi JSON-RPC metadata.',
27
- normalizedDeviceId: uuid,
33
+ confidence: 'high',
34
+ reason: 'mDNS record is a Kodi endpoint hint; application selection still requires a live observation.',
28
35
  candidate: {
29
36
  source: 'mdns',
30
37
  integrationDomain: 'kodi',
31
- id: uuid,
32
- host: recordArg.host || recordArg.addresses?.[0],
33
- port: recordArg.port || defaultPort,
38
+ host: endpoint.host,
39
+ port: endpoint.port,
34
40
  name,
35
41
  manufacturer: 'Kodi',
36
42
  model: valueForKey(properties, 'version') ? 'Kodi Media Center' : undefined,
37
43
  metadata: {
38
44
  mdnsName: recordArg.name,
39
45
  mdnsType: recordArg.type,
40
- txt: properties,
41
- wsPort: defaultWsPort,
46
+ mdnsHost: recordArg.host,
47
+ advertisedId: uuid,
48
+ advertisedVersion: valueForKey(properties, 'version'),
49
+ selectionVerified: false,
42
50
  },
43
51
  },
44
52
  };
@@ -51,30 +59,24 @@ export class KodiManualMatcher implements IDiscoveryMatcher<IKodiManualEntry> {
51
59
  public description = 'Recognize manual Kodi setup entries.';
52
60
 
53
61
  public async matches(inputArg: IKodiManualEntry): Promise<IDiscoveryMatch> {
54
- const haystack = `${inputArg.name || ''} ${inputArg.model || ''} ${inputArg.manufacturer || ''}`.toLowerCase();
55
- const matched = Boolean(inputArg.host || inputArg.metadata?.kodi || haystack.includes('kodi') || haystack.includes('xbmc'));
56
- if (!matched) {
57
- return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Kodi setup hints.' };
62
+ const endpoint = kodiEndpoint(inputArg.host, inputArg.port);
63
+ if (!endpoint) {
64
+ return { matched: false, confidence: 'low', reason: 'Manual Kodi setup requires a supported numeric HTTP endpoint.' };
58
65
  }
59
- const id = inputArg.uuid || inputArg.id;
60
66
  return {
61
67
  matched: true,
62
- confidence: inputArg.host ? 'high' : 'medium',
68
+ confidence: 'high',
63
69
  reason: 'Manual entry can start Kodi setup.',
64
- normalizedDeviceId: id,
65
70
  candidate: {
66
71
  source: 'manual',
67
72
  integrationDomain: 'kodi',
68
- id,
69
- host: inputArg.host,
70
- port: inputArg.port || defaultPort,
73
+ host: endpoint.host,
74
+ port: endpoint.port,
71
75
  name: inputArg.name,
72
76
  manufacturer: inputArg.manufacturer || 'Kodi',
73
77
  model: inputArg.model,
74
78
  metadata: {
75
- ...inputArg.metadata,
76
- wsPort: inputArg.wsPort || defaultWsPort,
77
- ssl: inputArg.ssl,
79
+ selectionVerified: false,
78
80
  },
79
81
  },
80
82
  };
@@ -89,19 +91,25 @@ export class KodiCandidateValidator implements IDiscoveryValidator {
89
91
  const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
90
92
  const model = candidateArg.model?.toLowerCase() || '';
91
93
  const name = candidateArg.name?.toLowerCase() || '';
92
- const matched = candidateArg.integrationDomain === 'kodi'
94
+ const recognized = candidateArg.integrationDomain === 'kodi'
93
95
  || manufacturer.includes('kodi')
94
96
  || manufacturer.includes('xbmc')
95
97
  || model.includes('kodi')
96
98
  || model.includes('xbmc')
97
99
  || name.includes('kodi')
98
100
  || Boolean(candidateArg.metadata?.kodi);
101
+ if (!recognized) {
102
+ return { matched: false, confidence: 'low', reason: 'Candidate is not Kodi.' };
103
+ }
104
+ const endpoint = kodiEndpoint(candidateArg.host, candidateArg.port);
105
+ if (!endpoint) {
106
+ return { matched: false, confidence: 'low', reason: 'Kodi candidate does not contain a supported numeric HTTP endpoint.' };
107
+ }
99
108
  return {
100
- matched,
101
- confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
102
- reason: matched ? 'Candidate has Kodi metadata.' : 'Candidate is not Kodi.',
103
- candidate: matched ? candidateArg : undefined,
104
- normalizedDeviceId: candidateArg.id,
109
+ matched: true,
110
+ confidence: 'high',
111
+ reason: 'Candidate has Kodi metadata and a numeric endpoint hint; application selection still requires a live observation.',
112
+ candidate: { ...candidateArg, host: endpoint.host, port: endpoint.port },
105
113
  };
106
114
  }
107
115
  }
@@ -113,6 +121,19 @@ export const createKodiDiscoveryDescriptor = (): DiscoveryDescriptor => {
113
121
  .addValidator(new KodiCandidateValidator());
114
122
  };
115
123
 
124
+ /** Select only supplied endpoints; the public foundation owns origin policy. */
125
+ const kodiEndpoint = (hostArg: string | undefined, portArg: number | undefined): { host: string; port: number } | undefined => {
126
+ const port = portArg === undefined ? defaultPort : portArg;
127
+ if (typeof hostArg !== 'string' || typeof port !== 'number' || !Number.isSafeInteger(port)) return undefined;
128
+ try {
129
+ const origin = plugins.ecobridgeDeviceManager.validateKodiOrigin(`http://${hostArg}:${port}`);
130
+ return { host: new URL(origin).hostname, port };
131
+ } catch (error) {
132
+ if (error instanceof plugins.ecobridgeDeviceManager.KodiProtocolError && error.code === 'invalid-options') return undefined;
133
+ throw error;
134
+ }
135
+ };
136
+
116
137
  const normalizeType = (valueArg?: string): string => {
117
138
  return (valueArg || '').toLowerCase().replace(/\.$/, '');
118
139
  };
@@ -1,6 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { IIntegrationEntity } from '../../core/types.js';
3
- import type { IKodiMediaItem, IKodiSnapshot, IKodiTime } from './kodi.types.js';
3
+ import type { IKodiMediaItem, IKodiNativeSnapshot, IKodiSnapshot, IKodiTime } from './kodi.types.js';
4
4
 
5
5
  const kodiMediaTypes: Record<string, string> = {
6
6
  music: 'music',
@@ -19,24 +19,64 @@ const kodiMediaTypes: Record<string, string> = {
19
19
  };
20
20
 
21
21
  export class KodiMapper {
22
+ public static deviceId(snapshot: Pick<IKodiNativeSnapshot, 'homeId' | 'origin'>): string {
23
+ return `kodi:${encodeURIComponent(snapshot.homeId)}:${encodeURIComponent(snapshot.origin)}`;
24
+ }
25
+
26
+ public static entityId(snapshot: Pick<IKodiNativeSnapshot, 'homeId' | 'origin'>): string {
27
+ return `media_player.${this.deviceId(snapshot)}`;
28
+ }
29
+
30
+ public static toNativeDevices(snapshot: IKodiNativeSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
31
+ return [{
32
+ id: this.deviceId(snapshot), integrationDomain: 'kodi', name: snapshot.name, protocol: 'http',
33
+ manufacturer: 'Kodi', model: snapshot.applicationName, online: snapshot.online,
34
+ features: [
35
+ { id: 'volume', capability: 'media', name: 'Application volume', readable: true, writable: snapshot.capabilities.canSetVolume, unit: '%' },
36
+ { id: 'muted', capability: 'media', name: 'Application muted', readable: true, writable: snapshot.capabilities.canSetMuted },
37
+ ],
38
+ state: [
39
+ { featureId: 'volume', value: snapshot.volume, updatedAt: snapshot.observedAt },
40
+ { featureId: 'muted', value: snapshot.muted, updatedAt: snapshot.observedAt },
41
+ ],
42
+ metadata: {
43
+ homeId: snapshot.homeId, origin: snapshot.origin, applicationVersion: snapshot.applicationVersion,
44
+ apiVersion: snapshot.apiVersion, selection: snapshot.selection, uniqueDeviceIdentityVerified: false,
45
+ cryptographicServerIdentityVerified: false, volumeRange: snapshot.capabilities.volumeRange,
46
+ runtimeAuthority: 'native-observation', playbackObserved: false,
47
+ },
48
+ }];
49
+ }
50
+
51
+ public static toNativeEntities(snapshot: IKodiNativeSnapshot): IIntegrationEntity[] {
52
+ return [{
53
+ id: this.entityId(snapshot), uniqueId: this.deviceId(snapshot), integrationDomain: 'kodi',
54
+ deviceId: this.deviceId(snapshot), platform: 'media_player', name: snapshot.name, state: 'unknown', available: snapshot.online,
55
+ attributes: { applicationVolume: snapshot.volume, isVolumeMuted: snapshot.muted, playbackObserved: false,
56
+ applicationName: snapshot.applicationName, applicationVersion: snapshot.applicationVersion, apiVersion: snapshot.apiVersion,
57
+ selection: snapshot.selection, uniqueDeviceIdentityVerified: false, cryptographicServerIdentityVerified: false,
58
+ capabilities: snapshot.capabilities, observedAt: snapshot.observedAt },
59
+ }];
60
+ }
61
+
62
+ /** Converts offline imported data without granting runtime availability or writes. */
22
63
  public static toDevices(snapshotArg: IKodiSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
23
64
  const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
24
65
  const application = snapshotArg.application;
25
66
  return [{
26
- id: this.deviceId(snapshotArg),
67
+ id: this.offlineDeviceId(snapshotArg),
27
68
  integrationDomain: 'kodi',
28
69
  name: this.deviceName(snapshotArg),
29
70
  protocol: 'http',
30
71
  manufacturer: snapshotArg.deviceInfo.manufacturer || 'Kodi',
31
72
  model: snapshotArg.deviceInfo.model || application?.name,
32
- online: snapshotArg.online,
73
+ online: false,
33
74
  features: [
34
- { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: true },
35
- { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: true, unit: '%' },
36
- { id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: true },
75
+ { id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: false },
76
+ { id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: false, unit: '%' },
77
+ { id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: false },
37
78
  { id: 'active_player', capability: 'media', name: 'Active player', readable: true, writable: false },
38
79
  { id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
39
- { id: 'notification', capability: 'media', name: 'Notification', readable: false, writable: true },
40
80
  ],
41
81
  state: [
42
82
  { featureId: 'playback', value: this.mediaState(snapshotArg), updatedAt },
@@ -46,6 +86,7 @@ export class KodiMapper {
46
86
  { featureId: 'current_title', value: this.mediaTitle(snapshotArg.item) || null, updatedAt },
47
87
  ],
48
88
  metadata: {
89
+ runtimeAuthority: 'offline-import', uniqueDeviceIdentityVerified: false,
49
90
  uuid: snapshotArg.deviceInfo.uuid,
50
91
  host: snapshotArg.deviceInfo.host,
51
92
  port: snapshotArg.deviceInfo.port,
@@ -62,11 +103,12 @@ export class KodiMapper {
62
103
  id: `media_player.${this.slug(this.deviceName(snapshotArg))}`,
63
104
  uniqueId: `kodi_${this.uniqueBase(snapshotArg)}`,
64
105
  integrationDomain: 'kodi',
65
- deviceId: this.deviceId(snapshotArg),
106
+ deviceId: this.offlineDeviceId(snapshotArg),
66
107
  platform: 'media_player',
67
108
  name: this.deviceName(snapshotArg),
68
109
  state: this.mediaState(snapshotArg),
69
110
  attributes: {
111
+ runtimeAuthority: 'offline-import', uniqueDeviceIdentityVerified: false,
70
112
  volumeLevel: typeof snapshotArg.application?.volume === 'number' ? snapshotArg.application.volume / 100 : undefined,
71
113
  isVolumeMuted: snapshotArg.application?.muted,
72
114
  mediaContentId: this.mediaContentId(item),
@@ -86,7 +128,7 @@ export class KodiMapper {
86
128
  live: snapshotArg.playerProperties?.live,
87
129
  dynamicRange: this.dynamicRange(item),
88
130
  },
89
- available: snapshotArg.online,
131
+ available: false,
90
132
  }];
91
133
  }
92
134
 
@@ -151,7 +193,7 @@ export class KodiMapper {
151
193
  return Array.isArray(valueArg) ? valueArg[0] : valueArg;
152
194
  }
153
195
 
154
- private static deviceId(snapshotArg: IKodiSnapshot): string {
196
+ private static offlineDeviceId(snapshotArg: IKodiSnapshot): string {
155
197
  return `kodi.device.${this.uniqueBase(snapshotArg)}`;
156
198
  }
157
199