M5Stack Atomic GPS. Tracker ESP32 TinyGPS++, export GPX sur carte SD, visualisation sur Google Maps ou VSCode

Partager sur facebook
Partager sur twitter
Partager sur linkedin
Partager sur pinterest
Partager sur email
Partager sur telegram

Table des matières

Le module GPS pour M5Stack Atom est un mini boitier qui combine un module GPS u-blox UBX-M8030-KT et un mini module M5Atom Lite ESP32-PICO-D4 de M5Stack. Pour ce projet, nous allons développer un tracker GPS à l’aide de la librairie Arduino TinyGPS++ qui nous servira à récupérer les données de localisation, le nombre de satellite, l’altitude et la précision de positionnement (HDOP). Les données GPS du tracker seront enregistrées sur une carte microSD au format GPX afin de pouvoir afficher le tracé sur un logiciel de cartographie tel que OpenStreetMap, Google MAP ou Leaflet.

 

Un fichier de statistique est également généré automatiquement par le programme Arduino. Il exporte la distance parcourue en mètres et en kilomètres, la vitesse maxi et moyenne.

Présentation du module GPS M5Atom Lite de M5Stack

Le module GPS de M5Stack est un boitier qui embarque un récepteur GPS UBX-M8030-KT u-blox sur lequel vient s’insérer un Core M5Atom Lite (la version avec un seul point lumineux).

Le package contient les éléments suivants dans un boitier de protection en plastique transparent

  • M5Atom Lite construit autour d’un ESP32-PICO-D4 d’Espressif
  • Le boitier GPS u-blox
  • Un câble USB-C pour la programmation
  • Visserie + Clé 6-pans

atomic m5stack M5atom lite gps module

Le boitier GPS est un équipé des éléments suivants

  • D’un récepteur GPS UBX-M8030-KT du fabricant u-blox
  • D’un lecteur de carte Micro-SD
  • D’un port Grove A PH2.0 4 broches exposant le bus I2C (broches 21 et 22 de l’ESP32-PICO-D4)
atomic m5stack M5atom lite gps tailbat module atomic m5stack M5atom lite gps tailbat module side atomic m5stack M5atom lite gps module sdcard

Petit regret, le boitier n’embarque pas de batterie LiPo. Pour alimenter le pack, il faudra utiliser une Power Bank, le bloc d’alimentation TailBat de M5Stack vendu séparément ou bricoler sa propre solution par impression 3D.

7.8Score Expert
Module GPS M5Atom Lite

Pack GPS avec mini module ESP32-Pico-D4 M5Atom Lite

Rapport Prix / Spécifications
8
Qualité de fabrication
9
Support logiciel
5
Accessoires
9
POUR
  • Compact
  • Rapidité d'acquisition du signal GPS
CONTRE
  • Absence de batterie LiPo ou d'un connecteur
  • Pack TailBat mal adapté au pack
  • Impossible de paramétrer le GPS

Présentation du boitier M5Atom Lite ESP32-PICO-D4

m5stack m5atom lite esp32 pico d4Le M5Stack Atom Lite est une mini carte de développement très compacte mesurant 24x24x10mm construit autour d’un micro-contrôleur ESP32-PICO-D4 d’Espressif équipé de 4Mo de mémoire flash SPI intégré. Le PICO est une version adaptée à l’électronique vestimentaire ou mobile. Comme son grand frère, il dispose de la connectivité WiFi et Bluetooth.

Voici les principales équipement et caractéristiques techniques de l’Atom Lite

LED RGB Pilote SK6812 sur la broche GPIO27
Emetteur IR Broche GPIO12
Boutons Utilisateur sur la broche GPIO39

RESET

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

3V3, 5V, GND

Ports USB-C pour alimentation et programmation

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

Antenne WiFi Antenne 3D 2.4G
Température de fonctionnement 0°C à 40°C
Poids 3g
Dimensions 24 x 24 x 10 mm

Plus d’informations sur cette page.

Remarque, l’émetteur IR n’est pas utilisable lorsque le module est installé sur le module GPS.

Récepteur GPS UBX-M8030 u-blox (série M8)

