Add time last changed to Python script

before changing, does this also the the local time-zone? in the current setup i need to specify +1 explicitly.

Might i ask you @petro to have a look at Python sensor creation oddities for several related python question i have? thanks!

you may have to add that in. I believe it does timing based on your OS. So if your OS is in the wrong time zone, then it will be in the wrong time zone. Either way. simply adding dt = dt + datetime.timedelta(hours=+1) back into it will not impact the functionality. datetime objects can be offset with time deltas. Just perform the strfttime after the offset.

I can take a look at your other questions in the morning.

1 Like

could you please have a look at this for me too:

# Get params
event = data.get('event')
# logger.error("LAST CMD: " + str(event))
# Sample: <Event call_service[L]: service_data=, service_call_id=78356624-86, service=mp_playpause, domain=script>

# Find the script name.
pos_start = event.find('service=')+8
pos_end = event.find(',', pos_start)

# Get the state object (script) from the name
entity_id = 'script.' + event[pos_start:pos_end]
state = hass.states.get(entity_id)
dt = datetime.datetime.now() #state.attributes.get('last_triggered') #
time = "%02d:%02d" % (dt.hour, dt.minute)
msg = []

try:
    msg = state.name
except:
    msg = ''

# Ignore some names
# msg = state.name
if (msg == 'None') or (msg.startswith('Set ')):
    msg = ''

if (msg != '') :
    # Sensor update
    hass.states.set('sensor.last_command', '{}'.format( msg), {
#        'custom_ui_state_card': 'state-card-value_only',
#        'text': sensor_message,
        'unit_of_measurement': 'Cmd',
        'friendly_name': time,
        'entity_picture': '/local/buttons/command.png'
    })

ist a script that shows me the last command processed. It throws many errors like below, when the state hasn’t been set yet.

2018-03-19 09:44:21 ERROR (SyncWorker_15) [homeassistant.components.python_script.last_cmd.py] Error executing script: 'NoneType' object is not callable
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "last_cmd.py", line 7, in <module>

we’ve been trying to prevent that by the try: and except: statement, but it doesnt work as desired.

i have now added line 15 msg = [], to see if that makes things work better, and indeed i do seem to get less errors. Still, it must be a silly workaround for a deeper issue.

The None-type error is one I get in my bigger Summary script very often too, so if you could help me out here, in this smaller one, i might be able to solve the other issue myself (…)

Error executing script: unsupported operand type(s) for +: 'NoneType' and 'datetime.timedelta'
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "summary.py", line 299, in <module>
TypeError: unsupported operand type(s) for +: 'NoneType' and 'datetime.timedelta'

line 297-300 being:

for entity_id in hass.states.get(lights_group).attributes['entity_id']:
    dt = hass.states.get('automation.sense_lights_change').attributes.get('last_triggered')
    dt = dt + datetime.timedelta(hours=+1)
    time = '%02d:%02d' % (dt.hour, dt.minute)

apparently i cant add the dt (called NoneType) and the datetime.timedelta(hours=+1)?

Please have a look?

Marius

forgot about this, sorry. Please elaborate a but more on this, what do you want me to use in the script exactly?

also, changing
time = "%02d:%02d" % (dt.hour+1, dt.minute)

into:

time = dt.strftime("%H:%M")

throws this error:

Error executing script: '__import__'
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/components/python_script.py", line 166, in execute
    exec(compiled.code, restricted_globals, local)
  File "summary.py", line 240, in <module>
KeyError: '__import__'

see relevant bit:

mode_desc = ''
state = hass.states.get('input_select.mode')

# Get info
#dt = datetime.datetime.now()
#time = "%02d:%02d" % (dt.hour, dt.minute)

if state:
    hidden = False #if (state.state != 'Normal') else True
    if state.state is not 'Unknown':
        dt = hass.states.get('automation.mode_selection').attributes.get('last_triggered')
        if dt:
            time = dt.strftime("%H:%M")
#            time = "%02d:%02d" % (dt.hour+1, dt.minute)

        hass.states.set('sensor.mode_badge',state.state, {
         'entity_picture': '/local/modes/{''}.png'.format(state.state.replace(' ','').lower()),
         'friendly_name': time,
         'unit_of_measurement': 'Mode',
         'hidden': hidden,
         'order': order
          })
    if state.state is not 'Hidden':
        mode_desc = '{}*- {} mode selected at: {}\n'.format(summary, state.state, time)

