[project] GPS data on Miniscreen

So i finally finished with the GPS on both the Xavier NX and pi-top. was a little frustrating but finally got there. I have re-written the code for this about 5 times, making improvements each time and now i finally got it running stably without crashes of the python sctipt.

I do want to add a logging mode to this and its not that hard to add but for this, its just to get the data and display on the screen.

The NMEA Sentences I used was

  • GPGGA - Altitude and number of satellites connected to
  • GPGSA - GPS Fix, and fix type
  • GPRMC - Lat, Lon and speed
  • GPZDA - Date and Time

there is a line serialConf.write(b'$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1*34\r\n') which I used to enable GPZDA on the Adafruit Ultimate GPS Breakout v3 and used it to only show the data I wanted


Sorry the GPS Lat and Lon has been censored

The code (commented) can be found on GitHub here :

little disclaimer: testing has only been done indoor with an active antenna, i have yet to take it out and test it while not indoor, waiting for snow to go away and will test it then

as a fun note: i coded the final script in VS code on my PC using the remote SSH extension which allows you to use VS Code remotely and run code as if it was on the device

UPDATE: added KeyboardInterupt to clear the screen and exit the script, i forgot to add this back in on a rewrite

2 Likes

Thanks so much for sharing this detailed build! Am I right in thinking this also works with a gps usb dongle and the change would be in serialconf to /dev/ttyACM0?

Would be interesting to see this as a menu item on the pitop oled menu. Any ideea if this can be integrated there?

Thanks again for sharing and congrats on this buildšŸ˜Š

it work with a USB GPS dongle yes, also by changing the the port info should work but also need to check the baudrate the device works at and change that too

use this python code to check that you are receiving data from the GPS device

import serial
from time import sleep

port = "/dev/ttyTHS0" #change to the serial for your device
ser = serial.Serial(port, baudrate=9600) #change the baudrate to match the gps device
while True:
    sleep(1)
    ser.write(b'A')
    nbChars = ser.inWaiting()
    if nbChars>0:
        data = ser.read(nbChars)
        print(data)

might also want to disable the line serialConf.write(b'$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1*34\r\n') and also so a check to see what NMEA sentences your dongle can receive by default. You can change all 0ā€™s to 1ā€™s will show you everything, then you can disable (0) the data you do not want. also in my code.

if there is any extra NMEA sentences you want to get data from you will have to add to the NMEA list and also add an elif to nmeaDecode to add the info to the nmea_dict which is returned. sadly python has no case switch function built in and its more code to make one so thats why i went with if elif else way

I did comment as much as I could so anyone can understand what is going on, which also helps people learn. I am also sure that the code can be optimized even more but, its working for now

need to add some useful links to my GitHub repo

It can be, not looked into it, also the system menu for the oled will be getting an upddate some time frrom what was discussed here

Thanks for the quick reply and detailed guidance. I will try the above with my setup and see if I can get it to work.

The oled display in my case caused a lot of issues as I couldnā€™t get it to work with Raspbian Buster. It would go on a continuous loop on the pi-top logo sequence and I ended up commenting that out in the code. It would be great if the oled will be updated to work properly with other OS, not only the pi-top OS.

If you have a chance to look into the oled menu code and find a way to add new items please let me know, I would be very much grateful for your support with this.

Thanks for taking the time to share your knowledge here.

Have a great weekend!

@CAProjects this is great, love this application for the OLED miniscreen! Thanks for sharing the code too - Iā€™ll definitely use this for inspiration when I get around to playing with some GPS :+1:

Also yeah, VS Code SSH extension is pretty awesome!

I have broken down code for adding it into Further after we have a call about that

1 Like

@CAProjects: Have tried today today the code but I am getting some errors.

  1. Import error
    20210213_194440

To fix this i changed name from miniscreen to OLED
After this the code seemed to be working as I could see the screen clearing.

  1. nmea_display(nmeaDecode(l)) followed by a couple more errors.
    20210213_193651

The GPS had a fix as the green light was blinking suggesting that (this is a Ublox 7 gps usb). Also running the ā€œgrep ā€œGPGSAā€ --line-buffered < /dev/ttyACM0ā€ did output the coordinates in the terminal.

Would you happen to know what could be causing this? Would be very greatful if you could help me with fixing this.

Many thanks

Is your OS and firmware etc up to date? That import errors suggests an older version of the SDK which would also explain the 2nd error. I think OLED was an old function so your SDK may be out of date. All the errors you get are SDK related

If you have a spare SD card try flashing it with the latest OS image and try on a fresh up to date install

https://www.pi-top.com/products/os

All the code runs perfectly on my system which is always kept up to date

You can also try updating the SDK via pip3?

Also I run the code without sudo

1 Like

