@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.
- package/changelog.md +49 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/classes.configstore.d.ts +2 -0
- package/dist_ts/core/classes.configstore.js +84 -27
- package/dist_ts/core/classes.nativehomeadmission.d.ts +19 -0
- package/dist_ts/core/classes.nativehomeadmission.js +84 -0
- package/dist_ts/core/classes.nativeintegrationowner.d.ts +79 -0
- package/dist_ts/core/classes.nativeintegrationowner.js +269 -0
- package/dist_ts/core/index.d.ts +2 -0
- package/dist_ts/core/index.js +3 -1
- package/dist_ts/core/types.d.ts +14 -0
- package/dist_ts/integrations/braviatv/braviatv.classes.client.d.ts +23 -72
- package/dist_ts/integrations/braviatv/braviatv.classes.client.js +265 -625
- package/dist_ts/integrations/braviatv/braviatv.classes.configflow.d.ts +7 -7
- package/dist_ts/integrations/braviatv/braviatv.classes.configflow.js +51 -68
- package/dist_ts/integrations/braviatv/braviatv.classes.integration.d.ts +8 -11
- package/dist_ts/integrations/braviatv/braviatv.classes.integration.js +186 -135
- package/dist_ts/integrations/braviatv/braviatv.discovery.js +12 -11
- package/dist_ts/integrations/braviatv/braviatv.mapper.d.ts +6 -14
- package/dist_ts/integrations/braviatv/braviatv.mapper.js +37 -131
- package/dist_ts/integrations/braviatv/braviatv.types.d.ts +55 -133
- package/dist_ts/integrations/braviatv/braviatv.validation.d.ts +7 -0
- package/dist_ts/integrations/braviatv/braviatv.validation.js +72 -0
- package/dist_ts/integrations/broadlink/broadlink.actions.d.ts +2 -0
- package/dist_ts/integrations/broadlink/broadlink.actions.js +36 -0
- package/dist_ts/integrations/broadlink/broadlink.classes.client.d.ts +21 -19
- package/dist_ts/integrations/broadlink/broadlink.classes.client.js +185 -124
- package/dist_ts/integrations/broadlink/broadlink.classes.configflow.d.ts +5 -4
- package/dist_ts/integrations/broadlink/broadlink.classes.configflow.js +64 -83
- package/dist_ts/integrations/broadlink/broadlink.classes.integration.d.ts +16 -19
- package/dist_ts/integrations/broadlink/broadlink.classes.integration.js +114 -59
- package/dist_ts/integrations/broadlink/broadlink.mapper.d.ts +3 -2
- package/dist_ts/integrations/broadlink/broadlink.mapper.js +2 -1
- package/dist_ts/integrations/broadlink/broadlink.selected.mapper.d.ts +10 -0
- package/dist_ts/integrations/broadlink/broadlink.selected.mapper.js +41 -0
- package/dist_ts/integrations/broadlink/broadlink.types.d.ts +43 -7
- package/dist_ts/integrations/broadlink/broadlink.validation.d.ts +7 -0
- package/dist_ts/integrations/broadlink/broadlink.validation.js +63 -0
- package/dist_ts/integrations/daikin/daikin.classes.client.d.ts +10 -52
- package/dist_ts/integrations/daikin/daikin.classes.client.js +55 -614
- package/dist_ts/integrations/daikin/daikin.classes.configflow.d.ts +8 -12
- package/dist_ts/integrations/daikin/daikin.classes.configflow.js +77 -138
- package/dist_ts/integrations/daikin/daikin.classes.integration.d.ts +7 -24
- package/dist_ts/integrations/daikin/daikin.classes.integration.js +175 -67
- package/dist_ts/integrations/daikin/daikin.discovery.d.ts +11 -24
- package/dist_ts/integrations/daikin/daikin.discovery.js +98 -305
- package/dist_ts/integrations/daikin/daikin.mapper.d.ts +6 -7
- package/dist_ts/integrations/daikin/daikin.mapper.js +95 -155
- package/dist_ts/integrations/daikin/daikin.types.d.ts +24 -44
- package/dist_ts/integrations/daikin/daikin.types.js +1 -1
- package/dist_ts/integrations/daikin/daikin.validation.d.ts +5 -0
- package/dist_ts/integrations/daikin/daikin.validation.js +53 -0
- package/dist_ts/integrations/denonavr/denonavr.classes.client.d.ts +31 -40
- package/dist_ts/integrations/denonavr/denonavr.classes.client.js +149 -404
- package/dist_ts/integrations/denonavr/denonavr.classes.configflow.d.ts +7 -2
- package/dist_ts/integrations/denonavr/denonavr.classes.configflow.js +44 -36
- package/dist_ts/integrations/denonavr/denonavr.classes.integration.d.ts +6 -11
- package/dist_ts/integrations/denonavr/denonavr.classes.integration.js +192 -113
- package/dist_ts/integrations/denonavr/denonavr.discovery.d.ts +10 -18
- package/dist_ts/integrations/denonavr/denonavr.discovery.js +71 -193
- package/dist_ts/integrations/denonavr/denonavr.mapper.d.ts +5 -13
- package/dist_ts/integrations/denonavr/denonavr.mapper.js +35 -159
- package/dist_ts/integrations/denonavr/denonavr.types.d.ts +55 -90
- package/dist_ts/integrations/denonavr/denonavr.validation.d.ts +10 -0
- package/dist_ts/integrations/denonavr/denonavr.validation.js +86 -0
- package/dist_ts/integrations/kodi/kodi.classes.client.d.ts +11 -45
- package/dist_ts/integrations/kodi/kodi.classes.client.js +84 -310
- package/dist_ts/integrations/kodi/kodi.classes.configflow.d.ts +9 -8
- package/dist_ts/integrations/kodi/kodi.classes.configflow.js +53 -48
- package/dist_ts/integrations/kodi/kodi.classes.integration.d.ts +11 -11
- package/dist_ts/integrations/kodi/kodi.classes.integration.js +114 -171
- package/dist_ts/integrations/kodi/kodi.discovery.js +53 -29
- package/dist_ts/integrations/kodi/kodi.mapper.d.ts +7 -2
- package/dist_ts/integrations/kodi/kodi.mapper.js +48 -10
- package/dist_ts/integrations/kodi/kodi.types.d.ts +32 -28
- package/dist_ts/integrations/kodi/kodi.validation.d.ts +9 -0
- package/dist_ts/integrations/kodi/kodi.validation.js +62 -0
- package/dist_ts/integrations/modbus/modbus.classes.client.d.ts +15 -61
- package/dist_ts/integrations/modbus/modbus.classes.client.js +117 -776
- package/dist_ts/integrations/modbus/modbus.classes.configflow.d.ts +9 -5
- package/dist_ts/integrations/modbus/modbus.classes.configflow.js +70 -38
- package/dist_ts/integrations/modbus/modbus.classes.integration.d.ts +6 -17
- package/dist_ts/integrations/modbus/modbus.classes.integration.js +133 -57
- package/dist_ts/integrations/modbus/modbus.discovery.d.ts +5 -8
- package/dist_ts/integrations/modbus/modbus.discovery.js +38 -93
- package/dist_ts/integrations/modbus/modbus.mapper.d.ts +8 -32
- package/dist_ts/integrations/modbus/modbus.mapper.js +71 -431
- package/dist_ts/integrations/modbus/modbus.types.d.ts +82 -332
- package/dist_ts/integrations/modbus/modbus.validation.d.ts +10 -0
- package/dist_ts/integrations/modbus/modbus.validation.js +128 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.d.ts +12 -34
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.client.js +143 -231
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.configflow.js +105 -18
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.d.ts +8 -8
- package/dist_ts/integrations/nanoleaf/nanoleaf.classes.integration.js +141 -155
- package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.d.ts +9 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.credentials.js +35 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.d.ts +6 -18
- package/dist_ts/integrations/nanoleaf/nanoleaf.mapper.js +45 -220
- package/dist_ts/integrations/nanoleaf/nanoleaf.types.d.ts +33 -147
- package/dist_ts/integrations/nanoleaf/nanoleaf.validation.d.ts +6 -0
- package/dist_ts/integrations/nanoleaf/nanoleaf.validation.js +82 -0
- package/dist_ts/integrations/onkyo/onkyo.classes.client.d.ts +15 -20
- package/dist_ts/integrations/onkyo/onkyo.classes.client.js +161 -404
- package/dist_ts/integrations/onkyo/onkyo.classes.configflow.d.ts +9 -4
- package/dist_ts/integrations/onkyo/onkyo.classes.configflow.js +51 -6
- package/dist_ts/integrations/onkyo/onkyo.classes.integration.d.ts +22 -6
- package/dist_ts/integrations/onkyo/onkyo.classes.integration.js +144 -11
- package/dist_ts/integrations/onkyo/onkyo.discovery.d.ts +6 -1
- package/dist_ts/integrations/onkyo/onkyo.discovery.js +40 -4
- package/dist_ts/integrations/onkyo/onkyo.mapper.d.ts +7 -8
- package/dist_ts/integrations/onkyo/onkyo.mapper.js +45 -12
- package/dist_ts/integrations/onkyo/onkyo.types.d.ts +58 -8
- package/dist_ts/integrations/onkyo/onkyo.types.js +13 -102
- package/dist_ts/integrations/onkyo/onkyo.validation.d.ts +16 -0
- package/dist_ts/integrations/onkyo/onkyo.validation.js +77 -0
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.d.ts +25 -25
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.client.js +116 -360
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.d.ts +11 -3
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.js +76 -6
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.d.ts +26 -4
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.classes.integration.js +142 -9
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.d.ts +2 -1
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.discovery.js +31 -4
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.d.ts +9 -9
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.mapper.js +31 -149
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.d.ts +51 -11
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.types.js +22 -87
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.d.ts +6 -0
- package/dist_ts/integrations/panasonic_viera/panasonic_viera.validation.js +56 -0
- package/dist_ts/integrations/pioneer/pioneer.classes.client.d.ts +31 -16
- package/dist_ts/integrations/pioneer/pioneer.classes.client.js +167 -409
- package/dist_ts/integrations/pioneer/pioneer.classes.configflow.d.ts +10 -3
- package/dist_ts/integrations/pioneer/pioneer.classes.configflow.js +66 -6
- package/dist_ts/integrations/pioneer/pioneer.classes.integration.d.ts +13 -5
- package/dist_ts/integrations/pioneer/pioneer.classes.integration.js +239 -11
- package/dist_ts/integrations/pioneer/pioneer.discovery.d.ts +14 -1
- package/dist_ts/integrations/pioneer/pioneer.discovery.js +51 -4
- package/dist_ts/integrations/pioneer/pioneer.mapper.d.ts +14 -8
- package/dist_ts/integrations/pioneer/pioneer.mapper.js +54 -12
- package/dist_ts/integrations/pioneer/pioneer.types.d.ts +79 -12
- package/dist_ts/integrations/pioneer/pioneer.types.js +12 -76
- package/dist_ts/integrations/pioneer/pioneer.validation.d.ts +10 -0
- package/dist_ts/integrations/pioneer/pioneer.validation.js +85 -0
- package/dist_ts/integrations/soundtouch/soundtouch.classes.client.d.ts +13 -36
- package/dist_ts/integrations/soundtouch/soundtouch.classes.client.js +179 -502
- package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/soundtouch/soundtouch.classes.configflow.js +50 -40
- package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.d.ts +14 -15
- package/dist_ts/integrations/soundtouch/soundtouch.classes.integration.js +108 -128
- package/dist_ts/integrations/soundtouch/soundtouch.discovery.d.ts +2 -20
- package/dist_ts/integrations/soundtouch/soundtouch.discovery.js +23 -113
- package/dist_ts/integrations/soundtouch/soundtouch.mapper.d.ts +3 -12
- package/dist_ts/integrations/soundtouch/soundtouch.mapper.js +35 -189
- package/dist_ts/integrations/soundtouch/soundtouch.types.d.ts +57 -142
- package/dist_ts/integrations/soundtouch/soundtouch.types.js +18 -3
- package/dist_ts/integrations/soundtouch/soundtouch.validation.d.ts +22 -0
- package/dist_ts/integrations/soundtouch/soundtouch.validation.js +65 -0
- package/dist_ts/integrations/wiz/index.d.ts +1 -0
- package/dist_ts/integrations/wiz/index.js +2 -1
- package/dist_ts/integrations/wiz/wiz.classes.client.d.ts +14 -28
- package/dist_ts/integrations/wiz/wiz.classes.client.js +93 -229
- package/dist_ts/integrations/wiz/wiz.classes.configflow.d.ts +6 -4
- package/dist_ts/integrations/wiz/wiz.classes.configflow.js +38 -67
- package/dist_ts/integrations/wiz/wiz.classes.integration.d.ts +10 -21
- package/dist_ts/integrations/wiz/wiz.classes.integration.js +204 -44
- package/dist_ts/integrations/wiz/wiz.discovery.d.ts +10 -31
- package/dist_ts/integrations/wiz/wiz.discovery.js +48 -249
- package/dist_ts/integrations/wiz/wiz.mapper.d.ts +7 -51
- package/dist_ts/integrations/wiz/wiz.mapper.js +82 -668
- package/dist_ts/integrations/wiz/wiz.types.d.ts +34 -208
- package/dist_ts/integrations/wiz/wiz.validation.d.ts +8 -0
- package/dist_ts/integrations/wiz/wiz.validation.js +72 -0
- package/dist_ts/integrations/yamaha/index.d.ts +1 -0
- package/dist_ts/integrations/yamaha/index.js +2 -1
- package/dist_ts/integrations/yamaha/yamaha.classes.client.d.ts +30 -4
- package/dist_ts/integrations/yamaha/yamaha.classes.client.js +162 -6
- package/dist_ts/integrations/yamaha/yamaha.classes.configflow.d.ts +10 -3
- package/dist_ts/integrations/yamaha/yamaha.classes.configflow.js +106 -6
- package/dist_ts/integrations/yamaha/yamaha.classes.integration.d.ts +22 -4
- package/dist_ts/integrations/yamaha/yamaha.classes.integration.js +243 -8
- package/dist_ts/integrations/yamaha/yamaha.discovery.d.ts +13 -1
- package/dist_ts/integrations/yamaha/yamaha.discovery.js +57 -4
- package/dist_ts/integrations/yamaha/yamaha.mapper.d.ts +16 -8
- package/dist_ts/integrations/yamaha/yamaha.mapper.js +58 -12
- package/dist_ts/integrations/yamaha/yamaha.types.d.ts +73 -6
- package/dist_ts/integrations/yamaha/yamaha.types.js +8 -94
- package/dist_ts/integrations/yamaha/yamaha.validation.d.ts +12 -0
- package/dist_ts/integrations/yamaha/yamaha.validation.js +89 -0
- package/package.json +3 -3
- package/readme.md +514 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/classes.configstore.ts +84 -27
- package/ts/core/classes.nativehomeadmission.ts +96 -0
- package/ts/core/classes.nativeintegrationowner.ts +297 -0
- package/ts/core/index.ts +2 -0
- package/ts/core/types.ts +16 -0
- package/ts/integrations/braviatv/braviatv.classes.client.ts +245 -709
- package/ts/integrations/braviatv/braviatv.classes.configflow.ts +47 -76
- package/ts/integrations/braviatv/braviatv.classes.integration.ts +167 -147
- package/ts/integrations/braviatv/braviatv.discovery.ts +11 -10
- package/ts/integrations/braviatv/braviatv.mapper.ts +39 -142
- package/ts/integrations/braviatv/braviatv.types.ts +53 -173
- package/ts/integrations/braviatv/braviatv.validation.ts +68 -0
- package/ts/integrations/broadlink/broadlink.actions.ts +31 -0
- package/ts/integrations/broadlink/broadlink.classes.client.ts +170 -144
- package/ts/integrations/broadlink/broadlink.classes.configflow.ts +59 -89
- package/ts/integrations/broadlink/broadlink.classes.integration.ts +110 -64
- package/ts/integrations/broadlink/broadlink.mapper.ts +5 -4
- package/ts/integrations/broadlink/broadlink.selected.mapper.ts +46 -0
- package/ts/integrations/broadlink/broadlink.types.ts +48 -8
- package/ts/integrations/broadlink/broadlink.validation.ts +66 -0
- package/ts/integrations/daikin/daikin.classes.client.ts +52 -661
- package/ts/integrations/daikin/daikin.classes.configflow.ts +72 -152
- package/ts/integrations/daikin/daikin.classes.integration.ts +166 -71
- package/ts/integrations/daikin/daikin.discovery.ts +94 -346
- package/ts/integrations/daikin/daikin.mapper.ts +83 -166
- package/ts/integrations/daikin/daikin.types.ts +27 -48
- package/ts/integrations/daikin/daikin.validation.ts +51 -0
- package/ts/integrations/daikin/readme.md +83 -0
- package/ts/integrations/denonavr/denonavr.classes.client.ts +132 -421
- package/ts/integrations/denonavr/denonavr.classes.configflow.ts +44 -37
- package/ts/integrations/denonavr/denonavr.classes.integration.ts +171 -117
- package/ts/integrations/denonavr/denonavr.discovery.ts +67 -220
- package/ts/integrations/denonavr/denonavr.mapper.ts +36 -182
- package/ts/integrations/denonavr/denonavr.types.ts +48 -111
- package/ts/integrations/denonavr/denonavr.validation.ts +87 -0
- package/ts/integrations/kodi/kodi.classes.client.ts +72 -352
- package/ts/integrations/kodi/kodi.classes.configflow.ts +52 -55
- package/ts/integrations/kodi/kodi.classes.integration.ts +97 -178
- package/ts/integrations/kodi/kodi.discovery.ts +49 -28
- package/ts/integrations/kodi/kodi.mapper.ts +52 -10
- package/ts/integrations/kodi/kodi.types.ts +33 -58
- package/ts/integrations/kodi/kodi.validation.ts +60 -0
- package/ts/integrations/modbus/modbus.classes.client.ts +100 -842
- package/ts/integrations/modbus/modbus.classes.configflow.ts +70 -41
- package/ts/integrations/modbus/modbus.classes.integration.ts +115 -60
- package/ts/integrations/modbus/modbus.discovery.ts +38 -106
- package/ts/integrations/modbus/modbus.mapper.ts +64 -467
- package/ts/integrations/modbus/modbus.types.ts +64 -381
- package/ts/integrations/modbus/modbus.validation.ts +119 -0
- package/ts/integrations/nanoleaf/nanoleaf.classes.client.ts +115 -249
- package/ts/integrations/nanoleaf/nanoleaf.classes.configflow.ts +90 -19
- package/ts/integrations/nanoleaf/nanoleaf.classes.integration.ts +130 -181
- package/ts/integrations/nanoleaf/nanoleaf.credentials.ts +39 -0
- package/ts/integrations/nanoleaf/nanoleaf.mapper.ts +47 -260
- package/ts/integrations/nanoleaf/nanoleaf.types.ts +29 -147
- package/ts/integrations/nanoleaf/nanoleaf.validation.ts +77 -0
- package/ts/integrations/onkyo/onkyo.classes.client.ts +148 -457
- package/ts/integrations/onkyo/onkyo.classes.configflow.ts +52 -6
- package/ts/integrations/onkyo/onkyo.classes.integration.ts +121 -12
- package/ts/integrations/onkyo/onkyo.discovery.ts +41 -3
- package/ts/integrations/onkyo/onkyo.mapper.ts +46 -19
- package/ts/integrations/onkyo/onkyo.types.ts +55 -110
- package/ts/integrations/onkyo/onkyo.validation.ts +85 -0
- package/ts/integrations/onkyo/readme.md +126 -0
- package/ts/integrations/panasonic_viera/panasonic_viera.classes.client.ts +101 -398
- package/ts/integrations/panasonic_viera/panasonic_viera.classes.configflow.ts +69 -5
- package/ts/integrations/panasonic_viera/panasonic_viera.classes.integration.ts +141 -9
- package/ts/integrations/panasonic_viera/panasonic_viera.discovery.ts +32 -3
- package/ts/integrations/panasonic_viera/panasonic_viera.mapper.ts +34 -161
- package/ts/integrations/panasonic_viera/panasonic_viera.types.ts +52 -96
- package/ts/integrations/panasonic_viera/panasonic_viera.validation.ts +56 -0
- package/ts/integrations/pioneer/pioneer.classes.client.ts +132 -428
- package/ts/integrations/pioneer/pioneer.classes.configflow.ts +67 -5
- package/ts/integrations/pioneer/pioneer.classes.integration.ts +225 -11
- package/ts/integrations/pioneer/pioneer.discovery.ts +45 -3
- package/ts/integrations/pioneer/pioneer.mapper.ts +57 -13
- package/ts/integrations/pioneer/pioneer.types.ts +87 -85
- package/ts/integrations/pioneer/pioneer.validation.ts +84 -0
- package/ts/integrations/soundtouch/soundtouch.classes.client.ts +152 -571
- package/ts/integrations/soundtouch/soundtouch.classes.configflow.ts +47 -42
- package/ts/integrations/soundtouch/soundtouch.classes.integration.ts +95 -150
- package/ts/integrations/soundtouch/soundtouch.discovery.ts +21 -133
- package/ts/integrations/soundtouch/soundtouch.mapper.ts +36 -207
- package/ts/integrations/soundtouch/soundtouch.types.ts +50 -195
- package/ts/integrations/soundtouch/soundtouch.validation.ts +60 -0
- package/ts/integrations/wiz/index.ts +1 -0
- package/ts/integrations/wiz/wiz.classes.client.ts +84 -254
- package/ts/integrations/wiz/wiz.classes.configflow.ts +39 -70
- package/ts/integrations/wiz/wiz.classes.integration.ts +189 -45
- package/ts/integrations/wiz/wiz.discovery.ts +48 -291
- package/ts/integrations/wiz/wiz.mapper.ts +81 -754
- package/ts/integrations/wiz/wiz.types.ts +24 -224
- package/ts/integrations/wiz/wiz.validation.ts +73 -0
- package/ts/integrations/yamaha/index.ts +1 -0
- package/ts/integrations/yamaha/yamaha.classes.client.ts +150 -6
- package/ts/integrations/yamaha/yamaha.classes.configflow.ts +100 -5
- package/ts/integrations/yamaha/yamaha.classes.integration.ts +236 -8
- package/ts/integrations/yamaha/yamaha.discovery.ts +52 -3
- package/ts/integrations/yamaha/yamaha.mapper.ts +65 -13
- package/ts/integrations/yamaha/yamaha.types.ts +77 -97
- package/ts/integrations/yamaha/yamaha.validation.ts +86 -0
|
@@ -1,52 +1,81 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
|
|
2
|
+
import type { IConfigFlow, IConfigFlowStep, IDiscoveryCandidate, INativeConfigFlowContext } from '../../core/types.js';
|
|
3
|
+
import { ModbusClient } from './modbus.classes.client.js';
|
|
4
|
+
import { ModbusMapper } from './modbus.mapper.js';
|
|
5
|
+
import type { IModbusConfig, IModbusIntegrationOptions } from './modbus.types.js';
|
|
6
|
+
import { modbusRecord, normalizeModbusConfig } from './modbus.validation.js';
|
|
5
7
|
|
|
6
8
|
export class ModbusConfigFlow implements IConfigFlow<IModbusConfig> {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
readonly #scope: NativeIntegrationScope;
|
|
10
|
+
|
|
11
|
+
constructor(optionsArg: IModbusIntegrationOptions = {}, scopeArg?: NativeIntegrationScope) {
|
|
12
|
+
this.#scope = scopeArg ?? new NativeIntegrationOwner('modbus', optionsArg).createFlowScope();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async start(candidateArg: IDiscoveryCandidate, contextArg: INativeConfigFlowContext): Promise<IConfigFlowStep<IModbusConfig>> {
|
|
16
|
+
const candidate = modbusRecord(candidateArg);
|
|
17
|
+
if (candidate.source !== 'manual' || candidate.integrationDomain !== undefined && candidate.integrationDomain !== 'modbus') {
|
|
18
|
+
throw new TypeError('Modbus config admission accepts explicit manual candidates only.');
|
|
19
|
+
}
|
|
20
|
+
const metadata = candidate.metadata === undefined ? {} : modbusRecord(candidate.metadata);
|
|
21
|
+
const context = this.#scope.admit(contextArg);
|
|
9
22
|
return {
|
|
10
|
-
kind: 'form',
|
|
11
|
-
|
|
12
|
-
description: 'Configure a known Modbus TCP endpoint. Serial RTU ports are intentionally not guessed or probed.',
|
|
23
|
+
kind: 'form', title: 'Select Modbus TCP device',
|
|
24
|
+
description: 'Select the private IPv4 endpoint, unit, vendor and product for the host-selected home. Supply an explicit JSON array of zero-based raw read mappings; writes require single-address permission. Identity strings are unauthenticated LAN observations.',
|
|
13
25
|
fields: [
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
16
|
-
{ name: '
|
|
17
|
-
{ name: '
|
|
18
|
-
{ name: '
|
|
26
|
+
{ name: 'address', label: 'Selected local IPv4 address', type: 'text', required: true },
|
|
27
|
+
{ name: 'port', label: 'TCP port', type: 'number' },
|
|
28
|
+
{ name: 'unitId', label: 'Selected unit ID', type: 'number', required: true },
|
|
29
|
+
{ name: 'expectedVendorName', label: 'Exact vendor name', type: 'text', required: true },
|
|
30
|
+
{ name: 'expectedProductCode', label: 'Exact product code', type: 'text', required: true },
|
|
31
|
+
{ name: 'expectedRevision', label: 'Optional exact revision', type: 'text' },
|
|
32
|
+
{ name: 'allowUnencryptedLan', label: 'Accept unencrypted LAN transport', type: 'boolean', required: true },
|
|
33
|
+
{ name: 'mappings', label: 'Owner raw mappings JSON', type: 'text', required: true },
|
|
34
|
+
{ name: 'name', label: 'Display name', type: 'text' },
|
|
19
35
|
],
|
|
20
|
-
submit:
|
|
21
|
-
const unitId = this.numberValue(valuesArg.unitId) || this.numberValue(candidateArg.metadata?.unitId) || 1;
|
|
22
|
-
const port = this.numberValue(valuesArg.port) || candidateArg.port || defaultModbusTcpPort;
|
|
23
|
-
return {
|
|
24
|
-
kind: 'done',
|
|
25
|
-
title: 'Modbus TCP hub configured',
|
|
26
|
-
config: {
|
|
27
|
-
hubs: [{
|
|
28
|
-
id: candidateArg.id,
|
|
29
|
-
name: this.stringValue(valuesArg.name) || candidateArg.name || 'modbus_hub',
|
|
30
|
-
type: 'tcp',
|
|
31
|
-
host: this.stringValue(valuesArg.host) || candidateArg.host || '',
|
|
32
|
-
port,
|
|
33
|
-
timeoutMs: this.numberValue(valuesArg.timeoutMs) || 5000,
|
|
34
|
-
slave: unitId,
|
|
35
|
-
manufacturer: candidateArg.manufacturer,
|
|
36
|
-
model: candidateArg.model,
|
|
37
|
-
serialNumber: candidateArg.serialNumber,
|
|
38
|
-
}],
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
},
|
|
36
|
+
submit: (valuesArg) => this.submit(valuesArg, candidate, metadata, context),
|
|
42
37
|
};
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
40
|
+
public destroy(): Promise<void> { return this.#scope.close(); }
|
|
41
|
+
public retryCleanup(): Promise<void> { return this.#scope.retryCleanup(); }
|
|
48
42
|
|
|
49
|
-
private
|
|
50
|
-
|
|
43
|
+
private async submit(valuesArg: Record<string, unknown>, candidateArg: Record<string, unknown>, metadataArg: Record<string, unknown>,
|
|
44
|
+
contextArg: INativeConfigFlowContext): Promise<IConfigFlowStep<IModbusConfig>> {
|
|
45
|
+
try {
|
|
46
|
+
const values = modbusRecord(valuesArg, ['homeId', 'address', 'port', 'unitId', 'expectedVendorName', 'expectedProductCode', 'expectedRevision',
|
|
47
|
+
'allowUnencryptedLan', 'mappings', 'name', 'localAddress', 'timeoutMs', 'maxQueuedOperations']);
|
|
48
|
+
if (values.homeId !== undefined && values.homeId !== contextArg.homeId) throw new Error('Modbus submitted home conflicts with the host-selected home.');
|
|
49
|
+
let mappings: unknown = values.mappings;
|
|
50
|
+
if (typeof mappings === 'string') {
|
|
51
|
+
if (mappings.length > 32768) throw new TypeError('Modbus mapping JSON exceeds its size bound.');
|
|
52
|
+
mappings = JSON.parse(mappings);
|
|
53
|
+
}
|
|
54
|
+
const config = normalizeModbusConfig({ ...values, homeId: contextArg.homeId, mappings,
|
|
55
|
+
address: values.address === undefined ? candidateArg.host : values.address,
|
|
56
|
+
port: values.port === undefined ? candidateArg.port : values.port,
|
|
57
|
+
unitId: values.unitId === undefined ? metadataArg.unitId : values.unitId,
|
|
58
|
+
expectedVendorName: values.expectedVendorName === undefined ? metadataArg.expectedVendorName : values.expectedVendorName,
|
|
59
|
+
expectedProductCode: values.expectedProductCode === undefined ? metadataArg.expectedProductCode : values.expectedProductCode,
|
|
60
|
+
expectedRevision: values.expectedRevision === undefined ? metadataArg.expectedRevision : values.expectedRevision });
|
|
61
|
+
return await this.#scope.run(contextArg, config.homeId, async (leaseArg) => {
|
|
62
|
+
const client = new ModbusClient(config, leaseArg.signal);
|
|
63
|
+
let cleanupAttempted = false;
|
|
64
|
+
leaseArg.own(() => {
|
|
65
|
+
if (cleanupAttempted) return client.retryCleanup();
|
|
66
|
+
cleanupAttempted = true;
|
|
67
|
+
return client.destroy();
|
|
68
|
+
});
|
|
69
|
+
await client.getSnapshot(leaseArg.signal);
|
|
70
|
+
leaseArg.assertOpen();
|
|
71
|
+
await client.destroy();
|
|
72
|
+
leaseArg.assertOpen();
|
|
73
|
+
if (leaseArg.context.configStore) await leaseArg.context.configStore.set(`device:${ModbusMapper.deviceId(config)}`, config);
|
|
74
|
+
leaseArg.assertOpen();
|
|
75
|
+
return { kind: 'done', title: 'Selected Modbus device and read mappings verified', config };
|
|
76
|
+
});
|
|
77
|
+
} catch (errorArg) {
|
|
78
|
+
return { kind: 'error', title: 'Modbus admission failed', error: errorArg instanceof Error ? errorArg.message : 'Modbus admission failed.' };
|
|
79
|
+
}
|
|
51
80
|
}
|
|
52
81
|
}
|
|
@@ -1,96 +1,151 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { BaseIntegration } from '../../core/classes.baseintegration.js';
|
|
3
|
-
import
|
|
3
|
+
import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
|
|
4
|
+
import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, INativeIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
|
|
4
5
|
import { ModbusClient } from './modbus.classes.client.js';
|
|
5
6
|
import { ModbusConfigFlow } from './modbus.classes.configflow.js';
|
|
6
7
|
import { createModbusDiscoveryDescriptor } from './modbus.discovery.js';
|
|
7
8
|
import { ModbusMapper } from './modbus.mapper.js';
|
|
8
|
-
import type { IModbusConfig } from './modbus.types.js';
|
|
9
|
+
import type { IModbusConfig, IModbusIntegrationOptions, IModbusSnapshot, TModbusCommand } from './modbus.types.js';
|
|
10
|
+
import { normalizeModbusConfig } from './modbus.validation.js';
|
|
9
11
|
|
|
10
12
|
export class ModbusIntegration extends BaseIntegration<IModbusConfig> {
|
|
11
13
|
public readonly domain = 'modbus';
|
|
12
14
|
public readonly displayName = 'Modbus';
|
|
13
15
|
public readonly status = 'control-runtime' as const;
|
|
14
16
|
public readonly discoveryDescriptor = createModbusDiscoveryDescriptor();
|
|
15
|
-
public readonly configFlow
|
|
17
|
+
public readonly configFlow: ModbusConfigFlow;
|
|
16
18
|
public readonly metadata = {
|
|
17
|
-
source: '
|
|
18
|
-
|
|
19
|
-
upstreamDomain: 'modbus',
|
|
20
|
-
iotClass: 'local_polling',
|
|
21
|
-
requirements: ['pymodbus==3.11.2'],
|
|
22
|
-
dependencies: [],
|
|
23
|
-
afterDependencies: [],
|
|
24
|
-
codeowners: [],
|
|
25
|
-
documentation: 'https://www.home-assistant.io/integrations/modbus',
|
|
26
|
-
configFlow: true,
|
|
27
|
-
discovery: {
|
|
28
|
-
manual: true,
|
|
29
|
-
tcp: true,
|
|
30
|
-
serial: false,
|
|
31
|
-
note: 'Manual Modbus TCP setup is implemented. Serial RTU ports are not guessed or probed.',
|
|
32
|
-
},
|
|
33
|
-
runtime: {
|
|
34
|
-
type: 'control-runtime',
|
|
35
|
-
polling: 'local snapshot or explicit Modbus TCP requests',
|
|
36
|
-
services: ['read_coil', 'read_register', 'write_coil', 'write_register', 'stop', 'restart', 'refresh'],
|
|
37
|
-
},
|
|
19
|
+
source: 'native', upstreamDomain: 'modbus',
|
|
20
|
+
runtime: { type: 'control-runtime', polling: 'explicit owner reads', services: ['read', 'write_coil', 'write_register'] },
|
|
38
21
|
localApi: {
|
|
39
|
-
implemented: [
|
|
40
|
-
'
|
|
41
|
-
|
|
42
|
-
'
|
|
43
|
-
'register/coils mapping to sensor, binary_sensor, switch, and number entities',
|
|
44
|
-
],
|
|
45
|
-
explicitUnsupported: [
|
|
46
|
-
'unsafe serial RTU discovery or serial port guessing',
|
|
47
|
-
'serial RTU runtime transport',
|
|
48
|
-
'RTU-over-TCP framer runtime transport',
|
|
49
|
-
'UDP runtime transport',
|
|
50
|
-
'climate, cover, fan, and light platform behavior beyond configured register/coil entities',
|
|
51
|
-
],
|
|
22
|
+
implemented: ['selected home/IPv4/unit/vendor/product', 'fresh basic identity on every operation', 'explicit raw bit/register read mappings',
|
|
23
|
+
'default-deny single absolute FC5/6 writes', 'bounded devicemanager admission and terminal cleanup'],
|
|
24
|
+
explicitUnsupported: ['automatic discovery', 'serial RTU', 'UDP', 'RTU-over-TCP', 'register-map inference', 'scaling or semantic device capabilities',
|
|
25
|
+
'multi-address writes', 'toggles', 'automatic retries or replay', 'cryptographic device authentication', 'physical effect confirmation'],
|
|
52
26
|
},
|
|
53
27
|
};
|
|
28
|
+
readonly #owner: NativeIntegrationOwner;
|
|
54
29
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
30
|
+
constructor(optionsArg: IModbusIntegrationOptions = {}) {
|
|
31
|
+
super();
|
|
32
|
+
this.#owner = new NativeIntegrationOwner(this.domain, optionsArg);
|
|
33
|
+
this.configFlow = new ModbusConfigFlow({}, this.#owner.createFlowScope());
|
|
58
34
|
}
|
|
59
35
|
|
|
60
|
-
public
|
|
36
|
+
public setup(configArg: IModbusConfig, contextArg: INativeIntegrationSetupContext): Promise<IIntegrationRuntime> {
|
|
37
|
+
try {
|
|
38
|
+
const config = normalizeModbusConfig(configArg);
|
|
39
|
+
return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
|
|
40
|
+
const client = new ModbusClient(config, leaseArg.signal);
|
|
41
|
+
let cleanupAttempted = false;
|
|
42
|
+
leaseArg.own(() => {
|
|
43
|
+
if (cleanupAttempted) return client.retryCleanup();
|
|
44
|
+
cleanupAttempted = true;
|
|
45
|
+
return client.destroy();
|
|
46
|
+
});
|
|
47
|
+
const snapshot = await client.getSnapshot(leaseArg.signal);
|
|
48
|
+
leaseArg.assertOpen();
|
|
49
|
+
const runtime = new ModbusRuntime(client, snapshot, leaseArg);
|
|
50
|
+
leaseArg.promoteToRuntime();
|
|
51
|
+
return runtime;
|
|
52
|
+
});
|
|
53
|
+
} catch (errorArg) { return Promise.reject(errorArg); }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public destroy(): Promise<void> { return this.#owner.close(); }
|
|
57
|
+
public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
export class HomeAssistantModbusIntegration extends ModbusIntegration {}
|
|
64
61
|
|
|
65
62
|
class ModbusRuntime implements IIntegrationRuntime {
|
|
66
|
-
public domain = 'modbus';
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
public readonly domain = 'modbus';
|
|
64
|
+
readonly #subscriptions = new Set<{ handler: (event: IIntegrationEvent) => void; release: () => void }>();
|
|
65
|
+
readonly #ownerAbort = (): void => { void this.destroy().then(() => undefined, () => undefined); };
|
|
66
|
+
#closed = false;
|
|
67
|
+
#closing?: Promise<void>;
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
constructor(private readonly client: ModbusClient, private snapshot: IModbusSnapshot, private readonly lease: INativeSetupLease) {
|
|
70
|
+
lease.signal.addEventListener('abort', this.#ownerAbort, { once: true });
|
|
71
|
+
if (lease.signal.aborted) this.#ownerAbort();
|
|
72
72
|
}
|
|
73
|
+
public isHealthy(): boolean { return !this.#closed && this.client.isHealthy(); }
|
|
73
74
|
|
|
74
|
-
public async
|
|
75
|
-
return ModbusMapper.
|
|
75
|
+
public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
|
|
76
|
+
return ModbusMapper.toDevices(await this.refresh(signalArg));
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
public async
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
public async entities(): Promise<IIntegrationEntity[]> { return ModbusMapper.toEntities(await this.refresh()); }
|
|
80
|
+
|
|
81
|
+
/** Passive observations only: subscribing never starts reads or a polling timer. */
|
|
82
|
+
public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
|
|
83
|
+
this.assertOpen();
|
|
84
|
+
if (typeof handlerArg !== 'function') throw new TypeError('Modbus subscription handler must be a function.');
|
|
85
|
+
if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Modbus subscription signal must be an AbortSignal.');
|
|
86
|
+
signalArg?.throwIfAborted();
|
|
87
|
+
const entry = { handler: handlerArg, release: (): void => { this.#subscriptions.delete(entry); signalArg?.removeEventListener('abort', entry.release); } };
|
|
88
|
+
this.#subscriptions.add(entry);
|
|
89
|
+
signalArg?.addEventListener('abort', entry.release, { once: true });
|
|
90
|
+
if (signalArg?.aborted) entry.release();
|
|
91
|
+
return async () => entry.release();
|
|
81
92
|
}
|
|
82
93
|
|
|
83
94
|
public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
try { this.assertOpen(); return await this.execute(ModbusMapper.commandForService(this.client.config, requestArg)); }
|
|
96
|
+
catch (errorArg) { return this.failure(errorArg); }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
|
|
100
|
+
try { this.assertOpen(); return await this.execute(ModbusMapper.commandForFeature(this.client.config, requestArg), signalArg); }
|
|
101
|
+
catch (errorArg) { return this.failure(errorArg); }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public destroy(): Promise<void> {
|
|
105
|
+
if (this.#closing) return this.#closing;
|
|
106
|
+
this.#closed = true;
|
|
107
|
+
this.#closing = Promise.resolve().then(() => this.lease.close());
|
|
108
|
+
this.lease.signal.removeEventListener('abort', this.#ownerAbort);
|
|
109
|
+
for (const entry of [...this.#subscriptions]) entry.release();
|
|
110
|
+
return this.#closing;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async refresh(signalArg?: AbortSignal): Promise<IModbusSnapshot> {
|
|
114
|
+
this.assertOpen();
|
|
115
|
+
const snapshot = await this.client.getSnapshot(signalArg);
|
|
116
|
+
this.assertOpen();
|
|
117
|
+
this.snapshot = snapshot;
|
|
118
|
+
for (const entity of ModbusMapper.toEntities(snapshot)) this.notify({ type: 'state_changed', integrationDomain: 'modbus',
|
|
119
|
+
deviceId: entity.deviceId, entityId: entity.id, timestamp: Date.parse(snapshot.observedAt), data: { entity } });
|
|
120
|
+
return snapshot;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private async execute(commandArg: TModbusCommand, signalArg?: AbortSignal): Promise<IServiceCallResult> {
|
|
124
|
+
const result = await this.client.sendCommand(commandArg, signalArg);
|
|
125
|
+
if ('mapping' in result) {
|
|
126
|
+
this.assertOpen();
|
|
127
|
+
const snapshot = { ...this.snapshot, identity: result.identity, observations: [result], observedAt: result.observedAt };
|
|
128
|
+
const entity = ModbusMapper.toEntities(snapshot)[0];
|
|
129
|
+
this.notify({ type: 'state_changed', integrationDomain: 'modbus', deviceId: entity.deviceId, entityId: entity.id,
|
|
130
|
+
timestamp: Date.parse(result.observedAt), data: { entity } });
|
|
88
131
|
}
|
|
89
|
-
|
|
90
|
-
return { success: result.success, error: result.error, data: result.data };
|
|
132
|
+
return { success: true, data: result };
|
|
91
133
|
}
|
|
92
134
|
|
|
93
|
-
|
|
94
|
-
|
|
135
|
+
private failure(errorArg: unknown): IServiceCallResult {
|
|
136
|
+
return { success: false, error: errorArg instanceof Error ? errorArg.message : 'Modbus operation failed.',
|
|
137
|
+
data: { physicalEffectConfirmed: false, effectOutcome: errorArg instanceof plugins.ecobridgeDeviceManager.ModbusProtocolError ? errorArg.effectOutcome : 'not-sent',
|
|
138
|
+
...(errorArg instanceof plugins.ecobridgeDeviceManager.ModbusProtocolError ? { errorCode: errorArg.code } : {}) } };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private assertOpen(): void {
|
|
142
|
+
if (this.#closed) throw new Error('Modbus runtime is destroyed.');
|
|
143
|
+
this.lease.assertOpen();
|
|
144
|
+
}
|
|
145
|
+
private notify(eventArg: IIntegrationEvent): void {
|
|
146
|
+
for (const entry of [...this.#subscriptions]) {
|
|
147
|
+
if (this.#closed) break;
|
|
148
|
+
entry.handler(eventArg);
|
|
149
|
+
}
|
|
95
150
|
}
|
|
96
151
|
}
|
|
@@ -1,124 +1,56 @@
|
|
|
1
1
|
import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
|
|
2
2
|
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
|
|
3
3
|
import type { IModbusManualTcpEntry } from './modbus.types.js';
|
|
4
|
-
|
|
5
|
-
const modbusDomain = 'modbus';
|
|
6
|
-
const defaultModbusTcpPort = 502;
|
|
4
|
+
import { modbusAddress, modbusIdentityText, modbusInteger, modbusRecord, modbusText, modbusUnit } from './modbus.validation.js';
|
|
7
5
|
|
|
8
6
|
export class ModbusManualTcpMatcher implements IDiscoveryMatcher<IModbusManualTcpEntry> {
|
|
9
|
-
public id = 'modbus-manual-tcp-match';
|
|
10
|
-
public source = 'manual' as const;
|
|
11
|
-
public description = '
|
|
7
|
+
public readonly id = 'modbus-manual-tcp-match';
|
|
8
|
+
public readonly source = 'manual' as const;
|
|
9
|
+
public readonly description = 'Accept explicit Modbus endpoint and family identity selections without network probes.';
|
|
12
10
|
|
|
13
11
|
public async matches(inputArg: IModbusManualTcpEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
12
|
+
contextArg.abortSignal?.throwIfAborted();
|
|
13
|
+
try {
|
|
14
|
+
const input = modbusRecord(inputArg, ['integrationDomain', 'address', 'port', 'unitId', 'expectedVendorName', 'expectedProductCode', 'expectedRevision', 'name']);
|
|
15
|
+
if (input.integrationDomain !== 'modbus') throw new TypeError('Manual Modbus selection requires the exact integration domain.');
|
|
16
|
+
const address = modbusAddress(input.address);
|
|
17
|
+
const port = modbusInteger(input.port === undefined ? 502 : input.port, 1, 65535);
|
|
18
|
+
const unitId = modbusUnit(input.unitId);
|
|
19
|
+
const expectedVendorName = modbusIdentityText(input.expectedVendorName);
|
|
20
|
+
const expectedProductCode = modbusIdentityText(input.expectedProductCode);
|
|
21
|
+
const expectedRevision = input.expectedRevision === undefined ? undefined : modbusIdentityText(input.expectedRevision);
|
|
22
|
+
const id = `modbus:${address}:${port}:${unitId}`;
|
|
23
|
+
return { matched: true, confidence: 'low', reason: 'Explicit manual selection is structurally valid; no network identity verification has occurred.',
|
|
24
|
+
normalizedDeviceId: id, candidate: { source: 'manual', integrationDomain: 'modbus', id, host: address, port,
|
|
25
|
+
...(input.name === undefined ? {} : { name: modbusText(input.name, 'name', 128) }),
|
|
26
|
+
metadata: { unitId, expectedVendorName, expectedProductCode, ...(expectedRevision === undefined ? {} : { expectedRevision }) } } };
|
|
27
|
+
} catch (errorArg) {
|
|
28
|
+
return { matched: false, confidence: 'low', reason: errorArg instanceof Error ? errorArg.message : 'Invalid explicit Modbus selection.' };
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
const id = this.normalizedId(inputArg);
|
|
35
|
-
return {
|
|
36
|
-
matched: true,
|
|
37
|
-
confidence: hasModbusHint ? 'high' : 'medium',
|
|
38
|
-
reason: 'Manual entry can be configured as a Modbus TCP hub.',
|
|
39
|
-
normalizedDeviceId: id,
|
|
40
|
-
candidate: {
|
|
41
|
-
source: 'manual',
|
|
42
|
-
integrationDomain: modbusDomain,
|
|
43
|
-
id,
|
|
44
|
-
host: inputArg.host,
|
|
45
|
-
port: inputArg.port || defaultModbusTcpPort,
|
|
46
|
-
name: inputArg.name,
|
|
47
|
-
manufacturer: inputArg.manufacturer || 'Modbus',
|
|
48
|
-
model: inputArg.model || 'TCP hub',
|
|
49
|
-
serialNumber: inputArg.serialNumber,
|
|
50
|
-
metadata: {
|
|
51
|
-
...inputArg.metadata,
|
|
52
|
-
type: 'tcp',
|
|
53
|
-
unitId: inputArg.unitId || inputArg.slave || 1,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private normalizedId(inputArg: IModbusManualTcpEntry): string {
|
|
60
|
-
return slug(inputArg.id || inputArg.serialNumber || `${inputArg.host || 'modbus'}_${inputArg.port || defaultModbusTcpPort}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private looksLikeSerialPort(valueArg: string | undefined): boolean {
|
|
64
|
-
return Boolean(valueArg && (/^\/dev\//.test(valueArg) || /^com\d+$/i.test(valueArg)));
|
|
65
30
|
}
|
|
66
31
|
}
|
|
67
32
|
|
|
68
33
|
export class ModbusCandidateValidator implements IDiscoveryValidator {
|
|
69
|
-
public id = 'modbus-candidate-validator';
|
|
70
|
-
public description = 'Validate
|
|
34
|
+
public readonly id = 'modbus-candidate-validator';
|
|
35
|
+
public readonly description = 'Validate explicit manual selections; live admission belongs to the config flow.';
|
|
71
36
|
|
|
72
37
|
public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
38
|
+
contextArg.abortSignal?.throwIfAborted();
|
|
39
|
+
try {
|
|
40
|
+
const candidate = modbusRecord(candidateArg);
|
|
41
|
+
if (candidate.source !== 'manual' || candidate.integrationDomain !== 'modbus') throw new TypeError('Modbus accepts explicit manual candidates only.');
|
|
42
|
+
const metadata = modbusRecord(candidate.metadata, ['unitId', 'expectedVendorName', 'expectedProductCode', 'expectedRevision']);
|
|
43
|
+
return new ModbusManualTcpMatcher().matches({ integrationDomain: 'modbus', address: modbusAddress(candidate.host),
|
|
44
|
+
port: candidate.port === undefined ? undefined : modbusInteger(candidate.port, 1, 65535), unitId: modbusUnit(metadata.unitId),
|
|
45
|
+
expectedVendorName: modbusIdentityText(metadata.expectedVendorName), expectedProductCode: modbusIdentityText(metadata.expectedProductCode),
|
|
46
|
+
...(metadata.expectedRevision === undefined ? {} : { expectedRevision: modbusIdentityText(metadata.expectedRevision) }),
|
|
47
|
+
...(candidate.name === undefined ? {} : { name: modbusText(candidate.name, 'name', 128) }) }, contextArg);
|
|
48
|
+
} catch (errorArg) {
|
|
49
|
+
return { matched: false, confidence: 'low', reason: errorArg instanceof Error ? errorArg.message : 'Invalid explicit Modbus candidate.' };
|
|
81
50
|
}
|
|
82
|
-
|
|
83
|
-
const haystack = `${candidateArg.name || ''} ${candidateArg.model || ''} ${candidateArg.manufacturer || ''}`.toLowerCase();
|
|
84
|
-
const matched = Boolean(candidateArg.host && (
|
|
85
|
-
candidateArg.integrationDomain === modbusDomain
|
|
86
|
-
|| candidateArg.port === defaultModbusTcpPort
|
|
87
|
-
|| type === 'tcp'
|
|
88
|
-
|| haystack.includes('modbus')
|
|
89
|
-
|| candidateArg.metadata?.modbus
|
|
90
|
-
|| candidateArg.metadata?.modbusTcp
|
|
91
|
-
));
|
|
92
|
-
const id = slug(candidateArg.id || candidateArg.serialNumber || `${candidateArg.host || 'modbus'}_${candidateArg.port || defaultModbusTcpPort}`);
|
|
93
|
-
return {
|
|
94
|
-
matched,
|
|
95
|
-
confidence: matched && candidateArg.integrationDomain === modbusDomain ? 'high' : matched ? 'medium' : 'low',
|
|
96
|
-
reason: matched ? 'Candidate has Modbus TCP setup data.' : 'Candidate is not a Modbus TCP hub.',
|
|
97
|
-
normalizedDeviceId: matched ? id : undefined,
|
|
98
|
-
candidate: matched ? {
|
|
99
|
-
...candidateArg,
|
|
100
|
-
integrationDomain: modbusDomain,
|
|
101
|
-
id,
|
|
102
|
-
port: candidateArg.port || defaultModbusTcpPort,
|
|
103
|
-
manufacturer: candidateArg.manufacturer || 'Modbus',
|
|
104
|
-
model: candidateArg.model || 'TCP hub',
|
|
105
|
-
metadata: {
|
|
106
|
-
...candidateArg.metadata,
|
|
107
|
-
type: 'tcp',
|
|
108
|
-
},
|
|
109
|
-
} : undefined,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private looksLikeSerialPort(valueArg: string | undefined): boolean {
|
|
114
|
-
return Boolean(valueArg && (/^\/dev\//.test(valueArg) || /^com\d+$/i.test(valueArg)));
|
|
115
51
|
}
|
|
116
52
|
}
|
|
117
53
|
|
|
118
|
-
export const createModbusDiscoveryDescriptor = (): DiscoveryDescriptor =>
|
|
119
|
-
|
|
120
|
-
.addMatcher(new ModbusManualTcpMatcher())
|
|
121
|
-
.addValidator(new ModbusCandidateValidator());
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const slug = (valueArg: string): string => valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || modbusDomain;
|
|
54
|
+
export const createModbusDiscoveryDescriptor = (): DiscoveryDescriptor =>
|
|
55
|
+
new DiscoveryDescriptor({ integrationDomain: 'modbus', displayName: 'Modbus' })
|
|
56
|
+
.addMatcher(new ModbusManualTcpMatcher()).addValidator(new ModbusCandidateValidator());
|