diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 86d82e2c3..43e931135 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -573,64 +573,6 @@ const cfg = { }, }, - // Thermostat/HVAC - 'thermostat': (minTemp=7, maxTemp=30, temperatureStateProperty='occupied_heating_setpoint', tempStep=1) => { - return { - type: 'climate', - object_id: 'climate', - discovery_payload: { - state_topic: false, - min_temp: `${minTemp}`, - max_temp: `${maxTemp}`, - modes: ['off', 'auto', 'heat'], - mode_state_topic: true, - mode_state_template: '{{ value_json.system_mode }}', - mode_command_topic: true, - current_temperature_topic: true, - current_temperature_template: '{{ value_json.local_temperature }}', - temperature_state_topic: true, - temperature_state_template: `{{ value_json.${temperatureStateProperty} }}`, - temperature_command_topic: temperatureStateProperty, - temp_step: tempStep, - action_topic: true, - action_template: '{{ value_json.operation }}', - }, - }; - }, - 'thermostat_heatcool': /* istanbul ignore next */ (minTemp=7, maxTemp=30, tempStep=0.5, fanModes=[]) => { - return { - type: 'climate', - object_id: 'climate', - discovery_payload: { - state_topic: false, - unit_of_measurement: '°C', - min_temp: `${minTemp}`, - max_temp: `${maxTemp}`, - modes: ['off', 'auto', 'heat', 'cool'], - mode_state_topic: true, - mode_state_template: '{{ value_json.system_mode }}', - mode_command_topic: true, - current_temperature_topic: true, - current_temperature_template: '{{ value_json.local_temperature }}', - temperature_low_state_topic: true, - temperature_low_state_template: `{{ value_json.occupied_heating_setpoint }}`, - temperature_high_state_topic: true, - temperature_high_state_template: `{{ value_json.occupied_cooling_setpoint }}`, - temperature_low_command_topic: 'occupied_heating_setpoint', - temperature_high_command_topic: 'occupied_cooling_setpoint', - temp_step: tempStep, - action_topic: true, - action_template: - '{% set values = {\'idle\':\'off\',\'heat\':\'heating\',\'cool\':\'cooling\',\'fan only\':\'fan\'}'+ - ' %}{{ values[value_json.running_state] }}', - fan_modes: fanModes, - fan_mode_command_topic: true, - fan_mode_state_topic: true, - fan_mode_state_template: '{{ value_json.fan_mode }}', - }, - }; - }, - // Fan 'fan': { type: 'fan', @@ -664,29 +606,87 @@ const cfg = { }, }; -const switchWithPostfix = (postfix) => { +const switchEndpoint = (endpointName) => { return { type: 'switch', - object_id: `switch_${postfix}`, + object_id: `switch_${endpointName}`, discovery_payload: { payload_off: 'OFF', payload_on: 'ON', - value_template: `{{ value_json.state_${postfix} }}`, + value_template: `{{ value_json.state_${endpointName} }}`, command_topic: true, - command_topic_prefix: postfix, + command_topic_prefix: endpointName, }, }; }; -const lightWithPostfix = (configType, postfix) => { +const lightEndpoint = (configType, endpointName) => { const config = objectAssignDeep.noMutate(cfg[configType]); - config['object_id'] = `light_${postfix}`; - config['discovery_payload']['command_topic_prefix'] = postfix; - config['discovery_payload']['state_topic_postfix'] = postfix; + config['object_id'] = `light_${endpointName}`; + config['discovery_payload']['command_topic_prefix'] = endpointName; + config['discovery_payload']['state_topic_postfix'] = endpointName; return config; }; -// Map homeassitant configurations to devices. +const thermostat = (minTemp=7, maxTemp=30, temperatureStateProperty='occupied_heating_setpoint', tempStep=1) => { + return { + type: 'climate', + object_id: 'climate', + discovery_payload: { + state_topic: false, + min_temp: `${minTemp}`, + max_temp: `${maxTemp}`, + modes: ['off', 'auto', 'heat'], + mode_state_topic: true, + mode_state_template: '{{ value_json.system_mode }}', + mode_command_topic: true, + current_temperature_topic: true, + current_temperature_template: '{{ value_json.local_temperature }}', + temperature_state_topic: true, + temperature_state_template: `{{ value_json.${temperatureStateProperty} }}`, + temperature_command_topic: temperatureStateProperty, + temp_step: tempStep, + action_topic: true, + action_template: '{{ value_json.operation }}', + }, + }; +}; + +const thermostatHeatCool = /* istanbul ignore next */ (minTemp=7, maxTemp=30, tempStep=0.5, fanModes=[]) => { + return { + type: 'climate', + object_id: 'climate', + discovery_payload: { + state_topic: false, + unit_of_measurement: '°C', + min_temp: `${minTemp}`, + max_temp: `${maxTemp}`, + modes: ['off', 'auto', 'heat', 'cool'], + mode_state_topic: true, + mode_state_template: '{{ value_json.system_mode }}', + mode_command_topic: true, + current_temperature_topic: true, + current_temperature_template: '{{ value_json.local_temperature }}', + temperature_low_state_topic: true, + temperature_low_state_template: `{{ value_json.occupied_heating_setpoint }}`, + temperature_high_state_topic: true, + temperature_high_state_template: `{{ value_json.occupied_cooling_setpoint }}`, + temperature_low_command_topic: 'occupied_heating_setpoint', + temperature_high_command_topic: 'occupied_cooling_setpoint', + temp_step: tempStep, + action_topic: true, + action_template: + '{% set values = {\'idle\':\'off\',\'heat\':\'heating\',\'cool\':\'cooling\',\'fan only\':\'fan\'}'+ + ' %}{{ values[value_json.running_state] }}', + fan_modes: fanModes, + fan_mode_command_topic: true, + fan_mode_state_topic: true, + fan_mode_state_template: '{{ value_json.fan_mode }}', + }, + }; +}; + +// Map Home Assistant configurations to devices. const mapping = { 'WXKG01LM': [cfg.sensor_click, cfg.sensor_battery], 'WXKG11LM': [cfg.sensor_click, cfg.sensor_battery, cfg.sensor_action], @@ -695,7 +695,7 @@ const mapping = { 'WXKG03LM': [cfg.sensor_click, cfg.sensor_battery, cfg.sensor_action], 'WXKG02LM': [cfg.sensor_click, cfg.sensor_battery], 'QBKG04LM': [cfg.switch, cfg.sensor_click, cfg.sensor_action], - 'QBKG03LM': [switchWithPostfix('left'), switchWithPostfix('right'), cfg.sensor_click, cfg.sensor_temperature], + 'QBKG03LM': [switchEndpoint('left'), switchEndpoint('right'), cfg.sensor_click, cfg.sensor_temperature], 'WSDCGQ01LM': [cfg.sensor_temperature, cfg.sensor_humidity, cfg.sensor_battery], 'WSDCGQ11LM': [cfg.sensor_temperature, cfg.sensor_humidity, cfg.sensor_pressure, cfg.sensor_battery], 'RTCGQ01LM': [cfg.binary_sensor_occupancy, cfg.sensor_battery], @@ -770,7 +770,7 @@ const mapping = { 'AA68199': [cfg.light_brightness_colortemp], 'QBKG11LM': [cfg.switch, cfg.sensor_power, cfg.sensor_click], 'QBKG12LM': [ - switchWithPostfix('left'), switchWithPostfix('right'), cfg.sensor_power, cfg.sensor_click, + switchEndpoint('left'), switchEndpoint('right'), cfg.sensor_power, cfg.sensor_click, cfg.sensor_temperature, ], 'K2RGBW01': [cfg.light_brightness_colortemp_colorxy], @@ -804,11 +804,11 @@ const mapping = { 'AC03642': [cfg.light_brightness_colortemp], 'AC08560': [cfg.light_brightness], 'AC10786-DIM': [cfg.light_brightness], - 'DNCKATSW002': [switchWithPostfix('left'), switchWithPostfix('right')], - 'DNCKATSW003': [switchWithPostfix('left'), switchWithPostfix('right'), switchWithPostfix('center')], + 'DNCKATSW002': [switchEndpoint('left'), switchEndpoint('right')], + 'DNCKATSW003': [switchEndpoint('left'), switchEndpoint('right'), switchEndpoint('center')], 'DNCKATSW004': [ - switchWithPostfix('bottom_left'), switchWithPostfix('bottom_right'), - switchWithPostfix('top_left'), switchWithPostfix('top_right'), + switchEndpoint('bottom_left'), switchEndpoint('bottom_right'), + switchEndpoint('top_left'), switchEndpoint('top_right'), ], 'BY 165': [cfg.light_brightness], 'ZLED-2709': [cfg.light_brightness], @@ -844,7 +844,7 @@ const mapping = { '53170161': [cfg.light_brightness_colortemp], '4058075036147': [cfg.light_brightness_colortemp_colorxy], 'KS-SM001': [cfg.switch], - 'MG-AUWS01': [switchWithPostfix('left'), switchWithPostfix('right')], + 'MG-AUWS01': [switchEndpoint('left'), switchEndpoint('right')], '9290002579A': [cfg.light_brightness_colortemp_colorxy], '4256251-RZHAC': [cfg.switch, cfg.sensor_power], '4257050-ZHAC': [cfg.light_brightness, cfg.sensor_power, cfg.sensor_current, cfg.sensor_voltage], @@ -893,7 +893,7 @@ const mapping = { 'GL-B-008Z': [cfg.light_brightness_colortemp_colorxy], 'AV2010/25': [cfg.switch, cfg.sensor_power], 'E12-N14': [cfg.light_brightness], - '1TST-EU': [cfg.thermostat(), cfg.sensor_battery], + '1TST-EU': [thermostat(), cfg.sensor_battery], 'RB 178 T': [cfg.light_brightness_colortemp], '45856GE': [cfg.switch], 'GL-D-003Z': [cfg.light_brightness_colortemp_colorxy], @@ -904,7 +904,7 @@ const mapping = { 'D1531': [cfg.light_brightness], 'D1532': [cfg.light_brightness], 'D1533': [cfg.light_brightness], - 'AV2010/32': [cfg.thermostat(7, 30, 'occupied_heating_setpoint', 0.5), cfg.sensor_battery], + 'AV2010/32': [thermostat(7, 30, 'occupied_heating_setpoint', 0.5), cfg.sensor_battery], 'HGZB-07A': [cfg.light_brightness_colortemp_colorxy], 'E1524/E1810': [cfg.sensor_action, cfg.sensor_battery], 'GL-C-006': [cfg.light_brightness_colortemp], @@ -928,8 +928,8 @@ const mapping = { '81809': [cfg.light_brightness_colortemp_colorxy], '4090130P7': [cfg.light_brightness_colortemp_colorxy], '100.110.39': [cfg.light_brightness_colortemp_colorxy], - 'TI0001': [switchWithPostfix('left'), switchWithPostfix('right')], - 'SPZB0001': [cfg.thermostat(5, 30, 'current_heating_setpoint', 0.5), cfg.sensor_battery], + 'TI0001': [switchEndpoint('left'), switchEndpoint('right')], + 'SPZB0001': [thermostat(5, 30, 'current_heating_setpoint', 0.5), cfg.sensor_battery], 'HS3CG': [cfg.binary_sensor_gas], '81825': [cfg.sensor_action], 'Z809AF': [cfg.switch, cfg.sensor_power], @@ -950,8 +950,8 @@ const mapping = { cfg.binary_sensor_occupancy, cfg.sensor_temperature, cfg.sensor_illuminance, cfg.sensor_illuminance_lux, cfg.sensor_battery, ], - 'HGZB-042': [switchWithPostfix('top'), switchWithPostfix('bottom')], - 'HGZB-42': [switchWithPostfix('top'), switchWithPostfix('bottom')], + 'HGZB-042': [switchEndpoint('top'), switchEndpoint('bottom')], + 'HGZB-42': [switchEndpoint('top'), switchEndpoint('bottom')], 'GL-FL-004TZ': [cfg.light_brightness_colortemp_colorxy], 'IM6001-OTP05': [cfg.switch], 'SV01': [ @@ -976,7 +976,7 @@ const mapping = { ], '9290018195': [cfg.light_brightness], 'HGZB-04D / HGZB-4D-UK': [cfg.light_brightness], - 'HGZB-043': [switchWithPostfix('top'), switchWithPostfix('bottom'), switchWithPostfix('center')], + 'HGZB-043': [switchEndpoint('top'), switchEndpoint('bottom'), switchEndpoint('center')], 'NCZ-3043-HA': [ cfg.binary_sensor_occupancy, cfg.sensor_temperature, cfg.sensor_battery, cfg.binary_sensor_battery_low, @@ -1013,7 +1013,7 @@ const mapping = { ], 'HGZB-1S': [cfg.switch, cfg.sensor_click], 'HGZB-045': [cfg.switch, cfg.sensor_click], - 'HGZB-43': [switchWithPostfix('top'), switchWithPostfix('bottom'), switchWithPostfix('center')], + 'HGZB-43': [switchEndpoint('top'), switchEndpoint('bottom'), switchEndpoint('center')], 'HGZB-01A': [cfg.switch], 'HGZB-02A': [cfg.light_brightness], 'MCT-350 SMA': [cfg.binary_sensor_contact], @@ -1041,17 +1041,17 @@ const mapping = { 'RS 122': [cfg.light_brightness], 'GL-B-001Z': [cfg.light_brightness_colortemp_colorxy], 'IM6001-MTP01': [cfg.sensor_temperature, cfg.sensor_battery, cfg.binary_sensor_occupancy], - 'U86K31ND6': [switchWithPostfix('left'), switchWithPostfix('right'), switchWithPostfix('center')], + 'U86K31ND6': [switchEndpoint('left'), switchEndpoint('right'), switchEndpoint('center')], 'HLD812-Z-SC': [cfg.light_brightness], 'HLC610-Z': [cfg.light_brightness], 'BY 285 C': [cfg.light_brightness_colortemp_colorxy], 'HS1RC-M': [cfg.sensor_action, cfg.sensor_battery], 'SWO-WDS1PA': [cfg.binary_sensor_contact], 'LLKZMK11LM': [ - switchWithPostfix('l1'), switchWithPostfix('l2'), + switchEndpoint('l1'), switchEndpoint('l2'), cfg.sensor_power, cfg.sensor_temperature, cfg.sensor_consumption, ], - 'T18W3Z': [switchWithPostfix('l1'), switchWithPostfix('l2'), switchWithPostfix('l3')], + 'T18W3Z': [switchEndpoint('l1'), switchEndpoint('l2'), switchEndpoint('l3')], 'LVS-SM10ZW': [cfg.binary_sensor_contact, cfg.binary_sensor_battery_low], 'HS2SK': [cfg.switch, cfg.sensor_power], '45853GE': [cfg.switch, cfg.sensor_power], @@ -1107,8 +1107,8 @@ const mapping = { 'ST8AU-CON': [cfg.light_brightness], 'HS3MS': [cfg.binary_sensor_occupancy], 'DIYRUZ_R4_5': [ - switchWithPostfix('bottom_left'), switchWithPostfix('bottom_right'), switchWithPostfix('center'), - switchWithPostfix('top_left'), switchWithPostfix('top_right'), + switchEndpoint('bottom_left'), switchEndpoint('bottom_right'), switchEndpoint('center'), + switchEndpoint('top_left'), switchEndpoint('top_right'), ], 'NCZ-3011-HA': [cfg.binary_sensor_contact, cfg.sensor_battery], 'MEAZON_BIZY_PLUG': [cfg.sensor_power, cfg.switch, cfg.sensor_temperature], @@ -1144,19 +1144,19 @@ const mapping = { 'RH3040': [cfg.sensor_battery, cfg.binary_sensor_occupancy], 'DZ4743-00B': [cfg.switch], 'GLSK3ZB-1711': [cfg.switch], - 'GLSK3ZB-1712': [switchWithPostfix('top'), switchWithPostfix('bottom')], - 'GLSK3ZB-1713': [switchWithPostfix('top'), switchWithPostfix('center'), switchWithPostfix('bottom')], + 'GLSK3ZB-1712': [switchEndpoint('top'), switchEndpoint('bottom')], + 'GLSK3ZB-1713': [switchEndpoint('top'), switchEndpoint('center'), switchEndpoint('bottom')], 'GLSK6ZB-1714': [ - switchWithPostfix('top_left'), switchWithPostfix('bottom_left'), - switchWithPostfix('top_right'), switchWithPostfix('bottom_right'), + switchEndpoint('top_left'), switchEndpoint('bottom_left'), + switchEndpoint('top_right'), switchEndpoint('bottom_right'), ], 'GLSK6ZB-1715': [ - switchWithPostfix('top_left'), switchWithPostfix('center_left'), switchWithPostfix('bottom_left'), - switchWithPostfix('top_right'), switchWithPostfix('bottom_right'), + switchEndpoint('top_left'), switchEndpoint('center_left'), switchEndpoint('bottom_left'), + switchEndpoint('top_right'), switchEndpoint('bottom_right'), ], 'GLSK6ZB-1716': [ - switchWithPostfix('top_left'), switchWithPostfix('center_left'), switchWithPostfix('bottom_left'), - switchWithPostfix('top_right'), switchWithPostfix('center_right'), switchWithPostfix('bottom_right'), + switchEndpoint('top_left'), switchEndpoint('center_left'), switchEndpoint('bottom_left'), + switchEndpoint('top_right'), switchEndpoint('center_right'), switchEndpoint('bottom_right'), ], '3306431P7': [cfg.light_brightness_colortemp], 'AC08559': [cfg.light_brightness_colortemp_colorxy], @@ -1165,28 +1165,28 @@ const mapping = { '2AJZ4KPKEY': [cfg.sensor_click, cfg.sensor_battery], '2AJZ4KPFT': [cfg.sensor_temperature, cfg.sensor_humidity, cfg.sensor_battery], 'TT001ZAV20': [cfg.sensor_temperature, cfg.sensor_humidity, cfg.sensor_battery], - 'TS0002': [switchWithPostfix('l1'), switchWithPostfix('l2')], + 'TS0002': [switchEndpoint('l1'), switchEndpoint('l2')], 'LVS-SN10ZW': [cfg.sensor_battery, cfg.binary_sensor_occupancy], 'LVS-ZB15R': [cfg.switch], 'TH1123ZB': [ - cfg.thermostat(7, 30, 'occupied_heating_setpoint', 1.0), cfg.sensor_local_temperature, + thermostat(7, 30, 'occupied_heating_setpoint', 1.0), cfg.sensor_local_temperature, cfg.lock_keypad_lockout, cfg.sensor_power, ], - 'TH1124ZB': [cfg.thermostat()], - 'TH1400ZB': [cfg.thermostat()], - 'TH1500ZB': [cfg.thermostat()], - 'Zen-01-W': [cfg.thermostat()], + 'TH1124ZB': [thermostat()], + 'TH1400ZB': [thermostat()], + 'TH1500ZB': [thermostat()], + 'Zen-01-W': [thermostat()], '9290022166': [cfg.light_brightness_colortemp_colorxy], 'PM-C140-ZB': [cfg.sensor_power, cfg.switch], 'PM-B530-ZB': [cfg.sensor_power, cfg.switch], 'PM-B430-ZB': [cfg.sensor_power, cfg.switch], 'ptvo.switch': [ - switchWithPostfix('bottom_left'), switchWithPostfix('bottom_right'), switchWithPostfix('top_left'), - switchWithPostfix('top_right'), switchWithPostfix('center'), cfg.sensor_click, + switchEndpoint('bottom_left'), switchEndpoint('bottom_right'), switchEndpoint('top_left'), + switchEndpoint('top_right'), switchEndpoint('center'), cfg.sensor_click, ], 'DIYRuZ_R4_5': [ - switchWithPostfix('bottom_left'), switchWithPostfix('bottom_right'), switchWithPostfix('top_left'), - switchWithPostfix('top_right'), switchWithPostfix('center'), + switchEndpoint('bottom_left'), switchEndpoint('bottom_right'), switchEndpoint('top_left'), + switchEndpoint('top_right'), switchEndpoint('center'), ], 'DIYRuZ_KEYPAD20': [], 'DTB190502A1': [], @@ -1199,7 +1199,7 @@ const mapping = { 'B00TN589ZG': [cfg.light_brightness], 'PSB19-SW27': [cfg.light_brightness], 'S1': [cfg.switch, cfg.sensor_power], - 'S2': [switchWithPostfix('l1'), switchWithPostfix('l2'), cfg.sensor_power], + 'S2': [switchEndpoint('l1'), switchEndpoint('l2'), cfg.sensor_power], 'ZWallRemote0': [cfg.sensor_click], 'D1': [cfg.light_brightness, cfg.sensor_power], 'J1': [cfg.cover_position_tilt, cfg.sensor_power], @@ -1276,7 +1276,7 @@ const mapping = { 'CR11S8UZ': [cfg.sensor_action], 'RB 148 T': [cfg.light_brightness_colortemp], 'STS-OUT-US-2': [cfg.switch, cfg.sensor_power], - 'UK7004240': [cfg.thermostat(), cfg.sensor_battery], + 'UK7004240': [thermostat(), cfg.sensor_battery], 'S31ZB': [cfg.switch], 'SA-003-Zigbee': [cfg.switch], 'SZ-DWS04': [cfg.binary_sensor_contact, cfg.sensor_temperature, cfg.sensor_battery], @@ -1315,10 +1315,10 @@ const mapping = { 'SP-EUC01': [cfg.switch, cfg.sensor_power], '511.012': [cfg.light_brightness], 'GL-S-008Z': [cfg.light_brightness_colortemp_colorxy], - 'TZSW22FW-L4': [switchWithPostfix('top'), switchWithPostfix('bottom')], + 'TZSW22FW-L4': [switchEndpoint('top'), switchEndpoint('bottom')], 'GDKES-01TZXD': [cfg.switch], - 'GDKES-02TZXD': [switchWithPostfix('left'), switchWithPostfix('right')], - 'GDKES-03TZXD': [switchWithPostfix('left'), switchWithPostfix('center'), switchWithPostfix('right')], + 'GDKES-02TZXD': [switchEndpoint('left'), switchEndpoint('right')], + 'GDKES-03TZXD': [switchEndpoint('left'), switchEndpoint('center'), switchEndpoint('right')], '6735/6736/6737': [cfg.switch, cfg.sensor_action], '4034031P7': [cfg.light_brightness_colortemp], '5900131C5': [cfg.light_brightness_colortemp], @@ -1344,22 +1344,22 @@ const mapping = { 'E1C-NB6': [cfg.switch], 'LVS-SC7': [cfg.sensor_action], '1742930P7': [cfg.light_brightness_colortemp_colorxy], - 'ZM-L03E-Z': [switchWithPostfix('left'), switchWithPostfix('center'), switchWithPostfix('right')], + 'ZM-L03E-Z': [switchEndpoint('left'), switchEndpoint('center'), switchEndpoint('right')], 'DL15S-1BZ': [cfg.switch], 'E1D-G73WNA': [cfg.binary_sensor_contact, cfg.binary_sensor_battery_low], - 'WV704R0A0902': [cfg.thermostat()], + 'WV704R0A0902': [thermostat()], '067776': [cfg.cover_position], '067773': [cfg.sensor_action, cfg.sensor_battery], '067771': [cfg.switch], '064873': [cfg.sensor_action], 'K4003C': [cfg.switch], 'STZB402': [ - cfg.thermostat(5, 30, 'occupied_heating_setpoint', 0.5), + thermostat(5, 30, 'occupied_heating_setpoint', 0.5), cfg.sensor_local_temperature, cfg.lock_keypad_lockout, ], 'SMT402': [ - cfg.thermostat(5, 30, 'occupied_heating_setpoint', 0.5), + thermostat(5, 30, 'occupied_heating_setpoint', 0.5), cfg.sensor_local_temperature, cfg.lock_keypad_lockout, ], @@ -1385,14 +1385,14 @@ const mapping = { 'AV2010/21A': [cfg.binary_sensor_battery_low, cfg.binary_sensor_contact, cfg.binary_sensor_tamper], 'AL-PIR02': [cfg.binary_sensor_occupancy, cfg.binary_sensor_tamper, cfg.sensor_battery], 'MKS-CM-W5': [ - switchWithPostfix('l1'), switchWithPostfix('l2'), - switchWithPostfix('l3'), switchWithPostfix('l4'), + switchEndpoint('l1'), switchEndpoint('l2'), + switchEndpoint('l3'), switchEndpoint('l4'), ], 'STS-WTR-250': [cfg.binary_sensor_water_leak, cfg.sensor_battery, cfg.sensor_temperature], 'ZG2835RAC': [cfg.light_brightness], 'BW-IS2': [cfg.binary_sensor_contact, cfg.sensor_battery], 'BW-IS3': [cfg.binary_sensor_occupancy], - 'SLR1b': [cfg.thermostat()], + 'SLR1b': [thermostat()], 'WPT1': [], '4058075047853': [cfg.light_brightness_colortemp_colorxy], 'ROB_200-003-0': [cfg.switch], @@ -1431,7 +1431,7 @@ const mapping = { 'GL-D-003ZS': [cfg.light_brightness_colortemp_colorxy], '66492-001': [cfg.lock, cfg.sensor_battery], '798.09': [cfg.light_brightness_colortemp_colorxy], - 'U202DST600ZB': [lightWithPostfix('light_brightness', 'l1'), lightWithPostfix('light_brightness', 'l2')], + 'U202DST600ZB': [lightEndpoint('light_brightness', 'l1'), lightEndpoint('light_brightness', 'l2')], 'SM10ZW': [cfg.binary_sensor_contact, cfg.sensor_battery], 'ICZB-R11D': [cfg.light_brightness], 'SW2500ZB': [cfg.switch], @@ -1440,8 +1440,8 @@ const mapping = { '4090331P9': [cfg.light_brightness_colortemp_colorxy], 'HS1EB': [cfg.sensor_click], 'HS2SW1A-N': [cfg.switch], - 'HS2SW2A-N': [switchWithPostfix('left'), switchWithPostfix('right')], - 'HS2SW3A-N': [switchWithPostfix('left'), switchWithPostfix('right'), switchWithPostfix('center')], + 'HS2SW2A-N': [switchEndpoint('left'), switchEndpoint('right')], + 'HS2SW3A-N': [switchEndpoint('left'), switchEndpoint('right'), switchEndpoint('center')], 'LifeControl_Leak_Sensor': [cfg.binary_sensor_water_leak, cfg.sensor_battery], 'LifeControl_Door_Sensor': [cfg.binary_sensor_contact, cfg.sensor_battery], 'LifeControl_RGB_Led': [cfg.light_brightness_colortemp_colorxy], @@ -1449,21 +1449,21 @@ const mapping = { 'ZL1000400-CCT-EU-2-V1A02': [cfg.light_brightness_colortemp], 'ROB_200-007-0': [cfg.sensor_action, cfg.sensor_battery], 'PM-S140-ZB': [cfg.switch], - 'PM-S240-ZB': [switchWithPostfix('top'), switchWithPostfix('bottom')], - 'PM-S340-ZB': [switchWithPostfix('top'), switchWithPostfix('center'), switchWithPostfix('bottom')], + 'PM-S240-ZB': [switchEndpoint('top'), switchEndpoint('bottom')], + 'PM-S340-ZB': [switchEndpoint('top'), switchEndpoint('center'), switchEndpoint('bottom')], 'U201DST600ZB': [cfg.light_brightness], 'U201SRY2KWZB': [cfg.switch], - 'U202SRY2KWZB': [switchWithPostfix('l1'), switchWithPostfix('l2')], + 'U202SRY2KWZB': [switchEndpoint('l1'), switchEndpoint('l2')], '93999': [cfg.light_brightness], 'ZHS-15': [cfg.switch, cfg.sensor_power, cfg.sensor_current, cfg.sensor_voltage], 'GS361A-H04': [ cfg.lock_child_lock, cfg.switch_window_detection, cfg.switch_valve_detection, - cfg.thermostat(5, 30, 'current_heating_setpoint', 0.5), + thermostat(5, 30, 'current_heating_setpoint', 0.5), cfg.sensor_battery, ], - 'HLC614-ZLL': [switchWithPostfix('l1'), switchWithPostfix('l2'), switchWithPostfix('l3')], + 'HLC614-ZLL': [switchEndpoint('l1'), switchEndpoint('l2'), switchEndpoint('l3')], 'EMIZB-132': [ cfg.sensor_power, cfg.sensor_voltage, cfg.sensor_current, cfg.sensor_energy, cfg.sensor_current_phase_b, cfg.sensor_current_phase_c, cfg.sensor_voltage_phase_b, cfg.sensor_voltage_phase_c, @@ -1489,22 +1489,22 @@ const mapping = { 'SWA01ZB': [cfg.binary_sensor_water_leak, cfg.sensor_battery], 'SDM01ZB': [cfg.binary_sensor_contact, cfg.sensor_battery], 'SFS01ZB': [cfg.switch], - 'SLS301ZB_2': [switchWithPostfix('left'), switchWithPostfix('right')], - 'SLS301ZB_3': [switchWithPostfix('left'), switchWithPostfix('right'), switchWithPostfix('center')], + 'SLS301ZB_2': [switchEndpoint('left'), switchEndpoint('right')], + 'SLS301ZB_3': [switchEndpoint('left'), switchEndpoint('right'), switchEndpoint('center')], 'SSS401ZB': [cfg.switch, cfg.sensor_action], 'E12-N1E': [cfg.light_brightness_colortemp_colorxy], - '4040B': [cfg.sensor_power, switchWithPostfix('l1'), switchWithPostfix('l2')], + '4040B': [cfg.sensor_power, switchEndpoint('l1'), switchEndpoint('l2')], '3460-L': [cfg.sensor_action, cfg.sensor_temperature, cfg.sensor_battery], - '3157100': [cfg.thermostat_heatcool(10, 30, 1, ['auto', 'on']), cfg.sensor_battery], + '3157100': [thermostatHeatCool(10, 30, 1, ['auto', 'on']), cfg.sensor_battery], '4257050-RZHAC': [cfg.switch, cfg.sensor_power], '27087-03': [cfg.switch, cfg.sensor_battery], '99140-002': [cfg.lock, cfg.sensor_battery], '4512706': [cfg.sensor_battery, cfg.sensor_action], 'GL-S-004ZS': [cfg.light_brightness_colortemp_colorxy], '7121131PU': [cfg.light_brightness_colortemp_colorxy], - 'RL804QZB': [switchWithPostfix('l1'), switchWithPostfix('l2'), switchWithPostfix('l3')], - 'RC-2000WH': [cfg.thermostat_heatcool(10, 30, 1, ['auto', 'on', 'smart'])], - 'TH1300ZB': [cfg.thermostat()], + 'RL804QZB': [switchEndpoint('l1'), switchEndpoint('l2'), switchEndpoint('l3')], + 'RC-2000WH': [thermostatHeatCool(10, 30, 1, ['auto', 'on', 'smart'])], + 'TH1300ZB': [thermostat()], 'SP 220': [cfg.switch], '511.040': [cfg.light_brightness_colortemp_colorxy], '511.344': [cfg.sensor_battery, cfg.sensor_action, cfg.sensor_action_color], @@ -1521,10 +1521,6 @@ const mapping = { 'AU-A1GSZ9RGBW': [cfg.light_brightness_colortemp_colorxy], }; -Object.keys(mapping).forEach((key) => { - mapping[key].push(cfg.sensor_linkquality); -}); - /** * This extensions handles integration with HomeAssistant */ @@ -1553,10 +1549,10 @@ class HomeAssistant extends Extension { } onDeviceRemoved(device) { - const mappedModel = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); - if (mappedModel) { + const definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); + if (definition) { logger.info(`Clearing Home Assistant discovery topic for '${device.ieeeAddr}'`); - this.getConfigs(mappedModel).forEach((config) => { + this.getConfigs(definition).forEach((config) => { const topic = this.getDiscoveryTopic(config, device); this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic); }); @@ -1565,7 +1561,7 @@ class HomeAssistant extends Extension { async onPublishEntityState(data) { /** - * In case we deal with a lightWithPostfix configuration Zigbee2mqtt publishes + * In case we deal with a lightEndpoint configuration Zigbee2mqtt publishes * e.g. {state_l1: ON, brightness_l1: 250} to zigbee2mqtt/mydevice. * As the Home Assistant MQTT JSON light cannot be configured to use state_l1/brightness_l1 * as the state variables, the state topic is set to zigbee2mqtt/mydevice/l1. @@ -1576,18 +1572,18 @@ class HomeAssistant extends Extension { for (const config of mapping[data.entity.definition.model]) { const match = /light_(.*)/.exec(config['object_id']); if (match) { - const postfix = match[1]; - const posfixRegExp = new RegExp(`(.*)_${postfix}`); + const endpoint = match[1]; + const endpointRegExp = new RegExp(`(.*)_${endpoint}`); const payload = {}; for (const key of Object.keys(data.payload)) { - const keyMatch = posfixRegExp.exec(key); + const keyMatch = endpointRegExp.exec(key); if (keyMatch) { payload[keyMatch[1]] = data.payload[key]; } } await this.mqtt.publish( - `${data.entity.name}/${postfix}`, JSON.stringify(payload), {}, + `${data.entity.name}/${endpoint}`, JSON.stringify(payload), {}, ); } } @@ -1618,7 +1614,7 @@ class HomeAssistant extends Extension { subtype: value, payload: value, topic: `${settings.get().mqtt.base_topic}/${data.entity.name}/${key}`, - device: this.getDevicePayload(data.entity.settings, data.entity.definition), + device: this.getDevicePayload(data.entity), }; await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic); @@ -1655,9 +1651,9 @@ class HomeAssistant extends Extension { } onDeviceRenamed(device) { - const mappedModel = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); + const resolvedEntity = this.zigbee.resolveEntity(device); logger.info(`Refreshing Home Assistant discovery topic for '${device.ieeeAddr}'`); - this.discover(device, mappedModel, true); + this.discover(resolvedEntity, true); } async onMQTTConnected() { @@ -1665,15 +1661,15 @@ class HomeAssistant extends Extension { // MQTT discovery of all paired devices on startup. for (const device of this.zigbee.getClients()) { - const mappedModel = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); - if (mappedModel) { - this.discover(device, mappedModel, true); - } + const resolvedEntity = this.zigbee.resolveEntity(device); + this.discover(resolvedEntity, true); } } getConfigs(mappedModel) { let configs = mapping[mappedModel.model].slice(); + configs.push(cfg.sensor_linkquality); + if (mappedModel.hasOwnProperty('ota')) { configs.push(cfg.binary_sensor_update_available); } @@ -1685,23 +1681,21 @@ class HomeAssistant extends Extension { return configs; } - discover(device, mappedModel, force=false) { + discover(resolvedEntity, force=false) { // Check if already discoverd and check if there are configs. + const {device, definition} = resolvedEntity; + const deviceSettings = resolvedEntity.settings; const discover = force || !this.discovered[device.ieeeAddr]; - if (!discover) { + if (!discover || !device || !definition || !mapping[definition.model] || !deviceSettings || + (deviceSettings.hasOwnProperty('homeassistant') && !deviceSettings.homeassistant)) { return; } - const entity = settings.getEntity(device.ieeeAddr); - if (!entity || (entity.type === 'device' && !mapping[mappedModel.model]) || - (entity.hasOwnProperty('homeassistant') && !entity.homeassistant)) { - return; - } - - this.getConfigs(mappedModel).forEach((config) => { + const friendlyName = deviceSettings.friendlyName; + this.getConfigs(definition).forEach((config) => { const topic = this.getDiscoveryTopic(config, device); const payload = {...config.discovery_payload}; - let stateTopic = `${settings.get().mqtt.base_topic}/${entity.friendlyName}`; + let stateTopic = `${settings.get().mqtt.base_topic}/${deviceSettings.friendlyName}`; if (payload.state_topic_postfix) { stateTopic += `/${payload.state_topic_postfix}`; delete payload.state_topic_postfix; @@ -1727,26 +1721,26 @@ class HomeAssistant extends Extension { payload.json_attributes_topic = stateTopic; // Set (unique) name - payload.name = `${entity.friendlyName}_${config.object_id}`; + payload.name = `${friendlyName}_${config.object_id}`; // Set unique_id - payload.unique_id = `${entity.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`; + payload.unique_id = `${deviceSettings.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`; // Attributes for device registry - payload.device = this.getDevicePayload(entity, mappedModel); + payload.device = this.getDevicePayload(resolvedEntity); // Set availability payload // When using availability_timeout each device has it's own availability topic. // If not, use the availability topic of zigbee2mqtt. if (settings.get().advanced.availability_timeout) { - payload.availability_topic = `${settings.get().mqtt.base_topic}/${entity.friendlyName}/availability`; + payload.availability_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/availability`; } else { payload.availability_topic = `${settings.get().mqtt.base_topic}/bridge/state`; } // Add precision to value_template - if (entity.hasOwnProperty(`${config.object_id}_precision`)) { - const precision = entity[`${config.object_id}_precision`]; + if (deviceSettings.hasOwnProperty(`${config.object_id}_precision`)) { + const precision = deviceSettings[`${config.object_id}_precision`]; let template = payload.value_template; template = template.replace('{{ ', '').replace(' }}', ''); template = `{{ (${template} | float) | round(${precision}) }}`; @@ -1754,7 +1748,7 @@ class HomeAssistant extends Extension { } if (payload.command_topic) { - payload.command_topic = `${settings.get().mqtt.base_topic}/${entity.friendlyName}/`; + payload.command_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/`; if (payload.command_topic_prefix) { payload.command_topic += `${payload.command_topic_prefix}/`; @@ -1836,7 +1830,7 @@ class HomeAssistant extends Extension { } // Override configuration with user settings. - if (entity.hasOwnProperty('homeassistant')) { + if (deviceSettings.hasOwnProperty('homeassistant')) { const add = (obj) => { Object.keys(obj).forEach((key) => { if (['number', 'string', 'boolean'].includes(typeof obj[key])) { @@ -1851,10 +1845,10 @@ class HomeAssistant extends Extension { }); }; - add(entity.homeassistant); + add(deviceSettings.homeassistant); - if (entity.homeassistant.hasOwnProperty(config.object_id)) { - add(entity.homeassistant[config.object_id]); + if (deviceSettings.homeassistant.hasOwnProperty(config.object_id)) { + add(deviceSettings.homeassistant[config.object_id]); } } @@ -1884,19 +1878,18 @@ class HomeAssistant extends Extension { } onZigbeeEvent(type, data, resolvedEntity) { - const device = data.device; - if (device && resolvedEntity && resolvedEntity.definition) { - this.discover(device, resolvedEntity.definition); + if (resolvedEntity && type !== 'deviceLeave') { + this.discover(resolvedEntity); } } - getDevicePayload(entity, mappedModel) { + getDevicePayload(resolvedEntity) { return { - identifiers: [`zigbee2mqtt_${entity.ID}`], - name: entity.friendlyName, + identifiers: [`zigbee2mqtt_${resolvedEntity.settings.ID}`], + name: resolvedEntity.settings.friendlyName, sw_version: `Zigbee2mqtt ${zigbee2mqttVersion}`, - model: `${mappedModel.description} (${mappedModel.model})`, - manufacturer: mappedModel.vendor, + model: `${resolvedEntity.definition.description} (${resolvedEntity.definition.model})`, + manufacturer: resolvedEntity.definition.vendor, }; } diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index c45150eb9..d2d7178ac 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -558,7 +558,7 @@ describe('HomeAssistant extension', () => { ); }); - it('Shouldnt discover when message has no device yet', async () => { + it('Shouldnt discover when device leaves', async () => { controller = new Controller(); await controller.start(); await flushPromises();