Implement lightWithPostfix Home Assistant configuration. #3003

This commit is contained in:
Koen Kanters
2020-03-15 09:38:39 +01:00
parent cb87b31cdd
commit 4d3bbce687
5 changed files with 87 additions and 29 deletions
+6 -2
View File
@@ -174,8 +174,12 @@ class EntityPublish extends BaseExtension {
const msg = result.state;
if (postfix) {
msg[`state_${postfix}`] = msg.state;
delete msg.state;
for (const key of ['state', 'brightness']) {
if (msg.hasOwnProperty(key)) {
msg[`${key}_${postfix}`] = msg[key];
delete msg[key];
}
}
}
this.publishEntityState(entity.settings.ID, msg);
+51 -26
View File
@@ -3,6 +3,7 @@ const settings = require('../util/settings');
const logger = require('../util/logger');
const zigbee2mqttVersion = require('../../package.json').version;
const BaseExtension = require('./baseExtension');
const objectAssignDeep = require(`object-assign-deep`);
const cfg = {
// Binary sensor
@@ -539,25 +540,12 @@ const switchWithPostfix = (postfix) => {
};
};
const lightWithPostfix = (postfix) => {
return {
type: 'light',
object_id: 'light',
discovery_payload: {
schema: 'template',
command_topic: true,
command_topic_prefix: postfix,
command_on_template: `
{"state": "on"
{%- if brightness is defined -%}
, "brightness": {{ brightness }}
{%- endif -%}
}`,
command_off_template: '{"state": "off"}',
state_template: `{{ value_json.state_${postfix} }}`,
brightness_template: `{{ value_json.brightness_${postfix} }}`,
},
};
const lightWithPostfix = (configType, postfix) => {
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;
return config;
};
// Map homeassitant configurations to devices.
@@ -1303,7 +1291,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('l1'), lightWithPostfix('l2')],
'U202DST600ZB': [lightWithPostfix('light_brightness', 'l1'), lightWithPostfix('light_brightness', 'l2')],
'SM10ZW': [cfg.binary_sensor_contact, cfg.sensor_battery],
'ICZB-R11D': [cfg.light_brightness],
'SW2500ZB': [cfg.switch],
@@ -1364,9 +1352,43 @@ class HomeAssistant extends BaseExtension {
}
async onPublishEntityState(data) {
const key = ['action', 'click'].find((k) => data.payload.hasOwnProperty(k) && data.payload[k] !== '');
/**
* In case we deal with a lightWithPostfix 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.
* Here we retrieve all the attributes with the _l1 values and republish them on
* zigbee2mqtt/mydevice/l1.
*/
if (data.entity.mapped && mapping[data.entity.mapped.model]) {
for (const config of mapping[data.entity.mapped.model]) {
const match = /light_(.*)/.exec(config['object_id']);
if (match) {
const postfix = match[1];
const posfixRegExp = new RegExp(`(.*)_${postfix}`);
const payload = {};
for (const key of Object.keys(data.payload)) {
const keyMatch = posfixRegExp.exec(key);
if (keyMatch) {
payload[keyMatch[1]] = data.payload[key];
}
}
if (key && data.entity.type === 'device') {
await this.mqtt.publish(
`${data.entity.name}/${postfix}`, JSON.stringify(payload), {},
);
}
}
}
/**
* Implements the MQTT device trigger (https://www.home-assistant.io/integrations/device_trigger.mqtt/)
* The MQTT device trigger does not support JSON parsing, so it cannot listen to zigbee2mqtt/my_device
* Whenever a device publish an {action: *} we discover an MQTT device trigger sensor
* and republish it to zigbee2mqtt/my_devic/action
*/
const key = ['action', 'click'].find((k) => data.payload.hasOwnProperty(k) && data.payload[k] !== '');
if (data.entity.mapped && key) {
const device = data.entity.device;
if (!this.discoveredTriggers[device.ieeeAddr]) {
this.discoveredTriggers[device.ieeeAddr] = new Set();
@@ -1379,16 +1401,15 @@ class HomeAssistant extends BaseExtension {
const config = cfg[`trigger_${key}`];
config.object_id = `${key}_${value}`;
const topic = this.getDiscoveryTopic(config, device);
const mappedModel = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID);
const payload = {
...config.discovery_payload,
subtype: value,
payload: value,
topic: `${settings.get().mqtt.base_topic}/${data.entity.name}/${key}`,
device: this.getDevicePayload(data.entity.settings, mappedModel),
device: this.getDevicePayload(data.entity.settings, data.entity.mapped),
};
this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic);
await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic);
this.discoveredTriggers[device.ieeeAddr].add(discoveredKey);
}
@@ -1443,7 +1464,11 @@ class HomeAssistant extends BaseExtension {
this.getConfigs(mappedModel).forEach((config) => {
const topic = this.getDiscoveryTopic(config, device);
const payload = {...config.discovery_payload};
const stateTopic = `${settings.get().mqtt.base_topic}/${entity.friendlyName}`;
let stateTopic = `${settings.get().mqtt.base_topic}/${entity.friendlyName}`;
if (payload.state_topic_postfix) {
stateTopic += `/${payload.state_topic_postfix}`;
delete payload.state_topic_postfix;
}
if (!payload.hasOwnProperty('state_topic') || payload.state_topic) {
payload.state_topic = stateTopic;
+25
View File
@@ -893,4 +893,29 @@ describe('HomeAssistant extension', () => {
expect(MQTT.publish).toHaveBeenCalledTimes(3);
});
it('Should republish payload to postfix topic with lightWithPostfix config', async () => {
controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/U202DST600ZB/l2/set', JSON.stringify({state: 'ON', brightness: 20}));
await flushPromises();
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB', JSON.stringify({state_l2:"ON", brightness_l2:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l2', JSON.stringify({state:"ON", brightness:20}), {"qos": 0, "retain": false}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/U202DST600ZB/l1', JSON.stringify({}), {"qos": 0, "retain": false}, expect.any(Function));
});
it('Shouldnt crash in onPublishEntityState on group publish', async () => {
controller = new Controller(false);
await controller.start();
await flushPromises();
logger.error.mockClear();
MQTT.publish.mockClear();
await MQTT.events.message('zigbee2mqtt/group_1/set', JSON.stringify({state: 'ON'}));
await flushPromises();
expect(logger.error).toHaveBeenCalledTimes(0);
});
});
+4 -1
View File
@@ -147,7 +147,10 @@ function writeDefaultConfiguration() {
},
'0x0017880104e45526': {
friendly_name: 'GL-S-007ZS',
}
},
'0x0017880104e43559': {
friendly_name: 'U202DST600ZB'
},
},
groups: {
'1': {
+1
View File
@@ -144,6 +144,7 @@ const devices = {
'SP600_NEW': new Device('Router', '0x90fd9ffffe4b64ab', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64aa', [], {seMetering: {"multiplier":1,"divisor":10000}})], true, "Mains (single phase)", "SP600", false, 'Salus', '20170220'),
'MKS-CM-W5': new Device('Router', '0x90fd9ffffe4b64ac', 33901, 4476, [new Endpoint(1, [0,4,3,5,10,258,13,19,6,1,1030,8,768,1027,1029,1026], [0,3,4,6,8,5], '0x90fd9ffffe4b64aa', [], {})], true, "Mains (single phase)", "qnazj70", false),
'GL-S-007ZS': new Device('Router', '0x0017880104e45526', 6540,4151, [new Endpoint(1, [0], [], '0x0017880104e45526')], true, "Mains (single phase)", 'GL-S-007ZS'),
'U202DST600ZB': new Device('Router', '0x0017880104e43559', 6540,4151, [new Endpoint(10, [0, 6], [], '0x0017880104e43559'), new Endpoint(11, [0, 6], [], '0x0017880104e43559')], true, "Mains (single phase)", 'U202DST600ZB'),
}
const groups = {