DIY Projects

M5Stack Atomic GPS. ESP32 TinyGPS++ tracker, GPX export on SD card, visualization on Google Maps or VSCode

Best deal at: banggood.com
CHF 10.04

The GPS module for M5Stack Atom is a mini box that combines a UBX-M8030-KT u-blox GPS module and an M5Atom Lite ESP32-PICO-D4 mini module from M5Stack. For this project, we are going to develop a GPS tracker using the Arduino TinyGPS++ library which will be used to retrieve the location data, the number of satellites, the altitude and the positioning accuracy (HDOP). The tracker’s GPS data will be recorded on a microSD card in GPX format in order to be able to display the track on mapping software such as OpenStreetMap, Google MAP or Leaflet.

 

A statistics file is also generated automatically by the Arduino program. It exports the distance traveled in meters and kilometers, the maximum and average speed.

Presentation of the M5Atom Lite GPS module from M5Stack

The M5Stack GPS module is a box that has a UBX-M8030-KT u-blox GPS receiver on which a Core M5Atom Lite is inserted (the version with a single light point).

The package contains the following items in a protective transparent plastic case

The GPS box is equipped with the following elements

 

Small regret, the box does not embed a LiPo battery. To power the pack, you will need to use a Power Bank, the M5Stack TailBat power supply sold separately or to DIY your own 3D printing solution.

6.2Expert Score
M5Atom Lite GPS module

GPS pack with ESP32-Pico-D4 M5Atom Lite mini module

Price / Specifications ratio
8
Build quality
9
Software support
5
Accessories
9
PROS
  • Compact
  • Speed of acquisition of the GPS signal
CONS
  • No LiPo battery or connector
  • TailBat pack poorly suited to the pack
  • Cannot configure the GPS

Presentation of the M5Atom Lite ESP32-PICO-D4

The M5Stack Atom Lite is a very compact mini development board measuring 24x24x10mm built around an ESP32-PICO-D4 microcontroller from Espressif equipped with 4MB of integrated SPI flash memory. The PICO is a version suitable for clothing or mobile electronics. Like its big brother, it has WiFi and Bluetooth connectivity.

Here are the main equipment and technical characteristics of the Atom Lite

LED RGB SK6812 driver on GPIO27 pin
IR emitter GPIO12 pin
Buttons User on pin GPIO39

RESET

GPIO compatible breadboard G19, G21, G22, G23, G25, G33

3V3, 5V, GND

Ports USB-C for power and programming

GROVE (I2C + I/0 + UART) 4 pin PH2.0 connector. GND, 5V, G26, G32

WiFi antenna 2.4G 3D antenna
Operating temperature 0°C to 40°C
Weight 3g
Dimensions 24 x 24 x 10 mm

More information on this page.

Note, the IR emitter cannot be used when the module is installed on the GPS module.

UBX-M8030 u-blox GPS receiver (M8 series)

The UBX-M8030 receiver is a versatile GPS chip from the u-blox M8 series, the main characteristics of which are:

From an IT point of view, the UBX-M8030 GPS receiver sends at a frequency of 10Hz (10 times per second) messages to the NMEA standard via several interfaces (UART, I2C, USB and SPI). M5Stack has opted for the UART 9600 baud serial interface.

Only regret, there is only the TX pin which is connected. This means that it will not be possible (without modifying the board) to send configuration commands. This can be blocking for some applications but on a daily basis, it works very well.

Useful Resources

Here are some links to useful resources

The NMEA 0183 standard

There are several constellations of satellites allowing for positioning

Each satellite sends frames which are a simple string of characters that the satellite receiver will recover and decode. The NEMA 0183 is a standard used by all positioning system enabling chip development versatile multi-systems. The NMEA 0183 standard   is a specification for communication between marine equipment, including GPS equipment. It is defined and controlled by the  National Marine Electronics Association (NMEA), an American association of manufacturers of marine electronic devices.

