diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 6b7e48887e..992af93559 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -1141,6 +1141,27 @@ export default class HomeAssistant extends Extension { }); } + /** + * If enum attribute does not have SET access and is named 'action', then expose + * as EVENT entity both sensor and event are discovered, this is to avoid + * breaking changes for sensors already existing in HA (legacy). + */ + if (firstExpose.access & ACCESS_STATE && !(firstExpose.access & ACCESS_SET) && firstExpose.property == 'action') { + discoveryEntries.push({ + type: 'event', + object_id: firstExpose.property, + mockProperties: [{property: firstExpose.property, value: null}], + discovery_payload: { + name: endpoint ? `${firstExpose.label} ${endpoint}` : firstExpose.label, + state_topic: true, + state_topic_postfix: 'action', + event_types: firstExpose.values.map((v) => v.toString()), + value_template: `{ "event_type": "{{value}}" }`, + ...ENUM_DISCOVERY_LOOKUP[firstExpose.name], + }, + }); + } + /** * If enum attribute has SET access then expose as SELECT entity too. * Note: currently both sensor and select are discovered, this is to avoid @@ -1248,9 +1269,12 @@ export default class HomeAssistant extends Extension { if (['binary_sensor', 'sensor'].includes(d.type) && d.discovery_payload.entity_category === 'config') { d.discovery_payload.entity_category = 'diagnostic'; } - }); - discoveryEntries.forEach((d) => { + // Event entities cannot have an entity_category set. + if (['event'].includes(d.type) && d.discovery_payload.entity_category) { + delete d.discovery_payload.entity_category; + } + // Let Home Assistant generate entity name when device_class is present if (d.discovery_payload.device_class) { delete d.discovery_payload.name; @@ -1532,7 +1556,7 @@ export default class HomeAssistant extends Extension { } if (!this.legacyTrigger) { - configs = configs.filter((c) => c.object_id !== 'action' && c.object_id !== 'click'); + configs = configs.filter((c) => (c.object_id !== 'action' && c.object_id !== 'click') || c.type == 'event'); } // deep clone of the config objects diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index 55e746d1d2..98ad49db30 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -1673,9 +1673,10 @@ describe('HomeAssistant extension', () => { it('Should discover trigger when click is published', async () => { const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]); - expect(discovered.length).toBe(7); + expect(discovered.length).toBe(8); expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/click/config'); expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/action/config'); + expect(discovered).toContain('homeassistant/event/0x0017880104e45520/action/config'); MQTT.publish.mockClear(); @@ -1856,8 +1857,9 @@ describe('HomeAssistant extension', () => { await resetExtension(); const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]); - expect(discovered.length).toBe(6); + expect(discovered.length).toBe(7); expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/action/config'); + expect(discovered).toContain('homeassistant/event/0x0017880104e45520/action/config'); expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/battery/config'); expect(discovered).toContain('homeassistant/sensor/0x0017880104e45520/linkquality/config'); }); @@ -1867,9 +1869,10 @@ describe('HomeAssistant extension', () => { await resetExtension(); const discovered = MQTT.publish.mock.calls.filter((c) => c[0].includes('0x0017880104e45520')).map((c) => c[0]); - expect(discovered.length).toBe(5); + expect(discovered.length).toBe(6); expect(discovered).not.toContain('homeassistant/sensor/0x0017880104e45520/click/config'); expect(discovered).not.toContain('homeassistant/sensor/0x0017880104e45520/action/config'); + expect(discovered).toContain('homeassistant/event/0x0017880104e45520/action/config'); MQTT.publish.mockClear();