Refactor controller.js.

This commit is contained in:
Koenkk
2018-05-21 11:49:02 +02:00
parent f58b0fa4f2
commit f886259f92
2 changed files with 74 additions and 91 deletions
+59 -91
View File
@@ -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;
+15
View File
@@ -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);