mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-27 21:20:03 +00:00
Refactor.
This commit is contained in:
+173
-180
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user