what’s summery.py?

summary.py is my bigger script giving me a summary of tracked things in my house, heavily based on @mviezzer’s home-assistant-config/python_scripts/summary.py at master · maattdiy/home-assistant-config · GitHub but with several follow-through developments :wink: this is a thread about that: Summary card and badges for people, devices and status (with python script and custom card)

The error is occuring in summery.py, not the script in this thread. it’s unable to execute script named import. whats at line 240?

the change you suggested:

time = dt.strftime("%H:%M")

it then complains about the import. maybe related to the other import issue we had changing the alarmclock bit (also in this summary.py)

show me your total summery.py please

i would but its quite big. Isn’t that against forum courtesy somehow ?

Do you use github or bitbucket? or there is some quick text drop site i’ve seen people use https://hastebin.com

here we go, no secrets in there after all…

##########################################################################################
## Resources:
## https://github.com/maattdiy/home-assistant-config @Mviezzer
## https://home-assistant.io/components/python_script/
## https://home-assistant.io/docs/configuration/state_object/
##########################################################################################
debug = False
show_badges = True
show_card = True

group_count = 0
group_desc = ''
summary = ''
idx = 0
dt_prevstate = None

if debug:
    event = data.get('event')
    logger.error("\n\nSUMMARY: " + str(event))

##########################################################################################
## Group count
## Groups config:
##  Entities summary by group name 
# groups = need to have same 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
##########################################################################################

on_theme ='red_badge|yellow_badge|grey_badge|blue_badge|orange_badge|brown_badge|black_badge|green_badge'

off_theme ='green_badge|black_badge|brown_badge|orange_badge|blue_badge|yellow_badge|grey_badge|red_badge'

groups = ['group.family','group.binary_sensors_active_template',
          'group.hubs_binary_pinged','group.critical_devices_state',
          'group.media_player_media','group.device_tracker_media']
groups_format = ['+- Home: {} -- {}','#- Active: {} -- {}','!|-> Offline: {} -- {}',
                 '!|-> Status - Critical device offline: {} -- {}','#- Playing: {} -- {}','#- Tracked: {} -- {}']
groups_filter = ['home', 'home|playing|on', 'off|idle|not_home', 
                    'off|idle|unknown|not_home', 'home|playing|on', 'home|playing|on']
groups_badge = ['Home', 'Active', 'Hubs', 'Status', 'Play', 'Track']
groups_badge_pic = ['', '', 'ok|bug|critical', 'ok|bug|critical', '', '']
groups_min_show = [0, 0, 0, 0, 0, 0]
groups_theme = [on_theme, on_theme, off_theme, off_theme, on_theme, on_theme]
groups_desc = ['!|-> Nobody home since ', '%|-> No Appliances active', '+- Hubs: all online',
                    '+- Status: all online', '%|-> Nothing playing', '%|-> Nothing tracked']
groups_count = [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_changed
            dt = dt + datetime.timedelta(hours=+1)
            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=+1)
                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
order = 2

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,
#              'state_card_mode': 'badges',
              'hidden': hidden,
              'templates': { 'theme': theme },
              'order': order
            })

        order = order + 1
        idx = idx + 1

##########################################################################################
## Alarm clock
## https://github.com/maattdiy/home-assistant-config/blob/master/config/packages/alarmclock.yaml
##########################################################################################

#alarms_prefix = ['alarmclock_wd', 'alarmclock_we']
#alarms_wfilter = ['1|2|3|4|5', '6|7']
#alarms_desc = ''
#idx = 0
#alarms_uom = []
#alarms_picture = []
#alarms_fn = []

#for entity_id in alarms_prefix:
#    state = hass.states.get('input_boolean.{}_enabled'.format(entity_id))
#    if state:
#        if state.state is 'on':
#            # Show the alarm for the next day
#            if (str(datetime.datetime.now().isoweekday()) in alarms_wfilter[idx].split('|')):
#                state = hass.states.get('sensor.{}_time_template'.format(entity_id))
#                alarms_desc = '{}{}, '.format(alarms_desc, state.state)
#    idx = idx + 1

