@smarthome.exchange/integrations 1.0.0 → 3.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 +84 -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 +81 -0
- package/dist_ts/core/classes.nativeintegrationowner.js +273 -0
- package/dist_ts/core/classes.runtimemanager.d.ts +2 -0
- package/dist_ts/core/classes.runtimemanager.js +11 -3
- package/dist_ts/core/index.d.ts +2 -0
- package/dist_ts/core/index.js +3 -1
- package/dist_ts/core/types.d.ts +16 -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/deconz/deconz.classes.client.d.ts +37 -29
- package/dist_ts/integrations/deconz/deconz.classes.client.js +521 -294
- package/dist_ts/integrations/deconz/deconz.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/deconz/deconz.classes.configflow.js +63 -33
- package/dist_ts/integrations/deconz/deconz.classes.integration.d.ts +4 -3
- package/dist_ts/integrations/deconz/deconz.classes.integration.js +85 -40
- package/dist_ts/integrations/deconz/deconz.mapper.d.ts +1 -0
- package/dist_ts/integrations/deconz/deconz.mapper.js +24 -6
- package/dist_ts/integrations/deconz/deconz.types.d.ts +43 -6
- package/dist_ts/integrations/deconz/deconz.validation.d.ts +7 -1
- package/dist_ts/integrations/deconz/deconz.validation.js +215 -4
- 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/esphome/esphome.classes.client.d.ts +19 -7
- package/dist_ts/integrations/esphome/esphome.classes.client.js +183 -89
- package/dist_ts/integrations/esphome/esphome.classes.configflow.d.ts +5 -2
- package/dist_ts/integrations/esphome/esphome.classes.configflow.js +43 -33
- package/dist_ts/integrations/esphome/esphome.classes.integration.d.ts +8 -6
- package/dist_ts/integrations/esphome/esphome.classes.integration.js +71 -43
- package/dist_ts/integrations/esphome/esphome.discovery.js +4 -12
- package/dist_ts/integrations/esphome/esphome.mapper.d.ts +2 -2
- package/dist_ts/integrations/esphome/esphome.mapper.js +6 -4
- package/dist_ts/integrations/esphome/esphome.types.d.ts +31 -6
- package/dist_ts/integrations/esphome/esphome.types.js +1 -1
- package/dist_ts/integrations/esphome/esphome.validation.d.ts +7 -0
- package/dist_ts/integrations/esphome/esphome.validation.js +66 -0
- package/dist_ts/integrations/homeassistant/homeassistant.classes.client.d.ts +32 -4
- package/dist_ts/integrations/homeassistant/homeassistant.classes.client.js +345 -116
- package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.d.ts +8 -5
- package/dist_ts/integrations/homeassistant/homeassistant.classes.integration.js +34 -8
- package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.d.ts +7 -4
- package/dist_ts/integrations/homeassistant/homeassistant.classes.runtime.js +44 -56
- package/dist_ts/integrations/homeassistant/homeassistant.types.d.ts +12 -1
- package/dist_ts/integrations/hue/hue.classes.client.d.ts +6 -1
- package/dist_ts/integrations/hue/hue.classes.client.js +120 -45
- package/dist_ts/integrations/hue/hue.classes.configflow.js +16 -7
- package/dist_ts/integrations/hue/hue.classes.integration.d.ts +2 -0
- package/dist_ts/integrations/hue/hue.classes.integration.js +103 -30
- package/dist_ts/integrations/hue/hue.validation.d.ts +1 -0
- package/dist_ts/integrations/hue/hue.validation.js +14 -4
- 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/mqtt/mqtt.classes.client.d.ts +15 -2
- package/dist_ts/integrations/mqtt/mqtt.classes.client.js +173 -23
- package/dist_ts/integrations/mqtt/mqtt.classes.configflow.d.ts +5 -1
- package/dist_ts/integrations/mqtt/mqtt.classes.configflow.js +53 -33
- package/dist_ts/integrations/mqtt/mqtt.classes.integration.d.ts +4 -1
- package/dist_ts/integrations/mqtt/mqtt.classes.integration.js +127 -56
- package/dist_ts/integrations/mqtt/mqtt.classes.session.d.ts +13 -0
- package/dist_ts/integrations/mqtt/mqtt.classes.session.js +130 -0
- package/dist_ts/integrations/mqtt/mqtt.mapper.d.ts +3 -0
- package/dist_ts/integrations/mqtt/mqtt.mapper.js +44 -1
- package/dist_ts/integrations/mqtt/mqtt.types.d.ts +46 -0
- package/dist_ts/integrations/mqtt/mqtt.validation.d.ts +7 -0
- package/dist_ts/integrations/mqtt/mqtt.validation.js +180 -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/samsungtv/samsungtv.classes.client.d.ts +4 -2
- package/dist_ts/integrations/samsungtv/samsungtv.classes.client.js +41 -16
- package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.d.ts +5 -0
- package/dist_ts/integrations/samsungtv/samsungtv.classes.configflow.js +20 -7
- package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.d.ts +1 -0
- package/dist_ts/integrations/samsungtv/samsungtv.classes.integration.js +53 -50
- package/dist_ts/integrations/samsungtv/samsungtv.types.d.ts +4 -1
- package/dist_ts/integrations/shelly/shelly.classes.client.d.ts +26 -30
- package/dist_ts/integrations/shelly/shelly.classes.client.js +504 -276
- package/dist_ts/integrations/shelly/shelly.classes.configflow.d.ts +8 -3
- package/dist_ts/integrations/shelly/shelly.classes.configflow.js +43 -38
- package/dist_ts/integrations/shelly/shelly.classes.integration.d.ts +6 -6
- package/dist_ts/integrations/shelly/shelly.classes.integration.js +103 -101
- package/dist_ts/integrations/shelly/shelly.classes.subscription.d.ts +4 -0
- package/dist_ts/integrations/shelly/shelly.classes.subscription.js +24 -2
- package/dist_ts/integrations/shelly/shelly.mapper.d.ts +1 -2
- package/dist_ts/integrations/shelly/shelly.mapper.js +21 -20
- package/dist_ts/integrations/shelly/shelly.rpc.js +4 -1
- package/dist_ts/integrations/shelly/shelly.types.d.ts +42 -2
- package/dist_ts/integrations/shelly/shelly.types.js +11 -2
- package/dist_ts/integrations/shelly/shelly.validation.d.ts +7 -0
- package/dist_ts/integrations/shelly/shelly.validation.js +79 -0
- package/dist_ts/integrations/sonos/sonos.classes.client.d.ts +79 -22
- package/dist_ts/integrations/sonos/sonos.classes.client.js +427 -139
- package/dist_ts/integrations/sonos/sonos.classes.configflow.d.ts +9 -2
- package/dist_ts/integrations/sonos/sonos.classes.configflow.js +26 -28
- package/dist_ts/integrations/sonos/sonos.classes.integration.d.ts +7 -6
- package/dist_ts/integrations/sonos/sonos.classes.integration.js +289 -224
- package/dist_ts/integrations/sonos/sonos.config.d.ts +6 -1
- package/dist_ts/integrations/sonos/sonos.config.js +23 -2
- package/dist_ts/integrations/sonos/sonos.mapper.d.ts +1 -1
- package/dist_ts/integrations/sonos/sonos.mapper.js +14 -9
- package/dist_ts/integrations/sonos/sonos.matchers.js +3 -3
- package/dist_ts/integrations/sonos/sonos.types.d.ts +26 -1
- 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/tasmota/tasmota.classes.integration.d.ts +5 -0
- package/dist_ts/integrations/tasmota/tasmota.classes.integration.js +23 -7
- package/dist_ts/integrations/tasmota/tasmota.classes.runtime.d.ts +19 -5
- package/dist_ts/integrations/tasmota/tasmota.classes.runtime.js +115 -41
- package/dist_ts/integrations/tasmota/tasmota.config.js +23 -1
- package/dist_ts/integrations/tasmota/tasmota.credentials.d.ts +11 -0
- package/dist_ts/integrations/tasmota/tasmota.credentials.js +97 -0
- package/dist_ts/integrations/tasmota/tasmota.discovery.d.ts +7 -1
- package/dist_ts/integrations/tasmota/tasmota.discovery.js +108 -30
- package/dist_ts/integrations/tasmota/tasmota.mapper.d.ts +2 -2
- package/dist_ts/integrations/tasmota/tasmota.mapper.js +7 -7
- package/dist_ts/integrations/tasmota/tasmota.types.d.ts +41 -2
- package/dist_ts/integrations/tuya/tuya.classes.integration.d.ts +4 -1
- package/dist_ts/integrations/tuya/tuya.classes.integration.js +79 -110
- package/dist_ts/integrations/tuya/tuya.classes.runtime.d.ts +5 -2
- package/dist_ts/integrations/tuya/tuya.classes.runtime.js +73 -26
- package/dist_ts/integrations/tuya/tuya.mapper.js +2 -2
- package/dist_ts/integrations/tuya/tuya.types.d.ts +3 -0
- package/dist_ts/integrations/tuya/tuya.validation.d.ts +2 -2
- package/dist_ts/integrations/tuya/tuya.validation.js +6 -11
- package/dist_ts/integrations/webostv/webostv.classes.client.d.ts +6 -5
- package/dist_ts/integrations/webostv/webostv.classes.client.js +147 -76
- package/dist_ts/integrations/webostv/webostv.classes.configflow.d.ts +7 -2
- package/dist_ts/integrations/webostv/webostv.classes.configflow.js +36 -20
- package/dist_ts/integrations/webostv/webostv.classes.integration.d.ts +4 -4
- package/dist_ts/integrations/webostv/webostv.classes.integration.js +46 -54
- package/dist_ts/integrations/webostv/webostv.mapper.js +5 -5
- package/dist_ts/integrations/webostv/webostv.types.d.ts +16 -3
- package/dist_ts/integrations/webostv/webostv.types.js +1 -1
- package/dist_ts/integrations/webostv/webostv.validation.d.ts +3 -1
- package/dist_ts/integrations/webostv/webostv.validation.js +23 -1
- 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/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.js +18 -8
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.d.ts +4 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.js +54 -6
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.d.ts +12 -6
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.js +73 -59
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.d.ts +3 -2
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.js +17 -12
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.types.d.ts +31 -2
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.d.ts +9 -1
- package/dist_ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.js +57 -7
- package/dist_ts/integrations/zwave_js/zwave_js.classes.client.d.ts +10 -4
- package/dist_ts/integrations/zwave_js/zwave_js.classes.client.js +167 -31
- package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.d.ts +6 -1
- package/dist_ts/integrations/zwave_js/zwave_js.classes.configflow.js +57 -35
- package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.d.ts +3 -1
- package/dist_ts/integrations/zwave_js/zwave_js.classes.integration.js +59 -37
- package/dist_ts/integrations/zwave_js/zwave_js.discovery.js +3 -3
- package/dist_ts/integrations/zwave_js/zwave_js.mapper.js +41 -24
- package/dist_ts/integrations/zwave_js/zwave_js.types.d.ts +25 -3
- package/dist_ts/integrations/zwave_js/zwave_js.validation.d.ts +6 -2
- package/dist_ts/integrations/zwave_js/zwave_js.validation.js +52 -17
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +9 -9
- package/readme.md +539 -8
- 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 +302 -0
- package/ts/core/classes.runtimemanager.ts +11 -2
- package/ts/core/index.ts +2 -0
- package/ts/core/types.ts +18 -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/deconz/deconz.classes.client.ts +348 -252
- package/ts/integrations/deconz/deconz.classes.configflow.ts +57 -29
- package/ts/integrations/deconz/deconz.classes.integration.ts +69 -37
- package/ts/integrations/deconz/deconz.mapper.ts +22 -5
- package/ts/integrations/deconz/deconz.types.ts +49 -6
- package/ts/integrations/deconz/deconz.validation.ts +177 -4
- package/ts/integrations/deconz/readme.md +15 -1
- 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/esphome/esphome.classes.client.ts +179 -94
- package/ts/integrations/esphome/esphome.classes.configflow.ts +40 -33
- package/ts/integrations/esphome/esphome.classes.integration.ts +63 -36
- package/ts/integrations/esphome/esphome.discovery.ts +3 -12
- package/ts/integrations/esphome/esphome.mapper.ts +9 -7
- package/ts/integrations/esphome/esphome.types.ts +35 -6
- package/ts/integrations/esphome/esphome.validation.ts +66 -0
- package/ts/integrations/esphome/readme.md +20 -6
- package/ts/integrations/homeassistant/homeassistant.classes.client.ts +353 -125
- package/ts/integrations/homeassistant/homeassistant.classes.integration.ts +35 -18
- package/ts/integrations/homeassistant/homeassistant.classes.runtime.ts +50 -63
- package/ts/integrations/homeassistant/homeassistant.types.ts +17 -1
- package/ts/integrations/homeassistant/readme.md +22 -6
- package/ts/integrations/hue/hue.classes.client.ts +110 -41
- package/ts/integrations/hue/hue.classes.configflow.ts +12 -6
- package/ts/integrations/hue/hue.classes.integration.ts +92 -26
- package/ts/integrations/hue/hue.validation.ts +13 -3
- package/ts/integrations/hue/readme.md +3 -3
- 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/mqtt/mqtt.classes.client.ts +136 -19
- package/ts/integrations/mqtt/mqtt.classes.configflow.ts +45 -32
- package/ts/integrations/mqtt/mqtt.classes.integration.ts +99 -38
- package/ts/integrations/mqtt/mqtt.classes.session.ts +118 -0
- package/ts/integrations/mqtt/mqtt.mapper.ts +43 -1
- package/ts/integrations/mqtt/mqtt.types.ts +52 -0
- package/ts/integrations/mqtt/mqtt.validation.ts +157 -0
- package/ts/integrations/mqtt/readme.md +12 -3
- 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/samsungtv/readme.md +11 -5
- package/ts/integrations/samsungtv/samsungtv.classes.client.ts +40 -15
- package/ts/integrations/samsungtv/samsungtv.classes.configflow.ts +21 -6
- package/ts/integrations/samsungtv/samsungtv.classes.integration.ts +53 -38
- package/ts/integrations/samsungtv/samsungtv.types.ts +4 -1
- package/ts/integrations/shelly/readme.md +22 -7
- package/ts/integrations/shelly/shelly.classes.client.ts +386 -269
- package/ts/integrations/shelly/shelly.classes.configflow.ts +43 -38
- package/ts/integrations/shelly/shelly.classes.integration.ts +92 -111
- package/ts/integrations/shelly/shelly.classes.subscription.ts +24 -1
- package/ts/integrations/shelly/shelly.mapper.ts +19 -20
- package/ts/integrations/shelly/shelly.rpc.ts +2 -0
- package/ts/integrations/shelly/shelly.types.ts +50 -2
- package/ts/integrations/shelly/shelly.validation.ts +76 -0
- package/ts/integrations/sonos/readme.md +117 -38
- package/ts/integrations/sonos/sonos.classes.client.ts +392 -123
- package/ts/integrations/sonos/sonos.classes.configflow.ts +25 -28
- package/ts/integrations/sonos/sonos.classes.integration.ts +211 -186
- package/ts/integrations/sonos/sonos.config.ts +24 -2
- package/ts/integrations/sonos/sonos.mapper.ts +13 -8
- package/ts/integrations/sonos/sonos.matchers.ts +2 -2
- package/ts/integrations/sonos/sonos.types.ts +30 -1
- 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/tasmota/readme.md +16 -5
- package/ts/integrations/tasmota/tasmota.classes.integration.ts +24 -6
- package/ts/integrations/tasmota/tasmota.classes.runtime.ts +112 -43
- package/ts/integrations/tasmota/tasmota.config.ts +17 -0
- package/ts/integrations/tasmota/tasmota.credentials.ts +92 -0
- package/ts/integrations/tasmota/tasmota.discovery.ts +87 -24
- package/ts/integrations/tasmota/tasmota.mapper.ts +6 -6
- package/ts/integrations/tasmota/tasmota.types.ts +43 -2
- package/ts/integrations/tuya/readme.md +52 -10
- package/ts/integrations/tuya/tuya.classes.integration.ts +69 -98
- package/ts/integrations/tuya/tuya.classes.runtime.ts +70 -24
- package/ts/integrations/tuya/tuya.mapper.ts +1 -1
- package/ts/integrations/tuya/tuya.types.ts +4 -0
- package/ts/integrations/tuya/tuya.validation.ts +5 -8
- package/ts/integrations/webostv/readme.md +115 -22
- package/ts/integrations/webostv/webostv.classes.client.ts +123 -50
- package/ts/integrations/webostv/webostv.classes.configflow.ts +35 -15
- package/ts/integrations/webostv/webostv.classes.integration.ts +44 -37
- package/ts/integrations/webostv/webostv.mapper.ts +4 -4
- package/ts/integrations/webostv/webostv.types.ts +18 -3
- package/ts/integrations/webostv/webostv.validation.ts +22 -1
- 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
- package/ts/integrations/zigbee2mqtt/readme.md +22 -27
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.configflow.ts +16 -7
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.integration.ts +53 -6
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.classes.runtime.ts +62 -49
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.mapper.ts +17 -11
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.types.ts +35 -2
- package/ts/integrations/zigbee2mqtt/zigbee2mqtt.validation.ts +53 -7
- package/ts/integrations/zwave_js/readme.md +42 -9
- package/ts/integrations/zwave_js/zwave_js.classes.client.ts +155 -31
- package/ts/integrations/zwave_js/zwave_js.classes.configflow.ts +55 -31
- package/ts/integrations/zwave_js/zwave_js.classes.integration.ts +57 -35
- package/ts/integrations/zwave_js/zwave_js.discovery.ts +2 -2
- package/ts/integrations/zwave_js/zwave_js.mapper.ts +37 -23
- package/ts/integrations/zwave_js/zwave_js.types.ts +26 -3
- package/ts/integrations/zwave_js/zwave_js.validation.ts +52 -18
- package/ts/plugins.ts +2 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { IPioneerConfig, TPioneerControl } from './pioneer.types.js';
|
|
3
|
+
|
|
4
|
+
export function pioneerRecord(value: unknown, allowed?: readonly string[]): Record<string, unknown> {
|
|
5
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
|
|
6
|
+
throw new TypeError('Pioneer input must be a plain data object.');
|
|
7
|
+
}
|
|
8
|
+
const result: Record<string, unknown> = {};
|
|
9
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
10
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
11
|
+
if (typeof key !== 'string' || (allowed && !allowed.includes(key)) || !descriptor || !('value' in descriptor) || !descriptor.enumerable) {
|
|
12
|
+
throw new TypeError('Pioneer input contains an unsupported field or accessor.');
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(result, key, { value: descriptor.value, enumerable: true });
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function pioneerText(value: unknown, label: string, maximum = 256): string {
|
|
20
|
+
if (typeof value !== 'string' || !value || value.length > maximum || value !== value.trim() || /[\p{Cc}\p{Cs}]/u.test(value)) {
|
|
21
|
+
throw new TypeError(`Pioneer ${label} must be bounded text without control characters.`);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function pioneerHost(value: unknown): string {
|
|
27
|
+
if (typeof value !== 'string' || plugins.net.isIP(value) !== 4) throw new TypeError('Pioneer requires a selected private IPv4 address.');
|
|
28
|
+
const [first, second, , last] = value.split('.').map(Number);
|
|
29
|
+
if (!(first === 10 || first === 127 || (first === 172 && second >= 16 && second <= 31) || (first === 192 && second === 168)) || last === 0 || last === 255) {
|
|
30
|
+
throw new TypeError('Pioneer requires a selected private or loopback IPv4 host.');
|
|
31
|
+
}
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function pioneerUdn(value: unknown): string {
|
|
36
|
+
if (typeof value !== 'string' || !/^uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) throw new TypeError('Pioneer requires an exact UPnP UUID.');
|
|
37
|
+
return value.toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function pioneerDescription(value: unknown, host: string): string {
|
|
41
|
+
const url = new URL(pioneerText(value, 'description URL', 2048));
|
|
42
|
+
if (url.protocol !== 'http:' || url.hostname !== host || url.username || url.password || url.hash || url.search || url.pathname === '/') {
|
|
43
|
+
throw new TypeError('Pioneer description must select an HTTP path at the receiver IP without credentials, query or fragment.');
|
|
44
|
+
}
|
|
45
|
+
return url.href;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function integer(value: unknown, fallback: number, minimum: number, maximum: number): number {
|
|
49
|
+
if (value === undefined) return fallback;
|
|
50
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < minimum || value > maximum) throw new RangeError('Pioneer numeric bound is invalid.');
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function pioneerVolume(value: unknown): number {
|
|
55
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0 || value > 185) throw new TypeError('Pioneer volume must be a raw integer from 0 through 185.');
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const pioneerConfigKeys = ['homeId', 'host', 'descriptionUrl', 'expectedUdn', 'model', 'allowUnencryptedLan', 'maximumVolumeRaw', 'name', 'port', 'timeoutMs', 'maxPendingOperations', 'maxDescriptionBytes', 'maxFrameBytes'] as const;
|
|
60
|
+
|
|
61
|
+
export function normalizePioneerConfig(value: unknown): Readonly<Required<Omit<IPioneerConfig, 'name'>> & Pick<IPioneerConfig, 'name'>> {
|
|
62
|
+
const config = pioneerRecord(value, pioneerConfigKeys);
|
|
63
|
+
const host = pioneerHost(config.host);
|
|
64
|
+
if (config.model !== 'SC-95') throw new TypeError('Pioneer model has no admitted vendor profile.');
|
|
65
|
+
if (config.allowUnencryptedLan !== true) throw new TypeError('Pioneer requires an explicit plaintext LAN acknowledgement.');
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
homeId: pioneerText(config.homeId, 'home ID'), host, descriptionUrl: pioneerDescription(config.descriptionUrl, host),
|
|
68
|
+
expectedUdn: pioneerUdn(config.expectedUdn), model: config.model, allowUnencryptedLan: config.allowUnencryptedLan,
|
|
69
|
+
maximumVolumeRaw: pioneerVolume(config.maximumVolumeRaw),
|
|
70
|
+
...(config.name === undefined ? {} : { name: pioneerText(config.name, 'name', 128) }),
|
|
71
|
+
port: integer(config.port, 23, 1, 65535), timeoutMs: integer(config.timeoutMs, 5000, 1, 60000),
|
|
72
|
+
maxPendingOperations: integer(config.maxPendingOperations, 16, 1, 128),
|
|
73
|
+
maxDescriptionBytes: integer(config.maxDescriptionBytes, 65536, 256, 1048576), maxFrameBytes: integer(config.maxFrameBytes, 512, 16, 4096),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validatePioneerControl(value: unknown): TPioneerControl {
|
|
78
|
+
const control = pioneerRecord(value, ['feature', 'value']);
|
|
79
|
+
if (control.feature === 'power' && typeof control.value === 'boolean') return { feature: 'power', value: control.value };
|
|
80
|
+
if (control.feature === 'muted' && typeof control.value === 'boolean') return { feature: 'muted', value: control.value };
|
|
81
|
+
if (control.feature === 'volume_raw') return { feature: 'volume_raw', value: pioneerVolume(control.value) };
|
|
82
|
+
if (control.feature === 'input' && typeof control.value === 'string' && /^\d{2}$/.test(control.value)) return { feature: 'input', value: control.value };
|
|
83
|
+
throw new TypeError('Pioneer supports one explicit power, mute, raw volume or observed physical input control.');
|
|
84
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Samsung Smart TV
|
|
2
2
|
|
|
3
|
-
The native integration consumes `SamsungTvProtocol` from published `@ecobridge.xyz/devicemanager
|
|
3
|
+
The native integration consumes `SamsungTvProtocol` from published `@ecobridge.xyz/devicemanager` 4.11.1. It owns configuration, encrypted pairing, home admission and smarthome mapping; the dependency owns HTTP/TLS/WebSocket parsing, identity verification, operation deadlines and transport cleanup.
|
|
4
4
|
|
|
5
5
|
Configure an exact `homeId`, the UUID reported by the TV, explicit `httpOrigin` and `websocketOrigin`, and `pairingMode: 'prompt' | 'existing'`. WSS certificate and hostname verification are mandatory. `certificateAuthority` adds an explicitly trusted PEM CA chain; it never disables verification. REST and WSS must use the same host. Local HTTP discovery requires `allowUnencryptedLocalDiscovery: true` and is restricted by the protocol to literal private/link-local/loopback IPv4 or ULA/loopback IPv6. HTTP carries no token or credentials and follows no redirects.
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
|
-
const integration = new SamsungtvIntegration({ db, keyProvider });
|
|
8
|
+
const integration = new SamsungtvIntegration({ homeId: 'home-one', db, keyProvider });
|
|
9
9
|
const runtime = await integration.setup({
|
|
10
10
|
homeId: 'home-one',
|
|
11
11
|
expectedDeviceId: '2ff68485-c4cb-48c7-a6ac-9c50e44d7084',
|
|
@@ -14,14 +14,20 @@ const runtime = await integration.setup({
|
|
|
14
14
|
certificateAuthority: trustedTvCa,
|
|
15
15
|
allowUnencryptedLocalDiscovery: true,
|
|
16
16
|
pairingMode: 'prompt',
|
|
17
|
-
}, {});
|
|
17
|
+
}, { homeId: 'home-one' });
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Every setup and config flow requires an independently host-selected context `homeId`. Configuration must match that selection. An optional constructor `homeId` prebinds the integration; otherwise the first valid admission binds it for its lifetime. Another home requires another integration instance. Constructor options, configuration, context, discovery hints and controls are captured as data fields before asynchronous work; accessors cannot supply authority. Config forms derive their home from the captured host context, and integration or flow destruction invalidates outstanding forms.
|
|
21
|
+
|
|
22
|
+
The standalone `SamsungtvClient` accepts `ISamsungtvClientOptions` containing `{ db, keyProvider }`, or an actual scoped `NoSqlConfigStore`. The integration's `homeId` and `capacity` options belong to `ISamsungtvIntegrationOptions`.
|
|
23
|
+
|
|
24
|
+
The database and external encryption key provider are caller-owned. The default registry constructor instead accepts a context `NoSqlConfigStore` scoped to the exact configured home and `samsungtv` domain. Generic stores, incorrect scopes and overridden scope claims are rejected. Pairing credentials never appear in config flow results, snapshots, entities or logs. Confirmed tokens are saved through encrypted document models before controls become ready, and are bound to the exact UUID and WSS origin. Existing mode requires prior pairing in the same home. Runtime destroy leaves caller-owned stores/databases open, drains accepted persistence and transports, clears the protocol reference after successful cleanup and is terminal. No filesystem application state is written. Clearing JavaScript references does not guarantee physical string zeroization.
|
|
25
|
+
|
|
26
|
+
The native owner tracks accepted setup operations, runtimes and failed cleanup under one `capacity` limit (default 16). Cleanup is registered before startup and transferred to the runtime only after successful setup. Integration destruction joins pending setups and every runtime; a failed cleanup retains its handle and capacity until explicit `integration.retryCleanup()`. Client and protocol close promises are published before synchronous abort callbacks and preserve their original result. A late pairing-persistence failure becomes a safe `PERSISTENCE_FAILED` cleanup error. The client also drains its owned encrypted store even when protocol cleanup fails. `client.retryCleanup()` requires terminal destruction and acknowledges settled cleanup through the published protocol/store APIs. Neither cleanup API repeats pairing, token persistence, startup or commands, and neither reopens a destroyed owner. A failed setup can free retained capacity through the integration's cleanup API while an otherwise open integration remains available for an explicitly requested new setup.
|
|
21
27
|
|
|
22
28
|
Every snapshot reads the current device information and observed power. Failed reads propagate; no manual fallback, guessed playback/source/volume, optimistic state or synthetic availability is produced. Device and entity IDs include the escaped home and exact TV UUID. `power` is read-only; `remote_key` admits one exact currently supported `KEY_` code. Directional keys additionally require the TV's current capability declaration.
|
|
23
29
|
|
|
24
|
-
Services require the exact owned `deviceId` or `entityId
|
|
30
|
+
Services require the exact owned `deviceId` or `entityId`; when both are present, both must match. Feature writes require the exact owned device and writable `remote_key` media feature, with current key capability rechecked by the protocol. Destruction permanently refuses observations and controls. Supported media-player services are `turn_off`, `media_play`, `media_pause`, `media_stop`, `volume_up` and `volume_down`. `remote.send_command` accepts exactly `{ command: 'KEY_HOME' }`; `samsungtv.send_key` accepts exactly `{ key: 'KEY_HOME' }`. `samsungtv.snapshot` accepts no data fields. Commands return `{ key, transport: 'sent', effectOutcome: 'indeterminate' }`: Samsung's remote channel does not acknowledge physical effects. Errors retain the protocol's safe code and effect outcome. No command is retried, repeated or replayed and concurrent effects are rejected.
|
|
25
31
|
|
|
26
32
|
The previous fixture config, plaintext token/host/port config, arbitrary WebSocket command, repeated keys and optimistic media fields have been removed. Reconfigure using the explicit native config above. `play_pause`, absolute mute/volume assignment, source/app selection, channel keys as track controls, power-on/Wake-on-LAN, press/hold/release, legacy TCP/PIN and Frame Art Mode remain unsupported. An explicit `KEY_MUTE` command is a toggle and is never presented as a boolean mute assignment.
|
|
27
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { NoSqlConfigStore } from '../../core/classes.configstore.js';
|
|
3
|
-
import type { ISamsungtvConfig,
|
|
3
|
+
import type { ISamsungtvConfig, ISamsungtvClientOptions, ISamsungtvSnapshot, TSamsungtvRemoteKey } from './samsungtv.types.js';
|
|
4
4
|
import { normalizeSamsungtvConfig, samsungtvKeys, samsungtvObject, samsungtvString } from './samsungtv.validation.js';
|
|
5
5
|
|
|
6
6
|
interface IStoredPairing {
|
|
@@ -18,24 +18,27 @@ export class SamsungtvClient {
|
|
|
18
18
|
#protocol?: plugins.ecobridgeDeviceManager.SamsungTvProtocol;
|
|
19
19
|
#start?: Promise<void>;
|
|
20
20
|
#destroy?: Promise<void>;
|
|
21
|
+
#retryCleanup?: Promise<void>;
|
|
21
22
|
#removeAbort: () => void = () => undefined;
|
|
22
23
|
|
|
23
|
-
constructor(configArg: ISamsungtvConfig, optionsArg?:
|
|
24
|
+
constructor(configArg: ISamsungtvConfig, optionsArg?: ISamsungtvClientOptions | NoSqlConfigStore, signalArg?: AbortSignal) {
|
|
24
25
|
this.#config = normalizeSamsungtvConfig(configArg);
|
|
25
26
|
if (optionsArg instanceof NoSqlConfigStore) {
|
|
26
|
-
const scope =
|
|
27
|
+
const scope = NoSqlConfigStore.prototype.getScope.call(optionsArg);
|
|
27
28
|
if (scope.homeId !== this.#config.homeId || scope.integrationDomain !== 'samsungtv') {
|
|
28
29
|
throw new TypeError('Samsung TV requires an encrypted NoSqlConfigStore scoped to the exact configured home and samsungtv domain.');
|
|
29
30
|
}
|
|
30
31
|
this.#store = optionsArg;
|
|
31
32
|
this.#ownsStore = false;
|
|
32
33
|
} else {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const options = samsungtvObject(optionsArg ?? {}, 'Samsung TV client persistence options');
|
|
35
|
+
samsungtvKeys(options, ['db', 'keyProvider']);
|
|
36
|
+
if (!options.db || !options.keyProvider) throw new TypeError('Samsung TV requires an owner-supplied database and external encryption key provider.');
|
|
37
|
+
this.#store = new NoSqlConfigStore({ db: options.db as ISamsungtvClientOptions['db'], keyProvider: options.keyProvider as ISamsungtvClientOptions['keyProvider'], homeId: this.#config.homeId, integrationDomain: 'samsungtv' });
|
|
35
38
|
this.#ownsStore = true;
|
|
36
39
|
}
|
|
37
40
|
if (signalArg) {
|
|
38
|
-
const aborted = (): void => { void this.destroy(); };
|
|
41
|
+
const aborted = (): void => { void this.destroy().catch(() => undefined); };
|
|
39
42
|
if (signalArg.aborted) this.#lifetime.abort(new Error('Samsung TV setup was cancelled.'));
|
|
40
43
|
else {
|
|
41
44
|
signalArg.addEventListener('abort', aborted, { once: true });
|
|
@@ -48,7 +51,7 @@ export class SamsungtvClient {
|
|
|
48
51
|
|
|
49
52
|
public async start(signalArg?: AbortSignal): Promise<void> {
|
|
50
53
|
this.#assertOpen(signalArg);
|
|
51
|
-
if (!this.#start) this.#start = this.#initialize(signalArg);
|
|
54
|
+
if (!this.#start) this.#start = Promise.resolve().then(() => this.#initialize(signalArg));
|
|
52
55
|
await this.#start;
|
|
53
56
|
this.#assertOpen(signalArg);
|
|
54
57
|
}
|
|
@@ -67,20 +70,42 @@ export class SamsungtvClient {
|
|
|
67
70
|
|
|
68
71
|
public destroy(): Promise<void> {
|
|
69
72
|
if (!this.#destroy) {
|
|
73
|
+
// Publish before aborting foreign callbacks; retain failed cleanup for explicit acknowledgement.
|
|
74
|
+
this.#destroy = Promise.resolve().then(() => this.#drain(false));
|
|
70
75
|
this.#lifetime.abort(new Error('Samsung TV client is destroyed.'));
|
|
71
76
|
this.#removeAbort();
|
|
72
|
-
this.#
|
|
73
|
-
await this.#protocol?.close();
|
|
74
|
-
if (this.#start) await Promise.allSettled([this.#start]);
|
|
75
|
-
await this.#protocol?.close();
|
|
76
|
-
this.#protocol = undefined;
|
|
77
|
-
if (this.#ownsStore) await this.#store?.close();
|
|
78
|
-
this.#store = undefined;
|
|
79
|
-
})();
|
|
77
|
+
this.#removeAbort = () => undefined;
|
|
80
78
|
}
|
|
81
79
|
return this.#destroy;
|
|
82
80
|
}
|
|
83
81
|
|
|
82
|
+
/** Terminal cleanup only; never repeats pairing persistence, startup or commands. */
|
|
83
|
+
public retryCleanup(): Promise<void> {
|
|
84
|
+
if (!this.#destroy) return Promise.reject(new Error('Samsung TV cleanup requires terminal destroy.'));
|
|
85
|
+
this.#retryCleanup ??= Promise.resolve().then(async () => {
|
|
86
|
+
await this.#destroy!.catch(() => undefined);
|
|
87
|
+
await this.#drain(true);
|
|
88
|
+
});
|
|
89
|
+
return this.#retryCleanup;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async #drain(retryArg: boolean): Promise<void> {
|
|
93
|
+
const protocol = this.#protocol;
|
|
94
|
+
const store = this.#store;
|
|
95
|
+
// Close both owners before joining either: persistence providers may await store cancellation.
|
|
96
|
+
const cleanup = Promise.allSettled([
|
|
97
|
+
Promise.resolve().then(() => retryArg ? protocol?.retryCleanup() : protocol?.close()),
|
|
98
|
+
Promise.resolve().then(() => this.#ownsStore ? (retryArg ? store?.retryCleanup() : store?.close()) : undefined),
|
|
99
|
+
]);
|
|
100
|
+
// Startup errors belong to the start caller; protocol close owns late persistence failures.
|
|
101
|
+
const [results] = await Promise.all([cleanup, Promise.allSettled(this.#start ? [this.#start] : [])]);
|
|
102
|
+
if (results[0].status === 'fulfilled') this.#protocol = undefined;
|
|
103
|
+
if (results[1].status === 'fulfilled') this.#store = undefined;
|
|
104
|
+
const errors = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected').map((result) => result.reason as unknown);
|
|
105
|
+
if (errors.length) throw new AggregateError(errors, 'Samsung TV client cleanup failed; resources remain retained.');
|
|
106
|
+
this.#start = undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
async #initialize(signalArg?: AbortSignal): Promise<void> {
|
|
85
110
|
const signal = this.#signal(signalArg);
|
|
86
111
|
const store = this.#store;
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
|
|
2
|
+
import { NativeIntegrationOwner, type NativeIntegrationScope } from '../../core/classes.nativeintegrationowner.js';
|
|
2
3
|
import type { ISamsungtvConfig } from './samsungtv.types.js';
|
|
3
4
|
import { normalizeSamsungtvConfig, samsungtvKeys, samsungtvObject } from './samsungtv.validation.js';
|
|
4
5
|
|
|
5
6
|
export class SamsungtvConfigFlow implements IConfigFlow<ISamsungtvConfig> {
|
|
7
|
+
readonly #scope: NativeIntegrationScope;
|
|
8
|
+
|
|
9
|
+
constructor(ownerArg: NativeIntegrationOwner = new NativeIntegrationOwner('samsungtv')) {
|
|
10
|
+
this.#scope = ownerArg.createFlowScope();
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<ISamsungtvConfig>> {
|
|
7
|
-
|
|
8
|
-
const
|
|
14
|
+
const context = this.#scope.admit(contextArg);
|
|
15
|
+
const candidate = samsungtvObject(candidateArg, 'Samsung TV discovery candidate');
|
|
16
|
+
const metadata = candidate.metadata === undefined ? {} : samsungtvObject(candidate.metadata, 'Samsung TV candidate metadata');
|
|
9
17
|
const hints: Record<string, unknown> = {};
|
|
10
18
|
for (const field of ['httpOrigin', 'websocketOrigin', 'expectedDeviceId']) {
|
|
11
19
|
if (typeof metadata[field] === 'string') hints[field] = metadata[field];
|
|
12
20
|
}
|
|
21
|
+
Object.freeze(hints);
|
|
13
22
|
return {
|
|
14
23
|
kind: 'form', title: 'Pair Samsung Smart TV',
|
|
15
|
-
description: 'Confirm
|
|
24
|
+
description: 'Confirm the exact TV UUID, explicit REST/WSS origins and trusted TV CA for the selected home. Confirm the TV pairing prompt when using prompt mode. Pairing credentials are stored only in encrypted home-scoped persistence.',
|
|
16
25
|
fields: [
|
|
17
|
-
{ name: 'homeId', label: 'Home ID', type: 'text', required: true },
|
|
18
26
|
{ name: 'expectedDeviceId', label: 'Exact TV UUID', type: 'text', required: true },
|
|
19
27
|
{ name: 'httpOrigin', label: 'REST origin', type: 'text', required: true },
|
|
20
28
|
{ name: 'websocketOrigin', label: 'Verified WSS origin', type: 'text', required: true },
|
|
@@ -27,17 +35,24 @@ export class SamsungtvConfigFlow implements IConfigFlow<ISamsungtvConfig> {
|
|
|
27
35
|
try {
|
|
28
36
|
const values = samsungtvObject(valuesArg, 'Samsung TV config form');
|
|
29
37
|
samsungtvKeys(values, ['homeId', 'expectedDeviceId', 'httpOrigin', 'websocketOrigin', 'pairingMode', 'certificateAuthority', 'allowUnencryptedLocalDiscovery']);
|
|
38
|
+
if (values.homeId !== undefined && values.homeId !== context.homeId) throw new Error('Samsung TV form home does not match the selected host home.');
|
|
30
39
|
const config = normalizeSamsungtvConfig({
|
|
31
|
-
homeId:
|
|
40
|
+
homeId: context.homeId, expectedDeviceId: values.expectedDeviceId ?? hints.expectedDeviceId,
|
|
32
41
|
httpOrigin: values.httpOrigin ?? hints.httpOrigin, websocketOrigin: values.websocketOrigin ?? hints.websocketOrigin,
|
|
33
42
|
pairingMode: values.pairingMode, certificateAuthority: values.certificateAuthority,
|
|
34
43
|
allowUnencryptedLocalDiscovery: values.allowUnencryptedLocalDiscovery,
|
|
35
44
|
});
|
|
36
|
-
return
|
|
45
|
+
return await this.#scope.run(context, config.homeId, async (leaseArg) => {
|
|
46
|
+
leaseArg.assertOpen();
|
|
47
|
+
return { kind: 'done', title: 'Samsung Smart TV configuration accepted', config };
|
|
48
|
+
});
|
|
37
49
|
} catch (errorArg) {
|
|
38
50
|
return { kind: 'error', error: errorArg instanceof Error ? errorArg.message : 'Samsung TV configuration is invalid.' };
|
|
39
51
|
}
|
|
40
52
|
},
|
|
41
53
|
};
|
|
42
54
|
}
|
|
55
|
+
|
|
56
|
+
public destroy(): Promise<void> { return this.#scope.close(); }
|
|
57
|
+
public retryCleanup(): Promise<void> { return this.#scope.retryCleanup(); }
|
|
43
58
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
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
4
|
import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationRuntime, IIntegrationSetupContext, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
|
|
5
5
|
import { SamsungtvClient } from './samsungtv.classes.client.js';
|
|
6
6
|
import { SamsungtvConfigFlow } from './samsungtv.classes.configflow.js';
|
|
7
7
|
import { createSamsungtvDiscoveryDescriptor } from './samsungtv.discovery.js';
|
|
8
8
|
import { SamsungtvMapper } from './samsungtv.mapper.js';
|
|
9
|
-
import type { ISamsungtvConfig, ISamsungtvIntegrationOptions, ISamsungtvSnapshot, TSamsungtvRemoteKey } from './samsungtv.types.js';
|
|
10
|
-
import { samsungtvKeys, samsungtvObject } from './samsungtv.validation.js';
|
|
9
|
+
import type { ISamsungtvConfig, ISamsungtvClientOptions, ISamsungtvIntegrationOptions, ISamsungtvSnapshot, TSamsungtvRemoteKey } from './samsungtv.types.js';
|
|
10
|
+
import { normalizeSamsungtvConfig, samsungtvKeys, samsungtvObject } from './samsungtv.validation.js';
|
|
11
11
|
|
|
12
12
|
const serviceKeys = {
|
|
13
13
|
turn_off: 'KEY_POWEROFF', media_play: 'KEY_PLAY', media_pause: 'KEY_PAUSE', media_stop: 'KEY_STOP',
|
|
@@ -32,65 +32,77 @@ export class SamsungtvIntegration extends BaseIntegration<ISamsungtvConfig> {
|
|
|
32
32
|
public readonly displayName = 'Samsung Smart TV';
|
|
33
33
|
public readonly status = 'control-runtime' as const;
|
|
34
34
|
public readonly discoveryDescriptor = createSamsungtvDiscoveryDescriptor();
|
|
35
|
-
public readonly configFlow
|
|
35
|
+
public readonly configFlow: SamsungtvConfigFlow;
|
|
36
36
|
public readonly metadata = {
|
|
37
37
|
source: 'home-assistant/core', upstreamDomain: 'samsungtv', integrationType: 'device', iotClass: 'local_poll',
|
|
38
38
|
localApi: { implemented: ['verified UUID and WSS pairing', 'encrypted home-scoped pairing', 'fresh REST observations', 'single supported remote Click'],
|
|
39
39
|
explicitUnsupported: ['optimistic state', 'automatic retries or replay', 'legacy TCP/PIN', 'play_pause', 'app launch', 'absolute volume/mute assignment', 'wake-on-LAN', 'hold/release/repeated keys'] },
|
|
40
40
|
};
|
|
41
|
-
readonly #
|
|
42
|
-
|
|
43
|
-
#options?: ISamsungtvIntegrationOptions;
|
|
44
|
-
#destroyed = false;
|
|
45
|
-
#destroy?: Promise<void>;
|
|
41
|
+
readonly #owner: NativeIntegrationOwner;
|
|
42
|
+
#options?: ISamsungtvClientOptions;
|
|
46
43
|
|
|
47
|
-
constructor(optionsArg
|
|
44
|
+
constructor(optionsArg: ISamsungtvIntegrationOptions = {}) {
|
|
48
45
|
super();
|
|
49
|
-
|
|
46
|
+
const options = samsungtvObject(optionsArg, 'Samsung TV integration options');
|
|
47
|
+
samsungtvKeys(options, ['homeId', 'capacity', 'db', 'keyProvider']);
|
|
48
|
+
if ((options.db === undefined) !== (options.keyProvider === undefined)) {
|
|
49
|
+
throw new TypeError('Samsung TV database and key provider must be supplied together.');
|
|
50
|
+
}
|
|
51
|
+
this.#options = options.db === undefined ? undefined : {
|
|
52
|
+
db: options.db as ISamsungtvClientOptions['db'], keyProvider: options.keyProvider as ISamsungtvClientOptions['keyProvider'],
|
|
53
|
+
};
|
|
54
|
+
this.#owner = new NativeIntegrationOwner(this.domain, { homeId: options.homeId as string | undefined, capacity: options.capacity as number | undefined });
|
|
55
|
+
this.configFlow = new SamsungtvConfigFlow(this.#owner);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
public async setup(configArg: ISamsungtvConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
59
|
+
const config = normalizeSamsungtvConfig(configArg);
|
|
60
|
+
return this.#owner.runSetup(contextArg, config.homeId, async (leaseArg) => {
|
|
61
|
+
const client = new SamsungtvClient(config, leaseArg.context.configStore ?? this.#options, leaseArg.signal);
|
|
62
|
+
let cleanupAttempted = false;
|
|
63
|
+
leaseArg.own(() => {
|
|
64
|
+
if (cleanupAttempted) return client.retryCleanup();
|
|
65
|
+
cleanupAttempted = true;
|
|
66
|
+
return client.destroy();
|
|
67
|
+
});
|
|
68
|
+
const snapshot = await client.getSnapshot(leaseArg.signal);
|
|
69
|
+
leaseArg.assertOpen();
|
|
70
|
+
const runtime = new SamsungtvRuntime(client, snapshot, leaseArg);
|
|
71
|
+
leaseArg.promoteToRuntime();
|
|
64
72
|
return runtime;
|
|
65
|
-
}
|
|
66
|
-
finally { this.#pending.delete(client); }
|
|
73
|
+
});
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
public destroy(): Promise<void> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
...[...this.#runtimes].map((runtimeArg) => runtimeArg.destroy()),
|
|
74
|
-
...[...this.#pending].map((clientArg) => clientArg.destroy()),
|
|
75
|
-
]).then(() => { this.#options = undefined; });
|
|
76
|
-
}
|
|
77
|
-
return this.#destroy;
|
|
77
|
+
const closed = this.#owner.close();
|
|
78
|
+
this.#options = undefined;
|
|
79
|
+
return closed;
|
|
78
80
|
}
|
|
81
|
+
|
|
82
|
+
public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
export class HomeAssistantSamsungtvIntegration extends SamsungtvIntegration {}
|
|
82
86
|
|
|
83
87
|
class SamsungtvRuntime implements IIntegrationRuntime {
|
|
84
88
|
public readonly domain = 'samsungtv';
|
|
85
|
-
|
|
89
|
+
readonly #deviceId: string;
|
|
90
|
+
constructor(private readonly client: SamsungtvClient, identityArg: ISamsungtvSnapshot, private readonly lease: INativeSetupLease) {
|
|
91
|
+
this.#deviceId = SamsungtvMapper.deviceId(identityArg);
|
|
92
|
+
}
|
|
86
93
|
public isHealthy(): boolean { return this.client.isHealthy(); }
|
|
87
94
|
public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
|
|
95
|
+
this.lease.assertOpen();
|
|
88
96
|
return SamsungtvMapper.toDevices(await this.client.getSnapshot(signalArg));
|
|
89
97
|
}
|
|
90
|
-
public async entities(): Promise<IIntegrationEntity[]> {
|
|
98
|
+
public async entities(): Promise<IIntegrationEntity[]> {
|
|
99
|
+
this.lease.assertOpen();
|
|
100
|
+
return SamsungtvMapper.toEntities(await this.client.getSnapshot());
|
|
101
|
+
}
|
|
91
102
|
|
|
92
103
|
public async callService(requestArg: IServiceCallRequest): Promise<IServiceCallResult> {
|
|
93
104
|
try {
|
|
105
|
+
this.lease.assertOpen();
|
|
94
106
|
const request = samsungtvObject(requestArg, 'Samsung TV service request');
|
|
95
107
|
samsungtvKeys(request, ['domain', 'service', 'target', 'data']);
|
|
96
108
|
this.#assertTarget(request.target);
|
|
@@ -117,24 +129,27 @@ class SamsungtvRuntime implements IIntegrationRuntime {
|
|
|
117
129
|
|
|
118
130
|
public async writeFeature(requestArg: IDeviceFeatureWriteRequest, signalArg?: AbortSignal): Promise<IServiceCallResult> {
|
|
119
131
|
try {
|
|
132
|
+
this.lease.assertOpen();
|
|
120
133
|
const request = samsungtvObject(requestArg, 'Samsung TV feature write');
|
|
121
134
|
samsungtvKeys(request, ['device', 'feature', 'value']);
|
|
122
135
|
const device = samsungtvObject(request.device, 'Samsung TV feature device');
|
|
123
136
|
const feature = samsungtvObject(request.feature, 'Samsung TV feature');
|
|
124
|
-
if (device.id !==
|
|
137
|
+
if (device.id !== this.#deviceId || device.integrationDomain !== this.domain) {
|
|
125
138
|
throw new TypeError('Samsung TV feature device is not owned by this runtime.');
|
|
126
139
|
}
|
|
127
|
-
if (feature.id !== 'remote_key'
|
|
140
|
+
if (feature.id !== 'remote_key' || feature.capability !== 'media' || feature.readable !== false || feature.writable !== true) {
|
|
141
|
+
throw new TypeError('Samsung TV feature is not the owned writable remote key.');
|
|
142
|
+
}
|
|
128
143
|
return { success: true, data: await this.client.sendKey(remoteKey(request.value), signalArg) };
|
|
129
144
|
} catch (errorArg) { return failure(errorArg); }
|
|
130
145
|
}
|
|
131
146
|
|
|
132
|
-
public
|
|
147
|
+
public destroy(): Promise<void> { return this.lease.close(); }
|
|
133
148
|
|
|
134
149
|
#assertTarget(valueArg: unknown): void {
|
|
135
150
|
const target = samsungtvObject(valueArg, 'Samsung TV service target');
|
|
136
151
|
samsungtvKeys(target, ['deviceId', 'entityId']);
|
|
137
|
-
const deviceId =
|
|
152
|
+
const deviceId = this.#deviceId;
|
|
138
153
|
if ((target.deviceId === undefined && target.entityId === undefined) ||
|
|
139
154
|
(target.deviceId !== undefined && target.deviceId !== deviceId) ||
|
|
140
155
|
(target.entityId !== undefined && target.entityId !== `${deviceId}:media_player`)) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type * as plugins from '../../plugins.js';
|
|
2
2
|
import type { IConfigEncryptionKeyProvider } from '../../core/classes.configstore.js';
|
|
3
|
+
import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
|
|
3
4
|
|
|
4
5
|
export interface ISamsungtvConfig {
|
|
5
6
|
homeId: string;
|
|
@@ -16,11 +17,13 @@ export interface ISamsungtvConfig {
|
|
|
16
17
|
|
|
17
18
|
export interface IHomeAssistantSamsungtvConfig extends ISamsungtvConfig {}
|
|
18
19
|
|
|
19
|
-
export interface
|
|
20
|
+
export interface ISamsungtvClientOptions {
|
|
20
21
|
db: plugins.nosqldb.SmartdataDb;
|
|
21
22
|
keyProvider: IConfigEncryptionKeyProvider;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
export interface ISamsungtvIntegrationOptions extends INativeIntegrationOwnerOptions, Partial<ISamsungtvClientOptions> {}
|
|
26
|
+
|
|
24
27
|
export interface ISamsungtvSnapshot {
|
|
25
28
|
readonly homeId: string;
|
|
26
29
|
readonly device: plugins.ecobridgeDeviceManager.ISamsungTvDeviceInfo;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
# Shelly Gen2/3
|
|
2
2
|
|
|
3
|
-
This native port connects to a local Shelly Gen2 or Gen3 HTTP/RPC endpoint. Setup reads and validates the
|
|
3
|
+
This native port connects to a local Shelly Gen2 or Gen3 HTTP/RPC endpoint. Setup reads and validates the selected endpoint's reported device ID, MAC, model, generation, methods, status and configuration before returning a runtime. These observations do not attest physical hardware identity. It maps switch outputs and their power, voltage, current, energy and temperature readings to canonical devices and entities.
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
|
-
const integration = new ShellyIntegration(
|
|
6
|
+
const integration = new ShellyIntegration({
|
|
7
|
+
resolvePassword, // Caller-owned callback; receives homeId, credentialRef, signal and monotonic deadline.
|
|
8
|
+
});
|
|
7
9
|
const runtime = await integration.setup({
|
|
8
10
|
host: '192.168.1.20',
|
|
9
|
-
|
|
11
|
+
homeId: selectedHomeId,
|
|
12
|
+
expectedDeviceId: selectedDeviceId,
|
|
13
|
+
expectedMacAddress: selectedMacAddress,
|
|
14
|
+
expectedModel: selectedModel,
|
|
15
|
+
expectedGeneration: selectedGeneration,
|
|
16
|
+
credentialRef: selectedCredentialRef,
|
|
17
|
+
allowUnencryptedLan: true,
|
|
18
|
+
allowControl: true, // Omit for read-only outputs.
|
|
10
19
|
requestTimeoutMs: 10000,
|
|
11
|
-
}, {});
|
|
20
|
+
}, { homeId: hostSelectedHomeId, abortSignal: callerSignal });
|
|
12
21
|
|
|
13
22
|
const [device] = await runtime.devices();
|
|
14
23
|
const output = device.features.find((feature) => feature.id === 'switch_0_output');
|
|
@@ -21,14 +30,20 @@ await unsubscribe?.();
|
|
|
21
30
|
await integration.destroy();
|
|
22
31
|
```
|
|
23
32
|
|
|
24
|
-
`
|
|
33
|
+
`hostSelectedHomeId` comes independently from the embedding host and must match configuration and any optional constructor home binding. Config flow captures that selection before returning a form. An optional actual `NoSqlConfigStore` must match the admitted home/domain and remains borrowed. The integration's construction is inert; setup, flow work, live runtimes and retained cleanup share a default capacity of 16.
|
|
34
|
+
|
|
35
|
+
`resolvePassword` is an application-owned asynchronous callback. Its immutable request contains admitted `homeId`, captured `credentialRef`, `signal` and an absolute `performance.now()` deadline including queue, authentication and readback. Config accepts references rather than plaintext passwords. The adapter registers actual provider work before callback entry, fences late credentials after cancellation and joins the actual callback during shutdown. Provider errors are sanitized; a provider that never settles keeps cleanup pending.
|
|
36
|
+
|
|
37
|
+
`host` is a hostname or IP address, with the optional port supplied separately. `protocol` defaults to `http`; `https` validates the endpoint certificate through Node's standard transport. Plain HTTP requires explicit `allowUnencryptedLan: true`. Authentication uses the Shelly `admin` username and SHA-256 digest challenges. Credentials and response bodies are excluded from error messages. Request deadlines default to 10 seconds and accept values between 10 ms and 60 seconds.
|
|
25
38
|
|
|
26
39
|
`switch.turn_on` and `switch.turn_off` accept an exact mapped switch entity id. A mapped device id is sufficient when the device exposes one switch. Feature writes require the connected device id, a mapped writable `switch_N_output` feature, and a boolean value. Foreign targets, nonexistent components, ambiguous device targets and fabricated writable sensor features are rejected before a write.
|
|
27
40
|
|
|
41
|
+
Controls are denied unless `allowControl: true` is captured in configuration. Each absolute output write freshly validates identity, current methods and component state before sending one `Switch.Set`; it then revalidates identity and independently reads `Switch.GetStatus`. The vendor's `was_on` acknowledges prior state. Results carry `commandAcknowledged`, `causalityConfirmed: false`, requested/prior values and an observed-target or indeterminate outcome. Lost acknowledgement, failed post-dispatch readback or changed identity leaves an indeterminate result. Setter challenges, cancellation and cleanup never replay the effect. Independent readback does not prove relay position, exclusive causality or an atomic preflight.
|
|
42
|
+
|
|
28
43
|
Subscriptions share one native WebSocket connection to `/rpc`. The initial `Shelly.GetStatus` request supplies a unique source and authenticates both current and older Gen2 digest challenges. `NotifyStatus` patches merge into the validated baseline, including nested fields and null removals. `NotifyFullStatus` replaces the baseline. `NotifyEvent` carries vendor events, and configuration changes refresh mapped names. Disconnects and malformed or foreign frames emit safe errors and unavailable events; `isHealthy()` reflects the failed stream. A later subscription can establish a new connection.
|
|
29
44
|
|
|
30
|
-
The last unsubscribe closes the socket; an aborted listener removes only that listener. The setup context's abort signal cancels live requests and the push transport. Runtime and integration destruction cancel outstanding requests, remove subscribers, and await owned socket closure. If the endpoint does not complete the native WebSocket close handshake before the deadline, cleanup reports failure and retains ownership
|
|
45
|
+
The last unsubscribe closes the socket; an aborted listener removes only that listener. The setup context's abort signal cancels live requests and the push transport. Runtime and integration destruction cancel outstanding requests, remove subscribers, and await owned socket closure. If the endpoint does not complete the native WebSocket close handshake before the deadline, cleanup reports failure and retains ownership. Repeated `destroy()` keeps its original promise and failure; explicit integration, config-flow or client `retryCleanup()` retries cleanup only, without credential-provider or device-effect replay. Async subscriber continuations and all sibling cleanup settle before completion. Terminal objects cannot reopen.
|
|
31
46
|
|
|
32
|
-
Production configuration requires a real endpoint. The former `deviceInfo`, `status` and `deviceConfig` fixture fields have been removed; use a local fixture server in tests or call `ShellyMapper` directly with a snapshot. Tests under `test/shelly/` exercise local HTTP and WebSocket servers without real devices or credentials.
|
|
47
|
+
Production configuration requires a real endpoint. The former `deviceInfo`, `status` and `deviceConfig` fixture fields have been removed; use a local fixture server in tests or call `ShellyMapper` directly with a snapshot. Tests under `test/shelly/` exercise local HTTP and WebSocket servers without real devices or credentials. Qualified synthetic profiles cover Plus 1PM Gen2 `SNSW-001P16EU`, 1PM Gen3 `S3SW-001P16EU` and Plus 2PM `SNSW-002P16EU` with two switch components; this is not universal model certification.
|
|
33
48
|
|
|
34
49
|
Protocol references: [RPC channels](https://shelly-api-docs.shelly.cloud/gen2/General/RPCChannels/), [authentication](https://shelly-api-docs.shelly.cloud/gen2/General/Authentication/), [notifications](https://shelly-api-docs.shelly.cloud/gen2/General/Notifications/), [mDNS](https://shelly-api-docs.shelly.cloud/gen2/General/mDNS/), and [switch RPC](https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/Switch/).
|