From 1d36dfd9329f7f6cd9d2af552be289e56181d6ed Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Wed, 9 Nov 2022 21:01:30 +0100 Subject: [PATCH] Support Home Assistant MQTT update #14748 (#14905) --- lib/extension/homeassistant.ts | 27 ++++++++++++++++++++++++++- test/frontend.test.js | 4 ++-- test/homeassistant.test.js | 14 +++++++------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 9172d847..72882ad4 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -1082,6 +1082,23 @@ export default class HomeAssistant extends Extension { }, }; configs.push(updateAvailableSensor); + const updateSensor: DiscoveryEntry = { + type: 'update', + object_id: 'update', + mockProperties: [{property: 'update', value: {state: null}}], + discovery_payload: { + entity_picture: 'https://github.com/Koenkk/zigbee2mqtt/raw/master/images/logo.png', + latest_version_topic: true, + state_topic: true, + device_class: 'firmware', + command_topic: `${settings.get().mqtt.base_topic}/bridge/request/device/ota_update/update`, + payload_install: `{"id": "${entity.ieeeAddr}"}`, + value_template: `{{ value_json['update']['installed_version'] }}`, + latest_version_template: `{% if value_json['update']['state'] == "available" %}{{ 'newer' }}` + + `{% else %}{{ value_json['update']['installed_version'] }}{% endif %}`, + }, + }; + configs.push(updateSensor); } if (isDevice && entity.options.hasOwnProperty('legacy') && !entity.options.legacy) { @@ -1193,7 +1210,7 @@ export default class HomeAssistant extends Extension { delete payload.command_topic_postfix; const commandTopic = `${baseTopic}/${commandTopicPrefix}set${commandTopicPostfix}`; - if (payload.command_topic) { + if (payload.command_topic && typeof payload.command_topic !== 'string') { payload.command_topic = commandTopic; } @@ -1248,6 +1265,10 @@ export default class HomeAssistant extends Extension { payload.fan_mode_state_topic = stateTopic; } + if (payload.latest_version_topic) { + payload.latest_version_topic = stateTopic; + } + if (payload.fan_mode_command_topic) { payload.fan_mode_command_topic = `${baseTopic}/${commandTopicPrefix}set/fan_mode`; } @@ -1439,6 +1460,10 @@ export default class HomeAssistant extends Extension { message.color.s = message.color.saturation; } } + + if (entity.isDevice() && entity.definition.ota) { + message['update']['installed_version'] = entity.zh.softwareBuildID; + } } private getEncodedBaseTopic(): string { diff --git a/test/frontend.test.js b/test/frontend.test.js index 57cab265..4a540dac 100644 --- a/test/frontend.test.js +++ b/test/frontend.test.js @@ -140,7 +140,7 @@ describe('Frontend', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', - stringify({state: 'ON', power_on_behavior:null, linkquality: null, update_available: null, update: {state: null}}), + stringify({state: 'ON', power_on_behavior:null, linkquality: null, update_available: null, update: {state: null, installed_version: null}}), { retain: false, qos: 0 }, expect.any(Function) ); @@ -151,7 +151,7 @@ describe('Frontend', () => { // Received message on socket expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(1); - expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', power_on_behavior:null, linkquality: null, update_available: null, update: {state: null}}})); + expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', power_on_behavior:null, linkquality: null, update_available: null, update: {state: null, installed_version: null}}})); // Shouldnt set when not ready mockWSClient.implementation.send.mockClear(); diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index bf39292a..9ab7d8da 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -955,7 +955,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', - stringify({"color":{"hue": 0, "saturation": 100, "h": 0, "s": 100}, "color_mode": "hs", "linkquality": null, "state": null, "update_available": null, "power_on_behavior":null, "update": {"state": null}}), + stringify({"color":{"hue": 0, "saturation": 100, "h": 0, "s": 100}, "color_mode": "hs", "linkquality": null, "state": null, "update_available": null, "power_on_behavior":null, "update": {"state": null, "installed_version": null}}), { retain: false, qos: 0 }, expect.any(Function), ); @@ -971,7 +971,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', - stringify({"color": {"x": 0.4576,"y": 0.41}, "color_mode": "xy", "linkquality": null,"state": null, "update_available": null, "power_on_behavior":null, "update": {"state": null}}), + stringify({"color": {"x": 0.4576,"y": 0.41}, "color_mode": "xy", "linkquality": null,"state": null, "update_available": null, "power_on_behavior":null, "update": {"state": null, "installed_version": null}}), { retain: false, qos: 0 }, expect.any(Function), ); @@ -987,7 +987,7 @@ describe('HomeAssistant extension', () => { expect(MQTT.publish).toHaveBeenCalledTimes(1); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb_color', - stringify({"linkquality": null,"state": "ON", "update_available": null, "power_on_behavior": null, "update": {"state": null}}), + stringify({"linkquality": null,"state": "ON", "update_available": null, "power_on_behavior": null, "update": {"state": null, "installed_version": null}}), { retain: false, qos: 0 }, expect.any(Function), ); @@ -1075,13 +1075,13 @@ describe('HomeAssistant extension', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null, "update": {"state": null}}), + stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null, "update": {"state": null, "installed_version": null}}), { retain: true, qos: 0 }, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/remote', - stringify({"action":null,"action_duration":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null, "update": {"state": null}}), + stringify({"action":null,"action_duration":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null, "update": {"state": null, "installed_version": null}}), { retain: true, qos: 0 }, expect.any(Function) ); @@ -1105,13 +1105,13 @@ describe('HomeAssistant extension', () => { await flushPromises(); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/bulb', - stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null, "update": {"state": null}}), + stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null, "update": {"state": null, "installed_version": null}}), { retain: true, qos: 0 }, expect.any(Function) ); expect(MQTT.publish).toHaveBeenCalledWith( 'zigbee2mqtt/remote', - stringify({"action":null,"action_duration":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null, "update": {"state": null}}), + stringify({"action":null,"action_duration":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null, "update": {"state": null, "installed_version": null}}), { retain: true, qos: 0 }, expect.any(Function) );