For anyone coming after me, it turns out there’s now a last_reported attribute on State objects. You can see this with the following template:
{% set dev = device_id('device_tracker.my_iphone') %}
{% set dev_states = expand(device_entities(dev)) %}
{% set last_reported = dev_states | map(attribute='last_reported') | max %}
Last report: {{ last_reported }}
Report age: {{ (now() - last_reported).total_seconds() }} ({{last_reported | relative_time }})
{% set last_reported_entities = dev_states | selectattr('last_reported', 'eq', last_reported) | map(attribute="entity_id") | join(",") %}
Last reporting entities: {{ last_reported_entities }}
which outputs something like:
Last report: 2024-12-16 21:38:43.162378+00:00
Report age: 257.063042 (4 minutes)
Last reporting entities: sensor.my_iphone_location_permission
It does seem to be the same entity reporting last each time on iPhone, but varies on Android. The overhead for checking all of them is too small to measure so I’m doing that.
I’ve set up an automation that checks every minute how long ago a device last checked in, and if it’s offline for a while, updates an input_select to show it’s Probably Away. This fits in with other automations that set the state to Away, Home, Bed, etc based on actual updates.
alias: Phone probably away detection
triggers:
- trigger: time_pattern
minutes: "*"
conditions:
- condition: not
conditions:
- condition: state
entity_id: input_select.my_phone
state: Away
- condition: state
entity_id: input_select.my_phone
state: Probably Away
- alias: Phone has been offline for 25 minutes
condition: template
value_template: |-
{% set dev = device_id('device_tracker.my_iphone') %}
{% set dev_states = expand(device_entities(dev)) %}
{% set last_reported = dev_states | map(attribute='last_reported') | max %}
{{ now() - last_reported > timedelta(minutes=25) }}
actions:
- action: input_select.select_option
data:
option: Probably Away
target:
entity_id: input_select.my_phone
You probably also need a “returned” automation to treat updates as evidence that the phone is back at the location it claims it is. The device tracker doesn’t update its reported time, so we look at all of them again:
triggers:
- trigger: time_pattern
minutes: "*"
conditions:
- condition: state
entity_id: input_select.my_phone
state: Probably Away
- condition: state
entity_id: device_tracker.my_iphone
state: Home
- alias: Phone has been online in the last 2 minutes
condition: template
value_template: |-
{% set dev = device_id('device_tracker.my_iphone') %}
{% set dev_states = expand(device_entities(dev)) %}
{% set last_reported = dev_states | map(attribute='last_reported') | max %}
{{ now() - last_reported < timedelta(minutes=2) }}
actions:
- action: input_select.select_option
data:
option: Probably Home
target:
entity_id: input_select.my_phone
If instead you want to use a template sensor, make sure you don’t associate it with the same device, or it’ll trigger an update on itself (thanks to expand).
Mark