diff --git a/lib/controller.js b/lib/controller.js index cf15bcfb..1817fa76 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -172,13 +172,13 @@ class Controller { } async onZigbeeEvent(type, data) { - const entity = this.zigbee.resolveEntity(data.device || data.ieeeAddr); - if (data.device && !entity.settings) { + const resolvedEntity = this.zigbee.resolveEntity(data.device || data.ieeeAddr); + if (data.device && !resolvedEntity.settings) { // Only deviceLeave doesn't have a device (not interesting to add to settings) - entity.settings = settings.addDevice(data.device.ieeeAddr); + resolvedEntity.settings = settings.addDevice(data.device.ieeeAddr); } - const name = entity && entity.settings ? entity.settings.friendlyName : null; + const name = resolvedEntity && resolvedEntity.settings ? resolvedEntity.settings.friendlyName : null; if (type === 'message') { logger.debug( @@ -192,11 +192,9 @@ class Controller { if (data.status === 'successful') { logger.info(`Successfully interviewed '${name}', device has successfully been paired`); - if (entity.definition) { - const {vendor, description, model} = entity.definition; - logger.info( - `Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`, - ); + if (resolvedEntity.definition) { + const {vendor, description, model} = resolvedEntity.definition; + logger.info(`Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`); } else { logger.warn( `Device '${name}' with Zigbee model '${data.device.modelID}' is NOT supported, ` + @@ -221,10 +219,7 @@ class Controller { } // Call extensions - this.callExtensionMethod( - 'onZigbeeEvent', - [type, data, entity ? entity.definition : null, entity ? entity.settings : null], - ); + this.callExtensionMethod('onZigbeeEvent', [type, data, resolvedEntity]); } onMQTTMessage(payload) { diff --git a/lib/extension/baseExtension.js b/lib/extension/baseExtension.js index d3569a63..ff7e8428 100644 --- a/lib/extension/baseExtension.js +++ b/lib/extension/baseExtension.js @@ -30,10 +30,10 @@ class BaseExtension { * Is called when a Zigbee message from a device is received. * @param {string} type Type of the message * @param {Object} data Data of the message - * @param {Object?} mappedDevice The mapped device + * @param {Object?} resolvedEntity Resolved entity returned from this.zigbee.resolveEntity() * @param {Object?} settingsDevice Device settings */ - // onZigbeeEvent(type, data, mappedDevice, settingsDevice) {} + // onZigbeeEvent(type, data, resolvedEntity) {} /** * Is called when a MQTT message is received diff --git a/lib/extension/bridgeLegacy.js b/lib/extension/bridgeLegacy.js index 4dc7042d..d64e93a4 100644 --- a/lib/extension/bridgeLegacy.js +++ b/lib/extension/bridgeLegacy.js @@ -147,11 +147,11 @@ class BridgeLegacy extends BaseExtension { }; if (device.type !== 'Coordinator') { - const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); + const definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); const friendlyDevice = settings.getDevice(device.ieeeAddr); - payload.model = mappedDevice ? mappedDevice.model : device.modelID; - payload.vendor = mappedDevice ? mappedDevice.vendor : '-'; - payload.description = mappedDevice ? mappedDevice.description : '-'; + payload.model = definition ? definition.model : device.modelID; + payload.vendor = definition ? definition.vendor : '-'; + payload.description = definition ? definition.description : '-'; payload.friendly_name = friendlyDevice ? friendlyDevice.friendly_name : device.ieeeAddr; payload.manufacturerID = device.manufacturerID; payload.manufacturerName = device.manufacturerName; @@ -368,39 +368,38 @@ class BridgeLegacy extends BaseExtension { await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}); } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { - if (type === 'deviceJoined') { - this.lastJoinedDeviceName = settingsDevice.friendlyName; + onZigbeeEvent(type, data, resolvedEntity) { + if (type === 'deviceJoined' && resolvedEntity) { + this.lastJoinedDeviceName = resolvedEntity.name; } - const entity = this.zigbee.resolveEntity(data.device || data.ieeeAddr); - const name = entity && entity.settings ? entity.settings.friendlyName : null; - if (type === 'deviceJoined') { - this.mqtt.log('device_connected', {friendly_name: name}); + this.mqtt.log('device_connected', {friendly_name: resolvedEntity.name}); } else if (type === 'deviceInterview') { if (data.status === 'successful') { - if (entity.definition) { - const {vendor, description, model} = entity.definition; - const log = {friendly_name: name, model, vendor, description, supported: true}; + if (resolvedEntity.definition) { + const {vendor, description, model} = resolvedEntity.definition; + const log = {friendly_name: resolvedEntity.name, model, vendor, description, supported: true}; this.mqtt.log('pairing', 'interview_successful', log); } else { - this.mqtt.log('pairing', 'interview_successful', {friendly_name: name, supported: false}); + this.mqtt.log('pairing', 'interview_successful', + {friendly_name: resolvedEntity.name, supported: false}); } } else if (data.status === 'failed') { - this.mqtt.log('pairing', 'interview_failed', {friendly_name: name}); + this.mqtt.log('pairing', 'interview_failed', {friendly_name: resolvedEntity.name}); } else { /* istanbul ignore else */ if (data.status === 'started') { - this.mqtt.log('pairing', 'interview_started', {friendly_name: name}); + this.mqtt.log('pairing', 'interview_started', {friendly_name: resolvedEntity.name}); } } } else if (type === 'deviceAnnounce') { - this.mqtt.log('device_announced', 'announce', {friendly_name: name}); + this.mqtt.log('device_announced', 'announce', {friendly_name: resolvedEntity.name}); } else { /* istanbul ignore else */ if (type === 'deviceLeave') { - this.mqtt.log('device_removed', 'left_network', {friendly_name: name || data.ieeeAddr}); + const name = resolvedEntity ? resolvedEntity.name : data.ieeeAddr; + this.mqtt.log('device_removed', 'left_network', {friendly_name: name}); } } } diff --git a/lib/extension/deviceAvailability.js b/lib/extension/deviceAvailability.js index 7a8ac9df..4c70433e 100644 --- a/lib/extension/deviceAvailability.js +++ b/lib/extension/deviceAvailability.js @@ -144,13 +144,13 @@ class DeviceAvailability extends BaseExtension { async onReconnect(device) { if (device && device.modelID) { - const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); + const definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); - if (mappedDevice) { + if (definition) { const used = []; try { for (const key of toZigbeeCandidates) { - const converter = mappedDevice.toZigbee.find((tz) => tz.key.includes(key)); + const converter = definition.toZigbee.find((tz) => tz.key.includes(key)); if (converter && !used.includes(converter)) { await converter.convertGet(device.endpoints[0], key, {}); used.push(converter); @@ -180,7 +180,7 @@ class DeviceAvailability extends BaseExtension { } } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { + onZigbeeEvent(type, data, resolvedEntity) { const device = data.device; if (!device) { return; diff --git a/lib/extension/deviceConfigure.js b/lib/extension/deviceConfigure.js index 6de8bbbd..e22e1cb8 100644 --- a/lib/extension/deviceConfigure.js +++ b/lib/extension/deviceConfigure.js @@ -1,6 +1,5 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); -const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const BaseExtension = require('./baseExtension'); class DeviceConfigure extends BaseExtension { @@ -12,18 +11,18 @@ class DeviceConfigure extends BaseExtension { this.topic = `${settings.get().mqtt.base_topic}/bridge/configure`; } - shouldConfigure(device, mappedDevice) { - if (!device || !mappedDevice || !mappedDevice.configure) { + shouldConfigure(resolvedEntity) { + if (!resolvedEntity || !resolvedEntity.definition || !resolvedEntity.definition.configure) { return false; } - if (device.meta && - device.meta.hasOwnProperty('configured') && - device.meta.configured === mappedDevice.meta.configureKey) { + if (resolvedEntity.device.meta && + resolvedEntity.device.meta.hasOwnProperty('configured') && + resolvedEntity.device.meta.configured === resolvedEntity.definition.meta.configureKey) { return false; } - if (device.interviewing === true) { + if (resolvedEntity.device.interviewing === true) { return false; } @@ -39,34 +38,32 @@ class DeviceConfigure extends BaseExtension { return; } - const entity = this.zigbee.resolveEntity(message); - if (!entity || entity.type !== 'device') { + const resolvedEntity = this.zigbee.resolveEntity(message); + if (!resolvedEntity || resolvedEntity.type !== 'device') { logger.error(`Device '${message}' does not exist`); return; } - const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(entity.device.modelID); - if (!mappedDevice.configure) { - logger.warn(`Skipping configure of '${entity.name}', device does not require this.`); + if (!resolvedEntity.definition || !resolvedEntity.definition.configure) { + logger.warn(`Skipping configure of '${resolvedEntity.name}', device does not require this.`); return; } - this.configure(entity.device, mappedDevice, entity.settings, true); + this.configure(resolvedEntity, true); } async onZigbeeStarted() { this.coordinatorEndpoint = this.zigbee.getDevicesByType('Coordinator')[0].getEndpoint(1); for (const device of this.zigbee.getClients()) { - const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); - const settingsDevice = settings.getDevice(device.ieeeAddr); - if (this.shouldConfigure(device, mappedDevice)) { - await this.configure(device, mappedDevice, settingsDevice); + const resolvedEntity = this.zigbee.resolveEntity(device); + if (this.shouldConfigure(resolvedEntity)) { + await this.configure(resolvedEntity); } } } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { + onZigbeeEvent(type, data, resolvedEntity) { const device = data.device; if (type === 'deviceJoined' && device.meta.hasOwnProperty('configured')) { @@ -74,12 +71,13 @@ class DeviceConfigure extends BaseExtension { device.save(); } - if (this.shouldConfigure(device, mappedDevice)) { - this.configure(device, mappedDevice, settingsDevice); + if (this.shouldConfigure(resolvedEntity)) { + this.configure(resolvedEntity); } } - async configure(device, mappedDevice, settingsDevice, force=false) { + async configure(resolvedEntity, force=false) { + const device = resolvedEntity.device; if (this.configuring.has(device.ieeeAddr) || (this.attempts[device.ieeeAddr] >= 3 && !force)) { return false; } @@ -90,16 +88,15 @@ class DeviceConfigure extends BaseExtension { this.attempts[device.ieeeAddr] = 0; } - logger.info(`Configuring '${settingsDevice.friendlyName}'`); + logger.info(`Configuring '${resolvedEntity.name}'`); try { - await mappedDevice.configure(device, this.coordinatorEndpoint); - logger.info(`Successfully configured '${settingsDevice.friendlyName}'`); - // eslint-disable-next-line - device.meta.configured = mappedDevice.meta.configureKey; + await resolvedEntity.definition.configure(device, this.coordinatorEndpoint); + logger.info(`Successfully configured '${resolvedEntity.name}'`); + device.meta.configured = resolvedEntity.definition.meta.configureKey; device.save(); } catch (error) { logger.error( - `Failed to configure '${settingsDevice.friendlyName}', ` + + `Failed to configure '${resolvedEntity.name}', ` + `attempt ${this.attempts[device.ieeeAddr] + 1} (${error.stack})`, ); this.attempts[device.ieeeAddr]++; diff --git a/lib/extension/deviceEvent.js b/lib/extension/deviceEvent.js index 54ca2949..ea56a4f5 100644 --- a/lib/extension/deviceEvent.js +++ b/lib/extension/deviceEvent.js @@ -8,9 +8,9 @@ class DeviceEvent extends BaseExtension { } } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { - if (data.device) { - this.callOnEvent(data.device, type, data, mappedDevice); + onZigbeeEvent(type, data, resolvedEntity) { + if (data.device && resolvedEntity.definition) { + this.callOnEvent(data.device, type, data, resolvedEntity.definition); } } @@ -20,15 +20,15 @@ class DeviceEvent extends BaseExtension { } } - callOnEvent(device, type, data, mappedDevice) { + callOnEvent(device, type, data, definition) { zigbeeHerdsmanConverters.onEvent(type, data, device); - if (!mappedDevice) { - mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); + if (!definition) { + definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); } - if (mappedDevice && mappedDevice.onEvent) { - mappedDevice.onEvent(type, data, device); + if (definition && definition.onEvent) { + definition.onEvent(type, data, device); } } } diff --git a/lib/extension/deviceReceive.js b/lib/extension/deviceReceive.js index 6e5e665d..fb879417 100755 --- a/lib/extension/deviceReceive.js +++ b/lib/extension/deviceReceive.js @@ -54,8 +54,8 @@ class DeviceReceive extends BaseExtension { return result; } - canHandleEvent(type, data, mappedDevice) { - if (type !== 'message') { + canHandleEvent(type, data, resolvedEntity) { + if (type !== 'message' || !resolvedEntity) { return false; } @@ -88,7 +88,7 @@ class DeviceReceive extends BaseExtension { return false; } - if (!mappedDevice) { + if (!resolvedEntity.definition) { logger.warn(`Received message from unsupported device with Zigbee model '${data.device.modelID}'`); logger.warn(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html.`); return false; @@ -97,12 +97,12 @@ class DeviceReceive extends BaseExtension { return true; } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { - if (!this.canHandleEvent(type, data, mappedDevice)) { + onZigbeeEvent(type, data, resolvedEntity) { + if (!this.canHandleEvent(type, data, resolvedEntity)) { return; } - const converters = mappedDevice.fromZigbee.filter((c) => { + const converters = resolvedEntity.definition.fromZigbee.filter((c) => { const type = Array.isArray(c.type) ? c.type.includes(data.type) : c.type === data.type; return c.cluster === data.cluster && type; }); @@ -110,14 +110,14 @@ class DeviceReceive extends BaseExtension { // Check if there is an available converter if (!converters.length) { logger.debug( - `No converter available for '${mappedDevice.model}' with cluster '${data.cluster}' ` + + `No converter available for '${resolvedEntity.definition.model}' with cluster '${data.cluster}' ` + `and type '${data.type}' and data '${JSON.stringify(data.data)}'`, ); return; } - const debounce = settingsDevice.debounce || settings.get().device_options.debounce; - const debounceIgnore = settingsDevice.debounce_ignore || settings.get().device_options.debounce_ignore; + 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. @@ -166,7 +166,7 @@ class DeviceReceive extends BaseExtension { let payload = {}; converters.forEach((converter) => { const options = {...settings.get().device_options, ...settings.getDevice(data.device.ieeeAddr)}; - const converted = converter.convert(mappedDevice, data, publish, options, meta); + const converted = converter.convert(resolvedEntity.definition, data, publish, options, meta); if (converted) { payload = {...payload, ...converted}; } diff --git a/lib/extension/deviceReport.js b/lib/extension/deviceReport.js index ab4a6f40..ebf708ae 100644 --- a/lib/extension/deviceReport.js +++ b/lib/extension/deviceReport.js @@ -107,8 +107,8 @@ class DeviceReport extends BaseExtension { this.pollDebouncers = {}; } - shouldIgnoreClusterForDevice(cluster, mappedDevice) { - if (mappedDevice === ZNLDP12LM && cluster === 'closuresWindowCovering') { + shouldIgnoreClusterForDevice(cluster, definition) { + if (definition === ZNLDP12LM && cluster === 'closuresWindowCovering') { // Device announces it but doesn't support it // https://github.com/Koenkk/zigbee2mqtt/issues/2611 return true; @@ -117,14 +117,16 @@ class DeviceReport extends BaseExtension { return false; } - async setupReporting(device, mappedDevice) { + async setupReporting(resolvedEntity) { + const {device, definition} = resolvedEntity; + if (this.configuring.has(device.ieeeAddr) || this.failed.has(device.ieeeAddr)) return; this.configuring.add(device.ieeeAddr); try { for (const ep of device.endpoints) { for (const [cluster, configuration] of Object.entries(clusters)) { - if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, mappedDevice)) { + if (ep.supportsInputCluster(cluster) && !this.shouldIgnoreClusterForDevice(cluster, definition)) { logger.debug(`Setup reporting for '${device.ieeeAddr}' - ${ep.ID} - ${cluster}`); const items = []; @@ -158,9 +160,11 @@ class DeviceReport extends BaseExtension { this.configuring.delete(device.ieeeAddr); } - shouldSetupReporting(mappedDevice, device, messageType) { - if (!device || !mappedDevice) return false; + shouldSetupReporting(resolvedEntity, messageType) { + if (!resolvedEntity || !resolvedEntity.device || !resolvedEntity.definition || + messageType === 'deviceLeave') return false; + const {device, definition} = resolvedEntity; // Handle messages of type endDeviceAnnce and devIncoming. // This message is typically send when a device comes online after being powered off // Ikea TRADFRI tend to forget their reporting after powered off. @@ -171,7 +175,7 @@ class DeviceReport extends BaseExtension { if (device.meta.hasOwnProperty('reporting') && device.meta.reporting === reportKey) return false; if (!utils.isRouter(device) || utils.isBatteryPowered(device)) return false; // Gledopto devices don't support reporting. - if (devicesNotSupportingReporting.includes(mappedDevice) || mappedDevice.vendor === 'Gledopto') return false; + if (devicesNotSupportingReporting.includes(definition) || definition.vendor === 'Gledopto') return false; return true; } @@ -179,16 +183,16 @@ class DeviceReport extends BaseExtension { this.coordinatorEndpoint = this.zigbee.getDevicesByType('Coordinator')[0].getEndpoint(1); for (const device of this.zigbee.getClients()) { - const mappedDevice = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); - if (this.shouldSetupReporting(mappedDevice, device, null)) { - this.setupReporting(device, mappedDevice); + const resolvedEntity = this.zigbee.resolveEntity(device); + if (this.shouldSetupReporting(resolvedEntity, null)) { + this.setupReporting(resolvedEntity); } } } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { - if (this.shouldSetupReporting(mappedDevice, data.device, type)) { - this.setupReporting(data.device, mappedDevice); + onZigbeeEvent(type, data, resolvedEntity) { + if (this.shouldSetupReporting(resolvedEntity, type)) { + this.setupReporting(resolvedEntity); } if (type === 'message') { diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index a0af1797..8543fca0 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -1862,10 +1862,10 @@ class HomeAssistant extends BaseExtension { } } - onZigbeeEvent(type, data, mappedDevice, settingsDevice) { + onZigbeeEvent(type, data, resolvedEntity) { const device = data.device; - if (device && mappedDevice) { - this.discover(device, mappedDevice); + if (device && resolvedEntity && resolvedEntity.definition) { + this.discover(device, resolvedEntity.definition); } } diff --git a/lib/extension/otaUpdate.js b/lib/extension/otaUpdate.js index 019de68d..57e01b0f 100644 --- a/lib/extension/otaUpdate.js +++ b/lib/extension/otaUpdate.js @@ -17,10 +17,10 @@ class OTAUpdate extends BaseExtension { this.mqtt.subscribe(`${settings.get().mqtt.base_topic}/bridge/ota_update/update`); } - async onZigbeeEvent(type, data, mappedDevice, settingsDevice) { - if (data.type !== 'commandQueryNextImageRequest') return; + async onZigbeeEvent(type, data, resolvedEntity) { + if (data.type !== 'commandQueryNextImageRequest' || !resolvedEntity || !resolvedEntity.definition) return; - const supportsOTA = mappedDevice && mappedDevice.hasOwnProperty('ota'); + const supportsOTA = resolvedEntity.definition.hasOwnProperty('ota'); if (supportsOTA) { // When a device does a next image request, it will usually do it a few times after each other // with only 10 - 60 seconds inbetween. It doesn' make sense to check for a new update @@ -30,13 +30,16 @@ class OTAUpdate extends BaseExtension { if (!check || this.inProgress.has(data.device.ieeeAddr)) return; this.lastChecked[data.device.ieeeAddr] = Date.now(); - const available = await mappedDevice.ota.isUpdateAvailable(data.device, logger, data.data); + const available = await resolvedEntity.definition.ota.isUpdateAvailable(data.device, logger, data.data); this.publishEntityState(data.device.ieeeAddr, {update_available: available}); if (available) { - const message = `Update available for '${settingsDevice.friendly_name}'`; + const message = `Update available for '${resolvedEntity.settings.friendly_name}'`; logger.info(message); - this.mqtt.log('ota_update', message, {status: 'available', device: settingsDevice.friendly_name}); + this.mqtt.log( + 'ota_update', message, + {status: 'available', device: resolvedEntity.settings.friendly_name}, + ); } } diff --git a/lib/zigbee.js b/lib/zigbee.js index da20e4e4..33d27074 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -136,10 +136,10 @@ class Zigbee extends events.EventEmitter { * @return {object} { * type: device | coordinator * device|group: zigbee-herdsman entity - * endpoint: selected endpoint + * endpoint: selected endpoint (only if type === device) * settings: from configuration.yaml * name: name of the entity - * definition: zigbee-herdsman-converters definition + * definition: zigbee-herdsman-converters definition (only if type === device) * } */ resolveEntity(key) {