Each system has its own prefix to identify it

Here is an example of a NMEA frame

$GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,0000*0E

$ GPGGA: Type of frame
074036.289     : Frame sent at 07:40m 36.289s ( UTC standard time )
4936.5375, N    : Latitude 49.608958 ° North = 48 ° 36’32.25 “North
00740.9373, E  : Longitude 7.682288 ° East = 7 ° 40’56.238 “East
1: Positioning type. 1 for the GPS
04                        : Number of satellites used to calculate the coordinates
3.2: Horizontal precision or HDOP (Horizontal dilution of precision)
202.2, M             : Altitude 202.2 meters
,,,,, 0000: Other information can be entered in these fields
* 0E: Parity checksum, a single XOR  on characters between $ and *3

More information on this Wikipedia page.

By retrieving the signal from several satellites, the GPS receiver is able to determine its position on the ground by triangulation. The more satellites there are, the better the positioning accuracy will be. This is the HDOP.

TinyGPSPlus, the ideal library for a GPS Tracker

There are several ESP32 / ESP8266 compatible Arduino libraries that allow you to decode GPS messages to the NMEA standard. You will also find in the example AtomicGPS with the M5Atom library, a library for decoding NMEA messages.

After having tested Steve Marple’s M5Stack and MicroNMEA library , I advise you to use Mikal Hart’s TinyGPSPlus.

The big advantage of TinyGPS++ is that it has lots of tools:

Create a project and install the ESP32 and M5Stack libraries on the Arduino IDE

Before you start, you will already need to install the ESP32-Arduino SDK that supports ESP32 and ESP32-Pico development boards. Everything is explained in detail in this tutorial.

Once the Arduino IDE is ready, open the library manager and install the following libraries:

Select the M5Stick-C development board

The M5Atom Lite is built a lite version of the ESP32, the ESP32-PICO. If you choose a standard ESP32 development board, you will get an error uploading the program from the Arduino IDE.

There is no configuration (yet) for the Core M5Atom Lite and M5Atom. You must select the M5Stick-C from the list which also includes an ESP32-PICO.

Decrease the transfer speed to 115200 baud otherwise you will get an error.

Select the level of debugging messages, Info for example.

Create a project and install the libraries on PlatformIO

On PlatformIO, there is much less preparation. Open the PlatformIO home screen

Create a new project

In the configuration wizard:

When the project is ready, open the platformio.ini configuration file and paste the following configuration to install the libraries. The installation of the library begins with each backup of the platformio.ini file.

[env:m5atom]
platform = espressif32
board = m5stick-c
framework = arduino

monitor_speed = 115200

lib_deps = 
    m5stack/M5Atom @ ^0.0.1
    fastled/FastLED @ ^3.3.3
    mikalhart/TinyGPSPlus @ ^1.0.2
    michalmonday/CSV Parser @ ^0.2.0

build_flags = -DCORE_DEBUG_LEVEL=5

What does this configuration contain

A GPS tracker project with export to GPX format

The objective of this project is to save the coordinates at regular intervals as well as in GPX format. The GPX format is a standard GPS coordinate recording format supported by all mapping software. It is a file in XML format, here is an example taken from the project. To learn more about the GPX format, you will find plenty of information on this Wikipedia page.

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="diyprojects.io" xmlns="http://www.topografix.com/GPX/1/1" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
  <trk>
    <trkseg>
      <trkpt lat="..." lon="...">
        <time>...</time>
        <sat>...</sat>
        <ele>...</ele>
        <hdop>...</hdop>
      </trkpt>
      <trkpt>
        ...
      </trkpt>
    </trkseg>
  </trk>
</gpx>
trk : trace

trkseg : segment

trkpt : GPS coordinates (latitude, longitude)

time : timestamp in ISO 8601 format

sat : number of satellites

he : altitude

hdop : precision

 

