@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { IPanasonicVieraConfig } from './panasonic_viera.types.js';
|
|
2
|
+
|
|
3
|
+
export function panasonicVieraRecord(valueArg: unknown, keysArg?: readonly string[]): Record<string, unknown> {
|
|
4
|
+
if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg) ||
|
|
5
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(valueArg))) {
|
|
6
|
+
throw new TypeError('Panasonic Viera input must be a plain data object.');
|
|
7
|
+
}
|
|
8
|
+
const result: Record<string, unknown> = {};
|
|
9
|
+
for (const key of Reflect.ownKeys(valueArg)) {
|
|
10
|
+
const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
|
|
11
|
+
if (typeof key !== 'string' || !descriptor || !('value' in descriptor) || !descriptor.enumerable ||
|
|
12
|
+
keysArg && !keysArg.includes(key)) throw new TypeError('Panasonic Viera input contains unsupported fields or accessors.');
|
|
13
|
+
Object.defineProperty(result, key, { value: descriptor.value, enumerable: true });
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function panasonicVieraText(valueArg: unknown, nameArg: string, maximumArg = 256): string {
|
|
19
|
+
if (typeof valueArg !== 'string' || !valueArg.trim() || valueArg.length > maximumArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
|
|
20
|
+
throw new TypeError(`Panasonic Viera ${nameArg} must be a bounded non-empty string without control characters.`);
|
|
21
|
+
}
|
|
22
|
+
return valueArg;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function optionalInteger(valueArg: unknown, minimumArg: number, maximumArg: number): number | undefined {
|
|
26
|
+
if (valueArg === undefined) return undefined;
|
|
27
|
+
if (typeof valueArg !== 'number' || !Number.isSafeInteger(valueArg) || valueArg < minimumArg || valueArg > maximumArg) {
|
|
28
|
+
throw new TypeError('Panasonic Viera numeric bound is invalid.');
|
|
29
|
+
}
|
|
30
|
+
return valueArg;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function normalizePanasonicVieraConfig(valueArg: unknown): Readonly<IPanasonicVieraConfig> {
|
|
34
|
+
const config = panasonicVieraRecord(valueArg, ['homeId', 'host', 'descriptionUrl', 'expectedUdn', 'expectedModelName', 'allowUnencryptedLan', 'name', 'timeoutMs', 'maxResponseBytes']);
|
|
35
|
+
if (config.allowUnencryptedLan !== true) throw new TypeError('Panasonic Viera requires explicit allowUnencryptedLan: true.');
|
|
36
|
+
// The owning protocol validates the selected private address, exact description origin and UPnP identity.
|
|
37
|
+
return Object.freeze({
|
|
38
|
+
homeId: panasonicVieraText(config.homeId, 'homeId'),
|
|
39
|
+
host: panasonicVieraText(config.host, 'host'),
|
|
40
|
+
descriptionUrl: panasonicVieraText(config.descriptionUrl, 'descriptionUrl', 4096),
|
|
41
|
+
expectedUdn: panasonicVieraText(config.expectedUdn, 'expectedUdn'),
|
|
42
|
+
expectedModelName: panasonicVieraText(config.expectedModelName, 'expectedModelName'),
|
|
43
|
+
allowUnencryptedLan: true,
|
|
44
|
+
name: config.name === undefined ? undefined : panasonicVieraText(config.name, 'name'),
|
|
45
|
+
timeoutMs: optionalInteger(config.timeoutMs, 1, 60_000),
|
|
46
|
+
maxResponseBytes: optionalInteger(config.maxResponseBytes, 256, 1024 * 1024),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Cleanup failures remain observable after every admitted operation has settled. */
|
|
51
|
+
export async function drainPanasonicVieraCleanup(cleanupsArg: readonly Promise<unknown>[], operationsArg: readonly Promise<unknown>[], messageArg: string): Promise<void> {
|
|
52
|
+
const [cleanups] = await Promise.all([Promise.allSettled(cleanupsArg), Promise.allSettled(operationsArg)]);
|
|
53
|
+
const errors: unknown[] = cleanups.filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected').map((resultArg) => resultArg.reason);
|
|
54
|
+
if (errors.length === 1) throw errors[0];
|
|
55
|
+
if (errors.length > 1) throw new AggregateError(errors, messageArg);
|
|
56
|
+
}
|
|
@@ -1,462 +1,166 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
public override async refresh(): Promise<IServiceCallResult> {
|
|
32
|
-
this.liveSnapshot = undefined;
|
|
33
|
-
const snapshot = await this.getSnapshot(true);
|
|
34
|
-
const success = snapshot.online && !snapshot.error;
|
|
35
|
-
return { success, error: success ? undefined : snapshot.error, data: snapshot };
|
|
2
|
+
import type { INativeIntegrationSetupContext } from '../../core/types.js';
|
|
3
|
+
import { NativeHomeAdmission, type INativeAdmissionContext } from '../../core/classes.nativehomeadmission.js';
|
|
4
|
+
import type { IPioneerConfig, IPioneerEffectResult, IPioneerSnapshot, TPioneerControl } from './pioneer.types.js';
|
|
5
|
+
import { normalizePioneerConfig, validatePioneerControl } from './pioneer.validation.js';
|
|
6
|
+
|
|
7
|
+
/** Home admission and caller work belong here; the published foundation owns the wire. */
|
|
8
|
+
export class PioneerClient {
|
|
9
|
+
public readonly config: ReturnType<typeof normalizePioneerConfig>;
|
|
10
|
+
private readonly protocol: plugins.ecobridgeDeviceManager.PioneerProtocol;
|
|
11
|
+
private readonly admission = new NativeHomeAdmission('pioneer');
|
|
12
|
+
private readonly context: INativeAdmissionContext;
|
|
13
|
+
private readonly lifetime = new AbortController();
|
|
14
|
+
private operationTail: Promise<void> = Promise.resolve();
|
|
15
|
+
private pendingOperations = 0;
|
|
16
|
+
private startPromise?: Promise<IPioneerSnapshot>;
|
|
17
|
+
private snapshot?: IPioneerSnapshot;
|
|
18
|
+
private closePromise?: Promise<void>;
|
|
19
|
+
private retryPromise?: Promise<void>;
|
|
20
|
+
|
|
21
|
+
public constructor(config: IPioneerConfig, context: INativeIntegrationSetupContext) {
|
|
22
|
+
this.config = normalizePioneerConfig(config);
|
|
23
|
+
this.context = this.admission.admit(context, this.config.homeId);
|
|
24
|
+
this.protocol = new plugins.ecobridgeDeviceManager.PioneerProtocol({
|
|
25
|
+
address: this.config.host, descriptionUrl: this.config.descriptionUrl, expectedUdn: this.config.expectedUdn,
|
|
26
|
+
expectedModel: this.config.model, allowUnencryptedLan: this.config.allowUnencryptedLan,
|
|
27
|
+
maximumVolumeRaw: this.config.maximumVolumeRaw, port: this.config.port, timeoutMs: this.config.timeoutMs,
|
|
28
|
+
maxPendingOperations: this.config.maxPendingOperations, maxDescriptionBytes: this.config.maxDescriptionBytes,
|
|
29
|
+
maxFrameBytes: this.config.maxFrameBytes,
|
|
30
|
+
});
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
public
|
|
39
|
-
|
|
40
|
-
return super.execute(requestArg);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
const command = await this.commandForService(requestArg);
|
|
45
|
-
const endpoint = this.requireEndpoint();
|
|
46
|
-
await sendPioneerCommand(endpoint, command, this.configArg.timeoutMs || 3000);
|
|
47
|
-
this.liveSnapshot = undefined;
|
|
48
|
-
return { success: true, data: { command } };
|
|
49
|
-
} catch (errorArg) {
|
|
50
|
-
return { success: false, error: errorMessage(errorArg) };
|
|
51
|
-
}
|
|
33
|
+
public isHealthy(): boolean {
|
|
34
|
+
try { this.assertOpen(); return this.snapshot !== undefined; } catch { return false; }
|
|
52
35
|
}
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
37
|
+
public start(signal?: AbortSignal): Promise<IPioneerSnapshot> {
|
|
38
|
+
try { this.assertOpen(); this.assertSignal(signal); } catch (error) { return Promise.reject(error); }
|
|
39
|
+
if (!this.startPromise) {
|
|
40
|
+
this.startPromise = this.run(async (operationSignal, timeoutMs) => this.observe(await this.protocol.inspect({ signal: operationSignal, timeoutMs })), signal, false)
|
|
41
|
+
.catch(async (error: unknown) => {
|
|
42
|
+
this.lifetime.abort(error);
|
|
43
|
+
try { await this.protocol.close(); }
|
|
44
|
+
catch (cleanupError) { throw new AggregateError([error, cleanupError], 'Pioneer startup and cleanup failed.'); }
|
|
45
|
+
throw error;
|
|
46
|
+
});
|
|
57
47
|
}
|
|
58
|
-
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
if (!forceRefreshArg && (this.configArg.rawData !== undefined || this.configArg.rawText !== undefined || this.configArg.entities?.length)) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
return true;
|
|
48
|
+
return this.startPromise;
|
|
65
49
|
}
|
|
66
50
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const commandTimeoutMs = this.configArg.commandTimeoutMs || 250;
|
|
72
|
-
const powerRaw = await connection.request('?P', 'PWR', commandTimeoutMs);
|
|
73
|
-
const volumeRaw = await connection.request('?V', 'VOL', commandTimeoutMs);
|
|
74
|
-
const muteRaw = await connection.request('?M', 'MUT', commandTimeoutMs);
|
|
75
|
-
const sourceNameToNumber = await this.sourceNameToNumber(connection, commandTimeoutMs);
|
|
76
|
-
const sourceNumberToName = reverseSourceMap(sourceNameToNumber);
|
|
77
|
-
const sourceRaw = await connection.request('?F', 'FN', commandTimeoutMs);
|
|
78
|
-
const sourceNumber = sourceRaw?.slice(2);
|
|
79
|
-
const source = sourceNumber ? sourceNumberToName[sourceNumber] || sourceNumber : undefined;
|
|
80
|
-
const volume = volumeRaw ? numberValue(volumeRaw.slice(3)) : undefined;
|
|
81
|
-
const state = pioneerPowerState(powerRaw);
|
|
82
|
-
const name = this.configArg.name || pioneerDefaultName;
|
|
83
|
-
const entity: ISimpleLocalEntitySnapshot = {
|
|
84
|
-
id: 'media_player',
|
|
85
|
-
uniqueId: `${pioneerProfile.domain}_${SimpleLocalMapper.slug(this.configArg.uniqueId || this.configArg.deviceId || endpoint.host)}_media_player`,
|
|
86
|
-
name,
|
|
87
|
-
platform: 'media_player',
|
|
88
|
-
state,
|
|
89
|
-
available: true,
|
|
90
|
-
writable: true,
|
|
91
|
-
attributes: cleanAttributes({
|
|
92
|
-
powerRaw,
|
|
93
|
-
volumeRaw,
|
|
94
|
-
volume,
|
|
95
|
-
volumeLevel: volume === undefined ? undefined : volume / pioneerMaxVolume,
|
|
96
|
-
isVolumeMuted: muteRaw === undefined ? undefined : muteRaw === 'MUT0',
|
|
97
|
-
muteRaw,
|
|
98
|
-
source,
|
|
99
|
-
sourceNumber,
|
|
100
|
-
sourceList: Object.keys(sourceNameToNumber).length ? Object.keys(sourceNameToNumber).sort() : undefined,
|
|
101
|
-
sources: Object.keys(sourceNameToNumber).length ? sourceNameToNumber : undefined,
|
|
102
|
-
}),
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
device: {
|
|
107
|
-
id: this.configArg.uniqueId || this.configArg.deviceId || `${endpoint.host}:${endpoint.port}`,
|
|
108
|
-
name,
|
|
109
|
-
manufacturer: 'Pioneer',
|
|
110
|
-
model: stringValue(this.configArg.model) || 'Pioneer AVR',
|
|
111
|
-
serialNumber: this.configArg.macAddress,
|
|
112
|
-
host: endpoint.host,
|
|
113
|
-
port: endpoint.port,
|
|
114
|
-
protocol: 'telnet',
|
|
115
|
-
},
|
|
116
|
-
entities: [entity],
|
|
117
|
-
online: true,
|
|
118
|
-
updatedAt: new Date().toISOString(),
|
|
119
|
-
source: 'tcp',
|
|
120
|
-
rawData: {
|
|
121
|
-
powerRaw,
|
|
122
|
-
volumeRaw,
|
|
123
|
-
muteRaw,
|
|
124
|
-
sourceRaw,
|
|
125
|
-
sources: sourceNameToNumber,
|
|
126
|
-
},
|
|
127
|
-
capabilities: {
|
|
128
|
-
localRead: true,
|
|
129
|
-
localControl: true,
|
|
130
|
-
telnet: true,
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
} finally {
|
|
134
|
-
connection.close();
|
|
135
|
-
}
|
|
51
|
+
public getObservedSnapshot(): IPioneerSnapshot {
|
|
52
|
+
this.assertOpen();
|
|
53
|
+
if (!this.snapshot) throw new Error('Pioneer has no admitted observation.');
|
|
54
|
+
return this.snapshot;
|
|
136
55
|
}
|
|
137
56
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (configured) {
|
|
141
|
-
return configured;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const sources: Record<string, string> = {};
|
|
145
|
-
const limit = Math.max(0, Math.min(pioneerMaxSourceNumbers, this.configArg.sourceScanLimit ?? pioneerMaxSourceNumbers));
|
|
146
|
-
for (let index = 0; index < limit; index++) {
|
|
147
|
-
const response = await connectionArg.request(`?RGB${String(index).padStart(2, '0')}`, 'RGB', commandTimeoutMsArg);
|
|
148
|
-
const source = parseSourceResponse(response);
|
|
149
|
-
if (source) {
|
|
150
|
-
sources[source.name] = source.number;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return sources;
|
|
57
|
+
public getSnapshot(signal?: AbortSignal): Promise<IPioneerSnapshot> {
|
|
58
|
+
return this.run(async (operationSignal, timeoutMs) => this.observe(await this.protocol.inspect({ signal: operationSignal, timeoutMs })), signal);
|
|
154
59
|
}
|
|
155
60
|
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return 'VD';
|
|
172
|
-
}
|
|
173
|
-
if (requestArg.service === 'volume_mute' || requestArg.service === 'mute_volume') {
|
|
174
|
-
return boolValue(data.mute ?? data.is_volume_muted ?? data.muted ?? true) ? 'MO' : 'MF';
|
|
175
|
-
}
|
|
176
|
-
if (requestArg.service === 'set_volume_level' || requestArg.service === 'volume_set' || requestArg.service === 'set_level') {
|
|
177
|
-
const requested = numberValue(data.volume_level ?? data.volume ?? data.level);
|
|
178
|
-
const percentage = numberValue(data.percentage);
|
|
179
|
-
const value = percentage !== undefined ? (percentage / 100) * pioneerMaxVolume : requested !== undefined && requested <= 1 ? requested * pioneerMaxVolume : requested;
|
|
180
|
-
if (value === undefined) {
|
|
181
|
-
throw new Error('Pioneer set_volume_level requires data.volume_level, data.volume, data.level, or data.percentage.');
|
|
182
|
-
}
|
|
183
|
-
return `${String(Math.max(0, Math.min(pioneerMaxVolume, Math.round(value)))).padStart(3, '0')}VL`;
|
|
184
|
-
}
|
|
185
|
-
if (requestArg.service === 'select_source') {
|
|
186
|
-
const sourceNumber = stringValue(data.sourceNumber) || stringValue(data.source_number) || stringValue(data.input);
|
|
187
|
-
if (sourceNumber && /^\d{2}$/.test(sourceNumber)) {
|
|
188
|
-
return `${sourceNumber}FN`;
|
|
189
|
-
}
|
|
190
|
-
const source = stringValue(data.source) || stringValue(data.source_name);
|
|
191
|
-
const sourceMap = recordValue(this.configArg.sources) || recordValue(this.configArg.metadata?.sources) || recordValue((await this.getSnapshot()).entities[0]?.attributes?.sources);
|
|
192
|
-
const mapped = source ? sourceMap?.[source] : undefined;
|
|
193
|
-
if (!mapped) {
|
|
194
|
-
throw new Error('Pioneer select_source requires data.sourceNumber or data.source matching configured/discovered sources.');
|
|
61
|
+
public async setControl(input: TPioneerControl, signal?: AbortSignal): Promise<IPioneerEffectResult> {
|
|
62
|
+
const control = validatePioneerControl(input);
|
|
63
|
+
if (control.feature === 'volume_raw' && control.value > this.config.maximumVolumeRaw) throw new TypeError('Pioneer volume exceeds the owner ceiling.');
|
|
64
|
+
return this.run(async (operationSignal, timeoutMs) => {
|
|
65
|
+
const options = { signal: operationSignal, timeoutMs };
|
|
66
|
+
const result = control.feature === 'power' ? await this.protocol.setPower(control.value, options)
|
|
67
|
+
: control.feature === 'muted' ? await this.protocol.setMuted(control.value, options)
|
|
68
|
+
: control.feature === 'volume_raw' ? await this.protocol.setVolumeRaw(control.value, options)
|
|
69
|
+
: await this.protocol.setInput(control.value, options);
|
|
70
|
+
try {
|
|
71
|
+
return Object.freeze({ dispatch: result.dispatch, outcome: result.outcome,
|
|
72
|
+
commandAcknowledged: result.commandAcknowledged, causalityConfirmed: result.causalityConfirmed,
|
|
73
|
+
snapshot: this.observe(result.observation) });
|
|
74
|
+
} catch {
|
|
75
|
+
throw new plugins.ecobridgeDeviceManager.PioneerProtocolError('identity-mismatch', 'Pioneer observation could not be admitted after dispatch.', 'indeterminate');
|
|
195
76
|
}
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
throw new Error(`Unsupported Pioneer native service: ${requestArg.domain}.${requestArg.service}`);
|
|
77
|
+
}, signal);
|
|
199
78
|
}
|
|
200
79
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
80
|
+
public destroy(): Promise<void> {
|
|
81
|
+
if (!this.closePromise) {
|
|
82
|
+
// Publish the memo before protocol abort listeners can reenter this facade.
|
|
83
|
+
this.closePromise = Promise.resolve().then(() => this.drain());
|
|
84
|
+
this.lifetime.abort(new Error('Pioneer client is closed.'));
|
|
205
85
|
}
|
|
206
|
-
return
|
|
86
|
+
return this.closePromise;
|
|
207
87
|
}
|
|
208
88
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
89
|
+
/** Retries retained terminal cleanup only; never repeats protocol observations or effects. */
|
|
90
|
+
public retryCleanup(): Promise<void> {
|
|
91
|
+
const closed = this.closePromise;
|
|
92
|
+
if (!closed) return Promise.reject(new Error('Pioneer cleanup retry requires terminal destruction.'));
|
|
93
|
+
if (!this.retryPromise) {
|
|
94
|
+
const retry = Promise.resolve().then(async () => {
|
|
95
|
+
await closed.catch(() => undefined);
|
|
96
|
+
await this.drain();
|
|
97
|
+
});
|
|
98
|
+
this.retryPromise = retry;
|
|
99
|
+
const settled = (): void => { this.retryPromise = undefined; };
|
|
100
|
+
void retry.then(settled, settled);
|
|
220
101
|
}
|
|
221
|
-
|
|
222
|
-
if (parsedHost) {
|
|
223
|
-
return { host: parsedHost.hostname, port: this.configArg.port || numberValue(parsedHost.port) || pioneerDefaultPort };
|
|
224
|
-
}
|
|
225
|
-
const hostPort = splitHostPort(host);
|
|
226
|
-
return { host: hostPort.host, port: this.configArg.port || hostPort.port || pioneerDefaultPort };
|
|
102
|
+
return this.retryPromise;
|
|
227
103
|
}
|
|
228
104
|
|
|
229
|
-
private
|
|
230
|
-
|
|
105
|
+
private async drain(): Promise<void> {
|
|
106
|
+
const results = await Promise.allSettled([this.protocol.close(), this.operationTail, this.startPromise]);
|
|
107
|
+
this.snapshot = undefined;
|
|
108
|
+
const closure = results[0];
|
|
109
|
+
if (closure.status === 'rejected') throw closure.reason;
|
|
231
110
|
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
interface IPioneerEndpoint {
|
|
235
|
-
host: string;
|
|
236
|
-
port: number;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
class PioneerTelnetConnection {
|
|
240
|
-
private buffer = '';
|
|
241
|
-
private lines: string[] = [];
|
|
242
|
-
private waiters: Array<() => void> = [];
|
|
243
|
-
private closed = false;
|
|
244
|
-
private lastError?: Error;
|
|
245
111
|
|
|
246
|
-
private
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.
|
|
250
|
-
this.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
this.notify();
|
|
255
|
-
});
|
|
256
|
-
socket.on('error', (errorArg) => {
|
|
257
|
-
this.lastError = errorArg;
|
|
258
|
-
this.closed = true;
|
|
259
|
-
this.notify();
|
|
112
|
+
private observe(state: plugins.ecobridgeDeviceManager.IPioneerState): IPioneerSnapshot {
|
|
113
|
+
this.assertOpen();
|
|
114
|
+
this.snapshot = Object.freeze({
|
|
115
|
+
homeId: this.config.homeId, host: this.config.host, port: this.config.port,
|
|
116
|
+
name: this.config.name ?? state.identity.name, identity: Object.freeze({ ...state.identity }),
|
|
117
|
+
power: state.power, muted: state.muted, volumeRaw: state.volumeRaw, inputId: state.inputId,
|
|
118
|
+
observedInputs: Object.freeze([...this.protocol.observedInputs]), maximumVolumeRaw: this.config.maximumVolumeRaw,
|
|
119
|
+
observedAt: state.observedAt,
|
|
260
120
|
});
|
|
121
|
+
return this.snapshot;
|
|
261
122
|
}
|
|
262
123
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const finish = (connectionArg?: PioneerTelnetConnection, errorArg?: Error) => {
|
|
268
|
-
if (settled) {
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
settled = true;
|
|
272
|
-
socket.off('connect', onConnect);
|
|
273
|
-
socket.off('error', onError);
|
|
274
|
-
socket.off('timeout', onTimeout);
|
|
275
|
-
if (errorArg) {
|
|
276
|
-
socket.destroy();
|
|
277
|
-
reject(errorArg);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
resolve(connectionArg!);
|
|
281
|
-
};
|
|
282
|
-
const onConnect = () => finish(new PioneerTelnetConnection(socket));
|
|
283
|
-
const onError = (errorArg: Error) => finish(undefined, errorArg);
|
|
284
|
-
const onTimeout = () => finish(undefined, new Error(`Pioneer Telnet connection to ${endpointArg.host}:${endpointArg.port} timed out after ${timeoutMsArg}ms.`));
|
|
285
|
-
socket.setTimeout(timeoutMsArg);
|
|
286
|
-
socket.once('connect', onConnect);
|
|
287
|
-
socket.once('error', onError);
|
|
288
|
-
socket.once('timeout', onTimeout);
|
|
289
|
-
});
|
|
124
|
+
private assertOpen(): void {
|
|
125
|
+
this.lifetime.signal.throwIfAborted();
|
|
126
|
+
if (this.protocol.isClosed) throw new plugins.ecobridgeDeviceManager.PioneerProtocolError('closed', 'Pioneer protocol is terminally closed.');
|
|
127
|
+
this.admission.admit(this.context, this.config.homeId);
|
|
290
128
|
}
|
|
291
129
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const line = await this.readLine(timeoutMsArg);
|
|
296
|
-
if (!line) {
|
|
297
|
-
return undefined;
|
|
298
|
-
}
|
|
299
|
-
if (line.startsWith(expectedPrefixArg)) {
|
|
300
|
-
return line;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return undefined;
|
|
130
|
+
private assertSignal(signal?: AbortSignal): void {
|
|
131
|
+
if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('Pioneer cancellation requires an AbortSignal.');
|
|
132
|
+
signal?.throwIfAborted();
|
|
304
133
|
}
|
|
305
134
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
this.
|
|
135
|
+
/** Scope is checked when adapter work runs, before admission to the foundation's FIFO. */
|
|
136
|
+
private run<TResult>(execute: (signal: AbortSignal, timeoutMs: number) => Promise<TResult>, signal?: AbortSignal, started = true): Promise<TResult> {
|
|
137
|
+
try {
|
|
138
|
+
this.assertOpen(); this.assertSignal(signal);
|
|
139
|
+
if (started && !this.snapshot) throw new Error('Pioneer client has not started.');
|
|
140
|
+
if (this.pendingOperations >= this.config.maxPendingOperations) throw new Error('Pioneer operation capacity reached.');
|
|
141
|
+
} catch (error) { return Promise.reject(error); }
|
|
142
|
+
const deadline = performance.now() + this.config.timeoutMs;
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const combined = AbortSignal.any([this.lifetime.signal, controller.signal,
|
|
145
|
+
...(this.context.abortSignal ? [this.context.abortSignal] : []), ...(signal ? [signal] : [])]);
|
|
146
|
+
const timer = setTimeout(() => controller.abort(new plugins.ecobridgeDeviceManager.PioneerProtocolError('timeout', 'Pioneer adapter deadline expired.')), this.config.timeoutMs);
|
|
147
|
+
this.pendingOperations++;
|
|
148
|
+
let rejectWaiting: () => void = () => {};
|
|
149
|
+
const waitingCancelled = new Promise<never>((_resolve, reject) => {
|
|
150
|
+
rejectWaiting = () => reject(combined.reason);
|
|
151
|
+
combined.addEventListener('abort', rejectWaiting, { once: true });
|
|
312
152
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
private async readLine(timeoutMsArg: number): Promise<string | undefined> {
|
|
322
|
-
if (this.lastError) {
|
|
323
|
-
throw this.lastError;
|
|
324
|
-
}
|
|
325
|
-
if (this.lines.length) {
|
|
326
|
-
return this.lines.shift();
|
|
327
|
-
}
|
|
328
|
-
if (this.closed) {
|
|
329
|
-
return undefined;
|
|
330
|
-
}
|
|
331
|
-
return new Promise<string | undefined>((resolve, reject) => {
|
|
332
|
-
let settled = false;
|
|
333
|
-
const cleanup = () => {
|
|
334
|
-
clearTimeout(timer);
|
|
335
|
-
this.waiters = this.waiters.filter((waiterArg) => waiterArg !== waiter);
|
|
336
|
-
};
|
|
337
|
-
const done = (valueArg?: string, errorArg?: Error) => {
|
|
338
|
-
if (settled) {
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
settled = true;
|
|
342
|
-
cleanup();
|
|
343
|
-
if (errorArg) {
|
|
344
|
-
reject(errorArg);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
resolve(valueArg);
|
|
348
|
-
};
|
|
349
|
-
const waiter = () => {
|
|
350
|
-
if (this.lastError) {
|
|
351
|
-
done(undefined, this.lastError);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (this.lines.length) {
|
|
355
|
-
done(this.lines.shift());
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if (this.closed) {
|
|
359
|
-
done(undefined);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
const timer = setTimeout(() => done(undefined), timeoutMsArg);
|
|
363
|
-
this.waiters.push(waiter);
|
|
153
|
+
const operation = this.operationTail.then(async () => {
|
|
154
|
+
combined.removeEventListener('abort', rejectWaiting);
|
|
155
|
+
combined.throwIfAborted(); this.assertOpen();
|
|
156
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
157
|
+
if (remaining < 1) throw new plugins.ecobridgeDeviceManager.PioneerProtocolError('timeout', 'Pioneer queued operation expired.');
|
|
158
|
+
return execute(combined, remaining);
|
|
159
|
+
}).finally(() => {
|
|
160
|
+
combined.removeEventListener('abort', rejectWaiting); clearTimeout(timer); this.pendingOperations--;
|
|
364
161
|
});
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
this.buffer += chunkArg.toString('ascii');
|
|
369
|
-
let match = /\r\n|\r|\n/.exec(this.buffer);
|
|
370
|
-
while (match?.index !== undefined) {
|
|
371
|
-
const line = this.buffer.slice(0, match.index).trim();
|
|
372
|
-
this.buffer = this.buffer.slice(match.index + match[0].length);
|
|
373
|
-
if (line) {
|
|
374
|
-
this.lines.push(line);
|
|
375
|
-
}
|
|
376
|
-
match = /\r\n|\r|\n/.exec(this.buffer);
|
|
377
|
-
}
|
|
378
|
-
this.notify();
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
private notify(): void {
|
|
382
|
-
const waiters = this.waiters.splice(0);
|
|
383
|
-
for (const waiter of waiters) {
|
|
384
|
-
waiter();
|
|
385
|
-
}
|
|
162
|
+
this.operationTail = operation.then(() => {}, () => {});
|
|
163
|
+
if (combined.aborted) rejectWaiting();
|
|
164
|
+
return Promise.race([operation, waitingCancelled]);
|
|
386
165
|
}
|
|
387
166
|
}
|
|
388
|
-
|
|
389
|
-
const sendPioneerCommand = async (endpointArg: IPioneerEndpoint, commandArg: string, timeoutMsArg: number): Promise<void> => {
|
|
390
|
-
const connection = await PioneerTelnetConnection.connect(endpointArg, timeoutMsArg);
|
|
391
|
-
try {
|
|
392
|
-
await connection.write(`${commandArg}\r`);
|
|
393
|
-
await delay(40);
|
|
394
|
-
} finally {
|
|
395
|
-
connection.close();
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
const parseSourceResponse = (responseArg: string | undefined): { number: string; name: string } | undefined => {
|
|
400
|
-
const match = responseArg ? /^RGB(\d{2})\s*(.+)$/.exec(responseArg) : undefined;
|
|
401
|
-
const name = match?.[2]?.trim();
|
|
402
|
-
return match && name ? { number: match[1], name } : undefined;
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
const pioneerPowerState = (valueArg: string | undefined): string => {
|
|
406
|
-
if (valueArg === 'PWR0') {
|
|
407
|
-
return 'on';
|
|
408
|
-
}
|
|
409
|
-
if (valueArg === 'PWR1' || valueArg === 'PWR2') {
|
|
410
|
-
return 'off';
|
|
411
|
-
}
|
|
412
|
-
return 'unknown';
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
const reverseSourceMap = (sourcesArg: Record<string, string>): Record<string, string> => Object.fromEntries(Object.entries(sourcesArg).map(([nameArg, numberArg]) => [numberArg, nameArg]));
|
|
416
|
-
|
|
417
|
-
const recordValue = (valueArg: unknown): Record<string, string> | undefined => {
|
|
418
|
-
if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg)) {
|
|
419
|
-
return undefined;
|
|
420
|
-
}
|
|
421
|
-
const entries = Object.entries(valueArg).filter((entryArg): entryArg is [string, string] => typeof entryArg[1] === 'string' && entryArg[1].trim().length > 0);
|
|
422
|
-
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
|
|
426
|
-
|
|
427
|
-
const numberValue = (valueArg: unknown): number | undefined => {
|
|
428
|
-
if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
|
|
429
|
-
return valueArg;
|
|
430
|
-
}
|
|
431
|
-
if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
|
|
432
|
-
return Number(valueArg);
|
|
433
|
-
}
|
|
434
|
-
return undefined;
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
const boolValue = (valueArg: unknown): boolean => valueArg === true || valueArg === 'true' || valueArg === '1' || valueArg === 1;
|
|
438
|
-
|
|
439
|
-
const cleanAttributes = (attributesArg: Record<string, unknown>): Record<string, unknown> | undefined => {
|
|
440
|
-
const clean = Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
|
|
441
|
-
return Object.keys(clean).length ? clean : undefined;
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
const parseUrl = (valueArg: string): URL | undefined => {
|
|
445
|
-
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
|
|
446
|
-
return undefined;
|
|
447
|
-
}
|
|
448
|
-
try {
|
|
449
|
-
return new URL(valueArg);
|
|
450
|
-
} catch {
|
|
451
|
-
return undefined;
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
const splitHostPort = (valueArg: string): { host: string; port?: number } => {
|
|
456
|
-
const match = /^([^:]+):(\d+)$/.exec(valueArg);
|
|
457
|
-
return match ? { host: match[1], port: Number(match[2]) } : { host: valueArg };
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
const delay = async (msArg: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, msArg));
|
|
461
|
-
|
|
462
|
-
const errorMessage = (errorArg: unknown): string => errorArg instanceof Error ? errorArg.message : String(errorArg);
|