Custom attribute doesn't persist long enough

Hi,

I witness a weird situation - I added a custom attribute to an entity I control 100% and still it sometimes lose its value within a 1 min.

This is the setup:

The entity is MQTT device_tracker. The custom attribute (added through customize.yaml) is ‘last_update’ - datetime stamp for last MQTT message with location (having ‘secondary_info: last-changed’ is not good enough as if my tracker is in the same zone, I don’t see the last update but only how much time it was at the zone).
I send the MQTT location messages from my Android Tasker - so I control it 100% and knows exactly when messages are being send and if they were sent.
This is the entry on customize.yaml:

device_tracker.xyz_gps:
  last_update: '-'

After witnessing the ‘-’ reappear after a successful update, I wrote an AppDaemon app that does 2 things:

  1. waits for MQTT location messages, updates the attribute and also keeps the value in memory.
  2. runs a timer every 1 min that takes the value from memory and set it to the attribute again.
    Checking AD logs - all seem to work as expected.

Only that I still see sometimes the value as ‘-’ on the Lovelace -> Overview -> entity.

BTW, didn’t want to complex the explanation above but I added the same attribute also to the person entity with the same logic all over the way with same results…

Why isn’t it persistent? How can I fix it? Is there a better way than mine to solve what I want to do?

Thanks!

I’m not 100% sure I understand what you’re trying to achieve, do you want to track the last time you received an update on your sensor which receives data via MQTT?
If so why not just try to use

force_update: true

in your sensor definition?

Yes, you got me right - I want to know when was the last time I received an update (MQTT message from the tracker), even if it didn’t change the entity’s state (i.e. has exactly same lat/lon as last message).

I’m using JSON MQTT Device Tracker which doesn’t hold the force_update configuration variable.

What does the states page display? Ignore lovelace for now.

Same phenomena… :frowning:

can you share your appdeamon code?

Sure (I didn’t want to load the post for nothing, as I thought it should have worked without the AD code…)
Here it is:

import appdaemon.plugins.hass.hassapi as hass
import json
import datetime
from time import localtime, strftime

class tracker_last_update(hass.Hass):

  def initialize(self):
    self.last_update = json.loads('{"A1": "*", "A2": "*", "A3": "*", "A4": "*"}')
    self.listen_event(self.event_listener, 'HASS_MQTT')
    self.run_every(self.run_every_timer, datetime.datetime.now(), 1 * 60)

  def event_listener(self, event_name, data, *args, **kwargs):
    topic = data['topic']
    current_time = strftime('%Y-%m-%d %H:%M:%S', localtime())
    name = topic.replace('ha/location/','')
    self.log(name + ', ' + current_time, self.show_log)
    self.set_state('device_tracker.' + name + '_gps', attributes = {'last_update': current_time})
    self.set_state('person.' + name, attributes = {'last_update': current_time})       
    self.last_update[name] = current_time
    self.log(self.last_update)
        
  def run_every_timer(self, kwargs):
    names = ['A1', 'A2', 'A3', 'A4']
    for name in names:
        last_update = self.last_update[name]
        self.log(self.last_update)
        self.log('from timer')
        if last_update != '*':
            self.set_state('device_tracker.' + name + '_gps', attributes = {'last_update': last_update})
            self.set_state('person.' + name, attributes = {'last_update': last_update}) 
        self.log(name + ', ' + last_update, self.show_log)

and this is the AD log from last call (typical results where not all trackers sent their data lately):

2019-07-08 17:02:49.110962 INFO utils: {'A1': '2019-07-08 17:31:03', 'A2': '2019-07-08 17:31:08', 'A3': '2019-07-08 13:31:02', 'A4': '2019-07-08 17:01:02'}
2019-07-08 17:02:49.115086 INFO utils: from timer
2019-07-08 17:02:49.496536 INFO utils: A1, 2019-07-08 17:31:03
2019-07-08 17:02:49.499978 INFO utils: {'A1': '2019-07-08 17:31:03', 'A2': '2019-07-08 17:31:08', 'A3': '2019-07-08 13:31:02', 'A4': '2019-07-08 17:01:02'}
2019-07-08 17:02:49.502985 INFO utils: from timer
2019-07-08 17:02:49.916719 INFO utils: A2, 2019-07-08 17:31:08
2019-07-08 17:02:49.919880 INFO utils: {'A1': '2019-07-08 17:31:03', 'A2': '2019-07-08 17:31:08', 'A3': '2019-07-08 13:31:02', 'A4': '2019-07-08 17:01:02'}
2019-07-08 17:02:49.923163 INFO utils: from timer
2019-07-08 17:02:50.334671 INFO utils: A3, 2019-07-08 13:31:02
2019-07-08 17:02:50.346845 INFO utils: {'A1': '2019-07-08 17:31:03', 'A2': '2019-07-08 17:31:08', 'A3': '2019-07-08 13:31:02', 'A4': '2019-07-08 17:01:02'}
2019-07-08 17:02:50.352629 INFO utils: from timer
2019-07-08 17:02:50.760542 INFO utils: A4, 2019-07-08 17:01:02