The more the acquisition frequency increases, the more the risk of destroying the micro SD card increases when you want to recover the data. Rather than suddenly cutting off the power supply by removing the battery for example, it will suffice to press the user button once (pin GPIO39 of the ESP32) and wait for the LED to flash twice in orange.

To resume recording, insert the micro SD card in the reader and do a Reset using the side button.

Arduino program parameters

Here are the settings that you can adjust according to your needs. The value in (parentheses) is the default.

How to use the TinyGPS++ library

The GPS receiver continuously sends messages to the serial port (UART). To retrieve these strings, we will therefore open a second serial port and “pass” the strings to the TinyGPS++ library which will take care of decoding all this.

We created an object of type TinyGPSPlus which contains all the methods and the GPS data as well as a second serial port (the first being used to upload and debug the program). The name doesn’t matter.

TinyGPSPlus gps;
HardwareSerial gps_uart(1);

In the setup() , it will then suffice to open the serial port on pin 22. The RX pin of the GPS module not being connected, we pass the value -1 as a parameter.

gps_uart.begin(9600,SERIAL_8N1,22,-1);

Then, it suffices to retrieve at regular intervals (for example every second) the messages sent by the GPS module in the loop () .

loop()
{
  readGPS();
  delay(1000);
}
static void readGPS(unsigned long ms)
{
  do 
  {
    while (gps_uart.available())
      gps.encode(gps_uart.read());  
  } while (millis() - start < ms);
}

How to retrieve GPS data with TinyGPS++ (API)

The GPS data are grouped in the TinyGPS++ library in the following objects. Each object has an isValid() method which makes it possible to check that the decoded values ​​are correct and isUpdated() which makes it possible to know if the value has been updated since the last acquisition. This reduces the number of GPS points in the GPX file.

For example to recover the speed of the vehicle in km/h, we will execute

gps.speed.kmph();

In addition to these methods, we can know the distance between two points (GPS coordinates) using the methods

static double distanceBetween(double lat1, double long1, double lat2, double long2)

or

static double courseTo(double lat1, double long1, double lat2, double long2)

For example, knowing the GPS coordinates of the previous point, we can estimate the distance traveled between two measurements like this

double _distance = gps.distanceBetween(gps.location.lat(), gps.location.lng(), prev_lat, prev_long);
Serial.print("distance = "); Serial.println(_distance);

How do I create and then add data to a GPX file?

In the article “Store data on a micro SD card. Arduino code compatible ESP32, ESP8266”, we saw the basic methods of storing data on an SD card. It’s ultimately as easy as printing text on the serial monitor.

Here, we want to add data to an existing file each time we get new GPS coordinates. To add a new point to the GPX file (which is an XML format), the trick is to move the pointer to the place where you want to insert new data using the seek() function.

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="diyprojects.io" xmlns="http://www.topografix.com/GPX/1/1" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
  <trk>
    <trkseg>
      <trkpt lat="..." lon="..."></trkpt>
      <trkpt lat="..." lon="..."></trkpt>
      <trkpt lat="..." lon="..."></trkpt>
            <--------------------------------- POINTER
    </trkseg>
  </trk>
</gpx>

This way, there will be no need to store a large amount of data in memory. This is much more efficient than JSON which stores data on flash memory.

Remember that flash memory has a limited number of write cycles which is not suitable for a data acquisition system.

To position the pointer, all you have to do is “measure” the size of the file using the size() function and subtract the size used by the end string (here 27 bytes).

unsigned long filesize = gpxFile.size();
filesize -= 27;
gpxFile.seek(filesize);

Then we write the data of the new point and the end of the file using the print() method. To avoid unnecessary use of the microcontroller’s memory, use the F macro.

gpxFile.print(F("<trkpt lat=\"")); 
gpxFile.print(gps.location.lat(),6);
gpxFile.print(F("\" lon=\""));
gpxFile.print(gps.location.lng(),6);
...
gpxFile.print(F("</trkseg>\r\n</trk>\r\n</gpx>\r\n"));
gpxFile.close();

