From 4900a2501d88ea27e5574f35ca25f74f33a3dad0 Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Fri, 3 Jul 2020 21:20:22 +0200 Subject: [PATCH] Support Home Assistant configuration for external converters. https://github.com/Koenkk/zigbee-herdsman-converters/issues/1343 --- lib/extension/externalConverters.js | 25 ++++------------ lib/extension/homeassistant.js | 7 +++++ lib/util/utils.js | 23 +++++++++++++++ .../mock-external-converter-multiple.js | 15 +++++++++- test/externalConverters.test.js | 3 +- test/homeassistant.test.js | 29 +++++++++++++++++-- 6 files changed, 79 insertions(+), 23 deletions(-) diff --git a/lib/extension/externalConverters.js b/lib/extension/externalConverters.js index 4d26a6be..d7906fe6 100644 --- a/lib/extension/externalConverters.js +++ b/lib/extension/externalConverters.js @@ -1,30 +1,17 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const settings = require('../util/settings'); const Extension = require('./extension'); -const data = require('../util/data'); +const utils = require('../util/utils'); class ExternalConverters extends Extension { constructor(zigbee, mqtt, state, publishEntityState, eventBus) { super(zigbee, mqtt, state, publishEntityState, eventBus); - const externalConverters = settings.get().external_converters; - - externalConverters.forEach((moduleName) => { - let converterModule = moduleName; - - if (moduleName.endsWith('.js')) { - converterModule = data.joinPath(moduleName.split('.')[0]); - } - - const converter = require(converterModule); - if (Array.isArray(converter)) { - converter.forEach((mod) => { - zigbeeHerdsmanConverters.addDeviceDefinition(mod); - }); - } else { - zigbeeHerdsmanConverters.addDeviceDefinition(converter); - } - }); + for (const definition of utils.getExternalConvertersDefinitions(settings)) { + const toAdd = {...definition}; + delete toAdd['homeassistant']; + zigbeeHerdsmanConverters.addDeviceDefinition(toAdd); + } } } diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index 5cb86d18..7944fe6a 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -1,6 +1,7 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const settings = require('../util/settings'); const logger = require('../util/logger'); +const utils = require('../util/utils'); const zigbee2mqttVersion = require('../../package.json').version; const Extension = require('./extension'); const objectAssignDeep = require(`object-assign-deep`); @@ -1776,6 +1777,12 @@ class HomeAssistant extends Extension { this.eventBus.on('deviceRemoved', (data) => this.onDeviceRemoved(data.device)); this.eventBus.on('publishEntityState', (data) => this.onPublishEntityState(data)); this.eventBus.on('deviceRenamed', (data) => this.onDeviceRenamed(data.device)); + + for (const definition of utils.getExternalConvertersDefinitions(settings)) { + if (definition.hasOwnProperty('homeassistant')) { + mapping[definition.model] = definition.homeassistant; + } + } } onDeviceRemoved(device) { diff --git a/lib/util/utils.js b/lib/util/utils.js index 397ea09b..e36918ef 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -1,6 +1,7 @@ const zigbeeHerdsmanConverters = require('zigbee-herdsman-converters'); const equals = require('fast-deep-equal'); const humanizeDuration = require('humanize-duration'); +const data = require('./data'); // Xiaomi uses 4151 and 4447 (lumi.plug) as manufacturer ID. const xiaomiManufacturerID = [4151, 4447]; @@ -150,6 +151,27 @@ function parseJSON(value, failedReturnValue) { } } +function* getExternalConvertersDefinitions(settings) { + const externalConverters = settings.get().external_converters; + + for (const moduleName of externalConverters) { + let converterModule = moduleName; + + if (moduleName.endsWith('.js')) { + converterModule = data.joinPath(moduleName.split('.')[0]); + } + + const converter = require(converterModule); + if (Array.isArray(converter)) { + for (const item of converter) { + yield item; + } + } else { + yield converter; + } + } +} + module.exports = { millisecondsToSeconds: (milliseconds) => milliseconds / 1000, secondsToMilliseconds: (seconds) => seconds * 1000, @@ -169,4 +191,5 @@ module.exports = { getResponse, capitalize, parseJSON, + getExternalConvertersDefinitions, }; diff --git a/test/assets/mock-external-converter-multiple.js b/test/assets/mock-external-converter-multiple.js index 148f1329..338a065f 100644 --- a/test/assets/mock-external-converter-multiple.js +++ b/test/assets/mock-external-converter-multiple.js @@ -1,5 +1,18 @@ +const homeassistantSwitch = { + type: 'switch', + object_id: 'switch', + discovery_payload: { + payload_off: 'OFF', + payload_on: 'ON', + value_template: '{{ value_json.state }}', + command_topic: true, + }, +}; + const mockDevices = [{ - mock: 1 + mock: 1, + model: 'external_converters_device', + homeassistant: [homeassistantSwitch], }, { mock: 2 }]; diff --git a/test/externalConverters.test.js b/test/externalConverters.test.js index 1ccfb514..4fef558a 100644 --- a/test/externalConverters.test.js +++ b/test/externalConverters.test.js @@ -80,7 +80,8 @@ describe('Loads external converters', () => { await flushPromises(); expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenCalledTimes(2); expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(1, { - mock: 1 + mock: 1, + model: 'external_converters_device', }); expect(zigbeeHerdsmanConverters.addDeviceDefinition).toHaveBeenNthCalledWith(2, { mock: 2 diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index a0aea033..95174b4d 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -5,6 +5,9 @@ const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); const flushPromises = () => new Promise(setImmediate); const MQTT = require('./stub/mqtt'); const Controller = require('../lib/controller'); +const fs = require('fs'); +const path = require('path'); +const HomeAssistant = require('../lib/extension/homeassistant'); describe('HomeAssistant extension', () => { beforeEach(async () => { @@ -20,7 +23,6 @@ describe('HomeAssistant extension', () => { it('Should have mapping for all devices supported by zigbee-herdsman-converters', () => { const missing = []; - const HomeAssistant = require('../lib/extension/homeassistant'); const ha = new HomeAssistant(null, null, null, null, {on: () => {}}); require('zigbee-herdsman-converters').devices.forEach((d) => { @@ -34,7 +36,6 @@ describe('HomeAssistant extension', () => { it('Should not have duplicate type/object_ids in a mapping', () => { const duplicated = []; - const HomeAssistant = require('../lib/extension/homeassistant'); const ha = new HomeAssistant(null, null, null, null, {on: () => {}}); require('zigbee-herdsman-converters').devices.forEach((d) => { @@ -960,4 +961,28 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('homeassistant/device_automation/0x0017880104e45520/click_single/config'); expect(MQTT.publish.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/button/click'); }); + + it('Load Home Assistant mapping from external converters', async () => { + fs.copyFileSync(path.join(__dirname, 'assets', 'mock-external-converter-multiple.js'), path.join(data.mockDir, 'mock-external-converter-multiple.js')); + const beforeCount = Object.entries((new HomeAssistant(null, null, null, null, {on: () => {}}))._getMapping()).length; + settings.set(['external_converters'], ['mock-external-converter-multiple.js']); + controller = new Controller(); + const ha = controller.extensions.find((e) => e.constructor.name === 'HomeAssistant'); + await controller.start(); + await flushPromises(); + const afterCount = Object.entries(ha._getMapping()).length; + expect(beforeCount + 1).toStrictEqual(afterCount); + + const homeassistantSwitch = { + type: 'switch', + object_id: 'switch', + discovery_payload: { + payload_off: 'OFF', + payload_on: 'ON', + value_template: '{{ value_json.state }}', + command_topic: true, + }, + }; + expect(ha._getMapping()['external_converters_device']).toStrictEqual([homeassistantSwitch]); + }); });