@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,867 +1,125 @@
1
1
  import * as plugins from '../../plugins.js';
2
- import { ModbusMapper } from './modbus.mapper.js';
3
- import type {
4
- IModbusCoilConfig,
5
- IModbusCoilState,
6
- IModbusCommand,
7
- IModbusCommandResult,
8
- IModbusConfig,
9
- IModbusEntityConfigBase,
10
- IModbusEntitySnapshot,
11
- IModbusEvent,
12
- IModbusHubConfig,
13
- IModbusHubSnapshot,
14
- IModbusManualTcpEntry,
15
- IModbusRegisterConfig,
16
- IModbusRegisterState,
17
- IModbusSlaveConfig,
18
- IModbusSlaveSnapshot,
19
- IModbusSnapshot,
20
- IModbusSwitchConfig,
21
- IModbusTcpCommandShape,
22
- IModbusTcpResponse,
23
- TModbusCoilType,
24
- TModbusDataType,
25
- TModbusRegisterType,
26
- TModbusTransport,
27
- TModbusWriteType,
28
- } from './modbus.types.js';
29
-
30
- const defaultHubName = 'modbus_hub';
31
- const defaultTimeoutMs = 5000;
32
- const defaultTcpPort = 502;
33
-
34
- type TModbusEventHandler = (eventArg: IModbusEvent) => void;
35
-
36
- export class ModbusUnsupportedError extends Error {
37
- constructor(messageArg: string) {
38
- super(messageArg);
39
- this.name = 'ModbusUnsupportedError';
40
- }
41
- }
2
+ import type { IModbusConfig, IModbusNormalizedConfig, IModbusObservation, IModbusSnapshot, TModbusCommand, TModbusNormalizedMapping } from './modbus.types.js';
3
+ import { modbusCommand, normalizeModbusConfig } from './modbus.validation.js';
42
4
 
5
+ /** Owns selection and mapping; devicemanager owns every wire exchange and queue. */
43
6
  export class ModbusClient {
44
- private snapshot?: IModbusSnapshot;
45
- private transactionId = 1;
46
- private readonly eventHandlers = new Set<TModbusEventHandler>();
47
-
48
- constructor(private readonly config: IModbusConfig) {}
49
-
50
- public async getSnapshot(): Promise<IModbusSnapshot> {
51
- if (this.config.snapshot) {
52
- this.snapshot = this.normalizeSnapshot(this.cloneSnapshot(this.config.snapshot));
53
- return this.snapshot;
54
- }
55
- if (!this.snapshot) {
56
- this.snapshot = this.snapshotFromConfig(this.config.connected ?? true);
57
- }
58
- return this.snapshot;
7
+ readonly #config: IModbusNormalizedConfig;
8
+ readonly #protocol: plugins.ecobridgeDeviceManager.ModbusProtocol;
9
+ readonly #lifetime = new AbortController();
10
+ readonly #operations = new Set<Promise<unknown>>();
11
+ readonly #ownerSignal?: AbortSignal;
12
+ readonly #ownerAbort = (): void => {
13
+ // Keep the rejecting close promise available to the owner's eventual join.
14
+ void this.destroy().then(() => undefined, () => undefined);
15
+ };
16
+ #healthy = false;
17
+ #closed = false;
18
+ #closing?: Promise<void>;
19
+ #retrying?: Promise<void>;
20
+ #cleanupFailed = false;
21
+
22
+ constructor(configArg: IModbusConfig, ownerSignalArg?: AbortSignal) {
23
+ this.#config = normalizeModbusConfig(configArg);
24
+ if (ownerSignalArg !== undefined && !(ownerSignalArg instanceof AbortSignal)) throw new TypeError('Modbus owner signal must be an AbortSignal.');
25
+ ownerSignalArg?.throwIfAborted();
26
+ this.#ownerSignal = ownerSignalArg;
27
+ const writableCoils: number[] = [];
28
+ const writableRegisters: plugins.ecobridgeDeviceManager.IModbusRegisterWritePermission[] = [];
29
+ for (const mapping of this.#config.mappings) {
30
+ if (mapping.kind === 'coil' && mapping.write === true) writableCoils.push(mapping.address);
31
+ if (mapping.kind === 'holding' && mapping.write !== undefined) writableRegisters.push({ address: mapping.address, ...mapping.write });
32
+ }
33
+ this.#protocol = new plugins.ecobridgeDeviceManager.ModbusProtocol({
34
+ address: this.#config.address, port: this.#config.port, unitId: this.#config.unitId,
35
+ expectedVendorName: this.#config.expectedVendorName, expectedProductCode: this.#config.expectedProductCode,
36
+ expectedRevision: this.#config.expectedRevision, allowUnencryptedLan: true, localAddress: this.#config.localAddress,
37
+ timeoutMs: this.#config.timeoutMs, maxQueuedOperations: this.#config.maxQueuedOperations, writableCoils, writableRegisters,
38
+ });
39
+ ownerSignalArg?.addEventListener('abort', this.#ownerAbort, { once: true });
40
+ if (ownerSignalArg?.aborted) this.#ownerAbort();
59
41
  }
60
42
 
