Compass indicator from GPS(d) data?

Hi all,
I am starting a smart boat project based on a RPI4, HA Core and a Waveshare GPS-HAT for the Pi which is being accessed by gpsd and the corresponding intergation in HA.
I also installed the compass-card through HACS and am looking for a solution to have a compass on my HA’s dashboard. Best would be with rotating (instead of fixed) North, but that’s not match making at the moment.
Is anybody able to guide me on how to achieve a suitable direction sensor?
The following values are available from gpsd:

  • latitude
  • longitude
  • speed
  • elevation
  • modus
  • climb
  • time

In my understanding, something like a triangulation of the last two/three latitude/longitude pairs (maybe by considering the time) should lead to a direction/compass indicator, but like said before, I have no idea on how to implement this.
Any help is apreciated.

From here:

https://www.movable-type.co.uk/scripts/latlong.html

Formula: θ = atan2( sin Δλ ⋅ cos φ2 , cos φ1 ⋅ sin φ2 − sin φ1 ⋅ cos φ2 ⋅ cos Δλ )

You don’t say how you get the lat and long into your system. I’m going to assume you have a sensor.gpsd with a timestamp as the state, and lat/long as attributes.

Then we set up a trigger-based template sensor updated whenever that gpsd sensor changes (the from: null ensures it triggers only when the state changes):

template:
  - trigger:
      - trigger: state
        entity_id: sensor.gpsd
        from: null

    sensor:
      - name: Bearing
        unit_of_measurement: '°'
        state: >
          {% set lat1 = trigger.from_state.attributes.latitude * pi / 180 %}
          {% set long1 = trigger.from_state.attributes.longitude * pi / 180 %}
          {% set lat2 = trigger.to_state.attributes.latitude * pi / 180 %}
          {% set long2 = trigger.to_state.attributes.longitude * pi / 180 %}
          {% set y = sin(long2-long1) * cos(lat2) %}
          {% set x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(long2-long1) %}
          {% set r = atan2(y, x) %}
          {{ (r * 180 / pi + 360) % 360 }}

That gives the initial bearing between the two points, although if you’re updating with any reasonable frequency, that’s a distinction that doesn’t matter.

1 Like

Cool, thanks.
That points me to the right direction.
My “general” sensort is sensor.gps_127_0_0_1 which has attributes such as latitude, longitude etc. The integration also provides dedicated sensors for each attribute.
I have tried the following code now:

er:
      - trigger: state
        entity_id: sensor.gps_127_0_0_1
        from: null

    sensor:
      - name: Bearing
        unit_of_measurement: '°'
        state: >
          {% set lat1 = trigger.from_state.attributes.latitude * pi / 180 %}
          {% set long1 = trigger.from_state.attributes.longitude * pi / 180 %}
          {% set lat2 = trigger.to_state.attributes.latitude * pi / 180 %}
          {% set long2 = trigger.to_state.attributes.longitude * pi / 180 %}
          {% set y = sin(long2-long1) * cos(lat2) %}
          {% set x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(long2-long1) %}
          {% set r = atan2(y, x) %}
          {{ (r * 180 / pi + 360) % 360 }}

and restarted HA afterwards.
Unfortunately sensor.bearing shown ‘unknown’ even if the GPS locations are changing.
Logs also do not show anything.
Any ideas what might be wrong?

Where have you put that code? Needs to be under template: — your paste isn’t clear.

I’ve just tried it with my wife’s car’s device tracker as she’s on her way to work and it works fine for me, giving values that match the map. I had to remove the from: null as the tracker state doesn’t change from not_home once on the move.

Sorry for the incomplete copy+paste.
I’ve put it in configuration.yaml as follows:

template:
  - trigger:
      - trigger: state
        entity_id: sensor.gps_127_0_0_1
          #        from: null

    sensor:
      - name: Bearing
        unit_of_measurement: '°'
        state: >
          {% set lat1 = trigger.from_state.attributes.latitude * pi / 180 %}
          {% set long1 = trigger.from_state.attributes.longitude * pi / 180 %}
          {% set lat2 = trigger.to_state.attributes.latitude * pi / 180 %}
          {% set long2 = trigger.to_state.attributes.longitude * pi / 180 %}
          {% set y = sin(long2-long1) * cos(lat2) %}
          {% set x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(long2-long1) %}
          {% set r = atan2(y, x) %}
          {{ (r * 180 / pi + 360) % 360 }}

After commenting out the “from: null” it is working!
Sorry for my too early reply…

1 Like

Good news: the from: null was just an assumption as I didn’t know what your sensor structure was.

You can turn this into a compass heading easily enough. I do this in my weather setup with a reusable template (macro) like this:

{% macro dir(bearing=0,tokens=('N','NNE','NE','ENE','E','ESE','SE','SSE','S','SSW','SW','WSW','W','WNW','NW','NNW'),range=360,centre=True) -%}
{% set ns=namespace(tokens=tokens) -%}
{% if centre -%}
{% set ns.tokens = [tokens[0]] -%}
{% for t in tokens[1:] -%}
{% set ns.tokens = ns.tokens + [t] + [t] -%}
{% endfor -%}
{% set ns.tokens = ns.tokens + [tokens[0]] -%}
{% endif -%}
{% set divs = ns.tokens|count -%}
{% set bclip = ((0,bearing,range-1)|sort)[1] -%}
{{ ns.tokens[(bclip * divs // range)|int] -}}
{% endmacro -%}

and you can set up a template sensor to use it:

{% from 'direction.jinja' import dir %}
{{ dir(states('sensor.bearing')|float(0)) }}
1 Like