Thanks for the quick reply, my OS (Buster) is up to date. I need to check the firmware. But to be on the safe side will do a fresh up to date install as suggested. One thing I wanted to ask, can I run both the pi-top menu system and this code? For example if the menu system is showing on the pi-top, when this code is executed will it display the gps data on the oled display or do I need to kill the process for oled menu system firsr? Thanks

When you run the code it will basically disable the menu and the script will only be on the screen. Nothing else is programmed. When we get an update to the system menu, I will look into converting it into a system widget.

No need to disable the the system menu service. When you run the script the screen will go blank then display the info on the first successful parse. When you stop the script, the system menu will return

1 Like

I went back on your last reply and I think I misunderstood something. You mentioned a fresh up to date install and provided the link to Pi-top OS Sirius. In my case I am not running Pi-top OS, I am running Raspbian Buster.
When running pi-top device hub I get the following output:
20210214_132923

I did tinker a bit with the gps code you provided and the issue is with the nmea decode variables (longitude, altitude, etc). If I comment these out:

displays the required GPS data

# to the pi-top[4] miniscreen
canvas.rectangle(ms.bounding_box, fill=0)
canvas.text((0, 0),f"Lat:  {data['latitude']}",font=ImageFont.load_default(),fill=1)
canvas.text((0, 12),f"Lon: {data['longitude']}",font=ImageFont.load_default(),fill=1)
canvas.text((0, 24),f"Spd: {data['speed']}",font=ImageFont.load_default(),fill=1)
canvas.text((0, 36),f"Fix: {'Yes' if data['fix'] else 'No'}, {data['fix_type']}, {data['satelites']} Sats",font=ImageFont.load_default(),fill=1)
canvas.text((0, 48),f"UTC:{data['date_time']}",font=ImageFont.load_default(),fill=1)

And display a simple text, the text is shown on the display and the code runs perfectly.

Could for example f"Lon: {data[ā€˜longitudeā€™]}" be replaced with something else to see if this would work? As this is the part I think is breaking the code in my case?

Also does the code require a gps fix in order to output on the screen? Or can it display and populate the gps details when a fix is established?

I do apologies I am not very versed in terms of programming. I am still learning.

Thanks again for your support!

Iā€™m at work atm will look at it when at home

I double checked the code on my repo and itā€™s all good

nmea_display(nmeaDecode(l))

This line sends a list of NMEA statements to def nmeaDecode to Extract dada from in to a dictionary and returns the dictionary and sends that to def nmea_display

f"Lon: {data[ā€˜longitudeā€™]}ā€ is a formatted string that displays the text Lon: and the information in the dictionary with the key ā€˜longitudeā€™

KeyError your getting means it cannot find the entry in the dictionary with that key name.

When I get home I will give you the code with print statements in it to see what the code is actually doing, if itā€™s actually getting the information or not

@Luis try running this and screenshot the print statements

I also forgot to add the KeyboardInterupt (ctrl+c) exception which clears the screen and exits the script

import io
import serial
from pitop.miniscreen import Miniscreen
from PIL import Image, ImageDraw, ImageFont

# Serial setup
serialConf = serial.Serial('/dev/ttyAMA0', 9600,timeout=0.3)
serialio = io.TextIOWrapper(io.BufferedRWPair(serialConf, serialConf))

# Enable only NMEA sentences required
serialConf.write(b'$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1*34\r\n')

# pi-top[4] Miniscreen setup 
ms = Miniscreen()
image = Image.new(ms.mode, ms.size,)
canvas = ImageDraw.Draw(image)
ms.set_max_fps(1)

# NMEA sentences required
nmea_list = ['$GPGGA', '$GPGSA', '$GPRMC', '$GPZDA']

def speedCalc(data, u):
    #converts speed to user required units
    if u == 1:
          return f'{round((float(data) * 1.150779448),1)} MPH' 
    elif u == 2:
        return f'{round((float(data) * 1.852),1)} KM/H'
    elif u == 3:
        return f'{round((float(data) * 1.943844),1)} m/s'
    else:
        return f'{round(float(data),1)} kn'

def coordDecode(data, b):
    #decodes lat and lon co-ordinates from GPS NMEA
    sec = round((60*float(f"0.{data.split('.')[1]}")),4)
    return f"{data.split('.')[0][0:-2]}{b} {data.split('.')[0][-2:]}m {sec}s "