Le récepteur UBX-M8030 est une puce GPS polyvalente de la série u-blox M8 dont voici les principales caractéristiques :

  • Réception simultanée jusqu’à 3 types de systèmes de positionnement GNSS (GPS, Galileo, GLONASS, BeiDou)
  • Sensibilité de navigation allant jusqu’à -167 dBm
  • Faible consommation énergétique
  • Précision de positionnement supérieur en milieu urbain
  • Prise en charge de tous les systèmes satellites
  • Plage de températures de fonctionnement de -40°C à +105°C (qualité automobile)

D’un point de vue informatique, le récepteur GPS UBX-M8030 envoie à la fréquence de 10Hz (10 fois par secondes) des messages au standard NMEA via plusieurs interfaces (UART, I2C, USB et SPI). M5Stack a opté pour l’interface série UART à 9600 bauds.

Seul regret, il n’y a que la broche TX qui est connectée. Cela signifie qu’il ne sera pas possible (sans modifier la carte) d’envoyer des commandes de configuration. Cela peut être bloquant pour certaines application mais au quotidien, ça fonctionne très bien.

Ressources utiles

Voici quelques liens vers des ressources utiles

Le standard NMEA 0183

Il existe plusieurs constellations de satellites permettant de faire de positionnement

  • Galileo le système Européen
  • GPS le système Américain le plus ancien (et le plus connu)
  • GLONASS le système Ruse
  • BeiDou le système Chinois

Chaque satellites envoie des trames qui sont une simple chaîne de caractère que le récepteur satellite va récupérer et décoder. Le NMEA 0183 est un standard employé par tous les système de positionnement ce qui a permis le développement de puces polyvalentes multi-systèmes. La norme NMEA 0183 est une spécification pour la communication entre équipements marins, dont les équipements GPS. Elle est définie et contrôlée par la National Marine Electronics Association (NMEA), association américaine de fabricants d’appareils électroniques maritimes.

Chaque système dispose de son préfixe pour l’identifier

Voici un exemple de trame NMEA

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

$GPGGA          :   Type de trame
074036.289     :   Trame envoyée à 07h40m 36,289s (heure au standard UTC)
4936.5375,N    :   Latitude 49,608958° Nord = 48°36’32.25″ Nord
00740.9373,E  :   Longitude 7,682288° Est = 7°40’56.238″ Est
1                          :   Type de positionnement. 1 pour le GPS
04                       :   Nombre de satellites utilisés pour calculer les coordonnées
3.2                      :   Précision horizontale ou HDOP (Horizontal dilution of precision)
202.2,M            :    Altitude 202.2 mètres
,,,,,0000            :   D’autres informations peuvent être inscrites dans ces champs
*0E                    :    Somme de contrôle de parité, un simple XOR sur les caractères entre $ et *3

Plus d’informations sur cette page Wikipedia.

En récupérant le signal de plusieurs satellites, le récepteur GPS est capable de déterminer par triangulation sa position au sol. Plus il y aura de satellite, meilleur sera la précision de positionnement. C’est le HDOP.

TinyGPSPlus, la librairie idéale pour un Tracker GPS

Il existe plusieurs librairies Arduino compatibles ESP32 / ESP8266 qui permettent de décoder les messages GPS au standard NMEA. Vous trouverez également dans l’exemple AtomicGPS avec la librairie M5Atom une librairie pour décoder les messages NMEA.

Après avoir testé la librairie M5Stack et MicroNMEA de Steve Marple, je vous conseille d’utiliser TinyGPSPlus de Mikal Hart. Le gros avantage de TinyGPS++ est quelle dispose de pleins d’outils :

  • Extraction de la date et de l’heure qui est au format UTC
  • Vérifie la validité de toutes les informations (localisation, date, heure, vitesse…)
  • Et surtout la méthode distanceBetween() qui permet de calculer la distance parcourue entre deux points

Créer un projet et installer les librairies ESP32 et M5Stack sur l’IDE Arduino

Avant de commencer, vous devrez déjà installer le SDK Arduino-ESP32 prenant en charge les cartes de développement ESP32 et ESP32-Pico. Tout est expliqué en détail dans ce tutoriel.

A LIRE AUSSI :
ESP32. Débuter avec Arduino-ESP32 sur IDE Arduino, macOS, Windows, Linux

