diff --git a/lib/controller.ts b/lib/controller.ts index 7ce2dee39..b931742ad 100644 --- a/lib/controller.ts +++ b/lib/controller.ts @@ -21,7 +21,6 @@ import ExtensionDeviceGroupMembership from './extension/legacy/deviceGroupMember import ExtensionBridgeLegacy from './extension/legacy/bridgeLegacy'; import ExtensionBridge from './extension/bridge'; import ExtensionGroups from './extension/groups'; -import ExtensionAvailabilityLegacy from './extension/legacy/availability'; import ExtensionAvailability from './extension/availability'; import ExtensionBind from './extension/bind'; import ExtensionReport from './extension/legacy/report'; @@ -33,7 +32,7 @@ import ExtensionExternalExtension from './extension/externalExtension'; const AllExtensions = [ ExtensionPublish, ExtensionReceive, ExtensionNetworkMap, ExtensionSoftReset, ExtensionHomeAssistant, ExtensionConfigure, ExtensionDeviceGroupMembership, ExtensionBridgeLegacy, ExtensionBridge, ExtensionGroups, - ExtensionAvailabilityLegacy, ExtensionBind, ExtensionReport, ExtensionOnEvent, ExtensionOTAUpdate, + ExtensionBind, ExtensionReport, ExtensionOnEvent, ExtensionOTAUpdate, ExtensionExternalConverters, ExtensionFrontend, ExtensionExternalExtension, ExtensionAvailability, ]; @@ -62,8 +61,6 @@ class Controller { this.extensionArgs = [this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus, this.enableDisableExtension, this.restartCallback, this.addExtension]; - const availabilityLegacy = !settings.get().advanced.availability_timeout && - settings.get().advanced.availability_timeout; this.extensions = [ new ExtensionBridge(...this.extensionArgs), new ExtensionPublish(...this.extensionArgs), @@ -77,15 +74,13 @@ class Controller { new ExtensionOTAUpdate(...this.extensionArgs), new ExtensionReport(...this.extensionArgs), new ExtensionExternalExtension(...this.extensionArgs), + new ExtensionAvailability(...this.extensionArgs), settings.get().frontend && new ExtensionFrontend(...this.extensionArgs), settings.get().advanced.legacy_api && new ExtensionBridgeLegacy(...this.extensionArgs), settings.get().external_converters.length && new ExtensionExternalConverters(...this.extensionArgs), settings.get().homeassistant && new ExtensionHomeAssistant(...this.extensionArgs), /* istanbul ignore next */ settings.get().advanced.soft_reset_timeout !== 0 && new ExtensionSoftReset(...this.extensionArgs), - settings.get().experimental.availability_new && new ExtensionAvailability(...this.extensionArgs), - /* istanbul ignore next */ - availabilityLegacy && new ExtensionAvailabilityLegacy(...this.extensionArgs), ].filter((n) => n); } diff --git a/lib/extension/availability.ts b/lib/extension/availability.ts index a13df5ffb..20a4df678 100644 --- a/lib/extension/availability.ts +++ b/lib/extension/availability.ts @@ -5,9 +5,6 @@ import * as settings from '../util/settings'; import debounce from 'debounce'; import bind from 'bind-decorator'; -// TODO -// - Enable for HA addon -// - Add to setting schema (when old availability is removed) export default class Availability extends Extension { private timers: {[s: string]: NodeJS.Timeout} = {}; private availabilityCache: {[s: string]: boolean} = {}; diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 2db1c987c..1aaf69b92 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -901,10 +901,6 @@ export default class HomeAssistant extends Extension { /* istanbul ignore next */ if (availabilityEnabled) { payload.availability_mode = 'all'; - } - - /* istanbul ignore next */ - if (availabilityEnabled || settings.get().advanced.availability_timeout) { payload.availability.push({topic: `${settings.get().mqtt.base_topic}/${entity.name}/availability`}); } diff --git a/lib/extension/legacy/availability.ts b/lib/extension/legacy/availability.ts deleted file mode 100644 index 65384b5c1..000000000 --- a/lib/extension/legacy/availability.ts +++ /dev/null @@ -1,230 +0,0 @@ -import logger from '../../util/logger'; -import * as settings from '../../util/settings'; -import utils from '../../util/utils'; -// @ts-ignore -import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters'; -import Extension from '../extension'; -const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/(.*)/availability`); -import bind from 'bind-decorator'; - -// Pingable end devices, some end devices should be pinged -// e.g. E11-G13 https://github.com/Koenkk/zigbee2mqtt/issues/775#issuecomment-453683846 -const pingableEndDevices = [ - zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'E11-G13'), - zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'E11-N1EA'), - zigbeeHerdsmanConverters.definitions.find((d) => d.model === '53170161'), -]; - -const Hours25 = 1000 * 60 * 60 * 25; -const AvailabilityLagRatio = 0.1; - - -function timeoutLag(timeout: number, ratio: number): number { - const lag = timeout * ratio; - return Math.floor(Math.random() * Math.floor(lag)); -} - -/** - * This extensions pings devices to check if they are online. - */ -export default class AvailabilityLegacy extends Extension { - // eslint-disable-next-line - private availability_timeout = settings.get().advanced.availability_timeout; - private timers: KeyValue = {}; - private stateLookup: KeyValue = {}; - private blocklist = settings.get().advanced.availability_blocklist - .concat(settings.get().advanced.availability_blacklist) - .map((e) => settings.getDevice(e).ID); - private passlist = settings.get().advanced.availability_passlist - .concat(settings.get().advanced.availability_whitelist) - .map((e) => settings.getDevice(e).ID); - - override async start(): Promise { - this.eventBus.onDeviceRemoved(this, this.onDeviceRemoved); - this.eventBus.onDeviceRenamed(this, this.onDeviceRenamed); - this.eventBus.onMQTTMessage(this, this.onMQTTMessage); - this.eventBus.onDeviceAnnounce(this, (data) => this.onZigbeeEvent_('deviceAnnounce', data.device)); - this.eventBus.onDeviceMessage(this, (data) => this.onZigbeeEvent_('dummy', data.device)); - this.eventBus.onDeviceJoined(this, (data) => this.onZigbeeEvent_('dummy', data.device)); - /* istanbul ignore next */ - this.eventBus.onDeviceNetworkAddressChanged(this, (data) => this.onZigbeeEvent_('dummy', data.device)); - - for (const device of this.zigbee.devices(false)) { - // Mark all devices as online on start - const ieeeAddr = device.ieeeAddr; - this.publishAvailability(device, this.stateLookup.hasOwnProperty(ieeeAddr) ? - this.stateLookup[ieeeAddr] : true, true); - - if (this.inPasslistOrNotInBlocklist(device)) { - if (this.isPingable(device)) { - this.setTimerPingable(device); - } else { - this.timers[ieeeAddr] = setInterval(() => { - this.handleIntervalNotPingable(device); - }, utils.seconds(300)); - } - } - } - } - - @bind onDeviceRenamed(data: eventdata.DeviceRenamed): void { - this.mqtt.publish(`${data.from}/availability`, null, {retain: true, qos: 0}); - } - - /* istanbul ignore next */ - @bind onDeviceRemoved(data: eventdata.DeviceRemoved): void { - this.mqtt.publish(`${data.name}/availability`, null, {retain: true, qos: 0}); - delete this.stateLookup[data.ieeeAddr]; - clearTimeout(this.timers[data.ieeeAddr]); - } - - inPasslistOrNotInBlocklist(device: Device): boolean { - const ieeeAddr = device.ieeeAddr; - const deviceSettings = settings.getDevice(ieeeAddr); - const name = deviceSettings && deviceSettings.friendly_name; - - // Passlist is not empty and device is in it, enable availability - if (this.passlist.length > 0) { - return this.passlist.includes(ieeeAddr) || (name && this.passlist.includes(name)); - } - - // Device is on blocklist, disable availability - if (this.blocklist.includes(ieeeAddr) || (name && this.blocklist.includes(name))) { - return false; - } - - return true; - } - - isPingable(device: Device): boolean { - if (pingableEndDevices.find((d) => d.hasOwnProperty('zigbeeModel') && - d.zigbeeModel.includes(device.zh.modelID))) { - return true; - } - - // Device is a mains powered router - return device.zh.type === 'Router' && device.zh.powerSource !== 'Battery'; - } - - @bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise { - // Clear topics for non-existing devices - const match = data.topic.match(topicRegex); - if (match && (!this.zigbee.resolveEntity(match[1]) || - this.zigbee.resolveEntity(match[1]).name !== match[1])) { - this.mqtt.publish(`${match[1]}/availability`, null, {retain: true, qos: 0}); - } - } - - async handleIntervalPingable(device: Device): Promise { - // When a device is already unavailable, log the ping failed on 'debug' instead of 'error'. - /* istanbul ignore next */ - if (!device.zh) { - logger.debug(`Stop pinging '${device.ieeeAddr}', device is not known anymore`); - return; - } - - const level = this.stateLookup.hasOwnProperty(device.ieeeAddr) && - !this.stateLookup[device.ieeeAddr] ? 'debug' : 'error'; - try { - await device.zh.ping(); - this.publishAvailability(device, true); - logger.debug(`Successfully pinged '${device.name}'`); - } catch (error) { - this.publishAvailability(device, false); - logger[level](`Failed to ping '${device.name}'`); - } finally { - this.setTimerPingable(device); - } - } - - async handleIntervalNotPingable(device: Device): Promise { - /* istanbul ignore next */ - if (!device.zh.lastSeen) { - return; - } - - const ago = Date.now() - device.zh.lastSeen; - logger.debug(`Non-pingable device '${device.name}' was last seen '${ago / 1000}' seconds ago.`); - - if (ago > Hours25) { - this.publishAvailability(device, false); - } - } - - setTimerPingable(device: Device): void { - const timeout = this.availability_timeout + timeoutLag(this.availability_timeout, AvailabilityLagRatio); - clearTimeout(this.timers[device.ieeeAddr]); - this.timers[device.ieeeAddr] = setTimeout(async () => { - await this.handleIntervalPingable(device); - }, utils.seconds(timeout)); - } - - override async stop(): Promise { - super.stop(); - for (const timer of Object.values(this.timers)) { - clearTimeout(timer); - } - - this.zigbee.devices(false).forEach((device) => this.publishAvailability(device, false)); - } - - async onReconnect(device: Device): Promise { - if (device.definition) { - try { - for (const key of ['state', 'brightness', 'color', 'color_temp']) { - const converter = device.definition.toZigbee.find((tz) => tz.key.includes(key)); - if (converter) { - await converter.convertGet(device.zh.endpoints[0], key, {}); - } - } - } catch (error) { - logger.error(`Failed to read state of '${device.name}' after reconnect`); - } - } - } - - private publishAvailability(device: Device, available: boolean, force=false): void { - const ieeeAddr = device.ieeeAddr; - if (this.stateLookup.hasOwnProperty(ieeeAddr) && !this.stateLookup[ieeeAddr] && available) { - this.onReconnect(device); - } - - const topic = `${device.name}/availability`; - const payload = available ? 'online' : 'offline'; - if (this.stateLookup[ieeeAddr] !== available || force) { - this.stateLookup[ieeeAddr] = available; - this.mqtt.publish(topic, payload, {retain: true, qos: 0}); - } - } - - onZigbeeEvent_(type: string, device: Device): Promise { - /* istanbul ignore next */ - if (!device) { - return; - } - - if (this.inPasslistOrNotInBlocklist(device)) { - this.publishAvailability(device, true); - - 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); - - const online = this.stateLookup.hasOwnProperty(device.ieeeAddr) && this.stateLookup[device.ieeeAddr]; - if (online && type === 'deviceAnnounce' && !device.isIkeaTradfri()) { - /** - * 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 - * 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); - } - } - } - } -} diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts index 055a2f86b..5dc7b71ec 100644 --- a/lib/types/types.d.ts +++ b/lib/types/types.d.ts @@ -220,7 +220,6 @@ declare global { }, experimental: { output: 'json' | 'attribute' | 'attribute_and_json', - availability_new?: boolean, transmit_power?: number, }, advanced: { @@ -238,11 +237,6 @@ declare global { channel: number, adapter_concurrent: number | null, adapter_delay: number | null, - availability_timeout: number, - availability_blocklist: string[], - availability_passlist: string[], - availability_blacklist: string[], - availability_whitelist: string[], cache_state: boolean, cache_state_persistent: boolean, cache_state_send_on_startup: boolean, @@ -258,6 +252,12 @@ declare global { baudrate?: number, rtscts?: boolean, ikea_ota_use_test_url?: boolean, + // below are deprecated + availability_timeout?: number, + availability_blocklist?: string[], + availability_passlist?: string[], + availability_blacklist?: string[], + availability_whitelist?: string[], }, ota: { update_check_interval: number, diff --git a/lib/util/settings.schema.json b/lib/util/settings.schema.json index e71cb6a66..9b54f0d78 100644 --- a/lib/util/settings.schema.json +++ b/lib/util/settings.schema.json @@ -26,6 +26,46 @@ }, "examples": ["DIYRuZ_FreePad.js"] }, + "availability": { + "type": ["boolean", "object"], + "title": "Availability feature", + "requiresRestart": true, + "description": "Checks wether devices are online/offline", + "default": false, + "examples": [true], + "properties": { + "active": { + "type": "object", + "title": "Active", + "requiresRestart": true, + "description": "Options for active devices (routers/mains powered)", + "properties": { + "timeout": { + "type": "number", + "title": "Timeout", + "requiresRestart": true, + "default": 10, + "description": "Time after which an active device will be marked as offline in minutes" + } + } + }, + "passive": { + "type": "object", + "title": "Active", + "requiresRestart": true, + "description": "Options for passive devices (routers/mains powered)", + "properties": { + "timeout": { + "type": "number", + "title": "Timeout", + "requiresRestart": true, + "default": 1500, + "description": "Time after which an passive device will be marked as offline in minutes" + } + } + } + } + }, "mqtt": { "type": "object", "title": "MQTT", @@ -365,50 +405,6 @@ "description": "Add an elapsed attribute to MQTT messages, contains milliseconds since the previous msg", "default": false }, - "availability_timeout": { - "type": "number", - "minimum": 0, - "default": 0, - "requiresRestart": true, - "title": "Availability Timeout", - "description": "Availability timeout in seconds when enabled, devices will be checked if they are still online. Only AC powered routers are checked for availability" - }, - "availability_blocklist": { - "type": "array", - "items": { - "type": "string" - }, - "requiresRestart": true, - "title": "Availability Blocklist", - "description": "Prevent devices from being checked for availability" - }, - "availability_passlist": { - "type": "array", - "items": { - "type": "string" - }, - "requiresRestart": true, - "title": "Availability passlist", - "description": "Only enable availability check for certain devices" - }, - "availability_blacklist": { - "type": "array", - "readOnly": true, - "requiresRestart": true, - "title": "Availability blacklist (deprecated, use availability_blocklist)", - "items": { - "type": "string" - } - }, - "availability_whitelist": { - "type": "array", - "readOnly": true, - "requiresRestart": true, - "items": { - "type": "string" - }, - "title": "Availability whitelist (deprecated, use passlist)" - }, "report": { "type": "boolean", "title": "Reporting", diff --git a/lib/util/settings.ts b/lib/util/settings.ts index ec6e65025..b64598d7e 100644 --- a/lib/util/settings.ts +++ b/lib/util/settings.ts @@ -11,8 +11,8 @@ export const schema = schemaJson; // DEPRECATED ZIGBEE2MQTT_CONFIG: https://github.com/Koenkk/zigbee2mqtt/issues/4697 const file = process.env.ZIGBEE2MQTT_CONFIG ?? data.joinPath('configuration.yaml'); -const ajvSetting = new Ajv({allErrors: true}).addKeyword('requiresRestart').compile(schema); -const ajvRestartRequired = new Ajv({allErrors: true}) +const ajvSetting = new Ajv({allErrors: true, allowUnionTypes: true}).addKeyword('requiresRestart').compile(schema); +const ajvRestartRequired = new Ajv({allErrors: true, allowUnionTypes: true}) .addKeyword({keyword: 'requiresRestart', validate: (schema: unknown) => !schema}).compile(schema); const defaults: RecursivePartial = { @@ -75,7 +75,6 @@ const defaults: RecursivePartial = { adapter_delay: null, // Availability timeout in seconds, disabled by default. - availability_timeout: 0, availability_blocklist: [], availability_passlist: [], // Deprecated, use block/passlist diff --git a/lib/util/utils.ts b/lib/util/utils.ts index 258c21ed4..daecd7a6d 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -245,9 +245,6 @@ function sanitizeImageParameter(parameter: string): string { } function isAvailabilityEnabledForDevice(device: Device, settings: Settings): boolean { - /* istanbul ignore next */ - if (!settings.experimental.availability_new) return false; - if (device.settings.hasOwnProperty('availability')) { return !!device.settings.availability; } diff --git a/test/availability.test.js b/test/availability.test.js index ddb7cad42..fb8aea448 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -33,7 +33,6 @@ describe('Availability', () => { jest.useFakeTimers('modern'); settings.reRead(); settings.set(['availability'], true); - settings.set(['experimental', 'availability_new'], true); controller = new Controller(jest.fn(), jest.fn()); await controller.start(); await flushPromises(); @@ -44,7 +43,6 @@ describe('Availability', () => { data.writeDefaultConfiguration(); settings.reRead(); settings.set(['availability'], true); - settings.set(['experimental', 'availability_new'], true); settings.set(['devices', devices.bulb_color_2.ieeeAddr, 'availability'], false); Object.values(devices).forEach(d => d.lastSeen = utils.minutes(1)); mocks.forEach((m) => m.mockClear()); diff --git a/test/bridge.test.js b/test/bridge.test.js index cfde00502..67e7c46e4 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -64,7 +64,7 @@ describe('Bridge', () => { const directory = settings.get().advanced.log_directory; expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/info', - stringify({ "restart_required": false, "commit": version.commitHash, "config": { "advanced": { "adapter_concurrent": null, "adapter_delay": null, "availability_blacklist": [], "availability_blocklist": [], "availability_passlist": [], "availability_timeout": 0, "availability_whitelist": [], "cache_state": true, "cache_state_persistent": true, "cache_state_send_on_startup": true, "channel": 11, "elapsed": false, "ext_pan_id": [221, 221, 221, 221, 221, 221, 221, 221], "homeassistant_discovery_topic": "homeassistant", "homeassistant_legacy_entity_attributes": true, "homeassistant_legacy_triggers": true, "homeassistant_status_topic": "hass/status", "last_seen": "disable", "legacy_api": false, "log_directory": directory, "log_file": "log.txt", "log_level": "info", "log_output": ["console", "file"], "log_rotation": true, "log_symlink_current": false, "log_syslog": {}, "pan_id": 6754, "report": false, "soft_reset_timeout": 0, "timestamp_format": "YYYY-MM-DD HH:mm:ss" }, "ban": [], "blocklist": [], "device_options": {}, "devices": { "0x000b57fffec6a5b2": { "friendly_name": "bulb", "retain": true }, "0x000b57fffec6a5b3": { "friendly_name": "bulb_color", "retain": false }, "0x000b57fffec6a5b4": { "friendly_name": "bulb_color_2", "retain": false }, "0x000b57fffec6a5b7": { "friendly_name": "bulb_2", "retain": false }, "0x0017880104a44559": { "friendly_name": "J1_cover" }, "0x0017880104e43559": { "friendly_name": "U202DST600ZB" }, "0x0017880104e44559": { "friendly_name": "3157100_thermostat" }, "0x0017880104e45517": { "friendly_name": "remote", "retain": true }, "0x0017880104e45520": { "friendly_name": "button", "retain": false }, "0x0017880104e45521": { "friendly_name": "button_double_key", "retain": false }, "0x0017880104e45522": { "friendly_name": "weather_sensor", "qos": 1, "retain": false }, "0x0017880104e45523": { "friendly_name": "occupancy_sensor", "retain": false }, "0x0017880104e45524": { "friendly_name": "power_plug", "retain": false }, "0x0017880104e45526": { "friendly_name": "GL-S-007ZS" }, "0x0017880104e45529": { "friendly_name": "unsupported2", "retain": false }, "0x0017880104e45530": { "friendly_name": "button_double_key_interviewing", "retain": false }, "0x0017880104e45540": { "friendly_name": "ikea_onoff" }, "0x0017880104e45541": { "friendly_name": "wall_switch", "retain": false }, "0x0017880104e45542": { "friendly_name": "wall_switch_double", "retain": false }, "0x0017880104e45543": { "friendly_name": "led_controller_1", "retain": false }, "0x0017880104e45544": { "friendly_name": "led_controller_2", "retain": false }, "0x0017880104e45545": { "friendly_name": "dimmer_wall_switch", "retain": false }, "0x0017880104e45547": { "friendly_name": "curtain", "retain": false }, "0x0017880104e45548": { "friendly_name": "fan", "retain": false }, "0x0017880104e45549": { "friendly_name": "siren", "retain": false }, "0x0017880104e45550": { "friendly_name": "thermostat", "retain": false }, "0x0017880104e45551": { "friendly_name": "smart vent", "retain": false }, "0x0017880104e45552": { "friendly_name": "j1", "retain": false }, "0x0017880104e45553": { "friendly_name": "bulb_enddevice", "retain": false }, "0x0017880104e45559": { "friendly_name": "cc2530_router", "retain": false }, "0x0017880104e45560": { "friendly_name": "livolo", "retain": false }, "0x0017880104e45724":{"friendly_name":"GLEDOPTO_2ID"}, "0x0017882104a44559": { "friendly_name": "TS0601_thermostat" }, "0x0017882194e45543": { "friendly_name": "QS-Zigbee-D02-TRIAC-2C-LN" }, "0x90fd9ffffe4b64aa": { "friendly_name": "SP600_OLD" }, "0x90fd9ffffe4b64ab": { "friendly_name": "SP600_NEW" }, "0x90fd9ffffe4b64ac": { "friendly_name": "MKS-CM-W5" }, "0x90fd9ffffe4b64ae": { "friendly_name": "tradfri_remote", "retain": false }, "0x90fd9ffffe4b64af": { "friendly_name": "roller_shutter" }, "0x90fd9ffffe4b64ax": { "friendly_name": "ZNLDP12LM" }, "0x0017880104e45561": { "friendly_name": "temperature_sensor" }, "0x0017880104e45562": { "friendly_name": "heating_actuator" } }, "experimental": { "output": "json" }, "external_converters": [], "groups": { "1": { "friendly_name": "group_1", "retain": false }, "9": { "devices": ['bulb_color_2', 'bulb_2', 'wall_switch_double/right'], "friendly_name": "ha_discovery_group"}, "11": { "devices": ["bulb_2"], "friendly_name": "group_with_tradfri", "retain": false }, "14": { "devices": ["power_plug", "bulb_2"], "friendly_name": "switch_group", "retain": false }, "12": { "devices": ["TS0601_thermostat"], "friendly_name": "thermostat_group", "retain": false }, "15071": { "devices": ["bulb_color_2", "bulb_2"], "friendly_name": "group_tradfri_remote", "retain": false }, "2": { "friendly_name": "group_2", "retain": false }, "21":{"devices":["GLEDOPTO_2ID/cct"],"friendly_name":"gledopto_group"} }, "homeassistant": false, "map_options": { "graphviz": { "colors": { "fill": { "coordinator": "#e04e5d", "enddevice": "#fff8ce", "router": "#4ea3e0" }, "font": { "coordinator": "#ffffff", "enddevice": "#000000", "router": "#ffffff" }, "line": { "active": "#009900", "inactive": "#994444" } } } }, "mqtt": { "base_topic": "zigbee2mqtt", "include_device_information": false, "server": "mqtt://localhost", "force_disable_retain": false, }, "ota":{"disable_automatic_update_check":false,"update_check_interval":1440}, "passlist": [], "permit_join": true, "serial": { "disable_led": false, "port": "/dev/dummy" }, "whitelist": [] }, "coordinator": { "meta": { "revision": 20190425, "version": 1 }, "type": "z-Stack" }, "log_level": "info", "network": { "channel": 15, "extended_pan_id": [0, 11, 22], "pan_id": 5674 }, "permit_join": false, "version": version.version, config_schema: settings.schema }), + stringify({ "restart_required": false, "commit": version.commitHash, "config": { "advanced": { "adapter_concurrent": null, "adapter_delay": null, "availability_blacklist": [], "availability_blocklist": [], "availability_passlist": [], "availability_whitelist": [], "cache_state": true, "cache_state_persistent": true, "cache_state_send_on_startup": true, "channel": 11, "elapsed": false, "ext_pan_id": [221, 221, 221, 221, 221, 221, 221, 221], "homeassistant_discovery_topic": "homeassistant", "homeassistant_legacy_entity_attributes": true, "homeassistant_legacy_triggers": true, "homeassistant_status_topic": "hass/status", "last_seen": "disable", "legacy_api": false, "log_directory": directory, "log_file": "log.txt", "log_level": "info", "log_output": ["console", "file"], "log_rotation": true, "log_symlink_current": false, "log_syslog": {}, "pan_id": 6754, "report": false, "soft_reset_timeout": 0, "timestamp_format": "YYYY-MM-DD HH:mm:ss" }, "ban": [], "blocklist": [], "device_options": {}, "devices": { "0x000b57fffec6a5b2": { "friendly_name": "bulb", "retain": true }, "0x000b57fffec6a5b3": { "friendly_name": "bulb_color", "retain": false }, "0x000b57fffec6a5b4": { "friendly_name": "bulb_color_2", "retain": false }, "0x000b57fffec6a5b7": { "friendly_name": "bulb_2", "retain": false }, "0x0017880104a44559": { "friendly_name": "J1_cover" }, "0x0017880104e43559": { "friendly_name": "U202DST600ZB" }, "0x0017880104e44559": { "friendly_name": "3157100_thermostat" }, "0x0017880104e45517": { "friendly_name": "remote", "retain": true }, "0x0017880104e45520": { "friendly_name": "button", "retain": false }, "0x0017880104e45521": { "friendly_name": "button_double_key", "retain": false }, "0x0017880104e45522": { "friendly_name": "weather_sensor", "qos": 1, "retain": false }, "0x0017880104e45523": { "friendly_name": "occupancy_sensor", "retain": false }, "0x0017880104e45524": { "friendly_name": "power_plug", "retain": false }, "0x0017880104e45526": { "friendly_name": "GL-S-007ZS" }, "0x0017880104e45529": { "friendly_name": "unsupported2", "retain": false }, "0x0017880104e45530": { "friendly_name": "button_double_key_interviewing", "retain": false }, "0x0017880104e45540": { "friendly_name": "ikea_onoff" }, "0x0017880104e45541": { "friendly_name": "wall_switch", "retain": false }, "0x0017880104e45542": { "friendly_name": "wall_switch_double", "retain": false }, "0x0017880104e45543": { "friendly_name": "led_controller_1", "retain": false }, "0x0017880104e45544": { "friendly_name": "led_controller_2", "retain": false }, "0x0017880104e45545": { "friendly_name": "dimmer_wall_switch", "retain": false }, "0x0017880104e45547": { "friendly_name": "curtain", "retain": false }, "0x0017880104e45548": { "friendly_name": "fan", "retain": false }, "0x0017880104e45549": { "friendly_name": "siren", "retain": false }, "0x0017880104e45550": { "friendly_name": "thermostat", "retain": false }, "0x0017880104e45551": { "friendly_name": "smart vent", "retain": false }, "0x0017880104e45552": { "friendly_name": "j1", "retain": false }, "0x0017880104e45553": { "friendly_name": "bulb_enddevice", "retain": false }, "0x0017880104e45559": { "friendly_name": "cc2530_router", "retain": false }, "0x0017880104e45560": { "friendly_name": "livolo", "retain": false }, "0x0017880104e45724":{"friendly_name":"GLEDOPTO_2ID"}, "0x0017882104a44559": { "friendly_name": "TS0601_thermostat" }, "0x0017882194e45543": { "friendly_name": "QS-Zigbee-D02-TRIAC-2C-LN" }, "0x90fd9ffffe4b64aa": { "friendly_name": "SP600_OLD" }, "0x90fd9ffffe4b64ab": { "friendly_name": "SP600_NEW" }, "0x90fd9ffffe4b64ac": { "friendly_name": "MKS-CM-W5" }, "0x90fd9ffffe4b64ae": { "friendly_name": "tradfri_remote", "retain": false }, "0x90fd9ffffe4b64af": { "friendly_name": "roller_shutter" }, "0x90fd9ffffe4b64ax": { "friendly_name": "ZNLDP12LM" }, "0x0017880104e45561": { "friendly_name": "temperature_sensor" }, "0x0017880104e45562": { "friendly_name": "heating_actuator" } }, "experimental": { "output": "json" }, "external_converters": [], "groups": { "1": { "friendly_name": "group_1", "retain": false }, "9": { "devices": ['bulb_color_2', 'bulb_2', 'wall_switch_double/right'], "friendly_name": "ha_discovery_group"}, "11": { "devices": ["bulb_2"], "friendly_name": "group_with_tradfri", "retain": false }, "14": { "devices": ["power_plug", "bulb_2"], "friendly_name": "switch_group", "retain": false }, "12": { "devices": ["TS0601_thermostat"], "friendly_name": "thermostat_group", "retain": false }, "15071": { "devices": ["bulb_color_2", "bulb_2"], "friendly_name": "group_tradfri_remote", "retain": false }, "2": { "friendly_name": "group_2", "retain": false }, "21":{"devices":["GLEDOPTO_2ID/cct"],"friendly_name":"gledopto_group"} }, "homeassistant": false, "map_options": { "graphviz": { "colors": { "fill": { "coordinator": "#e04e5d", "enddevice": "#fff8ce", "router": "#4ea3e0" }, "font": { "coordinator": "#ffffff", "enddevice": "#000000", "router": "#ffffff" }, "line": { "active": "#009900", "inactive": "#994444" } } } }, "mqtt": { "base_topic": "zigbee2mqtt", "include_device_information": false, "server": "mqtt://localhost", "force_disable_retain": false, }, "ota":{"disable_automatic_update_check":false,"update_check_interval":1440}, "passlist": [], "permit_join": true, "serial": { "disable_led": false, "port": "/dev/dummy" }, "whitelist": [] }, "coordinator": { "meta": { "revision": 20190425, "version": 1 }, "type": "z-Stack" }, "log_level": "info", "network": { "channel": 15, "extended_pan_id": [0, 11, 22], "pan_id": 5674 }, "permit_join": false, "version": version.version, config_schema: settings.schema }), { retain: true, qos: 0 }, expect.any(Function) ); diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index d1ab86c98..6c239a288 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -994,7 +994,7 @@ describe('HomeAssistant extension', () => { }); it('Should discover devices with availability', async () => { - settings.set(['advanced', 'availability_timeout'], 1) + settings.set(['availability'], true) await resetExtension(); let payload; @@ -1016,6 +1016,7 @@ describe('HomeAssistant extension', () => { 'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)', 'manufacturer': 'Xiaomi', }, + 'availability_mode': 'all', 'availability': [{topic: 'zigbee2mqtt/bridge/state'}, {topic: 'zigbee2mqtt/weather_sensor/availability'}], }; diff --git a/test/legacy/availabilityLegacy.test.js b/test/legacy/availabilityLegacy.test.js deleted file mode 100644 index 96cb632aa..000000000 --- a/test/legacy/availabilityLegacy.test.js +++ /dev/null @@ -1,403 +0,0 @@ -const data = require('../stub/data'); -const logger = require('../stub/logger'); -const stringify = require('json-stable-stringify-without-jsonify'); -const zigbeeHerdsman = require('../stub/zigbeeHerdsman'); -zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b3'); -zigbeeHerdsman.returnDevices.push('0x00124b00120144ae'); -zigbeeHerdsman.returnDevices.push('0x0017880104e45553'); -zigbeeHerdsman.returnDevices.push('0x0017880104e45517'); -zigbeeHerdsman.returnDevices.push('0x000b57fffec6a5b2'); -zigbeeHerdsman.returnDevices.push('0x0017880104e45535'); -zigbeeHerdsman.returnDevices.push('0x0017880104e45521'); -zigbeeHerdsman.returnDevices.push('0x0017880104e45525'); -const MQTT = require('../stub/mqtt'); -const settings = require('../../lib/util/settings'); -const Controller = require('../../lib/controller'); -const flushPromises = require('../lib/flushPromises'); -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); -const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {}); -const mocks = [MQTT.publish, logger.warn, logger.debug]; - -describe('Availability', () => { - let controller; - let extension; - - let resetExtension = async () => { - await controller.enableDisableExtension(false, 'AvailabilityLegacy'); - await controller.enableDisableExtension(true, 'AvailabilityLegacy'); - extension = controller.extensions.find((e) => e.constructor.name === 'AvailabilityLegacy'); - } - - beforeAll(async () => { - jest.useFakeTimers(); - controller = new Controller(jest.fn(), jest.fn()); - await controller.start(); - await flushPromises(); - }); - - beforeEach(async () => { - data.writeDefaultConfiguration(); - settings.reRead(); - settings.set(['advanced', 'availability_timeout'], 10); - mocks.forEach((m) => m.mockClear()); - await resetExtension(); - }); - - afterAll(async () => { - jest.useRealTimers(); - }) - - it('Should publish availabilty on startup', async () => { - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bulb_color/availability', - 'online', - { retain: true, qos: 0 }, - expect.any(Function) - ); - }); - - it('Should publish availabilty offline when ping fails', async () => { - MQTT.publish.mockClear(); - logger.error.mockClear(); - logger.debug.mockClear(); - const device = zigbeeHerdsman.devices.bulb_color; - device.ping.mockImplementationOnce(() => {throw new Error('failed')}); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenNthCalledWith(1, - 'zigbee2mqtt/bulb_color/availability', - 'offline', - { retain: true, qos: 0 }, - expect.any(Function) - ); - expect(logger.error).toHaveBeenCalledTimes(1); - expect(logger.error).toHaveBeenCalledWith("Failed to ping 'bulb_color'"); - device.ping.mockImplementationOnce(() => {throw new Error('failed')}); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(logger.debug).toHaveBeenCalledWith("Failed to ping 'bulb_color'"); - expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenNthCalledWith(1, - 'zigbee2mqtt/bulb_color/availability', - 'offline', - { retain: true, qos: 0 }, - expect.any(Function) - ); - }); - - it('Should publish availabilty online and query state on reconnect', async () => { - const device = zigbeeHerdsman.devices.E11_G13; - const endpoint = device.getEndpoint(1); - device.ping.mockImplementationOnce(() => {throw new Error('failed')}); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - MQTT.publish.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bulb_enddevice/availability', - 'online', - { retain: true, qos: 0 }, - expect.any(Function) - ); - - expect(endpoint.read).toHaveBeenCalledTimes(2); - expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']); - expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); - }); - - it('Should fail gracefully when quering state after reconnect fails', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - const endpoint = device.getEndpoint(1); - endpoint.read.mockClear(); - endpoint.read.mockImplementationOnce(() => {throw new Error('Device timedout')}); - device.ping.mockImplementationOnce(() => {throw new Error('failed')}); - logger.debug.mockClear(); - jest.advanceTimersByTime(11 * 2000); - await flushPromises(); - - expect(endpoint.read).toHaveBeenCalledTimes(1); - expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); - expect(logger.error).toHaveBeenCalledWith(`Failed to read state of 'bulb_color' after reconnect`); - }); - - it('Shouldnt ping again when still pinging', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - device.ping.mockClear(); - device.ping.mockImplementationOnce(async () => {await wait(100000000)}); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(1); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(1); - }); - - it('Should mark device online when receiving message while offline', async () => { - MQTT.publish.mockClear(); - const device = zigbeeHerdsman.devices.bulb_color; - const endpoint = device.getEndpoint(1); - device.ping.mockImplementationOnce(() => {throw new Error('failed')}); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - - MQTT.publish.mockClear(); - const data = {modelID: 'test'} - const payload = {data, cluster: 'genOnOff', device, endpoint, type: 'readResponse', linkquality: 10}; - await zigbeeHerdsman.events.message(payload); - await flushPromises(); - - expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenNthCalledWith(1, - 'zigbee2mqtt/bulb_color/availability', - 'online', - { retain: true, qos: 0 }, - expect.any(Function) - ); - }); - - it('Should retrieve the state when device is turned on/off within availability timeout', async () => { - MQTT.publish.mockClear(); - extension.stateLookup = {}; - const payload = {device: zigbeeHerdsman.devices.bulb_color}; - await zigbeeHerdsman.events.deviceJoined(payload); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bulb_color/availability', - 'online', - { retain: true, qos: 0 }, - expect.any(Function) - ); - }); - - it('Should retrieve the state when device is turned on/off within availability timeout on deviceAnnounce', async () => { - MQTT.publish.mockClear(); - const device = zigbeeHerdsman.devices.bulb_color; - const endpoint = device.getEndpoint(1); - endpoint.read.mockClear(); - await zigbeeHerdsman.events.deviceAnnounce({device}); - await flushPromises(); - expect(endpoint.read).toHaveBeenCalledTimes(2); - expect(endpoint.read).toHaveBeenCalledWith('genLevelCtrl', ['currentLevel']); - expect(endpoint.read).toHaveBeenCalledWith('genOnOff', ['onOff']); - }); - - it('Should not retrieve the state when device is turned on/off within availability timeout on deviceJoined', async () => { - MQTT.publish.mockClear(); - const device = zigbeeHerdsman.devices.E11_G13; - const endpoint = device.getEndpoint(1); - endpoint.read.mockClear(); - await zigbeeHerdsman.events.deviceJoined({device}); - await flushPromises(); - expect(endpoint.read).toHaveBeenCalledTimes(0); - }); - - it('Should not do anything when message has no device', async () => { - MQTT.publish.mockClear(); - const device = zigbeeHerdsman.devices.bulb_color; - const payload = {ieeeAddr: device.ieeeAddr}; - await zigbeeHerdsman.events.deviceLeave(payload); - await flushPromises(); - expect(MQTT.publish.mock.calls.find((c) => c[0].includes('availability'))).toBeUndefined(); - }); - - it('Should not ping devices on blocklist by friendly name', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_blocklist'], ['bulb_color']) - await resetExtension(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(0); - }); - - it('Should not ping devices on blacklist by friendly name', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_blacklist'], ['bulb_color']) - await resetExtension(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(0); - }); - - it('Should not ping devices on blocklist by IEEE address', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_blocklist'], [device.ieeeAddr]); - await resetExtension(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(0); - }); - - it('Should ping forced pingable devices', async () => { - const device = zigbeeHerdsman.devices.E11_G13; - const endpoint = device.getEndpoint(1); - const data = {modelID: 'test'} - const payload = {data, cluster: 'genOnOff', device, endpoint, type: 'readResponse', linkquality: 10}; - await zigbeeHerdsman.events.message(payload); - await flushPromises(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(1); - }); - - it('Should ping devices on passlist by friendly name if availability_passlist is set', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_passlist'], ['bulb_color']); - await resetExtension(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(1); - }); - - it('Should ping devices on whitelist by friendly name if availability_whitelist is set', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_whitelist'], ['bulb_color']); - await resetExtension(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(1); - }); - - it('Should ping devices on passlist by IEEE address if availability_passlist is set', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - settings.set(['advanced', 'availability_passlist'], [device.ieeeAddr]); - await resetExtension(); - device.ping.mockClear(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(1); - }); - - it('Should not ping devices not in passlist if availability_passlist is set', async () => { - const device = zigbeeHerdsman.devices.bulb; - device.ping.mockClear(); - extension.stateLookup[device.ieeeAddr] = false; - settings.set(['advanced', 'availability_passlist'], ['0x000b57fffec6a5b3']) - await resetExtension(); - jest.advanceTimersByTime(11 * 1000); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(0); - MQTT.publish.mockClear(); - await zigbeeHerdsman.events.deviceAnnounce({device}); - }); - - it('Should not read when device has no modelID and reconnects', async () => { - const device = zigbeeHerdsman.devices.nomodel; - extension.stateLookup[device.ieeeAddr] = true; - const endpoint = device.getEndpoint(1); - await zigbeeHerdsman.events.deviceAnnounce({device}); - await flushPromises(); - expect(endpoint.read).toHaveBeenCalledTimes(0); - }); - - it('Should not read when device has is unsupported', async () => { - const device = zigbeeHerdsman.devices.unsupported_router; - extension.stateLookup[device.ieeeAddr] = true; - const endpoint = device.getEndpoint(1); - await zigbeeHerdsman.events.deviceAnnounce({device}); - await flushPromises(); - expect(endpoint.read).toHaveBeenCalledTimes(0); - }); - - it('Should publish availability when end device joins', async () => { - delete extension.stateLookup[zigbeeHerdsman.devices.WXKG02LM_rev1.ieeeAddr]; - const device = zigbeeHerdsman.devices.WXKG02LM_rev1; - const payload = {device}; - MQTT.publish.mockClear(); - await zigbeeHerdsman.events.deviceJoined(payload); - await flushPromises(); - expect(device.ping).toHaveBeenCalledTimes(0); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/button_double_key/availability', - 'online', - { retain: true, qos: 0 }, - expect.any(Function) - ); - }); - - it('Should mark non-pingable device as non-available when offline for longer than 24 hours', async () => { - const device = zigbeeHerdsman.devices.remote; - const defaultLastSeen = device.lastSeen; - device.lastSeen = Date.now(); - MQTT.publish.mockClear(); - jest.advanceTimersByTime(1000 * 60 * 60 * 1); // 1 hours - device.lastSeen = device.lastSeen - (1000 * 60 * 60 * 25); - jest.advanceTimersByTime(1000 * 60 * 60 * 1); // 1 hours - expect(MQTT.publish).toHaveBeenCalledTimes(2); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/remote/availability', - 'offline', - { retain: true, qos: 0 }, - expect.any(Function) - ); - - // Shouldn't do anything more when device is removed - settings.removeDevice(device.ieeeAddr); - jest.advanceTimersByTime(1000 * 60 * 60 * 1); // 1 hours - expect(MQTT.publish).toHaveBeenCalledTimes(2); - - device.lastSeen = defaultLastSeen; - }); - - it('Should republish existing state on MQTT connected', async () => { - const device = zigbeeHerdsman.devices.bulb_color; - MQTT.publish.mockClear(); - extension.stateLookup[device.ieeeAddr] = false; - await extension.start(); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bulb_color/availability', - 'offline', - { retain: true, qos: 0 }, - expect.any(Function) - ); - }); - - it('Should clear retained availability topic when device is remove', async () => { - MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/remove', 'bulb_color'); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bulb_color/availability', - null, - {retain: true, qos: 0}, expect.any(Function) - ); - }); - - it('Should clear retained availability topic when device is renamed', async () => { - MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bridge/request/device/rename', stringify({"from": "bulb_color", "to": "bulb_color_new_name"})); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledWith( - 'zigbee2mqtt/bulb_color/availability', - null, - {retain: true, qos: 0}, expect.any(Function) - ); - }); - - it('Should clear retained availability topic when device does not exist', async () => { - MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/not_existing_hahaha/availability', 'offline'); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/not_existing_hahaha/availability', null, {retain: true, qos: 0}, expect.any(Function)); - - MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/0x000b57fffec6a5b3/availability', 'offline'); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledTimes(1); - expect(MQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/0x000b57fffec6a5b3/availability', null, {retain: true, qos: 0}, expect.any(Function)); - - MQTT.publish.mockClear(); - MQTT.events.message('zigbee2mqtt/bulb_color/availability', 'offline'); - await flushPromises(); - expect(MQTT.publish).toHaveBeenCalledTimes(0); - }); -}); diff --git a/test/settings.test.js b/test/settings.test.js index 5c4e096cb..d91069d09 100644 --- a/test/settings.test.js +++ b/test/settings.test.js @@ -67,7 +67,6 @@ describe('Settings', () => { process.env['ZIGBEE2MQTT_CONFIG_SERIAL_DISABLE_LED'] = 'true'; process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_SOFT_RESET_TIMEOUT'] = 1; process.env['ZIGBEE2MQTT_CONFIG_EXPERIMENTAL_OUTPUT'] = 'csvtest'; - process.env['ZIGBEE2MQTT_CONFIG_ADVANCED_AVAILABILITY_BLOCKLIST'] = '["0x43597f0dac781b1e", "x223b0aef2ae8d1b0"]'; process.env['ZIGBEE2MQTT_CONFIG_MAP_OPTIONS_GRAPHVIZ_COLORS_FILL'] = '{"enddevice": "#ff0000", "coordinator": "#00ff00", "router": "#0000ff"}'; process.env['ZIGBEE2MQTT_CONFIG_MQTT_BASE_TOPIC'] = 'testtopic'; @@ -79,7 +78,6 @@ describe('Settings', () => { expected.serial.disable_led = true; expected.advanced.soft_reset_timeout = 1; expected.experimental.output = 'csvtest'; - expected.advanced.availability_blocklist = ['0x43597f0dac781b1e', 'x223b0aef2ae8d1b0']; expected.map_options.graphviz.colors.fill = {enddevice: '#ff0000', coordinator: '#00ff00', router: '#0000ff'}; expected.mqtt.base_topic = 'testtopic';