mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-07-03 10:31:37 +00:00
Add device availability functionality for HASS based on router devices ping and attribute reporting also available on battery-powered devices (#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.
This commit is contained in:
committed by
Koen Kanters
parent
2a197a4ba8
commit
afeed4f372
@@ -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() {
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
@@ -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}`;
|
||||
@@ -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}/`;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
+3
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -2180,7 +2180,7 @@
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
|
||||
"resolved": "http://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
|
||||
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s="
|
||||
},
|
||||
"nan": {
|
||||
@@ -4795,8 +4795,8 @@
|
||||
}
|
||||
},
|
||||
"zigbee-shepherd": {
|
||||
"version": "git+https://github.com/Koenkk/zigbee-shepherd.git#8d22cb5e0167a9f2d754efc8b228007fcd403441",
|
||||
"from": "git+https://github.com/Koenkk/zigbee-shepherd.git#8d22cb5e0167a9f2d754efc8b228007fcd403441",
|
||||
"version": "git+https://github.com/Koenkk/zigbee-shepherd.git#bc2445dc0bb7a2a1d5b4a461c231e28d07f517e7",
|
||||
"from": "git+https://github.com/Koenkk/zigbee-shepherd.git#bc2445dc0bb7a2a1d5b4a461c231e28d07f517e7",
|
||||
"requires": {
|
||||
"areq": "^0.2.0",
|
||||
"busyman": "^0.3.0",
|
||||
|
||||
+1
-1
@@ -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": "*"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user