The code looks ok, so what happens when you remove the customize section and set the state of your devices on startup in the appdaemon app?

Not sure I understand what you are asking…
The state is being controled by the JSON MQTT Device tracker entity (and it works correctly ‘by itself’). I only want to have my custom attribute keep the value I feed it.

Yes, but you are adding

device_tracker.xyz_gps:
  last_update: '-'

to customize and your value is returning to that result after you set it. So what happens when you remove that, and initialize the attribute inside your appdeamon app?

  def initialize(self):
    self.last_update = json.loads('{"A1": "*", "A2": "*", "A3": "*", "A4": "*"}')
    for name in ['A1', 'A2', 'A3', 'A4']:
        self.set_state('device_tracker.' + name + '_gps', attributes = {'last_update': '-'})
    self.listen_event(self.event_listener, 'HASS_MQTT')
    self.run_every(self.run_every_timer, datetime.datetime.now(), 1 * 60)

Also, as a side note why are you using json loads with a dictionary when you can just set the dictionary?

self.last_update = {"A1": "*", "A2": "*", "A3": "*", "A4": "*"}

Also, if you use a few minor tweaks to your script, you can easily scale this without too much editing:

import appdaemon.plugins.hass.hassapi as hass
import json
import datetime
from time import localtime, strftime

class tracker_last_update(hass.Hass):

  def initialize(self):
    self.last_update = dict.fromkeys(['A1','A2','A3','A4'])
    for name in self.last_update.keys():
        self.set_state('device_tracker.' + name + '_gps', attributes = {'last_update': '-'})
    self.listen_event(self.event_listener, 'HASS_MQTT')
    self.run_every(self.run_every_timer, datetime.datetime.now(), 1 * 60)

  def event_listener(self, event_name, data, *args, **kwargs):
    topic = data['topic']
    current_time = strftime('%Y-%m-%d %H:%M:%S', localtime())
    name = topic.replace('ha/location/','')
    self.log(name + ', ' + current_time, self.show_log)
    self.set_state('device_tracker.' + name + '_gps', attributes = {'last_update': current_time})
    self.set_state('person.' + name, attributes = {'last_update': current_time})       
    self.last_update[name] = current_time
    self.log(self.last_update)
        
  def run_every_timer(self, kwargs):
    for name, last_update in self.last_update.items():
        self.log(self.last_update)
        self.log('from timer')
        if last_update != None:
            self.set_state('device_tracker.' + name + '_gps', attributes = {'last_update': last_update})
            self.set_state('person.' + name, attributes = {'last_update': last_update}) 
        self.log(name + ', ' + last_update, self.show_log)

dict.fromkeys() creates a dictionary from a list of keys. This will allow you to only update 1 list of names and now you’ll have a dictionary with names that looks like this:

{'A1': None, 'A3': None, 'A2': None, 'A4': None}

iterating accross the dictionary is easy so you don’t need to keep makings lists that contains your names.

for key in dictionary.keys():

that will give you only the names of the keys in the dictionary, each incremented key will be stored in the variable key: ['A1','A2','A3','A4']

for value in dictionary.values():

that will give you only the values of the keys in the dictionary, each incremented value will be stored in the variable value.

for key, value in dictionary.items():

that will give you a (key, value) tuple: [('A1', None), ('A3', None), ('A2', None), ('A4', None)]

Then all you need to do to add A5 is modify

self.last_update = dict.fromkeys(['A1','A2','A3','A4'])

to

self.last_update = dict.fromkeys(['A1','A2','A3','A4','A5'])

and the rest of the script is handled. No more editing.

1 Like

ohh, I didn’t know it is possible to add custom attribute ad-hoc. Anyway, when testing, it shows the same phenomena, only that instead of showing the “-”, the whole attribute disappears and re-show for couple of seconds every 1 min.

Cheers for the code improvement and clear tutorial! My whole python experience comes from couple of AD scripts…:smiley:

6 days passed and the problem persist.
Only that one device out of the four now works as expected.
Checking all over the place, the only difference I’ve found is on the file core.restore_state -> entities person and device_tracker on state->context->user_id have a value and not null as with all other devices. That doesn’t make sense to me as to be a reason for it to work. Any ideas?

Now everything works as expected.
I noticed the new status after upgrading to latest hass.io. I don’t think there’s a relation. Maybe the couple of restarts since last please with it. Anyway, I haven’t tried another restart since :wink: