From 3ef7555ca269bda20605e0de6194521b97cf48bd Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Sun, 5 Apr 2020 00:05:05 +0200 Subject: [PATCH] Refactor --- lib/controller.js | 12 ++++---- lib/extension/bridgeLegacy.js | 4 +-- lib/extension/entityPublish.js | 10 +++---- lib/extension/groups.js | 15 +++++++--- lib/extension/homeassistant.js | 14 ++++----- lib/extension/otaUpdate.js | 6 ++-- lib/util/settings.js | 2 +- lib/util/utils.js | 4 +-- lib/zigbee.js | 52 ++++++++++++++++++++-------------- test/settings.test.js | 2 +- 10 files changed, 68 insertions(+), 53 deletions(-) diff --git a/lib/controller.js b/lib/controller.js index 6ae8df9a6..cf15bcfb9 100644 --- a/lib/controller.js +++ b/lib/controller.js @@ -109,8 +109,8 @@ class Controller { logger.info( (entity.settings ? entity.settings.friendlyName : entity.device.ieeeAddr) + ` (${entity.device.ieeeAddr}): ` + - (entity.mapped ? - `${entity.mapped.model} - ${entity.mapped.vendor} ${entity.mapped.description} ` : + (entity.definition ? + `${entity.definition.model} - ${entity.definition.vendor} ${entity.definition.description} ` : 'Not supported ') + `(${entity.device.type})`, ); @@ -192,8 +192,8 @@ class Controller { if (data.status === 'successful') { logger.info(`Successfully interviewed '${name}', device has successfully been paired`); - if (entity.mapped) { - const {vendor, description, model} = entity.mapped; + if (entity.definition) { + const {vendor, description, model} = entity.definition; logger.info( `Device '${name}' is supported, identified as: ${vendor} ${description} (${model})`, ); @@ -223,7 +223,7 @@ class Controller { // Call extensions this.callExtensionMethod( 'onZigbeeEvent', - [type, data, entity ? entity.mapped : null, entity ? entity.settings : null], + [type, data, entity ? entity.definition : null, entity ? entity.settings : null], ); } @@ -278,7 +278,7 @@ class Controller { messagePayload.device = { friendlyName: entity.name, - model: entity.mapped ? entity.mapped.model : 'unknown', + model: entity.definition ? entity.definition.model : 'unknown', }; attributes.forEach((a) => messagePayload.device[a] = device[a]); diff --git a/lib/extension/bridgeLegacy.js b/lib/extension/bridgeLegacy.js index 2230f5d73..4dc7042df 100644 --- a/lib/extension/bridgeLegacy.js +++ b/lib/extension/bridgeLegacy.js @@ -380,8 +380,8 @@ class BridgeLegacy extends BaseExtension { this.mqtt.log('device_connected', {friendly_name: name}); } else if (type === 'deviceInterview') { if (data.status === 'successful') { - if (entity.mapped) { - const {vendor, description, model} = entity.mapped; + if (entity.definition) { + const {vendor, description, model} = entity.definition; const log = {friendly_name: name, model, vendor, description, supported: true}; this.mqtt.log('pairing', 'interview_successful', log); } else { diff --git a/lib/extension/entityPublish.js b/lib/extension/entityPublish.js index d73331a9c..c0ec011a4 100644 --- a/lib/extension/entityPublish.js +++ b/lib/extension/entityPublish.js @@ -6,7 +6,7 @@ const utils = require('../util/utils'); const assert = require('assert'); const BaseExtension = require('./baseExtension'); -const postfixes = utils.getPostfixes(); +const postfixes = utils.getEndpointNames(); const topicRegex = new RegExp(`^(.+?)(?:/(${postfixes.join('|')}))?/(get|set)(?:/(.+))?`); const groupConverters = [ @@ -74,16 +74,16 @@ class EntityPublish extends BaseExtension { assert(entity.type === 'device' || entity.type === 'group'); if (entity.type === 'device') { // Map device to a model - if (!entity.mapped) { + if (!entity.definition) { logger.warn(`Device with modelID '${entity.device.modelID}' is not supported.`); logger.warn(`Please see: https://www.zigbee2mqtt.io/how_tos/how_to_support_new_devices.html`); return; } device = entity.device; - mapped = entity.mapped; + mapped = entity.definition; target = entity.endpoint; - converters = entity.mapped.toZigbee; + converters = entity.definition.toZigbee; options = entity.settings; } else { converters = groupConverters; @@ -136,7 +136,7 @@ class EntityPublish extends BaseExtension { if (entity.type === 'device' && key.includes('_')) { const underscoreIndex = key.lastIndexOf('_'); const possiblePostfix = key.substring(underscoreIndex + 1, key.length); - if (utils.getPostfixes().includes(possiblePostfix)) { + if (utils.getEndpointNames().includes(possiblePostfix)) { postfix = possiblePostfix; key = key.substring(0, underscoreIndex); const device = target.getDevice(); diff --git a/lib/extension/groups.js b/lib/extension/groups.js index bfcee3b87..64e8d888a 100644 --- a/lib/extension/groups.js +++ b/lib/extension/groups.js @@ -1,6 +1,8 @@ const settings = require('../util/settings'); const logger = require('../util/logger'); const BaseExtension = require('./baseExtension'); +const utils = require('../util/utils'); +const postfixes = utils.getEndpointNames(); const topicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/(.+)/(remove|add|remove_all)$`); const topicRegexRemoveAll = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/remove_all$`); @@ -167,12 +169,17 @@ class Groups extends BaseExtension { `${entity.name}/${entity.endpoint.ID}`, ]; - if (entity.endpointName) { - keys.push(`${entity.device.ieeeAddr}/${entity.endpointName}`); - keys.push(`${entity.name}/${entity.endpointName}`); + const definition = entity.definition; + const endpoints = definition && definition.endpoint ? definition.endpoint(entity.device) : null; + const endpointName = endpoints ? Object.entries(endpoints).find((e) => e[1] === entity.endpoint.ID)[0] : null; + + if (endpointName) { + keys.push(`${entity.device.ieeeAddr}/${endpointName}`); + keys.push(`${entity.name}/${endpointName}`); } - if (entity.isDefaultEndpoint) { + const hasEndpointName = postfixes.find((p) => message.endsWith(`/${p}`)); + if (!hasEndpointName) { keys.push(entity.name); keys.push(entity.device.ieeeAddr); } diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 8cef23c35..a0af17970 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -1551,8 +1551,8 @@ class HomeAssistant extends BaseExtension { * Here we retrieve all the attributes with the _l1 values and republish them on * zigbee2mqtt/mydevice/l1. */ - if (data.entity.mapped && mapping[data.entity.mapped.model]) { - for (const config of mapping[data.entity.mapped.model]) { + if (data.entity.definition && mapping[data.entity.definition.model]) { + for (const config of mapping[data.entity.definition.model]) { const match = /light_(.*)/.exec(config['object_id']); if (match) { const postfix = match[1]; @@ -1579,7 +1579,7 @@ class HomeAssistant extends BaseExtension { * and republish it to zigbee2mqtt/my_devic/action */ const key = ['action', 'click'].find((k) => data.payload.hasOwnProperty(k) && data.payload[k] !== ''); - if (data.entity.mapped && key) { + if (data.entity.definition && key) { const device = data.entity.device; if (!this.discoveredTriggers[device.ieeeAddr]) { this.discoveredTriggers[device.ieeeAddr] = new Set(); @@ -1597,7 +1597,7 @@ class HomeAssistant extends BaseExtension { subtype: value, payload: value, topic: `${settings.get().mqtt.base_topic}/${data.entity.name}/${key}`, - device: this.getDevicePayload(data.entity.settings, data.entity.mapped), + device: this.getDevicePayload(data.entity.settings, data.entity.definition), }; await this.mqtt.publish(topic, JSON.stringify(payload), {retain: true, qos: 0}, this.discoveryTopic); @@ -1614,13 +1614,13 @@ class HomeAssistant extends BaseExtension { const mockedValues = [ { property: 'update_available', - condition: data.entity.device && data.entity.mapped && data.entity.mapped.hasOwnProperty('ota'), + condition: data.entity.device && data.entity.definition && data.entity.definition.hasOwnProperty('ota'), value: false, }, { property: 'water_leak', - condition: data.entity.device && data.entity.mapped && - mapping[data.entity.mapped.model].includes(cfg.binary_sensor_water_leak), + condition: data.entity.device && data.entity.definition && + mapping[data.entity.definition.model].includes(cfg.binary_sensor_water_leak), value: false, }, ]; diff --git a/lib/extension/otaUpdate.js b/lib/extension/otaUpdate.js index 33e4e2daa..019de68d8 100644 --- a/lib/extension/otaUpdate.js +++ b/lib/extension/otaUpdate.js @@ -71,7 +71,7 @@ class OTAUpdate extends BaseExtension { const device = this.zigbee.resolveEntity(message); assert(device != null && device.type === 'device', 'Device not found or not a device'); - if (!device.mapped || !device.mapped.ota) { + if (!device.definition || !device.definition.ota) { const message = `Device '${device.name}' does not support OTA updates`; logger.error(message); this.mqtt.log('ota_update', message, {status: `not_supported`, device: device.name}); @@ -90,7 +90,7 @@ class OTAUpdate extends BaseExtension { logger.info(message); this.mqtt.log('ota_update', message, {status: `checking_if_available`, device: device.name}); try { - const available = await device.mapped.ota.isUpdateAvailable(device.device, logger); + const available = await device.definition.ota.isUpdateAvailable(device.device, logger); const message=(available ? `Update available for '${device.name}'` : `No update available for '${device.name}'`); logger.info(message); @@ -119,7 +119,7 @@ class OTAUpdate extends BaseExtension { }; const from_ = await this.readSoftwareBuildIDAndDateCode(device.device, false); - await device.mapped.ota.updateToLatest(device.device, logger, onProgress); + await device.definition.ota.updateToLatest(device.device, logger, onProgress); const to = await this.readSoftwareBuildIDAndDateCode(device.device, true); const [fromS, toS] = [JSON.stringify(from_), JSON.stringify(to)]; const message = `Finished update of '${device.name}'` + (to ? `, from '${fromS}' to '${toS}'` : ``); diff --git a/lib/util/settings.js b/lib/util/settings.js index b74fdad61..4bcd8cba2 100644 --- a/lib/util/settings.js +++ b/lib/util/settings.js @@ -310,7 +310,7 @@ function write() { function validate() { const validate = ajv.compile(schema); const valid = validate(_settings); - const postfixes = utils.getPostfixes(); + const postfixes = utils.getEndpointNames(); // Verify that all friendly names are unique const names = []; diff --git a/lib/util/utils.js b/lib/util/utils.js index 6b5856662..f702c4bb1 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -27,7 +27,7 @@ function toLocalISOString(dDate) { ':' + pad(tzOffset % 60); } -const postfixes = [ +const endpointNames = [ 'left', 'right', 'center', 'bottom_left', 'bottom_right', 'default', 'top_left', 'top_right', 'white', 'rgb', 'system', 'top', 'bottom', 'center_left', 'center_right', 'ep1', 'ep2', 'row_1', 'row_2', 'row_3', 'row_4', 'relay', @@ -125,7 +125,7 @@ module.exports = { getZigbee2mqttVersion, objectHasProperties, getObjectsProperty, - getPostfixes: () => postfixes, + getEndpointNames: () => endpointNames, isXiaomiDevice: (device) => { return device.modelID !== 'lumi.router' && xiaomiManufacturerID.includes(device.manufacturerID) && (!device.manufacturerName || !device.manufacturerName.startsWith('Trust')); diff --git a/lib/zigbee.js b/lib/zigbee.js index 2cfa8f53c..da20e4e4d 100644 --- a/lib/zigbee.js +++ b/lib/zigbee.js @@ -8,7 +8,7 @@ const events = require('events'); const objectAssignDeep = require('object-assign-deep'); const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); -const postfixes = utils.getPostfixes(); +const endpointNames = utils.getEndpointNames(); const keyEndpointByNumber = new RegExp(`.*/([0-9]*)$`); const herdsmanSettings = { @@ -131,6 +131,17 @@ class Zigbee extends events.EventEmitter { return this.herdsman.getDevicesByType(type); } + /** + * @param {string} key + * @return {object} { + * type: device | coordinator + * device|group: zigbee-herdsman entity + * endpoint: selected endpoint + * settings: from configuration.yaml + * name: name of the entity + * definition: zigbee-herdsman-converters definition + * } + */ resolveEntity(key) { assert( typeof key === 'string' || typeof key === 'number' || @@ -153,13 +164,13 @@ class Zigbee extends events.EventEmitter { }; } - let postfix = postfixes.find((p) => key.endsWith(`/${p}`)); - const postfixByNumber = key.match(keyEndpointByNumber); - if (!postfix && postfixByNumber) { - postfix = Number(postfixByNumber[1]); + let endpointKey = endpointNames.find((p) => key.endsWith(`/${p}`)); + const endpointByNumber = key.match(keyEndpointByNumber); + if (!endpointKey && endpointByNumber) { + endpointKey = Number(endpointByNumber[1]); } - if (postfix) { - key = key.replace(`/${postfix}`, ''); + if (endpointKey) { + key = key.replace(`/${endpointKey}`, ''); } const entity = settings.getEntity(key); @@ -171,19 +182,17 @@ class Zigbee extends events.EventEmitter { return null; } - const mapped = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); - const endpoints = mapped && mapped.endpoint ? mapped.endpoint(device) : null; - let isDefaultEndpoint = true; + const definition = zigbeeHerdsmanConverters.findByZigbeeModel(device.modelID); + const endpoints = definition && definition.endpoint ? definition.endpoint(device) : null; let endpoint; - if (postfix) { - isDefaultEndpoint = false; - if (postfixByNumber) { - endpoint = device.getEndpoint(postfix); + if (endpointKey) { + if (endpointByNumber) { + endpoint = device.getEndpoint(endpointKey); } else { - assert(mapped != null, `Postfix '${postfix}' is given but device is unsupported`); - assert(endpoints != null, `Postfix '${postfix}' is given but device defines no endpoints`); - const endpointID = endpoints[postfix]; - assert(endpointID, `Postfix '${postfix}' is given but device has no such endpoint`); + assert(definition != null, `Endpoint name '${endpointKey}' is given but device is unsupported`); + assert(endpoints != null, `Endpoint name '${endpointKey}' is given but no endpoints defined`); + const endpointID = endpoints[endpointKey]; + assert(endpointID, `Endpoint name '${endpointKey}' is given but device has no such endpoint`); endpoint = device.getEndpoint(endpointID); } } else if (endpoints && endpoints['default']) { @@ -192,10 +201,8 @@ class Zigbee extends events.EventEmitter { endpoint = device.endpoints[0]; } - const endpointName = endpoints ? Object.entries(endpoints).find((e) => e[1] === endpoint.ID)[0] : null; return { - type: 'device', device, settings: entity, mapped, endpoint, name: entity.friendlyName, - isDefaultEndpoint, endpointName, + type: 'device', device, endpoint, settings: entity, name: entity.friendlyName, definition, }; } else { let group = this.getGroupByID(entity.ID); @@ -207,9 +214,10 @@ class Zigbee extends events.EventEmitter { return { type: 'device', device: key, + endpoint: key.endpoints[0], settings: setting, - mapped: zigbeeHerdsmanConverters.findByZigbeeModel(key.modelID), name: setting ? setting.friendlyName : (key.type === 'Coordinator' ? 'Coordinator' : key.ieeeAddr), + definition: zigbeeHerdsmanConverters.findByZigbeeModel(key.modelID), }; } } diff --git a/test/settings.test.js b/test/settings.test.js index f29057866..91e1e6ed2 100644 --- a/test/settings.test.js +++ b/test/settings.test.js @@ -595,7 +595,7 @@ describe('Settings', () => { expect(() => { settings.validate(); - }).toThrowError(`Following friendly_name are not allowed: '${utils.getPostfixes()}'`); + }).toThrowError(`Following friendly_name are not allowed: '${utils.getEndpointNames()}'`); }); it('Configuration shouldnt be valid when duplicate friendly_name are used', async () => {