mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-28 05:24:29 +00:00
Implement Home Assistant MQTT device trigger. #3033
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user