Une fois l’IDE Arduino prêt, ouvrez le gestionnaire de librairie et installez les librairies suivantes :

  • M5Atom la librairie de base qui permet d’accéder à la LED et ou bouton utilisateur
  • FastLED elle est utilisée par la librairie M5Atom pour piloter la LED située sous le bouton utilisateur
  • TinyGPSPlus la librairie pour décoder les trames GPS NMEA
  • CSV Parser utilisée dans le projet pour stocker et recharger quelques statistiques (distance parcourue, vitesse moyenne et vitesse maxi.)

Sélectionner la carte de développement M5Stick-C

Le M5Atom Lite est construit une version allégée de l’ESP32, l’ESP32-PICO. Si vous choisissez une carte de développement ESP32 standard, vous obtiendrez une erreur en téléversant le programme depuis l’IDE Arduino.

Il n’y a pas (encore) de configuration pour les Core M5Atom Lite et M5Atom. Il faut sélectionner le M5Stick-C dans la liste qui embarque également un ESP32-PICO.

ide arduino M5stickc M5atom lite M5atomic esp32-pico

Diminuez la vitesse de transfert à 115200 bauds sinon vous obtiendrez une erreur.

Sélectionner le niveau des messages de mise au point, Info par exemple.

Créer un projet et installer les librairies sur PlatformIO

Sur PlatformIO, il y a beaucoup moins de préparatifs. Ouvrez l’écran d’accueil de PlatformIO

platformio pio tool menu vscode

Créer un nouveau projet

Créer un nouveau projet avec PlatformIO sous VSCode

Dans l’assistant de configuration :

  • Donner un nom à votre projet
  • Sélectionner la carte M5Stick-C (comme sur l’IDE Arduino)
  • Conserver le framework Arduino
  • Modifier éventuellement le dossier d’enregistrement
  • Lancer la création du projet en cliquant sur Finish

platformio M5stickc M5atom lite M5atomic esp32-pico

Lorsque le projet est prêt, ouvrez le fichier de configuration platformio.ini et collez la configuration suivante pour installer les librairies. L’installation des librairie débute à chaque sauvegarde du fichier platformio.ini.

[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

Que contient cette configuration

  • monitor_speed permet de modifier la vitesse du moniteur série de PlatformIO qui utilise par défaut un débit de 9600 bauds
  • lib_deps la liste des librairies à installer (M5Atom, FastLED, TinyGPSPlus, CSV Parser)
  • build_flags permet de définir le niveau des messages de la librairie ESP_LOG, plus pratique que le Serial.print().

Un projet de tracker GPS avec export au format GPX

L’objectif de ce projet est d’enregistrer à intervalle régulier les coordonnées ainsi que au format GPX. Le format GPX est un format d’enregistrement de coordonnées GPS standard supporté par tous les logiciels de cartographies. C’est un fichier au format XML dont voici un exemple tiré du projet. Pour en savoir plus sur le format GPX, vous trouverez plein d’infos sur cette page Wikipedia.

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="domotique-et-objets-connectes.fr" 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 : coordonnées GPS (latitude, longitude)

time : horodatage au format ISO 8601

sat : nombre de satellites

ele : altitude

hdop : précision

 

Plus la fréquence d’acquisition augmente, plus le risque de détruire la carte micro SD augmente lorsqu’on souhaite récupérer les données. Plutôt que de couper brutalement l’alimentation en retirant la batterie par exemple, il suffira d’appuyer une fois sur le bouton utilisateur (broche GPIO39 de l’ESP32) et d’attendre que la LED clignote deux fois en orange.

Pour reprendre l’enregistrement, insérer la carte micro SD dans le lecteur et faites un Reset à l’aide du bouton latéral.

Paramètres du programme Arduino

Voici les paramètres que vous pouvez ajuster en fonction de vos besoins. La valeur entre (parenthèses) est la valeur par défaut.

  • TIME_ZONE (1) compensation du décalage horaire
  • RECORD_FREQUENCY (5000) Fréquence d’enregistrement
  • ACTIVATE_DEEP_SLEEP_ONCLICK (true) active la mise en veille lorsqu’on appuie sur le bouton utilisateur
  • GPS_TIMEOUT (5000) Signale une erreur si le GPS n’a pas répondu dans le temps alloué
  • FORCE_UPDATE_STATS (false) Permet de force l’enregistrement des statistiques
  • SPEED_BUFFER_SIZE (10) Nombre de points permettant de calculer la vitesse moyenne

Comment utiliser la librairie TinyGPS++

Le récepteur GPS envoie en permanence sur le port série (UART) les messages. Pour récupérer ces chaînes, on va donc ouvrir un second port série et “passer” les chaînes à la librairie TinyGPS++ qui va s’occuper de décoder tout ça.

On créé un objet de type TinyGPSPlus qui contient toutes les méthodes et les données GPS ainsi qu’un deuxième port série (le premier étant utilisé pour téléverser et mettre au point le programme). Le nom n’a aucune importance.

TinyGPSPlus gps;
HardwareSerial gps_uart(1);

Dans le setup(), il suffira ensuite d’ouvrir le port série sur la broche 22. La broche RX du module GPS n’étant pas connectée, on passe la valeur -1 comme paramètre.

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

Ensuite, il suffit de récupérer à intervalle régulier (par exemple chaque seconde) les messages envoyés par le module GPS dans la 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);
}