61
- public async refresh(): Promise<IModbusSnapshot> {
62
- this.snapshot = undefined;
63
- const snapshot = await this.getSnapshot();
64
- this.emit({ type: 'snapshot_refreshed', data: snapshot, timestamp: Date.now() });
65
- return snapshot;
66
- }
43
+ public get config(): IModbusNormalizedConfig { return this.#config; }
44
+ public isHealthy(): boolean { return this.#healthy && !this.#closed && !this.#protocol.isClosed; }
67
45
 
68
- public onEvent(handlerArg: TModbusEventHandler): () => void {
69
- this.eventHandlers.add(handlerArg);
70
- return () => this.eventHandlers.delete(handlerArg);
46
+ public getSnapshot(signalArg?: AbortSignal): Promise<IModbusSnapshot> {
47
+ return this.track(async () => {
48
+ const signal = this.signal(signalArg);
49
+ const observations: IModbusObservation[] = [];
50
+ for (const mapping of this.#config.mappings) observations.push(await this.read(mapping, signal));
51
+ signal.throwIfAborted();
52
+ this.#healthy = true;
53
+ return Object.freeze({ config: this.#config, identity: observations[observations.length - 1].identity, observations: Object.freeze(observations), observedAt: new Date().toISOString() });
54
+ });
71
55
  }
72
56
 
73
- public async sendCommand(commandArg: IModbusCommand): Promise<IModbusCommandResult> {
74
- this.emit({ type: 'command_mapped', command: commandArg, hubName: commandArg.hub, hubId: commandArg.hubId, unitId: this.commandUnitId(commandArg), entityId: commandArg.entityId, deviceId: commandArg.deviceId, uniqueId: commandArg.uniqueId, timestamp: Date.now() });
75
- try {
76
- const result = await this.executeCommand(commandArg);
77
- this.emit({
78
- type: result.success ? 'command_executed' : 'command_failed',
79
- command: commandArg,
80
- hubName: commandArg.hub,
81
- hubId: commandArg.hubId,
82
- unitId: this.commandUnitId(commandArg),
83
- entityId: commandArg.entityId,
84
- deviceId: commandArg.deviceId,
85
- uniqueId: commandArg.uniqueId,
86
- data: result,
87
- timestamp: Date.now(),
88
- });
89
- return result;
90
- } catch (errorArg) {
91
- const result: IModbusCommandResult = { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg), data: { command: commandArg } };
92
- this.emit({ type: 'command_failed', command: commandArg, data: result, timestamp: Date.now() });
57
+ public sendCommand(commandArg: TModbusCommand, signalArg?: AbortSignal): Promise<IModbusObservation | plugins.ecobridgeDeviceManager.IModbusWriteReceipt> {
58
+ return this.track(async () => {
59
+ const command = modbusCommand(commandArg, this.#config);
60
+ const signal = this.signal(signalArg);
61
+ const mapping = this.#config.mappings.find((entry) => entry.id === command.mappingId)!;
62
+ const result = command.type === 'read' ? await this.read(mapping, signal) :
63
+ command.type === 'write_coil' ? await this.#protocol.writeSingleCoil(mapping.address, command.value, { signal }) :
64
+ await this.#protocol.writeSingleRegister(mapping.address, command.value, { signal });
65
+ this.#healthy = true;
93
66
  return result;
94
- }
95
- }
96
-
97
- public tcpCommandShape(commandArg: IModbusCommand, hubArg?: IModbusHubSnapshot): IModbusTcpCommandShape {
98
- const unitId = this.commandUnitId(commandArg);
99
- const transactionId = this.nextTransactionId();
100
- const commandPdu = this.pduForCommand(commandArg);
101
- const frame = this.tcpFrame(transactionId, unitId, commandPdu.pdu);
102
- return {
103
- transport: 'tcp',
104
- host: hubArg?.host,
105
- port: this.numericPort(hubArg?.port),
106
- unitId,
107
- transactionId,
108
- functionCode: commandPdu.functionCode,
109
- functionName: commandPdu.functionName,
110
- address: commandPdu.address,
111
- quantity: commandPdu.quantity,
112
- values: commandPdu.values,
113
- mbap: {
114
- transactionId,
115
- protocolId: 0,
116
- length: commandPdu.pdu.length + 1,
117
- unitId,
118
- },
119
- pduHex: commandPdu.pdu.toString('hex'),
120
- requestHex: frame.toString('hex'),
121
- };
122
- }
123
-
124
- public async destroy(): Promise<void> {
125
- this.eventHandlers.clear();
126
- }
127
-
128
- private async executeCommand(commandArg: IModbusCommand): Promise<IModbusCommandResult> {
129
- if (commandArg.type === 'refresh') {
130
- return { success: true, data: await this.refresh() };
131
- }
132
-
133
- const snapshot = await this.getSnapshot();
134
- if (commandArg.type === 'stop' || commandArg.type === 'restart') {
135
- this.patchHubOnline(snapshot, commandArg, commandArg.type === 'restart');
136
- return { success: true, data: { mode: 'snapshot', command: commandArg } };
137
- }
138
-
139
- if (this.config.commandExecutor) {
140
- return this.commandResult(await this.config.commandExecutor(commandArg));
141
- }
142
-
143
- const hub = this.resolveHub(snapshot, commandArg);
144
- if (!hub) {
145
- return { success: false, error: `Modbus hub not found: ${commandArg.hub || commandArg.hubId || defaultHubName}` };
146
- }
147
-
148
- const unsupportedReason = hub.unsupportedReason || this.unsupportedReason(hub.type);
149
- if (unsupportedReason) {
150
- return { success: false, error: unsupportedReason };
151
- }
152
-
153
- const shape = this.tcpCommandShape(commandArg, hub);
154
- if (!hub.host || !this.numericPort(hub.port)) {
155
- const snapshotResult = this.snapshotCommand(commandArg, snapshot, shape);
156
- return snapshotResult || { success: false, error: 'No live Modbus TCP host is configured for this hub.', data: { shape } };
157
- }
158
-
159
- const response = await this.executeTcpCommand(hub, shape);
160
- this.patchSnapshotFromResponse(snapshot, commandArg, response);
161
- return { success: true, data: { response, shape } };
162
- }
163
-
164
- private async executeTcpCommand(hubArg: IModbusHubSnapshot, shapeArg: IModbusTcpCommandShape): Promise<IModbusTcpResponse> {
165
- if (hubArg.type !== 'tcp') {
166
- throw new ModbusUnsupportedError(this.unsupportedReason(hubArg.type) || `Unsupported Modbus transport: ${hubArg.type}`);
167
- }
168
- const host = hubArg.host;
169
- const port = this.numericPort(hubArg.port);
170
- if (!host || !port) {
171
- throw new Error('Modbus TCP command requires host and numeric port.');
172
- }
173
- const request = Buffer.from(shapeArg.requestHex, 'hex');
174
- const response = await this.requestTcp(host, port, request, this.timeoutMs(hubArg));
175
- return this.parseTcpResponse(response, shapeArg);
176
- }
177
-
178
- private async requestTcp(hostArg: string, portArg: number, frameArg: Buffer, timeoutMsArg: number): Promise<Buffer> {
179
- return await new Promise<Buffer>((resolve, reject) => {
180
- const socket = new plugins.net.Socket();
181
- const chunks: Buffer[] = [];
182
- let settled = false;
183
- const finish = (errorArg?: Error, dataArg?: Buffer) => {
184
- if (settled) {
185
- return;
186
- }
187
- settled = true;
188
- socket.destroy();
189
- if (errorArg) {
190
- reject(errorArg);
191
- } else {
192
- resolve(dataArg || Buffer.alloc(0));
193
- }
194
- };
195
- socket.setTimeout(timeoutMsArg, () => finish(new Error(`Modbus TCP request timed out after ${timeoutMsArg}ms.`)));
196
- socket.once('error', (errorArg) => finish(errorArg));
197
- socket.once('close', () => {
198
- if (!settled) {
199
- finish(new Error('Modbus TCP connection closed before a full response was received.'));
200
- }
201
- });
202
- socket.on('data', (dataArg) => {
203
- chunks.push(Buffer.isBuffer(dataArg) ? dataArg : Buffer.from(dataArg));
204
- const buffer = Buffer.concat(chunks);
205
- if (buffer.length < 7) {
206
- return;
207
- }
208
- const responseLength = buffer.readUInt16BE(4);
209
- const totalLength = 6 + responseLength;
210
- if (buffer.length >= totalLength) {
211
- finish(undefined, buffer.subarray(0, totalLength));
212
- }
213
- });
214
- socket.connect(portArg, hostArg, () => socket.write(frameArg));
215
67
  });
216
68
  }
217
69
 
218
- private parseTcpResponse(responseArg: Buffer, shapeArg: IModbusTcpCommandShape): IModbusTcpResponse {
219
- if (responseArg.length < 8) {
220
- throw new Error('Modbus TCP response is shorter than the MBAP header.');
221
- }
222
- const transactionId = responseArg.readUInt16BE(0);
223
- const protocolId = responseArg.readUInt16BE(2);
224
- const unitId = responseArg.readUInt8(6);
225
- const pdu = responseArg.subarray(7);
226
- const functionCode = pdu.readUInt8(0);
227
- if (transactionId !== shapeArg.transactionId) {
228
- throw new Error(`Modbus TCP transaction mismatch: expected ${shapeArg.transactionId}, got ${transactionId}.`);
229
- }
230
- if (protocolId !== 0) {
231
- throw new Error(`Unsupported Modbus TCP protocol id: ${protocolId}.`);
232
- }
233
- if (functionCode & 0x80) {
234
- const exceptionCode = pdu.readUInt8(1);
235
- throw new Error(`Modbus exception response ${exceptionCode} for function ${functionCode & 0x7f}.`);
236
- }
237
- if (functionCode !== shapeArg.functionCode) {
238
- throw new Error(`Modbus function mismatch: expected ${shapeArg.functionCode}, got ${functionCode}.`);
239
- }
240
-
241
- const response: IModbusTcpResponse = {
242
- transactionId,
243
- unitId,
244
- functionCode,
245
- rawHex: responseArg.toString('hex'),
246
- };
247
- if (functionCode === 1 || functionCode === 2) {
248
- const byteCount = pdu.readUInt8(1);
249
- const bits: boolean[] = [];
250
- const quantity = shapeArg.quantity || byteCount * 8;
251
- for (let index = 0; index < quantity; index++) {
252
- bits.push(Boolean(pdu[2 + Math.floor(index / 8)] & (1 << (index % 8))));
253
- }
254
- response.bits = bits;
255
- response.quantity = quantity;
256
- } else if (functionCode === 3 || functionCode === 4) {
257
- const byteCount = pdu.readUInt8(1);
258
- const registers: number[] = [];
259
- for (let index = 0; index < byteCount / 2; index++) {
260
- registers.push(pdu.readUInt16BE(2 + index * 2));
261
- }
262
- response.registers = registers;
263
- response.quantity = registers.length;
264
- } else if (functionCode === 5 || functionCode === 6) {
265
- response.address = pdu.readUInt16BE(1);
266
- response.quantity = 1;
267
- } else if (functionCode === 15 || functionCode === 16) {
268
- response.address = pdu.readUInt16BE(1);
269
- response.quantity = pdu.readUInt16BE(3);
270
- }
271
- return response;
272
- }
273
-
274
- private snapshotCommand(commandArg: IModbusCommand, snapshotArg: IModbusSnapshot, shapeArg: IModbusTcpCommandShape): IModbusCommandResult | undefined {
275
- if (commandArg.type === 'read_register') {
276
- const register = this.findRegister(snapshotArg, commandArg.hub || commandArg.hubId, this.commandUnitId(commandArg), commandArg.address, commandArg.inputType || 'holding');
277
- return register ? { success: true, data: { source: 'snapshot', shape: shapeArg, registers: register.registers, value: ModbusMapper.decodeRegisterValue(register) } } : undefined;
278
- }
279
- if (commandArg.type === 'read_coil') {
280
- const coil = this.findCoil(snapshotArg, commandArg.hub || commandArg.hubId, this.commandUnitId(commandArg), commandArg.address, commandArg.inputType || 'coil');
281
- return coil ? { success: true, data: { source: 'snapshot', shape: shapeArg, bits: coil.bits, value: coil.value } } : undefined;
282
- }
283
- if (commandArg.type === 'write_register' || commandArg.type === 'write_coil') {
284
- this.patchSnapshotFromCommand(snapshotArg, commandArg);
285
- return { success: true, data: { source: 'snapshot', shape: shapeArg } };
286
- }
287
- return undefined;
70
+ public destroy(): Promise<void> {
71
+ if (this.#closing) return this.#closing;
72
+ this.#closed = true;
73
+ this.#healthy = false;
74
+ this.#closing = Promise.resolve().then(() => this.drain());
75
+ this.#ownerSignal?.removeEventListener('abort', this.#ownerAbort);
76
+ this.#lifetime.abort(new Error('Modbus client is destroyed.'));
77
+ return this.#closing;
288
78
  }
289
79
 
290
- private pduForCommand(commandArg: IModbusCommand): { pdu: Buffer; functionCode: number; functionName: string; address?: number; quantity?: number; values?: number[] | boolean[] } {
291
- if (commandArg.type === 'read_register') {
292
- const functionCode = commandArg.inputType === 'input' ? 4 : 3;
293
- const quantity = commandArg.count || ModbusMapper.registerCountForDataType(commandArg.dataType || 'int16');
294
- const pdu = Buffer.alloc(5);
295
- pdu.writeUInt8(functionCode, 0);
296
- pdu.writeUInt16BE(commandArg.address, 1);
297
- pdu.writeUInt16BE(quantity, 3);
298
- return { pdu, functionCode, functionName: functionCode === 4 ? 'read_input_registers' : 'read_holding_registers', address: commandArg.address, quantity };
299
- }
300
- if (commandArg.type === 'read_coil') {
301
- const functionCode = commandArg.inputType === 'discrete_input' ? 2 : 1;
302
- const quantity = commandArg.count || 1;
303
- const pdu = Buffer.alloc(5);
304
- pdu.writeUInt8(functionCode, 0);
305
- pdu.writeUInt16BE(commandArg.address, 1);
306
- pdu.writeUInt16BE(quantity, 3);
307
- return { pdu, functionCode, functionName: functionCode === 2 ? 'read_discrete_inputs' : 'read_coils', address: commandArg.address, quantity };
308
- }
309
- if (commandArg.type === 'write_register') {
310
- const values = Array.isArray(commandArg.value) ? commandArg.value : [commandArg.value];
311
- if (values.length === 1) {
312
- const pdu = Buffer.alloc(5);
313
- pdu.writeUInt8(6, 0);
314
- pdu.writeUInt16BE(commandArg.address, 1);
315
- pdu.writeUInt16BE(values[0] & 0xffff, 3);
316
- return { pdu, functionCode: 6, functionName: 'write_register', address: commandArg.address, quantity: 1, values };
317
- }
318
- const pdu = Buffer.alloc(6 + values.length * 2);
319
- pdu.writeUInt8(16, 0);
320
- pdu.writeUInt16BE(commandArg.address, 1);
321
- pdu.writeUInt16BE(values.length, 3);
322
- pdu.writeUInt8(values.length * 2, 5);
323
- values.forEach((valueArg, indexArg) => pdu.writeUInt16BE(valueArg & 0xffff, 6 + indexArg * 2));
324
- return { pdu, functionCode: 16, functionName: 'write_registers', address: commandArg.address, quantity: values.length, values };
80
+ /** Retries retained teardown only; the terminal transport is never reopened or replayed. */
81
+ public retryCleanup(): Promise<void> {
82
+ if (!this.#closing) return this.destroy();
83
+ if (!this.#cleanupFailed) return this.#closing;
84
+ if (!this.#retrying) {
85
+ const retry = Promise.resolve().then(() => this.drain());
86
+ this.#retrying = retry;
87
+ const settled = (): void => { this.#retrying = undefined; };
88
+ void retry.then(settled, settled);
325
89
  }
326
- if (commandArg.type === 'write_coil') {
327
- const values = this.coilValues(commandArg.value);
328
- if (values.length === 1) {
329
- const pdu = Buffer.alloc(5);
330
- pdu.writeUInt8(5, 0);
331
- pdu.writeUInt16BE(commandArg.address, 1);
332
- pdu.writeUInt16BE(values[0] ? 0xff00 : 0x0000, 3);
333
- return { pdu, functionCode: 5, functionName: 'write_coil', address: commandArg.address, quantity: 1, values };
334
- }
335
- const byteCount = Math.ceil(values.length / 8);
336
- const pdu = Buffer.alloc(6 + byteCount);
337
- pdu.writeUInt8(15, 0);
338
- pdu.writeUInt16BE(commandArg.address, 1);
339
- pdu.writeUInt16BE(values.length, 3);
340
- pdu.writeUInt8(byteCount, 5);
341
- values.forEach((valueArg, indexArg) => {
342
- if (valueArg) {
343
- pdu[6 + Math.floor(indexArg / 8)] |= 1 << (indexArg % 8);
344
- }
345
- });
346
- return { pdu, functionCode: 15, functionName: 'write_coils', address: commandArg.address, quantity: values.length, values };
347
- }
348
- throw new Error(`Modbus command has no TCP PDU shape: ${commandArg.type}`);
90
+ return this.#retrying;
349
91
  }
350
92
 
351
- private tcpFrame(transactionIdArg: number, unitIdArg: number, pduArg: Buffer): Buffer {
352
- const frame = Buffer.alloc(7 + pduArg.length);
353
- frame.writeUInt16BE(transactionIdArg, 0);
354
- frame.writeUInt16BE(0, 2);
355
- frame.writeUInt16BE(pduArg.length + 1, 4);
356
- frame.writeUInt8(unitIdArg, 6);
357
- pduArg.copy(frame, 7);
358
- return frame;
93
+ private async drain(): Promise<void> {
94
+ const results = await Promise.allSettled([Promise.resolve().then(() => this.#protocol.close()), ...this.#operations]);
95
+ this.#operations.clear();
96
+ // Operation failures are returned to their own callers; close failures belong here.
97
+ const close = results[0];
98
+ this.#cleanupFailed = close.status === 'rejected';
99
+ if (close.status === 'rejected') throw new AggregateError([close.reason], 'Modbus client cleanup failed.');
359
100
  }
360
101
 
361
- private snapshotFromConfig(connectedArg: boolean): IModbusSnapshot {
362
- const updatedAt = new Date().toISOString();
363
- const hubs = this.hubsFromConfig().map((hubArg, indexArg) => this.hubSnapshotFromConfig(hubArg, indexArg, connectedArg, updatedAt));
364
- return {
365
- hubs,
366
- events: [],
367
- connected: connectedArg && hubs.some((hubArg) => hubArg.online),
368
- updatedAt,
369
- };
102
+ private async read(mappingArg: TModbusNormalizedMapping, signalArg: AbortSignal): Promise<IModbusObservation> {
103
+ const options = { signal: signalArg };
104
+ const result = mappingArg.kind === 'coil' ? await this.#protocol.readCoils(mappingArg.address, mappingArg.quantity, options) :
105
+ mappingArg.kind === 'discrete_input' ? await this.#protocol.readDiscreteInputs(mappingArg.address, mappingArg.quantity, options) :
106
+ mappingArg.kind === 'holding' ? await this.#protocol.readHoldingRegisters(mappingArg.address, mappingArg.quantity, options) :
107
+ await this.#protocol.readInputRegisters(mappingArg.address, mappingArg.quantity, options);
108
+ return Object.freeze({ mapping: mappingArg, identity: result.identity, values: result.values, observedAt: result.observedAt });
370
109
  }
371
110
 
372
- private hubSnapshotFromConfig(hubArg: IModbusHubConfig, indexArg: number, connectedArg: boolean, updatedAtArg: string): IModbusHubSnapshot {
373
- const type = this.transportValue(hubArg.type) || 'tcp';
374
- const name = hubArg.name || (indexArg ? `${defaultHubName}_${indexArg}` : defaultHubName);
375
- const id = slug(hubArg.id || name || `${hubArg.host || 'modbus'}_${hubArg.port || defaultTcpPort}`);
376
- const unsupportedReason = this.unsupportedReason(type);
377
- const slaveMap = new Map<number, IModbusSlaveSnapshot>();
378
- const registers: IModbusRegisterState[] = [];
379
- const coils: IModbusCoilState[] = [];
380
- const entities: IModbusEntitySnapshot[] = [];
381
- const defaultUnitId = this.unitIdFromConfig(hubArg, hubArg);
382
-
383
- for (const slave of hubArg.slaves || []) {
384
- this.ensureSlave(slaveMap, id, this.unitIdFromConfig(slave, hubArg), slave, connectedArg && !unsupportedReason);
385
- }
386
-
387
- const addRegister = (registerArg: IModbusRegisterConfig, platformArg: 'sensor' | 'number') => {
388
- const unitId = this.unitIdFromConfig(registerArg, hubArg);
389
- const register = this.registerStateFromConfig(registerArg, id, unitId, updatedAtArg);
390
- registers.push(register);
391
- this.ensureSlave(slaveMap, id, unitId, undefined, connectedArg && !unsupportedReason).registers.push(register);
392
- entities.push(this.registerEntityFromConfig(registerArg, platformArg, id, name, register, connectedArg && !unsupportedReason));
393
- };
394
- const addCoil = (coilArg: IModbusCoilConfig | IModbusSwitchConfig, platformArg: 'binary_sensor' | 'switch') => {
395
- const unitId = this.unitIdFromConfig(coilArg, hubArg);
396
- const writeType = this.writeTypeValue((coilArg as IModbusSwitchConfig).writeType || (coilArg as IModbusSwitchConfig).write_type);
397
- const isRegisterBackedSwitch = platformArg === 'switch' && (writeType === 'holding' || writeType === 'holdings');
398
- if (isRegisterBackedSwitch) {
399
- const register = this.registerStateFromSwitchConfig(coilArg as IModbusSwitchConfig, id, unitId, updatedAtArg);
400
- registers.push(register);
401
- this.ensureSlave(slaveMap, id, unitId, undefined, connectedArg && !unsupportedReason).registers.push(register);
402
- entities.push(this.switchRegisterEntityFromConfig(coilArg as IModbusSwitchConfig, id, name, register, connectedArg && !unsupportedReason));
403
- return;
404
- }
405
- const coil = this.coilStateFromConfig(coilArg, id, unitId, updatedAtArg);
406
- coils.push(coil);
407
- this.ensureSlave(slaveMap, id, unitId, undefined, connectedArg && !unsupportedReason).coils.push(coil);
408
- entities.push(this.coilEntityFromConfig(coilArg, platformArg, id, name, coil, connectedArg && !unsupportedReason));
409
- };
410
-
411
- for (const register of hubArg.registers || []) {
412
- addRegister(register, register.platform === 'number' || register.writable ? 'number' : 'sensor');
413
- }
414
- for (const sensor of hubArg.sensors || []) {
415
- addRegister(sensor, 'sensor');
416
- }
417
- for (const number of hubArg.numbers || []) {
418
- addRegister(number, 'number');
419
- }
420
- for (const coil of hubArg.coils || []) {
421
- addCoil(coil, coil.platform === 'switch' || coil.writable ? 'switch' : 'binary_sensor');
422
- }
423
- for (const binarySensor of [...(hubArg.binarySensors || []), ...(hubArg.binary_sensors || [])]) {
424
- addCoil(binarySensor, 'binary_sensor');
425
- }
426
- for (const switchArg of hubArg.switches || []) {
427
- addCoil(switchArg, 'switch');
428
- }
429
-
430
- if (!slaveMap.size && defaultUnitId) {
431
- this.ensureSlave(slaveMap, id, defaultUnitId, undefined, connectedArg && !unsupportedReason);
432
- }
433
-
434
- return {
435
- id,
436
- name,
437
- type,
438
- host: hubArg.host,
439
- port: hubArg.port || (type === 'tcp' ? defaultTcpPort : undefined),
440
- online: connectedArg && hubArg.connected !== false && !unsupportedReason,
441
- manufacturer: hubArg.manufacturer || 'Modbus',
442
- model: hubArg.model || `${type.toUpperCase()} hub`,
443
- serialNumber: hubArg.serialNumber || hubArg.serial_number,
444
- unsupportedReason,
445
- slaves: [...slaveMap.values()],
446
- registers,
447
- coils,
448
- entities,
449
- metadata: this.cleanAttributes({
450
- timeoutMs: this.timeoutMs(hubArg),
451
- messageWaitMilliseconds: hubArg.messageWaitMilliseconds ?? hubArg.message_wait_milliseconds,
452
- delay: hubArg.delay,
453
- ...hubArg.metadata,
454
- }),
455
- };
456
- }
457
-
458
- private registerStateFromConfig(configArg: IModbusRegisterConfig, hubIdArg: string, unitIdArg: number, updatedAtArg: string): IModbusRegisterState {
459
- const dataType = this.dataTypeValue(configArg.dataType || configArg.data_type);
460
- return {
461
- hubId: hubIdArg,
462
- unitId: unitIdArg,
463
- address: configArg.address,
464
- count: configArg.count || ModbusMapper.registerCountForDataType(dataType),
465
- inputType: this.registerTypeValue(configArg.inputType || configArg.input_type || configArg.registerType || configArg.register_type),
466
- dataType,
467
- registers: configArg.registers,
468
- value: configArg.value,
469
- updatedAt: updatedAtArg,
470
- metadata: this.registerMetadata(configArg),
471
- };
472
- }
473
-
474
- private registerStateFromSwitchConfig(configArg: IModbusSwitchConfig, hubIdArg: string, unitIdArg: number, updatedAtArg: string): IModbusRegisterState {
475
- return {
476
- hubId: hubIdArg,
477
- unitId: unitIdArg,
478
- address: configArg.address,
479
- count: 1,
480
- inputType: 'holding',
481
- dataType: 'uint16',
482
- value: typeof configArg.value === 'number' ? configArg.value : configArg.value ? 1 : 0,
483
- updatedAt: updatedAtArg,
484
- };
485
- }
486
-
487
- private coilStateFromConfig(configArg: IModbusCoilConfig | IModbusSwitchConfig, hubIdArg: string, unitIdArg: number, updatedAtArg: string): IModbusCoilState {
488
- const slaveCount = configArg.slaveCount || configArg.slave_count || configArg.virtualCount || configArg.virtual_count || 0;
489
- return {
490
- hubId: hubIdArg,
491
- unitId: unitIdArg,
492
- address: configArg.address,
493
- count: configArg.count || slaveCount + 1,
494
- inputType: this.coilTypeValue(configArg.inputType || configArg.input_type || configArg.coilType || configArg.coil_type),
495
- bits: configArg.bits,
496
- value: configArg.value,
497
- updatedAt: updatedAtArg,
498
- };
111
+ private signal(signalArg?: AbortSignal): AbortSignal {
112
+ if (this.#closed) throw new Error('Modbus client is destroyed.');
113
+ if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Modbus operation signal must be an AbortSignal.');
114
+ const signal = signalArg ? AbortSignal.any([this.#lifetime.signal, signalArg]) : this.#lifetime.signal;
115
+ signal.throwIfAborted();
116
+ return signal;
499
117
  }
500
118
 
501
- private registerEntityFromConfig(configArg: IModbusRegisterConfig, platformArg: 'sensor' | 'number', hubIdArg: string, hubNameArg: string, registerArg: IModbusRegisterState, availableArg: boolean): IModbusEntitySnapshot {
502
- const uniqueId = configArg.uniqueId || configArg.unique_id || `modbus_${hubIdArg}_slave_${registerArg.unitId}_${platformArg}_${registerArg.address}`;
503
- return {
504
- id: `${platformArg}.${slug(configArg.name)}`,
505
- name: configArg.name,
506
- uniqueId,
507
- platform: platformArg,
508
- hubId: hubIdArg,
509
- hubName: hubNameArg,
510
- unitId: registerArg.unitId,
511
- address: registerArg.address,
512
- available: availableArg && configArg.available !== false,
513
- writable: platformArg === 'number' || Boolean(configArg.writable),
514
- register: registerArg,
515
- attributes: this.cleanAttributes({
516
- unit: configArg.unitOfMeasurement || configArg.unit_of_measurement,
517
- deviceClass: configArg.deviceClass || configArg.device_class,
518
- stateClass: configArg.stateClass || configArg.state_class,
519
- min: configArg.min ?? configArg.minValue ?? configArg.min_value,
520
- max: configArg.max ?? configArg.maxValue ?? configArg.max_value,
521
- step: configArg.step,
522
- scanInterval: configArg.scanInterval ?? configArg.scan_interval,
523
- scale: configArg.scale,
524
- offset: configArg.offset,
525
- precision: configArg.precision,
526
- swap: configArg.swap,
527
- ...configArg.metadata,
528
- }),
529
- };
530
- }
531
-
532
- private coilEntityFromConfig(configArg: IModbusCoilConfig | IModbusSwitchConfig, platformArg: 'binary_sensor' | 'switch', hubIdArg: string, hubNameArg: string, coilArg: IModbusCoilState, availableArg: boolean): IModbusEntitySnapshot {
533
- const switchConfig = configArg as IModbusSwitchConfig;
534
- const uniqueId = configArg.uniqueId || configArg.unique_id || `modbus_${hubIdArg}_slave_${coilArg.unitId}_${platformArg}_${coilArg.address}`;
535
- return {
536
- id: `${platformArg}.${slug(configArg.name)}`,
537
- name: configArg.name,
538
- uniqueId,
539
- platform: platformArg,
540
- hubId: hubIdArg,
541
- hubName: hubNameArg,
542
- unitId: coilArg.unitId,
543
- address: coilArg.address,
544
- available: availableArg && configArg.available !== false,
545
- writable: platformArg === 'switch' || Boolean(configArg.writable),
546
- coil: coilArg,
547
- attributes: this.cleanAttributes({
548
- deviceClass: configArg.deviceClass || configArg.device_class,
549
- scanInterval: configArg.scanInterval ?? configArg.scan_interval,
550
- writeType: this.writeTypeValue(switchConfig.writeType || switchConfig.write_type) || 'coil',
551
- commandOn: switchConfig.commandOn ?? switchConfig.command_on ?? 1,
552
- commandOff: switchConfig.commandOff ?? switchConfig.command_off ?? 0,
553
- verify: switchConfig.verify,
554
- ...configArg.metadata,
555
- }),
556
- };
557
- }
558
-
559
- private switchRegisterEntityFromConfig(configArg: IModbusSwitchConfig, hubIdArg: string, hubNameArg: string, registerArg: IModbusRegisterState, availableArg: boolean): IModbusEntitySnapshot {
560
- const commandOn = configArg.commandOn ?? configArg.command_on ?? 1;
561
- const commandOff = configArg.commandOff ?? configArg.command_off ?? 0;
562
- const value = ModbusMapper.decodeRegisterValue(registerArg);
563
- return {
564
- id: `switch.${slug(configArg.name)}`,
565
- name: configArg.name,
566
- uniqueId: configArg.uniqueId || configArg.unique_id || `modbus_${hubIdArg}_slave_${registerArg.unitId}_switch_${registerArg.address}`,
567
- platform: 'switch',
568
- hubId: hubIdArg,
569
- hubName: hubNameArg,
570
- unitId: registerArg.unitId,
571
- address: registerArg.address,
572
- available: availableArg && configArg.available !== false,
573
- writable: true,
574
- register: registerArg,
575
- state: value === commandOn ? 'on' : value === commandOff ? 'off' : 'unknown',
576
- attributes: this.cleanAttributes({
577
- deviceClass: configArg.deviceClass || configArg.device_class,
578
- scanInterval: configArg.scanInterval ?? configArg.scan_interval,
579
- writeType: this.writeTypeValue(configArg.writeType || configArg.write_type) || 'holding',
580
- commandOn,
581
- commandOff,
582
- verify: configArg.verify,
583
- ...configArg.metadata,
584
- }),
585
- };
586
- }
587
-
588
- private hubsFromConfig(): IModbusHubConfig[] {
589
- if (this.config.hubs?.length) {
590
- return this.config.hubs;
591
- }
592
- if (this.config.modbus?.length) {
593
- return this.config.modbus;
594
- }
595
- if (this.config.manualEntries?.length) {
596
- return this.config.manualEntries.map((entryArg) => this.hubFromManualEntry(entryArg));
597
- }
598
- return [];
599
- }
600
-
601
- private hubFromManualEntry(entryArg: IModbusManualTcpEntry): IModbusHubConfig {
602
- return {
603
- id: entryArg.id,
604
- name: entryArg.name || defaultHubName,
605
- type: 'tcp',
606
- host: entryArg.host,
607
- port: entryArg.port || defaultTcpPort,
608
- slave: entryArg.unitId || entryArg.slave || 1,
609
- manufacturer: entryArg.manufacturer || 'Modbus',
610
- model: entryArg.model || 'TCP hub',
611
- serialNumber: entryArg.serialNumber,
612
- metadata: entryArg.metadata,
613
- };
614
- }
615
-
616
- private ensureSlave(slaveMapArg: Map<number, IModbusSlaveSnapshot>, hubIdArg: string, unitIdArg: number, slaveArg: IModbusSlaveConfig | undefined, onlineArg: boolean): IModbusSlaveSnapshot {
617
- const existing = slaveMapArg.get(unitIdArg);
618
- if (existing) {
619
- return existing;
620
- }
621
- const slave: IModbusSlaveSnapshot = {
622
- id: slug(slaveArg?.id || `${hubIdArg}_${unitIdArg}`),
623
- hubId: hubIdArg,
624
- unitId: unitIdArg,
625
- name: slaveArg?.name || `Modbus unit ${unitIdArg}`,
626
- online: onlineArg && slaveArg?.connected !== false,
627
- manufacturer: slaveArg?.manufacturer,
628
- model: slaveArg?.model,
629
- serialNumber: slaveArg?.serialNumber || slaveArg?.serial_number,
630
- registers: [],
631
- coils: [],
632
- metadata: slaveArg?.metadata,
633
- };
634
- slaveMapArg.set(unitIdArg, slave);
635
- return slave;
636
- }
637
-
638
- private normalizeSnapshot(snapshotArg: IModbusSnapshot): IModbusSnapshot {
639
- const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
640
- const hubs = snapshotArg.hubs.map((hubArg) => ({
641
- ...hubArg,
642
- id: slug(hubArg.id || hubArg.name),
643
- name: hubArg.name || defaultHubName,
644
- type: this.transportValue(hubArg.type) || 'tcp',
645
- online: hubArg.online && !this.unsupportedReason(hubArg.type),
646
- unsupportedReason: hubArg.unsupportedReason || this.unsupportedReason(hubArg.type),
647
- slaves: hubArg.slaves || [],
648
- registers: hubArg.registers || [],
649
- coils: hubArg.coils || [],
650
- entities: hubArg.entities || [],
651
- }));
652
- return {
653
- ...snapshotArg,
654
- hubs,
655
- events: snapshotArg.events || [],
656
- connected: snapshotArg.connected && hubs.some((hubArg) => hubArg.online),
657
- updatedAt,
658
- };
659
- }
660
-
661
- private resolveHub(snapshotArg: IModbusSnapshot, commandArg: IModbusCommand): IModbusHubSnapshot | undefined {
662
- if (commandArg.hubId) {
663
- return snapshotArg.hubs.find((hubArg) => hubArg.id === commandArg.hubId || hubArg.name === commandArg.hubId);
664
- }
665
- if (commandArg.hub) {
666
- return snapshotArg.hubs.find((hubArg) => hubArg.name === commandArg.hub || hubArg.id === commandArg.hub);
667
- }
668
- return snapshotArg.hubs[0];
669
- }
670
-
671
- private findRegister(snapshotArg: IModbusSnapshot, hubArg: string | undefined, unitIdArg: number, addressArg: number, inputTypeArg: TModbusRegisterType): IModbusRegisterState | undefined {
672
- const hubs = hubArg ? snapshotArg.hubs.filter((entryArg) => entryArg.name === hubArg || entryArg.id === hubArg) : snapshotArg.hubs;
673
- return hubs.flatMap((entryArg) => entryArg.registers).find((registerArg) => registerArg.unitId === unitIdArg && registerArg.address === addressArg && registerArg.inputType === inputTypeArg);
674
- }
675
-
676
- private findCoil(snapshotArg: IModbusSnapshot, hubArg: string | undefined, unitIdArg: number, addressArg: number, inputTypeArg: TModbusCoilType): IModbusCoilState | undefined {
677
- const hubs = hubArg ? snapshotArg.hubs.filter((entryArg) => entryArg.name === hubArg || entryArg.id === hubArg) : snapshotArg.hubs;
678
- return hubs.flatMap((entryArg) => entryArg.coils).find((coilArg) => coilArg.unitId === unitIdArg && coilArg.address === addressArg && coilArg.inputType === inputTypeArg);
679
- }
680
-
681
- private patchSnapshotFromResponse(snapshotArg: IModbusSnapshot, commandArg: IModbusCommand, responseArg: IModbusTcpResponse): void {
682
- if (commandArg.type === 'read_register' && responseArg.registers) {
683
- this.patchRegister(snapshotArg, commandArg, responseArg.registers);
684
- this.emit({ type: 'register_read', command: commandArg, data: responseArg, timestamp: Date.now() });
685
- } else if (commandArg.type === 'write_register') {
686
- this.patchRegister(snapshotArg, commandArg, Array.isArray(commandArg.value) ? commandArg.value : [commandArg.value]);
687
- this.emit({ type: 'register_written', command: commandArg, data: responseArg, timestamp: Date.now() });
688
- } else if (commandArg.type === 'read_coil' && responseArg.bits) {
689
- this.patchCoil(snapshotArg, commandArg, responseArg.bits);
690
- this.emit({ type: 'coil_read', command: commandArg, data: responseArg, timestamp: Date.now() });
691
- } else if (commandArg.type === 'write_coil') {
692
- this.patchCoil(snapshotArg, commandArg, this.coilValues(commandArg.value));
693
- this.emit({ type: 'coil_written', command: commandArg, data: responseArg, timestamp: Date.now() });
694
- }
695
- }
696
-
697
- private patchSnapshotFromCommand(snapshotArg: IModbusSnapshot, commandArg: IModbusCommand): void {
698
- if (commandArg.type === 'write_register') {
699
- this.patchRegister(snapshotArg, commandArg, Array.isArray(commandArg.value) ? commandArg.value : [commandArg.value]);
700
- } else if (commandArg.type === 'write_coil') {
701
- this.patchCoil(snapshotArg, commandArg, this.coilValues(commandArg.value));
702
- }
703
- }
704
-
705
- private patchRegister(snapshotArg: IModbusSnapshot, commandArg: IModbusCommand, registersArg: number[]): void {
706
- if (commandArg.type !== 'read_register' && commandArg.type !== 'write_register') {
707
- return;
708
- }
709
- const hub = this.resolveHub(snapshotArg, commandArg);
710
- if (!hub) {
711
- return;
712
- }
713
- const inputType = commandArg.type === 'read_register' ? commandArg.inputType || 'holding' : 'holding';
714
- const register = hub.registers.find((registerArg) => registerArg.unitId === this.commandUnitId(commandArg) && registerArg.address === commandArg.address && registerArg.inputType === inputType);
715
- const updatedAt = new Date().toISOString();
716
- if (register) {
717
- register.registers = registersArg;
718
- register.value = undefined;
719
- register.updatedAt = updatedAt;
720
- }
721
- }
722
-
723
- private patchCoil(snapshotArg: IModbusSnapshot, commandArg: IModbusCommand, bitsArg: boolean[]): void {
724
- if (commandArg.type !== 'read_coil' && commandArg.type !== 'write_coil') {
725
- return;
726
- }
727
- const hub = this.resolveHub(snapshotArg, commandArg);
728
- if (!hub) {
729
- return;
730
- }
731
- const inputType = commandArg.type === 'read_coil' ? commandArg.inputType || 'coil' : 'coil';
732
- const coil = hub.coils.find((coilArg) => coilArg.unitId === this.commandUnitId(commandArg) && coilArg.address === commandArg.address && coilArg.inputType === inputType);
733
- const updatedAt = new Date().toISOString();
734
- if (coil) {
735
- coil.bits = bitsArg;
736
- coil.value = bitsArg[0] ?? false;
737
- coil.updatedAt = updatedAt;
738
- }
739
- }
740
-
741
- private patchHubOnline(snapshotArg: IModbusSnapshot, commandArg: IModbusCommand, onlineArg: boolean): void {
742
- const hub = this.resolveHub(snapshotArg, commandArg);
743
- if (!hub) {
744
- return;
745
- }
746
- hub.online = onlineArg && !hub.unsupportedReason;
747
- for (const slave of hub.slaves) {
748
- slave.online = hub.online;
749
- }
750
- snapshotArg.connected = snapshotArg.hubs.some((hubArg) => hubArg.online);
751
- snapshotArg.updatedAt = new Date().toISOString();
752
- }
753
-
754
- private commandResult(resultArg: IModbusCommandResult | unknown): IModbusCommandResult {
755
- if (resultArg && typeof resultArg === 'object' && 'success' in resultArg) {
756
- return resultArg as IModbusCommandResult;
757
- }
758
- return { success: true, data: resultArg };
759
- }
760
-
761
- private emit(eventArg: IModbusEvent): void {
762
- for (const handler of this.eventHandlers) {
763
- handler(eventArg);
764
- }
765
- }
766
-
767
- private commandUnitId(commandArg: IModbusCommand): number {
768
- return commandArg.unitId || commandArg.slave || 1;
769
- }
770
-
771
- private nextTransactionId(): number {
772
- const current = this.transactionId;
773
- this.transactionId = this.transactionId >= 0xffff ? 1 : this.transactionId + 1;
774
- return current;
775
- }
776
-
777
- private coilValues(valueArg: boolean | boolean[] | number | number[]): boolean[] {
778
- const values = Array.isArray(valueArg) ? valueArg : [valueArg];
779
- return values.map((value) => typeof value === 'boolean' ? value : Boolean(value));
780
- }
781
-
782
- private registerTypeValue(valueArg: unknown): TModbusRegisterType {
783
- return valueArg === 'input' ? 'input' : 'holding';
784
- }
785
-
786
- private coilTypeValue(valueArg: unknown): TModbusCoilType | TModbusRegisterType {
787
- if (valueArg === 'discrete_input') {
788
- return 'discrete_input';
789
- }
790
- if (valueArg === 'holding' || valueArg === 'input') {
791
- return valueArg;
792
- }
793
- return 'coil';
794
- }
795
-
796
- private dataTypeValue(valueArg: unknown): TModbusDataType {
797
- return typeof valueArg === 'string' && valueArg ? valueArg as TModbusDataType : 'int16';
798
- }
799
-
800
- private writeTypeValue(valueArg: unknown): TModbusWriteType | undefined {
801
- return valueArg === 'holding' || valueArg === 'holdings' || valueArg === 'coil' || valueArg === 'coils' ? valueArg : undefined;
802
- }
803
-
804
- private transportValue(valueArg: unknown): TModbusTransport | undefined {
805
- return valueArg === 'tcp' || valueArg === 'udp' || valueArg === 'rtuovertcp' || valueArg === 'serial' ? valueArg : undefined;
806
- }
807
-
808
- private unsupportedReason(typeArg: TModbusTransport): string | undefined {
809
- if (typeArg === 'serial') {
810
- return 'Modbus RTU serial is explicitly unsupported in this TypeScript runtime; configure TCP or provide a snapshot/commandExecutor.';
811
- }
812
- if (typeArg === 'udp') {
813
- return 'Modbus UDP transport is not implemented in this TypeScript runtime.';
814
- }
815
- if (typeArg === 'rtuovertcp') {
816
- return 'Modbus RTU-over-TCP framer is not implemented in this TypeScript runtime.';
817
- }
818
- return undefined;
819
- }
820
-
821
- private unitIdFromConfig(configArg: IModbusEntityConfigBase | IModbusSlaveConfig | IModbusHubConfig, hubArg: IModbusHubConfig): number {
822
- return configArg.unitId || configArg.unit || configArg.slave || configArg.deviceAddress || configArg.device_address || hubArg.slave || hubArg.deviceAddress || hubArg.device_address || 1;
823
- }
824
-
825
- private numericPort(valueArg: number | string | undefined): number | undefined {
826
- return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg)) ? Number(valueArg) : undefined;
827
- }
828
-
829
- private timeoutMs(hubArg: Pick<IModbusHubConfig, 'timeout' | 'timeoutMs'> | Pick<IModbusHubSnapshot, 'metadata'>): number {
830
- const metadataTimeout = 'metadata' in hubArg ? this.numberValue(hubArg.metadata?.timeoutMs) : undefined;
831
- if ('timeoutMs' in hubArg && hubArg.timeoutMs) {
832
- return hubArg.timeoutMs;
833
- }
834
- if ('timeout' in hubArg && hubArg.timeout) {
835
- return hubArg.timeout * 1000;
836
- }
837
- return metadataTimeout || this.config.timeoutMs || defaultTimeoutMs;
838
- }
839
-
840
- private numberValue(valueArg: unknown): number | undefined {
841
- return typeof valueArg === 'number' && Number.isFinite(valueArg) ? valueArg : typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg)) ? Number(valueArg) : undefined;
842
- }
843
-
844
- private registerMetadata(configArg: IModbusRegisterConfig): Record<string, unknown> {
845
- return this.cleanAttributes({
846
- scale: configArg.scale,
847
- offset: configArg.offset,
848
- precision: configArg.precision,
849
- swap: configArg.swap,
850
- minValue: configArg.minValue ?? configArg.min_value,
851
- maxValue: configArg.maxValue ?? configArg.max_value,
852
- nanValue: configArg.nanValue ?? configArg.nan_value,
853
- zeroSuppress: configArg.zeroSuppress ?? configArg.zero_suppress,
854
- structure: configArg.structure,
855
- });
856
- }
857
-
858
- private cloneSnapshot(snapshotArg: IModbusSnapshot): IModbusSnapshot {
859
- return JSON.parse(JSON.stringify(snapshotArg)) as IModbusSnapshot;
860
- }
861
-
862
- private cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
863
- return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
119
+ private track<T>(workArg: () => Promise<T>): Promise<T> {
120
+ const operation = Promise.resolve().then(workArg).catch((errorArg: unknown) => { this.#healthy = false; throw errorArg; });
121
+ this.#operations.add(operation);
122
+ void operation.then(() => this.#operations.delete(operation), () => this.#operations.delete(operation));
123
+ return operation;
864
124
  }
865
125
  }
866
-
867
- const slug = (valueArg: string): string => valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'modbus';