From 42a4596be6a7fbedb98e82fd7cae32c1f083edb3 Mon Sep 17 00:00:00 2001 From: John Doe Date: Thu, 8 Apr 2021 22:57:55 +0800 Subject: [PATCH] Add device icon from converter definition (#7001) --- lib/extension/bridge.js | 17 +++++++++++++--- lib/util/utils.js | 8 ++++++++ test/bridge.test.js | 44 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/lib/extension/bridge.js b/lib/extension/bridge.js index 763552e54..72046ae9e 100644 --- a/lib/extension/bridge.js +++ b/lib/extension/bridge.js @@ -116,7 +116,7 @@ class Bridge extends Extension { if (data.status === 'successful') { const definition = resolvedEntity.definition; payload.supported = !!definition; - payload.definition = this.getDefinitionPayload(definition); + payload.definition = this.getDefinitionPayload(definition, resolvedEntity); } } else if (type === 'deviceAnnounce') { payload = { @@ -618,7 +618,7 @@ class Bridge extends Extension { network_address: device.networkAddress, supported: !!definition, friendly_name: resolved.name, - definition: this.getDefinitionPayload(definition), + definition: this.getDefinitionPayload(definition, resolved), power_source: device.powerSource, software_build_id: device.softwareBuildID, date_code: device.dateCode, @@ -652,14 +652,25 @@ class Bridge extends Extension { 'bridge/groups', stringify(groups), {retain: true, qos: 0}, settings.get().mqtt.base_topic, true); } - getDefinitionPayload(definition) { + getDefinitionPayload(definition, resolvedDevice) { if (definition) { + let icon = definition.icon; + if (icon) { + let zigbeeModel = ''; + /* istanbul ignore else */ + if (resolvedDevice && resolvedDevice.hasOwnProperty('device')) { + zigbeeModel = resolvedDevice.device.modelID; + } + icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(zigbeeModel)); + icon = icon.replace('${model}', utils.sanitizeImageParameter(definition.model)); + } return { model: definition.model, vendor: definition.vendor, description: definition.description, exposes: definition.exposes, supports_ota: !!definition.ota, + icon, }; } else { return null; diff --git a/lib/util/utils.js b/lib/util/utils.js index 9b67f0cae..b93102336 100644 --- a/lib/util/utils.js +++ b/lib/util/utils.js @@ -243,6 +243,13 @@ function sleep(seconds) { return new Promise((resolve) => setTimeout(resolve, seconds * 1000)); } +function sanitizeImageParameter(parameter) { + const replaceByDash = [/\?/g, /&/g, /[^a-z\d\- _./:]/gi]; + let sanitized = parameter; + replaceByDash.forEach((r) => sanitized = sanitized.replace(r, '-')); + return sanitized; +} + module.exports = { millisecondsToSeconds: (milliseconds) => milliseconds / 1000, secondsToMilliseconds: (seconds) => seconds * 1000, @@ -268,4 +275,5 @@ module.exports = { loadModuleFromFile, loadModuleFromText, getKey, + sanitizeImageParameter, }; diff --git a/test/bridge.test.js b/test/bridge.test.js index ecf874f68..994ffbf23 100644 --- a/test/bridge.test.js +++ b/test/bridge.test.js @@ -1048,4 +1048,46 @@ describe('Bridge', () => { {retain: false, qos: 0}, expect.any(Function) ); }); -}); + + it('Icon link handling', async () => { + const bridge = controller.extensions.find((e) => e.constructor.name === 'Bridge'); + expect(bridge).not.toBeUndefined(); + + const definition = {'model': 'lumi.plug'}; + const device = zigbeeHerdsman.devices.ZNCZ02LM; + const icon_link = 'https://www.zigbee2mqtt.io/images/devices/ZNCZ02LM.jpg'; + definition.icon = icon_link; + let payload = bridge.getDefinitionPayload(definition, {device: device}); + expect(payload).not.toBeUndefined() + expect(payload['icon']).not.toBeUndefined() + expect(payload.icon).toBe(icon_link); + + definition.icon = '_${model}_'; + payload = bridge.getDefinitionPayload(definition, {device: device}); + expect(payload).not.toBeUndefined() + expect(payload['icon']).not.toBeUndefined() + expect(payload.icon).toBe('_lumi.plug_'); + + definition.icon = '_${model}_${zigbeeModel}_'; + payload = bridge.getDefinitionPayload(definition, {device: device}); + expect(payload).not.toBeUndefined() + expect(payload['icon']).not.toBeUndefined() + expect(payload.icon).toBe('_lumi.plug_lumi.plug_'); + + const svg_icon = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDo'; + definition.icon = svg_icon; + payload = bridge.getDefinitionPayload(definition, {device: device}); + expect(payload).not.toBeUndefined() + expect(payload['icon']).not.toBeUndefined() + expect(payload.icon).toBe(svg_icon); + + + device.modelID = '?._Z\\NC+Z02*LM'; + definition.model = '&&&&*+'; + definition.icon = '_${model}_${zigbeeModel}_'; + payload = bridge.getDefinitionPayload(definition, {device: device}); + expect(payload).not.toBeUndefined() + expect(payload['icon']).not.toBeUndefined() + expect(payload.icon).toBe('_------_-._Z-NC-Z02-LM_'); + }); +}); \ No newline at end of file