Device Availability: Allow battery devices to report offline/online based on device.lastSeen (#2464)

* Remove execution bits for deviceReceive.js

* deviceAvailability.isPingable should return true if 'availability_lastseen_timeout' is set for device

* deviceAvailability.handleInterval should know how to handle lastseen_timeout setting

* Allow availability_lastseen_timeout for all devices

* Use a default lastseen timeout of 24h

* Updates

* Updates

* Updates

* 25 hours instead of 24.
This commit is contained in:
Jorge Schrauwen
2019-12-09 18:12:13 +01:00
committed by Koen Kanters
parent 6bd9e74e0c
commit bcf79a7e34
2 changed files with 79 additions and 42 deletions
+48 -31
View File
@@ -12,6 +12,8 @@ const forcedPingable = [
const toZigbeeCandidates = ['state', 'brightness', 'color', 'color_temp'];
const Hours25 = 1000 * 60 * 60 * 25;
/**
* This extensions pings devices to check if they are online.
*/
@@ -34,7 +36,7 @@ class DeviceAvailability extends BaseExtension {
});
}
isPingable(device) {
isAllowed(device) {
// Whitelist is not empty and device is in it, enable availability
if (this.whitelist.length > 0) {
return this.whitelist.includes(device.ieeeAddr);
@@ -45,29 +47,39 @@ class DeviceAvailability extends BaseExtension {
return false;
}
return true;
}
isPingable(device) {
// Device is on forcedPingable-list, enable availability
if (forcedPingable.find((d) => d.zigbeeModel.includes(device.modelID))) {
return true;
}
// Device is a mains powered router
const result = utils.isRouter(device) && !utils.isBatteryPowered(device);
return result;
}
getAllPingableDevices() {
return this.zigbee.getClients().filter((d) => this.isPingable(d));
}
onMQTTConnected() {
// As some devices are not checked for availability (e.g. battery powered devices)
// we mark all device as online by default.
this.zigbee.getClients().forEach((device) => this.publishAvailability(device, true));
for (const device of this.zigbee.getClients()) {
// Mark all devices as online on start
this.publishAvailability(device, true);
// Start timers for all devices
this.getAllPingableDevices().forEach((device) => this.setTimer(device));
if (this.isAllowed(device)) {
if (this.isPingable(device)) {
this.setTimerPingable(device);
} else {
this.timers[device.ieeeAddr] = setInterval(() => {
this.handleIntervalNotPingable(device);
}, utils.secondsToMilliseconds(300));
}
}
}
}
async handleInterval(device) {
async handleIntervalPingable(device) {
// When a device is already unavailable, log the ping failed on 'debug' instead of 'error'.
const entity = this.zigbee.resolveEntity(device.ieeeAddr);
if (!entity) {
@@ -85,17 +97,31 @@ class DeviceAvailability extends BaseExtension {
this.publishAvailability(device, false);
logger[level](`Failed to ping '${entity.name}'`);
} finally {
this.setTimer(device);
this.setTimerPingable(device);
}
}
setTimer(device) {
async handleIntervalNotPingable(device) {
const ago = Date.now() - device.lastSeen;
const entity = this.zigbee.resolveEntity(device.ieeeAddr);
if (!entity || !device.lastSeen) {
return;
}
logger.debug(`Non-pingable device '${entity.name}' was last seen '${ago / 1000}' seconds ago.`);
if (ago > Hours25) {
this.publishAvailability(device, false);
}
}
setTimerPingable(device) {
if (this.timers[device.ieeeAddr]) {
clearTimeout(this.timers[device.ieeeAddr]);
}
this.timers[device.ieeeAddr] = setTimeout(async () => {
await this.handleInterval(device);
await this.handleIntervalPingable(device);
}, utils.secondsToMilliseconds(this.availability_timeout));
}
@@ -151,37 +177,28 @@ class DeviceAvailability extends BaseExtension {
return;
}
if (this.isPingable(device)) {
// When a zigbee message from a device is received we know the device is still alive.
// => reset the timer.
this.setTimer(device);
if (this.isAllowed(device)) {
this.publishAvailability(data.device, true);
const online = this.state.hasOwnProperty(device.ieeeAddr) && this.state[device.ieeeAddr];
const offline = this.state.hasOwnProperty(device.ieeeAddr) && !this.state[device.ieeeAddr];
if (this.isPingable(device)) {
// When a zigbee message from a device is received we know the device is still alive.
// => reset the timer.
this.setTimerPingable(device);
if (!online && !offline) {
// A new device has been connected
this.publishAvailability(device, true);
} else if (offline) {
// When a message is received and the device is marked as offline, mark it online.
this.publishAvailability(device, true);
} else {
/* istanbul ignore else */
const online = this.state.hasOwnProperty(device.ieeeAddr) && this.state[device.ieeeAddr];
if (online && type === 'deviceAnnounce' && !utils.isIkeaTradfriDevice(device)) {
/**
* In case the device is powered off AND on within the availability timeout,
* zigbee2qmtt does not detect the device as offline (device is still marked online).
* When a device is turned on again the state could be out of sync.
* https://github.com/Koenkk/zigbee2mqtt/issues/1383#issuecomment-489412168
* endDeviceAnnce is typically send when a device comes online.
* deviceAnnounce is typically send when a device comes online.
*
* This isn't needed for TRADFRI devices as they already send the state themself.
*/
this.onReconnect(device);
}
}
} else if (type === 'deviceJoined') {
this.publishAvailability(data.device, true);
}
}
}