Comment récupérer les données GPS avec TinyGPS++ (API)

Les données GPS sont regroupés dans la librairie TinyGPS++ dans les objets suivants. Chaque objet dispose d’une méthode isValid() qui permet de contrôler que les valeurs décodées sont correctes et isUpdated() qui permet de savoir si la valeur a été actualisée depuis la dernière acquisition. Cela permet de réduire le nombre de points GPS dans le fichier GPX.

  • TinyGPSLocation location, coordonnées GPS. Fonctions age(), lat(), lng(), rawlat(), rawlng()
  • TinyGPSDate date , date du point GPS. Fonctions age(), year(), month(), day()
  • TinyGPSTime time heure du point GPS. Fonctions age(), hour(), minute(), second(), centisecond()
  • TinyGPSSpeed speed. Vitesse. Fonctions knots(), mph(), mps(), kmph()
  • TinyGPSCourse course. Fonction course()
  • TinyGPSAltitude altitude. Fonctions meter(), miles(), kilometers(), feet()
  • TinyGPSInteger satellites. Fonctions value() pour récupérer le nombre de satellites
  • TinyGPSHDOP hdop. fonction hdop()

Par exemple pour récupérer la vitesse du véhicule en km/h, on exécutera

gps.speed.kmph();

En plus de ces méthodes, on peut connaître la distance entre deux points (coordonnées GPS) à l’aide des méthodes

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

ou

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

Par exemple, connaissant les coordonnées GPS du point précédent, on pourra estimée la distance parcourue entre deux mesures comme ceci

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

Comment créer puis ajouter des données à un fichier GPX ?

Dans l’article “Stocker des données sur une carte micro SD. Code Arduino compatible ESP32, ESP8266”, nous avons vu les méthodes de base de stocker des données sur une carte SD. C’est finalement aussi simple que d’imprimer du texte sur le moniteur série.

A LIRE AUSSI :
ESP32. Stocker des données temporaires dans la mémoire RTC durant la mise en veille

Ici, on souhaite ajouter des données à un fichier existant à chaque fois qu’on récupère de nouvelles coordonnées GPS. Pour ajouter un nouveau point au fichier GPX (qui est un format XML), l’astuce consiste à déplacer le pointeur à l’endroit ou l’on souhaite insérer de nouvelles données à l’aide de la fonction seek().

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="domotique-et-objets-connectes.fr" 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>
            <--------------------------------- POINTEUR
    </trkseg>
  </trk>
</gpx>

De cette façon, on aura pas besoin de stocker en mémoire une grande quantité de données. C’est beaucoup plus efficace que le JSON qui stocke les données sur la mémoire flash.

Il ne faut jamais oublier que la mémoire flash a un nombre limité de cycle d’écriture qui n’est pas adapté à un système d’acquisition de données.

Pour positionner le pointeur, il suffit de “mesurer” la taille du fichier à l’aide de la fonction size() et de soustraire la taille utilisé par la chaîne de fin (ici 27 bytes).

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

