Geek Culture
Published in

Geek Culture

My experience working with a GPS sensor on a Raspberry Pi

Recently I purchased a GPS sensor to integrate with Raspberry Pi. It’s been fun and a good learning experience to collect GPS data from it using Go code. The sensor I have is a PA1010D connected to a Raspberry Pi Zero W over I²C bus. With a battery-pack powering the Raspberry Pi, the whole setup turned out to be highly portable.

Below is the final output from the setup showing overlay of GPS data on Google maps. This overlay was produced using Google drive’s map feature that allows importing GPX formatting GPS coordinates. GPX format is, therefore, the output of the Go binary I ended up writing for this setup.

Overlay of GPS data on Google Maps

Let’s look into steps taken to get up to this point. First step is to connect the device and making sure it gets detected via i2cdetect command as shown below. In this case, 0x10 address is for the GPS sensor and the 0x77 is for the temperature, humidity and pressure sensor. We need to use this bus address to communicate with the sensor.

$ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: 10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77

Next step involves sending basic commands to initialize and configure the device. For instance following two commands initialize the device output and refresh rates. Read more about these commands in the datasheet.

[]byte("PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
[]byte("PMTK220,1000")

My eventual goal was to build a Go binary and I used the Golang library periph.io/x/conn/v3/i2c for sending read/write commands on I²C bus. The workflow as described in this python code is a very good reference.

At this point reading the I²C bus on device address 0x10 starts delivering so-called NEMA sentences. Below is an example ofGNRMC sentence type, which is one of the several types of sentences we receive from the device.

$GNRMC,152616.000,A,1234.5678,N,123456.9876,W,17.59,120.60,310521,,,D*5B

At this point the basic communication with the device is working and we can now begin to parse these GNRMC sentences to extract GPS information. The sentence format is as follows:

Reference: https://www.nxp.com/docs/en/application-note/AN4046.pdf

Considering there could be malformed sentences in the output and other possible errors that we may want to act upon in our code, I defined NotAGnrmsSentence and DataNotValid as custom error types. Defining these custom error types allows us to use errors.Is() function from the Go standard library to detect specific error instances… more on this at the end.

First we can filter onGNRMC sentence types:

parts := strings.Split(input, ",")
if strings.ToUpper(parts[0]) != "$GNRMC" {
return fmt.Errorf("invalid input: %w", NotAGnrmcSentence)
}

Furthermore, let’s make sure we are working with valid data:

if len(parts) == 13 && strings.ToUpper(parts[2]) != "A" {
return fmt.Errorf("valid GPS data not yet ready: %w", DataNotValid)
}

We can process other items in similar way, but I’ll describe bit more on extracting the GPS data. The latitude information needs to be decoded first, which is in ddmm.mmmm format, i.e., first two digits represent the degress in either the northern or southern hemispheres and the rest of the digits represent the minutes.

// parts[3] is the 4th element in the GNRMC sentence
if splits := strings.Split(parts[3], "."); len(splits) != 2 ||
len(splits[0]) != 4 {
return fmt.Errorf("latitude info is not in correct format ddmm.mmmm %s, %w", parts[3], DataNotValid)
}

We can then individually parse the splits for latitude:

latDegrees, err := strconv.ParseFloat(parts[3][0:2], 64)
if err != nil {
return fmt.Errorf("could not decode latitude degrees: %w", err)
}
latMinutes, err := strconv.ParseFloat(parts[3][2:], 64)
if err != nil {
return fmt.Errorf("could not decode latitude minutes: %w", err)
}
latDegrees += latMinutes/60

Similarly, we can parse the longitude info:

lonDegrees, err := strconv.ParseFloat(parts[5][0:3], 64)
if err != nil {
return fmt.Errorf("could not decode longitude: %w", err)
}
lonMinutes, err := strconv.ParseFloat(parts[5][3:], 64)
if err != nil {
return fmt.Errorf("could not decode longitude: %w", err)
}
lonDegrees += lonMinutes/60

Finally, we need to make sure we adjust the sign of these numbers depending on hemispheres reported in the GNRMC sentence:

switch strings.ToUpper(parts[4]) {
case "S":
latDegrees *= -1
case "N":
default:
return fmt.Errorf("latitude hemisphere neither N nor S, got %s: %w", g.LatDir, DataNotValid)
}
switch strings.ToUpper(parts[6]) {
case "W":
lonDegrees *= -1
case "E":
default:
return fmt.Errorf("longitude hemisphere neither W nor E, got %s: %w", g.LonDir, DataNotValid)
}

Let’s look at error management in Go. Defining a custom error type as follows allows us to parse for specific errors in downstream code:

// Error is a custom error type
type Error string
const (
NotAGnrmcSentence Error = "not-a-gnrmc-sentence"
DataNotValid Error = "data-not-valid"
)
// Error complies with error interface
func (e Error) Error() string {
return string(e)
}
// Is method allows use of errors.Is() to parse errors
func (e Error) Is(err error) bool {
var target Error
if !errors.As(err, &target) {
return false
}
if e == target {
return true
}
return false
}

Finally, we can use the parser to generate GPX output using github.com/tkrajina/gpxgo/gpx library, which can be imported on the Google maps to plot the GPS coordinates.

It is certainly very interesting to work with PA1010D sensor on a Raspberry Pi. In the past I have written about BME280 sensor for measuring temaperature, pressure and humidity. I think it will be awesome to combine these two sensor outputs so we could plot temperature, humidity and pressure on Google maps… more on that later.

--

--

--

A new tech publication by Start it up (https://medium.com/swlh).

Recommended from Medium

Now if the user changes their color preference in your app’s settings, or, for example, the sun…

TableView in Swift #1

How to advocate for Kotlin migration + sample migration process

JAMstackConf: A Sugar Rush

How to use enum class with dart

Live video streaming using python. :

Monitoring Python Flask application with Elastic APM

Do Yourself a Favor — Read More Code

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Saurabh Deoras

Saurabh Deoras

Software engineer and entrepreneur currently building Kubernetes infrastructure and cloud native stack for edge/IoT and ML workflows.

More from Medium

Smart Door Lock System using esp32-cam and Telegram chatbot

Backend with Python: Cruise Mode

How I played Tetris using my piano

Tuning in the Hot Spots

KiwiSDR tuned to a Ukrainian broadcaster.