I have a trigger template which has been nicely keeping a log of browser_users for several months but has recently started going wrong when HA restarts. After updating HA this morning I discovered that the attribute which normally stores the database as a dict has changed to a string. Can anyone help me understand why, and either how to prevent it or spot the error and convert the string back to a dict?
The attribute normally looks like this:
database:
rnli0020:
id: RNLI0020
user: RNLI
prev: "2025-04-16T02:04:03"
when: "2025-04-19T11:21:32"
for: 292649
vizes: 1
status: uv +292649
width: "980"
old: "prev:2025-04-16T01:03:46 for:0 vizes:1 status:vu "
gig9850davidc:
id: Gig9850DavidC
user: gig David C app
prev: "2025-04-19T11:28:22"
when: "2025-04-19T11:29:00"
for: 53
vizes: 2
status: "vh "
width: "412"
old: prev:2025-04-19T11:27:29 for:53 vizes:1 status:hv +53
But after HA restart, looks like this:
database: >-
{'rnlia762': {'id': Undefined, 'user': '', 'prev': '', 'when':
'2025-04-20T07:53:41', 'for': 0, 'vizes': 0, 'status': 'u', 'width': '',
'old': ''}, 'rnli9atonyphone': {'id': 'RNLI9aTonyPhone', 'user': 'RNLI',
'prev': '2025-04-19T23:18:36', 'when': '2025-04-20T08:11:32', 'for': 99,
'vizes': 2, 'status': 'vh ', 'width': '980', 'old': 'prev:2025-04-19T23:16:57
for:99 vizes:1 status:hv +99'}, 'rnli961b': {'id': 'RNLI961b', 'user':
'RNLI', 'prev': '2025-04-18T07:41:55', 'when': '2025-04-19T17:13:20', 'for':
1458, 'vizes': 6, 'status': 'vv ', 'width': '1272', 'old':
'prev:2025-04-17T16:56:06 for:1458 vizes:5 status:vv '}
My sensor code is:
- trigger:
- platform: state
entity_id:
- sensor.browser_users_last_state_viz
not_to:
- "unavailable"
- "unknown"
- 'none'
sensor:
- name: browser_users_db_viz
unique_id: browser_users_db_viz
state: "{{ trigger.to_state.state }}"
attributes:
database: >
{#- set this = states.sensor.browser_users_db_viz #}
{#- set trig = states('sensor.browser_users_last_state_viz') #} {# uncomment / swap for use in Developer Tools #}
{%- set trig = trigger.to_state.state %}
{%- set id = trig.split(' /')[-2] if ' /' in trig else trig %}
{%- set ids = 'sensor.' ~ id | slugify ~ '_browser_user' %}
{%- set idw = 'sensor.' ~ id | slugify ~ '_browser_width' %}
{%- set db = dict(this.attributes.get('database', {}).items() | selectattr('0', 'eq', id)) if this.attributes.database.items is defined else {} %}
{%- set others = dict(this.attributes.get('database', {}).items() | rejectattr('0', 'eq', id)) if this.attributes.database.items is defined else {} %}
{%- set ids, who, old, name = 'sensor.' ~ id | slugify ~ '_browser_user', '', '', '' %}
{%- set when = trig.split(';')[1] if ';' in trig else as_timestamp(now())|timestamp_custom('%Y-%m-%dT%H:%M:%S') %}
{%- set status = trig.split(' /')[1] if ' /' in trig else 'n' %}
{%- set status = status.split(';')[-2] if ';' in status else status %}
{%- set triggerviz, previz, vizes = status, 'n', 0 %}
{%- if states(ids) not in ('unavailable', 'none', 'unknown') %}
{%- set who = states(ids) %}
{%- if states[ids].last_changed not in ( 'none', 'unknown', 'unavailable') %}
{%- set when = as_timestamp(states[ids].last_changed)|timestamp_custom('%Y-%m-%dT%H:%M:%S') %}
{%- endif %}
{%- endif %}
{%- set width = states[idw].state if states[idw].state not in ( 'none', 'unknown', 'unavailable') else '' %}
{%- set name = states[ids].name.split(' Browser')[-2] %}
{%- set prev, dur = '', 0 %}
{%- set usr = who %}
{%- if db[id] != undefined %}
{%- set prev = db[id].when if db[id].when != undefined else '' %}
{%- set dur = db[id].for | int(0) if db[id].for != undefined else 0 %}
{%- set usr = db[id].user if db[id].user not in (undefined, null, '') else usr %}
{%- set width = db[id].width if db[id].width not in (undefined, null, '') else width %}
{%- set previz = db[id].status[0] if db[id].status != undefined else previz %}
{%- set vizes = db[id].vizes|int if db[id].vizes != undefined else vizes %}
{%- set status = status~previz~' ' %}
{%- set old = 'prev:' ~ db[id].prev ~ ' for:' ~ db[id].for ~ ' vizes:' ~ db[id].vizes ~ ' status:'~ db[id].status %}
{%- endif %}
{%- set inc = (as_timestamp(when, as_timestamp(now())) - as_timestamp(prev, 0)) | round(0) %}
{%- if previz == 'v' and triggerviz in ('u','h') %}
{%- set dur = max(dur + inc, 0) %}
{%- set status = status~' +'~inc %}
{%- endif %}
{%- set vizes = vizes + 1 if triggerviz == 'v' else vizes %}
{%- set new = {id: {'id': name, 'user': usr, 'prev': prev, 'when': when, 'for': dur, 'vizes': vizes, 'status': status, 'width': width, 'old':old }} %}
{%- if previz=='n' or ('v' in (previz, triggerviz) and inc >= 1) %}
{#- new record or was/is visible AND time has elapsed #}
{{ dict(new, **others) }}
{%- else %}
{#- moving between hidden & unavailable OR <1 sec between triggers, leave the database unchanged #}
{{ dict(db, **others) }}
{%- endif %}
And the sensor used to trigger it:
- sensor:
- name: browser_users_last_state_viz
unique_id: browser_users_last_state_viz
state: >
{% set lastone = states.sensor | selectattr('entity_id', 'search', '_browser_visibility$')| rejectattr('entity_id','contains','dji') | rejectattr('entity_id','contains','station') | sort(attribute='last_changed') | last | default %} {# #}
{% set trig = lastone.entity_id.split('_browser')[-2].split('sensor.')[1] ~' /'~ lastone.state[0] ~';'~lastone.last_changed.isoformat()[0:19] if lastone and lastone.entity_id is not undefined else 'none' %}
{{ trig }}