diff --git a/lib/controller.js b/lib/controller.js index 36155ed0a..f220d69d1 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -121,14 +121,10 @@ class Controller { logger.info(`Currently ${devices.length} devices are joined:`); for (const device of devices) { const entity = this.zigbee.resolveEntity(device); - logger.info( - (entity.settings ? entity.settings.friendlyName : entity.device.ieeeAddr) + - ` (${entity.device.ieeeAddr}): ` + - (entity.definition ? - `${entity.definition.model} - ${entity.definition.vendor} ${entity.definition.description} ` : - 'Not supported ') + - `(${entity.device.type})`, - ); + const model = entity.definition ? + `${entity.definition.model} - ${entity.definition.vendor} ${entity.definition.description}` : + 'Not supported'; + logger.info(`${entity.name} (${entity.device.ieeeAddr}): ${model} (${entity.device.type})`); } // Enable zigbee join. @@ -209,9 +205,9 @@ class Controller { async onZigbeeEvent(type, data) { const resolvedEntity = this.zigbee.resolveEntity(data.device || data.ieeeAddr); - if (data.device && !resolvedEntity.settings && data.device.type !== 'Coordinator') { + if (data.device && !settings.getDevice(data.device.ieeeAddr) && data.device.type !== 'Coordinator') { // Only deviceLeave doesn't have a device (not interesting to add to settings) - resolvedEntity.settings = settings.addDevice(data.device.ieeeAddr); + resolvedEntity.settings = {...settings.get().device_options, ...settings.addDevice(data.device.ieeeAddr)}; } const name = resolvedEntity && resolvedEntity.settings ? resolvedEntity.settings.friendlyName : null; @@ -286,13 +282,12 @@ class Controller { messagePayload = newState; } - const deviceOptions = settings.get().device_options; const options = { - retain: utils.getObjectsProperty([resolvedEntity.settings, deviceOptions], 'retain', false), - qos: utils.getObjectsProperty([resolvedEntity.settings, deviceOptions], 'qos', 0), + retain: utils.getObjectProperty(resolvedEntity.settings, 'retain', false), + qos: utils.getObjectProperty(resolvedEntity.settings, 'qos', 0), }; - const retention = utils.getObjectsProperty([resolvedEntity.settings, deviceOptions], 'retention', false); + const retention = utils.getObjectProperty(resolvedEntity.settings, 'retention', false); if (retention !== false) { options.properties = {messageExpiryInterval: retention}; } @@ -318,9 +313,6 @@ class Controller { } // filter mqtt message attributes - if (deviceOptions.filtered_attributes) { - deviceOptions.filtered_attributes.forEach((a) => delete messagePayload[a]); - } if (resolvedEntity.settings.filtered_attributes) { resolvedEntity.settings.filtered_attributes.forEach((a) => delete messagePayload[a]); } diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 450d40cd5..933574d11 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -1,4 +1,3 @@ -const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const settings = require('../util/settings'); const logger = require('../util/logger'); const utils = require('../util/utils'); @@ -1921,15 +1920,12 @@ class HomeAssistant extends Extension { } onDeviceRemoved(device) { + logger.debug(`Clearing Home Assistant discovery topic for '${device.ieeeAddr}'`); delete this.discovered[device.ieeeAddr]; const resolvedEntity = this.zigbee.resolveEntity(device); - if (resolvedEntity.definition) { - logger.info(`Clearing Home Assistant discovery topic for '${device.ieeeAddr}'`); - const deviceSettings = {...settings.get().device_options, ...resolvedEntity.settings}; - this.getConfigs(resolvedEntity.definition, deviceSettings).forEach((config) => { - const topic = this.getDiscoveryTopic(config, device); - this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic); - }); + for (const config of this.getConfigs(resolvedEntity)) { + const topic = this.getDiscoveryTopic(config, device); + this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic); } } @@ -2043,17 +2039,14 @@ class HomeAssistant extends Extension { } onDeviceRenamed(device) { - logger.info(`Refreshing Home Assistant discovery topic for '${device.ieeeAddr}'`); + logger.debug(`Refreshing Home Assistant discovery topic for '${device.ieeeAddr}'`); // Clear before rename so Home Assistant use new friendly_name // https://github.com/Koenkk/zigbee2mqtt/issues/4096#issuecomment-674044916 const resolvedEntity = this.zigbee.resolveEntity(device); - if (resolvedEntity.definition) { - const deviceSettings = {...settings.get().device_options, ...resolvedEntity.settings}; - this.getConfigs(resolvedEntity.definition, deviceSettings).forEach((config) => { - const topic = this.getDiscoveryTopic(config, device); - this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic); - }); + for (const config of this.getConfigs(resolvedEntity)) { + const topic = this.getDiscoveryTopic(config, device); + this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic); } this.discover(resolvedEntity, true); @@ -2071,11 +2064,13 @@ class HomeAssistant extends Extension { } } - getConfigs(definition, deviceSettings) { - let configs = mapping[definition.model].slice(); + getConfigs(resolvedEntity) { + if (!resolvedEntity || !resolvedEntity.definition) return []; + + let configs = mapping[resolvedEntity.definition.model].slice(); configs.push(cfg.sensor_linkquality); - if (definition.hasOwnProperty('ota')) { + if (resolvedEntity.definition.hasOwnProperty('ota')) { if (this.legacyApi) { configs.push(cfg.binary_sensor_update_available); } @@ -2085,7 +2080,7 @@ class HomeAssistant extends Extension { } } - if (deviceSettings.hasOwnProperty('legacy') && !deviceSettings.legacy) { + if (resolvedEntity.settings.hasOwnProperty('legacy') && !resolvedEntity.settings.legacy) { configs = configs.filter((c) => c !== cfg.sensor_click); } @@ -2099,18 +2094,17 @@ class HomeAssistant extends Extension { discover(resolvedEntity, force=false) { // Check if already discoverd and check if there are configs. const {device, definition} = resolvedEntity; - const deviceSettings = {...settings.get().device_options, ...resolvedEntity.settings}; const discover = force || !this.discovered[device.ieeeAddr]; - if (!discover || !device || !definition || !mapping[definition.model] || !deviceSettings || - (deviceSettings.hasOwnProperty('homeassistant') && !deviceSettings.homeassistant)) { + if (!discover || !device || !definition || !mapping[definition.model] || + (resolvedEntity.settings.hasOwnProperty('homeassistant') && !resolvedEntity.settings.homeassistant)) { return; } - const friendlyName = deviceSettings.friendlyName; - this.getConfigs(definition, deviceSettings).forEach((config) => { + const friendlyName = resolvedEntity.settings.friendlyName; + this.getConfigs(resolvedEntity).forEach((config) => { const topic = this.getDiscoveryTopic(config, device); const payload = {...config.discovery_payload}; - let stateTopic = `${settings.get().mqtt.base_topic}/${deviceSettings.friendlyName}`; + let stateTopic = `${settings.get().mqtt.base_topic}/${friendlyName}`; if (payload.state_topic_postfix) { stateTopic += `/${payload.state_topic_postfix}`; delete payload.state_topic_postfix; @@ -2140,7 +2134,7 @@ class HomeAssistant extends Extension { payload.name = `${friendlyName}${nameSeparator}${config.object_id}`; // Set unique_id - payload.unique_id = `${deviceSettings.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`; + payload.unique_id = `${resolvedEntity.settings.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`; // Attributes for device registry payload.device = this.getDevicePayload(resolvedEntity); @@ -2237,7 +2231,7 @@ class HomeAssistant extends Extension { } // Override configuration with user settings. - if (deviceSettings.hasOwnProperty('homeassistant')) { + if (resolvedEntity.settings.hasOwnProperty('homeassistant')) { const add = (obj) => { Object.keys(obj).forEach((key) => { if (['number', 'string', 'boolean'].includes(typeof obj[key])) { @@ -2252,10 +2246,10 @@ class HomeAssistant extends Extension { }); }; - add(deviceSettings.homeassistant); + add(resolvedEntity.settings.homeassistant); - if (deviceSettings.homeassistant.hasOwnProperty(config.object_id)) { - add(deviceSettings.homeassistant[config.object_id]); + if (resolvedEntity.settings.homeassistant.hasOwnProperty(config.object_id)) { + add(resolvedEntity.settings.homeassistant[config.object_id]); } } @@ -2285,11 +2279,9 @@ class HomeAssistant extends Extension { let clear = !resolvedEntity || !resolvedEntity.definition; if (!clear) { - const deviceSettings = {...settings.get().device_options, ...resolvedEntity.settings}; - const configs = this.getConfigs(resolvedEntity.definition, deviceSettings); const type = discoveryMatch[1]; const objectID = discoveryMatch[3]; - clear = !configs.find((c) => c.type === type && c.object_id === objectID); + clear = !this.getConfigs(resolvedEntity).find((c) => c.type === type && c.object_id === objectID); } if (clear) { diff --git a/lib/extension/publish.js b/lib/extension/publish.js index 7ba08c8b9..5ff09059c 100644 --- a/lib/extension/publish.js +++ b/lib/extension/publish.js @@ -92,11 +92,11 @@ class EntityPublish extends Extension { definition = resolvedEntity.definition; target = resolvedEntity.endpoint; converters = resolvedEntity.definition.toZigbee; - options = {...settings.get().device_options, ...resolvedEntity.settings}; + options = resolvedEntity.settings; } else { converters = groupConverters; target = resolvedEntity.group; - options = {...settings.get().device_options, ...resolvedEntity.settings}; + options = resolvedEntity.settings; definition = resolvedEntity.group.members.map((e) => zigbeeHerdsmanConverters.findByDevice(e.getDevice())); } diff --git a/lib/extension/receive.js b/lib/extension/receive.js index 93942bb99..693f027be 100755 --- a/lib/extension/receive.js +++ b/lib/extension/receive.js @@ -131,9 +131,6 @@ class Receive extends Extension { return; } - const debounce = resolvedEntity.settings.debounce || settings.get().device_options.debounce; - const debounceIgnore = resolvedEntity.settings.debounce_ignore || settings.get().device_options.debounce_ignore; - // Convert this Zigbee message to a MQTT message. // Get payload for the message. // - If a payload is returned publish it to the MQTT broker @@ -155,8 +152,11 @@ class Receive extends Extension { } // Check if we have to debounce - if (debounce) { - this.publishDebounce(data.device.ieeeAddr, payload, debounce, debounceIgnore); + if (resolvedEntity.settings.debounce) { + this.publishDebounce( + data.device.ieeeAddr, payload, resolvedEntity.settings.debounce, + resolvedEntity.settings.debounce_ignore, + ); } else { this.publishEntityState(data.device.ieeeAddr, payload); } @@ -165,8 +165,9 @@ class Receive extends Extension { const meta = {device: data.device, logger}; let payload = {}; converters.forEach((converter) => { - const options = {...settings.get().device_options, ...settings.getDevice(data.device.ieeeAddr)}; - const converted = converter.convert(resolvedEntity.definition, data, publish, options, meta); + const converted = converter.convert( + resolvedEntity.definition, data, publish, resolvedEntity.settings, meta, + ); if (converted) { payload = {...payload, ...converted}; } diff --git a/lib/util/utils.js b/lib/util/utils.js index e76c48635..7a0851ab9 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -111,14 +111,8 @@ function equalsPartial(object, expected) { return true; } -function getObjectsProperty(objects, key, defaultValue) { - for (const object of objects) { - if (object.hasOwnProperty(key)) { - return object[key]; - } - } - - return defaultValue; +function getObjectProperty(object, key, defaultValue) { + return object.hasOwnProperty(key) ? object[key] : defaultValue; } function getResponse(request, data, error) { @@ -180,7 +174,7 @@ module.exports = { getZigbee2mqttVersion, objectHasProperties, toSnakeCase, - getObjectsProperty, + getObjectProperty, getEndpointNames: () => endpointNames, isXiaomiDevice: (device) => { return device.modelID !== 'lumi.router' && xiaomiManufacturerID.includes(device.manufacturerID) && diff --git a/lib/zigbee.js b/lib/zigbee.js index 137efddd8..8e29b7062 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -162,6 +162,7 @@ class Zigbee extends events.EventEmitter { `Wrong type '${typeof key}'`, ); + const deviceOptions = settings.get().device_options; if (typeof key === 'string' || typeof key === 'number') { if (typeof key === 'number') { key = key.toString(); @@ -216,12 +217,13 @@ class Zigbee extends events.EventEmitter { } return { - type: 'device', device, endpoint, settings: entity, name: entity.friendlyName, definition, + type: 'device', device, endpoint, settings: {...deviceOptions, ...entity}, + name: entity.friendlyName, definition, }; } else { let group = this.getGroupByID(entity.ID); if (!group) group = this.createGroup(entity.ID); - return {type: 'group', group, settings: entity, name: entity.friendlyName}; + return {type: 'group', group, settings: {...deviceOptions, ...entity}, name: entity.friendlyName}; } } else if (key.constructor.name === 'Device') { const setting = settings.getEntity(key.ieeeAddr); @@ -229,7 +231,7 @@ class Zigbee extends events.EventEmitter { type: 'device', device: key, endpoint: key.endpoints[0], - settings: setting, + settings: {...deviceOptions, ...(setting || {})}, name: setting ? setting.friendlyName : (key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr), definition: zigbeeHerdsmanConverters.findByDevice(key), }; @@ -238,7 +240,7 @@ class Zigbee extends events.EventEmitter { return { type: 'group', group: key, - settings: setting, + settings: {...deviceOptions, ...(setting || {})}, name: setting ? setting.friendlyName : key.groupID, }; }