Add this function to your python script after desc = ''
def flatten(entity_id, result=[], searched=[]):
state = hass.states.get(entity_id)
if state is not None:
entity_ids = state.attributes.get("entity_id")
if entity_ids is not None:
searched.append(entity_id)
for entity_id in entity_ids:
if entity_id not in result and entity_id not in searched:
flatten(entity_id, result, searched)
else:
result.append(entity_id)
then change the beginning of your for loop from
for entity_id in hass.states.get(group).attributes['entity_id']:
to
entity_ids = []
flatten(group, entity_ids)
for entity_id in entity_ids:
thanks!
had been experimenting with the flatten function, but thought it would be available already, and not required the definition in the script itself.
Report any issues with it. It’s a recursive function and I tried to trap against circular group configurations. You could get max recursion exceptions, hopefully you won’t.
though, the groups I use don’t have any circular reference. As posted above the top group holds a few sub groups which again hold a few groups, until the last group that only holds single entities. None of the entities (lights) are in any other group.
The whole exercise was about that, so I have been careful in the group configurations.
but not in the python, in a js template referencing the now toplevel group.all_inside_lights:
- type: custom:button-card
entity: group.all_inside_lights
template: button_title_counter
triggers_update: input_boolean.run_lights_summary
label: >
[[[
var i;
var entities = entity.attributes.entity_id;
var count = 0;
for (i = 0; i < entities.length; i++) {
var state = states[entities[i]].state;
if (state == 'on') {count += 1;}
}
if (count == entities.length) return 'All ' + entities.length + ' lights on';
if (count == 0) return 'No lights on';
return 'Lights on: ' + count + ' of ' + entities.length;
]]]
counted the lights on before, but now, as is to be expected shows only the count of the groups (which now are the entities inside the group.all_inside_lights
so, the question is, can we also ‘flatten’ groups in JS…? some googling suggests we can, but I havent found a real JS replacement for what you made in the python_script.
If you’d have a moment and see a challenge, Id appreciate it. Guess it could be very useful for others too, having a counter iterating groups for entities
Otherwise I fear I’d see myself forced to re-introduce the verbose group…
would have been nice if something along then lines of
[[[
var i;
var flattened = entity.flat(4);
var entities = flattened.attributes.entity_id;
var count = 0;
for (i = 0; i < entities.length; i++) {
var state = states[entities[i]].state;
if (state == 'on') {count += 1;}
}
if (count == entities.length) return 'All ' + entities.length + ' lights on';
if (count == 0) return 'No lights on';
return 'Lights on: ' + count + ' of ' + entities.length;
]]]
would have been possible.
new territories, finding/learning available JS functions…
Yeah your googling the wrong information. You want to google recursive functions. But if you simply went through the python script and tried to replace each piece with the correct call/syntax for JS, you’d get what you want.
finding myself in new territory trying to define a recursive function in JS, and the translation from Python…
first structural attempt would be something like this, where entity_id is the main group, entities the group members, and e an individual light that will be appended (or not):
function flatten(entity, lights_result, lights_searched){
return entity; # ??< dont yet see what I should do here..
}
var entities = entity.attributes.entity_id;
var lights_result = []
var lights_searched = []
if (entity && entities ) return
searched.append(entity); # set the first main group to be the entity
for (e in entities) {(return e not in lights_result && e not in lights_searched) # check if an entity is in either list
? flatten(e, lights_result, lights_searched) : null ;} # if no, append, if yes, do nothing
return result.append(e);
could you please comment on the most basic of errors…
(replaced only one group per level for illustrational purposes )
making the full function like this then:
[[[
function flatten(entity, lights_result= [], lights_searched= []){
var state = entity.entity_id;
return state;
if (state != None) {
var entities = entity.attributes.entity_id;
if (entities != None) {
searched.append(entities);
for (e in entities) {
(! lights_result.includes(e) && ! lights_searched.includes(e))
? flatten(e, lights_result, lights_searched) : null
}
}
}
return result.append(entities);
}
and then use this in the actual template…
[[[
function flatten(entity, lights_result= [], lights_searched= []){
var state = entity.entity_id;
return state;
if (state != None) {
var entities = entity.attributes.entity_id;
if (entities != None) {
searched.append(entities);
for (e in entities) {
(! lights_result.includes(e) && ! lights_searched.includes(e))
? flatten(e, lights_result, lights_searched) : null
}
}
}
return result.append(entities);
}
# var entities = entity.attributes.entity_id; #<--- this needs to become the flattened list of lights so:
var light_ids = []; # <-- new variable to hold the light_ids
var entitities = flatten(entity, light_ids); # <-- flatten group 'entity' in list 'light_ids'
var i;
var count = 0;
for (i = 0; i < entities.length; i++) {
var state = states[entities[i]].state;
if (state == 'on') {count += 1;}
}
if (count == entities.length) return 'All ' + entities.length + ' lights on';
if (count == 0) return 'No lights on';
return 'Lights on: ' + count + ' of ' + entities.length;
]]]
That’s how I deal with it in button-card (the entity being a group). This returns the number of entities which are on in the group and works for nested groups.
if (!entity) return 0;
function loop(list, value) {
list.forEach(entity => { // This entity is different from the button-card entity, I should have named it differently...
if (states[entity])
if (entity.split('.')[0] === "group")
value = loop(states[entity].attributes.entity_id, value);
else if (["open", "on"].includes(states[entity].state))
value++;
});
return value;
}
return loop(entity.attributes.entity_id, 0);
ok, ha thanks.
thats a totally different approach isn’t it? how do you get the length of all entities in the group in this format? is that loop.length ?
label: >
[[[
if (!entity) return 0;
function loop(list, value) {
list.forEach(entity => { // This entity is different from the button-card entity, I should have named it differently...
if (states[entity])
if (entity.split('.')[0] === "group")
value = loop(states[entity].attributes.entity_id, value);
else if (["open", "on"].includes(states[entity].state))
value++;
});
return value;
}
return loop(entity.attributes.entity_id, 0);
]]]
can this be combined in 1 go? because, in fact I need them both, the ‘on’ number, and the total number:
if (count == entities.length) return 'All ' + entities.length + ' lights on';
if (count == 0) return 'No lights on';
return 'Lights on: ' + count + ' of ' + entities.length;
btw, I notice this updates immediately. How come the top template doesn’t work like that? (using groups in groups)
I ask, because in my FR just posted on the GitHub, I mention this, and your template seems to already do that…?