#if (alarms_desc == ''):
#    alarms_fn = 'Relax'
#    alarms_desc = '%- Alarm clock is not set, relax'
#    alarms_uom = 'Off'
#    alarms_picture = '/local/badges/alarm_off.png'
#else:
#    alarms_fn = alarms_desc[:-2]
#    alarms_desc = '/- Alarm clock set at ' + alarms_desc[:-2]
#    alarms_uom = 'On'
#    alarms_picture = '/local/badges/alarm.png'

#hass.states.set('sensor.alarms_badge',state.state, {
#         'entity_picture': alarms_picture,
#         'friendly_name': alarms_fn,
#         'unit_of_measurement': alarms_uom,
#         'hidden': hidden,
#         'order': order
#          })


#from datetime import datetime

alarms_prefix = ['alarmclock_wd', 'alarmclock_we']
alarms_wfilter = [range(1,6), range(6,8)]
alarms_desc = ''
mydict = {'hidden':hidden, 'order':order}

for entity_id, filter in zip(alarms_prefix, alarms_wfilter):
    state = hass.states.get('input_boolean.{}_enabled'.format(entity_id))
    if state:
        if state.state is 'on' and datetime.datetime.now().isoweekday() in filter:
            state = hass.states.get('sensor.{}_time_template'.format(entity_id))
            alarms_desc = '{}{}, '.format(alarms_desc, state.state)

if (alarms_desc == ''):
    mydict['entity_picture'] = '/local/badges/alarm_off.png'
    mydict['friendly_name'] =  'Relax'
    mydict['unit_of_measurement'] = 'Off'
    mydict['theme'] = 'grey_badge'
    alarms_desc = '%- Alarm clock is not set, relax'
else:
    mydict['entity_picture'] = '/local/badges/alarm.png'
    mydict['friendly_name'] = alarms_desc[:-2]
    mydict['unit_of_measurement'] = 'On'
    mydict['theme'] = 'orange_badge'
    alarms_desc = '/- Alarm clock set at ' + alarms_desc[:-2]

hass.states.set('sensor.alarms_badge', '', mydict)
##########################################################################################
## Mode:
## General package: https://github.com/maattdiy/home-assistant-config/blob/master/config/packages/profiles.yaml
## Developer package: https://github.com/maattdiy/home-assistant-config/blob/master/config/packages/developer.yaml
## Badges images: /config/www/badges
##########################################################################################

mode_desc = ''
state = hass.states.get('input_select.mode')

# Get info
#dt = datetime.datetime.now()
#time = "%02d:%02d" % (dt.hour, dt.minute)

if state:
    hidden = False #if (state.state != 'Normal') else True
    if state.state is not 'Unknown':
        dt = hass.states.get('automation.mode_selection').attributes.get('last_triggered')
        if dt:
#            time = dt.strftime("%H:%M")
            time = "%02d:%02d" % (dt.hour+1, dt.minute)

        hass.states.set('sensor.mode_badge',state.state, {
         'entity_picture': '/local/modes/{''}.png'.format(state.state.replace(' ','').lower()),
         'friendly_name': time,
         'unit_of_measurement': 'Mode',
         'hidden': hidden,
         'order': order
          })
    if state.state is not 'Hidden':
        mode_desc = '{}*- {} mode selected at: {}\n'.format(summary, state.state, time)

##########################################################################################
## Activity:
##
##########################################################################################

activity_desc = ''
## Get activity description
state = hass.states.get('input_select.activity')
state_value = hass.states.get('input_select.activity').state
#time = '?'
# Get info
#dt = state.attributes.get('last_triggered')
#time = '%02d:%02d' % (dt.hour, dt.minute)

if not state is None:
    hidden = False #if (state.state != 'Normal') else True
    if state.state != 'Unknown':
        dt = hass.states.get('automation.activity_selection').attributes.get('last_triggered')
        if not dt is None:
            time = "%02d:%02d" % (dt.hour+1, dt.minute)
    if not hidden:
        activity_desc = '{}$- {} activity selected at: {}\n'.format(summary, state.state, time)
        
        hass.states.set('sensor.activity_badge', state_value, {
            'friendly_name': time, #state_value,
            'entity_picture': '/local/activities/{' '}.png'.format(state_value.replace(' ','').lower()),
            'unit_of_measurement': 'Act'
            })

