Week 11: Interfaces

2018-11-28

The Task

Write an application that interfaces with an input and/or output device that you made.

My Idea

I want to keep working on my final project - for this week, I want to write the code that instructs the module to log output every 10 seconds, spit that data out, then parse it into a GPX file for use in Strava.

Planning

A very nice thing about the Ultimate GPS module is that it includes a built-in logger that by default logs every 15 seconds in when it is instructed to begin. The logger has 32KB of flash memory - because each store is 15 bytes, this means that in total, the logger can hold about 16 hours of data at this rate. My goal is to figure out how to log in 5 second intervals - I don’t need to store that much because runs only take ~1 hr, so the logger should realistically be able to log multiple running sessions no matter what before syncing with my computer and dumping the data. Control of the logger is documented here.

Now, all that needs to be done is the actual parsing of documentation and coding.

The Code

It turns out that sending custom (i.e., not predefined by the Adafruit GPS module) involves a really weird checksum to be added after the command sequence. The syntax is $<command>*<checksum> where the checksum is the hexadecimal of the XORs of all off the bytes between $ and *. There is a handy calculator for calculating these checksums.

Further, there is a discrepancy in the notation for commands - it seems that some commands work when there is a comma between the starting letters of each packet (PMTK) and the packet name (seems to always be a three-digit number).I struggled for quite a while figuring this out.

After a bunch of trial and error, I was able to write a program that logs the output of the GPS module every 5 seconds instead of the default 15 seconds. Further, when the letter ‘d’ is inputted, the log is serial dumped (note that there is an option to dump all memory and an option to dump only used memory, and I choose to do the latter in my code). Everytime the letter ‘e’ is inputted, the logger is erased to make room for more logging.

#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(3, 2);
Adafruit_GPS GPS(&mySerial);

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO  true

// this keeps track of whether we're using the interrupt
// off by default!
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

void setup()
{
  // connect at 115200 so we can read the GPS fast enough and
  // also spit it out
  Serial.begin(115200);
  while (!Serial);
  delay(1000);
  Serial.println("Adafruit GPS logging start test!");

  // 9600 NMEA is the default baud rate for MTK - some use 4800
  GPS.begin(9600);

  // You can adjust which sentences to have the module emit, below
  // Default is RMC + GGA
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // Default is 1 Hz update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_OFF);
  // Sets 5 second logging interval
  GPS.sendCommand("$PMTK187,1,5*38");
  // the nice thing about this code is you can have a timer0 interrupt go off
  // every 1 millisecond, and read data from the GPS for you. that makes the
  // loop code a heck of a lot easier!
  useInterrupt(true);
  delay(500);
  Serial.print("\nSTARTING LOGGING....");
  if (GPS.LOCUS_StartLogger())
    Serial.println(" STARTED!");
  else
    Serial.println(" no response :(");
  delay(1000);
}

void loop()                     // run over and over again
{
  // displays statistics
  GPS.sendCommand("$PMTK183*38");
  delay(10000);
}

/******************************************************************/

// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  if (GPSECHO && c) {
#ifdef UDR0
    UDR0 = c;
    // writing direct to UDR0 is much much faster than Serial.print
    // but only one character can be written at a time.
#else
    Serial.write(c);
#endif
  }
  if (Serial.available()){
    char command = Serial.read();
    // dump data
    if (command == 'd'){
      GPS.sendCommand("$PMTK622,1*29");
    }
    // erase all data
    else if (command == 'e'){
      GPS.sendCommand("$PMTK184,1*22");
    }
  }
}

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

The following is an example of a line of the output. It is in a strange hexadecimal format that must be decoded. `$PMTKLOX,1,5,9825FE5B,04FF7829,42C23B8E,C24A000F,A725FE5B,04D77829,42BE3B8E,C2470069,B625FE5B,04DE7829,42BF3B8E,C2440073,CC25FE5B,04197929,42B93B8E,C24300CE,DB25FE5B,04EC7829,42BE3B8E,C242002B,EA25FE5B,04EB7829,42BF3B8E,C242001C*24

Here is what it looks like on the Arduino serial monitor:

serial output

The next step is writing a Python script that parses this output and turns it into GPX files. I used tkinter to do this - when the “dump” button is pressed, data is dumped. When the “erase” button is pressed, data is completely erased from the GPS module’s logger.

interface

Here is the code:

import serial
import time
import sys
import math
import tkinter as tk

port = sys.argv[1]
ser = serial.Serial(port, 115200)

root = tk.Tk()
root.title("Welcome to your GPS!")
root.geometry("300x200")
frame = tk.Frame(root)
frame.pack()

def dump():
    ser.write(str.encode('d'))
    shit = ser.read_until(str.encode("$PMTK001,622,3*36\r\n"))
    print(shit)

def erase():
    ser.write(str.encode('e'))
    print("erased all parses")

dump_button = tk.Button(frame, text="dump", command=dump)
dump_button.pack(side=tk.LEFT)

erase_button = tk.Button(frame, text="erase all", command=erase)
erase_button.pack(side = tk.RIGHT)
root.mainloop()

This interface is going to get a lot prettier with the end of my final project - stay tuned for cool updates! :)