Same automations, different behavior, last_triggered not set at startup

what i can’t understand is why this is happening in the first place.
If I check more info on the automation, I can clearly see the history:

16

it really shouldn’t be empty or non existing?

same for the last item in the script, the appliances:

52

if this one is not set, the full script wont run, and the final summary isnt displayed, until there’s at least 1 on of each of the checked entities.

btw ive taken out some other oddities i hadn’t spotted before: i had all the empty declarations with , instead of ‘’. Now ive only left the empty lists like that…

lights_message = ''
sensor_lights_message = ''
sensor_lights_desc = ''
lights_desc = ''
lights_on = []
lights_picture = ''
lights_uom = ''

No change in running the script (why doesnt this error out…) but seemed more correct, no?

Yes, but all that information you are looking at is in the SQL database, its not always in home assistant. And that information does not persist through shutdown/startup.

couldn’t it, if I leave the intitial: on out of the automations? believe thats the thing to set to let it persist or not

let me ask you the other way around (instead of trying to correct my script)

If you were to show a list of entities with the respective time of state change, what would be you’re technique to do so. Ive have come a long way in adjusting another script to where i am right now, starting from a clean slate might be leaner and meaner?

Yes, for automations, not lights.

history statistics component:

Maybe sometime in the future I can try to replicate this in my setup. I do not have a summery page, and it wouldn’t hurt to add it.

cool, if you need inspirational questions, just let me know :+1:thumb

btw just figured out that below adaptation is much simpler, and though I would have thought it to be the same, it really works better, and lets the script run, even when entities on = 0. Last_triggered or last_updated both work, but since i’m reading the individual entities, I believed last_updated to be more adequate. Lo and behold, why didn’t i think of this before…

state = hass.states.get('automation.sense_appliances_change')

if state:
    for entity_id in hass.states.get(appliances_group).attributes['entity_id']:
        dt = state.last_updated + datetime.timedelta(hours=2)
#        dt = state.attributes.get('last_triggered') + datetime.timedelta(hours= 2)
        time = '%02d:%02d' % (dt.hour, dt.minute)

tbh I found this out by experimenting with you adaptation of my earlier anything_on.py which I now use in the summary script.

I was looking for a way to display the time next to the number of entities in this card:

14

lights on 1 09.00
switches on 2 09.01
appliances on 1 08.45
total on 4 09.01

changed code isn’t correct yet…added the 4th argument time, dont get any errors, it just doesn’t display the time in the card:

def CountMyEntities(hass, entity_list, entity_state, time):
    cnt = 0
    for entity_id in entity_list:
        state = hass.states.get(entity_id)
        dt = state.last_updated 
        time = '%02d:%02d' % (dt.hour, dt.minute)
        if state.state == entity_state:
            cnt = cnt + 1
    return cnt, time

filteredLightEntities = [ entity_id for entity_id in lightEntities if ' ' not in entity_id ]


switchesOn = CountMyEntities(hass, switchEntities, 'on', time)
appliancesOn = CountMyEntities(hass, applianceEntities, 'on', time)
lightsOn = CountMyEntities(hass, filteredLightEntities, 'on', time)

totalOn = switchesOn + lightsOn + appliancesOn
whichIcon = "mdi:lightbulb-on-outline" if totalOn else "mdi:lightbulb-outline" # will evaluate "off" if totalOn == 0.
status = 'on' if totalOn else 'off' # will evaluate "off" if totalOn == 0.

hass.states.set('input_boolean.whats_on', status, { 
    'friendly_name': "What's On?",
    'icon': whichIcon,
    'lights_on': lightsOn,
    'switches_on': switchesOn,
    'appliances_on': appliancesOn,
    'total_on': totalOn,
    'extra_data_template':'{total_on} on'
})

47

finally (for now…)

how can i have the script read a group, instead of a list of entities. Something like this:

switchEntities = ['group.iungo_switch_switches_template']
lightEntities =  ['group.all_lights_only']
applianceEntities = ['group.iungo_switch_appliances_template'] 
filteredLightEntities = [ entity_id for entity_id in lightEntities if ' ' not in entity_id ]

def CountMyEntities(hass, entity_list, entity_state):
    cnt = 0
#    for entity_id in entity_list:
    for entity_id in hass.states.get(entity_list).attributes['entity_id']
        state = hass.states.get(entity_id)
        if state.state == entity_state:
            cnt = cnt + 1
    return cnt

