Parsing NMEA GPS for RV home location

Tags to add: GPS, BT, Serial, NMEA 0183

I live in an RV, would love to be able to track the home on map, trigger on locations and whatnot. I have 15 minute updates based on reading location from the Android App, but that thing feels ‘glitchy’, works sometimes and then suddenly not - regardless of settings.

now I found a way to get a “NMEA 0183” stream coming through a serial port, (not Bluetooth though, but USB)

So now, next step:
Is there any NMEA parsing helpers, or whatever?
A search for “NMEA”, “GPS” - or anything else I can think of - turns up nothing.

The basics, what to parse:
https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm

Also found:
https://stackoverflow.com/a/28932962/3720510

Just discovered this also:

This entity (‘sensor.bt_gps_02’) does not have a unique ID, therefore its settings cannot be managed from the UI. See the documentation for more detail.

" id: ‘BT_GPS_02’ " is apparently not the way to go there…

What does this have to do with Blueprints?

What are you actually asking for help with?

Here is one of my problems with HA; I do not know in where to look for solutions to this. A blueprint might be it, IDK.

And what i need help with: Parse the NMEA line, make HA understand it.

So is “the NMEA line” already in HA as an entity state?

Here, simple python to read such lines (via stdin) and print the corresponding text that makes google maps accept the lat/lon.

import sys

header,utc,status,lat,latdir,lon,londir,speedKn,tracktrue, \
date,magvar,vardir,modeind,chksum=range(13+1)

for line in sys.stdin:
    line=line.split(',')
    lat=line[lat]
    lon=line[lon]
    lat=float(lat[:2])+float(lat[2:])/60
    lon=float(lon[:3])+float(lon[3:])/60
    print(lat,line[latdir],lon,line[londir])

Unlikely, I see those lines as they come in from the serial port. In a log(?)

Click on the “Entity ID” and then “Logbook”

Then you should set up a Serial sensor:

Then you can use that to trigger an automation using the homeassistant.set_location action to update the location:

triggers:
  - trigger: state
    entity_id: sensor.YOUR_SERIAL_SENSOR
conditions: []
actions:
  - variables:
      nmea: "{{ (trigger.to_state.state).split(',') }}"
      lat: |
        {% set lat_num = nmea[3] %}
        {% set ord = -1 if nmea[4] == "S" else 1%}
        {{ (ord * (lat_num[:2]|int + lat_num[2:]|float/60))|round(5) }}
      long: |
        {% set long_num = nmea[5] %}
        {{ (long_num[:3]|int + long_num[3:]|float/60)|round(5) }}
  - action: homeassistant.set_location
    data:
      latitude: "{{lat}}"
      longitude: "{{long}}"
1 Like

Ahhh… Wonderful; I’ll try it out!

Apparently, I forgot to define the nmea variable, I have fixed it in my post above.

Hmm… second portion of lat and lon are floats, so all “int” → “float” likely required.
Now i have this set in “automations.yaml” with sensor.bt_gps_02 for sensor.YOUR_SERIAL_SENSOR - it is OK for CHECK CONFIGURATION - remains to see if it needs more amending :wink:

Note: there are other lines as well in the serial readout. The lines with
$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68
… are just part of it, but the actual ones having the lat/lon.

Will the above ignore those other lines, or might it be necessary to skip them?

You will need to skip them or modify the automation to process them differently.

Can the trigger be modified to act only on $GPRMC as first item on the line?

You could either use a Template trigger or add a Template condition to what you have already.

I would probably go for the condition… by defining the nmea variable in a variables block, it will be populated after the trigger.

triggers:
  - trigger: state
    entity_id: sensor.YOUR_SERIAL_SENSOR
variables:
  nmea: "{{ (trigger.to_state.state).split(',') }}"
conditions:
  - condition: template
    value_template: "{{ nmea[0] == '$GPRMC' }}"
actions:
  - variables:
      lat: |
        {% set lat_num = nmea[3] %}
        {% set ord = -1 if nmea[4] == "S" else 1%}
        {{ (ord * (lat_num[:2]|int + lat_num[2:]|float/60))|round(5) }}
      long: |
        {% set long_num = nmea[5] %}
        {{ (long_num[:3]|int + long_num[3:]|float/60)|round(5) }}
  - action: homeassistant.set_location
    data:
      latitude: "{{lat}}"
      longitude: "{{long}}"

