@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,80 +1,224 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { BaseIntegration } from '../../core/classes.baseintegration.js';
|
|
3
|
-
import
|
|
3
|
+
import { ConsoleLogger } from '../../core/classes.logger.js';
|
|
4
|
+
import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
|
|
5
|
+
import type { IDeviceFeatureWriteRequest, ILogger, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime,
|
|
6
|
+
IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
|
|
4
7
|
import { WizClient } from './wiz.classes.client.js';
|
|
5
8
|
import { WizConfigFlow } from './wiz.classes.configflow.js';
|
|
6
9
|
import { createWizDiscoveryDescriptor } from './wiz.discovery.js';
|
|
7
10
|
import { WizMapper } from './wiz.mapper.js';
|
|
8
|
-
import type { IWizConfig } from './wiz.types.js';
|
|
11
|
+
import type { IWizConfig, IWizSnapshot, IWizWriteResult, TWizControl } from './wiz.types.js';
|
|
12
|
+
import { normalizeWizConfig } from './wiz.validation.js';
|
|
13
|
+
|
|
14
|
+
export interface IWizIntegrationOptions {
|
|
15
|
+
readonly homeId?: string;
|
|
16
|
+
readonly capacity?: number;
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
export class WizIntegration extends BaseIntegration<IWizConfig> {
|
|
11
20
|
public readonly domain = 'wiz';
|
|
12
21
|
public readonly displayName = 'WiZ';
|
|
13
22
|
public readonly status = 'control-runtime' as const;
|
|
14
23
|
public readonly discoveryDescriptor = createWizDiscoveryDescriptor();
|
|
15
|
-
public readonly configFlow
|
|
24
|
+
public readonly configFlow: WizConfigFlow;
|
|
16
25
|
public readonly metadata = {
|
|
17
|
-
source: 'home-assistant/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
integrationType: 'device',
|
|
21
|
-
iotClass: 'local_push',
|
|
22
|
-
requirements: ['pywizlight==0.6.3'],
|
|
23
|
-
dependencies: ['network'],
|
|
24
|
-
afterDependencies: [] as string[],
|
|
25
|
-
codeowners: ['@sbidy', '@arturpragacz'],
|
|
26
|
-
documentation: 'https://www.home-assistant.io/integrations/wiz',
|
|
27
|
-
protocolDocumentation: 'https://docs.pro.wizconnected.com/#introduction',
|
|
28
|
-
dhcp: [
|
|
29
|
-
{ registeredDevices: true },
|
|
30
|
-
{ macaddress: 'A8BB50*' },
|
|
31
|
-
{ macaddress: 'D8A011*' },
|
|
32
|
-
{ macaddress: '444F8E*' },
|
|
33
|
-
{ macaddress: '6C2990*' },
|
|
34
|
-
{ hostname: 'wiz_*' },
|
|
35
|
-
],
|
|
26
|
+
source: '@ecobridge.xyz/devicemanager', documentation: 'https://www.home-assistant.io/integrations/wiz',
|
|
27
|
+
integrationType: 'device', iotClass: 'local_polling', networkAuthenticated: false,
|
|
28
|
+
supportedControls: ['explicit power', 'observed brightness', 'observed RGB'],
|
|
36
29
|
};
|
|
30
|
+
readonly #owner: NativeIntegrationOwner;
|
|
31
|
+
|
|
32
|
+
constructor(optionsArg: IWizIntegrationOptions = {}) {
|
|
33
|
+
super();
|
|
34
|
+
this.#owner = new NativeIntegrationOwner('wiz', optionsArg);
|
|
35
|
+
this.configFlow = new WizConfigFlow(this.#owner);
|
|
36
|
+
}
|
|
37
37
|
|
|
38
38
|
public async setup(configArg: IWizConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
|
|
39
|
-
|
|
40
|
-
return
|
|
39
|
+
const config = normalizeWizConfig(configArg);
|
|
40
|
+
return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
|
|
41
|
+
const client = new WizClient(config, { ...leaseArg.context, abortSignal: leaseArg.signal });
|
|
42
|
+
let runtime: WizRuntime | undefined;
|
|
43
|
+
let cleanupAttempts = 0;
|
|
44
|
+
const closeClient = (): Promise<void> => cleanupAttempts++ === 0 ? client.destroy() : client.retryCleanup();
|
|
45
|
+
leaseArg.own(() => runtime ? runtime.cleanup() : closeClient());
|
|
46
|
+
const snapshot = await client.getSnapshot(leaseArg.signal);
|
|
47
|
+
leaseArg.assertOpen();
|
|
48
|
+
runtime = new WizRuntime(client, snapshot, leaseArg.context.logger, leaseArg, closeClient);
|
|
49
|
+
leaseArg.promoteToRuntime();
|
|
50
|
+
return runtime;
|
|
51
|
+
});
|
|
41
52
|
}
|
|
42
53
|
|
|
43
|
-
public
|
|
54
|
+
public destroy(): Promise<void> { return this.#owner.close(); }
|
|
55
|
+
public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
export class HomeAssistantWizIntegration extends WizIntegration {}
|
|
47
59
|
|
|
60
|
+
interface IWizSubscription {
|
|
61
|
+
handler(eventArg: IIntegrationEvent): void;
|
|
62
|
+
release(): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
class WizRuntime implements IIntegrationRuntime {
|
|
49
|
-
public domain = 'wiz';
|
|
66
|
+
public readonly domain = 'wiz';
|
|
67
|
+
readonly #subscriptions = new Set<IWizSubscription>();
|
|
68
|
+
readonly #client: WizClient;
|
|
69
|
+
readonly #logger: ILogger;
|
|
70
|
+
readonly #lease: INativeSetupLease;
|
|
71
|
+
readonly #closeClient: () => Promise<void>;
|
|
72
|
+
readonly #ownerAbort = (): void => { this.markClosed(); };
|
|
73
|
+
#snapshot: IWizSnapshot;
|
|
74
|
+
#timer?: ReturnType<typeof setTimeout>;
|
|
75
|
+
#poll?: Promise<void>;
|
|
76
|
+
#pollController?: AbortController;
|
|
77
|
+
#closed = false;
|
|
50
78
|
|
|
51
|
-
constructor(
|
|
79
|
+
constructor(clientArg: WizClient, snapshotArg: IWizSnapshot, loggerArg: ILogger | undefined, leaseArg: INativeSetupLease, closeClientArg: () => Promise<void>) {
|
|
80
|
+
this.#client = clientArg; this.#snapshot = snapshotArg; this.#logger = loggerArg ?? new ConsoleLogger();
|
|
81
|
+
this.#lease = leaseArg; this.#closeClient = closeClientArg;
|
|
82
|
+
leaseArg.signal.addEventListener('abort', this.#ownerAbort, { once: true });
|
|
83
|
+
if (leaseArg.signal.aborted) this.#ownerAbort();
|
|
84
|
+
}
|
|
52
85
|
|
|
53
|
-
public
|
|
54
|
-
|
|
86
|
+
public isHealthy(): boolean { return !this.#closed && this.#client.isHealthy(); }
|
|
87
|
+
public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> { return WizMapper.toDevices(await this.refresh(signalArg)); }
|
|
88
|
+
public async entities(): Promise<IIntegrationEntity[]> { return WizMapper.toEntities(await this.refresh()); }
|
|
89
|
+
|
|
90
|
+
public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
|
|
91
|
+
if (this.#closed) throw new Error('WiZ runtime is destroyed.');
|
|
92
|
+
if (typeof handlerArg !== 'function') throw new TypeError('WiZ subscription requires a handler.');
|
|
93
|
+
signalArg?.throwIfAborted();
|
|
94
|
+
const subscription: IWizSubscription = {
|
|
95
|
+
handler: handlerArg,
|
|
96
|
+
release: () => {
|
|
97
|
+
this.#subscriptions.delete(subscription);
|
|
98
|
+
signalArg?.removeEventListener('abort', subscription.release);
|
|
99
|
+
if (!this.#subscriptions.size) this.stopPolling();
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const snapshot = await this.#client.getSnapshot(signalArg);
|
|
104
|
+
if (this.#closed) throw new Error('WiZ runtime is destroyed.');
|
|
105
|
+
signalArg?.throwIfAborted();
|
|
106
|
+
this.#snapshot = snapshot;
|
|
107
|
+
handlerArg(this.observation(snapshot));
|
|
108
|
+
if (this.#closed) throw new Error('WiZ runtime was destroyed during initial subscription delivery.');
|
|
109
|
+
signalArg?.throwIfAborted();
|
|
110
|
+
this.#subscriptions.add(subscription);
|
|
111
|
+
signalArg?.addEventListener('abort', subscription.release, { once: true });
|
|
112
|
+
if (signalArg?.aborted) subscription.release();
|
|
113
|
+
this.schedulePolling();
|
|
114
|
+
} catch (errorArg) { subscription.release(); throw errorArg; }
|
|
115
|
+
return async () => {
|
|
116
|
+
subscription.release();
|
|
117
|
+
if (!this.#subscriptions.size) await this.#poll;
|
|
118
|
+
};
|
|
55
119
|
}
|
|
56
120
|
|
|
57
|
-
public async
|
|
58
|
-
|
|
121
|
+
public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
|
|
122
|
+
let control: TWizControl;
|
|
123
|
+
try { control = WizMapper.controlForService(this.#snapshot, requestArg); }
|
|
124
|
+
catch (errorArg) { return this.admissionFailure(errorArg); }
|
|
125
|
+
return this.write(control);
|
|
59
126
|
}
|
|
60
127
|
|
|
61
|
-
public async
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
128
|
+
public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
|
|
129
|
+
let control: TWizControl;
|
|
130
|
+
try { control = WizMapper.controlForFeature(this.#snapshot, requestArg); }
|
|
131
|
+
catch (errorArg) { return this.admissionFailure(errorArg); }
|
|
132
|
+
return this.write(control, signalArg);
|
|
65
133
|
}
|
|
66
134
|
|
|
67
|
-
public
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
135
|
+
public destroy(): Promise<void> {
|
|
136
|
+
this.markClosed();
|
|
137
|
+
return this.#lease.close();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Called only by the owning lease, including explicit retained-cleanup retry. */
|
|
141
|
+
public async cleanup(): Promise<void> {
|
|
142
|
+
this.markClosed();
|
|
143
|
+
const results = await Promise.allSettled([Promise.resolve().then(this.#closeClient), this.#poll]);
|
|
144
|
+
const errors = results.flatMap((resultArg) => resultArg.status === 'rejected' ? [resultArg.reason] : []);
|
|
145
|
+
if (errors.length) throw new AggregateError(errors, 'WiZ runtime cleanup failed after draining polling.');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private markClosed(): void {
|
|
149
|
+
if (this.#closed) return;
|
|
150
|
+
this.#closed = true;
|
|
151
|
+
this.#lease.signal.removeEventListener('abort', this.#ownerAbort);
|
|
152
|
+
for (const subscription of [...this.#subscriptions]) subscription.release();
|
|
153
|
+
this.stopPolling();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async refresh(signalArg?: AbortSignal): Promise<IWizSnapshot> {
|
|
157
|
+
if (this.#closed) throw new Error('WiZ runtime is destroyed.');
|
|
158
|
+
const snapshot = await this.#client.getSnapshot(signalArg);
|
|
159
|
+
if (this.#closed) throw new Error('WiZ runtime is destroyed.');
|
|
160
|
+
this.#snapshot = snapshot;
|
|
161
|
+
this.notify(this.observation(snapshot));
|
|
162
|
+
return snapshot;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async write(controlArg: TWizControl, signalArg?: AbortSignal): Promise<IServiceCallResult> {
|
|
166
|
+
if (this.#closed) return this.admissionFailure(new Error('WiZ runtime is destroyed.'));
|
|
167
|
+
const data = await this.#client.write(controlArg, signalArg);
|
|
168
|
+
if (data.observedState && !this.#closed) {
|
|
169
|
+
this.#snapshot = Object.freeze({ ...this.#snapshot, observedAt: new Date().toISOString(),
|
|
170
|
+
info: Object.freeze({ ...this.#snapshot.info, observedState: data.observedState }) });
|
|
171
|
+
this.notify(this.observation(this.#snapshot));
|
|
72
172
|
}
|
|
73
|
-
|
|
74
|
-
|
|
173
|
+
return this.writeResult(data);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private writeResult(dataArg: IWizWriteResult): IServiceCallResult {
|
|
177
|
+
return { success: dataArg.outcome === 'accepted' && dataArg.errorCode === undefined, data: dataArg,
|
|
178
|
+
error: dataArg.outcome === 'accepted' && dataArg.errorCode === undefined ? undefined : dataArg.outcome === 'accepted' ?
|
|
179
|
+
'WiZ firmware accepted the operation, but observed readback failed; do not replay it.' :
|
|
180
|
+
`WiZ operation outcome is ${dataArg.outcome}; automatic replay is prohibited.` };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private admissionFailure(errorArg: unknown): IServiceCallResult {
|
|
184
|
+
return { success: false, error: errorArg instanceof Error ? errorArg.message : 'WiZ admission failed.',
|
|
185
|
+
data: { outcome: 'not-sent', physicalStateConfirmed: false } };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private observation(snapshotArg: IWizSnapshot): IIntegrationEvent {
|
|
189
|
+
return { type: 'state_changed', integrationDomain: 'wiz', deviceId: WizMapper.deviceId(snapshotArg),
|
|
190
|
+
entityId: WizMapper.entityId(snapshotArg), timestamp: Date.parse(snapshotArg.observedAt),
|
|
191
|
+
data: { device: WizMapper.toDevices(snapshotArg)[0], entity: WizMapper.toEntities(snapshotArg)[0] } };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private notify(eventArg: IIntegrationEvent): void {
|
|
195
|
+
for (const subscription of [...this.#subscriptions]) {
|
|
196
|
+
try { subscription.handler(eventArg); }
|
|
197
|
+
catch { subscription.release(); this.#logger.log('error', 'WiZ subscription handler failed and was removed.'); }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private schedulePolling(): void {
|
|
202
|
+
if (this.#closed || this.#timer || this.#poll || !this.#subscriptions.size || !this.#client.pollIntervalMs) return;
|
|
203
|
+
this.#timer = setTimeout(() => {
|
|
204
|
+
this.#timer = undefined;
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
this.#pollController = controller;
|
|
207
|
+
this.#poll = this.refresh(controller.signal).then(() => undefined).catch((errorArg: unknown) => {
|
|
208
|
+
if (controller.signal.aborted || this.#closed) return;
|
|
209
|
+
this.notify({ type: 'availability_changed', integrationDomain: 'wiz', deviceId: WizMapper.deviceId(this.#snapshot),
|
|
210
|
+
timestamp: Date.now(), data: { available: false } });
|
|
211
|
+
this.#logger.log('error', 'WiZ polling did not obtain an authoritative observation.');
|
|
212
|
+
if (errorArg instanceof plugins.ecobridgeDeviceManager.WizProtocolError && errorArg.code === 'EXCHANGE_LIMIT') void this.destroy();
|
|
213
|
+
}).finally(() => {
|
|
214
|
+
this.#poll = undefined; this.#pollController = undefined; this.schedulePolling();
|
|
215
|
+
});
|
|
216
|
+
}, this.#client.pollIntervalMs);
|
|
75
217
|
}
|
|
76
218
|
|
|
77
|
-
|
|
78
|
-
|
|
219
|
+
private stopPolling(): void {
|
|
220
|
+
if (this.#timer) clearTimeout(this.#timer);
|
|
221
|
+
this.#timer = undefined;
|
|
222
|
+
this.#pollController?.abort(new Error('WiZ subscription polling stopped.'));
|
|
79
223
|
}
|
|
80
224
|
}
|
|
@@ -1,314 +1,71 @@
|
|
|
1
1
|
import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export class WizUdpDiscoveryProbe implements IDiscoveryProbe {
|
|
18
|
-
public id = 'wiz-udp-discovery-probe';
|
|
19
|
-
public source = 'custom' as const;
|
|
20
|
-
public description = 'Discover WiZ devices by UDP registration broadcast on port 38899.';
|
|
21
|
-
|
|
22
|
-
public async probe(contextArg: IDiscoveryContext): Promise<IDiscoveryProbeResult> {
|
|
23
|
-
if (contextArg.abortSignal?.aborted) {
|
|
24
|
-
return { candidates: [] };
|
|
25
|
-
}
|
|
26
|
-
return { candidates: await this.discover(1200) };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private async discover(timeoutMsArg: number): Promise<IDiscoveryCandidate[]> {
|
|
30
|
-
const { createSocket } = await import('node:dgram');
|
|
31
|
-
const message = Buffer.from(JSON.stringify({
|
|
32
|
-
method: 'registration',
|
|
33
|
-
params: {
|
|
34
|
-
phoneMac: 'AAAAAAAAAAAA',
|
|
35
|
-
register: false,
|
|
36
|
-
phoneIp: '1.2.3.4',
|
|
37
|
-
id: '1',
|
|
2
|
+
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
|
|
3
|
+
import type { IWizManualEntry, IWizMdnsRecord, IWizUdpDiscoveryRecord } from './wiz.types.js';
|
|
4
|
+
import { wizAddress, wizInteger, wizMac, wizRecord, wizText } from './wiz.validation.js';
|
|
5
|
+
|
|
6
|
+
function hint(hostArg: unknown, macArg: unknown, portArg: unknown, nameArg: unknown, sourceArg: IDiscoveryCandidate['source']): IDiscoveryMatch {
|
|
7
|
+
try {
|
|
8
|
+
const host = wizAddress(hostArg);
|
|
9
|
+
const macAddress = wizMac(macArg);
|
|
10
|
+
return {
|
|
11
|
+
matched: true, confidence: 'medium', reason: 'Selected WiZ endpoint and MAC are unauthenticated setup hints; live admission is required.',
|
|
12
|
+
normalizedDeviceId: macAddress, candidate: {
|
|
13
|
+
source: sourceArg, integrationDomain: 'wiz', id: macAddress, host, macAddress,
|
|
14
|
+
port: wizInteger(portArg, 38899, 1, 65535), name: nameArg === undefined ? 'WiZ' : wizText(nameArg, 'name', 128),
|
|
15
|
+
manufacturer: 'WiZ', metadata: { authenticated: false },
|
|
38
16
|
},
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const candidates: IDiscoveryCandidate[] = [];
|
|
42
|
-
|
|
43
|
-
return new Promise((resolve) => {
|
|
44
|
-
const socket = createSocket({ type: 'udp4', reuseAddr: true });
|
|
45
|
-
const timer = setTimeout(() => {
|
|
46
|
-
try {
|
|
47
|
-
socket.close();
|
|
48
|
-
} catch {
|
|
49
|
-
// The discovery socket may already be closed after an OS error.
|
|
50
|
-
}
|
|
51
|
-
resolve(candidates);
|
|
52
|
-
}, timeoutMsArg);
|
|
53
|
-
|
|
54
|
-
socket.on('message', async (dataArg, remoteArg) => {
|
|
55
|
-
let response: IWizUdpResponse<Record<string, unknown>> | undefined;
|
|
56
|
-
try {
|
|
57
|
-
response = JSON.parse(dataArg.toString('utf8')) as IWizUdpResponse<Record<string, unknown>>;
|
|
58
|
-
} catch {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const match = await matcher.matches({ host: remoteArg.address, port: remoteArg.port, response });
|
|
62
|
-
if (match.matched && match.candidate && !candidates.some((candidateArg) => candidateArg.id === match.candidate?.id || candidateArg.host === match.candidate?.host)) {
|
|
63
|
-
candidates.push(match.candidate);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
socket.on('error', () => {
|
|
67
|
-
clearTimeout(timer);
|
|
68
|
-
try {
|
|
69
|
-
socket.close();
|
|
70
|
-
} catch {
|
|
71
|
-
// Ignore discovery socket close races.
|
|
72
|
-
}
|
|
73
|
-
resolve(candidates);
|
|
74
|
-
});
|
|
75
|
-
socket.bind(() => {
|
|
76
|
-
socket.setBroadcast(true);
|
|
77
|
-
socket.send(message, wizDefaultPort, '255.255.255.255');
|
|
78
|
-
setTimeout(() => socket.send(message, wizDefaultPort, '255.255.255.255'), 500);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
17
|
+
};
|
|
18
|
+
} catch { return { matched: false, confidence: 'low', reason: 'WiZ hints require an explicit local IPv4 endpoint and unicast MAC.' }; }
|
|
82
19
|
}
|
|
83
20
|
|
|
84
21
|
export class WizMdnsMatcher implements IDiscoveryMatcher<IWizMdnsRecord> {
|
|
85
|
-
public id = 'wiz-mdns-match';
|
|
86
|
-
public source = 'mdns' as const;
|
|
87
|
-
public description = 'Recognize WiZ mDNS or hostname advertisements.';
|
|
88
|
-
|
|
22
|
+
public readonly id = 'wiz-mdns-match';
|
|
23
|
+
public readonly source = 'mdns' as const;
|
|
89
24
|
public async matches(recordArg: IWizMdnsRecord): Promise<IDiscoveryMatch> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const text = [type, recordArg.name, recordArg.hostname, host, name, this.txt(txt, 'manufacturer'), this.txt(txt, 'model')]
|
|
96
|
-
.filter((valueArg): valueArg is string => typeof valueArg === 'string')
|
|
97
|
-
.join(' ')
|
|
98
|
-
.toLowerCase();
|
|
99
|
-
const matched = Boolean(mac && this.isWizMac(mac)) || /(^|[._-])wiz([._-]|$)/i.test(text) || text.includes('wizconnected');
|
|
100
|
-
if (!matched) {
|
|
101
|
-
return { matched: false, confidence: 'low', reason: 'mDNS record is not a WiZ advertisement.' };
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
matched: true,
|
|
105
|
-
confidence: mac ? 'certain' : host ? 'high' : 'medium',
|
|
106
|
-
reason: mac ? 'mDNS record contains a WiZ MAC address.' : 'mDNS record contains WiZ hostname or TXT metadata.',
|
|
107
|
-
normalizedDeviceId: mac || recordArg.name || host,
|
|
108
|
-
candidate: {
|
|
109
|
-
source: 'mdns',
|
|
110
|
-
integrationDomain: 'wiz',
|
|
111
|
-
id: mac || recordArg.name || host,
|
|
112
|
-
host,
|
|
113
|
-
port: recordArg.port || wizDefaultPort,
|
|
114
|
-
name: name || 'WiZ',
|
|
115
|
-
manufacturer: 'WiZ',
|
|
116
|
-
model: this.txt(txt, 'model') || this.txt(txt, 'moduleName') || 'WiZ device',
|
|
117
|
-
macAddress: mac,
|
|
118
|
-
metadata: {
|
|
119
|
-
mdnsName: recordArg.name,
|
|
120
|
-
mdnsType: recordArg.type,
|
|
121
|
-
txt,
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
private txt(txtArg: Record<string, string | undefined>, keyArg: string): string | undefined {
|
|
128
|
-
return txtArg[keyArg] || txtArg[keyArg.toUpperCase()];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
private name(recordArg: IWizMdnsRecord, txtArg: Record<string, string | undefined>): string | undefined {
|
|
132
|
-
return this.txt(txtArg, 'name') || this.txt(txtArg, 'friendly_name') || recordArg.name?.split('._', 1)[0] || recordArg.hostname?.replace(/\.local\.?$/i, '');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private normalizeType(valueArg?: string): string {
|
|
136
|
-
return (valueArg || '').toLowerCase().replace(/\.$/, '');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private normalizeMac(valueArg?: string): string | undefined {
|
|
140
|
-
if (!valueArg) {
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
143
|
-
const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
|
|
144
|
-
return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
private isWizMac(valueArg: string): boolean {
|
|
148
|
-
const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
|
|
149
|
-
return wizMacPrefixes.some((prefixArg) => compact.startsWith(prefixArg));
|
|
25
|
+
try {
|
|
26
|
+
const value = wizRecord(recordArg, ['type', 'name', 'host', 'hostname', 'addresses', 'port', 'txt', 'properties']);
|
|
27
|
+
const txt = wizRecord(value.txt ?? value.properties ?? {});
|
|
28
|
+
return hint(value.host, txt.mac ?? txt.macaddress ?? txt.mac_address, value.port, value.name, 'mdns');
|
|
29
|
+
} catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ mDNS hint.' }; }
|
|
150
30
|
}
|
|
151
31
|
}
|
|
152
32
|
|
|
153
33
|
export class WizUdpMatcher implements IDiscoveryMatcher<IWizUdpDiscoveryRecord> {
|
|
154
|
-
public id = 'wiz-udp-match';
|
|
155
|
-
public source = 'custom' as const;
|
|
156
|
-
public description = 'Recognize WiZ UDP JSON discovery responses.';
|
|
157
|
-
|
|
34
|
+
public readonly id = 'wiz-udp-match';
|
|
35
|
+
public readonly source = 'custom' as const;
|
|
158
36
|
public async matches(recordArg: IWizUdpDiscoveryRecord): Promise<IDiscoveryMatch> {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return { matched: false, confidence: 'low', reason: 'UDP record is not a WiZ JSON response.' };
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
matched: true,
|
|
170
|
-
confidence: mac && host ? 'certain' : mac || host ? 'high' : 'medium',
|
|
171
|
-
reason: mac ? 'UDP response contains a WiZ MAC address.' : 'UDP response method matches WiZ JSON protocol.',
|
|
172
|
-
normalizedDeviceId: mac || recordArg.name || host,
|
|
173
|
-
candidate: {
|
|
174
|
-
source: 'custom',
|
|
175
|
-
integrationDomain: 'wiz',
|
|
176
|
-
id: mac || recordArg.name || host,
|
|
177
|
-
host,
|
|
178
|
-
port: recordArg.port || wizDefaultPort,
|
|
179
|
-
name: recordArg.name || (mac ? `WiZ ${this.shortMac(mac)}` : 'WiZ'),
|
|
180
|
-
manufacturer: 'WiZ',
|
|
181
|
-
model: 'WiZ UDP JSON device',
|
|
182
|
-
macAddress: mac,
|
|
183
|
-
metadata: {
|
|
184
|
-
...recordArg.metadata,
|
|
185
|
-
discoveryProtocol: 'udp',
|
|
186
|
-
method,
|
|
187
|
-
result,
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private stringValue(valueArg: unknown): string | undefined {
|
|
194
|
-
return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private shortMac(valueArg?: string): string {
|
|
198
|
-
return (valueArg || '').replace(/[^a-fA-F0-9]/g, '').slice(-6).toUpperCase();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
private normalizeMac(valueArg?: string): string | undefined {
|
|
202
|
-
if (!valueArg) {
|
|
203
|
-
return undefined;
|
|
204
|
-
}
|
|
205
|
-
const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
|
|
206
|
-
return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
|
|
37
|
+
try {
|
|
38
|
+
const value = wizRecord(recordArg, ['host', 'port', 'method', 'result', 'response']);
|
|
39
|
+
const response = value.response === undefined ? value : wizRecord(value.response, ['method', 'result']);
|
|
40
|
+
if (typeof response.method !== 'string' || !['getPilot', 'getSystemConfig', 'registration'].includes(response.method)) throw new TypeError('Unsupported WiZ hint method.');
|
|
41
|
+
const result = wizRecord(response.result);
|
|
42
|
+
return hint(value.host, result.mac, value.port, undefined, 'custom');
|
|
43
|
+
} catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ UDP hint.' }; }
|
|
207
44
|
}
|
|
208
45
|
}
|
|
209
46
|
|
|
210
47
|
export class WizManualMatcher implements IDiscoveryMatcher<IWizManualEntry> {
|
|
211
|
-
public id = 'wiz-manual-match';
|
|
212
|
-
public source = 'manual' as const;
|
|
213
|
-
public
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.filter((valueArg): valueArg is string => typeof valueArg === 'string')
|
|
219
|
-
.join(' ')
|
|
220
|
-
.toLowerCase();
|
|
221
|
-
const matched = Boolean(inputArg.host || mac || inputArg.metadata?.wiz || text.includes('wiz'));
|
|
222
|
-
if (!matched) {
|
|
223
|
-
return { matched: false, confidence: 'low', reason: 'Manual entry does not contain WiZ setup hints.' };
|
|
224
|
-
}
|
|
225
|
-
return {
|
|
226
|
-
matched: true,
|
|
227
|
-
confidence: inputArg.host && mac ? 'certain' : inputArg.host || mac ? 'high' : 'medium',
|
|
228
|
-
reason: 'Manual entry can start WiZ setup.',
|
|
229
|
-
normalizedDeviceId: mac || inputArg.id || inputArg.host,
|
|
230
|
-
candidate: {
|
|
231
|
-
source: 'manual',
|
|
232
|
-
integrationDomain: 'wiz',
|
|
233
|
-
id: inputArg.id || mac || inputArg.host,
|
|
234
|
-
host: inputArg.host,
|
|
235
|
-
port: inputArg.port || wizDefaultPort,
|
|
236
|
-
name: inputArg.name || inputArg.deviceInfo?.name || (mac ? `WiZ ${this.shortMac(mac)}` : 'WiZ'),
|
|
237
|
-
manufacturer: inputArg.manufacturer || inputArg.deviceInfo?.manufacturer || 'WiZ',
|
|
238
|
-
model: inputArg.model || inputArg.deviceInfo?.model || inputArg.deviceInfo?.moduleName || 'WiZ device',
|
|
239
|
-
macAddress: mac,
|
|
240
|
-
metadata: {
|
|
241
|
-
...inputArg.metadata,
|
|
242
|
-
discoveryProtocol: 'manual',
|
|
243
|
-
deviceInfo: inputArg.deviceInfo,
|
|
244
|
-
pilot: inputArg.pilot,
|
|
245
|
-
snapshot: inputArg.snapshot,
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private shortMac(valueArg?: string): string {
|
|
252
|
-
return (valueArg || '').replace(/[^a-fA-F0-9]/g, '').slice(-6).toUpperCase();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
private normalizeMac(valueArg?: string): string | undefined {
|
|
256
|
-
if (!valueArg) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
|
|
260
|
-
return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
|
|
48
|
+
public readonly id = 'wiz-manual-match';
|
|
49
|
+
public readonly source = 'manual' as const;
|
|
50
|
+
public async matches(recordArg: IWizManualEntry): Promise<IDiscoveryMatch> {
|
|
51
|
+
try {
|
|
52
|
+
const value = wizRecord(recordArg, ['host', 'port', 'macAddress', 'name']);
|
|
53
|
+
return hint(value.host, value.macAddress, value.port, value.name, 'manual');
|
|
54
|
+
} catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ manual hint.' }; }
|
|
261
55
|
}
|
|
262
56
|
}
|
|
263
57
|
|
|
264
58
|
export class WizCandidateValidator implements IDiscoveryValidator {
|
|
265
|
-
public id = 'wiz-candidate-validator';
|
|
266
|
-
public description = 'Validate WiZ candidates from mDNS, UDP, DHCP, and manual setup.';
|
|
267
|
-
|
|
59
|
+
public readonly id = 'wiz-candidate-validator';
|
|
268
60
|
public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const manufacturer = (candidateArg.manufacturer || '').toLowerCase();
|
|
275
|
-
const mdnsType = typeof metadata.mdnsType === 'string' ? metadata.mdnsType.toLowerCase() : '';
|
|
276
|
-
const discoveryProtocol = typeof metadata.discoveryProtocol === 'string' ? metadata.discoveryProtocol : undefined;
|
|
277
|
-
const macMatched = wizMacPrefixes.some((prefixArg) => compactMac.startsWith(prefixArg));
|
|
278
|
-
const hostMatched = Boolean(candidateArg.host && /^wiz[_-]/i.test(candidateArg.host));
|
|
279
|
-
const textMatched = name.includes('wiz') || model.includes('wiz') || manufacturer.includes('wiz') || mdnsType.includes('wiz');
|
|
280
|
-
const matched = candidateArg.integrationDomain === 'wiz'
|
|
281
|
-
|| macMatched
|
|
282
|
-
|| hostMatched
|
|
283
|
-
|| textMatched
|
|
284
|
-
|| candidateArg.port === wizDefaultPort
|
|
285
|
-
|| metadata.wiz === true
|
|
286
|
-
|| discoveryProtocol === 'udp'
|
|
287
|
-
|| discoveryProtocol === 'manual';
|
|
288
|
-
return {
|
|
289
|
-
matched,
|
|
290
|
-
confidence: matched && (candidateArg.integrationDomain === 'wiz' || macMatched) && candidateArg.host ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
|
|
291
|
-
reason: matched ? 'Candidate has WiZ metadata or local UDP port information.' : 'Candidate is not WiZ.',
|
|
292
|
-
candidate: matched ? candidateArg : undefined,
|
|
293
|
-
normalizedDeviceId: mac || candidateArg.id || candidateArg.host,
|
|
294
|
-
metadata: matched ? { macMatched, hostMatched, discoveryProtocol } : undefined,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private normalizeMac(valueArg?: string): string | undefined {
|
|
299
|
-
if (!valueArg) {
|
|
300
|
-
return undefined;
|
|
301
|
-
}
|
|
302
|
-
const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
|
|
303
|
-
return compact.length === 12 ? compact.match(/.{1,2}/g)?.join(':') : valueArg.toLowerCase();
|
|
61
|
+
try {
|
|
62
|
+
const candidate = wizRecord(candidateArg);
|
|
63
|
+
if (candidate.integrationDomain !== undefined && candidate.integrationDomain !== 'wiz') return { matched: false, confidence: 'low', reason: 'Candidate belongs to another integration.' };
|
|
64
|
+
return hint(candidate.host, candidate.macAddress, candidate.port, candidate.name, candidateArg.source);
|
|
65
|
+
} catch { return { matched: false, confidence: 'low', reason: 'Malformed WiZ candidate.' }; }
|
|
304
66
|
}
|
|
305
67
|
}
|
|
306
68
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
.addMatcher(new WizMdnsMatcher())
|
|
311
|
-
.addMatcher(new WizUdpMatcher())
|
|
312
|
-
.addMatcher(new WizManualMatcher())
|
|
313
|
-
.addValidator(new WizCandidateValidator());
|
|
314
|
-
};
|
|
69
|
+
/** Discovery consumes supplied advertisements only; it never broadcasts or provisions devices. */
|
|
70
|
+
export const createWizDiscoveryDescriptor = (): DiscoveryDescriptor => new DiscoveryDescriptor({ integrationDomain: 'wiz', displayName: 'WiZ' })
|
|
71
|
+
.addMatcher(new WizMdnsMatcher()).addMatcher(new WizUdpMatcher()).addMatcher(new WizManualMatcher()).addValidator(new WizCandidateValidator());
|