Close

Hacked together GPS track

A project log for ESP32 LoRa GPS Bike Thing

ESP32-based tracker, LoRa beacon and road-quality monitor

usedbytesusedbytes 05/08/2020 at 20:470 Comments

With the main components at least powered on, I need to start adding more control and purpose.

GPS Config

First, is being able to configure the GPS module. Instead of the default setting, I want to tell it to:

u-blox have their own command/communication protocol for their GPS modules, called "UBX" - it's all detailed in the M8 Receiver description - Including protocol Specification document.

UBX is nice and simple. Two sync characters ('µb'), two bytes of packet type information, a length field, a payload, and a checksum. So first order of business is to write or find some code which I can use to send and receive UBX messages.

There are some libraries out there:

I really don't need much functionality, so I decided to just write my own. I only plan to support a couple of messages, and the core of my implementation is around 200 lines of C - in this case I think the small targeted implementation makes sense instead of trying to integrate one of the larger more featureful libraries.

I'm still in prototyping mode, so there's no sensible encapsulation or interface definition - but the code is here:

https://github.com/usedbytes/tbeam/blob/b835efd1482f628b7bd0bb4345208d666a2bbf4f/main/gps.c

The crux of it is the receive_ubx function, which can be called with data received from the serial port. It searches for valid UBX messages, and returns them. It's stateful so you don't need to worry about messages getting split by the serial driver, you just call it each time you have data and it will return messages as it finds them. The intended usage is something like so:

#define BUF_SIZE 256
uint8_t serial_buf[BUF_SIZE];

while (1) {
        len = serial_receive(serial_buf, BUF_SIZE);

        uint8_t *cursor = serial_buf;
        while (len > 0) {
                // On success, receive_ubx() will update 'p' to point just
                // after the end of any message it finds.
                uint8_t *p = cursor;
                struct ubx_message *msg = receive_ubx(&p, len);
                if (msg != NULL) {
                        // Do something with msg
                        free(msg);
                } else if (p == cursor) {
                        // Something went wrong - bail
                        break;
                }
                len = serial_buf + BUF_SIZE - p;
                cursor = p;
        }
}

... but I've realised in writing this log, that:

  1. I'm using it wrong in my code (I don't handle multiple messages in one serial receive)
  2. The interface is horrible for using it right - you need to maintain two "cursor" pointers

So I'll probably refine that a bit.

Anyway, my transmitter and receiver code is functional, so I've used it to send a:

And then use the receiver to receive UBX-NAV-PVT messages and decode them into (Timestamp + Longitude + Latitude).

SPIFFS for data storage

Next up I need somewhere to log the GPS data. I don't have an SD card on my board (I kinda forgot about that - some of the TTGO boards do, but not this one), so the only non-volatile storage I have available is the main flash of the ESP32.

ESP-IDF has a spiffs implementation, so I've set up a 1 MB partition to write the data to. Right now I'm writing 16 bytes every second, which will fill up the megabyte in ~18 hours. That's OK - but perhaps a little on the short side. Also once I start logging more data (acceleration, battery etc.) it would reduce, so I'll probably implement some kind of simple compression scheme (store difference from previous sample or something).

Uploading the data

Having the data on the ESP32 is no use if I can't get it off.

I hack-hack-hacked the WiFi station and simple HTTP request examples to attempt to connect to my WiFi at boot, then attempt to upload any files in the spiffs partition via HTTP PUT requests. If that succeeds, it deletes the file from flash (though I'm not correctly receiving HTTP responses, so that's probably broken).

To handle the PUT requests, I found this simple gist which builds on top of the Python 2 SimpleHTTPServer to handle any PUT request. This is obviously a terrible idea in production, but fine for testing.

With all those things in-place, I can now record GPS data, and upload it back to my PC.

Processing the data

The last part is converting my raw binary records to something more useful like a GPX file. golang is my second language of choice, so I whipped up a simple go program which reads in one of the uploaded binary files and writes the data out to a GPX file. Code on Github: https://github.com/usedbytes/tbeam/blob/5eeb99a9c0dc5f6b80e3913240c0e602d334ae66/go/process.go

With that, I can read in one of my logs, save it out as a GPX, and then view it in Viking. Here's a little excerpt from my test ride yesterday:

This feels like good progress.

Next I really need to start tidying up the code so that I can more easily extend the functionality to add in the accelerometer data, some battery data, and a TTN mapper task.

Discussions