How to flash the LED of the M5Atom Lite

If you run the example delivered with the M5Atom Lite library, you will get two errors.

FastLed, the library is not installed. We have already solved the problem previously

The line M5.dis.fillpix(0x00004f); returns an error because the fillpix() method is not exposed by the M5Atom Lite library. The fillpix() method has been replaced by the drawpix() method which manages the LED matrix of the M5Atom 2020. As there is only one light point available on the M5Atom Lite, it suffices to indicate 0.0 as coordinates.

M5.dis.drawpix(0,0,0x00004f);

In theory, it is possible to use the color palette of the FastLED library rather than passing the Hexa value of the desired color. It is much simpler. However, I obtained unexpected results without finding the source of the problem. If you’ve fixed the issue, feel free to share your solution in the comments below the project.

M5.dis.drawpix(0,0,CRGB::Orange);

Project Code of GPS Tracker with an M5Atom Lite

Here is the code for the project which you can also grab from GitHub.

#include <Arduino.h>
#include "M5Atom.h"
#include <SPI.h>
#include "FS.h"
#include <SD.h>
#include <TinyGPS++.h>
#include "esp_log.h"
#include <CSV_Parser.h>

#define TIME_ZONE 1             // Time compensation 
#define RECORD_FREQUENCY 5000   // Record frequency  

#define ACTIVATE_DEEP_SLEEP_ONCLICK true
#define GPS_TIMEOUT    5000     /* GPS acquisition Timeout  */
#define DISPLAY_GPS_DATA true   /* Display GPS message 

static const char* TAG = "m5atom_gps";

/****************************/
/*        PROTOTYPES        */
/****************************/
static void readGPS(unsigned long ms);
static void printGPSData();
static void createDataFile();
static void addGPXPoint();
static void blink_led_orange();
static void blink_led_red();
static void blink_led_blue();
static void blink_led_green();
static void updateStatFile();
static void printWakeupReason();

// The TinyGPS++ object
TinyGPSPlus gps;
HardwareSerial gps_uart(1);

bool firstStart = true;         // Create GPX file in first start 
char filepath_gpx[25];          // GPX file name 
char filepath_stats[25];        // Stat file name 
char today_folder[15];          // Today folder 
char point_date[22];
float prev_lat = NULL;            // No previous point on startup
float prev_long;  
bool ENABLE_GPS = true;
bool DESACTIVATE_GPS = false;
#define FORCE_UPDATE_STATS true
#define SPEED_BUFFER_SIZE 10

typedef struct TODAYSTATS
{
  float dist;
  float speed_max;
  float speed_mean; 
  double  speedbuffer[SPEED_BUFFER_SIZE];
  bool  speedbufferfull = false;
  int   speedbufferpos = 0;
} TODAYSTATS_t;

TODAYSTATS_t today_stats;

void external_button_pressed()
{
  if ( !ENABLE_GPS ) {
    
  } else {
    blink_led_green();
  }
}

void setup() {
    // begin(bool SerialEnable , bool I2CEnable , bool DisplayEnable )
    M5.begin(true,false,true);   

    // Disable WiFi modem to save power
    // Désactive le modem WiFi pour économiser de la batterie
    //esp_wifi_stop();  

    SPI.begin(23,33,19,-1);
    if(!SD.begin(-1, SPI, 40000000)){
      ESP_LOGE(TAG, "initialization failed!");
    } else {
      sdcard_type_t Type = SD.cardType();

        Serial.printf("SDCard Type = %d \r\n",Type);
        Serial.printf("SDCard Size = %d \r\n" , (int)(SD.cardSize()/1024/1024));
    }

    // Open Serial port with GPS module 
    // Ouvre le port série avec le module HPS
    gps_uart.begin(9600,SERIAL_8N1,22,-1);

    delay(250);

    M5.dis.drawpix(0,0,0);
    
    // Disable GPS when pressing M5Atom Lite Button (GPIO39)
    pinMode(39, INPUT);
    attachInterrupt(39, [] {
      ENABLE_GPS = !ENABLE_GPS;
      ESP_LOGI(TAG,"Change GPS stat %u", ENABLE_GPS);
      if ( !ENABLE_GPS ) DESACTIVATE_GPS = true;
    }, RISING);

    printWakeupReason();
}

