diff --git a/lib/extension/bridge.ts b/lib/extension/bridge.ts index 2ce3f7c2f..dfcbc0284 100644 --- a/lib/extension/bridge.ts +++ b/lib/extension/bridge.ts @@ -6,7 +6,6 @@ import Transport from 'winston-transport'; import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; import objectAssignDeep from 'object-assign-deep'; -import {detailedDiff} from 'deep-object-diff'; import Extension from './extension'; import Device from '../model/device'; import Group from '../model/group'; @@ -148,37 +147,7 @@ export default class Bridge extends Extension { throw new Error(`Invalid payload`); } - const diff: KeyValue = detailedDiff(settings.get(), message.options); - - // Remove any settings that are in the deleted.diff but not in the passed options - const cleanupDeleted = (options: KeyValue, deleted: KeyValue): void => { - for (const key of Object.keys(deleted)) { - if (!(key in options)) { - delete deleted[key]; - } else if (!Array.isArray(options[key])) { - cleanupDeleted(options[key], deleted[key]); - } - } - }; - cleanupDeleted(message.options, diff.deleted); - - // objectAssignDeep requires object prototype which is missing from detailedDiff, therefore clone - const newSettings = objectAssignDeep({}, utils.clone(diff.added), utils.clone(diff.updated), - utils.clone(diff.deleted)); - - // deep-object-diff converts arrays to objects, set original array back here - const convertBackArray = (before: KeyValue, after: KeyValue): void => { - for (const [key, afterValue] of Object.entries(after)) { - const beforeValue = before[key]; - if (Array.isArray(beforeValue)) { - after[key] = beforeValue; - } else if (afterValue && typeof beforeValue === 'object') { - convertBackArray(beforeValue, afterValue); - } - } - }; - convertBackArray(message.options, newSettings); - + const newSettings = utils.computeSettingsToChange(settings.get(), message.options); const restartRequired = settings.apply(newSettings); if (restartRequired) this.restartRequired = true; @@ -421,15 +390,23 @@ export default class Bridge extends Extension { const ID = message.id; const entity = this.getEntity(entityType, ID); + const currentOptions = entityType === 'device' ? settings.get().devices[entity.ID] : + settings.get().groups[entity.ID]; + const options = utils.computeSettingsToChange(currentOptions, message.options); const oldOptions = objectAssignDeep({}, cleanup(entity.options)); - settings.changeEntityOptions(ID, message.options); + const restartRequired = settings.changeEntityOptions(ID, options); + if (restartRequired) this.restartRequired = true; const newOptions = cleanup(entity.options); await this.publishInfo(); logger.info(`Changed config for ${entityType} ${ID}`); this.eventBus.emitEntityOptionsChanged({from: oldOptions, to: newOptions, entity}); - return utils.getResponse(message, {from: oldOptions, to: newOptions, id: ID}, null); + return utils.getResponse( + message, + {from: oldOptions, to: newOptions, id: ID, restart_required: this.restartRequired}, + null, + ); } @bind async deviceConfigureReporting(message: string | KeyValue): Promise { @@ -679,6 +656,7 @@ export default class Bridge extends Extension { network_address: device.zh.networkAddress, supported: !!device.definition, friendly_name: device.name, + disabled: !!device.options.disabled, description: device.options.description, definition: this.getDefinitionPayload(device), power_source: device.zh.powerSource, diff --git a/lib/extension/configure.ts b/lib/extension/configure.ts index 76bfdc4e8..f3f181cdf 100644 --- a/lib/extension/configure.ts +++ b/lib/extension/configure.ts @@ -87,7 +87,7 @@ export default class Configure extends Extension { private async configure(device: Device, event: 'started' | 'zigbee_event' | 'reporting_disabled' | 'mqtt_message', force=false, thowError=false): Promise { if (!force) { - if (!device.definition?.configure || !device.zh.interviewCompleted) { + if (device.options.disabled || !device.definition?.configure || !device.zh.interviewCompleted) { return; } diff --git a/lib/extension/groups.ts b/lib/extension/groups.ts index c41ff0631..ac002cc85 100644 --- a/lib/extension/groups.ts +++ b/lib/extension/groups.ts @@ -142,6 +142,7 @@ export default class Groups extends Extension { const groupsToPublish: Set = new Set(); for (const member of entity.zh.members) { const device = this.zigbee.resolveEntity(member.getDevice()) as Device; + if (device.options.disabled) continue; const exposes = device.exposes(); const memberPayload: KeyValue = {}; Object.keys(payload).forEach((key) => { diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 74a7c1523..e8addc0d7 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -1214,7 +1214,10 @@ export default class HomeAssistant extends Extension { payload.availability.push({topic: `${baseTopic}/availability`}); } - if (!settings.get().advanced.legacy_availability_payload) { + if (entity.isDevice() && entity.options.disabled) { + // Mark disabled device always as unavailable + payload.availability.forEach((a: KeyValue) => a.value_template = '{{ "offline" }}'); + } else if (!settings.get().advanced.legacy_availability_payload) { payload.availability.forEach((a: KeyValue) => a.value_template = '{{ value_json.state }}'); } diff --git a/lib/extension/networkMap.ts b/lib/extension/networkMap.ts index 73969d445..57459fb68 100644 --- a/lib/extension/networkMap.ts +++ b/lib/extension/networkMap.ts @@ -214,7 +214,7 @@ export default class NetworkMap extends Extension { async networkScan(includeRoutes: boolean): Promise { logger.info(`Starting network scan (includeRoutes '${includeRoutes}')`); - const devices = this.zigbee.devices().filter((d) => d.zh.type !== 'GreenPower'); + const devices = this.zigbee.devices().filter((d) => d.zh.type !== 'GreenPower' && !d.options.disabled); const lqis: Map = new Map(); const routingTables: Map = new Map(); const failed: Map = new Map(); diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts index cd9b49a19..b625a32a3 100644 --- a/lib/types/types.d.ts +++ b/lib/types/types.d.ts @@ -279,6 +279,7 @@ declare global { interface DeviceOptions { ID?: string, + disabled?: boolean, retention?: number, availability?: boolean | {timeout: number}, optimistic?: boolean, diff --git a/lib/util/settings.schema.json b/lib/util/settings.schema.json index 8bed6896f..d19d149d5 100644 --- a/lib/util/settings.schema.json +++ b/lib/util/settings.schema.json @@ -794,6 +794,12 @@ "title": "Retain", "description": "Retain MQTT messages of this device" }, + "disabled": { + "type": "boolean", + "title": "Disabled", + "description": "Disables the device (excludes device from network scans, availability and group state updates)", + "requiresRestart": true + }, "retention": { "type": "number", "title": "Retention", diff --git a/lib/util/settings.ts b/lib/util/settings.ts index aabbf5af1..ae03b898b 100644 --- a/lib/util/settings.ts +++ b/lib/util/settings.ts @@ -3,7 +3,7 @@ import utils from './utils'; import objectAssignDeep from 'object-assign-deep'; import path from 'path'; import yaml from './yaml'; -import Ajv from 'ajv'; +import Ajv, {ValidateFunction} from 'ajv'; import schemaJson from './settings.schema.json'; export let schema = schemaJson; // @ts-ignore @@ -31,7 +31,10 @@ const file = process.env.ZIGBEE2MQTT_CONFIG ?? data.joinPath('configuration.yaml const ajvSetting = new Ajv({allErrors: true}).addKeyword('requiresRestart').compile(schemaJson); const ajvRestartRequired = new Ajv({allErrors: true}) .addKeyword({keyword: 'requiresRestart', validate: (s: unknown) => !s}).compile(schemaJson); - +const ajvRestartRequiredDeviceOptions = new Ajv({allErrors: true}) + .addKeyword({keyword: 'requiresRestart', validate: (s: unknown) => !s}).compile(schemaJson.definitions.device); +const ajvRestartRequiredGroupOptions = new Ajv({allErrors: true}) + .addKeyword({keyword: 'requiresRestart', validate: (s: unknown) => !s}).compile(schemaJson.definitions.group); const defaults: RecursivePartial = { permit_join: false, external_converters: [], @@ -667,21 +670,27 @@ export function removeGroup(IDorName: string | number): void { write(); } -export function changeEntityOptions(IDorName: string, newOptions: KeyValue): void { +export function changeEntityOptions(IDorName: string, newOptions: KeyValue): boolean { const settings = getInternalSettings(); delete newOptions.friendly_name; delete newOptions.devices; + let validator: ValidateFunction; if (getDevice(IDorName)) { objectAssignDeep(settings.devices[getDevice(IDorName).ID], newOptions); utils.removeNullPropertiesFromObject(settings.devices[getDevice(IDorName).ID]); + validator = ajvRestartRequiredDeviceOptions; } else if (getGroup(IDorName)) { objectAssignDeep(settings.groups[getGroup(IDorName).ID], newOptions); utils.removeNullPropertiesFromObject(settings.groups[getGroup(IDorName).ID]); + validator = ajvRestartRequiredGroupOptions; } else { throw new Error(`Device or group '${IDorName}' does not exist`); } write(); + validator(newOptions); + const restartRequired = validator.errors && !!validator.errors.find((e) => e.keyword === 'requiresRestart'); + return restartRequired; } export function changeFriendlyName(IDorName: string, newName: string): void { diff --git a/lib/util/utils.ts b/lib/util/utils.ts index 5f31507d1..07479a68d 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -4,6 +4,8 @@ import data from './data'; import vm from 'vm'; import fs from 'fs'; import path from 'path'; +import {detailedDiff} from 'deep-object-diff'; +import objectAssignDeep from 'object-assign-deep'; // construct a local ISO8601 string (instead of UTC-based) // Example: @@ -295,6 +297,8 @@ function isAvailabilityEnabledForEntity(entity: Device | Group, settings: Settin const enabledGlobal = settings.advanced.availability_timeout || settings.availability; if (!enabledGlobal) return false; + if (entity.isDevice() && entity.options.disabled) return false; + const passlist = settings.advanced.availability_passlist.concat(settings.advanced.availability_whitelist); if (passlist.length > 0) { return passlist.includes(entity.name) || passlist.includes(entity.ieeeAddr); @@ -363,11 +367,44 @@ function clone(obj: KeyValue): KeyValue { return JSON.parse(JSON.stringify(obj)); } +function computeSettingsToChange(current: KeyValue, new_: KeyValue): KeyValue { + const diff: KeyValue = detailedDiff(current, new_); + + // Remove any settings that are in the deleted.diff but not in the passed options + const cleanupDeleted = (options: KeyValue, deleted: KeyValue): void => { + for (const key of Object.keys(deleted)) { + if (!(key in options)) { + delete deleted[key]; + } else if (!Array.isArray(options[key])) { + cleanupDeleted(options[key], deleted[key]); + } + } + }; + cleanupDeleted(new_, diff.deleted); + + // objectAssignDeep requires object prototype which is missing from detailedDiff, therefore clone + const newSettings = objectAssignDeep({}, clone(diff.added), clone(diff.updated), clone(diff.deleted)); + + // deep-object-diff converts arrays to objects, set original array back here + const convertBackArray = (before: KeyValue, after: KeyValue): void => { + for (const [key, afterValue] of Object.entries(after)) { + const beforeValue = before[key]; + if (Array.isArray(beforeValue)) { + after[key] = beforeValue; + } else if (afterValue && typeof beforeValue === 'object') { + convertBackArray(beforeValue, afterValue); + } + } + }; + convertBackArray(new_, newSettings); + return newSettings; +} + export default { endpointNames, capitalize, getZigbee2MQTTVersion, getDependencyVersion, formatDate, objectHasProperties, equalsPartial, getObjectProperty, getResponse, parseJSON, loadModuleFromText, loadModuleFromFile, getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase, parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep, sanitizeImageParameter, isAvailabilityEnabledForEntity, publishLastSeen, availabilityPayload, - getAllFiles, filterProperties, flatten, arrayUnique, clone, + getAllFiles, filterProperties, flatten, arrayUnique, clone, computeSettingsToChange, }; diff --git a/test/availability.test.js b/test/availability.test.js index 132d360a6..cb4584db3 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -179,6 +179,15 @@ describe('Availability', () => { expect(devices.bulb_color.ping).toHaveBeenCalledTimes(1); }); + it('Should not ping disabled devices', async () => { + settings.set(['devices', devices.bulb_color.ieeeAddr, 'disabled'], true); + await resetExtension(); + MQTT.publish.mockClear(); + + await advancedTime(utils.minutes(15)); + expect(devices.bulb_color.ping).toHaveBeenCalledTimes(0); + }); + it('Should allow to change availability timeout via avaiability options', async () => { settings.set(['availability'], {active: {timeout: 30}}); await resetExtension(); diff --git a/test/bridge.test.js b/test/bridge.test.js index 80b36d28f..bb2a15261 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -89,9 +89,10 @@ describe('Bridge', () => { it('Should publish devices on startup', async () => { await resetExtension(); + // console.log(MQTT.publish.mock.calls.find((c) => c[0] === 'zigbee2mqtt/bridge/devices')[1]); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/devices', - stringify([{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":[],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"Coordinator","ieee_address":"0x00124b00120144ae","interview_completed":false,"interviewing":false,"model_id":null,"network_address":0,"power_source":null,"software_build_id":null,"supported":false,"type":"Coordinator"},{"date_code":null,"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454}],"property":"color_temp","type":"numeric","unit":"mired","value_max":454,"value_min":250},{"access":7,"description":"Color temperature after cold power on of this light","name":"color_temp_startup","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454},{"description":"Restore previous color_temp on cold power on","name":"previous","value":65535}],"property":"color_temp_startup","type":"numeric","unit":"mired","value_max":454,"value_min":250}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":7,"description":"Controls the behavior when the device is powered on after power loss","name":"power_on_behavior","property":"power_on_behavior","type":"enum","values":["off","on","toggle","previous"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"LED1545G12","options":[{"access":2,"description":"Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).","name":"transition","property":"transition","type":"numeric","value_min":0},{"access":2,"description":"When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).","name":"color_sync","property":"color_sync","type":"binary","value_off":false,"value_on":true}],"supports_ota":true,"vendor":"IKEA"},"description":"this is my bulb","endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]},"configured_reportings":[{"attribute":"onOff","cluster":"genOnOff","maximum_report_interval":10,"minimum_report_interval":1,"reportable_change":20}],"scenes":[]}},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","interview_completed":true,"interviewing":false,"model_id":"TRADFRI bulb E27 WS opal 980lm","network_address":40369,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"},{"date_code":"2019.09","definition":{"description":"Hue Go","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","presets":[{"description":"Coolest temperature supported","name":"coolest","value":150},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":500}],"property":"color_temp","type":"numeric","unit":"mired","value_max":500,"value_min":150},{"access":7,"description":"Color temperature after cold power on of this light","name":"color_temp_startup","presets":[{"description":"Coolest temperature supported","name":"coolest","value":150},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":500},{"description":"Restore previous color_temp on cold power on","name":"previous","value":65535}],"property":"color_temp_startup","type":"numeric","unit":"mired","value_max":500,"value_min":150},{"description":"Color of this light in the CIE 1931 color space (x/y)","features":[{"access":7,"name":"x","property":"x","type":"numeric"},{"access":7,"name":"y","property":"y","type":"numeric"}],"name":"color_xy","property":"color","type":"composite"},{"description":"Color of this light expressed as hue/saturation","features":[{"access":7,"name":"hue","property":"hue","type":"numeric"},{"access":7,"name":"saturation","property":"saturation","type":"numeric"}],"name":"color_hs","property":"color","type":"composite"}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":7,"description":"Controls the behavior when the device is powered on after power loss","name":"power_on_behavior","property":"power_on_behavior","type":"enum","values":["off","on","toggle","previous"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"7146060PH","options":[{"access":2,"description":"Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).","name":"transition","property":"transition","type":"numeric","value_min":0},{"access":2,"description":"When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).","name":"color_sync","property":"color_sync","type":"binary","value_off":false,"value_on":true}],"supports_ota":true,"vendor":"Philips"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]},"configured_reportings":[],"scenes":[{"id":1,"name":"Chill scene"}]}},"friendly_name":"bulb_color_2","ieee_address":"0x000b57fffec6a5b4","interview_completed":true,"interviewing":false,"manufacturer":"Philips","model_id":"LLC020","network_address":401292,"power_source":"Mains (single phase)","software_build_id":"5.127.1.26581","supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"Hue dimmer switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["on_press","on_hold","on_hold_release","up_press","up_hold","up_hold_release","down_press","down_hold","down_hold_release","off_press","off_hold","off_hold_release"]},{"access":1,"name":"action_duration","property":"action_duration","type":"numeric","unit":"second"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"324131092621","options":[{"access":2,"description":"Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).","name":"legacy","property":"legacy","type":"binary","value_off":false,"value_on":true},{"description":"Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. Only works when legacy is false.","features":[{"access":2,"description":"Delta per interval, 20 by default","name":"delta","property":"delta","type":"numeric","value_min":0},{"access":2,"description":"Interval duration","name":"interval","property":"interval","type":"numeric","unit":"ms","value_min":0}],"name":"simulated_brightness","property":"simulated_brightness","type":"composite"}],"supports_ota":true,"vendor":"Philips"},"endpoints":{"1":{"bindings":[{"cluster":"genLevelCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"lightingColorCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"id":1,"type":"group"}},{"cluster":"genLevelCtrl","target":{"id":1,"type":"group"}}],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[],"scenes":[]},"2":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genOta","genOnOff"]},"configured_reportings":[],"scenes":[]}},"friendly_name":"remote","ieee_address":"0x0017880104e45517","interview_completed":true,"interviewing":false,"model_id":"RWL021","network_address":6535,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":null,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[],"scenes":[]}},"friendly_name":"0x0017880104e45518","ieee_address":"0x0017880104e45518","interview_completed":true,"interviewing":false,"manufacturer":"notSupportedMfg","model_id":"notSupportedModelID","network_address":6536,"power_source":"Battery","software_build_id":null,"supported":false,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara wireless switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Voltage of the battery in millivolts","name":"voltage","property":"voltage","type":"numeric","unit":"mV"},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["single","double","triple","quadruple","hold","release"]},{"access":1,"description":"Temperature of the device","name":"device_temperature","property":"device_temperature","type":"numeric","unit":"°C"},{"access":1,"description":"Number of power outages (since last pairing)","name":"power_outage_count","property":"power_outage_count","type":"numeric"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WXKG11LM","options":[{"access":2,"description":"Calibrates the device_temperature value (absolute offset), takes into effect on next report of device.","name":"device_temperature_calibration","property":"device_temperature_calibration","type":"numeric"},{"access":2,"description":"Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).","name":"legacy","property":"legacy","type":"binary","value_off":false,"value_on":true}],"supports_ota":false,"vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[{"attribute":1337,"cluster":"genOnOff","maximum_report_interval":10,"minimum_report_interval":1,"reportable_change":20}],"scenes":[]}},"friendly_name":"button","ieee_address":"0x0017880104e45520","interview_completed":true,"interviewing":false,"model_id":"lumi.sensor_switch.aq2","network_address":6537,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara temperature, humidity and pressure sensor","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Measured temperature value","name":"temperature","property":"temperature","type":"numeric","unit":"°C"},{"access":1,"description":"Measured relative humidity","name":"humidity","property":"humidity","type":"numeric","unit":"%"},{"access":1,"description":"The measured atmospheric pressure","name":"pressure","property":"pressure","type":"numeric","unit":"hPa"},{"access":1,"description":"Voltage of the battery in millivolts","name":"voltage","property":"voltage","type":"numeric","unit":"mV"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WSDCGQ11LM","options":[{"access":2,"description":"Calibrates the temperature value (absolute offset), takes into effect on next report of device.","name":"temperature_calibration","property":"temperature_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for temperature, takes into effect on next report of device.","name":"temperature_precision","property":"temperature_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the pressure value (absolute offset), takes into effect on next report of device.","name":"pressure_calibration","property":"pressure_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for pressure, takes into effect on next report of device.","name":"pressure_precision","property":"pressure_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Number of digits after decimal point for humidity, takes into effect on next report of device.","name":"humidity_precision","property":"humidity_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the humidity value (absolute offset), takes into effect on next report of device.","name":"humidity_calibration","property":"humidity_calibration","type":"numeric"}],"supports_ota":false,"vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"weather_sensor","ieee_address":"0x0017880104e45522","interview_completed":true,"interviewing":false,"model_id":"lumi.weather","network_address":6539,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Mi power plug ZigBee","exposes":[{"features":[{"access":7,"description":"On/off state of the switch","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"}],"type":"switch"},{"access":5,"description":"Instantaneous measured power","name":"power","property":"power","type":"numeric","unit":"W"},{"access":1,"description":"Sum of consumed energy","name":"energy","property":"energy","type":"numeric","unit":"kWh"},{"access":1,"description":"Temperature of the device","name":"device_temperature","property":"device_temperature","type":"numeric","unit":"°C"},{"access":7,"description":"Enable/disable the power outage memory, this recovers the on/off mode after power failure","name":"power_outage_memory","property":"power_outage_memory","type":"binary","value_off":false,"value_on":true},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"ZNCZ02LM","options":[{"access":2,"description":"Calibrates the power value (percentual offset), takes into effect on next report of device.","name":"power_calibration","property":"power_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for power, takes into effect on next report of device.","name":"power_precision","property":"power_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the energy value (percentual offset), takes into effect on next report of device.","name":"energy_calibration","property":"energy_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for energy, takes into effect on next report of device.","name":"energy_precision","property":"energy_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the device_temperature value (absolute offset), takes into effect on next report of device.","name":"device_temperature_calibration","property":"device_temperature_calibration","type":"numeric"}],"supports_ota":true,"vendor":"Xiaomi"},"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"power_plug","ieee_address":"0x0017880104e45524","interview_completed":true,"interviewing":false,"model_id":"lumi.plug","network_address":6540,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"zigfred plus smart in-wall switch","exposes":[{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["button_1_single","button_1_double","button_1_hold","button_1_release","button_2_single","button_2_double","button_2_hold","button_2_release","button_3_single","button_3_double","button_3_hold","button_3_release","button_4_single","button_4_double","button_4_hold","button_4_release"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0},{"endpoint":"l1","features":[{"access":7,"description":"On/off state of this light","endpoint":"l1","name":"state","property":"state_l1","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l1","name":"brightness","property":"brightness_l1","type":"numeric","value_max":254,"value_min":0},{"description":"Color of this light in the CIE 1931 color space (x/y)","endpoint":"l1","features":[{"access":7,"name":"x","property":"x","type":"numeric"},{"access":7,"name":"y","property":"y","type":"numeric"}],"name":"color_xy","property":"color_l1","type":"composite"}],"type":"light"},{"endpoint":"l2","features":[{"access":7,"description":"On/off state of this light","endpoint":"l2","name":"state","property":"state_l2","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l2","name":"brightness","property":"brightness_l2","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l3","features":[{"access":7,"description":"On/off state of this light","endpoint":"l3","name":"state","property":"state_l3","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l3","name":"brightness","property":"brightness_l3","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l4","features":[{"access":7,"description":"On/off state of this light","endpoint":"l4","name":"state","property":"state_l4","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l4","name":"brightness","property":"brightness_l4","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l5","features":[{"access":7,"description":"On/off state of this light","endpoint":"l5","name":"state","property":"state_l5","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l5","name":"brightness","property":"brightness_l5","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l6","features":[{"access":7,"endpoint":"l6","name":"state","property":"state_l6","type":"enum","values":["OPEN","CLOSE","STOP"]},{"access":7,"description":"Position of this cover","endpoint":"l6","name":"position","property":"position_l6","type":"numeric","value_max":100,"value_min":0},{"access":7,"description":"Tilt of this cover","endpoint":"l6","name":"tilt","property":"tilt_l6","type":"numeric","value_max":100,"value_min":0}],"type":"cover"},{"endpoint":"l7","features":[{"access":7,"endpoint":"l7","name":"state","property":"state_l7","type":"enum","values":["OPEN","CLOSE","STOP"]},{"access":7,"description":"Position of this cover","endpoint":"l7","name":"position","property":"position_l7","type":"numeric","value_max":100,"value_min":0},{"access":7,"description":"Tilt of this cover","endpoint":"l7","name":"tilt","property":"tilt_l7","type":"numeric","value_max":100,"value_min":0}],"type":"cover"}],"model":"ZFP-1A-CH","options":[{"access":2,"description":"Front Surface LED enabled","name":"front_surface_enabled","property":"front_surface_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 1 enabled","name":"dimmer_1_enabled","property":"dimmer_1_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 1 dimmable","name":"dimmer_1_dimming_enabled","property":"dimmer_1_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 2 enabled","name":"dimmer_2_enabled","property":"dimmer_2_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 2 dimmable","name":"dimmer_2_dimming_enabled","property":"dimmer_2_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 3 enabled","name":"dimmer_3_enabled","property":"dimmer_3_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 3 dimmable","name":"dimmer_3_dimming_enabled","property":"dimmer_3_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 4 enabled","name":"dimmer_4_enabled","property":"dimmer_4_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 4 dimmable","name":"dimmer_4_dimming_enabled","property":"dimmer_4_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 1 enabled","name":"cover_1_enabled","property":"cover_1_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 1 tiltable","name":"cover_1_tilt_enabled","property":"cover_1_tilt_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 2 enabled","name":"cover_2_enabled","property":"cover_2_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 2 tiltable","name":"cover_2_tilt_enabled","property":"cover_2_tilt_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).","name":"color_sync","property":"color_sync","type":"binary","value_off":false,"value_on":true},{"access":2,"description":"Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).","name":"transition","property":"transition","type":"numeric","value_min":0},{"access":2,"description":"Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false).","name":"invert_cover","property":"invert_cover","type":"binary","value_off":false,"value_on":true}],"supports_ota":false,"vendor":"Siglis"},"endpoints":{"10":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"11":{"bindings":[],"clusters":{"input":["genBasic","genScenes","closuresWindowCovering"],"output":[]},"configured_reportings":[],"scenes":[]},"12":{"bindings":[],"clusters":{"input":["genBasic","genScenes","closuresWindowCovering"],"output":[]},"configured_reportings":[],"scenes":[]},"5":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"7":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"8":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"9":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"zigfred_plus","ieee_address":"0xf4ce368a38be56a1","interview_completed":true,"interviewing":false,"manufacturer":"Siglis","model_id":"zigfred plus","network_address":6589,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"}]), + stringify([{"date_code":null,"definition":null,"disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":[],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"Coordinator","ieee_address":"0x00124b00120144ae","interview_completed":false,"interviewing":false,"model_id":null,"network_address":0,"power_source":null,"software_build_id":null,"supported":false,"type":"Coordinator"},{"date_code":null,"definition":{"description":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454}],"property":"color_temp","type":"numeric","unit":"mired","value_max":454,"value_min":250},{"access":7,"description":"Color temperature after cold power on of this light","name":"color_temp_startup","presets":[{"description":"Coolest temperature supported","name":"coolest","value":250},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":454},{"description":"Restore previous color_temp on cold power on","name":"previous","value":65535}],"property":"color_temp_startup","type":"numeric","unit":"mired","value_max":454,"value_min":250}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":7,"description":"Controls the behavior when the device is powered on after power loss","name":"power_on_behavior","property":"power_on_behavior","type":"enum","values":["off","on","toggle","previous"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"LED1545G12","options":[{"access":2,"description":"Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).","name":"transition","property":"transition","type":"numeric","value_min":0},{"access":2,"description":"When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).","name":"color_sync","property":"color_sync","type":"binary","value_off":false,"value_on":true}],"supports_ota":true,"vendor":"IKEA"},"description":"this is my bulb","disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]},"configured_reportings":[{"attribute":"onOff","cluster":"genOnOff","maximum_report_interval":10,"minimum_report_interval":1,"reportable_change":20}],"scenes":[]}},"friendly_name":"bulb","ieee_address":"0x000b57fffec6a5b2","interview_completed":true,"interviewing":false,"model_id":"TRADFRI bulb E27 WS opal 980lm","network_address":40369,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"},{"date_code":"2019.09","definition":{"description":"Hue Go","exposes":[{"features":[{"access":7,"description":"On/off state of this light","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","name":"brightness","property":"brightness","type":"numeric","value_max":254,"value_min":0},{"access":7,"description":"Color temperature of this light","name":"color_temp","presets":[{"description":"Coolest temperature supported","name":"coolest","value":150},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":500}],"property":"color_temp","type":"numeric","unit":"mired","value_max":500,"value_min":150},{"access":7,"description":"Color temperature after cold power on of this light","name":"color_temp_startup","presets":[{"description":"Coolest temperature supported","name":"coolest","value":150},{"description":"Cool temperature (250 mireds / 4000 Kelvin)","name":"cool","value":250},{"description":"Neutral temperature (370 mireds / 2700 Kelvin)","name":"neutral","value":370},{"description":"Warm temperature (454 mireds / 2200 Kelvin)","name":"warm","value":454},{"description":"Warmest temperature supported","name":"warmest","value":500},{"description":"Restore previous color_temp on cold power on","name":"previous","value":65535}],"property":"color_temp_startup","type":"numeric","unit":"mired","value_max":500,"value_min":150},{"description":"Color of this light in the CIE 1931 color space (x/y)","features":[{"access":7,"name":"x","property":"x","type":"numeric"},{"access":7,"name":"y","property":"y","type":"numeric"}],"name":"color_xy","property":"color","type":"composite"},{"description":"Color of this light expressed as hue/saturation","features":[{"access":7,"name":"hue","property":"hue","type":"numeric"},{"access":7,"name":"saturation","property":"saturation","type":"numeric"}],"name":"color_hs","property":"color","type":"composite"}],"type":"light"},{"access":2,"description":"Triggers an effect on the light (e.g. make light blink for a few seconds)","name":"effect","property":"effect","type":"enum","values":["blink","breathe","okay","channel_change","finish_effect","stop_effect"]},{"access":7,"description":"Controls the behavior when the device is powered on after power loss","name":"power_on_behavior","property":"power_on_behavior","type":"enum","values":["off","on","toggle","previous"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"7146060PH","options":[{"access":2,"description":"Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).","name":"transition","property":"transition","type":"numeric","value_min":0},{"access":2,"description":"When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).","name":"color_sync","property":"color_sync","type":"binary","value_off":false,"value_on":true}],"supports_ota":true,"vendor":"Philips"},"disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":["genScenes","genOta"]},"configured_reportings":[],"scenes":[{"id":1,"name":"Chill scene"}]}},"friendly_name":"bulb_color_2","ieee_address":"0x000b57fffec6a5b4","interview_completed":true,"interviewing":false,"manufacturer":"Philips","model_id":"LLC020","network_address":401292,"power_source":"Mains (single phase)","software_build_id":"5.127.1.26581","supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"Hue dimmer switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["on_press","on_hold","on_hold_release","up_press","up_hold","up_hold_release","down_press","down_hold","down_hold_release","off_press","off_hold","off_hold_release"]},{"access":1,"name":"action_duration","property":"action_duration","type":"numeric","unit":"second"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"324131092621","options":[{"access":2,"description":"Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).","name":"legacy","property":"legacy","type":"binary","value_off":false,"value_on":true},{"description":"Simulate a brightness value. If this device provides a brightness_move_up or brightness_move_down action it is possible to specify the update interval and delta. The action_brightness_delta indicates the delta for each interval. Only works when legacy is false.","features":[{"access":2,"description":"Delta per interval, 20 by default","name":"delta","property":"delta","type":"numeric","value_min":0},{"access":2,"description":"Interval duration","name":"interval","property":"interval","type":"numeric","unit":"ms","value_min":0}],"name":"simulated_brightness","property":"simulated_brightness","type":"composite"}],"supports_ota":true,"vendor":"Philips"},"disabled":false,"endpoints":{"1":{"bindings":[{"cluster":"genLevelCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"lightingColorCtrl","target":{"endpoint":1,"ieee_address":"0x000b57fffec6a5b3","type":"endpoint"}},{"cluster":"genOnOff","target":{"id":1,"type":"group"}},{"cluster":"genLevelCtrl","target":{"id":1,"type":"group"}}],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[],"scenes":[]},"2":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genOta","genOnOff"]},"configured_reportings":[],"scenes":[]}},"friendly_name":"remote","ieee_address":"0x0017880104e45517","interview_completed":true,"interviewing":false,"model_id":"RWL021","network_address":6535,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":null,"disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[],"scenes":[]}},"friendly_name":"0x0017880104e45518","ieee_address":"0x0017880104e45518","interview_completed":true,"interviewing":false,"manufacturer":"notSupportedMfg","model_id":"notSupportedModelID","network_address":6536,"power_source":"Battery","software_build_id":null,"supported":false,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara wireless switch","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Voltage of the battery in millivolts","name":"voltage","property":"voltage","type":"numeric","unit":"mV"},{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["single","double","triple","quadruple","hold","release"]},{"access":1,"description":"Temperature of the device","name":"device_temperature","property":"device_temperature","type":"numeric","unit":"°C"},{"access":1,"description":"Number of power outages (since last pairing)","name":"power_outage_count","property":"power_outage_count","type":"numeric"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WXKG11LM","options":[{"access":2,"description":"Calibrates the device_temperature value (absolute offset), takes into effect on next report of device.","name":"device_temperature_calibration","property":"device_temperature_calibration","type":"numeric"},{"access":2,"description":"Set to false to disable the legacy integration (highly recommended), will change structure of the published payload (default true).","name":"legacy","property":"legacy","type":"binary","value_off":false,"value_on":true}],"supports_ota":false,"vendor":"Xiaomi"},"disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":["genBasic","genOnOff","genLevelCtrl","genScenes"]},"configured_reportings":[{"attribute":1337,"cluster":"genOnOff","maximum_report_interval":10,"minimum_report_interval":1,"reportable_change":20}],"scenes":[]}},"friendly_name":"button","ieee_address":"0x0017880104e45520","interview_completed":true,"interviewing":false,"model_id":"lumi.sensor_switch.aq2","network_address":6537,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Aqara temperature, humidity and pressure sensor","exposes":[{"access":1,"description":"Remaining battery in %","name":"battery","property":"battery","type":"numeric","unit":"%","value_max":100,"value_min":0},{"access":1,"description":"Measured temperature value","name":"temperature","property":"temperature","type":"numeric","unit":"°C"},{"access":1,"description":"Measured relative humidity","name":"humidity","property":"humidity","type":"numeric","unit":"%"},{"access":1,"description":"The measured atmospheric pressure","name":"pressure","property":"pressure","type":"numeric","unit":"hPa"},{"access":1,"description":"Voltage of the battery in millivolts","name":"voltage","property":"voltage","type":"numeric","unit":"mV"},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"WSDCGQ11LM","options":[{"access":2,"description":"Calibrates the temperature value (absolute offset), takes into effect on next report of device.","name":"temperature_calibration","property":"temperature_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for temperature, takes into effect on next report of device.","name":"temperature_precision","property":"temperature_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the pressure value (absolute offset), takes into effect on next report of device.","name":"pressure_calibration","property":"pressure_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for pressure, takes into effect on next report of device.","name":"pressure_precision","property":"pressure_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Number of digits after decimal point for humidity, takes into effect on next report of device.","name":"humidity_precision","property":"humidity_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the humidity value (absolute offset), takes into effect on next report of device.","name":"humidity_calibration","property":"humidity_calibration","type":"numeric"}],"supports_ota":false,"vendor":"Xiaomi"},"disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"weather_sensor","ieee_address":"0x0017880104e45522","interview_completed":true,"interviewing":false,"model_id":"lumi.weather","network_address":6539,"power_source":"Battery","software_build_id":null,"supported":true,"type":"EndDevice"},{"date_code":null,"definition":{"description":"Mi power plug ZigBee","exposes":[{"features":[{"access":7,"description":"On/off state of the switch","name":"state","property":"state","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"}],"type":"switch"},{"access":5,"description":"Instantaneous measured power","name":"power","property":"power","type":"numeric","unit":"W"},{"access":1,"description":"Sum of consumed energy","name":"energy","property":"energy","type":"numeric","unit":"kWh"},{"access":1,"description":"Temperature of the device","name":"device_temperature","property":"device_temperature","type":"numeric","unit":"°C"},{"access":7,"description":"Enable/disable the power outage memory, this recovers the on/off mode after power failure","name":"power_outage_memory","property":"power_outage_memory","type":"binary","value_off":false,"value_on":true},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0}],"model":"ZNCZ02LM","options":[{"access":2,"description":"Calibrates the power value (percentual offset), takes into effect on next report of device.","name":"power_calibration","property":"power_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for power, takes into effect on next report of device.","name":"power_precision","property":"power_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the energy value (percentual offset), takes into effect on next report of device.","name":"energy_calibration","property":"energy_calibration","type":"numeric"},{"access":2,"description":"Number of digits after decimal point for energy, takes into effect on next report of device.","name":"energy_precision","property":"energy_precision","type":"numeric","value_max":3,"value_min":0},{"access":2,"description":"Calibrates the device_temperature value (absolute offset), takes into effect on next report of device.","name":"device_temperature_calibration","property":"device_temperature_calibration","type":"numeric"}],"supports_ota":true,"vendor":"Xiaomi"},"disabled":false,"endpoints":{"1":{"bindings":[],"clusters":{"input":["genBasic"],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"power_plug","ieee_address":"0x0017880104e45524","interview_completed":true,"interviewing":false,"model_id":"lumi.plug","network_address":6540,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"},{"date_code":null,"definition":{"description":"zigfred plus smart in-wall switch","exposes":[{"access":1,"description":"Triggered action (e.g. a button click)","name":"action","property":"action","type":"enum","values":["button_1_single","button_1_double","button_1_hold","button_1_release","button_2_single","button_2_double","button_2_hold","button_2_release","button_3_single","button_3_double","button_3_hold","button_3_release","button_4_single","button_4_double","button_4_hold","button_4_release"]},{"access":1,"description":"Link quality (signal strength)","name":"linkquality","property":"linkquality","type":"numeric","unit":"lqi","value_max":255,"value_min":0},{"endpoint":"l1","features":[{"access":7,"description":"On/off state of this light","endpoint":"l1","name":"state","property":"state_l1","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l1","name":"brightness","property":"brightness_l1","type":"numeric","value_max":254,"value_min":0},{"description":"Color of this light in the CIE 1931 color space (x/y)","endpoint":"l1","features":[{"access":7,"name":"x","property":"x","type":"numeric"},{"access":7,"name":"y","property":"y","type":"numeric"}],"name":"color_xy","property":"color_l1","type":"composite"}],"type":"light"},{"endpoint":"l2","features":[{"access":7,"description":"On/off state of this light","endpoint":"l2","name":"state","property":"state_l2","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l2","name":"brightness","property":"brightness_l2","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l3","features":[{"access":7,"description":"On/off state of this light","endpoint":"l3","name":"state","property":"state_l3","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l3","name":"brightness","property":"brightness_l3","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l4","features":[{"access":7,"description":"On/off state of this light","endpoint":"l4","name":"state","property":"state_l4","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l4","name":"brightness","property":"brightness_l4","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l5","features":[{"access":7,"description":"On/off state of this light","endpoint":"l5","name":"state","property":"state_l5","type":"binary","value_off":"OFF","value_on":"ON","value_toggle":"TOGGLE"},{"access":7,"description":"Brightness of this light","endpoint":"l5","name":"brightness","property":"brightness_l5","type":"numeric","value_max":254,"value_min":0}],"type":"light"},{"endpoint":"l6","features":[{"access":7,"endpoint":"l6","name":"state","property":"state_l6","type":"enum","values":["OPEN","CLOSE","STOP"]},{"access":7,"description":"Position of this cover","endpoint":"l6","name":"position","property":"position_l6","type":"numeric","value_max":100,"value_min":0},{"access":7,"description":"Tilt of this cover","endpoint":"l6","name":"tilt","property":"tilt_l6","type":"numeric","value_max":100,"value_min":0}],"type":"cover"},{"endpoint":"l7","features":[{"access":7,"endpoint":"l7","name":"state","property":"state_l7","type":"enum","values":["OPEN","CLOSE","STOP"]},{"access":7,"description":"Position of this cover","endpoint":"l7","name":"position","property":"position_l7","type":"numeric","value_max":100,"value_min":0},{"access":7,"description":"Tilt of this cover","endpoint":"l7","name":"tilt","property":"tilt_l7","type":"numeric","value_max":100,"value_min":0}],"type":"cover"}],"model":"ZFP-1A-CH","options":[{"access":2,"description":"Front Surface LED enabled","name":"front_surface_enabled","property":"front_surface_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 1 enabled","name":"dimmer_1_enabled","property":"dimmer_1_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 1 dimmable","name":"dimmer_1_dimming_enabled","property":"dimmer_1_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 2 enabled","name":"dimmer_2_enabled","property":"dimmer_2_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 2 dimmable","name":"dimmer_2_dimming_enabled","property":"dimmer_2_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 3 enabled","name":"dimmer_3_enabled","property":"dimmer_3_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 3 dimmable","name":"dimmer_3_dimming_enabled","property":"dimmer_3_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 4 enabled","name":"dimmer_4_enabled","property":"dimmer_4_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Dimmer 4 dimmable","name":"dimmer_4_dimming_enabled","property":"dimmer_4_dimming_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 1 enabled","name":"cover_1_enabled","property":"cover_1_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 1 tiltable","name":"cover_1_tilt_enabled","property":"cover_1_tilt_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 2 enabled","name":"cover_2_enabled","property":"cover_2_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"Cover 2 tiltable","name":"cover_2_tilt_enabled","property":"cover_2_tilt_enabled","type":"enum","values":["auto","true","false"]},{"access":2,"description":"When enabled colors will be synced, e.g. if the light supports both color x/y and color temperature a conversion from color x/y to color temperature will be done when setting the x/y color (default true).","name":"color_sync","property":"color_sync","type":"binary","value_off":false,"value_on":true},{"access":2,"description":"Controls the transition time (in seconds) of on/off, brightness, color temperature (if applicable) and color (if applicable) changes. Defaults to `0` (no transition).","name":"transition","property":"transition","type":"numeric","value_min":0},{"access":2,"description":"Inverts the cover position, false: open=100,close=0, true: open=0,close=100 (default false).","name":"invert_cover","property":"invert_cover","type":"binary","value_off":false,"value_on":true}],"supports_ota":false,"vendor":"Siglis"},"disabled":false,"endpoints":{"10":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"11":{"bindings":[],"clusters":{"input":["genBasic","genScenes","closuresWindowCovering"],"output":[]},"configured_reportings":[],"scenes":[]},"12":{"bindings":[],"clusters":{"input":["genBasic","genScenes","closuresWindowCovering"],"output":[]},"configured_reportings":[],"scenes":[]},"5":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl","lightingColorCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"7":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"8":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]},"9":{"bindings":[],"clusters":{"input":["genBasic","genScenes","genOnOff","genLevelCtrl"],"output":[]},"configured_reportings":[],"scenes":[]}},"friendly_name":"zigfred_plus","ieee_address":"0xf4ce368a38be56a1","interview_completed":true,"interviewing":false,"manufacturer":"Siglis","model_id":"zigfred plus","network_address":6589,"power_source":"Mains (single phase)","software_build_id":null,"supported":true,"type":"Router"}]), { retain: true, qos: 0 }, expect.any(Function) ); @@ -662,7 +663,7 @@ describe('Bridge', () => { expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "retain": false, "transition": 1, "description": "this is my bulb"}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', - stringify({"data":{"from":{"retain": true, "description": "this is my bulb"},"to":{"retain": false,"transition":1, "description": "this is my bulb"}, "id":"bulb"},"status":"ok"}), + stringify({"data":{"from":{"retain": true, "description": "this is my bulb"},"to":{"retain": false,"transition":1, "description": "this is my bulb"}, "restart_required": false, "id":"bulb"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -676,7 +677,20 @@ describe('Bridge', () => { expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "retain": true, "description": "this is my bulb"}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/device/options', - stringify({"data":{"from":{"retain": true, "qos": 1, "description": "this is my bulb"},"to":{"retain": true, "description": "this is my bulb"}, "id":"bulb"},"status":"ok"}), + stringify({"data":{"from":{"retain": true, "qos": 1, "description": "this is my bulb"},"to":{"retain": true, "description": "this is my bulb"}, "restart_required": false, "id":"bulb"},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Should allow change device options with restart required', async () => { + MQTT.publish.mockClear(); + expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "retain": true, "description": "this is my bulb"}); + MQTT.events.message('zigbee2mqtt/bridge/request/device/options', stringify({options: {disabled: true}, id: 'bulb'})); + await flushPromises(); + expect(settings.getDevice('bulb')).toStrictEqual({"ID": "0x000b57fffec6a5b2", "friendly_name": "bulb", "retain": true, "disabled": true, "description": "this is my bulb"}); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/device/options', + stringify({"data":{"from":{"retain": true, "description": "this is my bulb"},"to":{"disabled": true, "retain": true, "description": "this is my bulb"}, "restart_required": true, "id":"bulb"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); @@ -689,7 +703,20 @@ describe('Bridge', () => { expect(settings.getGroup('group_1')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_1", "retain": true, "transition": 1}); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bridge/response/group/options', - stringify({"data":{"from":{"retain": false},"to":{"retain": true,"transition":1}, "id":"group_1"},"status":"ok"}), + stringify({"data":{"from":{"retain": false},"to":{"retain": true,"transition":1}, "restart_required": false, "id":"group_1"},"status":"ok"}), + {retain: false, qos: 0}, expect.any(Function) + ); + }); + + it('Should allow change group options with restart required', async () => { + MQTT.publish.mockClear(); + expect(settings.getGroup('group_1')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_1", "retain": false}); + MQTT.events.message('zigbee2mqtt/bridge/request/group/options', stringify({options: {off_state: 'all_members_off'}, id: 'group_1'})); + await flushPromises(); + expect(settings.getGroup('group_1')).toStrictEqual({"ID": 1, "devices": [], "friendly_name": "group_1", "retain": false, "off_state": "all_members_off"}); + expect(MQTT.publish).toHaveBeenCalledWith( + 'zigbee2mqtt/bridge/response/group/options', + stringify({"data":{"from":{"retain": false},"to":{"retain": false,"off_state":"all_members_off"}, "restart_required": true, "id":"group_1"},"status":"ok"}), {retain: false, qos: 0}, expect.any(Function) ); }); diff --git a/test/configure.test.js b/test/configure.test.js index ef9c29c97..c7a41c56c 100644 --- a/test/configure.test.js +++ b/test/configure.test.js @@ -102,6 +102,17 @@ describe('Configure', () => { expectBulbConfigured(); }); + it('Should not re-configure disabled devices', async () => { + expectBulbConfigured(); + const device = zigbeeHerdsman.devices.bulb; + await flushPromises(); + mockClear(device); + settings.set(['devices', device.ieeeAddr, 'disabled'], true); + zigbeeHerdsman.events.deviceJoined({device}); + await flushPromises(); + expectBulbNotConfigured(); + }); + it('Should reconfigure reporting on reconfigure event', async () => { expectBulbConfigured(); const device = controller.zigbee.resolveEntity(zigbeeHerdsman.devices.bulb); diff --git a/test/group.test.js b/test/group.test.js index 45390e6ff..d3fa86361 100644 --- a/test/group.test.js +++ b/test/group.test.js @@ -334,6 +334,22 @@ describe('Groups', () => { expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); }); + it('Should not publish state change when group changes state and device is disabled', async () => { + const device = zigbeeHerdsman.devices.bulb_color; + const endpoint = device.getEndpoint(1); + const group = zigbeeHerdsman.groups.group_1; + group.members.push(endpoint); + settings.set(['devices', device.ieeeAddr, 'disabled'], true); + settings.set(['groups'], {'1': {friendly_name: 'group_1', retain: false, devices: [device.ieeeAddr]}}); + await resetExtension(); + + MQTT.publish.mockClear(); + await MQTT.events.message('zigbee2mqtt/group_1/set', stringify({state: 'ON'})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledTimes(1); + expect(MQTT.publish).toHaveBeenCalledWith("zigbee2mqtt/group_1", stringify({"state":"ON"}), {"retain": false, qos: 0}, expect.any(Function)); + }); + it('Should publish state change for group when members state change', async () => { // Created for https://github.com/Koenkk/zigbee2mqtt/issues/5725 const device = zigbeeHerdsman.devices.bulb_color; diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index 587cacbfa..541495f6b 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -1906,6 +1906,60 @@ describe('HomeAssistant extension', () => { ); }); + it('Should discover with availability offline when device is disabled', async () => { + settings.set(['devices', '0x000b57fffec6a5b2', 'disabled'], true); + + await resetExtension(); + + const payload = { + "availability":[ + { + "topic":"zigbee2mqtt/bridge/state", + "value_template": `{{ "offline" }}`, + } + ], + "brightness":true, + "brightness_scale":254, + "color_mode":true, + "command_topic":"zigbee2mqtt/bulb/set", + "device":{ + "identifiers":[ + "zigbee2mqtt_0x000b57fffec6a5b2" + ], + "manufacturer":"IKEA", + "model":"TRADFRI LED bulb E26/E27 980 lumen, dimmable, white spectrum, opal white (LED1545G12)", + "name":"bulb", + "sw_version":null + }, + "effect":true, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], + "json_attributes_topic":"zigbee2mqtt/bulb", + "max_mireds":454, + "min_mireds":250, + "name":"bulb", + "schema":"json", + "state_topic":"zigbee2mqtt/bulb", + "supported_color_modes":[ + "color_temp" + ], + "unique_id":"0x000b57fffec6a5b2_light_zigbee2mqtt" + }; + + expect(MQTT.publish).toHaveBeenCalledWith( + 'homeassistant/light/0x000b57fffec6a5b2/light/config', + stringify(payload), + { retain: true, qos: 0 }, + expect.any(Function), + ); + }); + it('Should discover last_seen when enabled', async () => { settings.set(['advanced', 'last_seen'], 'ISO_8601'); await resetExtension(); diff --git a/test/networkMap.test.js b/test/networkMap.test.js index a4494abd2..57b72dcab 100644 --- a/test/networkMap.test.js +++ b/test/networkMap.test.js @@ -292,4 +292,19 @@ describe('Networkmap', () => { {retain: false, qos: 0}, expect.any(Function) ); }); + + it('Should exclude disabled devices from networkmap', async () => { + settings.set(['devices', '0x000b57fffec6a5b2', 'disabled'], true); + mock(); + MQTT.publish.mockClear(); + MQTT.events.message('zigbee2mqtt/bridge/request/networkmap', stringify({type: 'raw', routes: true})); + await flushPromises(); + expect(MQTT.publish).toHaveBeenCalledTimes(1); + let call = MQTT.publish.mock.calls[0]; + expect(call[0]).toStrictEqual('zigbee2mqtt/bridge/response/networkmap'); + + const expected = {"data":{"routes":true,"type":"raw","value":{"links":[{"depth":1,"linkquality":120,"lqi":120,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x000b57fffec6a5b3","networkAddress":40399},"sourceIeeeAddr":"0x000b57fffec6a5b3","sourceNwkAddr":40399,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[{"destinationAddress":6540,"nextHop":40369,"status":"ACTIVE"}],"source":{"ieeeAddr":"0x000b57fffec6a5b2","networkAddress":40369},"sourceIeeeAddr":"0x000b57fffec6a5b2","sourceNwkAddr":40369,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":1,"linkquality":92,"lqi":92,"relationship":2,"routes":[],"source":{"ieeeAddr":"0x0017880104e45511","networkAddress":1114},"sourceIeeeAddr":"0x0017880104e45511","sourceNwkAddr":1114,"target":{"ieeeAddr":"0x00124b00120144ae","networkAddress":0},"targetIeeeAddr":"0x00124b00120144ae"},{"depth":2,"linkquality":130,"lqi":130,"relationship":1,"routes":[],"source":{"ieeeAddr":"0x0017880104e45521","networkAddress":6538},"sourceIeeeAddr":"0x0017880104e45521","sourceNwkAddr":6538,"target":{"ieeeAddr":"0x0017880104e45559","networkAddress":6540},"targetIeeeAddr":"0x0017880104e45559"}],"nodes":[{"definition":null,"failed":[],"friendlyName":"Coordinator","ieeeAddr":"0x00124b00120144ae","lastSeen":1000,"modelID":null,"networkAddress":0,"type":"Coordinator"},{"definition":{"description":"Hue Go","model":"7146060PH","supports":"light (state, brightness, color_temp, color_temp_startup, color_xy, color_hs), effect, power_on_behavior, linkquality","vendor":"Philips"},"failed":[],"friendlyName":"bulb_color","ieeeAddr":"0x000b57fffec6a5b3","lastSeen":1000,"modelID":"LLC020","networkAddress":40399,"type":"Router"},{"definition":{"description":"Aqara double key wireless wall switch (2016 model)","model":"WXKG02LM_rev1","supports":"battery, action, voltage, power_outage_count, linkquality","vendor":"Xiaomi"},"friendlyName":"button_double_key","ieeeAddr":"0x0017880104e45521","lastSeen":1000,"modelID":"lumi.sensor_86sw2.es1","networkAddress":6538,"type":"EndDevice"},{"definition":null,"failed":["lqi","routingTable"],"friendlyName":"0x0017880104e45525","ieeeAddr":"0x0017880104e45525","lastSeen":1000,"manufacturerName":"Boef","modelID":"notSupportedModelID","networkAddress":6536,"type":"Router"},{"definition":{"description":"[CC2530 router](http://ptvo.info/cc2530-based-zigbee-coordinator-and-router-112/)","model":"CC2530.ROUTER","supports":"led, linkquality","vendor":"Custom devices (DiY)"},"failed":[],"friendlyName":"cc2530_router","ieeeAddr":"0x0017880104e45559","lastSeen":1000,"modelID":"lumi.router","networkAddress":6540,"type":"Router"},{"definition":{"description":"external","model":"external_converter_device","supports":"linkquality","vendor":"external"},"friendlyName":"0x0017880104e45511","ieeeAddr":"0x0017880104e45511","lastSeen":1000,"modelID":"external_converter_device","networkAddress":1114,"type":"EndDevice"}]}},"status":"ok"}; + const actual = JSON.parse(call[1]); + expect(actual).toStrictEqual(expected); + }); });