Determine proximity direction over time rather than with subsequent location updates

Consider the following log:

The tracker for this device (a car) stops sending updates when turned off, which is okay since the device/car isn’t moving. However proximity thinks that the tracked device is moving away, partly because the final update was 2m larger than the penultimate one (the large “away from” starts at 15:24:31):

15:23:29: 6085m
15:24:31: 6087m

In this case setting the distance tolerance to >2m would probably fix things, but I don’t think that’s the correct approach generally. In my view, distances should always be as accurate as the underlying device tracker.

I suspect that proximity is only calculated on location updates, but imo that’s not the correct approach - there are other examples of when it becomes “out of sync” with reality because of this. This behaviour also occurs when a phone loses connection, say when entering a building (where you’re most likely to be stationary for most of the time). If your final update gives you a vector, then you’ll have a direction of travel until you regain reception. Not ideal and very common.

Instead it seems proximity should be time-sample based rather than update based. This would also suppress “false” directions when for example we go around a roundabout or some such as it would smooth out temporary changes in direction/stationary phases. At the moment even the most regular journeys (eg a straight “there and back” renders lots of false directions which plays havoc with automations.

Similarly, the tolerance could then be expressed in terms of this sampling frequency.

Decided to have a go at fixing this myself:

blueprint:
  name: Update Proximity if No Device Tracker Update
  description: Avoid stuck proximity direction of travel if GPS stops updating (due to reception etc).
  domain: automation
  input:
    device_tracker_source:
      name: Device Tracker Source
      selector:
        entity:
          filter:
            - domain: device_tracker
    device_tracker_destination:
      name: Device Tracker Destination
      selector:
        entity:
          filter:
            - domain: device_tracker
    direction_sensor:
      name: Associated Direction of Travel Sensor
      selector:
        entity:
          filter:
            - integration: proximity
    timeout:
      name: Timeout
      selector:
        number:
          min: 1
          max: 120
          unit_of_measurement: minutes
          mode: box

variables:
  device_tracker_source: !input device_tracker_source
  device_tracker_destination: !input device_tracker_destination
  dev_id: '{{ device_tracker_destination | regex_replace(find="device_tracker.") }}'

trigger:
  - platform: state
    entity_id: !input device_tracker_source
    for:
      minutes: !input timeout

condition:
  - condition: state
    entity_id: !input direction_sensor
    state:
      - away_from
      - towards

action:
  - service: device_tracker.see
    data:
      dev_id: '{{ dev_id }}'
      gps: >-
        {{ [state_attr(device_tracker_source, 'latitude'),
        state_attr(device_tracker_source, 'longitude')] }}
      gps_accuracy: '{{ state_attr(device_tracker_source, "gps_accuracy") }}'
      battery: '{{ range(1,99) | random | int }}'

Very hacky but seems to do what I need it to (yet to test outside of the lab). Some notes:

  1. The input device tracker has to be the one attached to the input proximity sensor. Technically we don’t need the condition really, I just didn’t like the idea of updating the location outside of the specific situation I wanted to. It can probably be omitted if you don’t mind constant updates.
  2. Since you can’t see a person nor phone acting as a device tracker (both of which suck), you’ll need to attach a proxy device tracker to a person you’ve added to proximity. I don’t know the side effects of having two such devices but I assume/hope the person will just take the latest (although the person docs suggests things aren’t as simple and I suspect this will bite me).
  3. Proximity doesn’t update if you copy the exact same data, so I needed to guarantee dirtying things by using a random battery value. Obviously this will break things that use that attribute.

Thoughts welcome.

EDIT: This trigger doesn’t seem to work as intended and fires very frequently. Will update with another solution.

For some reason I concluded that last_reported didn’t update for attributes, but turns out that it does. So this works better:

blueprint:
  name: Update Proximity if No Device Tracker Update
  description: Avoid stuck proximity direction of travel if GPS stops updating (due to reception etc).
  domain: automation
  input:
    device_tracker_source:
      name: Device Tracker Source
      selector:
        entity:
          filter:
            - domain: device_tracker
    device_tracker_destination:
      name: Device Tracker Destination
      selector:
        entity:
          filter:
            - domain: device_tracker
    direction_sensor:
      name: Associated Direction of Travel Sensor
      selector:
        entity:
          filter:
            - integration: proximity
    timeout:
      name: Timeout
      selector:
        number:
          min: 1
          max: 120
          unit_of_measurement: minutes
          mode: box

trigger_variables:
  device_tracker_source: !input device_tracker_source
  timeout: !input timeout
variables:
  device_tracker_destination: !input device_tracker_destination
  dev_id: '{{ device_tracker_destination | regex_replace(find="device_tracker.") }}'

trigger:
  - platform: template
    value_template: >
            {{ now() - states[device_tracker_source].last_reported >= timedelta(minutes= timeout) }}

condition:
  - condition: state
    entity_id: !input direction_sensor
    state:
      - away_from
      - towards

action:
  - service: device_tracker.see
    data:
      dev_id: '{{ dev_id }}'
      gps: >-
        {{ [state_attr(device_tracker_source, 'latitude'),
        state_attr(device_tracker_source, 'longitude')] }}        
      gps_accuracy: '{{ state_attr(device_tracker_source, "gps_accuracy") }}'
      battery: '{{ range(1,99) | random | int }}'