Refactor & remove home assistant pair switch discovery.

This commit is contained in:
Koen Kanters committed 2018-04-25 20:54:41 +02:00
1 parent d20012e4d8
commit a13080f399
6 files changed
+101 -104

No files matched your search

+48 -77
View File
@@ -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);
}
}
+3 -3
View File
@@ -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
View File
@@ -1,4 +1,4 @@
const homeassistant = require('./homeassistant');
const homeassistant = require('./homeassistant').configurations;
const devices = {
// Xiaomi
+44 -18
View File
@@ -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
View File
@@ -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) {
+1 -1
View File
@@ -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" +