I was dabbling around in the trigger section of the automation, but could not find a way to get going. I find this frustrating :wink:

Ahh… you found the typo…

Now this looks promising, needs to be tried out; e.g. move the RV :wink:

I’ll try it out the following days and “report back” after that!

The serial port setup, connected physically to the Rpi4, a bluetooth capable GPS, which apparently also sends out NMEA 0183 -code via the USB-cable, in serial-port -form.:

sensor:
  - platform: serial
    serial_port: /dev/ttyUSB1
    name: BT-GPS-01
    baudrate: 9600
    bytesize: 8
    parity: N
    stopbits: 1
    xonxoff: false
    xonxoff: false

Have had this running for a while.
It does it’s thing, and very well so… but with several updates per second.

What I’d change is to have it update only as either of lat or lon changes a suitable amount, e.g. corresponding to 15 meters (~45 feet).
That corresponds somewhat to lat value changing 0.00005, and/or lon value changing 0.0001 - for a simplistic check.

Now, this is growing in complexity; might it be a better approach to split it into parts, e.g. to make the GPS readout a more versatile separate sensor? Then a separate “movement sensor” using the simplistic approach (above). And finally use them for the home-movement?

What would be a reasonable approach?


in all this - I am in a process of attempting to build my understanding of home assistant infrastructure, which admittedly is very low still.

I get frustrated when simple things as naming an entity gets stopped with “Invalid Entity ID” even though I try very simple name(s), e.g. corresponding to a variable name in python, with no indication of WHAT the problem is.

Using the same basic setup you did for the automation, you could have a trigger-based template sensor:

template:
  - trigger:
      - platform: state
        entity_id: sensor.YOUR_SERIAL_SENSOR
        variables:
          nmea: "{{ (trigger.to_state.state).split(',') }}"
          lat: |
            {% set lat_num = nmea[3] %}
            {% set ord = -1 if nmea[4] == "S" else 1%}
            {{ (ord * (lat_num[:2]|int(0) + lat_num[2:]|float(0)/60)) | round(5) }}
          long: |
            {% set long_num = nmea[5] %}
            {{ (long_num[:3]|int(0) + long_num[3:]|float(0)/60) | round(5) }}
    conditions:
      - condition: template
        value_template: "{{ nmea[0] == '$GPRMC' }}"
    sensor:
      - name: RV Position
        state: |
          {{lat}}, {{long}}
        attributes:
          latitude: "{{ lat | float }}"
          longitude: "{{ long | float }}"
        availability: "{{ has_value(sensor.YOUR_SERIAL_SENSOR) }}"

Then change the automation to watch that sensor’s attributes:

triggers:
  - trigger: state
    entity_id: sensor.rv_position
    attribute: lat
  - trigger: state
    entity_id: sensor.rv_position
    attribute: long
variables:
  old_lat: "{{ state_attr('zone.home', 'latitude') }}"
  old_long: "{{ state_attr('zone.home', 'longitude') }}"
  new_lat: "{{ trigger.to_state.attributes.latitude }}"
  new_long: "{{ trigger.to_state.attributes.longitude }}" 
conditions:
  - alias: Check for significant change
    condition: template
    value_template: |
      {{ not ((old_lat + 0.00005) < new_lat < (old_lat - 0.00005)) or
      not ((old_long + 0.0001) < new_long < (old_long - 0.0001)) }}
actions:
  - action: homeassistant.set_location
    data:
      latitude: "{{new_lat}}"
      longitude: "{{new_long}}"

There are other options to throttle the automation further if you’re still getting more updates than you want.

Eh, and there I got stuck; where do I go (in HA) to get this done?
Clicking on Settings > Quick Search gives me nothing for ‘template’ or ‘sensor’, do I need to copy this into any of the .yaml files?
I have wandered over many places in the documentation and not caught it - basic stuff, heh?

Trigger-based template sensors are YAML only, so they have to be manually added to your configuration file.

1 Like

Local time 21:30, long day…

6 days later, I have yet to get to it… But expectations are high. :slight_smile: