Need help selecting entities in grouped groups, in Python script

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);
        ]]]

For the length:

        if (!entity) return 0;
        const loop = (list, max) => {
          list.forEach(entity => {
            if (states[entity])
              if (entity.split('.')[0] === "group")
                max = loop(states[entity].attributes.entity_id, max);
              else {
                max++
              }
          });
          return max;
        }
        return loop(entity.attributes.entity_id, 0);
1 Like

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…?

You can combine both, but it will still not update automatically if a child entity/group is updated, only if the main group goes from on to off.

This should do the trick to retrieve both values (I didn’t try it but it should work)

if (!entity) return 0;
function loop(list, stats = {total: 0, enabled = 0}) {
  list.forEach(child_entity => {
    if (states[child_entity])
      if (child_entity.split('.')[0] === "group") {
        stats = loop(states[child_entity].attributes.entity_id, stats);
      } else {
        stats.total += 1;
        if (["open", "on"].includes(states[child_entity].state))
          stats.enabled += 1;
      }
  });
  return stats;
}
let stats = loop(entity.attributes.entity_id);
// stats.enabled => number of on
// stats.total => total

Thanks!

About the updating : I forgot I had set the triggers_update, so that’s why it updated immediately.

FR stands…

still not correct, i’ll give you the answer. This is what the second line would have been:

function flatten(entity_id, lights_result= [], lights_searched= [])
{
    var state = states[entity_id];
}

You keep trying to go from the entity, and you can’t do that. You have to go from the entity_id, which I said multiple times.

small typo

but this works!
stats = {total: 0, enabled : 0}

    label: >
      [[[
        if (!entity) return 0;
        function loop(list, stats = {total: 0, enabled : 0}) {
          list.forEach(child_entity => {
            if (states[child_entity])
              if (child_entity.split('.')[0] === "group") {
                stats = loop(states[child_entity].attributes.entity_id, stats);
              } else {
                stats.total += 1;
                if (["open", "on"].includes(states[child_entity].state))
                  stats.enabled += 1;
              }
          });
          return stats;
        }
        let stats = loop(entity.attributes.entity_id);

        if (stats.enabled == stats.total) return 'All ' + stats.enabled + ' lights on';
        if (stats.enabled == 0) return 'No lights on';
        return 'Lights on: ' + stats.enabled + ' of ' + stats.total;

        ]]]

thanks.

Now turning to Petro for some humble efforts to continue the other way of getting this to work…

ok, consuming your lessons. Please be gentle…

going from there, do I build the template the way I did above, (all this nesting/recursion in JS is unprecedented for me) ?

btw Romrider,

if one does this

  all_inside_lights:
    name: All inside lights
    icon: mdi:lightbulb-outline
    entities:
      - group.main_inside_lights
      - group.guest_inside_lights
      - group.living_ceiling_spots
      - group.main_inside_lights

the lights of the double listed groups are counted twice. Meaning the recursion test Petro does in the Python should be done here too. First flatten the group and then count ?

All I’m trying to do is get you to go line by line and convert the function. Once it’s done, it will work identical to the python one. I don’t want you to change the functionality, just recreate it in JS. You’ve used all the commands, and the word recursion just means that the function calls itself.

And just to clarify, @RomRider’s second template also uses recursion.

sure, and that’s appreciated highly.
Where I am lost though is at the definition of the function in JS, I don’t get what happens if it is only this in JS now:

function flatten(entity_id, lights_result= [], lights_searched= [])
{
    var state = states[entity_id];
}

where all the rest of the calucation is included in the function in Python.

And yes, I see the recursion in Romriders template of course, that’s why I have already tried to simply add the creation of the 2 lists in there, and return the counts only if an entity isn’t in an appended list, but I cant get the syntax correct.

Back to yours, going line by line was where I left it here. how to proceed from there.

my efforts stopped here:

          
           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);
}

the fact I can not test the code doesn’t help either… would really have liked an online parser

You can totally test the code. Just make a fake button with just that JS.

As an aside, You should try to make that readable, meaning, line the close brackets up with the open brackets call.

So lets go line by line…

None doesn’t exist in JS. You know this. You know what is in js too. You’re using it later on. null. You also have used code that replicates this exact check in other areas. Even @RomRider’s function is using it.

hint:


Correct!


see previous comment about none


That’s not how you add to a collection/list in JS. .append is a JS call. So if you went line by line, you’d just google “python .append equivalent JS”. This is what I meant with converting. You doing the leg work and figuring out the correct calls to make instead of relying on others. If you google “python .append equivalent JS” the first item in the search is the answer. .push().

Also, you’re adding entities to the list. That’s not correct. My code added entity_id.


Again, 100% python code, nothing translated to JS. Google “looping arrays JS”. Admittedly, this would be the hardest one and I would expect any beginner to ask questions here. This can be done a number of ways, @RomRider uses the method I would use, forEach. And I wouldn’t simplify entity_id down to e, but that’s a personal preference.