##########################################################################################
## Lights:
##########################################################################################
lights_group = 'group.all_lights_only'
#show only lights, not light groups
excluded = ['light.custom_group_for_group','light.custom_group_for_lights_2']
lights_message = []
sensor_lights_message = []
sensor_lights_desc = []
lights_desc = []
lights_on = []
lights_picture = []
lights_uom = []

total_on = hass.states.get('input_boolean.anything_on').attributes.get('lights_on')

for entity_id in hass.states.get(lights_group).attributes['entity_id']:
    dt = hass.states.get('automation.sense_lights_change').attributes.get('last_triggered')
    dt = dt + datetime.timedelta(hours=+1)
    time = '%02d:%02d' % (dt.hour, dt.minute)

    if hass.states.get(entity_id).state is 'on' and entity_id not in excluded:
        lights_on.append(hass.states.get(entity_id).attributes["friendly_name"])
        state = hass.states.get(entity_id)

if len(lights_on) > 0:
    lights_picture = '/local/lights/hue_pl.png'
    lights_message = ', '.join(lights_on)
    sensor_lights_message = 'Lights on: ' +', '.join(lights_on)
    lights_desc = '=- Lights on: {} -- {} since {}'.format(total_on,lights_message,time)
    lights_uom = 'Lights'
    if len(lights_on) == 1:
        lights_uom = 'Light'
else:
    lights_picture = '/local/lights/bulboff.png'
    lights_message= ''
    sensor_lights_message= 'No lights on'
    lights_desc = '!|-> No lights on since {}'.format(time)
    lights_uom = 'Lights'

sensor_lights_desc = '{}'.format(sensor_lights_message)

hass.states.set('sensor.lights_badge', total_on, {
#        'custom_ui_state_card': 'state-card-value_only',
        'text': sensor_lights_message,
        'unit_of_measurement': lights_uom,
        'friendly_name': time,
        'entity_picture': lights_picture
     })
##########################################################################################
## Switches:
##########################################################################################
switches_group = 'group.iungo_switch_switches_template'
switches_message = []
sensor_switches_message = []
sensor_switches_desc = []
switches_desc = []
switches_on = []
switches_picture = []
switches_uom = []

total_on = hass.states.get('input_boolean.anything_on').attributes.get('switches_on')

for entity_id in hass.states.get(switches_group).attributes['entity_id']:
    dt = hass.states.get('automation.sense_switches_change').attributes.get('last_triggered')
    dt = dt + datetime.timedelta(hours=+1)
    time = '%02d:%02d' % (dt.hour, dt.minute)

    if hass.states.get(entity_id).state is 'on' and entity_id not in excluded:
        switches_on.append(hass.states.get(entity_id).attributes["friendly_name"])
        state = hass.states.get(entity_id)

if len(switches_on) > 0:
    switches_picture = '/local/buttons/button_power_on.png'
    switches_message = ', '.join(switches_on)
    sensor_switches_message = 'Switches on: ' +', '.join(switches_on)
    switches_desc = '#- Switches on: {} -- {} since {}'.format(total_on,switches_message,time)
    switches_uom = 'Switches'
    if len(switches_on) == 1:
        switches_uom = 'Switch'
else:
    switches_picture = '/local/buttons/button_power_off.png'
    switches_message= ''
    sensor_switches_message= 'No switches on'
    switches_desc = '!|-> No switches on since {}'.format(time)
    switches_uom = 'Switches'

sensor_switches_desc = '{}'.format(sensor_switches_message)

hass.states.set('sensor.switches_badge', total_on, {
#        'custom_ui_state_card': 'state-card-value_only',
        'text': sensor_switches_message,
        'unit_of_measurement': switches_uom,
        'friendly_name': time,
        'entity_picture': switches_picture
     })

##########################################################################################
## Appliances:
##########################################################################################
appliances_group = 'group.iungo_switch_appliances_template'
appliances_message = []
sensor_appliances_message = []
sensor_aplliances_desc = []
appliances_desc = []
appliances_on = []
appliances_picture = []
appliances_uom = []