Puis on écrit les données du nouveau point et la fin du fichier à l’aide de la méthode print(). Pour éviter d’utiliser inutilement la mémoire du micro-contrôleur, on utiliser la macro F.

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();

Comment faire clignoter la LED du M5Atom Lite

Si vous exécutez l’exemple livré avec la librairie M5Atom Lite, vous obtiendrez deux erreurs.

FastLed, la librairie n’est installée. Nous avons déjà résolu le problème précédemment

La ligne M5.dis.fillpix(0x00004f); renvoie une erreur car la méthode fillpix() n’est pas exposée par la librairie M5Atom Lite. La méthode fillpix() a été remplacée par la méthode drawpix() qui gère la matrice de LED du M5Atom 2020. Comme il n’y a qu’un seul point lumineux disponible sur le M5Atom Lite, il suffit d’indiquer 0,0 comme coordonnées.

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

En théorie, il est possible d’utiliser la palette de couleurs de la librairie FastLED plutôt que de passer la valeur Hexa de la couleur souhaitée. C’est beaucoup plus simple. Cependant j’ai obtenu des résultats inattendus sans pour autant avoir trouvé l’origine du problème. Si vous avez résolu le problème, n’hésitez pas à partager votre solution dans les commentaires en dessous du projet.

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

Code du projet de Tracker GPS avec un M5Atom Lite

Voici le code du projet que vous pouvez également récupérer sur 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 / compensation décalage horaire
#define RECORD_FREQUENCY 5000   // Record frequency  / Fréquence d'enregistrement