groups contain their entity list inside the entities or entity id attribute. I forget which one, but its simple. Just call the group instead of the entity list, and get whatever attribute references the groups list

exactly, thats what i believed to be doing…not correct I am afraid.

I have done you proud and rebuilt the full summary to use just the one method for all groups.
Miracoulsy doing its job as I write this… It was there all the time, but forgot to look it in the eyes …
Glad you kept pushing !

So only this Whats_on.py group-instead-of-the-full-list needs to be solved … is it a formatting issue? or am I using the incorrect syntax.

does hass.states.get(entity_list).attributes['entity_id'] entity_list here call the group at all?

Your putting your group entities into a list, so you need to iterate that list and then iterate the entity list:

for group_entity in entity_list:
    for entity_id in hass.states.get(group_entity).attributes['entity_id']:

of course, and super handy for future expansion.
For now, ive taken them out of a list structure, and call only the one group:

for entity_id in hass.states.get(entity_list).attributes['entity_id']:
    state = hass.states.get(entity_id)

new i was close…

funny thing is, because of experimenting with these scripts and structures, the differences between the last_updated, last_triggered and last_changed start to become clearer…

a lot of confusion was created because of calling entities, , groups, or automations. They all have their own use in feedback to the user, so I might just tweak some further here an there.

Summary, made completely on 1 method

Former separate entity processing, albeit copied as you’ve seen it
35

really cool.
Thanks!!
Marius

