Implement Home Assistant MQTT device trigger. #3033

This commit is contained in:
Koen Kanters
2020-02-29 18:07:15 +01:00
parent 2eb6f075dc
commit ed75c85b69
4 changed files with 126 additions and 7 deletions
+2
View File
@@ -285,6 +285,8 @@ class Controller {
attributes.forEach((a) => messagePayload.device[a] = device[a]);
}
this.eventBus.emit('publishEntityState', {payload: messagePayload, entity});
if (Object.entries(messagePayload).length) {
if (settings.get().experimental.output === 'attribute_and_json') {
await this.mqtt.publish(entity.name, JSON.stringify(messagePayload), options);
+1
View File
@@ -3,6 +3,7 @@ const assert = require('assert');
const allowedEvents = [
'deviceRemoved', // Device has been removed
'publishEntityState', // Entity state will be published
];
class EventBus extends events.EventEmitter {
+62 -7
View File
@@ -507,6 +507,22 @@ const cfg = {
speeds: ['off', 'low', 'medium', 'high', 'on', 'auto', 'smart'],
},
},
// Trigger
'trigger_action': {
type: 'device_automation',
discovery_payload: {
automation_type: 'trigger',
type: 'action',
},
},
'trigger_click': {
type: 'device_automation',
discovery_payload: {
automation_type: 'trigger',
type: 'click',
},
},
};
const switchWithPostfix = (postfix) => {
@@ -1274,6 +1290,7 @@ class HomeAssistant extends BaseExtension {
// A map of all discoverd devices
this.discovered = {};
this.discoveredTriggers = {};
if (!settings.get().advanced.cache_state) {
logger.warn('In order for HomeAssistant integration to work properly set `cache_state: true');
@@ -1287,6 +1304,7 @@ class HomeAssistant extends BaseExtension {
this.statusTopic = settings.get().advanced.homeassistant_status_topic;
this.eventBus.on('deviceRemoved', (data) => this.onDeviceRemoved(data.device));
this.eventBus.on('publishEntityState', (data) => this.onPublishEntityState(data));
}
onDeviceRemoved(device) {
@@ -1300,6 +1318,39 @@ class HomeAssistant extends BaseExtension {
}
}
async onPublishEntityState(data) {
const key = ['action', 'click'].find((k) => data.payload.hasOwnProperty(k) && data.payload[k] !== '');
if (key && data.entity.type === 'device') {
const device = data.entity.device;
if (!this.discoveredTriggers[device.ieeeAddr]) {
this.discoveredTriggers[device.ieeeAddr] = new Set();
}
const value = data.payload[key];
const discoveredKey = `${key}_${value}`;
if (!this.discoveredTriggers[device.ieeeAddr].has(discoveredKey)) {
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),
};
this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic);
this.discoveredTriggers[device.ieeeAddr].add(discoveredKey);
}
await this.mqtt.publish(`${data.entity.name}/${key}`, value, {});
}
}
async onMQTTConnected() {
this.mqtt.subscribe(this.statusTopic);
@@ -1364,13 +1415,7 @@ class HomeAssistant extends BaseExtension {
payload.unique_id = `${entity.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`;
// Attributes for device registry
payload.device = {
identifiers: [`zigbee2mqtt_${entity.ID}`],
name: entity.friendlyName,
sw_version: `Zigbee2mqtt ${zigbee2mqttVersion}`,
model: `${mappedModel.description} (${mappedModel.model})`,
manufacturer: mappedModel.vendor,
};
payload.device = this.getDevicePayload(entity, mappedModel);
// Set availability payload
// When using availability_timeout each device has it's own availability topic.
@@ -1503,6 +1548,16 @@ class HomeAssistant extends BaseExtension {
}
}
getDevicePayload(entity, mappedModel) {
return {
identifiers: [`zigbee2mqtt_${entity.ID}`],
name: entity.friendlyName,
sw_version: `Zigbee2mqtt ${zigbee2mqttVersion}`,
model: `${mappedModel.description} (${mappedModel.model})`,
manufacturer: mappedModel.vendor,
};
}
getDiscoveryTopic(config, device) {
return `${config.type}/${device.ieeeAddr}/${config.object_id}/config`;
}
+61
View File
@@ -719,4 +719,65 @@ describe('HomeAssistant extension', () => {
expect.any(Function),
);
});
it('Should discover trigger when click is published', async () => {
controller = new Controller(false);
await controller.start();
await flushPromises();
MQTT.publish.mockClear();
const device = zigbeeHerdsman.devices.WXKG11LM;
const payload = {data: {onOff: 1}, cluster: 'genOnOff', device, endpoint: device.getEndpoint(1), type: 'attributeReport', linkquality: 10};
await zigbeeHerdsman.events.message(payload);
await flushPromises();
const discoverPayload = {
"automation_type":"trigger",
"type":"click",
"subtype":"single",
"payload":"single",
"topic":"zigbee2mqtt/button/click",
"device":{
"identifiers":[
"zigbee2mqtt_0x0017880104e45520"
],
"name":"button",
"sw_version": this.version,
"model":"Aqara wireless switch (WXKG11LM)",
"manufacturer":"Xiaomi"
}
};
expect(MQTT.publish).toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/click_single/config',
JSON.stringify(discoverPayload),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/click',
'single',
{ retain: false, qos: 0 },
expect.any(Function),
);
// Should only discover it once
MQTT.publish.mockClear();
await zigbeeHerdsman.events.message(payload);
await flushPromises();
expect(MQTT.publish).not.toHaveBeenCalledWith(
'homeassistant/device_automation/0x0017880104e45520/click_single/config',
JSON.stringify(discoverPayload),
{ retain: true, qos: 0 },
expect.any(Function),
);
expect(MQTT.publish).toHaveBeenCalledWith(
'zigbee2mqtt/button/click',
'single',
{ retain: false, qos: 0 },
expect.any(Function),
);
});
});