mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-10-23 06:12:23 +00:00
Refactor & remove home assistant pair switch discovery.
This commit is contained in:
1 parent
d20012e4d8
commit
a13080f399
6 files changed
+101
-104
No files matched your search
+48
-77
@@ -7,14 +7,15 @@ const zigbee2mqtt = require('./converters/zigbee2mqtt');
|
||||
const mqtt2zigbee = require('./converters/mqtt2zigbee');
|
||||
const homeassistant = require('./homeassistant');
|
||||
|
||||
const mqttConfigRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/config/\\w+`, 'g');
|
||||
const mqttDeviceRegex = new RegExp(`${settings.get().mqtt.base_topic}/\\w+/set`, 'g');
|
||||
|
||||
class Controller {
|
||||
|
||||
constructor() {
|
||||
this.zigbee = new Zigbee();
|
||||
this.mqtt = new MQTT();
|
||||
this.stateCache = {};
|
||||
this.hassDiscoveryCache = {};
|
||||
|
||||
this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this);
|
||||
this.handleMQTTMessage = this.handleMQTTMessage.bind(this);
|
||||
}
|
||||
@@ -24,9 +25,8 @@ class Controller {
|
||||
if (error) {
|
||||
logger.error('Failed to start');
|
||||
} else {
|
||||
this.mqtt.connect(this.handleMQTTMessage, () => {
|
||||
this.handleStarted();
|
||||
});
|
||||
const subscriptions = [`${settings.get().mqtt.base_topic}/+/set`, `${settings.get().mqtt.base_topic}/bridge/config/+`];
|
||||
this.mqtt.connect(this.handleMQTTMessage, subscriptions, () => this.handleStarted());
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -35,24 +35,9 @@ class Controller {
|
||||
// Home Assistant MQTT discovery on startup.
|
||||
if (settings.get().homeassistant) {
|
||||
// MQTT discovery of all paired devices on startup.
|
||||
const devices = this.zigbee.getAllClients();
|
||||
devices.forEach((device) => {
|
||||
const mappedModel = deviceMapping[device.modelId];
|
||||
|
||||
if (mappedModel && mappedModel.homeassistant && !this.hassDiscoveryCache[device.ieeeAddr]) {
|
||||
this.homeassistantDiscover(
|
||||
mappedModel.homeassistant,
|
||||
device.ieeeAddr,
|
||||
settings.getDevice(device.ieeeAddr).friendly_name
|
||||
);
|
||||
|
||||
this.hassDiscoveryCache[device.ieeeAddr] = true;
|
||||
}
|
||||
this.zigbee.getAllClients().forEach((device) => {
|
||||
homeassistant.discover(device.ieeeAddr, deviceMapping[device.modelId].homeassistant, this.mqtt);
|
||||
});
|
||||
|
||||
// MQTT discovery of zigbee2mqtt permit join switch.
|
||||
this.homeassistantDiscover([homeassistant.zigbee2mqtt_permit_join], 'zigbee2mqtt_permit_join', 'zigbee2mqtt_permit_join');
|
||||
this.mqtt.publish('permit_join', JSON.stringify({state: settings.get().permit_join ? '"ON"' : '"OFF"'}), true);
|
||||
}
|
||||
|
||||
// Enable zigbee join.
|
||||
@@ -72,13 +57,12 @@ class Controller {
|
||||
handleZigbeeMessage(message) {
|
||||
if (message.type == 'devInterview') {
|
||||
logger.info('Connecting with device, please wait...');
|
||||
}
|
||||
if (message.type == 'devIncoming') {
|
||||
} else if (message.type == 'devIncoming') {
|
||||
logger.info('New device joined the network!');
|
||||
}
|
||||
|
||||
// We dont handle messages without endpoints.
|
||||
if (!message.endpoints) {
|
||||
// We dont handle messages without endpoints.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,7 +87,6 @@ class Controller {
|
||||
// Map Zigbee modelID to vendor modelID.
|
||||
const modelID = message.endpoints[0].device.modelId;
|
||||
const mappedModel = deviceMapping[modelID];
|
||||
const friendlyName = settings.getDevice(device.ieeeAddr).friendly_name;
|
||||
|
||||
if (!mappedModel) {
|
||||
logger.warn(`Device with modelID '${modelID}' is not supported.`);
|
||||
@@ -112,10 +95,8 @@ class Controller {
|
||||
}
|
||||
|
||||
// Home Assistant MQTT discovery
|
||||
if (settings.get().homeassistant && mappedModel.homeassistant &&
|
||||
!this.hassDiscoveryCache[device.ieeeAddr]) {
|
||||
this.homeassistantDiscover(mappedModel.homeassistant, device.ieeeAddr, friendlyName);
|
||||
this.hassDiscoveryCache[device.ieeeAddr] = true;
|
||||
if (settings.get().homeassistant) {
|
||||
homeassistant.discover(device.ieeeAddr, mappedModel.homeassistant, this.mqtt);
|
||||
}
|
||||
|
||||
// After this point we cant handle message withoud cid anymore.
|
||||
@@ -134,21 +115,12 @@ class Controller {
|
||||
}
|
||||
|
||||
// Convert this Zigbee message to a MQTT message.
|
||||
const retain = settings.getDevice(device.ieeeAddr).retain;
|
||||
|
||||
const publish = (payload) => {
|
||||
if (this.stateCache[device.ieeeAddr]) {
|
||||
payload = {...this.stateCache[device.ieeeAddr], ...payload};
|
||||
}
|
||||
|
||||
this.mqtt.publish(friendlyName, JSON.stringify(payload), retain);
|
||||
}
|
||||
|
||||
// Get payload for the message.
|
||||
// - If a payload is returned publish it to the MQTT broker
|
||||
// - If NO payload is returned do nothing. This is for non-standard behaviour
|
||||
// for e.g. click switches where we need to count number of clicks and detect long presses.
|
||||
converters.forEach((converter) => {
|
||||
const publish = (payload) => this.mqttPublishDeviceState(device.ieeeAddr, payload);
|
||||
const payload = converter.convert(message, publish);
|
||||
|
||||
if (payload) {
|
||||
@@ -162,8 +134,35 @@ class Controller {
|
||||
}
|
||||
|
||||
handleMQTTMessage(topic, message) {
|
||||
if (topic.match(mqttConfigRegex)) {
|
||||
this.handleMQTTMessageConfig(topic, message);
|
||||
} else if (topic.match(mqttDeviceRegex)) {
|
||||
this.handleMQTTMessageDevice(topic, message);
|
||||
} else {
|
||||
logger.warn(`Cannot handle MQTT message with topic '${topic}' and message '${message}'`);
|
||||
}
|
||||
}
|
||||
|
||||
handleMQTTMessageConfig(topic, message) {
|
||||
const option = topic.split('/')[3];
|
||||
|
||||
if (option === 'permit_join') {
|
||||
this.zigbee.permitJoin(message.toString().toLowerCase() === 'true');
|
||||
} else {
|
||||
logger.warn(`Cannot handle MQTT config option '${option}' with message '${message}'`);
|
||||
}
|
||||
}
|
||||
|
||||
handleMQTTMessageDevice(topic, message) {
|
||||
const friendlyName = topic.split('/')[1];
|
||||
|
||||
// Map friendlyName to deviceID.
|
||||
const deviceID = Object.keys(settings.get().devices).find((id) => settings.getDevice(id).friendly_name === friendlyName);
|
||||
if (!deviceID) {
|
||||
logger.error(`Cannot handle '${topic}' because deviceID of '${friendlyName}' cannot be found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the MQTT message to a Zigbee message.
|
||||
let json = null;
|
||||
try {
|
||||
@@ -173,20 +172,6 @@ class Controller {
|
||||
json = {state: message.toString()};
|
||||
}
|
||||
|
||||
// Check if permit_join
|
||||
if (friendlyName === 'zigbee2mqtt_permit_join') {
|
||||
this.zigbee.permitJoin(json.state === 'ON' ? true : false);
|
||||
this.mqtt.publish(friendlyName, JSON.stringify({state: json.state}), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Map friendlyName to deviceID.
|
||||
const deviceID = Object.keys(settings.get().devices).find((id) => settings.getDevice(id).friendly_name === friendlyName);
|
||||
if (!deviceID) {
|
||||
logger.error(`Cannot handle '${topic}' because deviceID of '${friendlyName}' cannot be found`);
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(json).forEach((key) => {
|
||||
// Find converter for this key.
|
||||
const converter = mqtt2zigbee[key];
|
||||
@@ -199,11 +184,7 @@ class Controller {
|
||||
const callback = (error) => {
|
||||
// Devices do not report when they go off, this ensures state (on/off) is always in sync.
|
||||
if (!error && key === 'state') {
|
||||
this.mqtt.publish(
|
||||
friendlyName,
|
||||
JSON.stringify({state: json[key]}),
|
||||
settings.getDevice(deviceID).retain,
|
||||
);
|
||||
this.mqttPublishDeviceState(deviceID, {state: json[key]})
|
||||
}
|
||||
};
|
||||
|
||||
@@ -211,26 +192,16 @@ class Controller {
|
||||
});
|
||||
}
|
||||
|
||||
homeassistantDiscover(configurations, deviceID, friendlyName) {
|
||||
configurations.forEach((config) => {
|
||||
const topic = `${config.type}/${deviceID}/${config.object_id}/config`;
|
||||
const payload = config.discovery_payload;
|
||||
payload.state_topic = `${settings.get().mqtt.base_topic}/${friendlyName}`;
|
||||
payload.availability_topic = `${settings.get().mqtt.base_topic}/bridge/state`;
|
||||
mqttPublishDeviceState(deviceID, payload) {
|
||||
if (this.stateCache[deviceID]) {
|
||||
payload = {...this.stateCache[deviceID], ...payload};
|
||||
}
|
||||
|
||||
// Set unique names in cases this device produces multiple entities in homeassistant.
|
||||
if (configurations.length > 1) {
|
||||
payload.name = `${friendlyName}_${config.object_id}`;
|
||||
} else {
|
||||
payload.name = friendlyName;
|
||||
}
|
||||
// Update state cache with new state.
|
||||
this.stateCache[deviceID] = payload;
|
||||
|
||||
if (payload.command_topic) {
|
||||
payload.command_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/set`;
|
||||
}
|
||||
|
||||
this.mqtt.publish(topic, JSON.stringify(payload), true, null, 'homeassistant');
|
||||
});
|
||||
const deviceSettings = settings.getDevice(deviceID);
|
||||
this.mqtt.publish(deviceSettings.friendly_name, JSON.stringify(payload), deviceSettings.retain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ const parsers = [
|
||||
devices: ['WXKG11LM'],
|
||||
cid: 'genOnOff',
|
||||
type: 'attReport',
|
||||
convert: (msg, publish) => {
|
||||
convert: (msg) => {
|
||||
const data = msg.data.data;
|
||||
let clicks;
|
||||
|
||||
@@ -178,7 +178,7 @@ const parsers = [
|
||||
devices: ['WXKG03LM'],
|
||||
cid: 'genOnOff',
|
||||
type: 'attReport',
|
||||
convert: (msg) => {return {click: 'single'}}
|
||||
convert: () => {return {click: 'single'}}
|
||||
},
|
||||
{
|
||||
devices: ['SJCGQ11LM'],
|
||||
@@ -246,7 +246,7 @@ const parsers = [
|
||||
cid: 'msPressureMeasurement',
|
||||
type: 'devChange',
|
||||
convert: () => null
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: ['ZNCZ02LM'],
|
||||
cid: 'genAnalogInput',
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const homeassistant = require('./homeassistant');
|
||||
const homeassistant = require('./homeassistant').configurations;
|
||||
|
||||
const devices = {
|
||||
// Xiaomi
|
||||
|
||||
+44
-18
@@ -1,4 +1,6 @@
|
||||
const homeassistant = {
|
||||
const settings = require('./util/settings');
|
||||
|
||||
const configurations = {
|
||||
// Binary sensor
|
||||
'binary_sensor_occupancy': {
|
||||
type: 'binary_sensor',
|
||||
@@ -104,7 +106,7 @@ const homeassistant = {
|
||||
xy: true,
|
||||
platform: 'mqtt_json',
|
||||
command_topic: true
|
||||
}
|
||||
}
|
||||
},
|
||||
'light_brightness_colortemp': {
|
||||
type: 'light',
|
||||
@@ -114,7 +116,7 @@ const homeassistant = {
|
||||
color_temp: true,
|
||||
platform: 'mqtt_json',
|
||||
command_topic: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Switch
|
||||
@@ -126,21 +128,45 @@ const homeassistant = {
|
||||
payload_on: 'ON',
|
||||
value_template: '{{ value_json.state }}',
|
||||
command_topic: true
|
||||
}
|
||||
},
|
||||
|
||||
// zigbee2mqtt
|
||||
'zigbee2mqtt_permit_join': {
|
||||
type: 'switch',
|
||||
object_id: 'switch',
|
||||
discovery_payload: {
|
||||
payload_off: 'OFF',
|
||||
payload_on: 'ON',
|
||||
value_template: '{{ value_json.state }}',
|
||||
command_topic: true,
|
||||
icon: 'mdi:router-wireless',
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = homeassistant;
|
||||
// A map of all discoverd devices
|
||||
const discovered = {};
|
||||
|
||||
function discover(deviceID, configs, mqtt) {
|
||||
// Check if already discoverd and check if there are configs.
|
||||
if (discovered[deviceID] || !configs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const friendlyName = settings.getDevice(deviceID).friendly_name;
|
||||
|
||||
configs.forEach((config) => {
|
||||
const topic = `${config.type}/${deviceID}/${config.object_id}/config`;
|
||||
const payload = config.discovery_payload;
|
||||
payload.state_topic = `${settings.get().mqtt.base_topic}/${friendlyName}`;
|
||||
payload.availability_topic = `${settings.get().mqtt.base_topic}/bridge/state`;
|
||||
|
||||
// Set unique names in cases this device produces multiple entities in homeassistant.
|
||||
if (configs.length > 1) {
|
||||
payload.name = `${friendlyName}_${config.object_id}`;
|
||||
} else {
|
||||
payload.name = friendlyName;
|
||||
}
|
||||
|
||||
if (payload.command_topic) {
|
||||
payload.command_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/set`;
|
||||
}
|
||||
|
||||
mqtt.publish(topic, JSON.stringify(payload), true, null, 'homeassistant');
|
||||
});
|
||||
|
||||
discovered[deviceID] = true;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
configurations: configurations,
|
||||
discover: (deviceID, configs, mqtt) => discover(deviceID, configs, mqtt),
|
||||
};
|
||||
+4
-4
@@ -2,7 +2,6 @@ const mqtt = require('mqtt');
|
||||
const logger = require('./util/logger');
|
||||
const settings = require('./util/settings');
|
||||
|
||||
|
||||
class MQTT {
|
||||
|
||||
constructor() {
|
||||
@@ -10,7 +9,7 @@ class MQTT {
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
}
|
||||
|
||||
connect(onMessage, callback) {
|
||||
connect(onMessage, subscriptions, callback) {
|
||||
const mqttSettings = settings.get().mqtt;
|
||||
logger.info(`Connecting to MQTT server at ${mqttSettings.server}`);
|
||||
|
||||
@@ -27,7 +26,7 @@ class MQTT {
|
||||
this.handleConnect();
|
||||
callback();
|
||||
});
|
||||
|
||||
|
||||
this.client.on('message', this.handleMessage);
|
||||
|
||||
// Set timer at interval to check if connected to MQTT server.
|
||||
@@ -39,6 +38,7 @@ class MQTT {
|
||||
}, interval);
|
||||
|
||||
this.onMessage = onMessage;
|
||||
this.subscriptions = subscriptions;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
@@ -54,7 +54,7 @@ class MQTT {
|
||||
handleConnect() {
|
||||
logger.info('Connected to MQTT server');
|
||||
this.publish('bridge/state', 'online', true);
|
||||
this.client.subscribe(`${settings.get().mqtt.base_topic}/+/set`);
|
||||
this.subscriptions.forEach((topic) => this.client.subscribe(topic));
|
||||
}
|
||||
|
||||
handleMessage(topic, message) {
|
||||
|
||||
@@ -13,7 +13,7 @@ function read() {
|
||||
|
||||
function addDevice(id) {
|
||||
if (!yawn.json.devices) {
|
||||
const first = "\n" +
|
||||
const first = "\n" +
|
||||
"# List of devices, automatically updates when new devices join, E.G.:\n" +
|
||||
"# '0x00158d0001d8e1d2':\n" +
|
||||
"# friendly_name: bedroom_switch # Friendly name to be used in MQTT topic\n" +
|
||||
|
||||
Reference in new issue
Block a user