Skip to content

Commit

Permalink
Add device availability functionality for HASS based on router device…
Browse files Browse the repository at this point in the history
…s ping and attribute reporting also available on battery-powered devices (Koenkk#761)

* Discovery on HASS restart and last_message attribute added

- On restarting Home Assistant, resending device discovery information
- Add timestamp on receiving message from Zigbee

* Add option: add_timestamp in settings

* typo

* Update homeassistant.js

* Update homeassistant.js

* Update homeassistant.js

* Update controller.js

* Update zigbee.js

* Add files via upload

* Update zigbee.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update homeassistant.js

* Update deviceAvailabilityHandler.js

* Update deviceAvailabilityHandler.js

* Update homeassistant.js

* Fix checkonline callback.

* Refactor.

* Refactor.
  • Loading branch information
ugrug authored and Koenkk committed Dec 29, 2018
1 parent 3a56d80 commit 6524b7b
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 8 deletions.
7 changes: 7 additions & 0 deletions lib/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const ExtensionDeviceReceive = require('./extension/deviceReceive');
const ExtensionMarkOnlineXiaomi = require('./extension/markOnlineXiaomi');
const ExtensionBridgeConfig = require('./extension/bridgeConfig');
const ExtensionGroups = require('./extension/groups');
const DeviceAvailability = require('./extension/deviceAvailability');

class Controller {
constructor() {
Expand Down Expand Up @@ -53,6 +54,12 @@ class Controller {
this.zigbee, this.mqtt, this.state, this.publishDeviceState
));
}

if (settings.get().experimental.availablility_timeout) {
this.extensions.push(new DeviceAvailability(
this.zigbee, this.mqtt, this.state, this.publishDeviceState
));
}
}

