@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,216 +1,135 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { BaseIntegration } from '../../core/classes.baseintegration.js';
|
|
3
|
-
import
|
|
3
|
+
import { NativeIntegrationOwner, type INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
|
|
4
|
+
import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationRuntime, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
|
|
4
5
|
import { KodiClient } from './kodi.classes.client.js';
|
|
5
6
|
import { KodiConfigFlow } from './kodi.classes.configflow.js';
|
|
6
7
|
import { createKodiDiscoveryDescriptor } from './kodi.discovery.js';
|
|
7
8
|
import { KodiMapper } from './kodi.mapper.js';
|
|
8
|
-
import type { IKodiConfig,
|
|
9
|
+
import type { IKodiConfig, IKodiIntegrationOptions, IKodiNativeSnapshot, IKodiSetupContext } from './kodi.types.js';
|
|
10
|
+
import { kodiBoolean, kodiInteger, kodiKeys, kodiRecord, kodiSignal, normalizeKodiConfig } from './kodi.validation.js';
|
|
9
11
|
|
|
10
12
|
export class KodiIntegration extends BaseIntegration<IKodiConfig> {
|
|
11
13
|
public readonly domain = 'kodi';
|
|
12
14
|
public readonly displayName = 'Kodi';
|
|
13
15
|
public readonly status = 'control-runtime' as const;
|
|
14
16
|
public readonly discoveryDescriptor = createKodiDiscoveryDescriptor();
|
|
15
|
-
public readonly configFlow
|
|
17
|
+
public readonly configFlow: KodiConfigFlow;
|
|
16
18
|
public readonly metadata = {
|
|
17
|
-
source: '
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
iotClass: 'local_push',
|
|
22
|
-
requirements: ['pykodi==0.2.7'],
|
|
23
|
-
dependencies: [],
|
|
24
|
-
afterDependencies: ['media_source'],
|
|
25
|
-
codeowners: ['@OnFreund'],
|
|
26
|
-
configFlow: true,
|
|
27
|
-
documentation: 'https://www.home-assistant.io/integrations/kodi',
|
|
19
|
+
source: 'native-typescript', integrationType: 'device', iotClass: 'local_poll',
|
|
20
|
+
localApi: { status: 'Selected Kodi HTTP JSON-RPC API13 through devicemanager.',
|
|
21
|
+
implemented: ['fresh application observation', 'read-only default', 'explicit owner-trusted absolute volume and boolean mute', 'home-scoped endpoint targets', 'joined terminal lifetime'],
|
|
22
|
+
explicitUnsupported: ['unique or cryptographic server identity', 'arbitrary JSON-RPC', 'media transport', 'library and power operations', 'toggle or increment', 'notification transport', 'retry or replay', 'Home Assistant bridge'] },
|
|
28
23
|
};
|
|
24
|
+
readonly #owner: NativeIntegrationOwner;
|
|
25
|
+
#getBasicCredentials?: IKodiIntegrationOptions['getBasicCredentials'];
|
|
26
|
+
#closePromise?: Promise<void>;
|
|
29
27
|
|
|
30
|
-
public
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
public constructor(options: IKodiIntegrationOptions = {}) {
|
|
29
|
+
super();
|
|
30
|
+
const value = kodiRecord(options, 'integration options');
|
|
31
|
+
kodiKeys(value, ['homeId', 'getBasicCredentials']);
|
|
32
|
+
if (value.getBasicCredentials !== undefined && typeof value.getBasicCredentials !== 'function') throw new TypeError('Kodi Basic provider must be a function.');
|
|
33
|
+
this.#owner = new NativeIntegrationOwner('kodi', { homeId: value.homeId as string | undefined });
|
|
34
|
+
this.#getBasicCredentials = value.getBasicCredentials as IKodiIntegrationOptions['getBasicCredentials'];
|
|
35
|
+
this.configFlow = new KodiConfigFlow(this.#owner);
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
public
|
|
38
|
+
public setup(config: IKodiConfig, context: IKodiSetupContext): Promise<IIntegrationRuntime> {
|
|
39
|
+
try {
|
|
40
|
+
const captured = normalizeKodiConfig(config);
|
|
41
|
+
return this.#owner.runSetup(context, captured.homeId, async (lease) => {
|
|
42
|
+
const client = new KodiClient(captured, { ...lease.context, abortSignal: lease.signal }, { getBasicCredentials: this.#getBasicCredentials });
|
|
43
|
+
lease.own(() => client.destroy());
|
|
44
|
+
const initial = await client.getSnapshot();
|
|
45
|
+
lease.assertOpen();
|
|
46
|
+
const runtime = new KodiRuntime(client, initial, lease);
|
|
47
|
+
lease.promoteToRuntime();
|
|
48
|
+
return runtime;
|
|
49
|
+
});
|
|
50
|
+
} catch (error) { return Promise.reject(error); }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public destroy(): Promise<void> {
|
|
54
|
+
if (!this.#closePromise) {
|
|
55
|
+
this.#closePromise = Promise.resolve().then(async () => {
|
|
56
|
+
try { await this.#owner.close(); }
|
|
57
|
+
finally { this.#getBasicCredentials = undefined; }
|
|
58
|
+
});
|
|
59
|
+
// Install the facade memo before synchronously closing admission or dispatching abort.
|
|
60
|
+
void this.#owner.close().catch(() => undefined);
|
|
61
|
+
}
|
|
62
|
+
return this.#closePromise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
|
|
36
66
|
}
|
|
37
67
|
|
|
38
68
|
export class HomeAssistantKodiIntegration extends KodiIntegration {}
|
|
39
69
|
|
|
40
70
|
class KodiRuntime implements IIntegrationRuntime {
|
|
41
|
-
public domain = 'kodi';
|
|
42
|
-
|
|
43
|
-
|
|
71
|
+
public readonly domain = 'kodi';
|
|
72
|
+
readonly #deviceId: string;
|
|
73
|
+
readonly #entityId: string;
|
|
44
74
|
|
|
45
|
-
public
|
|
46
|
-
|
|
75
|
+
public constructor(private readonly client: KodiClient, initial: IKodiNativeSnapshot, private readonly lease: INativeSetupLease) {
|
|
76
|
+
this.#deviceId = KodiMapper.deviceId(initial);
|
|
77
|
+
this.#entityId = KodiMapper.entityId(initial);
|
|
47
78
|
}
|
|
48
79
|
|
|
49
|
-
public
|
|
50
|
-
|
|
51
|
-
}
|
|
80
|
+
public isHealthy(): boolean { return this.client.isHealthy(); }
|
|
81
|
+
public async devices(signal?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> { return KodiMapper.toNativeDevices(await this.client.getSnapshot(signal)); }
|
|
82
|
+
public async entities(): Promise<IIntegrationEntity[]> { return KodiMapper.toNativeEntities(await this.client.getSnapshot()); }
|
|
52
83
|
|
|
53
|
-
public async callService(
|
|
84
|
+
public async callService(input: IServiceCallRequest): Promise<IServiceCallResult> {
|
|
54
85
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
86
|
+
const request = kodiRecord(input, 'service request');
|
|
87
|
+
kodiKeys(request, ['domain', 'service', 'target', 'data']);
|
|
88
|
+
this.assertTarget(request.target);
|
|
89
|
+
const data = request.data === undefined ? {} : kodiRecord(request.data, 'service data');
|
|
90
|
+
if (request.domain === 'kodi' && request.service === 'snapshot') {
|
|
91
|
+
kodiKeys(data, []); return { success: true, data: await this.client.getSnapshot() };
|
|
60
92
|
}
|
|
61
|
-
if (
|
|
62
|
-
return await this.
|
|
93
|
+
if (request.domain === 'kodi' && request.service === 'set_volume') {
|
|
94
|
+
kodiKeys(data, ['volume']); return { success: true, data: await this.client.setVolume(kodiInteger(data.volume, 0, 100, 'absolute volume')) };
|
|
63
95
|
}
|
|
64
|
-
if (
|
|
65
|
-
return await this.
|
|
96
|
+
if ((request.domain === 'kodi' && request.service === 'set_muted') || (request.domain === 'media_player' && request.service === 'volume_mute')) {
|
|
97
|
+
kodiKeys(data, ['muted']); return { success: true, data: await this.client.setMuted(kodiBoolean(data.muted)) };
|
|
66
98
|
}
|
|
67
|
-
|
|
68
|
-
} catch (
|
|
69
|
-
return { success: false, error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
public async destroy(): Promise<void> {
|
|
74
|
-
await this.client.destroy();
|
|
99
|
+
throw new TypeError('Kodi service is unsupported; only snapshot, absolute volume and explicit mute are admitted.');
|
|
100
|
+
} catch (error) { return kodiFailure(error); }
|
|
75
101
|
}
|
|
76
102
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await this.client.
|
|
87
|
-
return { success: true };
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
await this.client.pause();
|
|
91
|
-
return { success: true };
|
|
92
|
-
}
|
|
93
|
-
if (requestArg.service === 'play_pause' || requestArg.service === 'media_play_pause') {
|
|
94
|
-
await this.client.playPause();
|
|
95
|
-
return { success: true };
|
|
96
|
-
}
|
|
97
|
-
if (requestArg.service === 'stop' || requestArg.service === 'media_stop') {
|
|
98
|
-
await this.client.stop();
|
|
99
|
-
return { success: true };
|
|
100
|
-
}
|
|
101
|
-
if (requestArg.service === 'next_track' || requestArg.service === 'media_next_track') {
|
|
102
|
-
await this.client.nextTrack();
|
|
103
|
-
return { success: true };
|
|
104
|
-
}
|
|
105
|
-
if (requestArg.service === 'previous_track' || requestArg.service === 'media_previous_track') {
|
|
106
|
-
await this.client.previousTrack();
|
|
107
|
-
return { success: true };
|
|
108
|
-
}
|
|
109
|
-
if (requestArg.service === 'seek' || requestArg.service === 'media_seek') {
|
|
110
|
-
const position = requestArg.data?.seek_position ?? requestArg.data?.position;
|
|
111
|
-
if (typeof position !== 'number') {
|
|
112
|
-
return { success: false, error: 'Kodi seek requires data.seek_position.' };
|
|
113
|
-
}
|
|
114
|
-
await this.client.seek(position);
|
|
115
|
-
return { success: true };
|
|
116
|
-
}
|
|
117
|
-
if (requestArg.service === 'volume_set') {
|
|
118
|
-
const level = requestArg.data?.volume_level;
|
|
119
|
-
if (typeof level !== 'number') {
|
|
120
|
-
return { success: false, error: 'Kodi volume_set requires data.volume_level.' };
|
|
121
|
-
}
|
|
122
|
-
await this.client.setVolumeLevel(level);
|
|
123
|
-
return { success: true };
|
|
124
|
-
}
|
|
125
|
-
if (requestArg.service === 'volume_up' || requestArg.service === 'volume_down') {
|
|
126
|
-
await this.client.stepVolume(requestArg.service === 'volume_up' ? 'increment' : 'decrement');
|
|
127
|
-
return { success: true };
|
|
128
|
-
}
|
|
129
|
-
if (requestArg.service === 'volume_mute') {
|
|
130
|
-
const muted = requestArg.data?.is_volume_muted ?? requestArg.data?.muted;
|
|
131
|
-
if (typeof muted !== 'boolean') {
|
|
132
|
-
return { success: false, error: 'Kodi volume_mute requires data.is_volume_muted.' };
|
|
133
|
-
}
|
|
134
|
-
await this.client.setMuted(muted);
|
|
135
|
-
return { success: true };
|
|
136
|
-
}
|
|
137
|
-
if (requestArg.service === 'play_media') {
|
|
138
|
-
const mediaId = requestArg.data?.media_content_id ?? requestArg.data?.uri;
|
|
139
|
-
const mediaType = requestArg.data?.media_content_type ?? requestArg.data?.media_type ?? 'file';
|
|
140
|
-
if ((typeof mediaId !== 'string' && typeof mediaId !== 'number') || mediaId === '') {
|
|
141
|
-
return { success: false, error: 'Kodi play_media requires data.media_content_id or data.uri.' };
|
|
142
|
-
}
|
|
143
|
-
await this.client.playMedia(typeof mediaType === 'string' ? mediaType : 'file', mediaId);
|
|
144
|
-
return { success: true };
|
|
145
|
-
}
|
|
146
|
-
return { success: false, error: `Unsupported Kodi media_player service: ${requestArg.service}` };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private async callKodiService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
|
|
150
|
-
if (requestArg.service === 'call_method') {
|
|
151
|
-
const method = requestArg.data?.method;
|
|
152
|
-
if (typeof method !== 'string' || !method) {
|
|
153
|
-
return { success: false, error: 'Kodi call_method requires data.method.' };
|
|
154
|
-
}
|
|
155
|
-
const params = this.callMethodParams(requestArg);
|
|
156
|
-
return { success: true, data: await this.client.callMethod(method, params) };
|
|
157
|
-
}
|
|
158
|
-
if (requestArg.service === 'add_to_playlist') {
|
|
159
|
-
const mediaType = requestArg.data?.media_type;
|
|
160
|
-
const mediaId = requestArg.data?.media_id;
|
|
161
|
-
if (typeof mediaType !== 'string' || (typeof mediaId !== 'string' && typeof mediaId !== 'number')) {
|
|
162
|
-
return { success: false, error: 'Kodi add_to_playlist requires data.media_type and data.media_id.' };
|
|
163
|
-
}
|
|
164
|
-
await this.client.addToPlaylist(mediaType as TKodiMediaType, mediaId);
|
|
165
|
-
return { success: true };
|
|
166
|
-
}
|
|
167
|
-
if (requestArg.service === 'show_notification') {
|
|
168
|
-
return this.callNotifyService(requestArg);
|
|
169
|
-
}
|
|
170
|
-
return { success: false, error: `Unsupported Kodi service: ${requestArg.service}` };
|
|
103
|
+
public async writeFeature(input: IDeviceFeatureWriteRequest, signal?: AbortSignal): Promise<IServiceCallResult> {
|
|
104
|
+
try {
|
|
105
|
+
kodiSignal(signal);
|
|
106
|
+
const request = kodiRecord(input, 'feature write');
|
|
107
|
+
kodiKeys(request, ['device', 'feature', 'value']);
|
|
108
|
+
const device = kodiRecord(request.device, 'feature device');
|
|
109
|
+
const feature = kodiRecord(request.feature, 'feature');
|
|
110
|
+
if (device.id !== this.#deviceId || device.integrationDomain !== 'kodi') throw new TypeError('Kodi feature target is outside this home and selected endpoint.');
|
|
111
|
+
if (feature.capability !== 'media') throw new TypeError('Kodi feature capability differs from the canonical feature.');
|
|
112
|
+
if (feature.id === 'volume') return { success: true, data: await this.client.setVolume(kodiInteger(request.value, 0, 100, 'absolute volume'), signal) };
|
|
113
|
+
if (feature.id === 'muted') return { success: true, data: await this.client.setMuted(kodiBoolean(request.value), signal) };
|
|
114
|
+
throw new TypeError('Kodi feature is not writable.');
|
|
115
|
+
} catch (error) { return kodiFailure(error); }
|
|
171
116
|
}
|
|
172
117
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (typeof message !== 'string') {
|
|
176
|
-
return { success: false, error: 'Kodi notification requires data.message.' };
|
|
177
|
-
}
|
|
178
|
-
const title = typeof requestArg.data?.title === 'string' ? requestArg.data.title : 'Home Assistant';
|
|
179
|
-
const icon = typeof requestArg.data?.icon === 'string' ? requestArg.data.icon : 'info';
|
|
180
|
-
const displayTime = typeof requestArg.data?.displaytime === 'number' ? requestArg.data.displaytime : 10000;
|
|
181
|
-
await this.client.showNotification(title, message, icon, displayTime);
|
|
182
|
-
return { success: true };
|
|
118
|
+
public destroy(): Promise<void> {
|
|
119
|
+
return this.lease.close();
|
|
183
120
|
}
|
|
184
121
|
|
|
185
|
-
private
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (!commands.length) {
|
|
192
|
-
return { success: false, error: 'Kodi remote.send_command requires data.command.' };
|
|
193
|
-
}
|
|
194
|
-
for (const item of commands) {
|
|
195
|
-
await this.client.input(item);
|
|
196
|
-
}
|
|
197
|
-
return { success: true };
|
|
122
|
+
private assertTarget(input: unknown): void {
|
|
123
|
+
const target = kodiRecord(input, 'service target');
|
|
124
|
+
kodiKeys(target, ['deviceId', 'entityId']);
|
|
125
|
+
if (target.deviceId === undefined && target.entityId === undefined ||
|
|
126
|
+
target.deviceId !== undefined && target.deviceId !== this.#deviceId ||
|
|
127
|
+
target.entityId !== undefined && target.entityId !== this.#entityId) throw new TypeError('Kodi requires an exact owned device or entity target.');
|
|
198
128
|
}
|
|
129
|
+
}
|
|
199
130
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
if (explicitParams && typeof explicitParams === 'object') {
|
|
206
|
-
return explicitParams as Record<string, unknown>;
|
|
207
|
-
}
|
|
208
|
-
const params: Record<string, unknown> = {};
|
|
209
|
-
for (const [key, value] of Object.entries(requestArg.data || {})) {
|
|
210
|
-
if (key !== 'method') {
|
|
211
|
-
params[key] = value;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return Object.keys(params).length ? params : undefined;
|
|
215
|
-
}
|
|
131
|
+
function kodiFailure(error: unknown): IServiceCallResult {
|
|
132
|
+
const protocolError = error instanceof plugins.ecobridgeDeviceManager.KodiProtocolError ? error : undefined;
|
|
133
|
+
return { success: false, error: protocolError?.message ?? 'Kodi operation was refused or failed.',
|
|
134
|
+
data: { code: protocolError?.code ?? 'invalid-operation', effectOutcome: protocolError?.effectOutcome ?? 'not-sent', ...(protocolError?.rpcErrorCode === undefined ? {} : { rpcErrorCode: protocolError.rpcErrorCode }) } };
|
|
216
135
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
1
2
|
import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
|
|
2
3
|
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
|
|
3
4
|
import type { IKodiManualEntry, IKodiMdnsRecord } from './kodi.types.js';
|
|
4
5
|
|
|
5
6
|
const defaultPort = 8080;
|
|
6
|
-
const defaultWsPort = 9090;
|
|
7
7
|
const kodiMdnsType = '_xbmc-jsonrpc-h._tcp.local';
|
|
8
8
|
|
|
9
9
|
export class KodiMdnsMatcher implements IDiscoveryMatcher<IKodiMdnsRecord> {
|
|
@@ -20,25 +20,33 @@ export class KodiMdnsMatcher implements IDiscoveryMatcher<IKodiMdnsRecord> {
|
|
|
20
20
|
if (!matched) {
|
|
21
21
|
return { matched: false, confidence: 'low', reason: 'mDNS record is not a Kodi JSON-RPC advertisement.' };
|
|
22
22
|
}
|
|
23
|
+
let endpoint = kodiEndpoint(recordArg.host, recordArg.port);
|
|
24
|
+
for (const address of recordArg.addresses || []) {
|
|
25
|
+
if (endpoint) break;
|
|
26
|
+
endpoint = kodiEndpoint(address, recordArg.port);
|
|
27
|
+
}
|
|
28
|
+
if (!endpoint) {
|
|
29
|
+
return { matched: false, confidence: 'low', reason: 'Kodi advertisement does not contain a supported numeric HTTP endpoint.' };
|
|
30
|
+
}
|
|
23
31
|
return {
|
|
24
32
|
matched: true,
|
|
25
|
-
confidence:
|
|
26
|
-
reason: 'mDNS record
|
|
27
|
-
normalizedDeviceId: uuid,
|
|
33
|
+
confidence: 'high',
|
|
34
|
+
reason: 'mDNS record is a Kodi endpoint hint; application selection still requires a live observation.',
|
|
28
35
|
candidate: {
|
|
29
36
|
source: 'mdns',
|
|
30
37
|
integrationDomain: 'kodi',
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
port: recordArg.port || defaultPort,
|
|
38
|
+
host: endpoint.host,
|
|
39
|
+
port: endpoint.port,
|
|
34
40
|
name,
|
|
35
41
|
manufacturer: 'Kodi',
|
|
36
42
|
model: valueForKey(properties, 'version') ? 'Kodi Media Center' : undefined,
|
|
37
43
|
metadata: {
|
|
38
44
|
mdnsName: recordArg.name,
|
|
39
45
|
mdnsType: recordArg.type,
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
mdnsHost: recordArg.host,
|
|
47
|
+
advertisedId: uuid,
|
|
48
|
+
advertisedVersion: valueForKey(properties, 'version'),
|
|
49
|
+
selectionVerified: false,
|
|
42
50
|
},
|
|
43
51
|
},
|
|
44
52
|
};
|
|
@@ -51,30 +59,24 @@ export class KodiManualMatcher implements IDiscoveryMatcher<IKodiManualEntry> {
|
|
|
51
59
|
public description = 'Recognize manual Kodi setup entries.';
|
|
52
60
|
|
|
53
61
|
public async matches(inputArg: IKodiManualEntry): Promise<IDiscoveryMatch> {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Kodi setup hints.' };
|
|
62
|
+
const endpoint = kodiEndpoint(inputArg.host, inputArg.port);
|
|
63
|
+
if (!endpoint) {
|
|
64
|
+
return { matched: false, confidence: 'low', reason: 'Manual Kodi setup requires a supported numeric HTTP endpoint.' };
|
|
58
65
|
}
|
|
59
|
-
const id = inputArg.uuid || inputArg.id;
|
|
60
66
|
return {
|
|
61
67
|
matched: true,
|
|
62
|
-
confidence:
|
|
68
|
+
confidence: 'high',
|
|
63
69
|
reason: 'Manual entry can start Kodi setup.',
|
|
64
|
-
normalizedDeviceId: id,
|
|
65
70
|
candidate: {
|
|
66
71
|
source: 'manual',
|
|
67
72
|
integrationDomain: 'kodi',
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
port: inputArg.port || defaultPort,
|
|
73
|
+
host: endpoint.host,
|
|
74
|
+
port: endpoint.port,
|
|
71
75
|
name: inputArg.name,
|
|
72
76
|
manufacturer: inputArg.manufacturer || 'Kodi',
|
|
73
77
|
model: inputArg.model,
|
|
74
78
|
metadata: {
|
|
75
|
-
|
|
76
|
-
wsPort: inputArg.wsPort || defaultWsPort,
|
|
77
|
-
ssl: inputArg.ssl,
|
|
79
|
+
selectionVerified: false,
|
|
78
80
|
},
|
|
79
81
|
},
|
|
80
82
|
};
|
|
@@ -89,19 +91,25 @@ export class KodiCandidateValidator implements IDiscoveryValidator {
|
|
|
89
91
|
const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
|
|
90
92
|
const model = candidateArg.model?.toLowerCase() || '';
|
|
91
93
|
const name = candidateArg.name?.toLowerCase() || '';
|
|
92
|
-
const
|
|
94
|
+
const recognized = candidateArg.integrationDomain === 'kodi'
|
|
93
95
|
|| manufacturer.includes('kodi')
|
|
94
96
|
|| manufacturer.includes('xbmc')
|
|
95
97
|
|| model.includes('kodi')
|
|
96
98
|
|| model.includes('xbmc')
|
|
97
99
|
|| name.includes('kodi')
|
|
98
100
|
|| Boolean(candidateArg.metadata?.kodi);
|
|
101
|
+
if (!recognized) {
|
|
102
|
+
return { matched: false, confidence: 'low', reason: 'Candidate is not Kodi.' };
|
|
103
|
+
}
|
|
104
|
+
const endpoint = kodiEndpoint(candidateArg.host, candidateArg.port);
|
|
105
|
+
if (!endpoint) {
|
|
106
|
+
return { matched: false, confidence: 'low', reason: 'Kodi candidate does not contain a supported numeric HTTP endpoint.' };
|
|
107
|
+
}
|
|
99
108
|
return {
|
|
100
|
-
matched,
|
|
101
|
-
confidence:
|
|
102
|
-
reason:
|
|
103
|
-
candidate:
|
|
104
|
-
normalizedDeviceId: candidateArg.id,
|
|
109
|
+
matched: true,
|
|
110
|
+
confidence: 'high',
|
|
111
|
+
reason: 'Candidate has Kodi metadata and a numeric endpoint hint; application selection still requires a live observation.',
|
|
112
|
+
candidate: { ...candidateArg, host: endpoint.host, port: endpoint.port },
|
|
105
113
|
};
|
|
106
114
|
}
|
|
107
115
|
}
|
|
@@ -113,6 +121,19 @@ export const createKodiDiscoveryDescriptor = (): DiscoveryDescriptor => {
|
|
|
113
121
|
.addValidator(new KodiCandidateValidator());
|
|
114
122
|
};
|
|
115
123
|
|
|
124
|
+
/** Select only supplied endpoints; the public foundation owns origin policy. */
|
|
125
|
+
const kodiEndpoint = (hostArg: string | undefined, portArg: number | undefined): { host: string; port: number } | undefined => {
|
|
126
|
+
const port = portArg === undefined ? defaultPort : portArg;
|
|
127
|
+
if (typeof hostArg !== 'string' || typeof port !== 'number' || !Number.isSafeInteger(port)) return undefined;
|
|
128
|
+
try {
|
|
129
|
+
const origin = plugins.ecobridgeDeviceManager.validateKodiOrigin(`http://${hostArg}:${port}`);
|
|
130
|
+
return { host: new URL(origin).hostname, port };
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error instanceof plugins.ecobridgeDeviceManager.KodiProtocolError && error.code === 'invalid-options') return undefined;
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
116
137
|
const normalizeType = (valueArg?: string): string => {
|
|
117
138
|
return (valueArg || '').toLowerCase().replace(/\.$/, '');
|
|
118
139
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { IIntegrationEntity } from '../../core/types.js';
|
|
3
|
-
import type { IKodiMediaItem, IKodiSnapshot, IKodiTime } from './kodi.types.js';
|
|
3
|
+
import type { IKodiMediaItem, IKodiNativeSnapshot, IKodiSnapshot, IKodiTime } from './kodi.types.js';
|
|
4
4
|
|
|
5
5
|
const kodiMediaTypes: Record<string, string> = {
|
|
6
6
|
music: 'music',
|
|
@@ -19,24 +19,64 @@ const kodiMediaTypes: Record<string, string> = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
export class KodiMapper {
|
|
22
|
+
public static deviceId(snapshot: Pick<IKodiNativeSnapshot, 'homeId' | 'origin'>): string {
|
|
23
|
+
return `kodi:${encodeURIComponent(snapshot.homeId)}:${encodeURIComponent(snapshot.origin)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public static entityId(snapshot: Pick<IKodiNativeSnapshot, 'homeId' | 'origin'>): string {
|
|
27
|
+
return `media_player.${this.deviceId(snapshot)}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static toNativeDevices(snapshot: IKodiNativeSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
|
|
31
|
+
return [{
|
|
32
|
+
id: this.deviceId(snapshot), integrationDomain: 'kodi', name: snapshot.name, protocol: 'http',
|
|
33
|
+
manufacturer: 'Kodi', model: snapshot.applicationName, online: snapshot.online,
|
|
34
|
+
features: [
|
|
35
|
+
{ id: 'volume', capability: 'media', name: 'Application volume', readable: true, writable: snapshot.capabilities.canSetVolume, unit: '%' },
|
|
36
|
+
{ id: 'muted', capability: 'media', name: 'Application muted', readable: true, writable: snapshot.capabilities.canSetMuted },
|
|
37
|
+
],
|
|
38
|
+
state: [
|
|
39
|
+
{ featureId: 'volume', value: snapshot.volume, updatedAt: snapshot.observedAt },
|
|
40
|
+
{ featureId: 'muted', value: snapshot.muted, updatedAt: snapshot.observedAt },
|
|
41
|
+
],
|
|
42
|
+
metadata: {
|
|
43
|
+
homeId: snapshot.homeId, origin: snapshot.origin, applicationVersion: snapshot.applicationVersion,
|
|
44
|
+
apiVersion: snapshot.apiVersion, selection: snapshot.selection, uniqueDeviceIdentityVerified: false,
|
|
45
|
+
cryptographicServerIdentityVerified: false, volumeRange: snapshot.capabilities.volumeRange,
|
|
46
|
+
runtimeAuthority: 'native-observation', playbackObserved: false,
|
|
47
|
+
},
|
|
48
|
+
}];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public static toNativeEntities(snapshot: IKodiNativeSnapshot): IIntegrationEntity[] {
|
|
52
|
+
return [{
|
|
53
|
+
id: this.entityId(snapshot), uniqueId: this.deviceId(snapshot), integrationDomain: 'kodi',
|
|
54
|
+
deviceId: this.deviceId(snapshot), platform: 'media_player', name: snapshot.name, state: 'unknown', available: snapshot.online,
|
|
55
|
+
attributes: { applicationVolume: snapshot.volume, isVolumeMuted: snapshot.muted, playbackObserved: false,
|
|
56
|
+
applicationName: snapshot.applicationName, applicationVersion: snapshot.applicationVersion, apiVersion: snapshot.apiVersion,
|
|
57
|
+
selection: snapshot.selection, uniqueDeviceIdentityVerified: false, cryptographicServerIdentityVerified: false,
|
|
58
|
+
capabilities: snapshot.capabilities, observedAt: snapshot.observedAt },
|
|
59
|
+
}];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Converts offline imported data without granting runtime availability or writes. */
|
|
22
63
|
public static toDevices(snapshotArg: IKodiSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
|
|
23
64
|
const updatedAt = snapshotArg.updatedAt || new Date().toISOString();
|
|
24
65
|
const application = snapshotArg.application;
|
|
25
66
|
return [{
|
|
26
|
-
id: this.
|
|
67
|
+
id: this.offlineDeviceId(snapshotArg),
|
|
27
68
|
integrationDomain: 'kodi',
|
|
28
69
|
name: this.deviceName(snapshotArg),
|
|
29
70
|
protocol: 'http',
|
|
30
71
|
manufacturer: snapshotArg.deviceInfo.manufacturer || 'Kodi',
|
|
31
72
|
model: snapshotArg.deviceInfo.model || application?.name,
|
|
32
|
-
online:
|
|
73
|
+
online: false,
|
|
33
74
|
features: [
|
|
34
|
-
{ id: 'playback', capability: 'media', name: 'Playback', readable: true, writable:
|
|
35
|
-
{ id: 'volume', capability: 'media', name: 'Volume', readable: true, writable:
|
|
36
|
-
{ id: 'muted', capability: 'media', name: 'Muted', readable: true, writable:
|
|
75
|
+
{ id: 'playback', capability: 'media', name: 'Playback', readable: true, writable: false },
|
|
76
|
+
{ id: 'volume', capability: 'media', name: 'Volume', readable: true, writable: false, unit: '%' },
|
|
77
|
+
{ id: 'muted', capability: 'media', name: 'Muted', readable: true, writable: false },
|
|
37
78
|
{ id: 'active_player', capability: 'media', name: 'Active player', readable: true, writable: false },
|
|
38
79
|
{ id: 'current_title', capability: 'media', name: 'Current title', readable: true, writable: false },
|
|
39
|
-
{ id: 'notification', capability: 'media', name: 'Notification', readable: false, writable: true },
|
|
40
80
|
],
|
|
41
81
|
state: [
|
|
42
82
|
{ featureId: 'playback', value: this.mediaState(snapshotArg), updatedAt },
|
|
@@ -46,6 +86,7 @@ export class KodiMapper {
|
|
|
46
86
|
{ featureId: 'current_title', value: this.mediaTitle(snapshotArg.item) || null, updatedAt },
|
|
47
87
|
],
|
|
48
88
|
metadata: {
|
|
89
|
+
runtimeAuthority: 'offline-import', uniqueDeviceIdentityVerified: false,
|
|
49
90
|
uuid: snapshotArg.deviceInfo.uuid,
|
|
50
91
|
host: snapshotArg.deviceInfo.host,
|
|
51
92
|
port: snapshotArg.deviceInfo.port,
|
|
@@ -62,11 +103,12 @@ export class KodiMapper {
|
|
|
62
103
|
id: `media_player.${this.slug(this.deviceName(snapshotArg))}`,
|
|
63
104
|
uniqueId: `kodi_${this.uniqueBase(snapshotArg)}`,
|
|
64
105
|
integrationDomain: 'kodi',
|
|
65
|
-
deviceId: this.
|
|
106
|
+
deviceId: this.offlineDeviceId(snapshotArg),
|
|
66
107
|
platform: 'media_player',
|
|
67
108
|
name: this.deviceName(snapshotArg),
|
|
68
109
|
state: this.mediaState(snapshotArg),
|
|
69
110
|
attributes: {
|
|
111
|
+
runtimeAuthority: 'offline-import', uniqueDeviceIdentityVerified: false,
|
|
70
112
|
volumeLevel: typeof snapshotArg.application?.volume === 'number' ? snapshotArg.application.volume / 100 : undefined,
|
|
71
113
|
isVolumeMuted: snapshotArg.application?.muted,
|
|
72
114
|
mediaContentId: this.mediaContentId(item),
|
|
@@ -86,7 +128,7 @@ export class KodiMapper {
|
|
|
86
128
|
live: snapshotArg.playerProperties?.live,
|
|
87
129
|
dynamicRange: this.dynamicRange(item),
|
|
88
130
|
},
|
|
89
|
-
available:
|
|
131
|
+
available: false,
|
|
90
132
|
}];
|
|
91
133
|
}
|
|
92
134
|
|
|
@@ -151,7 +193,7 @@ export class KodiMapper {
|
|
|
151
193
|
return Array.isArray(valueArg) ? valueArg[0] : valueArg;
|
|
152
194
|
}
|
|
153
195
|
|
|
154
|
-
private static
|
|
196
|
+
private static offlineDeviceId(snapshotArg: IKodiSnapshot): string {
|
|
155
197
|
return `kodi.device.${this.uniqueBase(snapshotArg)}`;
|
|
156
198
|
}
|
|
157
199
|
|