diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 2936542c7..945b6fa7a 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -1056,9 +1056,11 @@ export default class HomeAssistant extends Extension { payload.json_attributes_topic = stateTopic; } - // Set (unique) name, separate by space if friendlyName contains space. - const nameSeparator = entity.name.includes('_') ? '_' : ' '; - payload.name = entity.name; + const devicePayload = this.getDevicePayload(entity); + + // Set (unique) name, separate by space if device name contains space. + const nameSeparator = devicePayload.name.includes('_') ? '_' : ' '; + payload.name = devicePayload.name; if (config.object_id.startsWith(config.type) && config.object_id.includes('_')) { payload.name += `${nameSeparator}${config.object_id.split(/_(.+)/)[1]}`; } else if (!config.object_id.startsWith(config.type)) { @@ -1069,7 +1071,7 @@ export default class HomeAssistant extends Extension { payload.unique_id = `${entity.options.ID}_${config.object_id}_${settings.get().mqtt.base_topic}`; // Attributes for device registry - payload.device = this.getDevicePayload(entity); + payload.device = devicePayload; // Availability payload payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}]; @@ -1185,10 +1187,12 @@ export default class HomeAssistant extends Extension { // Override configuration with user settings. if (entity.options.hasOwnProperty('homeassistant')) { - const add = (obj: KeyValue): void => { + const add = (obj: KeyValue, ignoreName: boolean): void => { Object.keys(obj).forEach((key) => { if (['type', 'object_id'].includes(key)) { return; + } else if (ignoreName && key === 'name') { + return; } else if (['number', 'string', 'boolean'].includes(typeof obj[key]) || Array.isArray(obj[key])) { payload[key] = obj[key]; @@ -1202,10 +1206,10 @@ export default class HomeAssistant extends Extension { }); }; - add(entity.options.homeassistant); + add(entity.options.homeassistant, true); if (entity.options.homeassistant.hasOwnProperty(config.object_id)) { - add(entity.options.homeassistant[config.object_id]); + add(entity.options.homeassistant[config.object_id], false); } } @@ -1295,9 +1299,16 @@ export default class HomeAssistant extends Extension { private getDevicePayload(entity: Device | Group): KeyValue { const identifierPostfix = entity.isGroup() ? `zigbee2mqtt_${this.getEncodedBaseTopic()}` : 'zigbee2mqtt'; + + // Allow device name to be overriden by homeassistant config + let deviceName = entity.name; + if (typeof entity.options.homeassistant?.name === 'string') { + deviceName = entity.options.homeassistant.name; + } + const payload: KeyValue = { identifiers: [`${identifierPostfix}_${entity.options.ID}`], - name: entity.name, + name: deviceName, sw_version: `Zigbee2MQTT ${this.zigbee2MQTTVersion}`, }; diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index e04a04387..b8beded35 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -476,7 +476,7 @@ describe('HomeAssistant extension', () => { device: { manufacturer: 'Not from Xiaomi', model: 'custom model', - } + }, }, friendly_name: 'weather_sensor', retain: false, @@ -545,6 +545,75 @@ describe('HomeAssistant extension', () => { ); }); + it('Should discover devices with overriden name', async () => { + settings.set(['devices', '0x0017880104e45522'], { + homeassistant: { + name: "Weather Sensor", + }, + friendly_name: 'weather_sensor', + retain: false, + }) + + await resetExtension(); + + let payload; + await flushPromises(); + + payload = { + 'unit_of_measurement': '°C', + 'device_class': 'temperature', + 'state_class': 'measurement', + 'value_template': '{{ value_json.temperature }}', + 'state_topic': 'zigbee2mqtt/weather_sensor', + 'json_attributes_topic': 'zigbee2mqtt/weather_sensor', + 'name': 'Weather Sensor temperature', + 'unique_id': '0x0017880104e45522_temperature_zigbee2mqtt', + 'device': { + 'identifiers': ['zigbee2mqtt_0x0017880104e45522'], + 'name': 'Weather Sensor', + 'sw_version': null, + 'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)', + 'manufacturer': 'Xiaomi', + }, + 'availability': [{topic: 'zigbee2mqtt/bridge/state'}], + 'enabled_by_default': true, + }; + + expect(MQTT.publish).toHaveBeenCalledWith( + 'homeassistant/sensor/0x0017880104e45522/temperature/config', + stringify(payload), + { retain: true, qos: 0 }, + expect.any(Function), + ); + + payload = { + 'unit_of_measurement': '%', + 'device_class': 'humidity', + 'state_class': 'measurement', + 'value_template': '{{ value_json.humidity }}', + 'state_topic': 'zigbee2mqtt/weather_sensor', + 'json_attributes_topic': 'zigbee2mqtt/weather_sensor', + 'name': 'Weather Sensor humidity', + 'unique_id': '0x0017880104e45522_humidity_zigbee2mqtt', + 'enabled_by_default': true, + 'device': { + 'identifiers': ['zigbee2mqtt_0x0017880104e45522'], + 'name': 'Weather Sensor', + 'sw_version': null, + 'model': 'Aqara temperature, humidity and pressure sensor (WSDCGQ11LM)', + 'manufacturer': 'Xiaomi', + }, + 'availability': [{topic: 'zigbee2mqtt/bridge/state'}], + }; + + expect(MQTT.publish).toHaveBeenCalledWith( + 'homeassistant/sensor/0x0017880104e45522/humidity/config', + stringify(payload), + { retain: true, qos: 0 }, + expect.any(Function), + ); + }); + it('Should discover devices with overriden user configuration affecting type and object_id', async () => { settings.set(['devices', '0x0017880104e45541'], { friendly_name: 'my_switch', @@ -556,8 +625,7 @@ describe('HomeAssistant extension', () => { light: { type: 'this should be ignored', name: 'my_light_name_override' - } - + }, }, })