please see a related recursive script in which I adapted the mean sensor (from reading all explicitly named sensors, to reading the group with sensors. The script returns the correct mean and number of tempsCounted, but the sensorsTotal = len(tempEntities) is incorrect. It amounts to 26 (the count of characters of the group-name :wink: ) while it should count all sensors in the group…:

tempEntities = 'group.philips_temp_sensors'
#tempEntities = ["sensor.auditorium_motion_sensor_temperature","sensor.corridor_motion_sensor_temperature",
#                "sensor.dining_table_motion_sensor_temperature", "sensor.master_bedroom_motion_sensor_temperature",
#                "sensor.dorm_motion_sensor_temperature","sensor.frontdoor_motion_sensor_temperature"]
sensorsTotal = len(tempEntities)
tempsCounted = 0
tempsTotal = 0

# look up each entity_id
#for entity_id in tempEntities:
for entity_id in hass.states.get(tempEntities).attributes['entity_id']:
  # copy it's state
  state = hass.states.get(entity_id)

  # If not None, add up and increase counter
  if state.state is not None:
     if state.state is not 'unknown':
       tempsCounted = tempsCounted + 1

# Get average
averageTemp = "{0:.2f}".format(tempsTotal / tempsCounted)

51

-----edit
never mind, that was too easy…solved of course. sensorsTotal = sensorsTotal + 1 …sorry

@petro

i have another question: my badges get themed according to the number of active entities counted. I have max 9 themes.

    if (theme.find('|') > 0):
        list = theme.split('|')
        if (len(list) in [1, groups_count[idx]]):
            theme = list[len(list)-1]
        else:
            theme = list[groups_count[idx]]

How can i prevent Error executing script: list index out of range when i have more than 8 lamps on? something along the lines of if (theme.find('|') > 8): theme = 9th theme

Themes are like this:

on ='black_badge|red_badge|yellow_badge|grey_badge|blue_badge|orange_badge|brown_badge|purple_badge|green_badge'

off ='green_badge|purple_badge|brown_badge|orange_badge|blue_badge|grey_badge|yellow_badge|red_badge|black_badge'

I’m not sure I follow you. What is groups_count?

ill give you the full section on that, it is the number of counted entities for each type of monitored items.
It is important, because the max number of entities in a group determines whether the scripts runs or not. In this case: if more lights are on than i have themes, the script errors. that’s why id need aan extra rule for that situation.

##########################################################################################
# Group count
# Groups config:
# Entities summary by group name 
# groups = need to have same max member_count
# groups_format = # Message prefix
# groups_filter = # Filter to list
# groups_badge = # Badge 'belt' (unit_of_measurement)
# groups_badge_pic = # none, one, or a list of pictures (picture position = match the count)
# groups_min_show = # Mininum count to show
# groups_theme =  # Theme template
# groups_desc = # Can set the default description, for use in case count = 0
# themes build up or down according to counted value
##########################################################################################
group_count = 0
group_desc = ''
summary = ''
idx = 0
dt_prevstate = None

on ='black_badge|red_badge|yellow_badge|grey_badge|blue_badge|orange_badge|brown_badge|purple_badge|green_badge'

off ='green_badge|purple_badge|brown_badge|orange_badge|blue_badge|grey_badge|yellow_badge|red_badge|black_badge'

groups = ['group.family',
          'group.hubs_binary_pinged',
          'group.critical_devices_state',
          'group.media_player_media',
          'group.device_tracker_media',
          'group.all_lights_only',
          'group.iungo_switch_switches_template',
          'group.iungo_switch_appliances_template',
          'group.binary_sensors_active_template']

groups_format = ['+- Home: {} -- {}',
                 '!|-> Offline: {} -- {}',
                 '!|-> Status - Critical device offline: {} -- {}',
                 '#- Playing: {} -- {}',
                 '#- Tracked: {} -- {}',
                 '=- Lights on: {} -- {}',
                 '#- Switches on: {} -- {}',
                 '#- Appliances on: {} -- {}',
                 '+- Active: {} -- {}']

groups_filter = ['home',
                 'off|idle|not_home', 
                 'off|idle|unknown|not_home',
                 'home|playing|on',
                 'home|playing|on',
                 'on',
                 'on',
                 'on',
                 'home|playing|on']

groups_badge = ['Home','Hubs','Status','Play','Track','Lamps','Switch','Appli','Active']

groups_badge_pic = ['','ok|bug|critical','ok|bug|critical','','','','','','']

groups_min_show = [0,0,0,0,0,0,0,0,0]

groups_theme = [on,off,off,on,on,on,on,on,on]

groups_desc = ['!|-> Nobody home since ',
               '+- Hubs: all online',
               '+- Status: all online',
               '%|-> Nothing playing',
               '%|-> Nothing tracked',
               '!|-> No lights on since ',
               '!|-> No switches on since ',
               '!|-> No appliances on since ',
               '%|-> No Appliances active since ']

groups_count = [0, 0, 0, 0, 0, 0, 0, 0, 0]

for group in groups:
    group_count = 0
    group_desc = ''

    for entity_id in hass.states.get(group).attributes['entity_id']:
        state = hass.states.get(entity_id)
        filter = groups_filter[idx]

        if (state.state in filter.split('|') or debug):
            dt = state.last_updated + datetime.timedelta(hours= 2)
            time = '%02d:%02d' % (dt.hour, dt.minute)
            ch = state.last_changed + datetime.timedelta(hours= 2)
            ch_time = '%02d:%02d' % (dt.hour, dt.minute)
          # If state changed in the past days show the date too
            if dt.date() < datetime.datetime.now().date():
                time = '{} {}'.format('%02d/%02d' % (dt.day, dt.month), time)

            group_count = group_count + 1
            group_desc = '{} {} ({}), '.format( group_desc, state.name, time)

        else:
            if (dt_prevstate is None):
                dt_prevstate = state.last_changed
            else:
                if (not state.last_changed is None):
                    if (state.last_changed > dt_prevstate):
                        dt_prevstate = state.last_changed

    # Final format for this group
    if (group_count >= groups_min_show[idx]):
        if (group_count == 0):
            group_desc = groups_desc[idx]
           # If there is none 'On/Home' state in group, show since
            if (group_desc.find(' since ') > 0): #if (group_desc != ''):
                dt = dt_prevstate + datetime.timedelta(hours= 2)
                group_desc = '{} {}'.format(group_desc, '%02d:%02d' % (dt.hour, dt.minute))

        else:
            group_desc = groups_format[idx].format(group_count, group_desc[:-2])

        groups_desc[idx] = group_desc
        groups_count[idx] = group_count

    idx = idx + 1

##########################################################################################
# Badges updates
##########################################################################################

idx = 0

if show_badges:
    for badge in groups_badge:
        if (badge != ''):
            entity_id = 'sensor.{}_badge'.format(badge.replace(' ', '').lower());
            hidden = False if (groups_count[idx] >= groups_min_show[idx] or debug) else True
            fname = groups_desc[idx] if debug else ' '
            picture = groups_badge_pic[idx].replace(' ', '').lower()
            theme = groups_theme[idx].replace('value', \
                    'entities["{}"].state'.format(entity_id)) \
                    if (groups_theme[idx] != '') else 'default'

           # Check for theme X index/count
            if (theme.find('|') > 0):
                list = theme.split('|')
                if (len(list) in [1, groups_count[idx]]):
                    theme = list[len(list)-1]
                else:
                    theme = list[groups_count[idx]]

            # Check for picture X index/count
            if (picture != ''):
                list = picture.split('|')
                if (len(list) in [1, groups_count[idx]]):
                    picture = list[len(list)-1]
                else:
                    picture = list[groups_count[idx]]

                if (picture != ''):
                    picture = '/local/badges/{}.png'.format(picture)

            hass.states.set(entity_id, groups_count[idx], {
              'friendly_name': fname,
              'unit_of_measurement': badge, 
              'entity_picture': picture,
#              'hidden': hidden,
              'templates': { 'theme': theme }
            })

        idx = idx + 1

if (theme.find(’|’) > 0):

find() will return the first index position in the string. Why are you doing this? It appears that you don’t care where the position is, just the number of them? find does not give you the number of them, it gives you the position of the first one from the right:

https://www.tutorialspoint.com/python/string_find.htm


list = theme.split('|')

This not good syntax. list is a built-in of python, overriding it is bad. use mylist instead.


if (len(list) in [1, groups_count[idx]]):

I don’t know what you are trying to do here. [1, groups_count[idx]] will only return a 2 item list. The only way this will pass is if the length of list is 1 or the length of list is the same size as groups_count[idx]. If this is what you want, then its fine.


theme = list[len(list)-1]

are you trying to grab the last item in the list? If thats the case, just use:

theme = list[-1]

theme = list[groups_count[idx]]

you don’t verify that this is a valid index, probably why you are getting an error randomly.


I have no idea why you are spending time counting things. Also, you have many many lists that you are using this idx for. This may not be needed if you use the zip() function:

for grp, fmat, filter, bdg, pic, min_show, thm, desc in zip(groups, groups_format, groups_filter, groups_badge, groups_badge_pic, groups_min_show, groups_theme, groups_desc):

if you insist on counting things, you can use the enumerate function so you don’t have to declare idx:

for idx, group in enumerate(groups):

well thank you! need some time to consume this.
quite some improvements to be made…
before that is reached could you help me taking care of the theme issue? that would give me some extra opportunity and instruction how to start making this better…

on the themes:
as you can see (i hope…) there are items that when counted are fine (lights, switches etc) and times that when counted are issue (hubs offline, critical devices )

originally i would have wanted these t read the themes from left to right (up) or from right to left (down).
that was too much to get right, so settled with the addition of groups_theme [on or off].

If you would now how to implement this in a better way, id be very keen to learn.

would have hoped it to be rather easy in python, read themes from left to right, or right to left would do the trick… taking max number into account makes it rather more difficult though.

Thanks!
Marius

I don’t know why you even have this line. It won’t do anything? You never add anything to badges or themes, they should always be populated, so the second half will never be populated with ‘default’. Also, the themes never have the word ‘value’ in them, so the replace does nothing.

    themelist = group_theme[idx].split('|')
    if len(themelist) > 1:
        try:
            theme = themelist[groups_count[idx]]
        except IndexError:
            theme = 'black_badge' # Oops, error badge.1
    else:
        theme = 'black_badge' # this will be your default theme

Again, I have no clue this will work because I do not know what you are attempting to do.

@mviezzer created the better part of the python, and he made this bit since I suggested theming the badges depending on the number count of entities counted.

Ive taken the line out completely which then results in badges not getting themed anymore, so the line is necessary. I can see the probable overkill of the last If, so commented that out and all seems well indeed.

what the script should do is: count the number of entities, and set that number count th theme.

for the on_theme: 0 lights on → 1 (or 0…) 2 lights on → theme 2, etc etc
for the off_theme: 0 critical devices offline → theme 0, 2 critical devices offline → theme 2

ive made the adjustment you suggested above, including taking out the full line theme = groups_theme etc and replaced that section with your code.

working just fine! thank you very much indeed. Will set another badge for error_badge, and default, but the operation are fine. In fact, im am wondering why I would need a default_badge at all. theme is always set according to number-count, or set with the Error_badge?

Well, if you get 1 result, that means the split() didn’t work because there was nothing to split. Chances are that this result will not be a normal badge. So in that case, make it a normal badge.