diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index c9f9301bc..c37eefe2c 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -111,7 +111,7 @@ export default class HomeAssistant extends Extension { } private exposeToConfig(exposes: zhc.DefinitionExpose[], entityType: 'device' | 'group', - definition?: zhc.Definition, definitionExposes?: zhc.DefinitionExpose[]): DiscoveryEntry[] { + allExposes: zhc.DefinitionExpose[], definition?: zhc.Definition): DiscoveryEntry[] { // For groups an array of exposes (of the same type) is passed, this is to determine e.g. what features // to use for a bulb (e.g. color_xy/color_temp) assert(entityType === 'group' || exposes.length === 1, 'Multiple exposes for device not allowed'); @@ -171,10 +171,11 @@ export default class HomeAssistant extends Extension { discoveryEntry.discovery_payload.min_mireds = min; } - const effect = definitionExposes?.find((e) => e.type === 'enum' && e.name === 'effect'); - if (effect) { + const effects = utils.arrayUnique(utils.flatten( + allExposes.filter((e) => e.type === 'enum' && e.name === 'effect').map((e) => e.values))); + if (effects.length) { discoveryEntry.discovery_payload.effect = true; - discoveryEntry.discovery_payload.effect_list = effect.values; + discoveryEntry.discovery_payload.effect_list = effects; } discoveryEntries.push(discoveryEntry); @@ -386,9 +387,9 @@ export default class HomeAssistant extends Extension { ?.features.find((f) => f.name === 'position'); const tilt = exposes.find((expose) => expose.features.find((e) => e.name === 'tilt')) ?.features.find((f) => f.name === 'tilt'); - const motorState = definitionExposes?.find((e) => e.type === 'enum' && e.name === 'motor_state' && + const motorState = allExposes?.find((e) => e.type === 'enum' && e.name === 'motor_state' && e.access === ACCESS_STATE); - const running = definitionExposes?.find((e) => e.type === 'binary' && e.name === 'running'); + const running = allExposes?.find((e) => e.type === 'binary' && e.name === 'running'); const discoveryEntry: DiscoveryEntry = { type: 'cover', @@ -983,7 +984,7 @@ export default class HomeAssistant extends Extension { if (isDevice) { const exposes = entity.exposes(); // avoid calling it hundred of times/s for (const expose of exposes) { - configs.push(...this.exposeToConfig([expose], 'device', entity.definition, exposes)); + configs.push(...this.exposeToConfig([expose], 'device', exposes, entity.definition)); } for (const mapping of legacyMapping) { @@ -1000,10 +1001,13 @@ export default class HomeAssistant extends Extension { } } else { // group const exposesByType: {[s: string]: zhc.DefinitionExpose[]} = {}; + const allExposes: zhc.DefinitionExpose[] = []; entity.zh.members.map((e) => this.zigbee.resolveEntity(e.getDevice()) as Device) .filter((d) => d.definition).forEach((device) => { - for (const expose of device.exposes().filter((e) => groupSupportedTypes.includes(e.type))) { + const exposes = device.exposes(); + allExposes.push(...exposes); + for (const expose of exposes.filter((e) => groupSupportedTypes.includes(e.type))) { let key = expose.type; if (['switch', 'lock', 'cover'].includes(expose.type) && expose.endpoint) { // A device can have multiple of these types which have to discovered seperately. @@ -1018,7 +1022,7 @@ export default class HomeAssistant extends Extension { }); configs = [].concat(...Object.values(exposesByType) - .map((exposes) => this.exposeToConfig(exposes, 'group'))); + .map((exposes) => this.exposeToConfig(exposes, 'group', allExposes))); } if (isDevice && settings.get().advanced.last_seen !== 'disable') { diff --git a/lib/util/utils.ts b/lib/util/utils.ts index f051f412b..1db69a873 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -314,6 +314,14 @@ function isEndpoint(obj: unknown): obj is zh.Endpoint { return obj.constructor.name.toLowerCase() === 'endpoint'; } +function flatten(arr: Type[][]): Type[] { + return [].concat(...arr); +} + +function arrayUnique(arr: Type[]): Type[] { + return [...new Set(arr)]; +} + function isZHGroup(obj: unknown): obj is zh.Group { return obj.constructor.name.toLowerCase() === 'group'; } @@ -357,5 +365,5 @@ export default { getExternalConvertersDefinitions, removeNullPropertiesFromObject, toNetworkAddressHex, toSnakeCase, parseEntityID, isEndpoint, isZHGroup, hours, minutes, seconds, validateFriendlyName, sleep, sanitizeImageParameter, isAvailabilityEnabledForEntity, publishLastSeen, availabilityPayload, - getAllFiles, filterProperties, + getAllFiles, filterProperties, flatten, arrayUnique, }; diff --git a/test/homeassistant.test.js b/test/homeassistant.test.js index fd7f3adc7..c53824496 100644 --- a/test/homeassistant.test.js +++ b/test/homeassistant.test.js @@ -98,6 +98,15 @@ describe('HomeAssistant extension', () => { "xy", "color_temp" ], + "effect":true, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], "unique_id":"9_light_zigbee2mqtt" }; @@ -1306,6 +1315,15 @@ describe('HomeAssistant extension', () => { "xy", "color_temp" ], + "effect":true, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], "unique_id":"9_light_zigbee2mqtt" }; @@ -1822,6 +1840,15 @@ describe('HomeAssistant extension', () => { "xy", "color_temp" ], + "effect":true, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], "unique_id":"9_light_zigbee2mqtt" }; @@ -1858,6 +1885,15 @@ describe('HomeAssistant extension', () => { "xy", "color_temp" ], + "effect":true, + "effect_list":[ + "blink", + "breathe", + "okay", + "channel_change", + "finish_effect", + "stop_effect" + ], "unique_id":"9_light_zigbee2mqtt" };