mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-29 07:08:56 +00:00
+58
-1
@@ -6,13 +6,15 @@ const deviceMapping = require('./devices');
|
||||
const zigbee2mqtt = require('./converters/zigbee2mqtt');
|
||||
const mqtt2zigbee = require('./converters/mqtt2zigbee');
|
||||
const homeassistant = require('./homeassistant');
|
||||
const debug = require('debug')('zigbee2mqtt');
|
||||
const debug = require('debug')('zigbee2mqtt:controller');
|
||||
|
||||
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');
|
||||
const mqttDevicePrefixRegex = new RegExp(`${settings.get().mqtt.base_topic}/\\w+/\\w+/set`, 'g');
|
||||
|
||||
const issueLink = 'https://github.com/Koenkk/zigbee2mqtt/issues';
|
||||
const pollInterval = 60 * 1000; // seconds * 1000.
|
||||
const softResetTimeout = 3600 * 1000; // seconds * 1000.
|
||||
|
||||
class Controller {
|
||||
constructor() {
|
||||
@@ -62,10 +64,62 @@ class Controller {
|
||||
logger.warn('Set `permit_join` to `false` once you joined all devices.');
|
||||
this.zigbee.permitJoin(true);
|
||||
}
|
||||
|
||||
// Start timers.
|
||||
this.pollTimer(true);
|
||||
this.softResetTimeout(true);
|
||||
}
|
||||
|
||||
softResetTimeout(start) {
|
||||
if (this._softResetTimer) {
|
||||
clearTimeout(this._softResetTimer);
|
||||
this._softResetTimer = null;
|
||||
}
|
||||
|
||||
if (start) {
|
||||
this._softResetTimer = setTimeout(() => {
|
||||
this.zigbee.softReset((error) => {
|
||||
if (error) {
|
||||
logger.warn('Soft reset error', error);
|
||||
this.zigbee.stop((error) => {
|
||||
logger.warn('Zigbee stopped');
|
||||
this.zigbee.start(this.handleZigbeeMessage, (error) => {
|
||||
if (error) {
|
||||
logger.error('Failed to restart!');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logger.warn('Soft resetted zigbee');
|
||||
}
|
||||
|
||||
this.softResetTimeout(true);
|
||||
});
|
||||
}, softResetTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
pollTimer(start) {
|
||||
// Some routers need polling to prevent them from sleeping.
|
||||
if (start && !this._pollTimer) {
|
||||
this._pollTimer = setInterval(() => {
|
||||
const devices = this.zigbee.getAllClients().filter((d) => {
|
||||
const power = d.powerSource ? d.powerSource.toLowerCase().split(' ')[0] : 'unknown';
|
||||
return power !== 'battery' && power !== 'unknown' && d.type === 'Router';
|
||||
});
|
||||
|
||||
devices.forEach((d) => this.zigbee.ping(d.ieeeAddr));
|
||||
}, pollInterval);
|
||||
} else if (!start && this._pollTimer) {
|
||||
clearTimeout(this._pollTimer);
|
||||
this._pollTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
stop(callback) {
|
||||
this.mqtt.disconnect();
|
||||
this.pollTimer(false);
|
||||
this.softResetTimeout(false);
|
||||
this.zigbee.stop(callback);
|
||||
}
|
||||
|
||||
@@ -86,6 +140,9 @@ class Controller {
|
||||
}
|
||||
|
||||
handleZigbeeMessage(message) {
|
||||
// Zigbee message receieved, reset soft reset timeout.
|
||||
this.softResetTimeout(true);
|
||||
|
||||
debug('Recieved zigbee message with data', message.data);
|
||||
|
||||
if (message.type == 'devInterview') {
|
||||
|
||||
@@ -312,6 +312,23 @@ const parsers = [
|
||||
return {power: precisionRound(msg.data.data['presentValue'], 2)};
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: ['ZNCZ02LM'],
|
||||
cid: 'genBasic',
|
||||
type: 'attReport',
|
||||
convert: (msg) => {
|
||||
if (msg.data.data['65281']) {
|
||||
const data = msg.data.data['65281'];
|
||||
return {
|
||||
state: data['100'] === 1 ? 'ON' : 'OFF',
|
||||
power: precisionRound(data['152'], 2),
|
||||
voltage: precisionRound(data['150'] * 0.1, 1),
|
||||
consumption: precisionRound(data['149'], 2),
|
||||
temperature: precisionRound(data['3'], 2),
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: ['QBKG04LM'],
|
||||
cid: 'genOnOff',
|
||||
@@ -343,6 +360,27 @@ const parsers = [
|
||||
return {smoke: msg.data.zoneStatus === 1};
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: ['CC2530.ROUTER'],
|
||||
cid: 'genOnOff',
|
||||
type: 'attReport',
|
||||
convert: (msg) => {
|
||||
return {state: msg.data.data['onOff'] === 1};
|
||||
},
|
||||
},
|
||||
{
|
||||
devices: ['CC2530.ROUTER'],
|
||||
cid: 'genBinaryValue',
|
||||
type: 'attReport',
|
||||
convert: (msg) => {
|
||||
const data = msg.data.data;
|
||||
return {
|
||||
description: data['description'],
|
||||
type: data['inactiveText'],
|
||||
rssi: data['presentValue'],
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
// Ignore parsers (these message dont need parsing).
|
||||
{
|
||||
@@ -357,7 +395,7 @@ const parsers = [
|
||||
{
|
||||
devices: [
|
||||
'WXKG11LM', 'MCCGQ11LM', 'RTCGQ11LM', 'WSDCGQ11LM', 'SJCGQ11LM', 'MCCGQ01LM', 'RTCGQ01LM', 'WXKG01LM',
|
||||
'WSDCGQ01LM', 'JTYJ-GD-01LM/BW',
|
||||
'WSDCGQ01LM', 'JTYJ-GD-01LM/BW', 'ZNCZ02LM',
|
||||
],
|
||||
cid: 'genBasic',
|
||||
type: 'devChange',
|
||||
|
||||
@@ -166,6 +166,14 @@ const devices = {
|
||||
description: 'WeMo smart LED bulb',
|
||||
supports: 'on/off, brightness',
|
||||
},
|
||||
|
||||
// Texax Instruments
|
||||
'lumi.router': {
|
||||
model: 'CC2530.ROUTER',
|
||||
vendor: 'Texas Instruments',
|
||||
description: 'CC2530 router [link](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)',
|
||||
supports: 'state, description, type, rssi',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = devices;
|
||||
|
||||
@@ -46,6 +46,17 @@ const configurations = {
|
||||
json_attributes: ['battery'],
|
||||
},
|
||||
},
|
||||
'binary_sensor_router': {
|
||||
type: 'binary_sensor',
|
||||
object_id: 'router',
|
||||
discovery_payload: {
|
||||
payload_on: true,
|
||||
payload_off: false,
|
||||
value_template: '{{ value_json.state }}',
|
||||
device_class: 'connectivity',
|
||||
json_attributes: ['description', 'type', 'rssi'],
|
||||
},
|
||||
},
|
||||
|
||||
// Sensor
|
||||
'sensor_illuminance': {
|
||||
@@ -104,6 +115,7 @@ const configurations = {
|
||||
unit_of_measurement: 'Watt',
|
||||
icon: 'mdi:flash',
|
||||
value_template: '{{ value_json.power }}',
|
||||
json_attributes: ['voltage', 'temperature', 'consumption'],
|
||||
},
|
||||
},
|
||||
'sensor_action': {
|
||||
@@ -210,6 +222,7 @@ const mapping = {
|
||||
'7146060PH': [configurations.light_brightness_colortemp_xy],
|
||||
'F7C033': [configurations.light_brightness],
|
||||
'JTYJ-GD-01LM/BW': [configurations.binary_sensor_smoke],
|
||||
'CC2530.ROUTER': [configurations.binary_sensor_router],
|
||||
};
|
||||
|
||||
// A map of all discoverd devices
|
||||
|
||||
+28
-2
@@ -2,9 +2,13 @@ const ZShepherd = require('zigbee-shepherd');
|
||||
const logger = require('./util/logger');
|
||||
const settings = require('./util/settings');
|
||||
const data = require('./util/data');
|
||||
const debug = require('debug')('zigbee2mqtt:zigbee');
|
||||
|
||||
const shepherdSettings = {
|
||||
net: {panId: settings.get().advanced.pan_id},
|
||||
net: {
|
||||
panId: settings.get().advanced.pan_id,
|
||||
channelList: [settings.get().advanced.hasOwnProperty('channel') ? settings.get().advanced.channel : 11],
|
||||
},
|
||||
dbPath: data.joinPath('database.db'),
|
||||
};
|
||||
|
||||
@@ -12,6 +16,7 @@ class Zigbee {
|
||||
constructor() {
|
||||
this.handleReady = this.handleReady.bind(this);
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
this.handleError = this.handleError.bind(this);
|
||||
}
|
||||
|
||||
start(onMessage, callback) {
|
||||
@@ -32,10 +37,15 @@ class Zigbee {
|
||||
// Register callbacks.
|
||||
this.shepherd.on('ready', this.handleReady);
|
||||
this.shepherd.on('ind', this.handleMessage);
|
||||
this.shepherd.on('error', this.handleError);
|
||||
|
||||
this.onMessage = onMessage;
|
||||
}
|
||||
|
||||
softReset(callback) {
|
||||
this.shepherd.reset('soft', callback);
|
||||
}
|
||||
|
||||
stop(callback) {
|
||||
this.shepherd.stop((error) => {
|
||||
logger.info('zigbee-shepherd stopped');
|
||||
@@ -46,9 +56,10 @@ class Zigbee {
|
||||
handleReady() {
|
||||
// Set all Xiaomi devices (manufId === 4151) to be online, so shepherd won't try
|
||||
// to query info from devices (which would fail because they go tosleep).
|
||||
// Xiaomi lumi.plug has manufId === 4447 and can be in the sleep mode too
|
||||
const devices = this.getAllClients();
|
||||
devices.forEach((device) => {
|
||||
if (device.manufId === 4151) {
|
||||
if ((device.manufId === 4151) || (device.manufId === 4447)) {
|
||||
this.shepherd.find(device.ieeeAddr, 1).getDevice().update({
|
||||
status: 'online',
|
||||
joinTime: Math.floor(Date.now() / 1000),
|
||||
@@ -59,6 +70,11 @@ class Zigbee {
|
||||
logger.info('zigbee-shepherd ready');
|
||||
}
|
||||
|
||||
handleError(message) {
|
||||
// This event may appear if zigbee-shepherd cannot decode bad packets (invalid checksum).
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
permitJoin(permit) {
|
||||
if (permit) {
|
||||
logger.info('Zigbee: allowing new devices to join.');
|
||||
@@ -77,6 +93,16 @@ class Zigbee {
|
||||
return this.shepherd.list().filter((device) => device.type !== 'Coordinator');
|
||||
}
|
||||
|
||||
ping(deviceID) {
|
||||
const device = this.shepherd._findDevByAddr(deviceID);
|
||||
|
||||
if (device) {
|
||||
// Note: checkOnline has the callback argument but does not call callback
|
||||
debug(`Check online ${deviceID}`);
|
||||
this.shepherd.controller.checkOnline(device);
|
||||
}
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
if (this.onMessage) {
|
||||
this.onMessage(message);
|
||||
|
||||
Reference in New Issue
Block a user