#define ACTIVATE_DEEP_SLEEP_ONCLICK true
#define GPS_TIMEOUT    5000     /* GPS acquisition Timeout | Temps autorisé pour acquiérir le signal GPS */
#define DISPLAY_GPS_DATA true   /* Display GPS message | affiche le message du GPS

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 | créé le fichier GPX au premier démarrage
char filepath_gpx[25];          // GPX file name | nom du fichier GPX
char filepath_stats[25];        // Stat file name | nom du fichier de statistiques
char today_folder[15];          // Today folder | Dossier pour les fichiers du jour
char point_date[22];
float prev_lat = NULL;            // No previous point on startup | Aucun point précédent au démarrage
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)
    // Désactive le GPS en appuyant sur le bouton du M5Atom Lite (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();
    }
  }
  // Le module GPS est activé
  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
  // Chemin vers le fichier de données
  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
  // Recharge les statistiques du jour 
  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);

      // Il faut absolument vérifier que le fichier n'est pas vide, sinon plantage assuré !
      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 {
        // Le fichier est probablement corrompu, on le supprime
        ESP_LOGW(TAG,"Stat file removed because probably corrupted. I'll be re-saved next time");
        SD.remove(filepath_stats);
      } 
    }  
  }  

  // Créé le fichier GPX du jour vide s'il n'existe pas  
  if ( !SD.exists(filepath_gpx) ) {
    ESP_LOGI(TAG, "Create new GPX file %s", filepath_gpx);
    // Create GPX file on startup if not exists
    // Créé le fichier GPX s'il n'existe pas
    File gpxFile = SD.open(filepath_gpx, FILE_WRITE);
    // GPX file header
    // Entête du fichier GPX
    if ( gpxFile ) {
      gpxFile.print(F(
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
        "<gpx version=\"1.1\" creator=\"domotique-et-objets-connectes.fr\" 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);
  }  
}

/********************************************/
/* LIT LE MESSAGE RENVOYE PAR LE MODULE GPS */
/********************************************/
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
  // Uniquement si la vitesse > 5 km/h, actualise vites moyenne/max, distance parcourue
  if ( gps.speed.kmph() > 5 ) {
    // Add new speed point in the buffer
    // Ajoute la vitesse au buffer tournant permettant de calculer la vitesse moyenne
    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
    // Estime la distance parcourue depuis la dernière position connue
    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 ) {
        // Stocke la position actuelle pour la prochaine estimation de distance parcourue
        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   */
/* CREE OU ACTUALISE LES STATS   */
/*********************************/
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);
}

Fichiers générés par le programme Arduino du Tracker

Chaque trame GPS étant horodatée, le programme créé automatiquement un dossier à la date du jour dès qu’on démarre le Tracker GPS. Ce dossier contient le fichier tracker.gpx qui est votre parcours et le fichier stats.csv (pour Comma Separated Values).

20201220
  |_ tracker.gpx
  |_ stats.csv

Les données statistiques sont séparées par une virgule : distance parcourue en mètres, distance parcourue en km, vitesse maxi en km/h, vitesse moyenne en km/h. Les données sont organisées sur trois colonnes (clé, valeur, unité).

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

Visualiser un fichier GPX ?

Attention, avant d’extraire la carte microSD du lecteur, n’oubliez pas d’arrêter le GPS en appuyant sur le bouton et en attendant que la LED clignote 2 fois en orange.

Il existe une multitude de site internets qui permettent de tracer des fichiers GPX et de suivre le dénivelé de la randonnée

Si vous utilisez VSCode (Visual Studio Code) pour développer vos projets ESP32 avec PlatformIO, il existe également deux plugins très pratiques (Geo data Viewer et VSCode Map Preview) qui permettent de visualiser des tracés GPX, KML, GeoJSON, CSV… tout cela sans quitter l’éditeur de code. Pratique !

Importer un tracé GPX sur Google Maps

Pour importer un tracé GPX sur Google Maps, vous aurez besoin de disposer d’un compte et de vous y connecter. Allez ensuite sur Google Map et ouvrez le menu latéral pour accéder à vos adresses.

google map gpx vos adresse

Allez sur l’onglet Cartes puis Créer une carte en bas du menu

google map onglet vos cartes google map ajouter carte gpx

Cliquer sur Importer puis glisser le fichier GPX récupéré sur la carte SD.

Google Map supporte également l’importation des formats CSV, XLSX (Microsoft Excel) et KML.

google map importer carte google map deposer carte gpx

Le tracé s’affiche immédiatement après la fin du transfert du fichier GPX sur les serveurs de Google.

google map gpx trace import m5stack M5atom gps

Afficher un fichier GPX directement dans VSCode avec le plugin Geo data Viewer

Si vous n’aimez pas l’idée d’envoyer vos données personnelles sur les serveurs de Google, vous pouvez très bien visualiser vos fichiers GPX directement dans l’éditeur de code VSCode de Microsoft à l’aide du plugin Geo Data Viewer. Ouvrez le menu plugin pour l’installer.

vscode plugin install menu kml

Pour afficher un fichier GPX (ou un autre format supporté), ouvrez le fichier dans l’éditeur puis convoquez le menu avec la combinaison de touches  Ctrl + Alt + M.

Geo Data Viewer ouvre une nouvelle page avec de nombreux outils de visualisation et d’export.

gpx vscode geo data viewer plugin m5atom lite gps

Mises à jour

22/12/2020 Publication de l’article

English version

 

Avez-vous aimé cet article ?
[Total: 1 Moyenne: 4]
Partager sur facebook
Partager sur twitter
Partager sur linkedin
Partager sur pinterest
Partager sur email
Partager sur telegram

Vous avez aimé ce projet ? Ne manquez plus aucun projet en vous abonnant à notre lettre d’information hebdomadaire!

quel modèle esp8266 choisir
Quel modèle d'ESP8266EX choisir en 2020 ?
guide choix esp32 development board
Quel ESP32 choisir en 2020 ?

Vous rencontrez un problème avec ce sujet ?

Peut-être que quelqu’un a déjà trouvé la solution, visitez le forum avant de poser votre question

Nous serions ravis de connaître votre avis

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

Calculateurs
×
Calculateur loi d'Ohm
Tension (U) - en Volt
Courant (I) - en Ampère
Résistance (R) - en Ohms
Puissance (P) - en Watts

Ce calculateur permet de calculer les relations entre le courant, la tension, la résistance et la puissance dans les circuits résistifs.

Saisir au moins deux valeurs puis cliquer sur calculer pour calculer les valeurs restantes. Réinitialisez après chaque calcul.

Rappel sur la Loi d'Ohm
La loi d'Ohm explique la relation entre la tension, le courant et la résistance en déclarant que le courant traversant un conducteur entre deux points est directement proportionnel à la différence de potentiel entre les deux points.
La loi d'Ohm s'écrit U = IR, où U est la différence de tension, I est le courant en ampère et R est la résistance en Ohms (symbole Ω).
Loi d'Ohm (U=RI)
×
Déchiffrer le code couleur d'une résistance à 4 bandes
Bande 1 Bande 2 Multiplicateur Tolérance
   

Résistance:  

1 000 Ω ±5%

Comment déchiffrer le code couleur d'une résistance à 4 anneaux
Formule : ab*cΩ ±d%
Les deux premières bandes (a, b) permettent de déterminer le chiffre significatif. La première bande correspond au chiffre de la dizaine, le second anneau le chiffre de l'unité. Par exemple Brun(1), Noir (0) donne le nombre 10.
La troisième bande (c) est un coefficient multiplicateur. Par exemple, l'anneau rouge est un coefficient multiplicateur de 100, ce qui donne 10 X 100 = 1000Ω.
Le quatrième anneau (d) indique la tolérance de la valeur nominale de la résistance. Par exemple l'anneau Or correspond à ±5%. Donc le fabricant de la résistance s'engage à ce que sa valeur soit comprise entre 950 Ω et 1050 Ω.
Déchiffrer code couleur 4 bandes
×
Déchiffrer le code couleur d'une résistance à 5 bandes
Bande 1 Bande 2 Bande 3 Multiplicateur Tolérance

Résistance:  

1 000 Ω ±5%

Comment déchiffrer le code couleur d'une résistance à 5 anneaux
Formule : abc*dΩ ±e%
Les trois premières bandes permettent de déterminer le chiffre significatif. La première bande correspond au chiffre de la dizaine, le second anneau le chiffre de l'unité. Par exemple Brun(1), Noir (0), Noir (0) donne le nombre 100
La quatrième bande est un coefficient multiplicateur. Par exemple, l'anneau brun correspond au coefficient multiplicateur 10, ce qui donne 100 X 10 = 1000Ω.
Le cinquième anneau indique la tolérance de la valeur nominale de la résistance. Par exemple l'anneau Or correspond à ±5%. Donc le fabricant de la résistance s'engage à ce que la valeur de la résistance soit comprise entre 950 Ω et 1050 Ω.
Déchiffrer code couleur 5 bandes
×
Calculateur de résistance série pour une ou plusieurs LED
Tension d'alimentation en Volt
Tension directe en Volt
Courant en mA
Résistance calculée en Ω
Puissance estimée en W

Ce calculateur permet de déterminer la résistance requise pour piloter une ou plusieurs LED connectées en série à partir d'une source de tension à un niveau de courant spécifié.

Remarque. Il est préférable d'alimenter le circuit avec une puissance nominale comprise entre 2 et 10 fois la valeur calculée afin d'éviter la surchauffe
Couleur Longueur d'onde (nm) Tension (V) pour LED ⌀3mm Tension(V) pour LED ⌀5mm
Rouge 625-630  1,9-2,1 2,1-2,2
Bleu 460-470 3,0-3,2 3,2-3,4
Vert 520-525 2,0-2,2 2,0-2,2
Jaune 585-595 2,0-2,2 3,0-3,2
Blanc 460-470 3,0-3,2 1,9-2,1
Résistance en série pour une ou plusieurs LED
×
Calculateur durée de vie d'une batterie
Capacité de la batterie
Consommation de l'appareil ou objet connecté

Ce calculateur estime la durée de vie d'une batterie, en fonction de sa capacité nominale et du courant ou de la puissance qu'une charge en tire.

La durée de vie de la batterie est une estimation idéalisée. La durée de vie réelle peut varier en fonction de l'état de la batterie, de son âge, de la température, du taux de décharge et d'autres facteurs. C'est le mieux que vous pouvez espérer obtenir.

Autonomie de la batterie = capacité de la batterie en mAh / courant de charge en mA

Durée de vie batterie
Publicité
À lire aussi
Composants
Sur le Forum
Domotique et objets connectés à faire soi-même
Domotique et objets connectés à faire soi-même
Vous avez aimé ce tutoriel

Ne manquez plus les prochains projets

Recevez chaque semaine le récapitulatif des tutoriels et projets.

Vous pouvez vous désabonner à tout moment.