Add availability_mode for Home Assistant and publish availability only when its enabled for that device. #6281

This commit is contained in:
Koen Kanters
2021-08-23 19:24:10 +02:00
parent b18373b669
commit 9129f95c39
6 changed files with 53 additions and 39 deletions
+5 -6
View File
@@ -88,13 +88,12 @@ class Controller {
this.extensions.push(new ExtensionSoftReset(...args));
}
if (settings.get().advanced.availability_timeout || settings.get().availability) {
if (settings.get().experimental.availability_new) {
this.extensions.push(new ExtensionAvailabilityNew(...args));
} else {
this.extensions.push(new ExtensionAvailability(...args));
}
if (settings.get().experimental.availability_new) {
this.extensions.push(new ExtensionAvailabilityNew(...args));
} else if (settings.get().advanced.availability_timeout) {
this.extensions.push(new ExtensionAvailability(...args));
}
this.extensions.push(new ExtensionExternalExtension(...args));
}
+8 -14
View File
@@ -1,6 +1,6 @@
import ExtensionTS from './extensionts';
import logger from '../util/logger';
import {sleep} from '../util/utils';
import {sleep, isAvailabilityNewEnabledForDevice} from '../util/utils';
import * as settings from '../util/settings';
const hours = (hours: number): number => 1000 * 60 * 60 * hours;
@@ -9,9 +9,9 @@ const seconds = (seconds: number): number => 1000 * seconds;
// TODO
// - State retrieval
// - Home Assistant add availability mode
// - Honour legacy availability_timeout, availability_blocklist and availability_passlist options.
// - Enable for HA addon
// - Add to setting schema
class AvailabilityNew extends ExtensionTS {
private timers: {[s: string]: NodeJS.Timeout} = {};
private availabilityCache: {[s: string]: boolean} = {};
@@ -25,10 +25,6 @@ class AvailabilityNew extends ExtensionTS {
logger.warn('Using experimental new availability feature');
}
private isEnabledForDevice(re: ResolvedEntity): boolean {
return re.settings.hasOwnProperty('availability') ? !!re.settings.availability : !!settings.get().availability;
}
private getTimeout(re: ResolvedEntity): number {
if (typeof re.settings.availability === 'object' && re.settings.availability?.timeout != null) {
return minutes(re.settings.availability.timeout);
@@ -114,11 +110,10 @@ class AvailabilityNew extends ExtensionTS {
override onMQTTConnected(): void {
for (const device of this.zigbee.getClients()) {
const re: ResolvedEntity = this.zigbee.resolveEntity(device);
if (isAvailabilityNewEnabledForDevice(re, settings)) {
// Publish initial availablility
this.publishAvailability(re, true);
// Publish initial availablility
this.publishAvailability(re, true);
if (this.isEnabledForDevice(re)) {
this.resetTimer(re);
// If an active device is initially unavailable, ping it.
@@ -134,8 +129,7 @@ class AvailabilityNew extends ExtensionTS {
}
private publishAvailability(re: ResolvedEntity, logLastSeen: boolean): void {
const enabled = this.isEnabledForDevice(re);
if (enabled && logLastSeen) {
if (logLastSeen) {
const ago = Date.now() - re.device.lastSeen;
if (this.isActiveDevice(re)) {
logger.debug(
@@ -145,7 +139,7 @@ class AvailabilityNew extends ExtensionTS {
}
}
const available = enabled ? this.isAvailable(re) : true;
const available = this.isAvailable(re);
if (this.availabilityCache[re.device.ieeeAddr] == available) {
return;
}
@@ -158,7 +152,7 @@ class AvailabilityNew extends ExtensionTS {
private lastSeenChanged(data: {device: Device}): void {
const re = this.zigbee.resolveEntity(data.device);
if (this.isEnabledForDevice(re)) {
if (isAvailabilityNewEnabledForDevice(re, settings)) {
// Remove from ping queue, not necessary anymore since we know the device is online.
this.removeFromPingQueue(re);
this.resetTimer(re);
+5 -2
View File
@@ -855,8 +855,11 @@ class HomeAssistant extends Extension {
// Availability payload
payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}];
const availabilityEnabled = settings.get().availability || settings.get().advanced.availability_timeout ||
resolvedEntity.settings.availability;
payload.availability_mode = 'all';
/* istanbul ignore next */
const availabilityEnabled = settings.get().experimental.availability_new ?
utils.isAvailabilityNewEnabledForDevice(resolvedEntity, settings) :
settings.get().advanced.availability_timeout;
if (resolvedEntity.type === 'device' && availabilityEnabled) {
payload.availability.push({topic: `${settings.get().mqtt.base_topic}/${friendlyName}/availability`});
}
+5
View File
@@ -267,6 +267,10 @@ function sanitizeImageParameter(parameter) {
return sanitized;
}
function isAvailabilityNewEnabledForDevice(re, settings) {
return re.settings.hasOwnProperty('availability') ? !!re.settings.availability : !!settings.get().availability;
}
module.exports = {
millisecondsToSeconds: (milliseconds) => milliseconds / 1000,
secondsToMilliseconds: (seconds) => seconds * 1000,
@@ -292,6 +296,7 @@ module.exports = {
validateFriendlyName,
loadModuleFromFile,
loadModuleFromText,
isAvailabilityNewEnabledForDevice,
getKey,
sanitizeImageParameter,
removeNullPropertiesFromObject,
-2
View File
@@ -71,8 +71,6 @@ describe('Availability', () => {
'online', {retain: true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/remote/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bulb_color_2/availability',
'online', {retain: true, qos: 0}, expect.any(Function));
});
it('Should publish offline for active device when not seen for 10 minutes', async () => {
+30 -15
View File
@@ -62,6 +62,7 @@ describe('HomeAssistant extension', () => {
payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"availability_mode": "all",
"brightness":true,
"brightness_scale":254,
"color_mode":true,
@@ -93,6 +94,7 @@ describe('HomeAssistant extension', () => {
payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"availability_mode": "all",
"command_topic":"zigbee2mqtt/ha_discovery_group/set",
"device":{
"identifiers":["zigbee2mqtt_1221051039810110150109113116116_9"],
@@ -132,6 +134,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -158,6 +161,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -184,6 +188,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -210,6 +215,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -237,6 +243,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -247,11 +254,8 @@ describe('HomeAssistant extension', () => {
);
payload = {
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"availability_mode": "all",
"command_topic":"zigbee2mqtt/wall_switch_double/left/set",
"device":{
"identifiers":[
@@ -279,11 +283,8 @@ describe('HomeAssistant extension', () => {
);
payload = {
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"availability_mode": "all",
"command_topic":"zigbee2mqtt/wall_switch_double/right/set",
"device":{
"identifiers":[
@@ -311,11 +312,8 @@ describe('HomeAssistant extension', () => {
);
payload = {
"availability":[
{
"topic":"zigbee2mqtt/bridge/state"
}
],
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"availability_mode": "all",
"brightness":true,
"brightness_scale":254,
"color_mode": true,
@@ -388,6 +386,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -414,6 +413,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -440,6 +440,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -497,6 +498,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'From Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
'expire_after': 90,
'icon': 'mdi:test',
};
@@ -524,6 +526,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Not from Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
'expire_after': 30,
'icon': 'mdi:test',
};
@@ -560,6 +563,7 @@ describe('HomeAssistant extension', () => {
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
"command_topic": "zigbee2mqtt/my_switch/set",
"device": {
"identifiers": [
@@ -659,6 +663,7 @@ describe('HomeAssistant extension', () => {
"manufacturer":"Hampton Bay"
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -678,6 +683,7 @@ describe('HomeAssistant extension', () => {
payload = {
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
"away_mode_command_topic":"zigbee2mqtt/TS0601_thermostat/set/away_mode",
"away_mode_state_template":"{{ value_json.away_mode }}",
"away_mode_state_topic":"zigbee2mqtt/TS0601_thermostat",
@@ -754,6 +760,7 @@ describe('HomeAssistant extension', () => {
manufacturer: 'Keen Home'
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -789,6 +796,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -932,6 +940,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -1055,6 +1064,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -1142,6 +1152,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -1213,6 +1224,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -1246,6 +1258,7 @@ describe('HomeAssistant extension', () => {
"manufacturer":"IKEA"
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -1694,6 +1707,7 @@ describe('HomeAssistant extension', () => {
'manufacturer': 'Xiaomi',
},
'availability': [{topic: 'zigbee2mqtt/bridge/state'}],
'availability_mode': 'all',
};
expect(MQTT.publish).toHaveBeenCalledWith(
@@ -1714,6 +1728,7 @@ describe('HomeAssistant extension', () => {
const payload = {
"availability":[{"topic":"zigbee2mqtt/bridge/state"}],
"availability_mode": "all",
"brightness":true,
"brightness_scale":254,
"color_mode":true,