entities.forEach(entity_id => {
}

You’re missing the if statement, and you can’t short hand this because there is no else. So it has to be a longhand if statement. Your syntax is correct for the if statement, but you either changed the list names and didn’t update them properly or you had a typo in a previous line.

        function flatten(entity_id, result= [], searched= []) {
          var state = states[entity_id];
          }

          if (state) {
            var entities = entity.attributes.entity_id;
            if (entities) {
              searched.push(entity_id);
              entities.forEach(entity_id) => {
                if (! result.includes(entity_id) && ! searched.includes(entity_id))
                  return flatten(entity_id, result, searched);
                  return null;
                                            }
                          }
          return result.push(entity_id);
                     }

corrected the list names, append -> push
not sure about the None, can this simply be if (state) ?

on the longhand if statement: Ive moved that to an explicit ‘if’, but is that what you meant?

Think I’ve taken each Python line now and replaced it with a JS equivalent?

test button:

    label: >
      [[[
        function flatten(entity_id, result= [], searched= []) {
          var state = states[entity_id];
          }

          if (state) {
            var entities = entity.attributes.entity_id;
            if (entities) {
              searched.push(entity_id);
              entities.forEach(entity_id) => {
                if (! result.includes(entity_id) && ! searched.includes(entity_id))
                  return flatten(entity_id, result, searched);
                  return null;
                                            }
                          }
          return result.push(entity_id);
                     }
       var light_ids = [];
       var entitities = flatten(entity, 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;

        ]]]

does work somewhat, still doesnt take out the already added lights/groups, and has this error:

ButtonCardJSTemplateError: SyntaxError: Malformed arrow function parameter list in 'function flatten(entity_id, result= [], searched= []) {
    var state = states[entity_id];
    }

...'
    at new Function (<anonymous>)

which I am not sure what that means yet. reading up on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_formal_parameter

Yep, it can be!

You don’t need this line

or this line

Also, your {} are not set properly, this is what I meant about formatting.

function flatten(entity_id, result=[], searched=[])
{
    var state = states[entity_id];
    if (state) {
        var entities = state.attributes.entity_id;
        if (entities) {
            searched.push(entity_id);
            entities.forEach(entity_id => {
                if (!result.includes(entity_id) && !searched.includes(entity_id) {
                    flatten(entity_id, result, searched);
                }
            }
        }
    }
}

Then using it…

var light_ids = [];
flatten(entity.entity_id, light_ids);

light_ids.forEach(entity_id => {
    var state = states[entity_id].state;
    ... extra crap here...
}
      [[[
        function flatten(entity_id, result= [], searched= [])
        {
          var state = states[entity_id];

          if (state) {
            var entities = entity.attributes.entity_id;
            if (entities) {
              searched.push(entity_id);
              entities.forEach(entity_id) => {
                if (!result.includes(entity_id) && !searched.includes(entity_id)) {
                  return flatten(entity_id, result, searched);
                         }
                      }
                  }
              }
          }
       var light_ids = [];
       flatten(entity.entity_id, light_ids);

       var i;
       var count = 0;

      light_ids.forEach(entity_id => {
          var state = states[entity_id].state;

         for (i = 0; i < light_ids.length; i++) {
           var state = states[entities[i]].state;
           if (state == 'on') {count += 1;}
           }
       }
       if (count == light_ids.length) return 'All ' + light_ids.length + ' lights on';
       if (count == 0) return 'No lights on';
       return 'Lights on: ' + count + ' of ' + light_ids.length;

        ]]]

still returning the same error about

arrow function parameter list in 'function flatten(entity_id, result= [], searched= [])
  {
    var state = states[entity_id];

    ...'

think the connection between the function and the other ‘crap’ in the code isnt as it should be…

what editor do you use? Your spacing is all whacky and it makes it hard to read.

Aslo, it seems you aren’t reading the code, you’re just pasting previous stuff where ... other crap... went

BBEdit

might the forum software, looking ok here (though it probably in yaml mode not JS?

nope, not true, please don’t think so bad of me all the time and have some faith.
It’s not that this is my livelyhood is it.
Given the fact this hasn’t been posted before on the forum at all, I’d like to focus on getting the result, not on being a bad student. Why would I?

so, back to progress:

       var light_ids = [];
       flatten(entity.entity_id, light_ids);

       var count = 0;

       light_ids.forEach(entity_id => {
          var state = states[entity_id].state;
          if (state == 'on') {count += 1;}
           }

       if (count == light_ids.length) return 'All ' + light_ids.length + ' lights on';
       if (count == 0) return 'No lights on';
       return 'Lights on: ' + count + ' of ' + light_ids.length;

same result…seems the flatten function doesnt de-duplicate yet, given the fact light_ids.length is still counting all (double listed) groups. The error must be in the function above, the Malformed arrow function parameter list.

wait, this is so stupid, I had another counting button there, showing as expected. But hiding the fact this new test button wasn’t showing at all… sorry for that

so, we’re stuck at the error (which is for this button) and preventing anything else from happening…

I missed an else statement in flatten, but you fixed using flatten.

function flatten(entity_id, result=[], searched=[])
{
    var state = states[entity_id];
    if (state) {
        var entities = state.attributes.entity_id;
        if (entities) {
            searched.push(entity_id);
            entities.forEach(entity_id => {
                if (!result.includes(entity_id) && !searched.includes(entity_id) {
                    flatten(entity_id, result, searched);
                }
            }
        }
        else
        {
            result.push(entity_id);
        }
    }
}

And, because you like tightening things up…

function flatten(entity_id, result=[], searched=[]) {
    var state = states[entity_id];
    if (state) {
        var entities = state.attributes.entity_id;
        if (entities) {
            searched.push(entity_id);
            entities.forEach(entity_id => {
                if (!result.includes(entity_id) && !searched.includes(entity_id)
                    flatten(entity_id, result, searched);
            }
        }
        else
            result.push(entity_id);
    }
}

back to my previous comment

Your brackets should line up and they don’t, also your lines don’t match up throughout.