void loop() 
{
  if ( DESACTIVATE_GPS ) {
    DESACTIVATE_GPS = false;
    blink_led_orange();
    blink_led_orange();
    // On met en sommeil le module
    if (ACTIVATE_DEEP_SLEEP_ONCLICK) {
      // Le tacker GPS pourra être 
      ESP_LOGI(TAG,"Wakeup on button activated");
      esp_sleep_enable_ext0_wakeup(GPIO_NUM_39,0);
      esp_deep_sleep_start();
    }
  }
  // GPS module is activated 
  if ( ENABLE_GPS ) {
    readGPS(500);

    if (gps.location.isValid() && gps.date.isValid() ) {
        if ( firstStart ) {
          createDataFile();
          delay(250);
          firstStart = false;
        } 

        addGPXPoint();
        
    }  
        
    if (millis() > GPS_TIMEOUT && gps.charsProcessed() < 10)
      ESP_LOGW(TAG, "No GPS data received: check wiring or position");
  }

  delay(RECORD_FREQUENCY);
}

static void createDataFile()
{
  // GPX File name
  // Nom du fichier GPX
  sprintf(today_folder, "/%04d-%02d-%02d", gps.date.year(), gps.date.month(), gps.date.day() );
  if (!SD.exists(today_folder)){
    SD.mkdir(today_folder);
    ESP_LOGI(TAG, "Create today folder %s", today_folder);
  } else {
    ESP_LOGI(TAG, "Today folder already exists %s\n", today_folder);
  }
  // GPX file path
  sprintf(filepath_gpx, "%s/track.gpx", today_folder);
  sprintf(filepath_stats, "%s/stats.csv", today_folder);
  ESP_LOGI(TAG, "GPX file path %s\n", filepath_gpx);
  ESP_LOGI(TAG, "Stats file path %s\n", filepath_stats);
  
  // Reload today stats
  if ( SD.exists(filepath_stats) ) {
    File statsFile = SD.open(filepath_stats, FILE_READ);
    if ( statsFile ) {
      CSV_Parser cp(/*format*/ "sf", /*has_header*/ true, /*delimiter*/ ',');
      cp.readSDfile(filepath_stats);

      if ( cp.getRowsCount() > 0 ) {
        float *val_col = (float*)cp["value"];

        if (val_col) 
          today_stats.dist        = (float)val_col[0]; 
          today_stats.speed_max   = (float)val_col[1];  
          today_stats.speed_mean  = (float)val_col[2];  
          cp.print();
          //Serial.printf("dist %f | max speed %f | mean speed %f  \n", today_stats.dist, today_stats.speed_max, today_stats.speed_mean);
      } else {
        ESP_LOGW(TAG,"Stat file removed because probably corrupted. I'll be re-saved next time");
        SD.remove(filepath_stats);
      } 
    }  
  }  

  // Create today GPX file if not exists  
  if ( !SD.exists(filepath_gpx) ) {
    ESP_LOGI(TAG, "Create new GPX file %s", filepath_gpx);
    // Create GPX file on startup if not exists
    File gpxFile = SD.open(filepath_gpx, FILE_WRITE);
    // GPX file header
    if ( gpxFile ) {
      gpxFile.print(F(
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
        "<gpx version=\"1.1\" creator=\"diyprojects.io\" xmlns=\"http://www.topografix.com/GPX/1/1\" \r\n"
        "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
        "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
        "\t<trk>\r\n<trkseg>\r\n"));  
      gpxFile.print(F("</trkseg>\r\n</trk>\r\n</gpx>\r\n"));
      gpxFile.close();
    } else {
      ESP_LOGW(TAG, "Impossible to open %s GPX file", filepath_gpx);
      blink_led_red();
    }  
  } else {
    ESP_LOGI(TAG, "%s file already exists", filepath_gpx);
  }  
}