onMQTTConnected() {
Expand Down
111 changes: 111 additions & 0 deletions lib/extension/deviceAvailability.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const logger = require('../util/logger');
const settings = require('../util/settings');
const utils = require('../util/utils');
const Queue = require('queue');

/**
* This extensions set availablity based on optionally polling router devices
* and optionally check device publish with attribute reporting
*/
class DeviceAvailabilityHandler {
constructor(zigbee, mqtt, state, publishDeviceState) {
this.zigbee = zigbee;
this.mqtt = mqtt;
this.availablility_timeout = settings.get().experimental.availablility_timeout;
this.timers = {};
this.pending = [];

/**
* Setup command queue.
* The command queue ensures that only 1 command is executed at a time.
* This is to avoid DDoSiNg of the coordinator.
*/
this.queue = new Queue();
this.queue.concurrency = 1;
this.queue.autostart = true;
}

getAllPingableDevices() {
return this.zigbee.getAllClients()
.filter((d) => d.type === 'Router' && (d.powerSource && d.powerSource !== 'Battery'));
}

onMQTTConnected() {
// As some devices are not checked for availability (e.g. battery powered devices)
// we mark all device as online by default.
this.zigbee.getDevices()
.filter((d) => d.type !== 'Coordinator')
.forEach((device) => this.publishAvailability(device.ieeeAddr, true));

// Start timers for all devices
this.getAllPingableDevices().forEach((device) => this.setTimer(device.ieeeAddr));
}

handleInterval(ieeeAddr) {
// Check if a job is already pending.
// This avoids overflowing of the queue in case the queue is not able to catch-up with the jobs being added.
if (this.pending.includes(ieeeAddr)) {
logger.debug(`Skipping ping for ${ieeeAddr} becuase job is already in queue`);
return;
}

this.pending.push(ieeeAddr);

this.queue.push((queueCallback) => {
this.zigbee.ping(ieeeAddr, (error) => {
if (error) {
logger.debug(`Failed to ping ${ieeeAddr}`);
} else {
logger.debug(`Sucesfully pinged ${ieeeAddr}`);
}

this.publishAvailability(ieeeAddr, !error);

// Remove from pending jobs.
const index = this.pending.indexOf(ieeeAddr);
if (index !== -1) {
this.pending.splice(index, 1);
}

this.setTimer(ieeeAddr);
queueCallback();
});
});
}

setTimer(ieeeAddr) {
if (this.timers[ieeeAddr]) {
clearTimeout(this.timers[ieeeAddr]);
}

this.timers[ieeeAddr] = setTimeout(() => {
this.handleInterval(ieeeAddr);
}, utils.secondsToMilliseconds(this.availablility_timeout));
}

stop() {
this.queue.stop();

this.zigbee.getDevices()
.filter((d) => d.type !== 'Coordinator')
.forEach((device) => this.publishAvailability(device.ieeeAddr, false));
}

publishAvailability(ieeeAddr, available) {
const deviceSettings = settings.getDevice(ieeeAddr);
const name = deviceSettings ? deviceSettings.friendly_name : ieeeAddr;
const topic = `${name}/availablility`;
const payload = available ? 'online' : 'offline';
this.mqtt.publish(topic, payload, {retain: true, qos: 0});
}

onZigbeeMessage(message, device, mappedDevice) {
// When a zigbee message from a device is received we know the device is still alive.
// => reset the timer.
if (device) {
this.setTimer(device.ieeeAddr);
}
}
}

module.exports = DeviceAvailabilityHandler;
10 changes: 9 additions & 1 deletion lib/extension/homeassistant.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,6 @@ class HomeAssistant {
const topic = `${config.type}/${ieeeAddr}/${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) name
payload.name = `${friendlyName}_${config.object_id}`;
Expand All @@ -517,6 +516,15 @@ class HomeAssistant {
manufacturer: mappedModel.vendor,
};

// Set availablility payload
// When using experimental availablility_timeout each device has it's own availablility topic.
// If not, use the availablility topic of zigbee2mqtt.
if (settings.get().experimental.availablility_timeout) {
payload.availability_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/availablility`;
} else {
payload.availability_topic = `${settings.get().mqtt.base_topic}/bridge/state`;
}

if (payload.command_topic) {
payload.command_topic = `${settings.get().mqtt.base_topic}/${friendlyName}/`;

Expand Down
4 changes: 4 additions & 0 deletions lib/util/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const defaults = {
*/
network_key: [1, 3, 5, 7, 9, 11, 13, 15, 0, 2, 4, 6, 8, 10, 12, 13],
},
experimental: {
// Availability timeout in seconds, disabled by default.
availablility_timeout: 0,
},
};

let settings = read();
Expand Down
6 changes: 3 additions & 3 deletions lib/zigbee.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,18 @@ class Zigbee {
}
}

ping(deviceID) {
ping(deviceID, callback) {
let friendlyName = 'unknown';
const device = this.shepherd._findDevByAddr(deviceID);
const ieeeAddr = device.ieeeAddr;

if (settings.getDevice(ieeeAddr)) {
friendlyName = settings.getDevice(ieeeAddr).friendly_name;
}

if (device) {
// Note: checkOnline has the callback argument but does not call callback
logger.debug(`Check online ${friendlyName} ${deviceID}`);
this.shepherd.controller.checkOnline(device);
this.shepherd.controller.checkOnline(device, callback);
}
}

Expand Down
6 changes: 3 additions & 3 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"semver": "*",
"winston": "2.4.2",
"ziee": "*",
"zigbee-shepherd": "git+https://github.com/Koenkk/zigbee-shepherd.git#8d22cb5e0167a9f2d754efc8b228007fcd403441",
"zigbee-shepherd": "git+https://github.com/Koenkk/zigbee-shepherd.git#bc2445dc0bb7a2a1d5b4a461c231e28d07f517e7",
"zigbee-shepherd-converters": "7.0.7",
"zive": "*"
},
Expand Down

0 comments on commit 6524b7b

Please sign in to comment.