[project] GPS data on Miniscreen

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

date and timw is got from GPZDA, not all GPS get that NMEA. Lat and Lon is got from GPRMC, all the NMEA messages are the same in structure, it depends on the device, RMC should be obtainable on every device

@CAProjects we have an RTC inside the pi-top [4] but I’m not sure we’ve made it accessible yet - would it be useful to have that for your project?

not for GPS since GPS uses GPS sats to sync its RTC, it may come in handy for somehting else later down the line tho.

@duwudi does pi-topOS support PPS? if not, no worriesjust wanted to play around with it

@CAProjects remind me what PPS stands for? It’s our acronym for pi-top Production System internally so I’m blanking :sweat_smile:

PPS means pulse per second. It’s used to sync time mostly but can be used for a lot of things.

Can think of it like a sort of time code

Hi @CAProjects

Hope all is well.
I wanted to ask for your help if possible please with the following. Just bought a multiconstelation GPS unit and would love to make use of the code you share with us. Checking gpsmon outputs the following nmea list:
GNRMC
GNVTG
GNGGA
GNGSA
GPGSV
GLGSV
GNGLL
GNGST
GNZDA
GNGBS

Unfortunately for me, none of these are in the nmea decode portion of the code and don’t have any ideea how to write the code to decode these sentences. Would you be able to help me with a few from the above list so that I can display the same info as you?

Have posted a picture in case anyone else would want a similar gps unit on their pitop:
IMG-20210225-WA0006

Many thanks,
Luis

‘$GPGGA’, ‘$GPGSA’, ‘$GPRMC’, ‘$GPZDA’ all get decoded but please note its coded to get the information I want. in the function def nmeaDecode(data): is the code to extract the data from ‘$GPGGA’, ‘$GPGSA’, ‘$GPRMC’, ‘$GPZDA’. you also need to add the sentences you want to decode to this line nmea_list = ['$GPGGA', '$GPGSA', '$GPRMC', '$GPZDA'] otherwise it will ignore them

you need to add an elif there for what ever sentence you want to add to get the information along with the code puting the information in to nmea_dict so that it returns to display on the screen. you will also need to edit the code in def nmea_display(data): to display tin information you want. please note that this does not ise the miniscreen buttons as this code was just to get the GPS info and display it.

to understand NMEA i highly recommend looking at this
http://aprs.gids.nl/nmea/
This explains what all the NMEA data actually is.

I am not going to write the code for you as there is enough information in the example code i have provided along with the useful links in the GitHub repo to code what your needing

1 Like

Many thanks for your reply and for the pointers on how to get this sorted. Much appreciated!

I am working on a Further Challenge for working with GPS and should hopefully finish it today and will get the pitop team to review it for making it public

1 Like

The RTC for the pi-top would be nice eventually. After first starting it, if the pi-top takes longer than usual to update the time, when using some sites you get a clock out of sync warning and no site when using https.

got PPS working, was rather simple

Added dtoverlay=pps-gpio,gpiopin=4 to /boot/config.txt and installed pps-tools, connected adafruit Ultimate GPS v3 PPS pin to GPIO pin 4, rebooted and run the command sudo ppstest /dev/pps0 and got this response (its good)

(env3) pi@pi-top:~/Documents/gps_test $ sudo ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1614297142.992461566, sequence: 460 - clear  0.000000000, sequence: 0
source 0 - assert 1614297143.992469645, sequence: 461 - clear  0.000000000, sequence: 0
source 0 - assert 1614297144.992472627, sequence: 462 - clear  0.000000000, sequence: 0
source 0 - assert 1614297145.992469306, sequence: 463 - clear  0.000000000, sequence: 0
source 0 - assert 1614297146.992476382, sequence: 464 - clear  0.000000000, sequence: 0
source 0 - assert 1614297147.992472796, sequence: 465 - clear  0.000000000, sequence: 0
source 0 - assert 1614297148.992470637, sequence: 466 - clear  0.000000000, sequence: 0
source 0 - assert 1614297149.992471604, sequence: 467 - clear  0.000000000, sequence: 0
source 0 - assert 1614297150.992470638, sequence: 468 - clear  0.000000000, sequence: 0

i guess it was added to the Raspbery Pi OS kernal, from things i read before you had to modify the kernal or remake the kernal to enable it

Dude, nice! I guess it just needs to be implemented so the internal clock stays updated. Have you figured out how to apply it to the GPS without an RTC module?