/********************************************/
/*    READ MESSAGE SENT BY GPS MODULE       */
/********************************************/
static void readGPS(unsigned long ms)
{
  ESP_LOGI(TAG, "Read GPS stream");
  bool led_state = true;
  unsigned long start = millis();
  do 
  {
    while (gps_uart.available())
      gps.encode(gps_uart.read());
      //Serial.println( (millis() - start) % 250);
      if ( (millis() - start) % 250 != 80 ) {
        led_state = !led_state;
      }
      if ( led_state ) 
        M5.dis.drawpix(0,0,0x0099ff);
      else
        M5.dis.drawpix(0,0,0);
      
  } while (millis() - start < ms);

  if ( DISPLAY_GPS_DATA ) printGPSData();
  M5.dis.drawpix(0,0,0);
}

static void addGPXPoint()
{
  // Only if speed > 5 km/h, update max/mean speed, distance traveled
  if ( gps.speed.kmph() > 5 ) {
    // Add new speed point in the buffer
    double current_speed = gps.speed.kmph();

    today_stats.speedbuffer[today_stats.speedbufferpos] = current_speed;
    today_stats.speedbufferpos += 1;

    if ( current_speed > today_stats.speed_max ) today_stats.speed_max = current_speed;
      
    if ( today_stats.speedbufferpos >= 10 ) {
      today_stats.speedbufferpos = 0;
      today_stats.speedbufferfull = true;
    }
      
    if ( today_stats.speedbufferfull ) {
      float speed_mean = 0;
      for (int l = 0; l < SPEED_BUFFER_SIZE; l++)
      {
        speed_mean += today_stats.speedbuffer[l];
        Serial.print("add to mean"); Serial.println(today_stats.speedbuffer[l]);
      }
      Serial.print("speed_mean total"); Serial.println(speed_mean);
      
      speed_mean = speed_mean / SPEED_BUFFER_SIZE;
      today_stats.speed_mean = speed_mean;
    }    
    
    // Estimate distance traveled from the lasted position
    if ( prev_lat != NULL ) {
      double _distance = gps.distanceBetween(gps.location.lat(), gps.location.lng(), prev_lat, prev_long);
      Serial.print("distance = "); Serial.println(_distance);
      // Ajoute la distance parcourue si < 1 km
      if ( _distance > 0 ) {
        today_stats.dist += _distance;

        updateStatFile();
      }
      prev_lat = gps.location.lat();
      prev_long = gps.location.lng();
      //ESP_LOGI(TAG,"Distance traveled %d", _distance );
    } else {
      prev_lat = gps.location.lat();
      prev_long = gps.location.lng();
    }
  }

  if ( FORCE_UPDATE_STATS ) updateStatFile();

  sprintf(point_date, "%4d-%02d-%02dT%02d:%02d:%02dZ",gps.date.year(), gps.date.month(), gps.date.day(), gps.time.hour() + TIME_ZONE, gps.time.minute(),gps.time.second());

  File gpxFile = SD.open(filepath_gpx, FILE_WRITE);
  if( !gpxFile ) {
    ESP_LOGW(TAG, "Impossible to open %s GPX file", filepath_gpx);
    blink_led_red();
  } else {
    ESP_LOGI(TAG, "Add new point %s", point_date);
    double _lat = gps.location.lat();
    double _lng = gps.location.lng();
    double _alt = gps.altitude.meters();
    double _hdop = gps.hdop.hdop();
    int    _sat  = gps.satellites.value();

    unsigned long filesize = gpxFile.size();
    // back up the file pointer to just before the closing tags
    filesize -= 27;
    gpxFile.seek(filesize);
    gpxFile.print(F("<trkpt lat=\"")); 
    gpxFile.print(_lat,6);
    gpxFile.print(F("\" lon=\""));
    gpxFile.print(_lng,6);
    gpxFile.println(F("\">"));
    gpxFile.print(F("<time>"));
    gpxFile.print(point_date);
    gpxFile.println(F("</time>"));  
    // Satellites
    gpxFile.print(F("<sat>"));
    gpxFile.print(_sat);
    gpxFile.println(F("</sat>"));    
    // Elevation | Altitude 
    gpxFile.print(F("<ele>")); 
    gpxFile.print(_alt,1);
    gpxFile.print(F("</ele>\r\n<hdop>")); 
    gpxFile.print(_hdop,3);
    gpxFile.println(F("</hdop>\r\n</trkpt>"));
    gpxFile.print(F("</trkseg>\r\n</trk>\r\n</gpx>\r\n"));
    gpxFile.close();

    blink_led_blue();
  }  
}

