@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,118 @@
|
|
|
1
|
+
import type { INativeOperationLease } from '../../core/classes.nativeintegrationowner.js';
|
|
2
|
+
import { MqttClient } from './mqtt.classes.client.js';
|
|
3
|
+
import type { IMqttConfig, IMqttTransportCredentials, TMqttCredentialResolver } from './mqtt.types.js';
|
|
4
|
+
import { mqttCredentials } from './mqtt.validation.js';
|
|
5
|
+
|
|
6
|
+
/** Owns actual caller credential work and the resulting transport, without owning the provider. */
|
|
7
|
+
export class MqttSession {
|
|
8
|
+
readonly #config: IMqttConfig & { homeId: string };
|
|
9
|
+
readonly #lease: INativeOperationLease;
|
|
10
|
+
readonly #resolver?: TMqttCredentialResolver;
|
|
11
|
+
readonly #lifetime = new AbortController();
|
|
12
|
+
readonly #signal: AbortSignal;
|
|
13
|
+
#provider?: Promise<IMqttTransportCredentials>;
|
|
14
|
+
#work?: Promise<MqttClient>;
|
|
15
|
+
#client?: MqttClient;
|
|
16
|
+
#closed = false;
|
|
17
|
+
#closePromise?: Promise<void>;
|
|
18
|
+
#retryPromise?: Promise<void>;
|
|
19
|
+
#providerDrained = false;
|
|
20
|
+
#transportClosed = false;
|
|
21
|
+
#transportCloseAttempted = false;
|
|
22
|
+
|
|
23
|
+
constructor(configArg: IMqttConfig & { homeId: string }, leaseArg: INativeOperationLease, resolverArg?: TMqttCredentialResolver) {
|
|
24
|
+
this.#config = configArg; this.#lease = leaseArg; this.#resolver = resolverArg;
|
|
25
|
+
this.#signal = AbortSignal.any([leaseArg.signal, this.#lifetime.signal]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public start(): Promise<MqttClient> {
|
|
29
|
+
if (!this.#work) this.#work = Promise.resolve().then(() => this.#start());
|
|
30
|
+
return this.#work;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public close(): Promise<void> {
|
|
34
|
+
if (!this.#closePromise) {
|
|
35
|
+
this.#closed = true;
|
|
36
|
+
this.#closePromise = Promise.resolve().then(() => this.#drain());
|
|
37
|
+
this.#lifetime.abort(new Error('MQTT session is closed.'));
|
|
38
|
+
}
|
|
39
|
+
return this.#closePromise;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public retryCleanup(): Promise<void> {
|
|
43
|
+
if (!this.#closed) return Promise.reject(new Error('MQTT cleanup can only be retried after close.'));
|
|
44
|
+
if (!this.#retryPromise) {
|
|
45
|
+
const retry = Promise.resolve().then(() => this.#drain());
|
|
46
|
+
this.#retryPromise = retry;
|
|
47
|
+
void retry.then(() => { this.#retryPromise = undefined; }, () => { this.#retryPromise = undefined; });
|
|
48
|
+
}
|
|
49
|
+
return this.#retryPromise;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#assertOpen(): void {
|
|
53
|
+
this.#lease.assertOpen();
|
|
54
|
+
this.#signal.throwIfAborted();
|
|
55
|
+
if (this.#closed) throw new Error('MQTT session is closed.');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async #start(): Promise<MqttClient> {
|
|
59
|
+
this.#assertOpen();
|
|
60
|
+
const deadline = performance.now() + (this.#config.connectTimeoutMs ?? 7000);
|
|
61
|
+
let credentials: IMqttTransportCredentials = {};
|
|
62
|
+
if (this.#config.credentialKey) {
|
|
63
|
+
if (!this.#resolver) throw new Error('MQTT credential reference requires a caller resolver.');
|
|
64
|
+
const resolver = this.#resolver;
|
|
65
|
+
const request = Object.freeze({ homeId: this.#lease.context.homeId, credentialKey: this.#config.credentialKey,
|
|
66
|
+
signal: this.#signal, deadline });
|
|
67
|
+
// The microtask enters foreign code only after its actual promise is retained here.
|
|
68
|
+
this.#provider = Promise.resolve().then(() => {
|
|
69
|
+
this.#assertOpen();
|
|
70
|
+
return resolver(request);
|
|
71
|
+
}).then((value) => mqttCredentials(value), () => { throw new Error('MQTT caller credential resolution failed.'); });
|
|
72
|
+
const provider = this.#provider;
|
|
73
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
74
|
+
let abort: () => void = () => {};
|
|
75
|
+
const cancellation = new Promise<never>((_resolve, reject) => {
|
|
76
|
+
abort = () => reject(this.#signal.reason as unknown);
|
|
77
|
+
timer = setTimeout(() => {
|
|
78
|
+
const error = new Error('MQTT credential resolution timed out.');
|
|
79
|
+
reject(error);
|
|
80
|
+
this.#lifetime.abort(error);
|
|
81
|
+
}, Math.max(1, deadline - performance.now()));
|
|
82
|
+
this.#signal.addEventListener('abort', abort, { once: true });
|
|
83
|
+
if (this.#signal.aborted) abort();
|
|
84
|
+
});
|
|
85
|
+
try { credentials = await Promise.race([provider, cancellation]); }
|
|
86
|
+
finally { if (timer) clearTimeout(timer); this.#signal.removeEventListener('abort', abort); }
|
|
87
|
+
}
|
|
88
|
+
this.#assertOpen();
|
|
89
|
+
const remaining = Math.floor(deadline - performance.now());
|
|
90
|
+
if (remaining < 1) throw new Error('MQTT setup timed out.');
|
|
91
|
+
const client = new MqttClient({ ...this.#config, connectTimeoutMs: remaining }, credentials);
|
|
92
|
+
this.#client = client;
|
|
93
|
+
// The session is already the lease's owned cleanup before this allocation and start.
|
|
94
|
+
try { await client.start(this.#signal); }
|
|
95
|
+
catch (error) { if (this.#signal.aborted) throw this.#signal.reason as unknown; throw error; }
|
|
96
|
+
this.#assertOpen();
|
|
97
|
+
return client;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async #drain(): Promise<void> {
|
|
101
|
+
const provider = this.#provider;
|
|
102
|
+
const client = this.#client;
|
|
103
|
+
const results = await Promise.allSettled([
|
|
104
|
+
...(provider && !this.#providerDrained ? [provider.then(() => { this.#providerDrained = true; }, () => {
|
|
105
|
+
this.#providerDrained = true;
|
|
106
|
+
throw new Error('MQTT caller credential work failed while draining.');
|
|
107
|
+
})] : []),
|
|
108
|
+
...(client && !this.#transportClosed ? [Promise.resolve().then(() => {
|
|
109
|
+
const retry = this.#transportCloseAttempted;
|
|
110
|
+
this.#transportCloseAttempted = true;
|
|
111
|
+
return retry ? client.retryCleanup() : client.destroy();
|
|
112
|
+
}).then(() => { this.#transportClosed = true; })] : []),
|
|
113
|
+
...(this.#work ? [this.#work.then(() => undefined, () => undefined)] : []),
|
|
114
|
+
]);
|
|
115
|
+
const failures = results.flatMap((result) => result.status === 'rejected' ? [result.reason as unknown] : []);
|
|
116
|
+
if (failures.length) throw new AggregateError(failures, 'MQTT session cleanup failed; retry cleanup explicitly.');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { IDeviceFeatureWriteRequest, IIntegrationEntity, IServiceCallRequest, TEntityPlatform } from '../../core/types.js';
|
|
3
|
-
import type { IMqttConfig, IMqttDeviceInfo, IMqttEntityConfig, IMqttMessage, IMqttPayload, IMqttPublishOptions, IMqttSnapshot } from './mqtt.types.js';
|
|
3
|
+
import type { IMqttConfig, IMqttDeviceInfo, IMqttEntityConfig, IMqttMessage, IMqttPayload, IMqttPublishOptions, IMqttSnapshot, IMqttTopicMapping } from './mqtt.types.js';
|
|
4
4
|
|
|
5
5
|
const defaultDiscoveryPrefix = 'homeassistant';
|
|
6
6
|
const defaultPayloadOn = 'ON';
|
|
@@ -68,6 +68,8 @@ export class MqttMapper {
|
|
|
68
68
|
...(configArg.discovery === false ? [] : this.entitiesFromDiscoveryMessages(messages, configArg.discoveryPrefix || defaultDiscoveryPrefix)),
|
|
69
69
|
];
|
|
70
70
|
return {
|
|
71
|
+
homeId: configArg.homeId,
|
|
72
|
+
selectedMappings: configArg.mode === 'selected-topics' ? structuredClone(configArg.mappings ?? []) : undefined,
|
|
71
73
|
brokerInfo: {
|
|
72
74
|
host: configArg.host,
|
|
73
75
|
port: configArg.port || (configArg.protocol === 'mqtts' ? 8883 : 1883),
|
|
@@ -82,6 +84,7 @@ export class MqttMapper {
|
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
public static toDevices(snapshotArg: IMqttSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
|
|
87
|
+
if (snapshotArg.selectedMappings) return this.selectedDevices(snapshotArg);
|
|
85
88
|
const updatedAt = new Date().toISOString();
|
|
86
89
|
const devices = new Map<string, plugins.shxInterfaces.data.IDeviceDefinition>();
|
|
87
90
|
const brokerId = this.brokerDeviceId(snapshotArg);
|
|
@@ -145,6 +148,13 @@ export class MqttMapper {
|
|
|
145
148
|
}
|
|
146
149
|
|
|
147
150
|
public static toEntities(snapshotArg: IMqttSnapshot): IIntegrationEntity[] {
|
|
151
|
+
if (snapshotArg.selectedMappings) return snapshotArg.selectedMappings.map((mappingArg) => ({
|
|
152
|
+
id: mappingArg.entityId, uniqueId: mappingArg.entityId, integrationDomain: 'mqtt', deviceId: mappingArg.deviceId,
|
|
153
|
+
platform: 'sensor', name: mappingArg.name, state: this.selectedValue(snapshotArg, mappingArg.stateTopic),
|
|
154
|
+
attributes: { homeId: snapshotArg.homeId, stateTopic: mappingArg.stateTopic, availabilityTopic: mappingArg.availabilityTopic,
|
|
155
|
+
observationFormat: 'raw-utf8', identitySource: 'owner-mapping' },
|
|
156
|
+
available: this.selectedAvailable(snapshotArg, mappingArg),
|
|
157
|
+
}));
|
|
148
158
|
return snapshotArg.entities.map((entityArg) => ({
|
|
149
159
|
id: entityArg.defaultEntityId || `${this.toEntityPlatform(entityArg.platform)}.${this.slug(entityArg.uniqueId || entityArg.objectId || entityArg.name || 'mqtt')}`,
|
|
150
160
|
uniqueId: this.uniqueId(entityArg),
|
|
@@ -168,6 +178,7 @@ export class MqttMapper {
|
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
public static commandForService(snapshotArg: IMqttSnapshot, requestArg: IServiceCallRequest): IMqttPublishOptions | undefined {
|
|
181
|
+
if (snapshotArg.selectedMappings) return undefined;
|
|
171
182
|
const entity = this.findTargetEntity(snapshotArg, requestArg);
|
|
172
183
|
if (entity && requestArg.target.deviceId && this.deviceIdForEntity(entity, snapshotArg) !== requestArg.target.deviceId) return undefined;
|
|
173
184
|
if (!entity?.commandTopic || requestArg.domain !== entity.platform || !this.controlSupported(entity) || !this.entityAvailable(entity, snapshotArg)) {
|
|
@@ -209,6 +220,7 @@ export class MqttMapper {
|
|
|
209
220
|
}
|
|
210
221
|
|
|
211
222
|
public static commandForFeature(snapshotArg: IMqttSnapshot, requestArg: IDeviceFeatureWriteRequest): IMqttPublishOptions | undefined {
|
|
223
|
+
if (snapshotArg.selectedMappings) return undefined;
|
|
212
224
|
const entities = snapshotArg.entities.filter((entityArg) =>
|
|
213
225
|
this.deviceIdForEntity(entityArg, snapshotArg) === requestArg.device.id &&
|
|
214
226
|
this.featureForEntity(entityArg).id === requestArg.feature.id
|
|
@@ -584,6 +596,36 @@ export class MqttMapper {
|
|
|
584
596
|
return Array.isArray(valueArg) ? JSON.stringify(valueArg) : valueArg as plugins.shxInterfaces.data.TDeviceStateValue;
|
|
585
597
|
}
|
|
586
598
|
|
|
599
|
+
private static selectedValue(snapshotArg: IMqttSnapshot, topicArg: string): string | null {
|
|
600
|
+
const value = snapshotArg.messages.find((messageArg) => messageArg.topic === topicArg)?.payload;
|
|
601
|
+
return typeof value === 'string' ? value : null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
private static selectedAvailable(snapshotArg: IMqttSnapshot, mappingArg: IMqttTopicMapping): boolean {
|
|
605
|
+
return snapshotArg.brokerInfo.connected && this.selectedValue(snapshotArg, mappingArg.stateTopic) !== null
|
|
606
|
+
&& (!mappingArg.availabilityTopic
|
|
607
|
+
|| this.selectedValue(snapshotArg, mappingArg.availabilityTopic) === (mappingArg.payloadAvailable ?? 'online'));
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private static selectedDevices(snapshotArg: IMqttSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
|
|
611
|
+
const devices = new Map<string, plugins.shxInterfaces.data.IDeviceDefinition>();
|
|
612
|
+
for (const mapping of snapshotArg.selectedMappings ?? []) {
|
|
613
|
+
let device = devices.get(mapping.deviceId);
|
|
614
|
+
if (!device) {
|
|
615
|
+
device = { id: mapping.deviceId, name: mapping.deviceName, integrationDomain: 'mqtt', protocol: 'mqtt',
|
|
616
|
+
online: false, features: [], state: [], metadata: { homeId: snapshotArg.homeId, identitySource: 'owner-mapping',
|
|
617
|
+
brokerHost: snapshotArg.brokerInfo.host, physicalIdentityConfirmed: false } };
|
|
618
|
+
devices.set(mapping.deviceId, device);
|
|
619
|
+
}
|
|
620
|
+
device.online ||= this.selectedAvailable(snapshotArg, mapping);
|
|
621
|
+
device.features.push({ id: mapping.featureId, name: mapping.name, capability: 'sensor', readable: true, writable: false });
|
|
622
|
+
const message = snapshotArg.messages.find((itemArg) => itemArg.topic === mapping.stateTopic);
|
|
623
|
+
device.state.push({ featureId: mapping.featureId, value: this.selectedValue(snapshotArg, mapping.stateTopic),
|
|
624
|
+
updatedAt: new Date(message?.timestamp ?? Date.now()).toISOString() });
|
|
625
|
+
}
|
|
626
|
+
return [...devices.values()];
|
|
627
|
+
}
|
|
628
|
+
|
|
587
629
|
private static stringValue(valueArg: unknown): string | undefined {
|
|
588
630
|
return typeof valueArg === 'string' && valueArg ? valueArg : undefined;
|
|
589
631
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { TEntityPlatform } from '../../core/types.js';
|
|
2
|
+
import type { INativeIntegrationOwnerOptions } from '../../core/classes.nativeintegrationowner.js';
|
|
2
3
|
|
|
3
4
|
export interface IMqttConfig {
|
|
5
|
+
/** Required by MqttIntegration; raw MqttClient remains a reusable transport. */
|
|
6
|
+
homeId?: string;
|
|
7
|
+
mode?: 'discovery' | 'selected-topics';
|
|
8
|
+
credentialKey?: string;
|
|
9
|
+
allowUnencryptedLan?: boolean;
|
|
10
|
+
mappings?: IMqttTopicMapping[];
|
|
4
11
|
host?: string;
|
|
5
12
|
port?: number;
|
|
6
13
|
protocol?: 'mqtt' | 'mqtts';
|
|
@@ -26,6 +33,49 @@ export interface IMqttConfig {
|
|
|
26
33
|
maxBufferedBytes?: number;
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
/** Owner-defined identity and exact topics; payloads are opaque UTF-8 observations. */
|
|
37
|
+
export interface IMqttTopicMapping {
|
|
38
|
+
deviceId: string;
|
|
39
|
+
entityId: string;
|
|
40
|
+
featureId: string;
|
|
41
|
+
name: string;
|
|
42
|
+
deviceName: string;
|
|
43
|
+
stateTopic: string;
|
|
44
|
+
availabilityTopic?: string;
|
|
45
|
+
payloadAvailable?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface IMqttSelectedConfig extends IMqttConfig {
|
|
49
|
+
homeId: string;
|
|
50
|
+
mode: 'selected-topics';
|
|
51
|
+
mappings: IMqttTopicMapping[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Runtime-only subset of the documented MQTT.js TCP/TLS options. No file paths. */
|
|
55
|
+
export interface IMqttTransportCredentials {
|
|
56
|
+
username?: string;
|
|
57
|
+
password?: string;
|
|
58
|
+
ca?: string;
|
|
59
|
+
cert?: string;
|
|
60
|
+
key?: string;
|
|
61
|
+
servername?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface IMqttCredentialRequest {
|
|
65
|
+
readonly homeId: string;
|
|
66
|
+
readonly credentialKey: string;
|
|
67
|
+
readonly signal: AbortSignal;
|
|
68
|
+
/** Absolute performance.now() deadline. */
|
|
69
|
+
readonly deadline: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The caller owns retrieval, storage and cancellation of this borrowed provider. */
|
|
73
|
+
export type TMqttCredentialResolver = (requestArg: IMqttCredentialRequest) => Promise<IMqttTransportCredentials>;
|
|
74
|
+
|
|
75
|
+
export interface IMqttIntegrationOptions extends INativeIntegrationOwnerOptions {
|
|
76
|
+
readonly resolveCredentials?: TMqttCredentialResolver;
|
|
77
|
+
}
|
|
78
|
+
|
|
29
79
|
export interface IMqttConnectionState {
|
|
30
80
|
connected: boolean;
|
|
31
81
|
error?: Error;
|
|
@@ -61,6 +111,8 @@ export interface IMqttSnapshot {
|
|
|
61
111
|
brokerInfo: IMqttBrokerInfo;
|
|
62
112
|
entities: IMqttEntityConfig[];
|
|
63
113
|
messages: IMqttMessage[];
|
|
114
|
+
homeId?: string;
|
|
115
|
+
selectedMappings?: IMqttTopicMapping[];
|
|
64
116
|
}
|
|
65
117
|
|
|
66
118
|
export interface IMqttDeviceInfo {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { validateMqttTopic } from './mqtt.classes.client.js';
|
|
2
|
+
import type { IMqttConfig, IMqttTopicMapping, IMqttTransportCredentials } from './mqtt.types.js';
|
|
3
|
+
|
|
4
|
+
export const mqttRecord = (valueArg: unknown, allowedArg: readonly string[]): Record<string, unknown> => {
|
|
5
|
+
if (!valueArg || typeof valueArg !== 'object'
|
|
6
|
+
|| (Object.getPrototypeOf(valueArg) !== Object.prototype && Object.getPrototypeOf(valueArg) !== null)) {
|
|
7
|
+
throw new TypeError('MQTT configuration must be a plain data object.');
|
|
8
|
+
}
|
|
9
|
+
const result: Record<string, unknown> = {};
|
|
10
|
+
for (const key of Reflect.ownKeys(valueArg)) {
|
|
11
|
+
const descriptor = Object.getOwnPropertyDescriptor(valueArg, key);
|
|
12
|
+
if (typeof key !== 'string' || !allowedArg.includes(key) || !descriptor?.enumerable || !('value' in descriptor)) {
|
|
13
|
+
throw new TypeError('MQTT configuration contains an unsupported field or accessor.');
|
|
14
|
+
}
|
|
15
|
+
result[key] = descriptor.value as unknown;
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const mqttText = (valueArg: unknown, maximumArg = 256): string => {
|
|
21
|
+
if (typeof valueArg !== 'string' || !valueArg || valueArg.trim() !== valueArg
|
|
22
|
+
|| valueArg.length > maximumArg || /[\p{Cc}\p{Cs}]/u.test(valueArg)) {
|
|
23
|
+
throw new TypeError('MQTT identifier must be a bounded non-empty string without whitespace or control characters.');
|
|
24
|
+
}
|
|
25
|
+
return valueArg;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const integer = (valueArg: unknown, maximumArg: number, minimumArg = 1): number | undefined => {
|
|
29
|
+
if (valueArg === undefined) return undefined;
|
|
30
|
+
if (typeof valueArg !== 'number' || !Number.isSafeInteger(valueArg) || valueArg < minimumArg || valueArg > maximumArg) {
|
|
31
|
+
throw new TypeError('MQTT numeric configuration is out of range.');
|
|
32
|
+
}
|
|
33
|
+
return valueArg;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const mapping = (valueArg: unknown): IMqttTopicMapping => {
|
|
37
|
+
const value = mqttRecord(valueArg, ['deviceId', 'entityId', 'featureId', 'name', 'deviceName', 'stateTopic', 'availabilityTopic', 'payloadAvailable']);
|
|
38
|
+
const stateTopic = mqttText(value.stateTopic, 65535);
|
|
39
|
+
const availabilityTopic = value.availabilityTopic === undefined ? undefined : mqttText(value.availabilityTopic, 65535);
|
|
40
|
+
validateMqttTopic(stateTopic);
|
|
41
|
+
if (availabilityTopic) validateMqttTopic(availabilityTopic);
|
|
42
|
+
if (value.payloadAvailable !== undefined && (typeof value.payloadAvailable !== 'string' || Buffer.byteLength(value.payloadAvailable) > 4096)) {
|
|
43
|
+
throw new TypeError('MQTT availability payload must be a bounded UTF-8 string.');
|
|
44
|
+
}
|
|
45
|
+
const payloadAvailable = typeof value.payloadAvailable === 'string' ? value.payloadAvailable : undefined;
|
|
46
|
+
if (payloadAvailable !== undefined && availabilityTopic === undefined) throw new TypeError('MQTT availability payload requires its exact topic.');
|
|
47
|
+
return Object.freeze({ deviceId: mqttText(value.deviceId), entityId: mqttText(value.entityId), featureId: mqttText(value.featureId),
|
|
48
|
+
name: mqttText(value.name), deviceName: mqttText(value.deviceName), stateTopic, availabilityTopic, payloadAvailable });
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Capture data without invoking nested accessors before host admission. */
|
|
52
|
+
const mqttData = (valueArg: unknown): unknown => {
|
|
53
|
+
const seen = new Set<object>();
|
|
54
|
+
let entries = 0;
|
|
55
|
+
const copy = (value: unknown, depth: number): unknown => {
|
|
56
|
+
if (++entries > 200000 || depth > 32) throw new TypeError('MQTT configuration data exceeds its bounds.');
|
|
57
|
+
if (value === null || value === undefined || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
58
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
59
|
+
if (typeof value !== 'object' || seen.has(value)) throw new TypeError('MQTT configuration requires finite acyclic data.');
|
|
60
|
+
seen.add(value);
|
|
61
|
+
try {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
if (Reflect.ownKeys(value).length !== value.length + 1) throw new TypeError('MQTT configuration arrays require exact data entries.');
|
|
64
|
+
const result: unknown[] = [];
|
|
65
|
+
for (let index = 0; index < value.length; index++) {
|
|
66
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
67
|
+
if (!descriptor?.enumerable || !('value' in descriptor)) throw new TypeError('MQTT configuration arrays cannot contain accessors or holes.');
|
|
68
|
+
result.push(copy(descriptor.value as unknown, depth + 1));
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
73
|
+
throw new TypeError('MQTT configuration requires plain data objects.');
|
|
74
|
+
}
|
|
75
|
+
const result: Record<string, unknown> = Object.create(null) as Record<string, unknown>;
|
|
76
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
77
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
78
|
+
if (typeof key !== 'string' || !descriptor?.enumerable || !('value' in descriptor)) throw new TypeError('MQTT configuration cannot contain accessors.');
|
|
79
|
+
result[key] = copy(descriptor.value as unknown, depth + 1);
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
} finally { seen.delete(value); }
|
|
83
|
+
};
|
|
84
|
+
return copy(valueArg, 0);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const normalizeMqttIntegrationConfig = (valueArg: unknown): IMqttConfig & { homeId: string } => {
|
|
88
|
+
const fields = mqttRecord(valueArg, ['homeId', 'mode', 'host', 'port', 'protocol', 'credentialKey', 'allowUnencryptedLan', 'mappings',
|
|
89
|
+
'clientId', 'keepalive', 'clean', 'discovery', 'discoveryPrefix', 'birthMessage', 'willMessage', 'subscriptions',
|
|
90
|
+
'discoveryMessages', 'retainedMessages', 'entities', 'connectTimeoutMs', 'operationTimeoutMs', 'reconnectPeriodMs',
|
|
91
|
+
'maxMessageTopics', 'maxPayloadBytes', 'maxBufferedBytes']);
|
|
92
|
+
const value = mqttData(fields) as Record<string, unknown>;
|
|
93
|
+
const homeId = mqttText(value.homeId);
|
|
94
|
+
if (value.mode !== undefined && value.mode !== 'discovery' && value.mode !== 'selected-topics') throw new TypeError('Unsupported MQTT mapping mode.');
|
|
95
|
+
if (value.protocol !== undefined && value.protocol !== 'mqtt' && value.protocol !== 'mqtts') throw new TypeError('Unsupported MQTT transport.');
|
|
96
|
+
const protocol = value.protocol === 'mqtts' ? 'mqtts' : 'mqtt';
|
|
97
|
+
const host = value.host === undefined ? undefined : mqttText(value.host, 253);
|
|
98
|
+
if (host && (/[\s/@?#\\:]/u.test(host) || host === '.')) throw new TypeError('MQTT host must be a selected hostname or IPv4 address, without URL or credentials.');
|
|
99
|
+
if (value.allowUnencryptedLan !== undefined && typeof value.allowUnencryptedLan !== 'boolean') throw new TypeError('MQTT LAN consent must be explicit.');
|
|
100
|
+
if (host && protocol === 'mqtt' && value.allowUnencryptedLan !== true) throw new TypeError('Unencrypted MQTT requires explicit LAN consent.');
|
|
101
|
+
const credentialKey = value.credentialKey === undefined ? undefined : mqttText(value.credentialKey);
|
|
102
|
+
const port = integer(value.port, 65535) ?? (protocol === 'mqtts' ? 8883 : 1883);
|
|
103
|
+
integer(value.connectTimeoutMs, 2147483647);
|
|
104
|
+
integer(value.operationTimeoutMs, 2147483647);
|
|
105
|
+
integer(value.reconnectPeriodMs, 2147483647, 0);
|
|
106
|
+
integer(value.keepalive, 65535, 0);
|
|
107
|
+
const topicLimit = integer(value.maxMessageTopics, 100000) ?? 4096;
|
|
108
|
+
integer(value.maxPayloadBytes, 16777216);
|
|
109
|
+
integer(value.maxBufferedBytes, 1073741824);
|
|
110
|
+
if (value.clean !== undefined && typeof value.clean !== 'boolean') throw new TypeError('MQTT clean session must be a boolean.');
|
|
111
|
+
if (value.discovery !== undefined && typeof value.discovery !== 'boolean') throw new TypeError('MQTT discovery must be a boolean.');
|
|
112
|
+
if (value.clientId !== undefined) mqttText(value.clientId);
|
|
113
|
+
if (value.clean === false && value.clientId === undefined) throw new TypeError('Persistent MQTT sessions require an explicit client ID.');
|
|
114
|
+
if (value.mode === 'selected-topics') {
|
|
115
|
+
if (!host || !Array.isArray(value.mappings) || !value.mappings.length || value.mappings.length > topicLimit) {
|
|
116
|
+
throw new TypeError('Selected MQTT requires a broker and bounded explicit mappings.');
|
|
117
|
+
}
|
|
118
|
+
if (value.discovery === true || value.birthMessage !== undefined && value.birthMessage !== false
|
|
119
|
+
|| value.willMessage !== undefined && value.willMessage !== false || value.clean === false
|
|
120
|
+
|| value.reconnectPeriodMs !== undefined && value.reconnectPeriodMs !== 0
|
|
121
|
+
|| ['subscriptions', 'discoveryMessages', 'retainedMessages', 'entities', 'discoveryPrefix'].some((key) => value[key] !== undefined)) {
|
|
122
|
+
throw new TypeError('Selected MQTT mappings cannot enable discovery, broker effects, fixtures or persistent sessions.');
|
|
123
|
+
}
|
|
124
|
+
const mappings = value.mappings.map((itemArg: unknown) => mapping(itemArg));
|
|
125
|
+
const entities = new Set<string>();
|
|
126
|
+
const features = new Set<string>();
|
|
127
|
+
const devices = new Map<string, string>();
|
|
128
|
+
for (const item of mappings) {
|
|
129
|
+
const feature = JSON.stringify([item.deviceId, item.featureId]);
|
|
130
|
+
if (entities.has(item.entityId) || features.has(feature) || devices.has(item.deviceId) && devices.get(item.deviceId) !== item.deviceName) {
|
|
131
|
+
throw new TypeError('Selected MQTT mappings require unique entities/features and consistent device names.');
|
|
132
|
+
}
|
|
133
|
+
entities.add(item.entityId); features.add(feature); devices.set(item.deviceId, item.deviceName);
|
|
134
|
+
}
|
|
135
|
+
const subscriptions = [...new Set(mappings.flatMap((item) => [item.stateTopic, ...(item.availabilityTopic ? [item.availabilityTopic] : [])]))];
|
|
136
|
+
if (subscriptions.length > topicLimit) throw new TypeError('Selected MQTT topic limit exceeded.');
|
|
137
|
+
return Object.freeze({ ...structuredClone(value) as IMqttConfig, homeId, host, port, protocol, credentialKey,
|
|
138
|
+
mode: 'selected-topics', mappings, discovery: false, birthMessage: false, willMessage: false,
|
|
139
|
+
clean: true, reconnectPeriodMs: 0 });
|
|
140
|
+
}
|
|
141
|
+
if (value.mappings !== undefined) throw new TypeError('MQTT mappings require selected-topics mode.');
|
|
142
|
+
return Object.freeze({ ...structuredClone(value) as IMqttConfig, homeId, host, port, protocol, credentialKey, mode: 'discovery' });
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const mqttCredentials = (valueArg: unknown): IMqttTransportCredentials => {
|
|
146
|
+
const value = mqttRecord(valueArg, ['username', 'password', 'ca', 'cert', 'key', 'servername']);
|
|
147
|
+
const result: IMqttTransportCredentials = {};
|
|
148
|
+
for (const key of ['username', 'password', 'ca', 'cert', 'key', 'servername'] as const) {
|
|
149
|
+
const item = value[key];
|
|
150
|
+
if (item === undefined) continue;
|
|
151
|
+
if (typeof item !== 'string' || item.length > 1048576 || item.includes('\u0000')) throw new TypeError('MQTT caller credentials are invalid.');
|
|
152
|
+
result[key] = item;
|
|
153
|
+
}
|
|
154
|
+
if ((result.cert === undefined) !== (result.key === undefined)) throw new TypeError('MQTT client certificate and key must be supplied together.');
|
|
155
|
+
if (result.servername !== undefined) mqttText(result.servername, 253);
|
|
156
|
+
return result;
|
|
157
|
+
};
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
`MqttIntegration` connects directly to a broker over MQTT 3.1.1 TCP or TLS through MQTT.js. No Home Assistant server or access token is involved. Setup completes after broker connection and owned subscription acknowledgements. MQTT has no end marker for retained discovery replay; newly received discovery produces normalized events and later snapshots.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Construct `MqttIntegration` with an optional host-home consistency binding. A selected `credentialKey` requires an explicit constructor `resolveCredentials` callback; a connection without credentials needs neither resolver nor store. Setup and config-flow start require an independently selected `context.homeId`; configuration `homeId` must match it. Configuration accepts endpoint, `credentialKey`, mapping and transport limits, with an explicit `allowUnencryptedLan: true` acknowledgment for plain MQTT. MQTTS is the flow default. Persist credential references rather than usernames, passwords, private CA or client key material. The borrowed resolver receives immutable `{ homeId, credentialKey, signal, deadline }`, with an absolute `performance.now()` deadline, and returns runtime-only `{ username?, password?, ca?, cert?, key?, servername? }` strings. Certificate and key must be supplied together. The caller resolver can retrieve an encrypted credential record through its own actual `NoSqlConfigStore` public API. Any store supplied in native context must match the admitted home and MQTT domain. The integration does not read credentials from context storage automatically or substitute it for a missing resolver. TLS verifies the certificate and hostname. No secret files are read, and provider, database and store remain caller-owned.
|
|
6
|
+
|
|
7
|
+
Construction is inert. Setup, config-flow work, live runtimes and failed cleanup share the native owner capacity, default 16. Terminal destruction publishes a stable promise before abort and joins actual provider callbacks even after caller cancellation or deadline. A provider that never settles keeps cleanup pending; a late unrelated rejection remains observable through sanitized cleanup. Explicit `retryCleanup()` retries cleanup only, without resolving credentials, reconnecting, subscribing or publishing again.
|
|
6
8
|
|
|
7
9
|
Discovery defaults to `homeassistant/#`. Both single-component and device discovery messages are supported, including abbreviations and topic bases. The latest configuration for each discovery topic replaces its predecessor; an empty payload removes it. State, availability, and attribute topics are subscribed dynamically and removed when no longer referenced. Availability lists support `all`, `any`, and `latest`. A disconnected broker marks its devices unavailable immediately.
|
|
8
10
|
|
|
@@ -12,17 +14,24 @@ The adapter reads supported canonical platforms. Writes support default-schema p
|
|
|
12
14
|
|
|
13
15
|
Snapshots retain one latest message per topic, bounded by `maxMessageTopics` (4096), `maxPayloadBytes` (262144), and `maxBufferedBytes` (8388608). A limit violation closes the owned connection. These settings bound application snapshots, not broker retention or the protocol library's frame parser.
|
|
14
16
|
|
|
17
|
+
The reusable low-level `MqttClient` preserves its first-argument username/password configuration API. Its optional second argument accepts runtime-only `IMqttTransportCredentials` for CA/mTLS use; the integration configuration migration above does not remove that raw-client API.
|
|
18
|
+
|
|
15
19
|
`MqttClient` is reusable by native integrations:
|
|
16
20
|
|
|
17
21
|
- `start(signal?)`, `publish(options, signal?)`, and `subscribe(filter, handler?, signal?)` are bounded operations.
|
|
18
22
|
- `subscribe` callbacks receive only matching topics. Its returned asynchronous cleanup sends UNSUBSCRIBE after the final owner releases the filter.
|
|
19
23
|
- `connected`, `isHealthy()`, and `onConnectionState(handler)` expose subscription readiness and connection loss.
|
|
20
24
|
- `onMessage(handler)` observes messages on owned subscriptions without subscribing to the entire broker.
|
|
21
|
-
- `getSnapshot(signal?)`
|
|
22
|
-
- `
|
|
25
|
+
- `getSnapshot(signal?)` preserves historical discovery and state metadata.
|
|
26
|
+
- Synchronous `getCurrentSnapshot()` clones only observations from the current live broker connection. Register state/message handlers before seeding readiness from it; fixtures and earlier connections are excluded, while current retained packets received before SUBACK are included. Eligibility clears before disconnect/error callbacks.
|
|
27
|
+
- `destroy()` closes resources and keeps its terminal promise, including its original failure. Explicit `retryCleanup()` retries retained transport cleanup without replaying connection setup, subscriptions or publishes.
|
|
23
28
|
|
|
24
29
|
Abort or timeout closes the physical connection that owns the operation before rejection; an explicit subsequent `start()` can establish a fresh connection. A stale operation cannot close a later connection generation. Subscriber cleanup is independent of transport destruction.
|
|
25
30
|
|
|
26
31
|
For an ecosystem that owns its own MQTT topics, set `discovery: false`, `birthMessage: false`, and `willMessage: false`, then subscribe only its selected topics. Fixture snapshots remain supported through `entities`, `discoveryMessages`, and `retainedMessages`; they do not claim a healthy broker connection.
|
|
27
32
|
|
|
33
|
+
Selected-topic mode uses `mode: 'selected-topics'` and explicit non-empty mappings with owner-selected device, entity and feature IDs, names and one exact `stateTopic`; optional exact `availabilityTopic` uses `payloadAvailable`, default `online`. Payloads remain raw UTF-8 strings and all mapped features are read-only sensors. Only the mapping topic union is subscribed. Wildcards, caller subscription arrays, discovery, birth/will, fixture snapshots, persistent sessions, reconnect and generic publish are refused in this mode. Unselected messages are ignored before caching. Disconnect immediately marks observations unavailable and requires a newly admitted setup. Mapping IDs and broker observations do not attest physical device identity.
|
|
34
|
+
|
|
35
|
+
Discovery mode remains the default and preserves the existing discovery/control surface. Connection loss during an admitted publish closes that owning connection generation before MQTT.js can replay an unacknowledged effect. Subscription-only reconnection remains supported; old-generation cancellation and cleanup cannot close a successor. No broker acknowledgment proves physical execution or application-level deduplication.
|
|
36
|
+
|
|
28
37
|
References: [MQTT.js 5.16.0](https://github.com/mqttjs/MQTT.js/tree/v5.16.0), [MQTT discovery](https://www.home-assistant.io/integrations/mqtt/), and [MQTT 3.1.1](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html).
|