@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
|
@@ -1,4 +1,41 @@
|
|
|
1
1
|
import type { IMqttConfig } from '../mqtt/mqtt.types.js';
|
|
2
|
+
import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
|
|
3
|
+
|
|
4
|
+
export interface ITasmotaBrokerIdentity {
|
|
5
|
+
readonly host: string;
|
|
6
|
+
readonly port: number;
|
|
7
|
+
readonly protocol: 'mqtt' | 'mqtts';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ITasmotaCredential extends ITasmotaBrokerIdentity {
|
|
11
|
+
readonly version: 1;
|
|
12
|
+
readonly username?: string;
|
|
13
|
+
readonly password?: string;
|
|
14
|
+
readonly ca?: string;
|
|
15
|
+
readonly cert?: string;
|
|
16
|
+
readonly key?: string;
|
|
17
|
+
readonly servername?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ITasmotaCredentialRequest {
|
|
21
|
+
readonly homeId: string;
|
|
22
|
+
readonly credentialRef: string;
|
|
23
|
+
readonly broker: ITasmotaBrokerIdentity;
|
|
24
|
+
readonly signal: AbortSignal;
|
|
25
|
+
/** Absolute deadline on performance.now(), not wall-clock time. */
|
|
26
|
+
readonly deadlineMonotonicMs: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TTasmotaCredentialResolver = (requestArg: ITasmotaCredentialRequest) => Promise<ITasmotaCredential>;
|
|
30
|
+
|
|
31
|
+
export interface ITasmotaIntegrationOptions extends INativeIntegrationOwnerOptions {
|
|
32
|
+
/** Runtime-only embedding-owner permission. Defaults to false. */
|
|
33
|
+
readonly enableControls?: boolean;
|
|
34
|
+
readonly credentialResolver?: TTasmotaCredentialResolver;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type TTasmotaAuthentication = { readonly mode: 'anonymous' }
|
|
38
|
+
| { readonly mode: 'credential-reference'; readonly credentialRef: string };
|
|
2
39
|
|
|
3
40
|
export interface ITasmotaDeviceConfig {
|
|
4
41
|
topic: string;
|
|
@@ -14,7 +51,9 @@ export interface ITasmotaDeviceConfig {
|
|
|
14
51
|
}
|
|
15
52
|
|
|
16
53
|
export interface IHomeAssistantTasmotaConfig {
|
|
17
|
-
|
|
54
|
+
readonly homeId: string;
|
|
55
|
+
broker: Omit<IMqttConfig, 'username' | 'password'> & { host: string };
|
|
56
|
+
readonly authentication: TTasmotaAuthentication;
|
|
18
57
|
devices: ITasmotaDeviceConfig[];
|
|
19
58
|
initialSnapshotTimeoutMs?: number;
|
|
20
59
|
}
|
|
@@ -34,7 +73,9 @@ export interface ITasmotaSelectedDevice {
|
|
|
34
73
|
}
|
|
35
74
|
|
|
36
75
|
export interface ITasmotaConfig {
|
|
37
|
-
|
|
76
|
+
readonly homeId: string;
|
|
77
|
+
readonly authentication: TTasmotaAuthentication;
|
|
78
|
+
broker: Omit<IMqttConfig, 'username' | 'password'> & { host: string };
|
|
38
79
|
devices: ITasmotaSelectedDevice[];
|
|
39
80
|
initialSnapshotTimeoutMs: number;
|
|
40
81
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Tuya local protocol 3.4
|
|
2
2
|
|
|
3
3
|
`TuyaIntegration` and the existing `HomeAssistantTuyaIntegration` export provide a
|
|
4
|
-
native local runtime through published `@ecobridge.xyz/devicemanager` 4.
|
|
4
|
+
native local runtime through published `@ecobridge.xyz/devicemanager` 4.11.1.
|
|
5
5
|
Setup requires an explicit home, device ID, literal private/link-local/loopback
|
|
6
6
|
IPv4 endpoint, local key and this product's verified datapoint schema. Discovery
|
|
7
7
|
has no broadcast probes and makes no claim to locate devices or obtain credentials.
|
|
@@ -33,11 +33,12 @@ const config: IHomeAssistantTuyaConfig = {
|
|
|
33
33
|
{ id: 108, name: 'Temperature', kind: 'sensor', valueType: 'integer', scale: 1, unit: '°C' },
|
|
34
34
|
],
|
|
35
35
|
};
|
|
36
|
-
const integration = new TuyaIntegration();
|
|
36
|
+
const integration = new TuyaIntegration({ homeId: 'home-one' });
|
|
37
|
+
const context = Object.freeze({ homeId: 'home-one', configStore: store });
|
|
37
38
|
// One explicit provisioning action stores configuration and the opaque 16-byte
|
|
38
39
|
// local key together, encrypted under the caller's key provider.
|
|
39
|
-
await integration.provisionLocalKey(config, configuredLocalKeyBytes,
|
|
40
|
-
const runtime = await integration.setup(config,
|
|
40
|
+
await integration.provisionLocalKey(config, configuredLocalKeyBytes, context);
|
|
41
|
+
const runtime = await integration.setup(config, context);
|
|
41
42
|
try {
|
|
42
43
|
const [device] = await runtime.devices();
|
|
43
44
|
const power = device.features.find((feature) => feature.id === 'dp_7');
|
|
@@ -52,6 +53,14 @@ try {
|
|
|
52
53
|
}
|
|
53
54
|
```
|
|
54
55
|
|
|
56
|
+
The host must independently select `context.homeId` for provisioning and setup.
|
|
57
|
+
It must match the configuration home and the actual store scope; it is never
|
|
58
|
+
inferred from either. An optional constructor `homeId` constrains the instance.
|
|
59
|
+
Otherwise, the first valid synchronous admission fixes its home even if subsequent
|
|
60
|
+
I/O fails. Configuration, context fields and local-key bytes are captured before
|
|
61
|
+
asynchronous work. Accessors and overridden store-scope methods cannot grant
|
|
62
|
+
admission. The host still owns authenticated access to the selected home.
|
|
63
|
+
|
|
55
64
|
The store must be an actual `NoSqlConfigStore` scoped to the exact configured home
|
|
56
65
|
and `tuya` domain. Generic stores are rejected. Configuration and credentials use
|
|
57
66
|
`device:<configured device ID>` in that encrypted scope; setup rejects a different
|
|
@@ -85,14 +94,38 @@ arrive. Failures retain `effectOutcome: 'not-sent'`, `'rejected'` or `'indetermi
|
|
|
85
94
|
a transmitted effect with a lost/late response may already have happened. There is
|
|
86
95
|
no retry, retransmission or reconnect. Lifetime/setup cancellation and feature-write
|
|
87
96
|
signals are supported. `destroy()` is permanent, idempotent and drains setup and
|
|
88
|
-
protocol operations
|
|
89
|
-
|
|
97
|
+
protocol operations and admitted asynchronous subscriber continuations. A fulfilled
|
|
98
|
+
provisioning write remains fulfilled if cancellation or close arrives during its
|
|
99
|
+
persistence; close joins the actual write result and never replays it. Setup and
|
|
100
|
+
provisioning are reserved before store or protocol callbacks run. Destruction
|
|
101
|
+
publishes one stable close promise before abort callbacks run. Caller cancellation
|
|
102
|
+
also closes promoted runtime ownership and releases its slot after cleanup.
|
|
103
|
+
Use a new integration for a new lifetime. Cached reads after
|
|
104
|
+
shutdown remain unavailable and read-only with their original report timestamps.
|
|
90
105
|
|
|
91
|
-
Subscriptions are bounded to 128 listeners
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
Subscriptions are bounded to 128 listeners and 128 outstanding callback
|
|
107
|
+
continuations, abortable and explicitly removable. Handlers must not block or
|
|
108
|
+
await destruction of their own runtime: destruction joins their continuation.
|
|
109
|
+
Returned promises and thenables are owned and joined even after listener removal.
|
|
110
|
+
Callback failures are retained through cleanup and logged with a generic,
|
|
111
|
+
credential-free message. Overflow closes the runtime and retains the failure.
|
|
94
112
|
All listeners are removed during destruction.
|
|
95
|
-
|
|
113
|
+
|
|
114
|
+
The shared native owner accounts for pending setup/provisioning operations,
|
|
115
|
+
promoted runtimes and retained failed cleanup. Its default limit is 16; the optional
|
|
116
|
+
constructor `capacity` selects a positive safe-integer limit. A failed cleanup
|
|
117
|
+
retains its capacity slot and rejects destruction. `integration.retryCleanup()`
|
|
118
|
+
retries only failed cleanup handles and explicitly acknowledges already-drained
|
|
119
|
+
subscriber failures; it never reruns callbacks, provisioning, queries or controls,
|
|
120
|
+
and never reopens a session. It does not change the original rejected close promise.
|
|
121
|
+
A resource whose owning cleanup still rejects remains retained.
|
|
122
|
+
|
|
123
|
+
Per-DP provisioned `read-write` access continues to grant control. There is no
|
|
124
|
+
additional host control-permission API and no automatic preflight or post-write
|
|
125
|
+
readback. Those public-contract changes require a separate product decision, as
|
|
126
|
+
does a config flow. Protocol authentication proves possession of the local key;
|
|
127
|
+
identity fields are checked when present, but this is not independent hardware
|
|
128
|
+
attestation. Firmware qualification remains separate from fixture verification.
|
|
96
129
|
|
|
97
130
|
Other protocol versions, cloud-only and sleeping devices, gateway children, BLE,
|
|
98
131
|
Zigbee, raw/bitmap/enum mappings and arbitrary actions are unsupported. The shared
|
|
@@ -101,6 +134,15 @@ device protocol union currently has no Tuya member, so the generic device report
|
|
|
101
134
|
loopback fixture tests cover the implementation; qualification against real product
|
|
102
135
|
firmware requires separately authorized hardware access.
|
|
103
136
|
|
|
137
|
+
Fixture qualification uses `pnpm exec tstest test/tuya/ --verbose --logfile --timeout 60`:
|
|
138
|
+
`test.tuya.node.ts` contains 28 encrypted persistence/protocol cases and
|
|
139
|
+
`test.tuya.owner.node.ts` adds 22 admission, reentry, cancellation, capacity,
|
|
140
|
+
accepted-persistence, retained-cleanup and callback-continuation regressions.
|
|
141
|
+
The 50-case suite passes with no device or cloud account. Strict domain/test checks
|
|
142
|
+
use `strict: true`, `noImplicitAny: true` and `skipLibCheck: false`; production
|
|
143
|
+
build and the complete test type check are also required. This fixture evidence
|
|
144
|
+
does not establish physical acceptance for a particular product or firmware.
|
|
145
|
+
|
|
104
146
|
Product types and permissions follow the vendor's [DP Model and Control Protocol](https://developer.tuya.com/en/docs/iot-device-dev/TuyaOS-iot_abi_dp_ctrl?id=Kcoglhn5r7ajr).
|
|
105
147
|
LAN framing is implemented by the owning dependency, whose public API and wire
|
|
106
148
|
contract are documented in its [readme](https://code.foss.global/ecobridge.xyz/devicemanager).
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { BaseIntegration } from '../../core/classes.baseintegration.js';
|
|
3
|
+
import { NativeIntegrationOwner } from '../../core/classes.nativeintegrationowner.js';
|
|
3
4
|
import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
|
|
4
5
|
import type { IIntegrationRuntime, IIntegrationSetupContext } from '../../core/types.js';
|
|
5
6
|
import { TuyaRuntime } from './tuya.classes.runtime.js';
|
|
6
|
-
import type { IHomeAssistantTuyaConfig, ITuyaStoredConfiguration } from './tuya.types.js';
|
|
7
|
-
import { normalizeTuyaConfig,
|
|
7
|
+
import type { IHomeAssistantTuyaConfig, ITuyaIntegrationOptions, ITuyaStoredConfiguration } from './tuya.types.js';
|
|
8
|
+
import { normalizeTuyaConfig, tuyaSetupContext, tuyaCredentialId, tuyaKeys, tuyaRecord } from './tuya.validation.js';
|
|
8
9
|
|
|
9
10
|
export class TuyaIntegration extends BaseIntegration<IHomeAssistantTuyaConfig> {
|
|
10
11
|
public readonly domain = 'tuya';
|
|
@@ -12,118 +13,88 @@ export class TuyaIntegration extends BaseIntegration<IHomeAssistantTuyaConfig> {
|
|
|
12
13
|
public readonly status = 'control-runtime' as const;
|
|
13
14
|
public readonly discoveryDescriptor = new DiscoveryDescriptor({ integrationDomain: 'tuya', displayName: 'Tuya 3.4 local device' });
|
|
14
15
|
public readonly metadata = Object.freeze({ source: 'native-typescript', integrationType: 'device', iotClass: 'local_push', protocolVersion: '3.4' });
|
|
15
|
-
readonly #
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
readonly #owner: NativeIntegrationOwner;
|
|
17
|
+
|
|
18
|
+
constructor(optionsArg: ITuyaIntegrationOptions = {}) {
|
|
19
|
+
super();
|
|
20
|
+
this.#owner = new NativeIntegrationOwner(this.domain, optionsArg);
|
|
21
|
+
}
|
|
20
22
|
|
|
21
23
|
public provisionLocalKey(configArg: IHomeAssistantTuyaConfig, localKeyArg: Uint8Array, contextArg: IIntegrationSetupContext): Promise<void> {
|
|
22
|
-
this.#assertOpen();
|
|
23
|
-
this.#assertCapacity();
|
|
24
24
|
const config = normalizeTuyaConfig(configArg);
|
|
25
|
-
const
|
|
25
|
+
const context = tuyaSetupContext(contextArg);
|
|
26
26
|
if (!(localKeyArg instanceof Uint8Array) || localKeyArg.byteLength !== 16) throw new TypeError('Tuya local key must contain exactly 16 opaque bytes.');
|
|
27
27
|
const copy = Buffer.from(localKeyArg);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
signal.throwIfAborted();
|
|
34
|
-
await store.initialize({ signal });
|
|
35
|
-
this.#assertOpen();
|
|
36
|
-
signal.throwIfAborted();
|
|
28
|
+
try {
|
|
29
|
+
return this.#owner.runOperation(context, config.homeId, async (leaseArg) => {
|
|
30
|
+
const store = leaseArg.context.configStore!;
|
|
31
|
+
await store.initialize({ signal: leaseArg.signal });
|
|
32
|
+
leaseArg.assertOpen();
|
|
37
33
|
await store.set<ITuyaStoredConfiguration>(tuyaCredentialId(config.deviceId), { version: 1, config, localKey: copy.toString('base64') });
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})();
|
|
42
|
-
return this.#track(operation);
|
|
34
|
+
// A fulfilled admitted write stays fulfilled even if close/cancellation arrives during persistence.
|
|
35
|
+
}).finally(() => copy.fill(0));
|
|
36
|
+
} catch (errorArg) { copy.fill(0); throw errorArg; }
|
|
43
37
|
}
|
|
44
38
|
|
|
45
39
|
public setup(configArg: IHomeAssistantTuyaConfig, contextArg: IIntegrationSetupContext): Promise<IIntegrationRuntime> {
|
|
46
|
-
this.#assertOpen();
|
|
47
|
-
this.#assertCapacity();
|
|
48
40
|
const config = normalizeTuyaConfig(configArg);
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
const context = tuyaSetupContext(contextArg);
|
|
42
|
+
return this.#owner.runSetup(context, config.homeId, async (leaseArg) => {
|
|
43
|
+
const store = leaseArg.context.configStore!;
|
|
44
|
+
const signal = leaseArg.signal;
|
|
45
|
+
await store.initialize({ signal });
|
|
46
|
+
leaseArg.assertOpen();
|
|
47
|
+
const stored = tuyaRecord(await store.get<unknown>(tuyaCredentialId(config.deviceId)), 'stored configuration');
|
|
48
|
+
tuyaKeys(stored, ['version', 'config', 'localKey']);
|
|
49
|
+
if (stored.version !== 1 || JSON.stringify(normalizeTuyaConfig(stored.config)) !== JSON.stringify(config)) {
|
|
50
|
+
throw new TypeError('Tuya configuration does not match its encrypted provisioned configuration.');
|
|
51
|
+
}
|
|
52
|
+
if (typeof stored.localKey !== 'string' || stored.localKey.length !== 24) throw new TypeError('Tuya stored local key is invalid.');
|
|
53
|
+
const key = Buffer.from(stored.localKey, 'base64');
|
|
54
|
+
let protocol: plugins.ecobridgeDeviceManager.TuyaProtocol;
|
|
55
|
+
let runtime: TuyaRuntime | undefined;
|
|
55
56
|
let unsubscribeAdmission: (() => void) | undefined;
|
|
57
|
+
let cleanupAttempted = false;
|
|
56
58
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw new TypeError('Tuya configuration does not match its encrypted provisioned configuration.');
|
|
64
|
-
}
|
|
65
|
-
if (typeof stored.localKey !== 'string' || stored.localKey.length !== 24) throw new TypeError('Tuya stored local key is invalid.');
|
|
66
|
-
const key = Buffer.from(stored.localKey, 'base64');
|
|
67
|
-
try {
|
|
68
|
-
if (key.byteLength !== 16 || key.toString('base64') !== stored.localKey) throw new TypeError('Tuya stored local key is invalid.');
|
|
69
|
-
signal.throwIfAborted();
|
|
70
|
-
protocol = new plugins.ecobridgeDeviceManager.TuyaProtocol({
|
|
71
|
-
deviceId: config.deviceId, localKey: key, host: config.host, port: config.port, version: config.version,
|
|
72
|
-
connectTimeoutMs: config.connectTimeoutMs, requestTimeoutMs: config.requestTimeoutMs,
|
|
73
|
-
heartbeatIntervalMs: config.heartbeatIntervalMs, abortSignal: signal,
|
|
74
|
-
});
|
|
75
|
-
} finally { key.fill(0); }
|
|
76
|
-
const reports: plugins.ecobridgeDeviceManager.ITuyaDatapointReport[] = [];
|
|
77
|
-
let admissionOverflow = false;
|
|
78
|
-
unsubscribeAdmission = protocol.subscribe((reportArg) => {
|
|
79
|
-
if (reports.length >= 128) { admissionOverflow = true; controller.abort(); }
|
|
80
|
-
else reports.push(reportArg);
|
|
59
|
+
if (key.byteLength !== 16 || key.toString('base64') !== stored.localKey) throw new TypeError('Tuya stored local key is invalid.');
|
|
60
|
+
leaseArg.assertOpen();
|
|
61
|
+
protocol = new plugins.ecobridgeDeviceManager.TuyaProtocol({
|
|
62
|
+
deviceId: config.deviceId, localKey: key, host: config.host, port: config.port, version: config.version,
|
|
63
|
+
connectTimeoutMs: config.connectTimeoutMs, requestTimeoutMs: config.requestTimeoutMs,
|
|
64
|
+
heartbeatIntervalMs: config.heartbeatIntervalMs, abortSignal: signal,
|
|
81
65
|
});
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.#destroyed = true;
|
|
109
|
-
for (const controller of this.#controllers) controller.abort();
|
|
110
|
-
this.#destroy = (async () => {
|
|
111
|
-
await Promise.allSettled([...this.#setups]);
|
|
112
|
-
await Promise.all([...this.#runtimes].map((runtimeArg) => runtimeArg.destroy()));
|
|
113
|
-
this.#controllers.clear();
|
|
114
|
-
})();
|
|
115
|
-
return this.#destroy;
|
|
66
|
+
// One composite handle transfers protocol and callback cleanup together to the runtime.
|
|
67
|
+
leaseArg.own(async () => {
|
|
68
|
+
const retry = cleanupAttempted;
|
|
69
|
+
cleanupAttempted = true;
|
|
70
|
+
unsubscribeAdmission?.();
|
|
71
|
+
unsubscribeAdmission = undefined;
|
|
72
|
+
if (runtime) await runtime.closeOwnedResources(retry);
|
|
73
|
+
else await protocol.close();
|
|
74
|
+
});
|
|
75
|
+
} finally { key.fill(0); }
|
|
76
|
+
const reports: plugins.ecobridgeDeviceManager.ITuyaDatapointReport[] = [];
|
|
77
|
+
let admissionOverflow = false;
|
|
78
|
+
unsubscribeAdmission = protocol.subscribe((reportArg) => {
|
|
79
|
+
if (reports.length >= 128) { admissionOverflow = true; void leaseArg.close().catch(() => undefined); }
|
|
80
|
+
else reports.push(reportArg);
|
|
81
|
+
});
|
|
82
|
+
await protocol.connect(signal);
|
|
83
|
+
await protocol.queryDatapoints(signal);
|
|
84
|
+
if (admissionOverflow) throw new Error('Tuya admission report capacity exceeded.');
|
|
85
|
+
leaseArg.assertOpen();
|
|
86
|
+
unsubscribeAdmission();
|
|
87
|
+
unsubscribeAdmission = undefined;
|
|
88
|
+
runtime = new TuyaRuntime(config, protocol, reports, leaseArg);
|
|
89
|
+
leaseArg.promoteToRuntime();
|
|
90
|
+
return runtime;
|
|
91
|
+
});
|
|
116
92
|
}
|
|
117
93
|
|
|
118
|
-
|
|
94
|
+
public destroy(): Promise<void> { return this.#owner.close(); }
|
|
119
95
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
#track<TResult>(operationArg: Promise<TResult>): Promise<TResult> {
|
|
123
|
-
this.#setups.add(operationArg);
|
|
124
|
-
void operationArg.then(() => this.#setups.delete(operationArg), () => this.#setups.delete(operationArg));
|
|
125
|
-
return operationArg;
|
|
126
|
-
}
|
|
96
|
+
/** Retry retained cleanup only; never reopen a session or replay an accepted operation. */
|
|
97
|
+
public retryCleanup(): Promise<void> { return this.#owner.retryCleanup(); }
|
|
127
98
|
}
|
|
128
99
|
|
|
129
100
|
export class HomeAssistantTuyaIntegration extends TuyaIntegration {}
|
|
@@ -1,47 +1,57 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { INativeSetupLease } from '../../core/classes.nativeintegrationowner.js';
|
|
3
|
+
import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IIntegrationEvent, IIntegrationRuntime, IServiceCallRequest, IServiceCallResult } from '../../core/types.js';
|
|
3
4
|
import type { IHomeAssistantTuyaConfig, TTuyaDatapointMapping } from './tuya.types.js';
|
|
4
5
|
import { tuyaDeviceId, tuyaDevices, tuyaEntities, tuyaEntityId, tuyaFeatureId, type ITuyaObservedDatapoint } from './tuya.mapper.js';
|
|
5
6
|
import { tuyaKeys, tuyaRecord, tuyaValueMatches } from './tuya.validation.js';
|
|
6
7
|
|
|
8
|
+
interface ITuyaCleanupResource {
|
|
9
|
+
readonly cleanup: () => Promise<void>;
|
|
10
|
+
promise?: Promise<void>;
|
|
11
|
+
failed: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
export class TuyaRuntime implements IIntegrationRuntime {
|
|
8
15
|
public readonly domain = 'tuya';
|
|
9
16
|
readonly #config: IHomeAssistantTuyaConfig;
|
|
10
17
|
readonly #protocol: plugins.ecobridgeDeviceManager.TuyaProtocol;
|
|
11
|
-
readonly #
|
|
12
|
-
readonly #logger?: ILogger;
|
|
18
|
+
readonly #lease: INativeSetupLease;
|
|
13
19
|
readonly #observed = new Map<number, ITuyaObservedDatapoint>();
|
|
14
20
|
readonly #subscriptions = new Set<(eventArg: IIntegrationEvent) => void>();
|
|
15
21
|
readonly #subscriptionCleanup = new Set<() => void>();
|
|
16
|
-
readonly #
|
|
17
|
-
readonly #
|
|
22
|
+
readonly #resources: ITuyaCleanupResource[] = [];
|
|
23
|
+
readonly #callbacks = new Set<Promise<void>>();
|
|
24
|
+
#subscriberFailure = false;
|
|
18
25
|
#closed = false;
|
|
19
26
|
#destroy?: Promise<void>;
|
|
20
27
|
|
|
21
28
|
constructor(configArg: IHomeAssistantTuyaConfig, protocolArg: plugins.ecobridgeDeviceManager.TuyaProtocol,
|
|
22
|
-
reportsArg: readonly plugins.ecobridgeDeviceManager.ITuyaDatapointReport[],
|
|
29
|
+
reportsArg: readonly plugins.ecobridgeDeviceManager.ITuyaDatapointReport[], leaseArg: INativeSetupLease) {
|
|
23
30
|
this.#config = configArg;
|
|
24
31
|
this.#protocol = protocolArg;
|
|
25
|
-
this.#
|
|
26
|
-
this.#
|
|
32
|
+
this.#lease = leaseArg;
|
|
33
|
+
this.#resources.push({ cleanup: () => this.#protocol.close(), failed: false });
|
|
27
34
|
for (const report of reportsArg) this.#accept(report);
|
|
28
35
|
if (this.#observed.size === 0) throw new Error('Tuya device reported none of the configured product datapoints.');
|
|
29
|
-
|
|
36
|
+
const unsubscribe = this.#protocol.subscribe((report) => {
|
|
37
|
+
if (this.#lease.signal.aborted || this.#destroy) return;
|
|
30
38
|
try { this.#accept(report); }
|
|
31
39
|
catch {
|
|
32
40
|
this.#closed = true;
|
|
33
41
|
this.#emit({ type: 'error', data: { code: 'schema-mismatch', message: 'Tuya reported data no longer matches the configured product schema.' } });
|
|
34
42
|
this.#emit({ type: 'availability_changed', data: { available: false } });
|
|
35
|
-
void this.destroy();
|
|
43
|
+
void this.destroy().catch(() => undefined);
|
|
36
44
|
}
|
|
37
45
|
});
|
|
38
|
-
this.#
|
|
46
|
+
this.#resources.push({ cleanup: async () => unsubscribe(), failed: false });
|
|
47
|
+
const unsubscribeErrors = this.#protocol.subscribeErrors((error) => {
|
|
39
48
|
this.#emit({ type: 'error', data: { code: error.code, effectOutcome: error.effectOutcome } });
|
|
40
49
|
if (!this.#protocol.connected) this.#emit({ type: 'availability_changed', data: { available: false } });
|
|
41
50
|
});
|
|
51
|
+
this.#resources.push({ cleanup: async () => unsubscribeErrors(), failed: false });
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
public isHealthy(): boolean { return !this.#closed && this.#protocol.connected; }
|
|
54
|
+
public isHealthy(): boolean { return !this.#closed && !this.#lease.signal.aborted && this.#protocol.connected; }
|
|
45
55
|
|
|
46
56
|
public async devices(signalArg?: AbortSignal): Promise<plugins.shxInterfaces.data.IDeviceDefinition[]> {
|
|
47
57
|
signalArg?.throwIfAborted();
|
|
@@ -53,6 +63,7 @@ export class TuyaRuntime implements IIntegrationRuntime {
|
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
public async subscribe(handlerArg: (eventArg: IIntegrationEvent) => void, signalArg?: AbortSignal): Promise<() => Promise<void>> {
|
|
66
|
+
this.#lease.assertOpen();
|
|
56
67
|
if (this.#closed) throw new Error('Tuya runtime is permanently closed.');
|
|
57
68
|
if (typeof handlerArg !== 'function') throw new TypeError('Tuya subscription handler must be a function.');
|
|
58
69
|
signalArg?.throwIfAborted();
|
|
@@ -106,16 +117,37 @@ export class TuyaRuntime implements IIntegrationRuntime {
|
|
|
106
117
|
} catch (errorArg) { return this.#failure(errorArg); }
|
|
107
118
|
}
|
|
108
119
|
|
|
109
|
-
public destroy(): Promise<void> {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.#
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
public destroy(): Promise<void> { return this.#lease.close(); }
|
|
121
|
+
|
|
122
|
+
/** Invoked by the retained owner handle, including explicit cleanup-only retry. */
|
|
123
|
+
public closeOwnedResources(retryArg = false): Promise<void> {
|
|
124
|
+
if (retryArg) return this.#drain(true);
|
|
125
|
+
if (!this.#destroy) {
|
|
126
|
+
this.#closed = true;
|
|
127
|
+
this.#destroy = Promise.resolve().then(() => this.#drain(false));
|
|
128
|
+
}
|
|
116
129
|
return this.#destroy;
|
|
117
130
|
}
|
|
118
131
|
|
|
132
|
+
async #drain(retryArg: boolean): Promise<void> {
|
|
133
|
+
for (const cleanup of [...this.#subscriptionCleanup]) cleanup();
|
|
134
|
+
// The first close already joined every admitted callback. Explicit retry acknowledges its
|
|
135
|
+
// retained failure; subscriber code is never invoked again as part of cleanup.
|
|
136
|
+
if (retryArg) this.#subscriberFailure = false;
|
|
137
|
+
const cleanup = this.#resources.map((resourceArg) => {
|
|
138
|
+
if (retryArg && resourceArg.failed) { resourceArg.promise = undefined; resourceArg.failed = false; }
|
|
139
|
+
resourceArg.promise ??= Promise.resolve().then(resourceArg.cleanup).catch(() => {
|
|
140
|
+
resourceArg.failed = true;
|
|
141
|
+
throw new Error('Tuya owned resource cleanup failed.');
|
|
142
|
+
});
|
|
143
|
+
return resourceArg.promise;
|
|
144
|
+
});
|
|
145
|
+
const [results] = await Promise.all([Promise.allSettled(cleanup), Promise.allSettled([...this.#callbacks])]);
|
|
146
|
+
const failures = results.filter((resultArg): resultArg is PromiseRejectedResult => resultArg.status === 'rejected').map((resultArg) => resultArg.reason as unknown);
|
|
147
|
+
if (this.#subscriberFailure) failures.push(new Error('Tuya event subscriber failed.'));
|
|
148
|
+
if (failures.length) throw new AggregateError(failures, 'Tuya cleanup failed; ownership remains retained for explicit retry.');
|
|
149
|
+
}
|
|
150
|
+
|
|
119
151
|
#accept(reportArg: plugins.ecobridgeDeviceManager.ITuyaDatapointReport): void {
|
|
120
152
|
const changes: ITuyaObservedDatapoint[] = [];
|
|
121
153
|
for (const mapping of this.#config.datapoints) {
|
|
@@ -134,6 +166,7 @@ export class TuyaRuntime implements IIntegrationRuntime {
|
|
|
134
166
|
}
|
|
135
167
|
|
|
136
168
|
async #write(mappingArg: TTuyaDatapointMapping, valueArg: unknown, signalArg?: AbortSignal): Promise<IServiceCallResult> {
|
|
169
|
+
this.#lease.assertOpen();
|
|
137
170
|
if (!this.isHealthy()) throw new Error('Tuya session is unavailable; no automatic reconnect or retry is performed.');
|
|
138
171
|
if (mappingArg.kind === 'sensor' || mappingArg.access !== 'read-write') throw new Error('Tuya datapoint is read-only.');
|
|
139
172
|
if (!tuyaValueMatches(mappingArg, valueArg)) throw new TypeError('Tuya write value does not match its configured type/range/step.');
|
|
@@ -151,16 +184,29 @@ export class TuyaRuntime implements IIntegrationRuntime {
|
|
|
151
184
|
|
|
152
185
|
#emit(eventArg: Omit<IIntegrationEvent, 'integrationDomain' | 'deviceId' | 'timestamp'>): void {
|
|
153
186
|
for (const handler of [...this.#subscriptions]) {
|
|
154
|
-
if (this.#destroy) return;
|
|
187
|
+
if (this.#destroy || this.#lease.signal.aborted) return;
|
|
188
|
+
if (this.#callbacks.size >= 128) {
|
|
189
|
+
this.#subscriberFailed();
|
|
190
|
+
void this.destroy().catch(() => undefined);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Reserve a continuation before entering foreign code, including synchronous close reentry.
|
|
194
|
+
let finish!: () => void;
|
|
195
|
+
const continuation = new Promise<void>((resolveArg) => { finish = resolveArg; });
|
|
196
|
+
this.#callbacks.add(continuation);
|
|
197
|
+
const settled = (): void => { this.#callbacks.delete(continuation); finish(); };
|
|
155
198
|
try {
|
|
156
199
|
const result: unknown = handler({ ...structuredClone(eventArg), integrationDomain: 'tuya', deviceId: tuyaDeviceId(this.#config), timestamp: Date.now() });
|
|
157
|
-
if (result
|
|
158
|
-
|
|
200
|
+
if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
|
|
201
|
+
void Promise.resolve(result).then(settled, () => { this.#subscriberFailed(); settled(); });
|
|
202
|
+
} else settled();
|
|
203
|
+
} catch { this.#subscriberFailed(); settled(); }
|
|
159
204
|
}
|
|
160
205
|
}
|
|
161
206
|
|
|
162
207
|
#subscriberFailed(): void {
|
|
163
|
-
|
|
164
|
-
this.#logger?.log('error', 'Tuya event subscriber failed.', { code: 'subscriber-error' });
|
|
208
|
+
this.#subscriberFailure = true;
|
|
209
|
+
try { this.#lease.context.logger?.log('error', 'Tuya event subscriber failed.', { code: 'subscriber-error' }); }
|
|
210
|
+
catch { /* Logging cannot detach or replace a retained subscriber failure. */ }
|
|
165
211
|
}
|
|
166
212
|
}
|
|
@@ -25,7 +25,7 @@ export const tuyaDevices = (configArg: IHomeAssistantTuyaConfig, observedArg: re
|
|
|
25
25
|
protocol: 'unknown', manufacturer: 'Tuya', online: availableArg,
|
|
26
26
|
features: observedArg.map(({ mapping }) => ({
|
|
27
27
|
id: tuyaFeatureId(mapping), name: mapping.name, readable: true,
|
|
28
|
-
writable: mapping.kind !== 'sensor' && mapping.access === 'read-write',
|
|
28
|
+
writable: availableArg && mapping.kind !== 'sensor' && mapping.access === 'read-write',
|
|
29
29
|
capability: mapping.kind === 'switch' ? 'switch' : mapping.kind === 'brightness' ? 'light' : 'sensor',
|
|
30
30
|
...(mapping.kind === 'sensor' && mapping.unit !== undefined ? { unit: mapping.unit } : {}),
|
|
31
31
|
})),
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
|
|
2
|
+
|
|
3
|
+
export interface ITuyaIntegrationOptions extends INativeIntegrationOwnerOptions {}
|
|
4
|
+
|
|
1
5
|
export type TTuyaDatapointAccess = 'read' | 'read-write';
|
|
2
6
|
|
|
3
7
|
export interface ITuyaDatapointIdentity {
|
|
@@ -97,15 +97,12 @@ export const normalizeTuyaConfig = (valueArg: unknown): IHomeAssistantTuyaConfig
|
|
|
97
97
|
});
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
/** Capture data fields before admission; the shared owner verifies home and actual store scope. */
|
|
101
|
+
export const tuyaSetupContext = (contextArg: IIntegrationSetupContext): IIntegrationSetupContext => {
|
|
101
102
|
const context = tuyaRecord(contextArg, 'setup context');
|
|
102
|
-
tuyaKeys(context, ['configStore', 'abortSignal', 'logger']);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const scope = store.getScope();
|
|
106
|
-
if (scope.homeId !== configArg.homeId || scope.integrationDomain !== 'tuya') throw new TypeError('Tuya encrypted store must belong to the exact configured home and integration.');
|
|
107
|
-
if (context.abortSignal !== undefined && !(context.abortSignal instanceof AbortSignal)) throw new TypeError('Tuya setup cancellation must be an AbortSignal.');
|
|
108
|
-
return store;
|
|
103
|
+
tuyaKeys(context, ['homeId', 'configStore', 'abortSignal', 'logger']);
|
|
104
|
+
if (!(context.configStore instanceof NoSqlConfigStore)) throw new TypeError('Tuya requires an encrypted NoSqlConfigStore and caller-owned encryption key provider.');
|
|
105
|
+
return Object.freeze(context) as IIntegrationSetupContext;
|
|
109
106
|
};
|
|
110
107
|
|
|
111
108
|
export const tuyaCredentialId = (deviceIdArg: string): string => `device:${deviceIdArg}`;
|