mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-22 18:49:59 +00:00
Refactor controller.js.
This commit is contained in:
+59
-91
@@ -6,46 +6,37 @@ 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';
|
||||
|
||||
function getTimestamp() {
|
||||
var d = new Date();
|
||||
return d.getTime();
|
||||
}
|
||||
const pollInterval = 60 * 1000; // seconds * 1000.
|
||||
const softResetTimeout = 3600 * 1000; // seconds * 1000.
|
||||
|
||||
class Controller {
|
||||
constructor() {
|
||||
this.zigbee = new Zigbee();
|
||||
this.mqtt = new MQTT();
|
||||
this.stateCache = {};
|
||||
|
||||
this.stateCache = {}; // Caches messages from devices.
|
||||
this.resetTimer = null; // After 1 hour of no message, reset CC2531 timer.
|
||||
|
||||
this.handleZigbeeMessage = this.handleZigbeeMessage.bind(this);
|
||||
this.handleMQTTMessage = this.handleMQTTMessage.bind(this);
|
||||
|
||||
this.checkOnlineTimer = null;
|
||||
this.lastDeviceActivity = {}; // timestamps of last data/activity
|
||||
this.lastControllerActivity = 0;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.zigbee.start(this.handleZigbeeMessage, (error) => {
|
||||
this.lastDeviceActivity = {};
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to start');
|
||||
} else {
|
||||
// Log zigbee clients on startup.
|
||||
const devices = this.zigbee.getAllClients();
|
||||
logger.info(`Currently ${devices.length} devices are joined:`);
|
||||
devices.forEach((device) => {
|
||||
logger.info(this.getDeviceStartupLogMessage(device))
|
||||
this.setLastDeviceActivity(device.ieeeAddr);
|
||||
});
|
||||
devices.forEach((device) => logger.info(this.getDeviceStartupLogMessage(device)));
|
||||
|
||||
// Connect to MQTT broker
|
||||
const subscriptions = [
|
||||
@@ -77,19 +68,58 @@ class Controller {
|
||||
this.zigbee.permitJoin(true);
|
||||
}
|
||||
|
||||
// Set timer at interval to check online status of Zigbee routers.
|
||||
// For example, it prevents Xiaomi routers to go to a deep sleep mode
|
||||
const interval = 1 * 1000; // seconds * 1000.
|
||||
this.checkOnlineTimer = setTimeout(this.zigbeeCheckOnline.bind(this), interval);
|
||||
this.lastControllerActivity = getTimestamp();
|
||||
// Start poll timer.
|
||||
this.pollTimer(true);
|
||||
this.resetSoftResetTimeout();
|
||||
}
|
||||
|
||||
resetSoftResetTimeout() {
|
||||
if (this._softResetTimer) {
|
||||
clearTimeout(this._softResetTimer);
|
||||
this._softResetTimer = null;
|
||||
}
|
||||
|
||||
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.resetSoftResetTimeout();
|
||||
});
|
||||
}, 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();
|
||||
if(this.checkOnlineTimer) {
|
||||
clearTimeout(this.checkOnlineTimer);
|
||||
this.checkOnlineTimer = null;
|
||||
}
|
||||
this.pollTimer(false);
|
||||
this.zigbee.stop(callback);
|
||||
}
|
||||
|
||||
@@ -110,6 +140,9 @@ class Controller {
|
||||
}
|
||||
|
||||
handleZigbeeMessage(message) {
|
||||
// Zigbee message receieved, reset soft reset timeout.
|
||||
this.resetSoftResetTimeout();
|
||||
|
||||
debug('Recieved zigbee message with data', message.data);
|
||||
|
||||
if (message.type == 'devInterview') {
|
||||
@@ -130,8 +163,6 @@ class Controller {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setLastDeviceActivity(device.ieeeAddr);
|
||||
|
||||
// Check if this is a new device.
|
||||
if (!settings.getDevice(device.ieeeAddr)) {
|
||||
logger.info(`New device with address ${device.ieeeAddr} connected!`);
|
||||
@@ -283,69 +314,6 @@ class Controller {
|
||||
|
||||
this.mqtt.publish(deviceSettings.friendly_name, JSON.stringify(payload), options);
|
||||
}
|
||||
|
||||
setLastDeviceActivity(ieeeAddr) {
|
||||
this.lastDeviceActivity[ieeeAddr] = getTimestamp();
|
||||
}
|
||||
|
||||
zigbeeCheckOnline() {
|
||||
var dt = getTimestamp();
|
||||
|
||||
//TO-DO: may be allow to configure the timeout
|
||||
if ((dt - this.lastControllerActivity) > 3600000) {
|
||||
// no data received in 1 hour.
|
||||
// This problem may occur sometimes with CC2531 (USB devices can be pluged/unpluged by a PnP system)
|
||||
// try to restart and self-recovery
|
||||
this.checkOnlineTimer = null;
|
||||
this.lastControllerActivity = dt;
|
||||
this.lastDeviceActivity = {};
|
||||
logger.warn('Soft restart');
|
||||
this.zigbee.shepherd.reset('soft', (err) => {
|
||||
if(err){
|
||||
logger.warn('Soft reset error:', err);
|
||||
this.zigbee.stop( (err) => {
|
||||
logger.warn('Stop:', err);
|
||||
this.zigbee.start(this.zigbee.onMessage, () => {});
|
||||
});
|
||||
}
|
||||
else{
|
||||
this.checkOnlineTimer = setTimeout(this.zigbeeCheckOnline.bind(this), 1000);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var device, devInfo, devType, power, dev_desc;
|
||||
for (device in this.lastDeviceActivity) {
|
||||
if ((dt - this.lastDeviceActivity[device]) > 60000) {
|
||||
this.lastDeviceActivity[device] = dt;
|
||||
|
||||
devInfo = this.zigbee.shepherd._findDevByAddr(device);
|
||||
if (devInfo) {
|
||||
// battery powered endpoint devices are in the sleep mode most time
|
||||
if(devInfo.powerSource){
|
||||
power = devInfo.powerSource.toLowerCase().split(' ')[0];
|
||||
}
|
||||
else{
|
||||
power = 'unknown';
|
||||
}
|
||||
devType = devInfo.type.toLowerCase();
|
||||
if (
|
||||
((power !== 'battery') && (power !== 'unknown')) ||
|
||||
(devType === 'router')
|
||||
) {
|
||||
dev_desc = this.getDeviceStartupLogMessage(devInfo);
|
||||
logger.info('Data timeout for device:', dev_desc, ' Checking online status.');
|
||||
// note: checkOnline has the callback argument but does not call callback
|
||||
this.zigbee.shepherd.controller.checkOnline(devInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.checkOnlineTimer = setTimeout(this.zigbeeCheckOnline.bind(this), 1000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controller;
|
||||
|
||||
@@ -2,6 +2,7 @@ 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: {
|
||||
@@ -41,6 +42,10 @@ class Zigbee {
|
||||
this.onMessage = onMessage;
|
||||
}
|
||||
|
||||
softReset(callback) {
|
||||
this.shepherd.reset('soft', callback);
|
||||
}
|
||||
|
||||
stop(callback) {
|
||||
this.shepherd.stop((error) => {
|
||||
logger.info('zigbee-shepherd stopped');
|
||||
@@ -88,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