mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-28 05:24:29 +00:00
Refactor router polling & only poll Xiaomi routers, refactor soft reset timeout and disable by default. #274
This commit is contained in:
+13
-60
@@ -4,6 +4,8 @@ const State = require('./state');
|
||||
const logger = require('./util/logger');
|
||||
const settings = require('./util/settings');
|
||||
const ExtensionNetworkMap = require('./extension/networkMap');
|
||||
const ExtensionSoftReset = require('./extension/softReset');
|
||||
const ExtensionRouterPollXiaomi = require('./extension/routerPollXiaomi');
|
||||
const zigbeeShepherdConverters = require('zigbee-shepherd-converters');
|
||||
const homeassistant = require('./homeassistant');
|
||||
const objectAssignDeep = require('object-assign-deep');
|
||||
@@ -12,8 +14,6 @@ const mqttConfigRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/con
|
||||
const mqttDeviceRegex = new RegExp(`${settings.get().mqtt.base_topic}/[\\w\\s\\d.-]+/set`, 'g');
|
||||
const mqttDevicePrefixRegex = new RegExp(`${settings.get().mqtt.base_topic}/[\\w\\s\\d.-]+/[\\w\\s\\d.-]+/set`, 'g');
|
||||
|
||||
const pollInterval = 60 * 1000; // seconds * 1000.
|
||||
const softResetTimeout = 3600 * 1000; // seconds * 1000.
|
||||
|
||||
const allowedLogLevels = ['error', 'warn', 'info', 'debug'];
|
||||
|
||||
@@ -32,17 +32,19 @@ if (settings.get().homeassistant && !cacheState) {
|
||||
|
||||
class Controller {
|
||||
constructor() {
|
||||
this.zigbee = new Zigbee();
|
||||
this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this);
|
||||
this.handleMQTTMessage = this.handleMQTTMessage.bind(this);
|
||||
|
||||
this.zigbee = new Zigbee(this.handleZigbeeMessage);
|
||||
this.mqtt = new MQTT();
|
||||
this.state = new State();
|
||||
this.configured = [];
|
||||
this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this);
|
||||
this.handleMQTTMessage = this.handleMQTTMessage.bind(this);
|
||||
this.extensions = [];
|
||||
}
|
||||
|
||||
start() {
|
||||
this.startupLogVersion(() => {
|
||||
this.zigbee.start(this.handleZigbeeMessage, (error) => {
|
||||
this.zigbee.start((error) => {
|
||||
if (error) {
|
||||
logger.error('Failed to start', error);
|
||||
} else {
|
||||
@@ -63,10 +65,6 @@ class Controller {
|
||||
|
||||
this.zigbee.permitJoin(settings.get().permit_join);
|
||||
|
||||
// Start timers.
|
||||
this.pollTimer(true);
|
||||
this.softResetTimeout(true);
|
||||
|
||||
// Connect to MQTT broker
|
||||
const subscriptions = [
|
||||
`${settings.get().mqtt.base_topic}/+/set`,
|
||||
@@ -99,6 +97,8 @@ class Controller {
|
||||
// Initialize extensions.
|
||||
this.extensions = [
|
||||
new ExtensionNetworkMap(this.zigbee, this.mqtt, this.state),
|
||||
new ExtensionSoftReset(this.zigbee, this.mqtt, this.state),
|
||||
new ExtensionRouterPollXiaomi(this.zigbee, this.mqtt, this.state),
|
||||
];
|
||||
|
||||
// Resend all cached states.
|
||||
@@ -113,57 +113,10 @@ class Controller {
|
||||
});
|
||||
}
|
||||
|
||||
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.extensions.filter((e) => e.stop).forEach((e) => e.stop());
|
||||
this.state.save();
|
||||
this.mqtt.disconnect();
|
||||
this.pollTimer(false);
|
||||
this.softResetTimeout(false);
|
||||
this.zigbee.stop(callback);
|
||||
}
|
||||
|
||||
@@ -235,8 +188,8 @@ class Controller {
|
||||
}
|
||||
|
||||
handleZigbeeMessage(message) {
|
||||
// Zigbee message receieved, reset soft reset timeout.
|
||||
this.softResetTimeout(true);
|
||||
// Call extensions.
|
||||
this.extensions.filter((e) => e.handleZigbeeMessage).forEach((e) => e.handleZigbeeMessage(message));
|
||||
|
||||
// Log the message.
|
||||
let logMessage = `Recieved zigbee message of type '${message.type}' ` +
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
const utils = require('../util/utils');
|
||||
const interval = utils.secondsToMilliseconds(60);
|
||||
|
||||
/**
|
||||
* This extensions polls Xiaomi Zigbee routers to keep them awake.
|
||||
*/
|
||||
class RouterPollXiaomi {
|
||||
constructor(zigbee, mqtt, state) {
|
||||
this.zigbee = zigbee;
|
||||
this.timer = null;
|
||||
|
||||
this.startTimer();
|
||||
}
|
||||
|
||||
startTimer() {
|
||||
this.clearTimer();
|
||||
this.timer = setInterval(() => this.handleInterval(), interval);
|
||||
}
|
||||
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.clearTimer();
|
||||
}
|
||||
|
||||
handleInterval() {
|
||||
this.zigbee.getAllClients()
|
||||
.filter((d) => utils.isXiaomiDevice(d)) // Filter Xiaomi devices
|
||||
.filter((d) => d.type === 'Router') // Filter routers
|
||||
.filter((d) => d.powerSource && d.powerSource !== 'Battery') // Remove battery powered devices
|
||||
.forEach((d) => this.zigbee.ping(d.ieeeAddr)); // Ping devices.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RouterPollXiaomi;
|
||||
@@ -0,0 +1,66 @@
|
||||
const settings = require('../util/settings');
|
||||
const logger = require('../util/logger');
|
||||
const utils = require('../util/utils');
|
||||
|
||||
/**
|
||||
* This extensions soft resets the ZNP after a certain timeout.
|
||||
*/
|
||||
class SoftReset {
|
||||
constructor(zigbee, mqtt, state) {
|
||||
this.zigbee = zigbee;
|
||||
this.timer = null;
|
||||
this.timeout = utils.secondsToMilliseconds(settings.get().advanced.soft_reset_timeout);
|
||||
|
||||
if (this.timeout === 0) {
|
||||
logger.debug(`Soft reset timeout disabled`);
|
||||
} else {
|
||||
logger.debug(`Soft reset timeout set to ${utils.millisecondsToSeconds(this.timeout)} seconds`);
|
||||
}
|
||||
|
||||
this.resetTimer();
|
||||
}
|
||||
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
resetTimer() {
|
||||
if (this.timeout === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearTimer();
|
||||
this.timer = setTimeout(() => this.handleTimeout(), this.timeout);
|
||||
}
|
||||
|
||||
handleTimeout() {
|
||||
logger.warn('Soft reset timeout triggered');
|
||||
|
||||
this.zigbee.softReset((error) => {
|
||||
if (error) {
|
||||
logger.warn('Soft reset failed, trying stop/start');
|
||||
this.zigbee.stop((error) => {
|
||||
logger.warn('Zigbee stopped');
|
||||
this.zigbee.start((error) => {
|
||||
if (error) {
|
||||
logger.error('Failed to restart!');
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logger.warn('Soft resetted ZNP due to timeout');
|
||||
}
|
||||
|
||||
this.resetTimer();
|
||||
});
|
||||
}
|
||||
|
||||
handleZigbeeMessage(message) {
|
||||
this.resetTimer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SoftReset;
|
||||
@@ -10,6 +10,7 @@ const defaults = {
|
||||
advanced: {
|
||||
log_directory: path.join(data.getPath(), 'log', '%TIMESTAMP%'),
|
||||
log_level: process.env.DEBUG ? 'debug' : 'info',
|
||||
soft_reset_timeout: 0,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID.
|
||||
const xiaomiManufacturerID = [4151, 4447];
|
||||
|
||||
module.exports = {
|
||||
millisecondsToSeconds: (milliseconds) => milliseconds / 1000,
|
||||
secondsToMilliseconds: (seconds) => seconds * 1000,
|
||||
isXiaomiDevice: (device) => xiaomiManufacturerID.includes(device.manufId),
|
||||
};
|
||||
+14
-13
@@ -3,6 +3,7 @@ const logger = require('./util/logger');
|
||||
const settings = require('./util/settings');
|
||||
const data = require('./util/data');
|
||||
const zclPacket = require('zcl-packet');
|
||||
const utils = require('./util/utils');
|
||||
|
||||
const advancedSettings = settings.get().advanced;
|
||||
|
||||
@@ -21,13 +22,14 @@ const shepherdSettings = {
|
||||
logger.debug(`Using zigbee-shepherd with settings: '${JSON.stringify(shepherdSettings)}'`);
|
||||
|
||||
class Zigbee {
|
||||
constructor() {
|
||||
constructor(onMessage) {
|
||||
this.onMessage = onMessage;
|
||||
this.handleReady = this.handleReady.bind(this);
|
||||
this.handleMessage = this.handleMessage.bind(this);
|
||||
this.handleError = this.handleError.bind(this);
|
||||
}
|
||||
|
||||
start(onMessage, callback) {
|
||||
start(callback) {
|
||||
logger.info(`Starting zigbee-shepherd`);
|
||||
|
||||
this.shepherd = new ZShepherd(settings.get().serial.port, shepherdSettings);
|
||||
@@ -62,8 +64,6 @@ class Zigbee {
|
||||
this.shepherd.on('ready', this.handleReady);
|
||||
this.shepherd.on('ind', this.handleMessage);
|
||||
this.shepherd.on('error', this.handleError);
|
||||
|
||||
this.onMessage = onMessage;
|
||||
}
|
||||
|
||||
_logStartupInfo() {
|
||||
@@ -84,12 +84,11 @@ 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
|
||||
// Set all Xiaomi devices to be online, so shepherd won't try
|
||||
// to query info from devices (which would fail because they go to sleep).
|
||||
const devices = this.getAllClients();
|
||||
devices.forEach((d) => {
|
||||
if ((d.manufId === 4151) || (d.manufId === 4447)) {
|
||||
if (utils.isXiaomiDevice(d)) {
|
||||
const device = this.shepherd.find(d.ieeeAddr, 1);
|
||||
if (device) {
|
||||
device.getDevice().update({
|
||||
@@ -100,7 +99,7 @@ class Zigbee {
|
||||
}
|
||||
});
|
||||
|
||||
// Check if we have to turn of the led
|
||||
// Check if we have to turn off the led
|
||||
if (settings.get().serial.disable_led) {
|
||||
this.shepherd.controller.request('UTIL', 'ledControl', {ledid: 3, mode: 0});
|
||||
}
|
||||
@@ -190,14 +189,16 @@ class Zigbee {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`Zigbee publish to '${deviceID}', ${cid} - ${cmd}
|
||||
- ${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}`);
|
||||
logger.info(
|
||||
`Zigbee publish to '${deviceID}', ${cid} - ${cmd} - ` +
|
||||
`${JSON.stringify(zclData)} - ${JSON.stringify(cfg)} - ${ep}`
|
||||
);
|
||||
|
||||
const callback_ = (error) => {
|
||||
if (error) {
|
||||
logger.error(
|
||||
`Zigbee publish to '${deviceID}', ${cid} - ${cmd} - ${JSON.stringify(zclData)}
|
||||
- ${JSON.stringify(cfg)} - ${ep} ` +
|
||||
`Zigbee publish to '${deviceID}', ${cid} - ${cmd} - ${JSON.stringify(zclData)} ` +
|
||||
`- ${JSON.stringify(cfg)} - ${ep} ` +
|
||||
`failed with error ${error}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user