/*********************************/
/*  CREATE OR UPDATE STAT FILE   */
/*********************************/
static void updateStatFile()
{
  File statsFile = SD.open(filepath_stats, FILE_WRITE);
  if(!statsFile) {
    ESP_LOGW(TAG, "Impossible to open %s stats file", filepath_stats);
  } else {
    ESP_LOGI(TAG, "Add stats data to file");
    
    statsFile.print(F("key,value,unit\r\n")); 
    // Distance parcourue aujourd'hui en mètres
    statsFile.print(F("distance,"));
    statsFile.print(today_stats.dist);
    statsFile.print(F(",m"));
    statsFile.print(F("\r\n"));
    // Distance parcourue aujourd'hui en km
    statsFile.print(F("distance,"));
    statsFile.print(today_stats.dist / 1000., 2);
    statsFile.print(F(",km"));
    statsFile.print(F("\r\n"));
    // Vitesse maxi aujourd'hui
    statsFile.print(F("speed_max,"));
    statsFile.print(today_stats.speed_max);
    statsFile.print(F(",hm/h"));
    statsFile.print(F("\r\n"));
    // Vitesse moyenne aujourd'hui
    statsFile.print(F("speed_mean,"));
    statsFile.print(today_stats.speed_mean);
    statsFile.print(F(",km/h"));
    statsFile.print(F("\r\n"));
    statsFile.close();
  }  
}
static void printGPSData()
{
  Serial.print(F("Location: ")); 
  if (gps.location.isValid())
  {
    Serial.print(gps.location.lat(), 6);
    Serial.print(F(","));
    Serial.print(gps.location.lng(), 6);
    Serial.print(F(", age:"));
    Serial.print(gps.location.age(), 6);    
    Serial.print(F(", hdop:"));
    Serial.print(gps.hdop.hdop(), 3);
  }
  else
  {
    Serial.print(F("INVALID LOCATION"));
  }

  Serial.print(F(" Date/Time: "));
  if (gps.date.isValid())
  {
    Serial.print(gps.date.month());
    Serial.print(F("/"));
    Serial.print(gps.date.day());
    Serial.print(F("/"));
    Serial.print(gps.date.year());
  }
  else
  {
    Serial.print(F("INVALID DATE"));
  }

  Serial.print(F(" "));
  if (gps.time.isValid())
  {
    if (gps.time.hour() < 10) Serial.print(F("0"));
    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    if (gps.time.minute() < 10) Serial.print(F("0"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    if (gps.time.second() < 10) Serial.print(F("0"));
    Serial.print(gps.time.second());
    Serial.print(F("."));
    //if (gps.time.centisecond() < 10) Serial.print(F("0"));
    //Serial.print(gps.time.centisecond());
  }
  else
  {
    Serial.print(F("INVALID TIME"));
  }

  Serial.print(F(" Altitude (m):"));
  Serial.print(gps.altitude.meters());

  Serial.print(F(" Speed (km/h):"));
  if (gps.speed.isValid() )
  {
    Serial.print(gps.speed.kmph());
  }
  else
  {
    Serial.print(F("INVALID SPEED"));
  }

  Serial.print(F(" Course:"));
  if (gps.course.isValid() )
  {
    Serial.print(gps.course.deg());
  }
  else
  {
    Serial.print(F("INVALID COURSE"));
  }

  Serial.println();
}

static void printWakeupReason()
{
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch(wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_GPIO : Serial.println("Wakeup caused by GPIO"); break;
    case ESP_SLEEP_WAKEUP_UART : Serial.println("Wakeup caused by UART"); break;
    default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
  }
}

static void blink_led_orange()
{
  M5.dis.drawpix(0,0,0xe68a00);
  delay(200);
  M5.dis.drawpix(0,0,0);
  delay(200);
}

static void blink_led_red()
{
  M5.dis.drawpix(0,0,0xff3300);
  delay(200);
  M5.dis.drawpix(0,0,0);
  delay(200);
}

static void blink_led_blue()
{
  M5.dis.drawpix(0,0,0x0000cc);
  delay(200);
  M5.dis.drawpix(0,0,0);
  delay(200);
}

static void blink_led_green()
{
  M5.dis.drawpix(0,0,0x00ff00);
  delay(200);
  M5.dis.drawpix(0,0,0);
  delay(200);
}

Files generated by the Tracker’s Arduino program

Each GPS frame being time-stamped, the program automatically creates a file with the current date as soon as the GPS Tracker is started. This folder contains the tracker.gpx file which is your route and the stats.csv file (for Comma Separated Values).

20201220
  |_ tracker.gpx
  |_ stats.csv

The statistical data are separated by a comma: distance traveled in meters, distance traveled in km, maximum speed in km / h, average speed in km / h. The data is organized in three columns (key, value, unit).

key,value,unit
distance,3566.13,m
distance,3.57,km
speed_max,38.97,hm/h
speed_mean,24.02,km/h

View a GPX file?

Warning, before extracting the microSD card from the reader, do not forget to stop the GPS by pressing the button and waiting for the LED flashes twice in orange.

There is a multitude of internet sites that allow you to plot GPX files and follow the altitude difference of the hike

If you use VSCode (Visual Studio Code) to develop your ESP32 projects with PlatformIO, there are also two very practical plugins (Geo data Viewer and VSCode Map Preview) that allow you to view GPX, KML, GeoJSON, CSV plots … all this without leaving the code editor. Great!

Import a GPX track to Google Maps

To import a GPX track to Google Maps, you will need to have an account and sign in to it. Then go to Google Map and open the side menu to access your addresses.

Sorry, all pictures are in French

Go to the Cards tab then Create a card at the bottom of the menu

 

Click on Import then drag the recovered GPX file onto the SD card.

Google Map also supports import of CSV, XLSX (Microsoft Excel) and KML formats.

 

The plot is displayed immediately after the end of the transfer of the GPX file to the Google servers.

Display a GPX file directly in VSCode with the Geo data Viewer plugin

If you don’t like the idea of ​​sending your personal data to Google’s servers, you can very well view your GPX files directly in Microsoft’s VSCode code editor using the Geo Data Viewer plugin. Open the plugin menu to install it.

To view a GPX file (or other supported format), open the file in the editor and then summon the menu with the key combination   Ctrl + Alt + M.

Geo Data Viewer opens a new page with many visualization and export tools.

Updates

2021/01/22 Publication of the article

Thanks for reading

French version

 

Click to rate this post!
[Total: 2 Average: 5]
Exit mobile version