diff --git a/README.md b/README.md index 2705855..f07b970 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,19 @@ filter: entity_id: this.entity_id ``` +Show all the lights in a group and its nested groups. I.e. a group of groups. +```yaml + type: custom:auto-entities + card: + type: entities + filter: + include: + - group: group.my_group_of_groups + domain: light + options: + nested_groups: true +``` + Example using templates: ```yaml type: custom:auto-entities diff --git a/src/filter.js b/src/filter.js index 33db425..c3b96fb 100644 --- a/src/filter.js +++ b/src/filter.js @@ -27,6 +27,38 @@ function match(pattern, value) { return pattern === value; } +const groupContainsEntityRecursively = (hass, groupName, entityId, iterationLevel = 0) => { + // directly return false if we are 5 levels deep into the recursion + // to avoid infinite loops + if (iterationLevel > 5) { + return false; + } + + const group = hass.states[groupName]; + + if (!group.attributes.entity_id || group.attributes.entity_id.length < 0) { + return false; + } + + const groupEntities = group.attributes.entity_id; + + if (groupEntities.includes(entityId)) { + return true; + } + + // Check recursively if an entity is found + return groupEntities + .filter((groupEntity) => groupEntity.startsWith("group.")) + .some((groupEntity) => { + return groupContainsEntityRecursively( + hass, + groupEntity, + entityId, + iterationLevel + 1 + ); + }); +}; + export function entity_filter(hass, filter) { return function(e) { const entity = typeof(e) === "string" @@ -63,9 +95,11 @@ export function entity_filter(hass, filter) { case "group": if(!value.startsWith("group.") - || !hass.states[value] - || !hass.states[value].attributes.entity_id - || !hass.states[value].attributes.entity_id.includes(entity.entity_id) + || (filter.options && filter.options.nested_groups && !groupContainsEntityRecursively(hass, value, entity.entity_id)) + || ((!filter.options || !filter.options.nested_groups) + && (!hass.states[value] + || !hass.states[value].attributes.entity_id + || !hass.states[value].attributes.entity_id.includes(entity.entity_id))) ) return false; break;