diff --git a/BaliseDGAC_GPS_Logger.ino b/BaliseDGAC_GPS_Logger.ino index 7de6cdb..1dde583 100644 --- a/BaliseDGAC_GPS_Logger.ino +++ b/BaliseDGAC_GPS_Logger.ino @@ -1,5 +1,5 @@ // BaliseDGAC_GPS_Logger Balise avec enregistrement de traces -// 26/2/2021 v1 +// 20/03/2021 v2 // Choisir la configuration du logiciel balise dans le fichier fs_options.h // (pin utilisées pour le GPS, option GPS, etc ...) @@ -31,17 +31,22 @@ ------------------------------------------------------------------------------*/ #include +#include #include #include -#include #include #include #include #include "droneID_FR.h" #include "fsBalise.h" #include "fs_options.h" +#include "fs_GPS.h" +#include "fs_filtreGpsAscii.h" extern pref preferences; +extern char statistics[] PROGMEM ; +extern File traceFile; + const char prefixe_ssid[] = "BALISE"; // Enter SSID here // déclaration de la variable qui va contenir le ssid complet = prefixe + MAC adresse char ssid[32]; @@ -101,8 +106,62 @@ IPAddress local_ip; // +++++ FS https://github.com/espressif/arduino-esp32/issue IPAddress gateway; IPAddress subnet; +boolean traceFileOpened = false; +boolean fileSystemFull = false; +boolean immobile = false; +long T0Immobile; +String messAlarm = ""; +unsigned int timeLogError = 0; +unsigned int counter = 0; +unsigned int TRBcounter = 0; +unsigned int nb_sat = 0; +unsigned int SV = 0; +uint64_t gpsSec = 0; +uint64_t beaconSec = 0; +float VMAX = 0.0; +char CLonLat[32]; +char CHomeLonLat[32]; +char Cmd; +float altitude_ref = 0.0 ; +float Lat = 0.0 ; +float Lon = 0.0 ; +float HLng = 0.0; +float HLat = 0.0; +float latLastLog, lngLastLog ; // FS ++++++ pour calcul déplacement depuis le dernier log +static uint64_t gpsMap = 0; +String Messages = "init"; +unsigned long _heures = 0; +unsigned long _minutes = 0; +unsigned long _secondes = 0; +const unsigned int limite_sat = 5; +const float limite_hdop = 2.0; +float _hdop = 0.0; +unsigned int _sat = 0; + +//=========== +#ifdef fs_STAT +// pour statistiques +unsigned long T0Beacon, T0Loop, T0SendPkt, T0Server; +int entreBeaconMin, entreBeaconMax, entreBeaconTotal, entreBeaconSousTotal; +int duree, dureeMinLog, dureeMaxLog, dureeTotaleLog, dureeSousTotalLog; +int segment, segmentMin, segmentMax, segmentTotal, segmentSousTotal; +int periodeLoop, perMinLoop, perMaxLoop, perTotaleLoop, perSousTotalLoop; +int dureeMinSendPkt, dureeMaxSendPkt, dureeTotaleSendPkt, dureeSousTotalSendPkt; +int dureeMinServer, dureeMaxServer, dureeTotaleServer, dureeSousTotalServer; +float entreBeaconMoyenneLocale, dureeMoyenneLocaleLog, segmentMoyenneLocale, perMoyenneLocaleLoop; +float dureeMoyenneLocaleSendPkt, dureeMoyenneLocaleServer; +int n0Failed, n0Passed; +unsigned int countLoop, countSendPkt, countServer, nbrLogError = 0; +#endif +// ou debug +unsigned int countLog = 0; +unsigned long T0Log, T1Log = 0; +char cGPS; +char ringBuffer[500]; +int indexBuffer; +//=========== //buzz(4, 2500, 1000); // buzz sur pin 4 à 2500Hz void buzz(int targetPin, long frequency, long length) { #ifdef ENABLE_BUZZER @@ -123,6 +182,7 @@ void buzz(int targetPin, long frequency, long length) { int led; void flip_Led() { +#ifdef EBABLE_LED //Flip internal LED if (led == HIGH) { digitalWrite(led_pin, led); @@ -131,23 +191,106 @@ void flip_Led() { digitalWrite(led_pin, led); led = HIGH; } +#endif } + + SoftwareSerial softSerial(GPS_RX_PIN, GPS_TX_PIN); TinyGPSPlus gps; #define USE_SERIAL Serial char buff[14][34]; // contient les valeurs envoyées àla page HTML cockpit - -void handleRead() { // pour traiter la requette de mise à jour de la page HTML cockpit +void handleReadValues() { // pour traiter la requette de mise à jour de la page HTML cockpit String mes = ""; for (int i = 0; i <= 13; i++) { if (i != 0) mes += ";"; mes += (String)i + "$" + buff[i] ; } + // ecraser VMAX (case 7) si une alarme. + if (messAlarm != "") mes += ";7$" + messAlarm; server.send(200, "text/plain", mes); - } +#ifdef fs_STAT +void resetStatistics() { + entreBeaconMin = 99999999; entreBeaconMax = 0; entreBeaconTotal = 0; entreBeaconSousTotal = 0; entreBeaconMoyenneLocale = 0.0; + dureeMinLog = 99999999; dureeMaxLog = 0; dureeTotaleLog = 0; dureeSousTotalLog = 0; dureeMoyenneLocaleLog = 0.0; + segmentMin = 99999999; segmentMax = 0; segmentTotal = 0; segmentSousTotal = 0; segmentMoyenneLocale = 0.0; + perMinLoop = 99999999; perMaxLoop = 0; perTotaleLoop = 0; perSousTotalLoop = 0, perMoyenneLocaleLoop = 0.0; + + dureeMinSendPkt = 99999999; dureeMaxSendPkt = 0; dureeTotaleSendPkt = 0; dureeSousTotalSendPkt = 0; dureeMoyenneLocaleSendPkt = 0.0; + dureeMinServer = 99999999; dureeMaxServer = 0; dureeTotaleServer = 0; dureeSousTotalServer = 0; dureeMoyenneLocaleServer = 0.0; + + countLoop = 0; countSendPkt = 0; countServer = 0; + countLog = 0; nbrLogError = 0; TRBcounter = 0; T1Log = 0; T0Beacon = 0; + n0Passed = gps.passedChecksum(); n0Failed = gps.failedChecksum(); + handleStat(); +} +// Calcul pour statistiques min/max etc ... +// Tout est passer par référence !! sauf T0 +void calculerStat (boolean calculerTemps, int T0, int &minimum, int &maximum, float &moyenneLocaleEchantillons, int &totalEchantillons, + int &sousTotalEchantillons, unsigned int &count) { + + // calculerTemps true: T0 représente le temps début de la periode de mesure + // (utile pour stat de timming) + // calculerTemps false: T0 represente la mesure elle même, dont on va calculer min/max etc .. + // (utile pour stat sur longeur de segment) + long T1; + int echantillon; + if (T0 == 0 && calculerTemps) return; // ignorer le premier car T0 est parfois mal initialisé + T1 = millis(); + if (calculerTemps) echantillon = T1 - T0; + else echantillon = T0; + totalEchantillons += echantillon; minimum = min(minimum, echantillon); maximum = max(maximum, echantillon); + sousTotalEchantillons += echantillon; + count++; + if (count % 20 == 0) { + moyenneLocaleEchantillons = float(sousTotalEchantillons) / 20.0; + sousTotalEchantillons = 0; + } +} +// Generation reponse pour la page statistique +void handleReadStatistics() { + char buf[350]; // 260 car env utlisés + int deb = 0; + deb += sprintf(&buf[deb], "0$%s Err GPS:%u
passedCheck:%u failedCheck:%u (%.0f%%);", messAlarm.c_str(), nbrLogError, + gps.passedChecksum() - n0Passed, gps.failedChecksum() - n0Failed , + 100 * float(gps.failedChecksum() - n0Failed) / float(gps.passedChecksum() - n0Passed)); // + deb += sprintf(&buf[deb], "1$Nbr entrées trace: %u ,Nbr Beacon: %u;", countLog, TRBcounter ); + deb += sprintf(&buf[deb], "2$%u;", dureeMinLog); + deb += sprintf(&buf[deb], "3$%u;", dureeMaxLog); + deb += sprintf(&buf[deb], "4$%.1f;", float(dureeTotaleLog) / countLog); + deb += sprintf(&buf[deb], "5$%.1f;", dureeMoyenneLocaleLog); + deb += sprintf(&buf[deb], "6$%u;", segmentMin); + deb += sprintf(&buf[deb], "7$%u;", segmentMax); + deb += sprintf(&buf[deb], "8$%.1f;", float(segmentTotal) / countLog); + deb += sprintf(&buf[deb], "9$%.1f;", segmentMoyenneLocale); + deb += sprintf(&buf[deb], "10$%u;", entreBeaconMin); + deb += sprintf(&buf[deb], "11$%u;", entreBeaconMax); + deb += sprintf(&buf[deb], "12$%.1f;", float(entreBeaconTotal) / TRBcounter); + deb += sprintf(&buf[deb], "13$%.1f;", entreBeaconMoyenneLocale); + deb += sprintf(&buf[deb], "14$%u;", perMinLoop); + deb += sprintf(&buf[deb], "15$%u;", perMaxLoop); + deb += sprintf(&buf[deb], "16$%.1f;", float(perTotaleLoop) / countLoop); + deb += sprintf(&buf[deb], "17$%.1f;", perMoyenneLocaleLoop); + deb += sprintf(&buf[deb], "18$%u;", dureeMinServer); + deb += sprintf(&buf[deb], "19$%u;", dureeMaxServer); + deb += sprintf(&buf[deb], "20$%.1f;", float(dureeTotaleServer) / countServer); + deb += sprintf(&buf[deb], "21$%.1f;", dureeMoyenneLocaleServer); + deb += sprintf(&buf[deb], "22$%u;", dureeMinSendPkt); + deb += sprintf(&buf[deb], "23$%u;", dureeMaxSendPkt); + deb += sprintf(&buf[deb], "24$%.1f;", float(dureeTotaleSendPkt) / countSendPkt); + deb += sprintf(&buf[deb], "25$%.1f", dureeMoyenneLocaleSendPkt); // pas de ; pour la dernière valeur + //Serial.println(deb); + // Serial.print("\t"); Serial.println(buf); + server.send(200, "text/plain", buf); +} +void handleStat() { + sendChunkDebut ( true ); // debut HTML style, avec topMenu + server.sendContent_P(statistics); + server.chunkedResponseFinalize(); +} +#endif // partie spécifique pour statistiques void beginServer() { @@ -161,8 +304,13 @@ void beginServer() Serial.print(F("Opening filesystem littleFS ... ")); Serial.println(LittleFS.begin() ? F("Ready") : F("Failed!")); fs_initServerOn( ); // server.on(xxx Spécifiques au log, gestion fichier,OTA,option +#ifdef fs_STAT + server.on("/stat", handleStat); + server.on("/readStatistics", handleReadStatistics); + server.on("/statReset", resetStatistics); +#endif //+++++++++++++++++++++++++++++++++++++++++++++++++++++ - server.on("/readValues", handleRead); + server.on("/readValues", handleReadValues); Serial.println (F("HTTP server started")); } @@ -192,7 +340,7 @@ void Rate500() } // Send the packet specified to the receiver. -void sendPacket(byte *packet, byte len) +void sendPacket(byte * packet, byte len) { for (byte i = 0; i < len; i++) { @@ -223,14 +371,14 @@ void setup() pinMode(BUZZER_PIN_G, OUTPUT); // set a pin for buzzer output digitalWrite(BUZZER_PIN_G, LOW); pinMode(BUZZER_PIN_P, OUTPUT); // set a pin for buzzer output - -#endif buzz(BUZZER_PIN_P, 2500, 100); +#endif +#ifdef ENABLE_LED //built in blue LED -> change d'état à chaque envoi de trame pinMode(led_pin, OUTPUT); digitalWrite(led_pin, LOW); - +#endif // for (uint8_t t = 4; t > 0; t--) { // Serial.printf("[SETUP] BOOT WAIT %d...\r\n", t); // Serial.flush(); @@ -250,7 +398,7 @@ void setup() temp = String(prefixe_ssid) + "_" + temp; //transfert dans la variable globale ssid temp.toCharArray(ssid, 32); - + Serial.print(F("Setting soft-AP configuration ... ")); Serial.println(WiFi.softAPConfig(local_ip, gateway, subnet) ? F("Ready") : F("Failed!")); Serial.print(F("Setting soft-AP ... ")); @@ -269,73 +417,75 @@ void setup() wifi_softap_set_config(¤t_config); beginServer(); //lancement du server WEB - + #ifdef GPS_STANDARD - softSerial.begin(9600); // ++++++++++ FS : communication à 9600. + fs_initGPS(); + // softSerial.begin(9600); // ++++++++++ FS : communication à 9600. #else - //--------------------------------------------- 57600 ->BAUDRATE 9600 - softSerial.begin(GPS_57600); - delay(100); // Little delay before flushing. - softSerial.flush(); - Serial.println("GPS BAUDRATE 9600"); - BaudRate9600(); - delay(100); // Little delay before flushing. - softSerial.flush(); - softSerial.begin(GPS_9600); - delay(100); // Little delay before flushing. - softSerial.flush(); - //-------Config RATE = 500 ms - Serial.println("Configure GPS RATE = 500 ms"); - Rate500(); - delay(100); // Little delay before flushing. - softSerial.flush(); - //--------Config CHANNELS - Serial.println("Configure GPS CHANNELS = GPS + Galileo + Glonas"); - SelectChannels(); - delay(100); // Little delay before flushing. - softSerial.flush(); + //--------------------------------------------- 57600 ->BAUDRATE 9600 + softSerial.begin(GPS_57600); + delay(100); // Little delay before flushing. + softSerial.flush(); + Serial.println("GPS BAUDRATE 9600"); + BaudRate9600(); + delay(100); // Little delay before flushing. + softSerial.flush(); + softSerial.begin(GPS_9600); + delay(100); // Little delay before flushing. + softSerial.flush(); + //-------Config RATE = 500 ms + Serial.println("Configure GPS RATE = 500 ms"); + Rate500(); + delay(100); // Little delay before flushing. + softSerial.flush(); + //--------Config CHANNELS + Serial.println("Configure GPS CHANNELS = GPS + Galileo + Glonas"); + SelectChannels(); + delay(100); // Little delay before flushing. + softSerial.flush(); #endif drone_idfr.set_drone_id(drone_id); snprintf(buff[0], sizeof(buff[0]), "ID:%s", drone_id); // on aura tout de suite l'info - +#ifdef fs_STAT + resetStatistics(); +#endif + // pour debug points aberrants + for (int i = 0; i < sizeof(ringBuffer); i++ ) ringBuffer[i] = ' '; + indexBuffer = 0; + Serial.println(F("Attente du fix & Co")); delay(1000); } -unsigned int counter = 0; -unsigned int TRBcounter = 0; -unsigned int nb_sat = 0; -unsigned int SV = 0; -uint64_t gpsSec = 0; -uint64_t beaconSec = 0; -float VMAX = 0.0; -char CLonLat[32]; -char CHomeLonLat[32]; -char Cmd; -float altitude_ref = 0.0 ; -float Lat = 0.0 ; -float Lon = 0.0 ; -float HLng = 0.0; -float HLat = 0.0; -static uint64_t gpsMap = 0; -String Messages = "init"; -unsigned long _heures = 0; -unsigned long _minutes = 0; -unsigned long _secondes = 0; -const unsigned int limite_sat = 5; -const float limite_hdop = 2.0; -float _hdop = 0.0; -unsigned int _sat = 0; -bool initialMove = false; // test pour savoir si on s'est déplace de logAfter mètre depis le Fix, pour commerver le log void loop() { + delay(1); +#ifdef fs_STAT + T0Server = millis(); +#endif server.handleClient(); +#ifdef fs_STAT + calculerStat (true, T0Server, dureeMinServer, dureeMaxServer, dureeMoyenneLocaleServer, dureeTotaleServer, + dureeSousTotalServer, countServer); +#endif dnsServer.processNextRequest(); + // Ici on lit les données qui arrivent du GPS et on les passe à la librairie TinyGPS++ pour les traiter - while (softSerial.available()) - gps.encode(softSerial.read()); + while (softSerial.available()) { + cGPS = softSerial.read(); + ringBuffer[indexBuffer++] = cGPS; + indexBuffer %= sizeof(ringBuffer); + // pour filtrer quelques erreurs et avoir une chance de faire un checksum error ..... Q&D !! + if ( cGPS > 0x57 || !asciiFilter[cGPS] ) { + // Serial.print(char(cGPS)); + continue; // ignorer à partir des minuscules a etc ... + } + gps.encode(cGPS); + // gps.encode(softSerial.read()); + } + yield(); // On traite le cas où le GPS a un problème if (millis() > 5000 && gps.charsProcessed() < 10) { Serial.println(F("NO GPS")); @@ -343,11 +493,12 @@ void loop() delay(2000); //pour ne pas encombrer Serial Monitor ... return; } + boolean saveLocationIsUpdated = gps.location.isUpdated(); // On traite le cas si la position GPS n'est pas valide if (!gps.location.isValid()) { if (millis() - gpsMap > 1000) { SV = gps.satellites.value(); - // Serial.print("Waiting... SAT="); Serial.println(SV); + Serial.print("Waiting... SAT="); Serial.println(SV); snprintf(buff[8], sizeof(buff[8]), "ATT SAT %u", SV); strncpy(buff[10], "ATTENTE", sizeof(buff[10])); buzz(BUZZER_PIN_P, 2500, 10);//tick @@ -357,11 +508,15 @@ void loop() } return; - } else if (gps.location.age() > 3000) { - // Serial.println("NO SAT"); + // } else if (gps.location.age() > 3000) { + } else if (gps.location.age() > 5000) { + // Serial.println("NO SAT"); strncpy(buff[8], "NO SAT", sizeof(buff[8])); return; - } else if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.course.isUpdated() && gps.speed.isUpdated()) { + // } else if (gps.location.isUpdated() && gps.altitude.isUpdated() && gps.course.isUpdated() && gps.speed.isUpdated()) { + // FS FS ++++++++++++++++++++++++++++++++ OU à la place de ET + } else if (gps.location.isUpdated() || gps.altitude.isUpdated() || gps.course.isUpdated() || gps.speed.isUpdated()) { // FS +++++++++++ + saveLocationIsUpdated = gps.location.isUpdated(); // On traite le cas où la position GPS est valide. // On renseigne le point de démarrage quand la précision est satisfaisante if ( gps.satellites.value() > nb_sat ) {//+ de satellites @@ -379,6 +534,7 @@ void loop() Serial.println(F("Setting Home Position")); HLat = gps.location.lat(); HLng = gps.location.lng(); + latLastLog = HLat; lngLastLog = HLng; // FS +++++++++++++++++++++++++++++++++++ drone_idfr.set_home_position(HLat, HLng, gps.altitude.meters()); altitude_ref = gps.altitude.meters(); snprintf(buff[11], sizeof(buff[11]), "DLNG:%.4f", HLng); @@ -416,7 +572,7 @@ void loop() snprintf(buff[4], sizeof(buff[4]), "LNG:%.4f", gps.location.lng()); snprintf(buff[5], sizeof(buff[5]), "LAT:%.4f", gps.location.lat()); snprintf(buff[6], sizeof(buff[6]), "ALT:%.2f", gps.altitude.meters() - altitude_ref); - snprintf(buff[7], sizeof(buff[7]), "VMAX(km/h):%.2f", float (VMAX * 3.6)); + snprintf(buff[7], sizeof(buff[7]), "%s VMAX(km/h):%.2f", messAlarm.c_str(), float (VMAX * 3.6)); //************************************************************* @@ -430,6 +586,8 @@ void loop() - uniquement si la position Home est déjà définie, - et dans le cas où les données GPS sont nouvelles. */ + + // ATTENTION: ne pas appeler 2 fois drone_idfr.time_to_send !! if (drone_idfr.has_home_set() && drone_idfr.time_to_send()) { float time_elapsed = (float(millis() - beaconSec) / 1000); beaconSec = millis(); @@ -453,17 +611,22 @@ void loop() /** On envoie la trame */ +#ifdef fs_STAT + T0SendPkt = millis(); +#endif if (wifi_send_pkt_freedom(beaconPacket, to_send, 0) != 0) // 0 sucess, -1 erreur Serial.println(F("--Err emission trame beacon")); +#ifdef fs_STAT + calculerStat (true, T0SendPkt, dureeMinSendPkt, dureeMaxSendPkt, dureeMoyenneLocaleSendPkt, dureeTotaleSendPkt, + dureeSousTotalSendPkt, countSendPkt); + //calcul de stat. Attention la fonction incrmente déjà le compteur + calculerStat (true, T0Beacon, entreBeaconMin, entreBeaconMax, entreBeaconMoyenneLocale, entreBeaconTotal, entreBeaconSousTotal, TRBcounter); + TRBcounter--; + T0Beacon = millis(); +#endif //Flip internal LED flip_Led(); - // log si logOn est true et si on s'est déplacé de plus de logAfter - if (preferences.logOn) { - if (!initialMove) initialMove = ( gps.distanceBetween(gps.location.lat(), gps.location.lng(), HLat, HLng) >= preferences.logAfter); - if (initialMove) fs_logEcrire (gps);; - } - //incrementation compteur de trame de balise envoyé TRBcounter++; snprintf(buff[9], sizeof(buff[9]), "TRAME:%u", TRBcounter); @@ -481,4 +644,77 @@ void loop() */ drone_idfr.set_last_send(); } + + // on regarde si on doit enregistrer un point dans la trace + if (drone_idfr.has_home_set() && !preferences.logNo && (saveLocationIsUpdated || preferences.logAfter < 0) && !fileSystemFull ) { + + float segmentf = distanceSimple(gps.location.lat(), gps.location.lng(), latLastLog, lngLastLog); + // rejet de points abberrants (problème de communication avec le GPS ...) + if ( gps.location.lat() == 0 || gps.location.lng() == 0 || (preferences.logAfter > 0) && segmentf > 30 * preferences.logAfter) { + // point GPS abberrant: enregistrer l'erreur et l'ignorer ... + // N'enregistrer que une fois le message, en début de rafale . + // if ( gps.time.value() != timeLogError) { // on suppose que gps.time.value() n'est pas aussi faux que gps.location()... + // logRingBuffer( ringBuffer, sizeof(ringBuffer), indexBuffer); + // logError(gps, latLastLog, lngLastLog, segmentf); + // timeLogError = gps.time.value(); + // } +#ifdef fs_STAT + nbrLogError++; +#endif + } else { // point GPS OK + // fermer le fichier trace si on ne bouge plus: avion posé ?, on risque la coupure de courant + if (gps.speed.kmph() <= 0.01) { // baddddddddddddddddddddddddd ?????????????????? +++++++++++++++++ + if ( immobile && millis() - T0Immobile > 2000 && preferences.logAfter > 0 && traceFileOpened ) { + //a l'arret depuis plus de 2000s.: fermer le fichier trace. Sauf si en mode test avec preferences.logAfter <= 0) + traceFile.close(); + traceFileOpened = false; + // Serial.println(F("-----------close main")); + } else if (!immobile) { + immobile = true; + T0Immobile = millis(); + } + } else { + immobile = false; + } + // Si preferences.logAfter > 0 :On stocke un point de trace si le nouveau segment et plus long que preferences.logAfter + // Si preferences.logAfter < 0 : pour des tests. On ne tient plus compte de la longeur du segment, mais on stocke + // un point de trace toutes les abs(preferences.logAfter) ms ( il faut au moins 70ms ??) + if (((preferences.logAfter > 0) && (segmentf > preferences.logAfter)) || ((preferences.logAfter < 0) && (millis() - T1Log > abs (preferences.logAfter)))) { + T0Log = millis(); + if ( !fs_ecrireTrace (gps)) { + Serial.println(F("File system full ? ")); + fileSystemFull = true; + messAlarm = "Mémoire pleine ?"; + } else { + // ne mettre a jour les stats que si ecriture est faite. (sinon le disque est plein ..) + fileSystemFull = false; + messAlarm = ""; +#ifdef fs_STAT + calculerStat (true, T0Log, dureeMinLog, dureeMaxLog, dureeMoyenneLocaleLog, dureeTotaleLog, + dureeSousTotalLog, countLog); + countLog--; // même compteur pour durée d'un log et nbr de segment + calculerStat (false, segment, segmentMin, segmentMax, segmentMoyenneLocale, segmentTotal, + segmentSousTotal, countLog); + countLog--; +#endif + countLog++; + if (countLog % 300 == 0) { + // Serial.println(F("-- Flush")); + traceFile.flush(); // forcer la mise a jour tous les 100 points + } + latLastLog = gps.location.lat(); + lngLastLog = gps.location.lng(); + // stats sur une tour complet de la boucle +#ifdef fs_STAT + calculerStat (true, T0Loop, perMinLoop, perMaxLoop, perMoyenneLocaleLoop, perTotaleLoop, + perSousTotalLoop, countLoop); +#endif + } + T1Log = millis(); + } + } + } // test si il faut ecrire un point +#ifdef fs_STAT + T0Loop = millis(); +#endif } diff --git a/README.md b/README.md index f8f8d2f..6b9b337 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,98 @@ + # BaliseDGAC_GPS_Logger -Version d'une balise de signalisation style DGAC pour drone et aéromodélisme avec enregistrement des traces +Version d'une balise de signalisation style DGAC pour drone et aéromodélisme avec enregistrement des traces. +[Voir ici les principales modifications par rapport à la version 1](#principales-modifications-par-rapport-à-la-version-1) # Balise avec enregistrement de traces Cette version de la balise DGAC de signalement électronique à distance pour drone et aéromodélisme est basée sur la version **GPS_Tracker_ESP8266V1_WEB** de "dev-fred" disponible à https://github.com/dev-fred/GPS_Tracker_ESP8266 . -La réalisation a été faite avec un ESP01 (ESP8266) et un GPS QUECTEL L80 ce qui donne une réalisation très compacte, mais bien d'autres possibilités existent avec par exemple un ESP8266 D1, un GSP BN220 etc… +La réalisation a été faite avec un ESP01 (ESP8266) et un GPS QUECTEL L80 ce qui donne une réalisation très compacte, mais bien d'autres possibilités existent avec par exemple un ESP8266 D1, un GSP BN220 etc... +La consommation de cette configuration varie de 80 à 90mA. -## Principales modifications: -- Extension de l'**interface Web** -- Ajout d'une fonction d'**enregistrement des traces** dans le système de fichiers de l'ESP avec interface Web de gestion (effacer / télécharger / choix des champs). +## Principales caractéristiques: +- **interface Web** +- Ajout d'une fonction d'**enregistrement des traces** format **CSV/GPX** dans le système de fichiers de l'ESP avec interface Web de gestion (effacer / télécharger / choix des champs / conditions d'enregistrement). - Modification de l'**identificateur de la balise**: l'adresse MAC est utilisée comme numéro de série. - Ajout d'une fonction de mise à jour du logiciel à travers la liaison WiFI (**OTA** Over The Air) - Ajout d'un **portail captif**: lors de la connexion au réseau créé par la balise le navigateur est lancé et on se retrouve directement dans l'interface utilisateur, sans besoin de donner une adresse IP - Interface utilisateur pour la **gestion des préférences**, la gestion "système" etc … -## Compilation: -- Avant de compiler il faut choisir quelques options dans le fichier fs_option.h(choix des pins IO pour le GPS, choix d'inclure ou non la mise à jour par OTA, vitesse/gestion GPS) -- Le fichier compilé avec option OTA occupe environ 370Kb et si on veut maximiser la place laissée au système de gestion de fichiers on peut choisir dans l'IDE Arduino, pour une module ESP01 un map mémoire FS 256Kb/OTA 375kb -Outil/Type de Carte "Generic ESP8266 Module" Flash Size 1MB(FS:256KB OTA ¨375KB) ce qui permet d'enregistrer plusieurs heures de vols. -Sans OTA on peut choisir un file system de 512KB -- Les librairies LittleFS, DNSServer, EEPROM sont installées en même temps que le SDK ESP8266. - | ![](/img/cockpit_LI.jpg) | ![](/img/traces.png) | | ------------ | ------------ | -Le logiciel efface automatiquement les fichiers les plus anciens quand la place manque dans la mémoire. Le fichier le plus ancien, le plus récent et le plus volumineux sont mis en valeur. -Les points de trace sont enregistrés à la même cadence que l'émission des trames Beacon, dès que le fix GPS est fait, et éventuellement après que la balise se soit déplacée de N mètres(paramétrable). -On peut importer ces fichiers CSV dans Google Maps pour visualiser: -Google Maps/Menu/Vos adresses/Cartes/Créer une carte/Importer -On peut aussi les transformer en fichier GPX, KML etc avec par exemple https://www.gpsvisualizer.com/ -Etc … +Si il y a plus de 4 fichiers de traces, le logiciel efface automatiquement les fichiers les plus anciens quand la place manque dans la mémoire. Le fichier le plus ancien, le plus récent et le plus volumineux sont mis en valeur. +La page "**Péférences**" permet de choisir le format de téléchargement des traces **CSV** et/ou **GPX** et la même trace peut être téléchargée dans les 2 formats. Les traces CSV sont plus faciles à analyser dans Excel par exemple, alors qu'un site comme [GEO JAVAWA](https://www.geo.javawa.nl/trackanalyse/index.php?lang=en) permet une analyse fine, segment par segment de traces GPX. +On peut importer ces fichiers CSV dans Google Maps pour visualisation: + Google Maps/Menu/Vos adresses/Cartes/Créer une carte/Importer +ou les transformer en fichier GPX, KML,... avec par exemple [GPSVisualizer](https://www.gpsvisualizer.com/) -![](/img/preferences.png) +L'enregistrement des points de trace ne se fait que lorsque la balise est en mouvement et est totalement décorrélé de l'émission des trames d'identification. +La page "**Préférences**" permet de choisir la distance minimale qui provoque l'enregistrement, dès que le fix GPS est fait. +La vitesse de transmission du GPS et sa fréquence de rafraichissement sont aussi sélectionnées sur cette page (38400Bds et 10Hz conseillés) + +… -La page" préférences "permet de choisir le contenu de la trace, sa mise en œuvre etc. Les coordonnées latitude/longitude sont toujours enregistrées. +![](/img/preferences.png) + Pour l'aspect WiFi, par défaut le réseau est ouvert (pas de mot de passe) et l'adresse IP est 192.168.4.1 -Le portail captif permet une connexion aisée, sans le besoin de donner l'adresse IP (Fonctionne très bien sous Windows avec Firefox, Chrome, Edge. Est un peu plus capricieux sous Androiïd où il suffit de donner une adresse pouvant être valide comme xx.fr !!) +Le portail captif permet une connexion aisée, sans le besoin de donner l'adresse IP (Fonctionne très bien sous Windows avec Firefox, Chrome, Edge. Est un peu plus capricieux sous Android où il suffit de donner une adresse pouvant être valide comme xx.fr !!) Le bouton ***Reset*** redémarre la balise et ***Reset Usine*** restaure les préférences à leurs valeurs par défaut. ***Format*** réinitialise le système de gestion de fichiers. ***OTA*** permet une mise à jour du logiciel par la liaison WiFi. ![](/img/OTA.png) +## Gestion de la trace +Chaque point de trace occupe 20 octets en mémoire, ce qui permet d'enregistrer environ 12000 points dans une partition filesystem de 256Ko. Avec un modèle volant à 20m/s (soit environ 70km/h) et en choisissant d'enregistrer 1 point pour chaque déplacement de 10m on pourra donc enregistrer environ 1h30 de vol. +Le GPS met à jour ses mesures au maximum à 10hz soit toutes les 0.1s ce qui donne un enregistrement de 20mn seulement dans les mêmes conditions de vitesse si on choisit d'enregistrer la trace tous les 2m. +Pour un modèle volant au dessus de 50km/h (14 m/s) il est illusoire de vouloir enregistrer une trace avec des points distants de 1m ..... + +Si besoin est, pour quelques dizaines de centimes il est possible d'augmenter la mémoire d'un ESP8266 jusqu'à 16mb ;-) + + ## Compilation +- Avant de compiler il faut choisir quelques options dans le fichier fs_option.h(choix des pins IO pour le GPS, choix d'inclure ou non la mise à jour par OTA, vitesse/gestion GPS, génération d'une page [statistiques](#génération-de-statistiques) etc ...) +- Le fichier compilé avec option OTA occupe environ 373Kb et si on veut maximiser la place laissée au système de gestion de fichiers on peut choisir dans l'IDE Arduino, pour une module ESP01 une map mémoire FS 256Kb/OTA 375kb +Avec les options OTA et STA (statistiques) le fichier compilé occupe environ 378Kb et il faut choisir une map mémoire FS 192kb/OTA 406kb +Outil/Type de Carte "Generic ESP8266 Module" Flash Size 1MB(FS:256KB OTA ¨375KB) ce qui permet d'enregistrer plusieurs heures de vols. +Sans OTA on peut choisir un filesystem de 512KB +- Pour un premier chargement du programme il est conseillé d'utiliser l'option Outils/Erase Flash: "All Flash Contents" +- Les librairies LittleFS, DNSServer, EEPROM sont installées en même temps que le SDK ESP8266. +## Modules GPS +Le logiciel a été testé avec un GPS QUECTEL L80 dont la gestion est principalement assurée dans le fichier fs_GPS.cpp. Des GPS qui utilisent les commandes style $PMTK251, $PMTK220, $PMTK314 (cas de Quectel, GlobalTop/Sierra Wireless, ...) peuvent être utilisés directement. Les modules Beitian demandent sûrement une adaptation plus complexe. +Si le GPS a une configuration connue et satisfaisante lors d'un cold start, il suffit de valider les 2 premières lignes dans la fonction fs_initGPS(). On perdra alors la possibilité de choisir dynamiquement la vitesse et la fréquence de rafraichissement. + +## Problèmes connus + + - Il peut arriver (rarement) quue les trames d'identification ne soient plus émises, alors que + le fix GPS est normal: le programme semble attendre (parfois + plusieurs dizaines de secondes) quelque part dans le code des + librairies spécifiques de l'ESP.... A approfondir ... + - La communication entre le GPS et le processeur via SoftwareSerial + n'est pas très robuste et parfois des erreurs de transmissions ne + sont pas détectées, ce qui donne des points GPS aberrants conduisant + à des calculs de longueur de segment parcourus faux et donc à des + points de trace faux. [Voir TinyGPSPlus issue#87](https://github.com/mikalhart/TinyGPSPlus/issues/87) + +## Principales modifications par rapport à la version 1 + + - téléchargement de traces en format CSV et/ou GPX. + - enregistrement de la trace décorrélé de l'émission des trames d'identification. + - enregistrement des points de traces uniquement dicté par le déplacement de la balise. + - choix possible de la vitesse de transmission du GPS et de sa fréquence de mise à jour (34500bds, 10hz recommandés). + - au moins 4 fichiers traces sont gardés en mémoire. + - option pour générer une page de statistiques pour analyser le comportement du logiciel. +## Génération de statistiques +Surtout intéressante dans un phase de développemnt/validation, l'option **fs_STAT** disponible dans le fichier **fs_options.h** permet de générer une page Web supplémentaire et de suivre en temps réel des statistiques sur le fonctionnement de la balise: + - nombre de trames GPS correctes/incorrectes + - nombre de points traces enregistrés + - durée / période d'exécution de certaines parties du logiciel. + - ... + +En phase de tests, il est possible de choisir une longueur de segment maximum négative à partir de la page de **Préférences**. Cette valeur devient alors en fait la période d'enregistrement de la trace: -200 donnera un point de trace toutes les 200ms. + Dans tous les cas le fix GPS doit être validé. Ceci permet d'explorer rapidement "sur la table" des choix vitesse / rafraichissement GPS, de voir l'évolution du taux d'erreurs, des temps d’exécution etc ... # Enjoy ! Les commentaires sont les bienvenus. -#### Idées de développements futurs -- Simplification de l'interface: je ne crois pas que le choix adresse Ip /gateway/sous réseau ait de l'intérêt +#### Idées de développements futurs. + +- nettoyage du code. Limiter l'utilisation de "string". +- revoir la partie condition de génération de la trame d'identification. - Arrêt du webserver /AP après N minutes ?? pour ne pas interférer avec le 2.4G de la télécommande ?? -- Choix du format trace CSV / GPX (GPX plus coûteux en place disque) -- Choix de la fréquence de l'enregistrement de la trace indépendante de l'émission Beacon (périodicité, déplacement max) -- Statistiques sur l'utilisation du système de fichiers littleFS ( durée écriture de la trace …). -- Ne garder que les N derniers fichiers ? (si problèmes de performance en écriture de llitleFS) +- portage sur ESP32 quand la librairie littleFS sera disponible. +- ??? diff --git a/fsBalise.cpp b/fsBalise.cpp index ebaf72b..fcc16e2 100644 --- a/fsBalise.cpp +++ b/fsBalise.cpp @@ -3,325 +3,139 @@ it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program. If not, see . */ /*------------------------------------------------------------------------------ */ #include "fsBalise.h" +#include "fs_GPS.h" #include "fs_options.h" +#include "fs_pagePROGMEM.h" #include #include extern ESP8266WebServer server; -struct pref preferences; -struct pref factoryPrefs; -// ATTENTION: il faut conditionner la compilation du code, sinonle linker -// va tout prendre, même si on veut une version mini sans OTA... - - - -#ifdef fs_OTA -char pageOTA[] PROGMEM = R"=====( -
- - -
-
- -
-
- -)====="; -#endif - -char fs_style[] PROGMEM = R"=====( - - - -Balise - - - - - - -)====="; - -char topMenu[] PROGMEM = R"=====( - -
- - - - - - -
-
-)====="; - -char menuSysteme[] PROGMEM = R"=====( -
- - - - - -
- -)====="; - -char pageOption[] PROGMEM = R"=====( -
-
- Préférences gestion de la trace
- -
-     - mètre(s).
- -
- -
- - -
-
-
Préférences WiFi (attention !!) -
- -
- -
- -
- - -
-
-
- - -)====="; - - -char byeBey[] PROGMEM = R"=====( -
-La balise redémarre. Bye Bye !! -
- - -)====="; -char cockpit[] PROGMEM = R"=====( - -
-

0

-

0

- - - - - - - - - - - - - - - - - - - - - -
000
000
0
ATTENTE
-

- - -)====="; +struct pref preferences; +struct pref factoryPrefs; void sendChunkDebut ( bool avecTopMenu ) { server.chunkedResponseModeStart(200, "text/html"); server.sendContent_P(fs_style); if (avecTopMenu)server.sendContent_P(topMenu); } -void handleCockpit_() { +void handleCockpit() { sendChunkDebut ( true ); // debut HTML style, avec topMenu server.sendContent_P(cockpit); server.chunkedResponseFinalize(); } - - -// Ecriture d'une ligne csv dnas le fichier de log +// Ecriture d'une ligne csv ou GPX dans le fichier de log. +// Attention: le fichier GPX a une trace non fermer. Le compléter lors d'un download // Si le file system est plein, on efface les fichiers les plus anciens. -// Dans ce cas on prda une ligne de log +// Dans ce cas on perdra une ligne de log // Return: true ecriture OK; false sinon // (le file system est surement plein, avec un seul fichier // -char logFileName[25] = ""; // en global , car utilisé aussi dans options. -bool fs_logEcrire(TinyGPSPlus &gps) { - // return; // +++++++++++++++++++++++++++++++++++++++++++++ debug - char timeBuffer[16]; - String logMessage; - if (logFileName[0] == 0) // on cree une seule fois le fichier dans un run - sprintf(logFileName, "/%04u-%02u-%02u_%02u-%02u.csv", gps.date.year(), gps.date.month(), gps.date.day(), gps.time.hour(), gps.time.minute() ); +void logRingBuffer(char ringBuffer[], int sizeBuffer, int indexBuffer) { + // char logFileName[] = "logRingBuf.txt"; + char logFileName[] = "errorlog.txt"; + File fileToAppend = LittleFS.open(logFileName, "a"); + fileToAppend.println("\n====================="); + fileToAppend.printf("%u bauds,%uHz\n", preferences.baud, preferences.hz); + fileToAppend.println(indexBuffer); + fileToAppend.write(ringBuffer + indexBuffer, sizeBuffer - indexBuffer); + fileToAppend.write(ringBuffer, indexBuffer); + fileToAppend.println(); + fileToAppend.close(); + yield(); +} + +// Problème détecté avec la lecture du GPS. On enregistre des infos dans un log d'erreur. +void logError(TinyGPSPlus &gps, float latLastLog, float lngLastLog , float segment) { + char logFileName[] = "errorlog.txt"; bool isFileExist = LittleFS.exists(logFileName); File fileToAppend = LittleFS.open(logFileName, "a"); - if (!fileToAppend) { - Serial.print(F("Error opening the file for appending ")); Serial.println(logFileName); - return removeOldest(); - } - if (!isFileExist) { //Header - logMessage = "Latitude,Longitude"; - if (preferences.logHeure) logMessage += ",Heure"; - if (preferences.logVitesse) logMessage += ",Vitesse"; - if (preferences.logAltitude) logMessage += ",Altitude"; - int longeurEcrite = fileToAppend.println(logMessage); - Serial.print(F(" Création fichier ")); Serial.println(logFileName); - if (longeurEcrite == 0) { - Serial.println(F("Erreur écriture")); - return removeOldest(); // on essaye de gagner de la place + // if (!isFileExist) { //Header + fileToAppend.println("\nlocation.isValid,lat,lng,latLastLog,lngLastLog,segment,speed_km,alt_m," + "sat,hdop,year,month,day,hour,min,sec,centi,failedChecksum,passedChecksum\n"); + // } + fileToAppend.printf("%s", gps.location.isValid() ? "T" : "F"); + fileToAppend.printf(",%.6f,%.6f,%.6f,%.6f", gps.location.lat(), gps.location.lng(), latLastLog, lngLastLog); + fileToAppend.printf(",%.0f,%.2f,%.2f", segment, gps.speed.kmph(), gps.altitude.meters()); + fileToAppend.printf(",%u,%i,%u,%u,%u", gps.satellites.value(), gps.hdop.value(), gps.date.year(), gps.date.month(), gps.date.day()); + fileToAppend.printf(",%u,%u,%u,%u,%u,%u\n", gps.time.hour(), gps.time.minute(), gps.time.second(), gps.time.centisecond(), + gps.failedChecksum(), gps.passedChecksum()); + fileToAppend.close(); +} + +// Ecriture d'une ligne dans le trace file. En général le fichier est deja ouvert en mode append +char traceFileName[25] = ""; // en global , car utilisé aussi dans telechargement. +File traceFile; // en global. Besoin dans le main et pour diwnload +bool fs_ecrireTrace(TinyGPSPlus &gps) { + char timeBuffer[16]; + String logMessage; + int longueurEcriture; + if (!traceFileOpened) { + if (traceFileName[0] == 0) // on cree une seule fois le fichier dans un run + sprintf(traceFileName, "%04u-%02u-%02u_%02u-%02u", gps.date.year(), gps.date.month(), gps.date.day(), + gps.time.hour(), gps.time.minute()); + if (!LittleFS.exists(traceFileName)) { + Serial.print(F("Création du fichier trace ")); Serial.println(traceFileName); + traceFile = LittleFS.open(traceFileName, "a"); + traceFile.close(); // le creer et forcer son enregistrement + } + traceFile = LittleFS.open(traceFileName, "a"); + if (!traceFile) { + Serial.print(F("Error opening the file for appending ")); Serial.println(traceFileName); + traceFile.close(); // just in case ;-) + return removeOldest(); } + traceFileOpened = true; } - - sprintf(timeBuffer, "%02u:%02u:%02u", gps.time.hour(), gps.time.minute(), gps.time.second()); - logMessage = String(gps.location.lat()) + "," + gps.location.lng(); - if (preferences.logHeure) logMessage += "," + String(timeBuffer); - if (preferences.logVitesse) logMessage += "," + String(gps.speed.kmph()); - if (preferences.logAltitude) logMessage += "," + String(gps.altitude.meters()); - int longeurEcrite = fileToAppend.println(logMessage); - if (longeurEcrite == 0) { - Serial.println(F("Erreur écriture")); + // structure servant à stocker en binaire une ligne (un point) du log de trace + trackLigne_t trackLigne;; + trackLigne.lat = gps.location.lat(); + trackLigne.lng = gps.location.lng(); + trackLigne.hour = gps.time.hour(); // Hour (0-23) (u8) + trackLigne.minute = gps.time.minute(); // Minute (0-59) (u8) + trackLigne.second = gps.time.second(); // Second (0-59) (u8) + trackLigne.centisecond = gps.time.centisecond(); // 100ths of a second (0-99) (u8) + trackLigne.altitude = gps.altitude.meters(); + trackLigne.speed = gps.speed.kmph(); + longueurEcriture = traceFile.write((uint8_t *)&trackLigne, sizeof(trackLigne)); + if (longueurEcriture != sizeof(trackLigne)) { + Serial.println(F("Erreur longueur écriture.")); + traceFile.close(); + traceFileOpened = false; return removeOldest(); // on essaye de gagner de la place } - fileToAppend.close(); return true; } // Efface le fichier le plus ancien pour faire de la place -// (sauf si il y a un seul fichier ....) -// Return true sion a fait de la place, false sinon +// (sauf si il y a moins de 4 fichiers ...) +// Return true si on a fait de la place, false sinon bool removeOldest() { - Serial.println(F("Entrée remove oldest")); + int nbrFiles = 0; String oldest = "zzzzz", youngest = ""; Dir dir = LittleFS.openDir("/"); int tailleTotale = 0; while (dir.next()) { String fileName = dir.fileName(); - Serial.println(fileName); + nbrFiles++; if (fileName < oldest ) { oldest = fileName; } @@ -329,15 +143,12 @@ bool removeOldest() { youngest = fileName; } } - Serial.print(F("!")); Serial.print(oldest); Serial.print(F("!")); Serial.println(youngest); - if (oldest != "" && oldest != youngest) { - Serial.print(F("Cleanup. On efface: ")); Serial.println(oldest); - if (LittleFS.remove(oldest)) { - Serial.print(F(" On a efface: ")); Serial.println(oldest); - return true; // on a pu gagner de la place - } - return false; // on a rien pu faire: erreur permanente + if ( nbrFiles > 4) { + messAlarm = ""; // on va faire de la place dans la mémoire (?) .Enlever message alarme + fileSystemFull = false; + return LittleFS.remove(oldest); } + return false ; // on a rien pu faire: erreur permanente ou moins de 4 fichiers } void listSPIFFS(String message) { @@ -345,35 +156,40 @@ void listSPIFFS(String message) { int nbrFiles = 0, longest = 0; String oldest = "zzzzz", youngest = ""; int idOldest = 0, idYoungest = 0, idLongest; - String response = "
"; + String response = ""; // refresh 30 seconds. + response += "
" + message + "
"; server.sendContent(response); Dir dir = LittleFS.openDir("/"); while (dir.next()) { String fileName = dir.fileName(); - //fs: File f = dir.openFile("r"); File f = dir.openFile("r"); if (!f) { - response = "\n"; - response += "\n"; - response += "\n"; + response += "\n"; + response += "\n"; } server.sendContent(response); } @@ -388,32 +204,141 @@ void listSPIFFS(String message) { if (LittleFS.info(fs_info)) { size_t totalBytes; size_t usedBytes; - response += "
Nombre de traces: " + String(nbrFiles) + "
Taille totale:" + String(totalSize); + response += "
Nombre de traces: " + String(nbrFiles) + "
Taille totale: " + String(totalSize); response += "
totalBytes: " + String(fs_info.totalBytes) + "
usedBytes: " + String(fs_info.usedBytes) ; } else { - Serial.println(F("Erreur LittleFS.info")); response += "
Erreurs dans le système de fichier "; } - response += ""; + response += " "; server.sendContent(response); return ; } bool loadFromSPIFFS(String path) { - String dataType = "text/plain"; - File dataFile = LittleFS.open(path.c_str(), "r"); - if (!dataFile) return false; - if (server.streamFile(dataFile, dataType) != dataFile.size()) { - Serial.println(F("Sent less data than expected")); + // Path est du type server.uri(), donc commence par / + // Les fichier de trace commence tous par l'année /2xxx et font l'objet d'un traitement particuliers. + // Un autre fichier peut exister errorlog.txt qui lui sera directement téléchargé. + // Les autre demande sont a tous les coup des "notFound" + // Les autres (fichiers log erreur sont simplement directement téléchargés. + if (path.substring(0, 2) == "/e" ) { + File dataFile = LittleFS.open(path.c_str(), "r"); + if (!dataFile) return false; + server.streamFile(dataFile, "text/plain") ; + dataFile.close(); + return true; + } else if (path.substring(0, 2) != "/2" ) { + return false; + } else { + + // download du fichier "path". On va générer un ficheer .csv ou .gpx suivant les option, + // a partir du log binaire + // structure servant à stocker en binaire une ligne (un point) du log de trace + trackLigne_t trackLigne; + // /yyyy-mm-dd + // 01234678901 + // dateDuFichier va servir à construire le champ date/heure du fichier GPX + String dateDuFichier = path.substring(1, 11); // on oublit le / du debut de l'uri yyyy-mm-dd + // Serial.print("Date | ");Serial.print(dateDuFichier);Serial.println(" | "); + // Si on veut charger le fichier trace en cours, le fermer + //Serial.printf("; % s; % s; \n" , traceFileName, dateDuFichier.c_str()); + String ss = path.substring(1); + if (strcmp(traceFileName, ss.c_str()) == 0) { + Serial.println("Fermeture fichier trace en cours"); + traceFile.close(); + traceFileOpened = false; + } + + File dataFile = LittleFS.open(path.c_str(), "r"); + if (!dataFile) return false; + Serial.print (F("download fichier ")); Serial.println(path); + server.chunkedResponseModeStart(200, "text/plain"); + // creer le header + if (strcmp(preferences.formatTrace, "csv") == 0) { + String logMessage; + logMessage = "Latitude, Longitude"; + if (preferences.logHeure) logMessage += ", Heure"; + if (preferences.logVitesse) logMessage += ", Vitesse"; + if (preferences.logAltitude) logMessage += ", Altitude"; + logMessage += "\n"; + server.sendContent(logMessage); + } + else { + server.sendContent_P(headerGPX); + } + char buf[1024]; + int deb ; + deb = 0; + while (dataFile.available()) { + dataFile.read((uint8_t *)&trackLigne, sizeof(trackLigne)); + if (strcmp(preferences.formatTrace, "csv") == 0) { //generation CSV + // -90.00000,-180.00000,12:30:31,1000.00,99999.99 + // 12345678901234567890123456789012345678901234567890 + // Soit un total de 46 + 2 (cr/lf) + 1 = 49 caractères par point CSV max + if (sizeof(buf) - deb < 51 ) { + server.sendContent((const char*)buf, deb); + deb = 0; + buf[0] = 0; // chaine vide + } + deb += sprintf(&buf[deb], " % .6f, % .6f", trackLigne.lat, trackLigne.lng); // + if (preferences.logHeure) deb += sprintf(&buf[deb], ", % 02u: % 02u: % 02u. % 02u", trackLigne.hour, trackLigne.minute, trackLigne.second, trackLigne.centisecond); + if (preferences.logVitesse) deb += sprintf(&buf[deb], ", % .2f", trackLigne.speed); + if (preferences.logAltitude) deb += sprintf(&buf[deb], ", % .2f", trackLigne.altitude); + deb += sprintf(&buf[deb], "\r\n"); + } + else { // generation GPX + // 99999.40 + // 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 + // 103 + 2(cr/lf) + 1 106 caractères max pour une entrée + if (sizeof(buf) - deb < 108 ) { + server.sendContent((const char*)buf, deb); + deb = 0; + buf[0] = 0; + } + // speed n'existe pas en GPX 1.1: géré par les boutons de sélection du format dans les preferences. + //
" + messAlarm + " " + message + "
" + fileName + " Open error !!<\td><\td>\n"; + response = "
" + fileName + "Open error !! <\td><\td>\n"; } else { totalSize += f.size(); nbrFiles++; - if (fileName < oldest ) { - oldest = fileName; - idOldest = nbrFiles; - } - if (fileName > youngest ) { - youngest = fileName; - idYoungest = nbrFiles; - } - if (f.size() > longest) { - idLongest = nbrFiles; - longest = f.size(); + if (fileName.substring(0, 1) == "2" ) { // ne prendre en compte que les fichier de trace pour oldet/youger + if (fileName < oldest ) { + oldest = fileName; + idOldest = nbrFiles; + } + if (fileName > youngest ) { + youngest = fileName; + idYoungest = nbrFiles; + } + if (f.size() > longest) { + idLongest = nbrFiles; + longest = f.size(); + } } + // on génère explicitement un download="kkjhjk.zzz" car sinon cela ne marche pas bien sur certains systèmes response = "
" + fileName + "" + (String)f.size() + "
+ + + + + + +
+
+)====="; +#else +char topMenu[] PROGMEM = R"=====( + +
+ + + + + + +
+
+)====="; +#endif +char menuSysteme[] PROGMEM = R"=====( +
+ + + + +
+)====="; + +char pageOption[] PROGMEM = R"=====( +
+
+Préférences gestion de la trace
+ +
+ +mètre(s).
+Format de téléchargement + + + +
+ + + +
+ + +
+
+
Configuration GPS +
+ +
+ + +
+
+
Préférences WiFi (attention !!) +
+ + +
+
+
+)====="; + + +char byeBey[] PROGMEM = R"=====( +
+La balise redémarre. Bye Bye !! +
+)====="; + +char cockpit[] PROGMEM = R"=====( + +
+

0

+

0

+ + + + + + + + + + + + + + + + + + + + +
000
000
0
ATTENTE

+)====="; + +#ifdef fs_STAT +char statistics[] PROGMEM = R"=====( + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MinMaxMoyen
géné
Moyen
sur 20
Durée ecr trace0000
Long segment0000
Période Beacon0
Période loop
Durée Server handle/pro
Durée Sendpkt
+
+ + +)====="; +#endif // fin de la page specifique pour statistiques + +char headerGPX[] PROGMEM = R"=====( + + + +)====="; diff --git a/img/preferences.png b/img/preferences.png index fa469f1..ba29996 100644 Binary files a/img/preferences.png and b/img/preferences.png differ diff --git a/img/traces.png b/img/traces.png index 6225a3e..eb28389 100644 Binary files a/img/traces.png and b/img/traces.png differ