@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,462 +1,153 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'29': 'USB',
|
|
36
|
-
'2B': 'NET',
|
|
37
|
-
'2E': 'Bluetooth',
|
|
38
|
-
'2F': 'USB DAC',
|
|
39
|
-
'40': 'Universal Port',
|
|
40
|
-
'44': 'Optical',
|
|
41
|
-
'45': 'Coaxial',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const onkyoListeningModes: Record<string, string> = {
|
|
45
|
-
'00': 'Stereo',
|
|
46
|
-
'01': 'Direct',
|
|
47
|
-
'02': 'Surround',
|
|
48
|
-
'0C': 'All Channel Stereo',
|
|
49
|
-
'0F': 'Mono',
|
|
50
|
-
'11': 'Pure Audio',
|
|
51
|
-
'40': 'Straight Decode',
|
|
52
|
-
FF: 'Auto Surround',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const hdmiOutputCodes: Record<string, string> = {
|
|
56
|
-
no: '00',
|
|
57
|
-
analog: '00',
|
|
58
|
-
yes: '01',
|
|
59
|
-
out: '01',
|
|
60
|
-
main: '01',
|
|
61
|
-
'out-sub': '02',
|
|
62
|
-
sub: '03',
|
|
63
|
-
hdbaset: '02',
|
|
64
|
-
both: '03',
|
|
65
|
-
up: 'UP',
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export class OnkyoClient extends SimpleLocalClient<IOnkyoConfig> {
|
|
69
|
-
private currentOnkyoSnapshot?: IOnkyoSnapshot;
|
|
70
|
-
|
|
71
|
-
constructor(private readonly configArg: IOnkyoConfig) {
|
|
72
|
-
super(onkyoProfile, configArg);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public async getSnapshot(forceRefreshArg = false): Promise<IOnkyoSnapshot> {
|
|
76
|
-
if (!forceRefreshArg && this.currentOnkyoSnapshot) {
|
|
77
|
-
return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (!forceRefreshArg && this.configArg.snapshot) {
|
|
81
|
-
this.currentOnkyoSnapshot = await super.getSnapshot(false) as IOnkyoSnapshot;
|
|
82
|
-
return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (this.configArg.client || this.configArg.snapshotProvider) {
|
|
86
|
-
this.currentOnkyoSnapshot = await super.getSnapshot(forceRefreshArg) as IOnkyoSnapshot;
|
|
87
|
-
return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (this.configArg.host && this.configArg.transport !== 'snapshot') {
|
|
91
|
-
try {
|
|
92
|
-
this.currentOnkyoSnapshot = await this.readEiscpSnapshot();
|
|
93
|
-
} catch (errorArg) {
|
|
94
|
-
this.currentOnkyoSnapshot = this.createOfflineSnapshot(errorMessage(errorArg));
|
|
95
|
-
}
|
|
96
|
-
return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
this.currentOnkyoSnapshot = await super.getSnapshot(forceRefreshArg) as IOnkyoSnapshot;
|
|
100
|
-
return SimpleLocalMapper.clone(this.currentOnkyoSnapshot) as IOnkyoSnapshot;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
public async refresh(): Promise<IServiceCallResult> {
|
|
104
|
-
this.currentOnkyoSnapshot = undefined;
|
|
105
|
-
const snapshot = await this.getSnapshot(true);
|
|
106
|
-
const success = snapshot.online && !snapshot.error;
|
|
107
|
-
return { success, error: success ? undefined : snapshot.error, data: snapshot };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
public async execute(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
|
|
111
|
-
if (!this.configArg.host || this.configArg.transport === 'snapshot') {
|
|
112
|
-
return super.execute(requestArg);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
const data = await this.executeEiscpCommand(requestArg);
|
|
117
|
-
this.currentOnkyoSnapshot = undefined;
|
|
118
|
-
return { success: true, data };
|
|
119
|
-
} catch (errorArg) {
|
|
120
|
-
return { success: false, error: errorMessage(errorArg) };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private async readEiscpSnapshot(): Promise<IOnkyoSnapshot> {
|
|
125
|
-
const state = await this.queryReceiverState();
|
|
126
|
-
const name = this.configArg.name || stringConfig(this.configArg, 'modelName') || onkyoDefaultName;
|
|
127
|
-
const uniqueBase = this.configArg.uniqueId || this.configArg.deviceId || this.configArg.macAddress || `${this.host()}:${this.port()}`;
|
|
128
|
-
const rawData = {
|
|
129
|
-
state,
|
|
130
|
-
sourceList: sourceList(this.configArg),
|
|
131
|
-
volumeResolution: this.volumeResolution(),
|
|
132
|
-
fetchedAt: new Date().toISOString(),
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
device: {
|
|
137
|
-
id: uniqueBase,
|
|
138
|
-
name,
|
|
139
|
-
manufacturer: 'Onkyo / Pioneer',
|
|
140
|
-
model: stringConfig(this.configArg, 'modelName') || stringConfig(this.configArg, 'model') || 'eISCP receiver',
|
|
141
|
-
serialNumber: this.configArg.macAddress || this.configArg.deviceId,
|
|
142
|
-
host: this.host(),
|
|
143
|
-
port: this.port(),
|
|
144
|
-
protocol: 'tcp',
|
|
145
|
-
configurationUrl: `tcp://${this.host()}:${this.port()}`,
|
|
146
|
-
},
|
|
147
|
-
entities: [{
|
|
148
|
-
id: 'media_player',
|
|
149
|
-
uniqueId: `${onkyoProfile.domain}_${SimpleLocalMapper.slug(uniqueBase)}_media_player`,
|
|
150
|
-
name,
|
|
151
|
-
platform: 'media_player',
|
|
152
|
-
state: state.power,
|
|
153
|
-
available: state.power !== 'unknown',
|
|
154
|
-
writable: true,
|
|
155
|
-
attributes: {
|
|
156
|
-
source: state.source,
|
|
157
|
-
source_code: state.sourceCode,
|
|
158
|
-
source_list: sourceList(this.configArg),
|
|
159
|
-
volume_level: state.volumeLevel,
|
|
160
|
-
volume_raw: state.volumeRaw,
|
|
161
|
-
is_volume_muted: state.muted,
|
|
162
|
-
sound_mode: state.listeningMode,
|
|
163
|
-
sound_mode_code: state.listeningModeCode,
|
|
164
|
-
},
|
|
165
|
-
}],
|
|
166
|
-
online: true,
|
|
167
|
-
updatedAt: new Date().toISOString(),
|
|
168
|
-
source: 'tcp',
|
|
169
|
-
rawData,
|
|
170
|
-
capabilities: {
|
|
171
|
-
localRead: true,
|
|
172
|
-
localControl: true,
|
|
173
|
-
liveControlRequiresExecutor: false,
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private async queryReceiverState(): Promise<IOnkyoReceiverState> {
|
|
179
|
-
const powerValue = await this.queryValue('PWR');
|
|
180
|
-
if (!powerValue) {
|
|
181
|
-
throw new Error('Onkyo eISCP power query returned no usable PWR status.');
|
|
182
|
-
}
|
|
183
|
-
const [volumeValue, muteValue, sourceValue, listeningModeValue] = await Promise.all([
|
|
184
|
-
optional(() => this.queryValue('MVL')),
|
|
185
|
-
optional(() => this.queryValue('AMT')),
|
|
186
|
-
optional(() => this.queryValue('SLI')),
|
|
187
|
-
optional(() => this.queryValue('LMD')),
|
|
188
|
-
]);
|
|
189
|
-
const volumeRaw = volumeValue ? Number.parseInt(volumeValue, 16) : undefined;
|
|
190
|
-
const sourceCode = sourceValue?.toUpperCase();
|
|
191
|
-
const listeningModeCode = listeningModeValue?.toUpperCase();
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
power: powerValue === '01' ? 'on' : powerValue === '00' ? 'off' : 'unknown',
|
|
195
|
-
volumeRaw: Number.isFinite(volumeRaw) ? volumeRaw : undefined,
|
|
196
|
-
volumeLevel: Number.isFinite(volumeRaw) ? clamp(Number(((volumeRaw as number) / (this.volumeResolution() * (this.maxVolume() / 100))).toFixed(3)), 0, 1) : undefined,
|
|
197
|
-
muted: muteValue === undefined ? undefined : muteValue === '01',
|
|
198
|
-
sourceCode,
|
|
199
|
-
source: sourceCode ? sourceName(sourceCode, this.configArg) : undefined,
|
|
200
|
-
listeningModeCode,
|
|
201
|
-
listeningMode: listeningModeCode ? onkyoListeningModes[listeningModeCode] || listeningModeCode : undefined,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
private async executeEiscpCommand(requestArg: IServiceCallRequest): Promise<unknown> {
|
|
206
|
-
const service = requestArg.service;
|
|
207
|
-
if (service === 'turn_on') {
|
|
208
|
-
return { response: await this.sendCommand('PWR01') };
|
|
209
|
-
}
|
|
210
|
-
if (service === 'turn_off') {
|
|
211
|
-
return { response: await this.sendCommand('PWR00') };
|
|
212
|
-
}
|
|
213
|
-
if (service === 'toggle') {
|
|
214
|
-
const power = await this.queryValue('PWR');
|
|
215
|
-
return { response: await this.sendCommand(power === '01' ? 'PWR00' : 'PWR01') };
|
|
216
|
-
}
|
|
217
|
-
if (service === 'volume_up') {
|
|
218
|
-
return { response: await this.sendCommand('MVLUP') };
|
|
219
|
-
}
|
|
220
|
-
if (service === 'volume_down') {
|
|
221
|
-
return { response: await this.sendCommand('MVLDOWN') };
|
|
222
|
-
}
|
|
223
|
-
if (service === 'volume_set' || service === 'set_volume' || service === 'set_volume_level') {
|
|
224
|
-
const level = numberData(requestArg, 'volume_level') ?? numberData(requestArg, 'volumeLevel') ?? numberData(requestArg, 'level');
|
|
225
|
-
if (level === undefined) {
|
|
226
|
-
throw new Error('Onkyo volume_set requires data.volume_level.');
|
|
227
|
-
}
|
|
228
|
-
const raw = clamp(Math.round(level * (this.maxVolume() / 100) * this.volumeResolution()), 0, this.volumeResolution());
|
|
229
|
-
return { response: await this.sendCommand(`MVL${hex(raw)}`), volumeRaw: raw };
|
|
230
|
-
}
|
|
231
|
-
if (service === 'volume_mute' || service === 'mute') {
|
|
232
|
-
const muted = booleanData(requestArg, 'is_volume_muted') ?? booleanData(requestArg, 'mute') ?? booleanData(requestArg, 'muted');
|
|
233
|
-
if (muted === undefined) {
|
|
234
|
-
throw new Error('Onkyo volume_mute requires data.is_volume_muted or data.mute.');
|
|
235
|
-
}
|
|
236
|
-
return { response: await this.sendCommand(`AMT${muted ? '01' : '00'}`) };
|
|
237
|
-
}
|
|
238
|
-
if (service === 'media_play') {
|
|
239
|
-
return { response: await this.sendCommand('NTCPLAY') };
|
|
240
|
-
}
|
|
241
|
-
if (service === 'media_pause') {
|
|
242
|
-
return { response: await this.sendCommand('NTCPAUSE') };
|
|
243
|
-
}
|
|
244
|
-
if (service === 'media_stop') {
|
|
245
|
-
return { response: await this.sendCommand('NTCSTOP') };
|
|
246
|
-
}
|
|
247
|
-
if (service === 'select_source' || service === 'set_source') {
|
|
248
|
-
const source = stringData(requestArg, 'source');
|
|
249
|
-
if (!source) {
|
|
250
|
-
throw new Error('Onkyo select_source requires data.source.');
|
|
251
|
-
}
|
|
252
|
-
const code = sourceCode(source, this.configArg);
|
|
253
|
-
if (!code) {
|
|
254
|
-
throw new Error(`Unsupported Onkyo input source: ${source}.`);
|
|
255
|
-
}
|
|
256
|
-
return { response: await this.sendCommand(`SLI${code}`), source, sourceCode: code };
|
|
257
|
-
}
|
|
258
|
-
if (service === 'onkyo_select_hdmi_output' || service === 'select_hdmi_output') {
|
|
259
|
-
const output = stringData(requestArg, 'hdmi_output') || stringData(requestArg, 'output');
|
|
260
|
-
const code = output ? hdmiOutputCodes[normalizeKey(output)] : undefined;
|
|
261
|
-
if (!code) {
|
|
262
|
-
throw new Error('Onkyo HDMI output selection requires data.hdmi_output with a supported value.');
|
|
263
|
-
}
|
|
264
|
-
return { response: await this.sendCommand(`HDO${code}`), hdmiOutput: output };
|
|
265
|
-
}
|
|
266
|
-
throw new Error(`Unsupported Onkyo eISCP service: ${requestArg.domain}.${service}`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private async queryValue(prefixArg: string): Promise<string | undefined> {
|
|
270
|
-
const response = await this.sendCommand(`${prefixArg}QSTN`, prefixArg);
|
|
271
|
-
return response.startsWith(prefixArg) ? response.slice(prefixArg.length) : undefined;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private async sendCommand(commandArg: string, responsePrefixArg = commandArg.slice(0, 3)): Promise<string> {
|
|
275
|
-
const frame = buildEiscpFrame(commandArg);
|
|
276
|
-
const host = this.host();
|
|
277
|
-
const port = this.port();
|
|
278
|
-
const timeoutMs = this.timeoutMs();
|
|
279
|
-
|
|
280
|
-
return new Promise<string>((resolve, reject) => {
|
|
281
|
-
const socket = plugins.net.createConnection({ host, port });
|
|
282
|
-
let buffer = Buffer.alloc(0);
|
|
283
|
-
let settled = false;
|
|
284
|
-
|
|
285
|
-
const finish = (errorArg?: Error, responseArg?: string) => {
|
|
286
|
-
if (settled) {
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
settled = true;
|
|
290
|
-
socket.removeAllListeners();
|
|
291
|
-
socket.destroy();
|
|
292
|
-
if (errorArg) {
|
|
293
|
-
reject(errorArg);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
resolve(responseArg || '');
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
socket.setTimeout(timeoutMs, () => finish(new Error(`Onkyo eISCP request to ${host}:${port} timed out after ${timeoutMs}ms.`)));
|
|
300
|
-
socket.on('connect', () => socket.write(frame));
|
|
301
|
-
socket.on('data', (chunkArg) => {
|
|
302
|
-
buffer = Buffer.concat([buffer, Buffer.isBuffer(chunkArg) ? chunkArg : Buffer.from(chunkArg)]);
|
|
303
|
-
const parsed = parseEiscpResponses(buffer);
|
|
304
|
-
buffer = Buffer.from(parsed.remaining);
|
|
305
|
-
const response = parsed.messages.find((messageArg) => messageArg.startsWith(responsePrefixArg));
|
|
306
|
-
if (response) {
|
|
307
|
-
finish(undefined, response);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
socket.on('end', () => finish(new Error(`Onkyo eISCP connection to ${host}:${port} closed before a response was received.`)));
|
|
311
|
-
socket.on('close', () => finish(new Error(`Onkyo eISCP connection to ${host}:${port} closed before a response was received.`)));
|
|
312
|
-
socket.on('error', (errorArg) => finish(errorArg));
|
|
2
|
+
import { NativeHomeAdmission } from '../../core/classes.nativehomeadmission.js';
|
|
3
|
+
import type { INativeIntegrationSetupContext } from '../../core/types.js';
|
|
4
|
+
import { OnkyoMapper } from './onkyo.mapper.js';
|
|
5
|
+
import type { IOnkyoConfig, IOnkyoSnapshot, TOnkyoClientEvent } from './onkyo.types.js';
|
|
6
|
+
import { normalizeOnkyoConfig, onkyoBoolean, onkyoInputId, onkyoInteger } from './onkyo.validation.js';
|
|
7
|
+
|
|
8
|
+
interface IOnkyoSubscriber {
|
|
9
|
+
handler: (eventArg: TOnkyoClientEvent) => void;
|
|
10
|
+
unsubscribe: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class OnkyoClient {
|
|
14
|
+
readonly #config: ReturnType<typeof normalizeOnkyoConfig>;
|
|
15
|
+
readonly #homeId: string;
|
|
16
|
+
readonly #protocol: plugins.ecobridgeDeviceManager.OnkyoProtocol;
|
|
17
|
+
readonly #lifetime = new AbortController();
|
|
18
|
+
readonly #operations = new Set<Promise<unknown>>();
|
|
19
|
+
readonly #subscribers = new Set<IOnkyoSubscriber>();
|
|
20
|
+
readonly #parentSignal?: AbortSignal;
|
|
21
|
+
readonly #onParentAbort: () => void;
|
|
22
|
+
#healthy = false;
|
|
23
|
+
#closed = false;
|
|
24
|
+
#closePromise?: Promise<void>;
|
|
25
|
+
|
|
26
|
+
constructor(configArg: IOnkyoConfig, contextArg: INativeIntegrationSetupContext, signalArg?: AbortSignal) {
|
|
27
|
+
this.#config = normalizeOnkyoConfig(configArg);
|
|
28
|
+
const context = new NativeHomeAdmission('onkyo').admit(contextArg, this.#config.homeId);
|
|
29
|
+
this.#homeId = context.homeId;
|
|
30
|
+
if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Onkyo lifetime signal must be an AbortSignal.');
|
|
31
|
+
signalArg = signalArg && context.abortSignal ? AbortSignal.any([signalArg, context.abortSignal]) : signalArg ?? context.abortSignal;
|
|
32
|
+
this.#protocol = new plugins.ecobridgeDeviceManager.OnkyoProtocol({
|
|
33
|
+
address: this.#config.host, port: this.#config.port, expectedMacAddress: this.#config.macAddress, expectedModel: this.#config.model,
|
|
34
|
+
timeoutMs: this.#config.requestTimeoutMs, maxQueuedOperations: this.#config.maxPendingOperations,
|
|
313
35
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
36
|
+
this.#parentSignal = signalArg;
|
|
37
|
+
// The terminal memo remains rejected for its owning lease or explicit caller.
|
|
38
|
+
this.#onParentAbort = () => { void this.destroy().catch(() => undefined); };
|
|
39
|
+
signalArg?.addEventListener('abort', this.#onParentAbort, { once: true });
|
|
40
|
+
if (signalArg?.aborted) this.#onParentAbort();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public isHealthy(): boolean { return this.#healthy && !this.#closed && !this.#protocol.isClosed; }
|
|
44
|
+
public getSnapshot(signalArg?: AbortSignal): Promise<IOnkyoSnapshot> {
|
|
45
|
+
return this.#run((signal) => this.#inspect(signal), signalArg);
|
|
46
|
+
}
|
|
47
|
+
public setPower(valueArg: boolean, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
|
|
48
|
+
const value = onkyoBoolean(valueArg, 'power');
|
|
49
|
+
return this.#run((signal) => this.#control('power', value, signal), signalArg);
|
|
50
|
+
}
|
|
51
|
+
public setMuted(valueArg: boolean, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
|
|
52
|
+
const value = onkyoBoolean(valueArg, 'mute');
|
|
53
|
+
return this.#run((signal) => this.#control('mute', value, signal), signalArg);
|
|
54
|
+
}
|
|
55
|
+
public setVolume(valueArg: number, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
|
|
56
|
+
const value = onkyoInteger(valueArg, 0, this.#config.volumeLimitRaw, 'raw volume');
|
|
57
|
+
return this.#run((signal) => this.#control('volume_raw', value, signal), signalArg);
|
|
58
|
+
}
|
|
59
|
+
public setInput(valueArg: string, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
|
|
60
|
+
const value = onkyoInputId(valueArg);
|
|
61
|
+
return this.#run((signal) => this.#control('input', value, signal), signalArg);
|
|
62
|
+
}
|
|
63
|
+
public writeFeature(idArg: string, valueArg: plugins.shxInterfaces.data.TDeviceStateValue, signalArg?: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
|
|
64
|
+
const value = idArg === 'power' || idArg === 'mute' ? onkyoBoolean(valueArg, idArg) :
|
|
65
|
+
idArg === 'volume_raw' ? onkyoInteger(valueArg, 0, this.#config.volumeLimitRaw, 'raw volume') :
|
|
66
|
+
idArg === 'input' ? onkyoInputId(valueArg) : undefined;
|
|
67
|
+
if (value === undefined) throw new TypeError('Onkyo feature is not writable.');
|
|
68
|
+
return this.#run(async (signal) => {
|
|
69
|
+
const current = OnkyoMapper.toDevices(await this.#inspect(signal))[0].features.find((featureArg) => featureArg.id === idArg);
|
|
70
|
+
if (!current?.writable) throw new TypeError('Onkyo current canonical feature is not writable.');
|
|
71
|
+
signal.throwIfAborted();
|
|
72
|
+
return this.#control(idArg, value, signal);
|
|
73
|
+
}, signalArg);
|
|
74
|
+
}
|
|
75
|
+
public subscribe(handlerArg: (eventArg: TOnkyoClientEvent) => void, signalArg?: AbortSignal): () => void {
|
|
76
|
+
if (this.#closed || this.#protocol.isClosed) throw new Error('Onkyo client is closed.');
|
|
77
|
+
if (typeof handlerArg !== 'function') throw new TypeError('Onkyo subscriber must be a function.');
|
|
78
|
+
if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) throw new TypeError('Onkyo subscription signal must be an AbortSignal.');
|
|
79
|
+
signalArg?.throwIfAborted();
|
|
80
|
+
const subscriber: IOnkyoSubscriber = { handler: handlerArg, unsubscribe: () => {
|
|
81
|
+
signalArg?.removeEventListener('abort', subscriber.unsubscribe);
|
|
82
|
+
this.#subscribers.delete(subscriber);
|
|
83
|
+
} };
|
|
84
|
+
this.#subscribers.add(subscriber);
|
|
85
|
+
signalArg?.addEventListener('abort', subscriber.unsubscribe, { once: true });
|
|
86
|
+
return subscriber.unsubscribe;
|
|
87
|
+
}
|
|
88
|
+
public destroy(): Promise<void> {
|
|
89
|
+
if (this.#closePromise) return this.#closePromise;
|
|
90
|
+
this.#closed = true;
|
|
91
|
+
this.#healthy = false;
|
|
92
|
+
this.#parentSignal?.removeEventListener('abort', this.#onParentAbort);
|
|
93
|
+
for (const subscriber of this.#subscribers) subscriber.unsubscribe();
|
|
94
|
+
this.#closePromise = Promise.resolve().then(async () => {
|
|
95
|
+
const close = Promise.resolve().then(() => this.#protocol.close());
|
|
96
|
+
const results = await Promise.allSettled([close, ...this.#operations]);
|
|
97
|
+
if (results[0].status === 'rejected') throw results[0].reason;
|
|
98
|
+
});
|
|
99
|
+
this.#lifetime.abort(new Error('Onkyo client is destroyed.'));
|
|
100
|
+
return this.#closePromise;
|
|
101
|
+
}
|
|
102
|
+
async #inspect(signalArg: AbortSignal): Promise<IOnkyoSnapshot> {
|
|
103
|
+
const state = await this.#protocol.inspect({ signal: signalArg });
|
|
104
|
+
const snapshot: IOnkyoSnapshot = Object.freeze({ ...state, homeId: this.#homeId,
|
|
105
|
+
name: this.#config.name ?? `${state.receiver.model} main zone`, volumeLimitRaw: this.#config.volumeLimitRaw, online: true });
|
|
106
|
+
this.#emit({ type: 'snapshot', snapshot });
|
|
107
|
+
return snapshot;
|
|
108
|
+
}
|
|
109
|
+
async #control(idArg: string, valueArg: boolean | number | string, signalArg: AbortSignal): Promise<plugins.ecobridgeDeviceManager.IOnkyoControlObservation> {
|
|
110
|
+
const options = { signal: signalArg };
|
|
111
|
+
let observation: plugins.ecobridgeDeviceManager.IOnkyoControlObservation;
|
|
112
|
+
if (idArg === 'power' && typeof valueArg === 'boolean') observation = await this.#protocol.setPower(valueArg, options);
|
|
113
|
+
else if (idArg === 'mute' && typeof valueArg === 'boolean') observation = await this.#protocol.setMuted(valueArg, options);
|
|
114
|
+
else if (idArg === 'volume_raw' && typeof valueArg === 'number') observation = await this.#protocol.setVolume(valueArg, options);
|
|
115
|
+
else if (idArg === 'input' && typeof valueArg === 'string') observation = await this.#protocol.setInput(valueArg, options);
|
|
116
|
+
else throw new TypeError('Onkyo control requires an explicit supported value.');
|
|
117
|
+
this.#emit({ type: 'control', observation });
|
|
118
|
+
return observation;
|
|
119
|
+
}
|
|
120
|
+
#run<TResult>(workArg: (signalArg: AbortSignal) => Promise<TResult>, signalArg?: AbortSignal): Promise<TResult> {
|
|
121
|
+
if (this.#closed || this.#protocol.isClosed) return Promise.reject(new plugins.ecobridgeDeviceManager.OnkyoProtocolError('closed', 'Onkyo client is terminally closed.'));
|
|
122
|
+
if (signalArg !== undefined && !(signalArg instanceof AbortSignal)) return Promise.reject(new TypeError('Onkyo operation signal must be an AbortSignal.'));
|
|
123
|
+
if (signalArg?.aborted) return Promise.reject(new plugins.ecobridgeDeviceManager.OnkyoProtocolError('cancelled', 'Onkyo operation was cancelled before admission.'));
|
|
124
|
+
if (this.#operations.size >= this.#config.maxPendingOperations) return Promise.reject(new plugins.ecobridgeDeviceManager.OnkyoProtocolError('capacity', 'Onkyo client operation capacity is full.'));
|
|
125
|
+
const deadline = new AbortController();
|
|
126
|
+
const signal = AbortSignal.any([this.#lifetime.signal, deadline.signal, ...(signalArg ? [signalArg] : [])]);
|
|
127
|
+
const timer = setTimeout(() => deadline.abort(), this.#config.requestTimeoutMs);
|
|
128
|
+
timer.unref();
|
|
129
|
+
const operation = Promise.resolve().then(() => { signal.throwIfAborted(); return workArg(signal); }).then((resultArg) => {
|
|
130
|
+
this.#healthy = !this.#closed && !signal.aborted && !this.#protocol.isClosed;
|
|
131
|
+
return resultArg;
|
|
132
|
+
}, (errorArg: unknown) => {
|
|
133
|
+
this.#healthy = false;
|
|
134
|
+
const failure = deadline.signal.aborted && errorArg instanceof plugins.ecobridgeDeviceManager.OnkyoProtocolError ?
|
|
135
|
+
new plugins.ecobridgeDeviceManager.OnkyoProtocolError('timeout', 'Onkyo consumer operation deadline exceeded.', errorArg.effectOutcome) : errorArg;
|
|
136
|
+
if (!this.#closed && failure instanceof Error) this.#emit({ type: 'error', error: failure });
|
|
137
|
+
throw failure;
|
|
138
|
+
}).finally(() => { clearTimeout(timer); this.#operations.delete(operation); });
|
|
139
|
+
this.#operations.add(operation);
|
|
140
|
+
return operation;
|
|
141
|
+
}
|
|
142
|
+
#emit(eventArg: TOnkyoClientEvent): void {
|
|
143
|
+
if (this.#closed) return;
|
|
144
|
+
for (const subscriber of [...this.#subscribers]) {
|
|
145
|
+
if (!this.#subscribers.has(subscriber)) continue;
|
|
146
|
+
try { subscriber.handler(structuredClone(eventArg)); }
|
|
147
|
+
catch {
|
|
148
|
+
subscriber.unsubscribe();
|
|
149
|
+
this.#emit({ type: 'error', error: new Error('Onkyo subscriber callback failed and was removed.') });
|
|
150
|
+
}
|
|
371
151
|
}
|
|
372
|
-
offset += frameSize;
|
|
373
|
-
}
|
|
374
|
-
return { messages, remaining: Buffer.from(bufferArg.subarray(offset)) };
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async function optional<TValue>(callbackArg: () => Promise<TValue>): Promise<TValue | undefined> {
|
|
378
|
-
try {
|
|
379
|
-
return await callbackArg();
|
|
380
|
-
} catch {
|
|
381
|
-
return undefined;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function sourceList(configArg: IOnkyoConfig): string[] {
|
|
386
|
-
const configured = recordConfig(configArg, 'sourceMap') || recordConfig(configArg, 'inputSources') || recordConfig(configArg, 'input_sources');
|
|
387
|
-
if (configured) {
|
|
388
|
-
return Object.values(configured).filter((valueArg): valueArg is string => typeof valueArg === 'string' && valueArg.trim().length > 0);
|
|
389
152
|
}
|
|
390
|
-
return Object.values(onkyoSourceCodes);
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function sourceName(codeArg: string, configArg: IOnkyoConfig): string {
|
|
394
|
-
const configured = recordConfig(configArg, 'sourceMap') || recordConfig(configArg, 'inputSources') || recordConfig(configArg, 'input_sources');
|
|
395
|
-
const configuredName = configured?.[codeArg];
|
|
396
|
-
return typeof configuredName === 'string' && configuredName.trim() ? configuredName : onkyoSourceCodes[codeArg] || codeArg;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function sourceCode(sourceArg: string, configArg: IOnkyoConfig): string | undefined {
|
|
400
|
-
const normalized = normalizeKey(sourceArg);
|
|
401
|
-
const configured = recordConfig(configArg, 'sourceMap') || recordConfig(configArg, 'inputSources') || recordConfig(configArg, 'input_sources');
|
|
402
|
-
const configuredMatch = configured ? Object.entries(configured).find(([codeArg, nameArg]) => normalizeKey(codeArg) === normalized || normalizeKey(String(nameArg)) === normalized) : undefined;
|
|
403
|
-
if (configuredMatch) {
|
|
404
|
-
return configuredMatch[0].toUpperCase();
|
|
405
|
-
}
|
|
406
|
-
return Object.entries(onkyoSourceCodes).find(([codeArg, nameArg]) => normalizeKey(codeArg) === normalized || normalizeKey(nameArg) === normalized)?.[0];
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function normalizeKey(valueArg: string): string {
|
|
410
|
-
return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function hex(valueArg: number): string {
|
|
414
|
-
return Math.round(valueArg).toString(16).toUpperCase().padStart(2, '0');
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function clamp(valueArg: number, minArg: number, maxArg: number): number {
|
|
418
|
-
return Math.max(minArg, Math.min(maxArg, valueArg));
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function numberData(requestArg: IServiceCallRequest, keyArg: string): number | undefined {
|
|
422
|
-
const value = requestArg.data?.[keyArg];
|
|
423
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
424
|
-
return value;
|
|
425
|
-
}
|
|
426
|
-
if (typeof value === 'string' && value.trim() && Number.isFinite(Number(value))) {
|
|
427
|
-
return Number(value);
|
|
428
|
-
}
|
|
429
|
-
return undefined;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function booleanData(requestArg: IServiceCallRequest, keyArg: string): boolean | undefined {
|
|
433
|
-
const value = requestArg.data?.[keyArg];
|
|
434
|
-
return typeof value === 'boolean' ? value : undefined;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function stringData(requestArg: IServiceCallRequest, keyArg: string): string | undefined {
|
|
438
|
-
const value = requestArg.data?.[keyArg];
|
|
439
|
-
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function numberConfig(configArg: IOnkyoConfig, keyArg: string): number | undefined {
|
|
443
|
-
const value = configArg[keyArg];
|
|
444
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function stringConfig(configArg: IOnkyoConfig, keyArg: string): string | undefined {
|
|
448
|
-
const value = configArg[keyArg];
|
|
449
|
-
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function recordConfig(configArg: IOnkyoConfig, keyArg: string): Record<string, string> | undefined {
|
|
453
|
-
const value = configArg[keyArg];
|
|
454
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
455
|
-
return undefined;
|
|
456
|
-
}
|
|
457
|
-
return Object.fromEntries(Object.entries(value as Record<string, unknown>).filter(([, valueArg]) => typeof valueArg === 'string')) as Record<string, string>;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function errorMessage(errorArg: unknown): string {
|
|
461
|
-
return errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
462
153
|
}
|