@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,739 +1,275 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from './braviatv.types.js';
|
|
14
|
-
|
|
15
|
-
interface IBraviatvRestResponse {
|
|
16
|
-
result?: unknown[];
|
|
17
|
-
error?: [number, string] | unknown[];
|
|
18
|
-
id?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const defaultTimeoutMs = 10000;
|
|
22
|
-
const defaultAudioTarget = 'speaker';
|
|
23
|
-
const defaultRestVersion = '1.0';
|
|
24
|
-
const powerOnCode = 'AAAAAQAAAAEAAAAuAw==';
|
|
25
|
-
|
|
26
|
-
const fallbackCommands: Record<string, string> = {
|
|
27
|
-
Power: 'AAAAAQAAAAEAAAAVAw==',
|
|
28
|
-
PowerOn: powerOnCode,
|
|
29
|
-
Input: 'AAAAAQAAAAEAAAAlAw==',
|
|
30
|
-
SyncMenu: 'AAAAAgAAABoAAABYAw==',
|
|
31
|
-
Hdmi1: 'AAAAAgAAABoAAABaAw==',
|
|
32
|
-
Hdmi2: 'AAAAAgAAABoAAABbAw==',
|
|
33
|
-
Hdmi3: 'AAAAAgAAABoAAABcAw==',
|
|
34
|
-
Hdmi4: 'AAAAAgAAABoAAABdAw==',
|
|
35
|
-
Num1: 'AAAAAQAAAAEAAAAAAw==',
|
|
36
|
-
Num2: 'AAAAAQAAAAEAAAABAw==',
|
|
37
|
-
Num3: 'AAAAAQAAAAEAAAACAw==',
|
|
38
|
-
Num4: 'AAAAAQAAAAEAAAADAw==',
|
|
39
|
-
Num5: 'AAAAAQAAAAEAAAAEAw==',
|
|
40
|
-
Num6: 'AAAAAQAAAAEAAAAFAw==',
|
|
41
|
-
Num7: 'AAAAAQAAAAEAAAAGAw==',
|
|
42
|
-
Num8: 'AAAAAQAAAAEAAAAHAw==',
|
|
43
|
-
Num9: 'AAAAAQAAAAEAAAAIAw==',
|
|
44
|
-
Num0: 'AAAAAQAAAAEAAAAJAw==',
|
|
45
|
-
Dot: 'AAAAAgAAAJcAAAAdAw==',
|
|
46
|
-
CC: 'AAAAAgAAAJcAAAAoAw==',
|
|
47
|
-
Red: 'AAAAAgAAAJcAAAAlAw==',
|
|
48
|
-
Green: 'AAAAAgAAAJcAAAAmAw==',
|
|
49
|
-
Yellow: 'AAAAAgAAAJcAAAAnAw==',
|
|
50
|
-
Blue: 'AAAAAgAAAJcAAAAkAw==',
|
|
51
|
-
Up: 'AAAAAQAAAAEAAAB0Aw==',
|
|
52
|
-
Down: 'AAAAAQAAAAEAAAB1Aw==',
|
|
53
|
-
Right: 'AAAAAQAAAAEAAAAzAw==',
|
|
54
|
-
Left: 'AAAAAQAAAAEAAAA0Aw==',
|
|
55
|
-
Confirm: 'AAAAAQAAAAEAAABlAw==',
|
|
56
|
-
Help: 'AAAAAgAAAMQAAABNAw==',
|
|
57
|
-
Display: 'AAAAAQAAAAEAAAA6Aw==',
|
|
58
|
-
Options: 'AAAAAgAAAJcAAAA2Aw==',
|
|
59
|
-
Back: 'AAAAAgAAAJcAAAAjAw==',
|
|
60
|
-
Home: 'AAAAAQAAAAEAAABgAw==',
|
|
61
|
-
VolumeUp: 'AAAAAQAAAAEAAAASAw==',
|
|
62
|
-
VolumeDown: 'AAAAAQAAAAEAAAATAw==',
|
|
63
|
-
Mute: 'AAAAAQAAAAEAAAAUAw==',
|
|
64
|
-
Audio: 'AAAAAQAAAAEAAAAXAw==',
|
|
65
|
-
ChannelUp: 'AAAAAQAAAAEAAAAQAw==',
|
|
66
|
-
ChannelDown: 'AAAAAQAAAAEAAAARAw==',
|
|
67
|
-
Play: 'AAAAAgAAAJcAAAAaAw==',
|
|
68
|
-
Pause: 'AAAAAgAAAJcAAAAZAw==',
|
|
69
|
-
Stop: 'AAAAAgAAAJcAAAAYAw==',
|
|
70
|
-
FlashPlus: 'AAAAAgAAAJcAAAB4Aw==',
|
|
71
|
-
FlashMinus: 'AAAAAgAAAJcAAAB5Aw==',
|
|
72
|
-
Prev: 'AAAAAgAAAJcAAAA8Aw==',
|
|
73
|
-
Next: 'AAAAAgAAAJcAAAA9Aw==',
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export class BraviatvAuthError extends Error {
|
|
77
|
-
constructor(messageArg: string) {
|
|
78
|
-
super(messageArg);
|
|
79
|
-
this.name = 'BraviatvAuthError';
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export class BraviatvLiveControlError extends Error {
|
|
84
|
-
constructor(messageArg: string) {
|
|
85
|
-
super(messageArg);
|
|
86
|
-
this.name = 'BraviatvLiveControlError';
|
|
87
|
-
}
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { NativeHomeAdmission, type INativeAdmissionContext } from '../../core/classes.nativehomeadmission.js';
|
|
3
|
+
import type { IBraviatvConfig, IBraviatvEffectResult, IBraviatvIntegrationOptions, IBraviatvSetupContext,
|
|
4
|
+
IBraviatvSnapshot, TBraviatvControl } from './braviatv.types.js';
|
|
5
|
+
import { normalizeBraviatvConfig, normalizeBraviatvOptions,
|
|
6
|
+
validateBraviatvControl } from './braviatv.validation.js';
|
|
7
|
+
|
|
8
|
+
interface IBraviatvOwnedProtocol {
|
|
9
|
+
readonly protocol: plugins.ecobridgeDeviceManager.SonyBraviaProtocol;
|
|
10
|
+
active: boolean;
|
|
11
|
+
failed: boolean;
|
|
12
|
+
cleanup?: Promise<void>;
|
|
88
13
|
}
|
|
89
14
|
|
|
15
|
+
/** Home admission and caller work only; SonyBraviaProtocol owns every wire operation. */
|
|
90
16
|
export class BraviatvClient {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
17
|
+
public readonly config: ReturnType<typeof normalizeBraviatvConfig>;
|
|
18
|
+
readonly #options: Readonly<IBraviatvIntegrationOptions>;
|
|
19
|
+
readonly #context: INativeAdmissionContext;
|
|
20
|
+
readonly #admission: NativeHomeAdmission;
|
|
21
|
+
readonly #lifetime = new AbortController();
|
|
22
|
+
readonly #protocols = new Map<plugins.ecobridgeDeviceManager.SonyBraviaProtocol, IBraviatvOwnedProtocol>();
|
|
23
|
+
readonly #operations = new Set<Promise<unknown>>();
|
|
24
|
+
readonly #credentialOperations = new Map<Promise<string>, Promise<unknown>>();
|
|
25
|
+
readonly #credentialFailures = new Map<plugins.ecobridgeDeviceManager.SonyBraviaProtocolError, Promise<unknown>>();
|
|
26
|
+
readonly #parentAbort: () => void;
|
|
27
|
+
#tail: Promise<void> = Promise.resolve();
|
|
28
|
+
#startPromise?: Promise<IBraviatvSnapshot>;
|
|
29
|
+
#snapshot?: IBraviatvSnapshot;
|
|
30
|
+
#started = false;
|
|
31
|
+
#healthy = false;
|
|
32
|
+
#closePromise?: Promise<void>;
|
|
33
|
+
#cleanupPromise?: Promise<void>;
|
|
34
|
+
#retryPromise?: Promise<void>;
|
|
35
|
+
#cleanupFailed = false;
|
|
36
|
+
|
|
37
|
+
public constructor(config: IBraviatvConfig, context: IBraviatvSetupContext, options: IBraviatvIntegrationOptions) {
|
|
38
|
+
this.config = normalizeBraviatvConfig(config);
|
|
39
|
+
this.#options = normalizeBraviatvOptions(options);
|
|
40
|
+
this.#admission = new NativeHomeAdmission('braviatv', { homeId: this.#options.homeId });
|
|
41
|
+
this.#context = this.#admission.admit(context, this.config.homeId);
|
|
42
|
+
this.#parentAbort = () => { void this.destroy().catch(() => {}); };
|
|
43
|
+
this.#context.abortSignal?.addEventListener('abort', this.#parentAbort, { once: true });
|
|
44
|
+
if (this.#context.abortSignal?.aborted) this.#parentAbort();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public isHealthy(): boolean { return !this.#lifetime.signal.aborted && this.#started && this.#healthy; }
|
|
48
|
+
|
|
49
|
+
public start(signal?: AbortSignal): Promise<IBraviatvSnapshot> {
|
|
50
|
+
if (!this.#startPromise) {
|
|
51
|
+
this.#startPromise = this.run((protocol, operation) => protocol.readState(operation), signal, false)
|
|
52
|
+
.then((state) => { const snapshot = this.observe(state); this.#started = true; return snapshot; })
|
|
53
|
+
.catch((error: unknown) => {
|
|
54
|
+
this.#healthy = false;
|
|
55
|
+
this.#lifetime.abort(this.failure('closed'));
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return this.#startPromise;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public getObservedSnapshot(): IBraviatvSnapshot {
|
|
63
|
+
this.assertOpen();
|
|
64
|
+
if (!this.#snapshot) throw new Error('BRAVIA has no admitted observation.');
|
|
65
|
+
return this.#snapshot;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public getSnapshot(signal?: AbortSignal): Promise<IBraviatvSnapshot> {
|
|
69
|
+
return this.run((protocol, operation) => protocol.readState(operation), signal).then((state) => this.observe(state));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public setControl(value: TBraviatvControl, signal?: AbortSignal): Promise<IBraviatvEffectResult> {
|
|
73
|
+
const control = validateBraviatvControl(value);
|
|
74
|
+
if (control.feature === 'speaker_volume_raw' && control.value > this.config.volumeLimitRaw) throw new TypeError('BRAVIA speaker volume exceeds the owner ceiling.');
|
|
75
|
+
return this.run((protocol, operation) => control.feature === 'power' ? protocol.setPower(control.value, operation)
|
|
76
|
+
: control.feature === 'device_mute' ? protocol.setMute(control.value, operation)
|
|
77
|
+
: protocol.setVolumeRaw(control.value, operation), signal).then((result) => {
|
|
78
|
+
try {
|
|
79
|
+
return Object.freeze({ control: result.control, requested: result.requested,
|
|
80
|
+
commandAcknowledged: result.commandAcknowledged, causalityConfirmed: result.causalityConfirmed,
|
|
81
|
+
observation: result.observation, snapshot: this.observe(result.observed) });
|
|
82
|
+
} catch {
|
|
83
|
+
throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('identity-mismatch', 'BRAVIA reported state could not be admitted after acknowledgment.', 'indeterminate', true);
|
|
110
84
|
}
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return this.normalizeSnapshot(this.snapshotFromManualConfig());
|
|
85
|
+
});
|
|
116
86
|
}
|
|
117
87
|
|
|
118
|
-
public
|
|
119
|
-
this
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
88
|
+
public destroy(): Promise<void> {
|
|
89
|
+
if (!this.#closePromise) {
|
|
90
|
+
this.#healthy = false;
|
|
91
|
+
this.#closePromise = Promise.resolve().then(() => this.drainCleanup(false));
|
|
92
|
+
this.#cleanupPromise = this.#closePromise;
|
|
93
|
+
this.#context.abortSignal?.removeEventListener('abort', this.#parentAbort);
|
|
94
|
+
this.#lifetime.abort(this.failure('closed'));
|
|
95
|
+
}
|
|
96
|
+
return this.#closePromise;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Explicit cleanup retry acknowledges settled credential failures; it cannot resolve a PSK, construct a facade or replay an effect. */
|
|
100
|
+
public retryCleanup(): Promise<void> {
|
|
101
|
+
if (!this.#closePromise) return this.destroy();
|
|
102
|
+
if (!this.#cleanupFailed) return this.#cleanupPromise ?? this.#closePromise;
|
|
103
|
+
if (!this.#retryPromise) {
|
|
104
|
+
this.#retryPromise = Promise.resolve().then(() => this.drainCleanup(true));
|
|
105
|
+
this.#cleanupPromise = this.#retryPromise;
|
|
106
|
+
const settled = (): void => { this.#retryPromise = undefined; };
|
|
107
|
+
void this.#retryPromise.then(settled, settled);
|
|
108
|
+
}
|
|
109
|
+
return this.#retryPromise;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async drainCleanup(retry: boolean): Promise<void> {
|
|
113
|
+
const protocolCleanup = [...this.#protocols.values()].map((owned) => this.closeProtocol(owned, retry));
|
|
114
|
+
const results = await Promise.allSettled([...protocolCleanup, ...this.#credentialOperations.keys(), ...this.#operations, this.#startPromise]);
|
|
115
|
+
const finalResults = await Promise.allSettled([...this.#protocols.values()].map((owned) => this.closeProtocol(owned)));
|
|
116
|
+
this.#snapshot = undefined;
|
|
117
|
+
const failures = [...results.slice(0, protocolCleanup.length), ...finalResults].filter((entry): entry is PromiseRejectedResult => entry.status === 'rejected');
|
|
118
|
+
const reasons = [...new Set(failures.map((entry) => entry.reason)), ...(!retry ? this.#credentialFailures.keys() : [])];
|
|
119
|
+
if (retry) this.#credentialFailures.clear();
|
|
120
|
+
this.#cleanupFailed = reasons.length > 0;
|
|
121
|
+
if (reasons.length) throw new AggregateError(reasons, 'BRAVIA owned operation cleanup failed.');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private closeProtocol(owned: IBraviatvOwnedProtocol, retry = false): Promise<void> {
|
|
125
|
+
if (retry && owned.failed) { owned.cleanup = undefined; owned.failed = false; }
|
|
126
|
+
if (!owned.cleanup) {
|
|
127
|
+
owned.cleanup = Promise.resolve().then(() => owned.protocol.close()).then(() => {
|
|
128
|
+
this.#protocols.delete(owned.protocol);
|
|
129
|
+
}, () => {
|
|
130
|
+
owned.failed = true;
|
|
131
|
+
throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('transport', 'BRAVIA owned protocol cleanup failed.');
|
|
132
|
+
});
|
|
124
133
|
}
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
public async turnOff(): Promise<void> {
|
|
129
|
-
await this.sendRestQuick('system', 'setPowerStatus', { status: false });
|
|
130
|
-
this.updateLocalState({ powerStatus: 'standby', power: 'off', playback: 'off' });
|
|
134
|
+
return owned.cleanup;
|
|
131
135
|
}
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.
|
|
137
|
+
private observe(state: plugins.ecobridgeDeviceManager.ISonyBraviaState): IBraviatvSnapshot {
|
|
138
|
+
this.assertOpen();
|
|
139
|
+
// Relevant readback replaces the prior observation; unrelated old state is never restamped fresh.
|
|
140
|
+
this.#snapshot = Object.freeze({ ...state, homeId: this.config.homeId, host: this.config.host,
|
|
141
|
+
port: this.config.port, name: this.config.name ?? `Sony BRAVIA ${state.identity.model}` });
|
|
142
|
+
this.#healthy = true;
|
|
143
|
+
return this.#snapshot;
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
private failure(code: 'cancelled' | 'closed' | 'timeout' | 'capacity'): plugins.ecobridgeDeviceManager.SonyBraviaProtocolError {
|
|
147
|
+
return new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError(code, code === 'closed' ? 'BRAVIA client is closed.'
|
|
148
|
+
: code === 'timeout' ? 'BRAVIA operation deadline expired.' : code === 'capacity' ? 'BRAVIA operation capacity reached.' : 'BRAVIA operation was cancelled.');
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
private assertOpen(): void {
|
|
152
|
+
if (this.#lifetime.signal.aborted) throw this.failure('closed');
|
|
153
|
+
this.#admission.admit(this.#context, this.config.homeId);
|
|
145
154
|
}
|
|
146
155
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
public async mediaPlay(): Promise<void> {
|
|
153
|
-
await this.sendCommand('Play');
|
|
154
|
-
this.updateLocalState({ playback: 'playing' });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
public async mediaPause(): Promise<void> {
|
|
158
|
-
await this.sendCommand('Pause');
|
|
159
|
-
this.updateLocalState({ playback: 'paused' });
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
public async mediaStop(): Promise<void> {
|
|
163
|
-
await this.sendCommand('Stop');
|
|
164
|
-
this.updateLocalState({ playback: 'idle' });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
public async nextTrack(): Promise<void> {
|
|
168
|
-
await this.sendCommand('Next');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public async previousTrack(): Promise<void> {
|
|
172
|
-
await this.sendCommand('Prev');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
public async selectSource(sourceArg: string): Promise<void> {
|
|
176
|
-
const source = sourceArg.trim();
|
|
177
|
-
if (!source) {
|
|
178
|
-
throw new Error('Sony Bravia TV select_source requires a source name or URI.');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const item = await this.findSource(source);
|
|
182
|
-
await this.startSource(item.uri, item.type);
|
|
183
|
-
this.updateLocalState({ powerStatus: 'active', power: 'on', source: item.title, sourceUri: item.uri });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
public async playMedia(mediaTypeArg: string, mediaIdArg: string): Promise<void> {
|
|
187
|
-
const normalizedType = mediaTypeArg.toLowerCase();
|
|
188
|
-
if (normalizedType !== 'app' && normalizedType !== 'channel') {
|
|
189
|
-
throw new Error(`Sony Bravia TV play_media supports app or channel media only, not ${mediaTypeArg}.`);
|
|
190
|
-
}
|
|
191
|
-
const source = await this.findSource(mediaIdArg, normalizedType as TBraviatvSourceType);
|
|
192
|
-
await this.startSource(source.uri, source.type);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
public async sendCommand(commandArg: string): Promise<void> {
|
|
196
|
-
const code = await this.commandCode(commandArg);
|
|
197
|
-
if (!code) {
|
|
198
|
-
throw new Error(`Unsupported Sony Bravia TV IRCC command: ${commandArg}`);
|
|
199
|
-
}
|
|
200
|
-
await this.sendIrcc(code);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
public async sendCommands(commandsArg: string[], repeatsArg = 1): Promise<void> {
|
|
204
|
-
const repeats = Math.max(1, Math.floor(repeatsArg));
|
|
205
|
-
for (let repeat = 0; repeat < repeats; repeat += 1) {
|
|
206
|
-
for (const command of commandsArg) {
|
|
207
|
-
await this.sendCommand(command);
|
|
208
|
-
}
|
|
156
|
+
private active(signal: AbortSignal, deadline: number): number {
|
|
157
|
+
if (signal.aborted) {
|
|
158
|
+
const reason: unknown = signal.reason;
|
|
159
|
+
throw reason instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError ? reason : this.failure('cancelled');
|
|
209
160
|
}
|
|
161
|
+
this.assertOpen();
|
|
162
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
163
|
+
if (remaining < 1) throw this.failure('timeout');
|
|
164
|
+
return remaining;
|
|
210
165
|
}
|
|
211
166
|
|
|
212
|
-
|
|
213
|
-
|
|
167
|
+
private credentialFailure(): plugins.ecobridgeDeviceManager.SonyBraviaProtocolError {
|
|
168
|
+
return new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('invalid-options', 'BRAVIA caller credential resolution failed.');
|
|
214
169
|
}
|
|
215
170
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
private async getLiveSnapshot(): Promise<IBraviatvSnapshot> {
|
|
223
|
-
const powerStatus = await this.getPowerStatus();
|
|
224
|
-
const systemInfo = await this.getSystemInfo().catch(() => this.systemInfoFromConfig());
|
|
225
|
-
const state: IBraviatvState = {
|
|
226
|
-
...this.config.state,
|
|
227
|
-
powerStatus,
|
|
228
|
-
power: this.powerFromStatus(powerStatus),
|
|
229
|
-
available: true,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
let sources = this.config.sources || [];
|
|
233
|
-
let apps = this.config.apps || [];
|
|
234
|
-
let channels = this.config.channels || [];
|
|
235
|
-
let commands = this.config.commands;
|
|
236
|
-
|
|
237
|
-
if (state.power === 'on') {
|
|
238
|
-
const [volumeInfo, playingInfo, liveSources, liveApps, liveChannels, liveCommands] = await Promise.all([
|
|
239
|
-
this.getVolumeInfo().catch(() => undefined),
|
|
240
|
-
this.getPlayingInfo().catch(() => undefined),
|
|
241
|
-
this.getExternalInputsStatus().catch(() => sources),
|
|
242
|
-
this.getAppList().catch(() => apps),
|
|
243
|
-
this.config.fetchChannels ? this.getContentListAll('tv').catch(() => channels) : Promise.resolve(channels),
|
|
244
|
-
this.getCommandList().catch(() => commands),
|
|
245
|
-
]);
|
|
246
|
-
sources = liveSources;
|
|
247
|
-
apps = liveApps;
|
|
248
|
-
channels = liveChannels;
|
|
249
|
-
commands = liveCommands;
|
|
250
|
-
this.applyVolumeInfo(state, volumeInfo);
|
|
251
|
-
this.applyPlayingInfo(state, playingInfo);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return this.normalizeSnapshot({
|
|
255
|
-
systemInfo,
|
|
256
|
-
state,
|
|
257
|
-
sources,
|
|
258
|
-
apps,
|
|
259
|
-
channels,
|
|
260
|
-
commands,
|
|
261
|
-
updatedAt: new Date().toISOString(),
|
|
171
|
+
private async resolvePsk(signal: AbortSignal, deadline: number, operation: Promise<unknown>): Promise<string> {
|
|
172
|
+
this.active(signal, deadline);
|
|
173
|
+
let cancel: () => void = () => {};
|
|
174
|
+
const cancellation = new Promise<never>((_resolve, reject) => {
|
|
175
|
+
cancel = () => { try { this.active(signal, deadline); } catch (error) { reject(error); } };
|
|
176
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
262
177
|
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private async getPowerStatus(): Promise<string> {
|
|
266
|
-
const response = await this.sendRest('system', 'getPowerStatus', undefined, defaultRestVersion, 5000);
|
|
267
|
-
return this.firstResult<{ status?: string }>(response)?.status || 'unknown';
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private async getSystemInfo(): Promise<IBraviatvSystemInfo> {
|
|
271
|
-
const response = await this.sendRest('system', 'getSystemInformation');
|
|
272
|
-
const info = this.firstResult<IBraviatvSystemInfo>(response) || {};
|
|
273
|
-
this.config.macAddress = info.macAddr || this.config.macAddress;
|
|
274
|
-
this.config.cid = info.cid || this.config.cid;
|
|
275
|
-
return info;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private async getVolumeInfo(targetArg = defaultAudioTarget): Promise<IBraviatvVolumeInfo | undefined> {
|
|
279
|
-
const response = await this.sendRest('audio', 'getVolumeInformation');
|
|
280
|
-
const outputs = this.firstResult<IBraviatvVolumeInfo[]>(response) || [];
|
|
281
|
-
return outputs.find((outputArg) => outputArg.target === targetArg) || outputs[0];
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private async getPlayingInfo(): Promise<IBraviatvPlayingInfo | undefined> {
|
|
285
|
-
const response = await this.sendRest('avContent', 'getPlayingContentInfo');
|
|
286
|
-
return this.firstResult<IBraviatvPlayingInfo>(response);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private async getExternalInputsStatus(): Promise<IBraviatvSource[]> {
|
|
290
|
-
const response = await this.sendRest('avContent', 'getCurrentExternalInputsStatus');
|
|
291
|
-
return (this.firstResult<IBraviatvSource[]>(response) || []).map((sourceArg) => ({ ...sourceArg, type: 'input' }));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private async getAppList(): Promise<IBraviatvApp[]> {
|
|
295
|
-
const response = await this.sendRest('appControl', 'getApplicationList');
|
|
296
|
-
return (this.firstResult<IBraviatvApp[]>(response) || []).map((appArg) => ({ ...appArg, type: 'app' }));
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
private async getSourceList(schemeArg: string): Promise<string[]> {
|
|
300
|
-
const response = await this.sendRest('avContent', 'getSourceList', { scheme: schemeArg });
|
|
301
|
-
return (this.firstResult<Array<{ source?: string }>>(response) || []).map((itemArg) => itemArg.source).filter((itemArg): itemArg is string => Boolean(itemArg));
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async getContentCount(sourceArg: string): Promise<number> {
|
|
305
|
-
const response = await this.sendRest('avContent', 'getContentCount', { source: sourceArg }, defaultRestVersion, 20000);
|
|
306
|
-
return this.firstResult<{ count?: number }>(response)?.count || 0;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private async getContentList(sourceArg: string, indexArg: number, countArg: number): Promise<IBraviatvChannel[]> {
|
|
310
|
-
const response = await this.sendRest('avContent', 'getContentList', { source: sourceArg, stIdx: indexArg, cnt: countArg }, defaultRestVersion, 20000);
|
|
311
|
-
return (this.firstResult<IBraviatvChannel[]>(response) || []).map((channelArg) => ({ ...channelArg, type: 'channel' }));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private async getContentListAll(schemeArg: string): Promise<IBraviatvChannel[]> {
|
|
315
|
-
const channels: IBraviatvChannel[] = [];
|
|
316
|
-
for (const source of await this.getSourceList(schemeArg)) {
|
|
317
|
-
const total = await this.getContentCount(source);
|
|
318
|
-
for (let index = 0; index < total; index += 50) {
|
|
319
|
-
channels.push(...await this.getContentList(source, index, Math.min(50, total - index)));
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
return channels;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
private async getCommandList(): Promise<Record<string, string>> {
|
|
326
|
-
if (this.commandCache) {
|
|
327
|
-
return this.commandCache;
|
|
328
|
-
}
|
|
329
|
-
const response = await this.sendRest('system', 'getRemoteControllerInfo');
|
|
330
|
-
const remoteCommands = Array.isArray(response.result?.[1]) ? response.result[1] as Array<{ name?: string; value?: string }> : [];
|
|
331
|
-
this.commandCache = {
|
|
332
|
-
...fallbackCommands,
|
|
333
|
-
...Object.fromEntries(remoteCommands.filter((itemArg) => itemArg.name && itemArg.value).map((itemArg) => [itemArg.name as string, itemArg.value as string])),
|
|
334
|
-
};
|
|
335
|
-
return this.commandCache;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private async stepVolume(stepArg: number): Promise<void> {
|
|
339
|
-
const prefix = stepArg > 0 ? '+' : '';
|
|
340
|
-
await this.sendRestQuick('audio', 'setAudioVolume', { target: defaultAudioTarget, volume: `${prefix}${stepArg}` });
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
private async findSource(queryArg: string, sourceTypeArg?: TBraviatvSourceType): Promise<Required<Pick<IBraviatvSource, 'title' | 'uri' | 'type'>>> {
|
|
344
|
-
const direct = this.directSource(queryArg, sourceTypeArg);
|
|
345
|
-
if (direct) {
|
|
346
|
-
return direct;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const snapshot = await this.getSnapshot();
|
|
350
|
-
const items = [...snapshot.sources, ...snapshot.apps, ...(snapshot.channels || [])].map((itemArg) => ({
|
|
351
|
-
...itemArg,
|
|
352
|
-
type: itemArg.type || this.inferSourceType(itemArg.uri),
|
|
353
|
-
}));
|
|
354
|
-
const exact = items.find((itemArg) => (!sourceTypeArg || itemArg.type === sourceTypeArg) && (itemArg.uri === queryArg || itemArg.title.toLowerCase() === queryArg.toLowerCase()));
|
|
355
|
-
if (exact) {
|
|
356
|
-
return { title: exact.title, uri: exact.uri, type: exact.type };
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const coarse = items.find((itemArg) => (!sourceTypeArg || itemArg.type === sourceTypeArg) && itemArg.title.toLowerCase().includes(queryArg.toLowerCase()));
|
|
360
|
-
if (coarse) {
|
|
361
|
-
return { title: coarse.title, uri: coarse.uri, type: coarse.type };
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
throw new Error(`Sony Bravia TV source is not known: ${queryArg}`);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private directSource(sourceArg: string, sourceTypeArg?: TBraviatvSourceType): Required<Pick<IBraviatvSource, 'title' | 'uri' | 'type'>> | undefined {
|
|
368
|
-
if (sourceArg.startsWith('extInput:') || sourceArg.startsWith('tv:')) {
|
|
369
|
-
return { title: sourceArg, uri: sourceArg, type: sourceArg.startsWith('tv:') ? 'channel' : 'input' };
|
|
370
|
-
}
|
|
371
|
-
if (sourceArg.startsWith('com.sony.dtv.') || sourceArg.startsWith('localapp:')) {
|
|
372
|
-
return { title: sourceArg, uri: sourceArg, type: sourceTypeArg || 'app' };
|
|
373
|
-
}
|
|
374
|
-
return undefined;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
private async startSource(uriArg: string, sourceTypeArg: TBraviatvSourceType): Promise<void> {
|
|
378
|
-
if (sourceTypeArg === 'app') {
|
|
379
|
-
await this.sendRestQuick('appControl', 'setActiveApp', { uri: uriArg });
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
await this.sendRestQuick('avContent', 'setPlayContent', { uri: uriArg });
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
private async sendRestQuick(serviceArg: TBraviatvRestService, methodArg: string, paramsArg?: unknown, versionArg = defaultRestVersion): Promise<boolean> {
|
|
386
|
-
const response = await this.sendRest(serviceArg, methodArg, paramsArg, versionArg);
|
|
387
|
-
return Boolean(response.result);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
private async sendRest(serviceArg: TBraviatvRestService, methodArg: string, paramsArg?: unknown, versionArg = defaultRestVersion, timeoutMsArg?: number): Promise<IBraviatvRestResponse> {
|
|
391
|
-
this.assertLivePskSupported(`${serviceArg}.${methodArg}`);
|
|
392
|
-
const params = paramsArg === undefined ? [] : Array.isArray(paramsArg) ? paramsArg : [paramsArg];
|
|
393
|
-
const response = await this.postJson(this.serviceUrl(serviceArg), {
|
|
394
|
-
method: methodArg,
|
|
395
|
-
params,
|
|
396
|
-
id: 1,
|
|
397
|
-
version: versionArg,
|
|
398
|
-
}, timeoutMsArg);
|
|
399
|
-
|
|
400
|
-
if (response.error) {
|
|
401
|
-
const [code, message] = response.error;
|
|
402
|
-
const text = String(message || code || 'Unknown REST API error');
|
|
403
|
-
if (Number(code) === 401) {
|
|
404
|
-
throw new BraviatvAuthError(`Sony Bravia TV authentication failed for ${methodArg}: ${text}`);
|
|
405
|
-
}
|
|
406
|
-
if (text.includes('not power-on')) {
|
|
407
|
-
throw new BraviatvLiveControlError(`Sony Bravia TV is turned off and rejected ${methodArg}.`);
|
|
408
|
-
}
|
|
409
|
-
throw new BraviatvLiveControlError(`Sony Bravia TV REST ${methodArg} failed: ${text}`);
|
|
410
|
-
}
|
|
411
|
-
return response;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
private async sendIrcc(codeArg: string): Promise<boolean> {
|
|
415
|
-
this.assertLivePskSupported('IRCC command');
|
|
416
178
|
try {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
'
|
|
431
|
-
|
|
432
|
-
|
|
179
|
+
const credential = Promise.resolve().then(() => {
|
|
180
|
+
this.active(signal, deadline);
|
|
181
|
+
return this.#options.resolvePsk(Object.freeze({ homeId: this.config.homeId,
|
|
182
|
+
credentialRef: this.config.credentialRef, signal, deadline }));
|
|
183
|
+
});
|
|
184
|
+
// Own the actual callback before entering it; cancelling its request does not settle this work.
|
|
185
|
+
this.#credentialOperations.set(credential, operation);
|
|
186
|
+
void credential.then(() => { this.#credentialOperations.delete(credential); }, (error: unknown) => {
|
|
187
|
+
this.#credentialOperations.delete(credential);
|
|
188
|
+
if (signal.aborted && error !== signal.reason) this.#credentialFailures.set(this.credentialFailure(), operation);
|
|
189
|
+
});
|
|
190
|
+
const psk = await Promise.race([credential, cancellation]);
|
|
191
|
+
this.active(signal, deadline);
|
|
192
|
+
if (typeof psk !== 'string') throw new Error('Invalid credential.');
|
|
193
|
+
return psk;
|
|
194
|
+
} catch {
|
|
195
|
+
this.active(signal, deadline);
|
|
196
|
+
throw this.credentialFailure();
|
|
197
|
+
} finally { signal.removeEventListener('abort', cancel); }
|
|
433
198
|
}
|
|
434
199
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
200
|
+
/** Admission deadline includes queue, caller credential resolution and owned transport drain. */
|
|
201
|
+
private run<TResult>(execute: (protocol: plugins.ecobridgeDeviceManager.SonyBraviaProtocol,
|
|
202
|
+
operation: plugins.ecobridgeDeviceManager.ISonyBraviaOperationOptions) => Promise<TResult>, signal?: AbortSignal,
|
|
203
|
+
requireStarted = true): Promise<TResult> {
|
|
439
204
|
try {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
return
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
private async request(urlArg: string, headersArg: Record<string, string>, bodyArg: string, timeoutMsArg?: number): Promise<string> {
|
|
205
|
+
this.assertOpen();
|
|
206
|
+
if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('BRAVIA cancellation requires an AbortSignal.');
|
|
207
|
+
if (signal?.aborted) throw this.failure('cancelled');
|
|
208
|
+
if (requireStarted && !this.#started) throw new Error('BRAVIA client has not started.');
|
|
209
|
+
const retained = [...this.#protocols.values()].filter((owned) => owned.failed && !owned.active).length;
|
|
210
|
+
const retainedCredentials = [...this.#credentialOperations.values(), ...this.#credentialFailures.values()]
|
|
211
|
+
.filter((operation) => !this.#operations.has(operation)).length;
|
|
212
|
+
if (this.#operations.size + retained + retainedCredentials >= this.config.maxPendingOperations) throw this.failure('capacity');
|
|
213
|
+
} catch (error) { return Promise.reject(error); }
|
|
214
|
+
const deadline = performance.now() + this.config.timeoutMs;
|
|
452
215
|
const controller = new AbortController();
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (!this.config.host) {
|
|
489
|
-
throw new BraviatvLiveControlError(`Sony Bravia TV host is required for live ${commandArg}.`);
|
|
490
|
-
}
|
|
491
|
-
if (!this.config.psk) {
|
|
492
|
-
if (this.config.pin || this.config.usePsk === false) {
|
|
493
|
-
throw new BraviatvLiveControlError(`Sony Bravia TV PIN pairing/cookie authentication is not implemented in this native TypeScript port. Configure a PSK to use live ${commandArg}.`);
|
|
494
|
-
}
|
|
495
|
-
throw new BraviatvLiveControlError(`Sony Bravia TV PSK is required for live ${commandArg}.`);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
private hasLivePskConfig(): boolean {
|
|
500
|
-
return Boolean(this.config.host && this.config.psk);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
private serviceUrl(serviceArg: string): string {
|
|
504
|
-
const protocol = this.config.useSsl ? 'https' : 'http';
|
|
505
|
-
const port = this.config.port ? `:${this.config.port}` : '';
|
|
506
|
-
return `${protocol}://${this.config.host}${port}/sony/${serviceArg}`;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
private irccBody(codeArg: string): string {
|
|
510
|
-
return `<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendIRCC xmlns:u="urn:schemas-sony-com:service:IRCC:1"><IRCCCode>${codeArg}</IRCCCode></u:X_SendIRCC></s:Body></s:Envelope>`;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
private firstResult<TResult>(responseArg: IBraviatvRestResponse): TResult | undefined {
|
|
514
|
-
return responseArg.result?.[0] as TResult | undefined;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
private async commandCode(commandArg: string): Promise<string | undefined> {
|
|
518
|
-
const command = commandArg.trim();
|
|
519
|
-
if (this.looksLikeIrccCode(command)) {
|
|
520
|
-
return command;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const commands = await this.getCommandList().catch(() => ({ ...fallbackCommands, ...(this.config.commands || {}) }));
|
|
524
|
-
const candidates = [command, this.normalizeCommand(command)].filter(Boolean);
|
|
525
|
-
for (const candidate of candidates) {
|
|
526
|
-
if (commands[candidate]) {
|
|
527
|
-
return commands[candidate];
|
|
216
|
+
const cancel = () => controller.abort(this.failure('cancelled'));
|
|
217
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
218
|
+
const combined = AbortSignal.any([controller.signal, this.#lifetime.signal]);
|
|
219
|
+
const timer = setTimeout(() => controller.abort(this.failure('timeout')), this.config.timeoutMs);
|
|
220
|
+
let cancelWaiting: () => void = () => {};
|
|
221
|
+
const waiting = new Promise<never>((_resolve, reject) => {
|
|
222
|
+
cancelWaiting = () => { try { this.active(combined, deadline); } catch (error) { reject(error); } };
|
|
223
|
+
combined.addEventListener('abort', cancelWaiting, { once: true });
|
|
224
|
+
});
|
|
225
|
+
let ownedProtocol: IBraviatvOwnedProtocol | undefined;
|
|
226
|
+
const operation = this.#tail.then(async () => {
|
|
227
|
+
combined.removeEventListener('abort', cancelWaiting);
|
|
228
|
+
this.active(combined, deadline);
|
|
229
|
+
const psk = await this.resolvePsk(combined, deadline, operation);
|
|
230
|
+
const timeoutMs = this.active(combined, deadline);
|
|
231
|
+
const protocol = new plugins.ecobridgeDeviceManager.SonyBraviaProtocol({ address: this.config.host,
|
|
232
|
+
expectedModel: this.config.expectedModel, expectedMacAddress: this.config.expectedMacAddress,
|
|
233
|
+
...(this.config.expectedSerial === undefined ? {} : { expectedSerial: this.config.expectedSerial }), psk,
|
|
234
|
+
allowUnencryptedLan: true, volumeLimitRaw: this.config.volumeLimitRaw, port: this.config.port,
|
|
235
|
+
timeoutMs, maxPendingOperations: 1, maxResponseBytes: this.config.maxResponseBytes });
|
|
236
|
+
const owned: IBraviatvOwnedProtocol = { protocol, failed: false, active: true };
|
|
237
|
+
ownedProtocol = owned;
|
|
238
|
+
this.#protocols.set(protocol, owned);
|
|
239
|
+
let result: TResult | undefined;
|
|
240
|
+
let failure: unknown;
|
|
241
|
+
try {
|
|
242
|
+
this.active(combined, deadline);
|
|
243
|
+
result = await execute(protocol, { signal: combined, timeoutMs: this.active(combined, deadline) });
|
|
244
|
+
} catch (error) { failure = error; }
|
|
245
|
+
try { await this.closeProtocol(owned); }
|
|
246
|
+
catch {
|
|
247
|
+
const acknowledged = (result as Partial<plugins.ecobridgeDeviceManager.ISonyBraviaControlObservation>)?.commandAcknowledged === true
|
|
248
|
+
|| failure instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError && failure.commandAcknowledged;
|
|
249
|
+
throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError('transport', 'BRAVIA owned protocol cleanup failed.',
|
|
250
|
+
acknowledged ? 'indeterminate' : failure instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError ? failure.effectOutcome : 'not-sent', acknowledged);
|
|
528
251
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
252
|
+
if (failure !== undefined) throw failure;
|
|
253
|
+
try { this.active(combined, deadline); }
|
|
254
|
+
catch (error) {
|
|
255
|
+
const acknowledged = (result as Partial<plugins.ecobridgeDeviceManager.ISonyBraviaControlObservation>)?.commandAcknowledged === true;
|
|
256
|
+
if (acknowledged) throw new plugins.ecobridgeDeviceManager.SonyBraviaProtocolError(
|
|
257
|
+
error instanceof plugins.ecobridgeDeviceManager.SonyBraviaProtocolError ? error.code : 'cancelled',
|
|
258
|
+
'BRAVIA operation ended after acknowledgment.', 'indeterminate', true);
|
|
259
|
+
throw error;
|
|
533
260
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
hdmi3: 'Hdmi3',
|
|
548
|
-
hdmi4: 'Hdmi4',
|
|
549
|
-
up: 'Up',
|
|
550
|
-
down: 'Down',
|
|
551
|
-
left: 'Left',
|
|
552
|
-
right: 'Right',
|
|
553
|
-
ok: 'Confirm',
|
|
554
|
-
enter: 'Confirm',
|
|
555
|
-
select: 'Confirm',
|
|
556
|
-
confirm: 'Confirm',
|
|
557
|
-
back: 'Back',
|
|
558
|
-
return: 'Back',
|
|
559
|
-
home: 'Home',
|
|
560
|
-
menu: 'Home',
|
|
561
|
-
options: 'Options',
|
|
562
|
-
info: 'Display',
|
|
563
|
-
display: 'Display',
|
|
564
|
-
volumeup: 'VolumeUp',
|
|
565
|
-
volup: 'VolumeUp',
|
|
566
|
-
volumedown: 'VolumeDown',
|
|
567
|
-
voldown: 'VolumeDown',
|
|
568
|
-
mute: 'Mute',
|
|
569
|
-
channelup: 'ChannelUp',
|
|
570
|
-
chup: 'ChannelUp',
|
|
571
|
-
channeldown: 'ChannelDown',
|
|
572
|
-
chdown: 'ChannelDown',
|
|
573
|
-
play: 'Play',
|
|
574
|
-
pause: 'Pause',
|
|
575
|
-
stop: 'Stop',
|
|
576
|
-
next: 'Next',
|
|
577
|
-
previous: 'Prev',
|
|
578
|
-
prev: 'Prev',
|
|
579
|
-
};
|
|
580
|
-
return aliases[normalized] || commandArg;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
private looksLikeIrccCode(valueArg: string): boolean {
|
|
584
|
-
return /^[A-Za-z0-9+/]+={0,2}$/.test(valueArg) && valueArg.length >= 16 && valueArg.includes('Aw');
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
private snapshotFromManualConfig(errorMessageArg?: string): IBraviatvSnapshot {
|
|
588
|
-
const systemInfo = this.systemInfoFromConfig();
|
|
589
|
-
const state: IBraviatvState = {
|
|
590
|
-
powerStatus: this.config.state?.powerStatus || (this.config.state?.power === 'on' ? 'active' : this.config.state?.power === 'off' ? 'standby' : 'unknown'),
|
|
591
|
-
available: Boolean(this.config.snapshot || this.config.state || this.config.systemInfo || this.config.host),
|
|
592
|
-
...this.config.state,
|
|
593
|
-
lastError: errorMessageArg || this.config.state?.lastError,
|
|
594
|
-
};
|
|
595
|
-
return {
|
|
596
|
-
systemInfo,
|
|
597
|
-
state,
|
|
598
|
-
sources: [...(this.config.sources || [])],
|
|
599
|
-
apps: [...(this.config.apps || [])],
|
|
600
|
-
channels: [...(this.config.channels || [])],
|
|
601
|
-
commands: this.config.commands,
|
|
602
|
-
updatedAt: new Date().toISOString(),
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
private systemInfoFromConfig(): IBraviatvSystemInfo {
|
|
607
|
-
return {
|
|
608
|
-
...this.config.systemInfo,
|
|
609
|
-
name: this.config.systemInfo?.name || this.config.name || this.config.host || 'Sony Bravia TV',
|
|
610
|
-
model: this.config.systemInfo?.model || this.config.model,
|
|
611
|
-
serial: this.config.systemInfo?.serial || this.config.serialNumber,
|
|
612
|
-
macAddr: this.config.systemInfo?.macAddr || this.config.macAddress,
|
|
613
|
-
cid: this.config.systemInfo?.cid || this.config.cid || this.config.macAddress || this.config.host,
|
|
614
|
-
product: this.config.systemInfo?.product || 'TV',
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
private normalizeSnapshot(snapshotArg: IBraviatvSnapshot): IBraviatvSnapshot {
|
|
619
|
-
const systemInfo = {
|
|
620
|
-
...this.systemInfoFromConfig(),
|
|
621
|
-
...snapshotArg.systemInfo,
|
|
622
|
-
};
|
|
623
|
-
const state = this.normalizeState(snapshotArg.state);
|
|
624
|
-
return {
|
|
625
|
-
...snapshotArg,
|
|
626
|
-
systemInfo,
|
|
627
|
-
state,
|
|
628
|
-
sources: this.normalizeSources(snapshotArg.sources, 'input'),
|
|
629
|
-
apps: this.normalizeSources(snapshotArg.apps, 'app') as IBraviatvApp[],
|
|
630
|
-
channels: this.normalizeSources(snapshotArg.channels || [], 'channel') as IBraviatvChannel[],
|
|
631
|
-
updatedAt: snapshotArg.updatedAt || new Date().toISOString(),
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
private normalizeState(stateArg: IBraviatvState): IBraviatvState {
|
|
636
|
-
const powerStatus = stateArg.powerStatus || (stateArg.power === 'on' ? 'active' : stateArg.power === 'off' ? 'standby' : 'unknown');
|
|
637
|
-
const power = stateArg.power || this.powerFromStatus(powerStatus);
|
|
638
|
-
let volumeLevel = stateArg.volumeLevel;
|
|
639
|
-
if (typeof volumeLevel === 'number' && volumeLevel > 1) {
|
|
640
|
-
volumeLevel /= 100;
|
|
641
|
-
}
|
|
642
|
-
if (typeof volumeLevel !== 'number' && typeof stateArg.volumePercent === 'number') {
|
|
643
|
-
volumeLevel = stateArg.volumePercent / 100;
|
|
644
|
-
}
|
|
645
|
-
return {
|
|
646
|
-
...stateArg,
|
|
647
|
-
powerStatus,
|
|
648
|
-
power,
|
|
649
|
-
playback: stateArg.playback || (power === 'off' ? 'off' : 'idle'),
|
|
650
|
-
volumeLevel,
|
|
651
|
-
volumePercent: typeof stateArg.volumePercent === 'number' ? stateArg.volumePercent : typeof volumeLevel === 'number' ? Math.round(volumeLevel * 100) : undefined,
|
|
652
|
-
};
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
private normalizeSources(sourcesArg: IBraviatvSource[], typeArg: TBraviatvSourceType): IBraviatvSource[] {
|
|
656
|
-
return sourcesArg.filter((sourceArg) => sourceArg.title && sourceArg.uri).map((sourceArg) => ({
|
|
657
|
-
...sourceArg,
|
|
658
|
-
type: sourceArg.type || typeArg,
|
|
659
|
-
}));
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
private applyVolumeInfo(stateArg: IBraviatvState, volumeInfoArg: IBraviatvVolumeInfo | undefined): void {
|
|
663
|
-
if (!volumeInfoArg) {
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const volume = typeof volumeInfoArg.volume === 'number' ? volumeInfoArg.volume : Number(volumeInfoArg.volume);
|
|
667
|
-
if (Number.isFinite(volume)) {
|
|
668
|
-
stateArg.volumePercent = volume;
|
|
669
|
-
stateArg.volumeLevel = volume / 100;
|
|
670
|
-
}
|
|
671
|
-
stateArg.muted = volumeInfoArg.mute;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
private applyPlayingInfo(stateArg: IBraviatvState, playingInfoArg: IBraviatvPlayingInfo | undefined): void {
|
|
675
|
-
if (!playingInfoArg) {
|
|
676
|
-
stateArg.mediaTitle = stateArg.mediaTitle || 'Smart TV';
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
stateArg.mediaTitle = playingInfoArg.programTitle || playingInfoArg.title || stateArg.mediaTitle;
|
|
680
|
-
stateArg.mediaContentId = playingInfoArg.uri || playingInfoArg.dispNum || stateArg.mediaContentId;
|
|
681
|
-
stateArg.sourceUri = playingInfoArg.uri || stateArg.sourceUri;
|
|
682
|
-
stateArg.mediaDuration = playingInfoArg.durationSec;
|
|
683
|
-
if (playingInfoArg.uri?.startsWith('extInput:')) {
|
|
684
|
-
stateArg.source = playingInfoArg.title || stateArg.source;
|
|
685
|
-
}
|
|
686
|
-
if (playingInfoArg.uri?.startsWith('tv:')) {
|
|
687
|
-
stateArg.mediaChannel = playingInfoArg.title || playingInfoArg.dispNum;
|
|
688
|
-
stateArg.mediaContentType = 'channel';
|
|
689
|
-
} else if (playingInfoArg.uri) {
|
|
690
|
-
stateArg.mediaContentType = this.inferSourceType(playingInfoArg.uri);
|
|
691
|
-
}
|
|
692
|
-
if (playingInfoArg.startDateTime) {
|
|
693
|
-
const startTime = Date.parse(playingInfoArg.startDateTime);
|
|
694
|
-
if (Number.isFinite(startTime)) {
|
|
695
|
-
stateArg.mediaPosition = Math.max(0, Math.floor((Date.now() - startTime) / 1000));
|
|
696
|
-
stateArg.mediaPositionUpdatedAt = new Date().toISOString();
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
private inferSourceType(uriArg: string): TBraviatvSourceType {
|
|
702
|
-
if (uriArg.startsWith('extInput:')) {
|
|
703
|
-
return 'input';
|
|
704
|
-
}
|
|
705
|
-
if (uriArg.startsWith('tv:')) {
|
|
706
|
-
return 'channel';
|
|
707
|
-
}
|
|
708
|
-
return 'app';
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
private powerFromStatus(statusArg: string): 'on' | 'off' | 'unknown' {
|
|
712
|
-
const status = statusArg.toLowerCase();
|
|
713
|
-
if (status === 'active') {
|
|
714
|
-
return 'on';
|
|
715
|
-
}
|
|
716
|
-
if (status === 'standby' || status === 'off') {
|
|
717
|
-
return 'off';
|
|
718
|
-
}
|
|
719
|
-
return 'unknown';
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
private updateLocalState(stateArg: Partial<IBraviatvState>): void {
|
|
723
|
-
const state = this.config.snapshot?.state || this.config.state || {};
|
|
724
|
-
Object.assign(state, stateArg);
|
|
725
|
-
if (this.config.snapshot) {
|
|
726
|
-
this.config.snapshot.state = state;
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
this.config.state = state;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
private cloneSnapshot(snapshotArg: IBraviatvSnapshot): IBraviatvSnapshot {
|
|
733
|
-
return JSON.parse(JSON.stringify(snapshotArg)) as IBraviatvSnapshot;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
private errorMessage(errorArg: unknown): string {
|
|
737
|
-
return errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
261
|
+
return result as TResult;
|
|
262
|
+
}).catch((error: unknown) => { this.#healthy = false; throw error; }).finally(() => {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
signal?.removeEventListener('abort', cancel);
|
|
265
|
+
combined.removeEventListener('abort', cancelWaiting);
|
|
266
|
+
if (ownedProtocol) ownedProtocol.active = false;
|
|
267
|
+
this.#operations.delete(operation);
|
|
268
|
+
});
|
|
269
|
+
this.#operations.add(operation);
|
|
270
|
+
this.#tail = operation.then(() => {}, () => {});
|
|
271
|
+
if (signal?.aborted) cancel();
|
|
272
|
+
if (combined.aborted) cancelWaiting();
|
|
273
|
+
return Promise.race([operation, waiting]);
|
|
738
274
|
}
|
|
739
275
|
}
|