def nmeaDecode(data):
    #create dictionary to put data into
    nmea_dict = {}
    
    # for each NMEA sentence, extract the data required
    # and add it to the dictionary
    for x in data:
        if x[0] == '$GPGGA':
            nmea_dict['satelites'] = int(x[7])
            nmea_dict['altitude'] = f'{x[9]} {x[10]}'
            print(f'GPGGA {nmea_dict}')

        elif x[0] == '$GPGSA':
            nmea_dict['fix'] = True if int(x[2]) > 1 else False
            nmea_dict['fix_type'] = f'{x[2]}D' if int(x[2]) > 1 else ''
            print(f'GPGSA {nmea_dict}')
        
        elif x[0] == '$GPRMC':
            #decodes lat and lon to degrees and mins
            nmea_dict['latitude'] = coordDecode(x[3], x[4])
            nmea_dict['longitude'] = coordDecode(x[5], x[6])
        
            # for speed, it can be calculated in MPH, KM/H, m/s Knots
            # 1 = MPH | 2 = KM/H | 3 = m/s | any other no. for knots
            nmea_dict['speed'] = speedCalc(x[7], 1)
            print(f'GPRMC {nmea_dict}')
        elif x[0] == '$GPZDA':
            # gets the date and time from GPS
            nmea_dict['date_time'] = f'{x[2]}/{x[3]}/{x[4][-2:]} {x[1][0:2]}:{x[1][2:4]}:{x[1][4:6]}'
            print(f'GPZDA {nmea_dict}')
    print(nmea_dict)
    # return the dictionary    
    return nmea_dict

def nmea_display(data):
    # displays the required GPS data
    # to the pi-top[4] miniscreen
    canvas.rectangle(ms.bounding_box, fill=0)
    canvas.text((0, 0),f"Lat:  {data['latitude']}",font=ImageFont.load_default(),fill=1)
    canvas.text((0, 12),f"Lon: {data['longitude']}",font=ImageFont.load_default(),fill=1)
    canvas.text((0, 24),f"Spd: {data['speed']}",font=ImageFont.load_default(),fill=1)
    canvas.text((0, 36),f"Fix: {'Yes' if data['fix'] else 'No'}, {data['fix_type']}, {data['satelites']} Sats",font=ImageFont.load_default(),fill=1)
    canvas.text((0, 48),f"UTC:{data['date_time']}",font=ImageFont.load_default(),fill=1)
    ms.display_image(image)

while 1:
    try:
        # create a list for NMEA sentences
        l=[]
        # Get NMEA sentence
        s = serialio.readline().strip().split(',')
        # look for the start of the sentence queue
        if s[0] =='$GPGGA':
            # get all the sentences that matches the list
            for x in nmea_list:
                print(f'GPS DATA: {s}')
                # add sentence to the list
                l.append(s)
                # get next sentence
                s = serialio.readline().strip().split(',')
            # decode the NMEA sentences and display the information
            # on the pi-top[4] miniscreen
            print(f'NMEA List: {l}')
            nmea_display(nmeaDecode(l))
            
    # This exeption is to prevent the script from crashing if there
    # is some garbled GPS data that cannot be decoded to UTF-8
    # this normally happens at the start of running the script
    # and is away of ignoring it
    except UnicodeDecodeError as e:
        continue
    except KeyboardInterrupt:
        ms.clear()
        exit()

@CAProjects Thanks a lot for looking into this, much appreciated :blush:

This is the output I got:
20210214_235412

The GPS has no fix at the moment as I am indoors and I donā€™t have a fix with it indoors due to its poor antenna.

In the code I changed to /dev/ttyACM0.
Thanks

ah, thatā€™s why that is happening, its because there is no fix. forgot to check for fix before getting data, my bad, will look into that this week

Looks like your also not getting GPRMC data which I use to get lat and lon coords, can you change the line

serialConf.write(bā€™$PMTK314,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1*34\r\nā€™)

And change all 0ā€™s to 1ā€™s and re run?

Iā€™ve had a chance to run this just now, here is the output when changing all to 1 (this is without gps fix):
20210215_215546

Iā€™ve done some research and it seems the pi 4 doesnā€™t go along to well with this usb gps. Itā€™s taking an awful long to get a gps fix and connecting anything on the usb 3.0 ports makes the reception and fix almost impossible. Iā€™ve ordered a new gps module (with ext antenna) that is going to be connected via gpio instead of usb. Hopefully this will fix the bad reception and gps fix issues. Thanks

Later edit: tested indoors with a 1m usb extension cable and the gps dongle gets a gps fix/lock. It seems the interference issues are true.

With the gps fix I still get the same error.

GPS can take up to 30 mins to get a fix with no RTC battery, those with an RTC (real time clock) battery can get a fix faster after the 1st fix. Mine will get a fix within 5 secs thanks to using an Battery keeping the RTC alive.

Cloud cover also effects signal and fix. Using an active antenna is the best. The adafruit module with the active antenna Is really good from my experience. I did have 4x NEO6M but it they all died the same day I first powered them on

The gps fix is now less than 1 min. It was only on this Pi4 that I am having issues. The one Iā€™ve ordered has a battery holder for RTC.

I have some good news, the code is partially working. I think something has to do with the format for latitude, longitude and date. If I comment these out the code runs. Just have to figure out how the decode sruff works so I can adapt to what my gps receiver is outputing.
20210215_234647