total_on = hass.states.get('input_boolean.anything_on').attributes.get('appliances_on')

for entity_id in hass.states.get(appliances_group).attributes['entity_id']:
    dt = hass.states.get('automation.sense_appliances_change').attributes.get('last_triggered')
    dt = dt + datetime.timedelta(hours=+1)
    time = '%02d:%02d' % (dt.hour, dt.minute)

    if hass.states.get(entity_id).state is 'on' and entity_id not in excluded:
        appliances_on.append(hass.states.get(entity_id).attributes["friendly_name"])
        state = hass.states.get(entity_id)

if len(appliances_on) > 0:
    appliances_picture = '/local/buttons/button_power_on.png'
    appliances_message = ', '.join(appliances_on)
    sensor_appliances_message = 'Appliances on: ' +', '.join(appliances_on)
    appliances_desc = '#- Appliances on: {} -- {} since {}'.format(total_on,appliances_message,time)
    appliances_uom = 'Appliances'
    if len(appliances_on) == 1:
        appliances_uom = 'Appliance'
else:
    appliances_picture = '/local/buttons/button_power_off.png'
    appliances_message= ''
    sensor_appliances_message= 'No appliances on'
    appliances_desc = '!|-> No appliances on since {}'.format(time)
    appliances_uom = 'Appliances'

sensor_appliances_desc = '{}'.format(sensor_appliances_message)

hass.states.set('sensor.appliances_badge', total_on, {
#        'custom_ui_state_card': 'state-card-value_only',
        'text': sensor_appliances_message,
        'unit_of_measurement': appliances_uom,
        'friendly_name': time,
        'entity_picture': appliances_picture
     })

##########################################################################################
## Summary update
## Custom card: /custom_ui/state-card-value_only.html
##########################################################################################

for group_desc in groups_desc:
    if group_desc != '' and not group_desc.endswith(': '):
        summary = '{}{}\n'.format(summary, group_desc)

## Add to final summary
summary = '{}\n{}\n{}\n{}\n{}\n{}\n{}'.format(summary, alarms_desc, activity_desc, mode_desc,\
                                           lights_desc, switches_desc, appliances_desc)

if show_card:
    hass.states.set('sensor.summary', '', {
        'custom_ui_state_card': 'state-card-value_only',
        'text': summary
    })

##########################################################################################
## Codes for text_colors declared in 
## Custom card: /custom_ui/state-card-value_only.html
##########################################################################################

#      case "*": return "bold";
#      case "/": return "italic";
#      case "!": return "red";
#      case "+": return "green";
#      case "=": return "yellow";
#      case "%": return "grey";
#      case "$": return "brown";
#      case "#": return "blue";
#      default:  return "normal";
dt = hass.states.get('automation.mode_selection').attributes.get('last_triggered')

this must not be a real date time object. Just use what you got:

time = "%02d:%02d" % (dt.hour+1, dt.minute)

but i need it to state the time the mode-selection was done, and to differentiate it form the other time selectors in the script. Ill show you why:

49

the script has perfect result, after initial boot. Seems to need to be populated at startup or something like that.

Yeah, and your method still works, I was only showing a different way to do it. Just leave the strftime commented and use the line you don’t have commented:

if state:
    hidden = False #if (state.state != 'Normal') else True
    if state.state is not 'Unknown':
        dt = hass.states.get('automation.mode_selection').attributes.get('last_triggered')
        if dt:
#            time = dt.strftime("%H:%M")
            time = "%02d:%02d" % (dt.hour+1, dt.minute)

ok thanks. Anythoughts why it is complaining about Imports? And, if i got that settled, would it be better of easier to program?

this is why

meaning dt is a fixed and standard defined object in Python and the ‘last_triggered’ attributes isnt the same?

could I try to set another variable, maybe choose my own, and use that?

still don’t understand why that would only cause errors at first run, and not later on though. This scripts is running all the time, and only throws these errors at boot, albeit in abundance …

Oh… that might be because the object may not have a last triggered datetime object on startup. Just perform a check to verify that it has the key inside the dictionary hass.states.get(‘automation.mode_selection’).attributes.has_key(‘last_triggered’)

great!
can i just add it to the existing script, any special place?