Aquaboun's /// gestion d'aquarium recifal

quel est le type de donneeLueSurSD[i]? vous allez lire des octets, donc idéalement ce sera un uint8_t

ensuite il faut rebâtir la structure pour mettre les octets dans le bon ordre pour les grouper pour qu'ils correspondent au type de donnée d'origine. par exemple pour imprimer en ASCII correctement horodatage, il faudra regrouper les 4 octets associés.

bien que ce ne soit pas officiellement dans la norme, vous pouvez déclarer une union:

struct __attribute__ ((packed)) stockDansSD_t {// force l'ordre des champs
  uint32_t horodatage;
  float mesuresT;
  uint16_t mesuresPh;
}; 

union reprensationBinaire_t {
  stockDansSD_t enregistrement;
  uint8_t lesOctetsAssocies[sizeof(stockDansSD_t)];
};

extern reprensationBinaire_t stockMesuresDansSD;

vous pouvez lire un bloc de sizeof(stockDansSD_t)de la carte SD dans stockMesuresDansSD.lesOctetsAssocies[index]et ensuite imprimer ce que vous avez lu en utilisant par exemple stockMesuresDansSD.enregistrement.horodatage

(c'est pas super clean car le spec C/C++ dit que c'est un comportement indéfini que d'écrire sous une forme de l'union et d'y accéder par une autre en lecture mais ça fonctionne avec GCC et la majorité des compilateurs, surtout si vous avez pris la précaution du __attribute__ ((packed)) ).

dans un premier temps la seul réponse qui me vient c’est :fearful: :sob: puis le smiley “balle dans la tête”

bidouille et compagnie plus tard :

#include <SPI.h>
#include "SdFat.h"
SdFat SD;

#define SD_CS_PIN SS
File myFile;
int i = 0;

struct __attribute__ ((packed)) stockDansSD_t {// force l'ordre des champs
  uint32_t horodatage;
  float mesuresT;
  uint16_t mesuresPh;
};
union reprensationBinaire_t {
  stockDansSD_t enregistrement;
  uint8_t lesOctetsAssocies[sizeof(stockDansSD_t)];
};
reprensationBinaire_t stockMesuresDansSD;

void setup() {
  Serial.begin(115200);// Open serial communications and wait for port to open:
  while (!Serial) {// wait for serial port to connect. Needed for native USB port only
  }
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS_PIN)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  myFile = SD.open("mesuresBinairePourGraph.txt"); // on ouvre le fichier
  if (myFile) {
    while (myFile.available()) {// read from the file until there's nothing else in it:
      if (i < sizeof(stockDansSD_t)) {
        stockMesuresDansSD.lesOctetsAssocies[i] = myFile.read(); // lit le fichier
        i++;
      }
      else {
        i = 0;
        Serial.print("horodatage : "); Serial.println(stockMesuresDansSD.enregistrement.horodatage);
        Serial.print("mesuresT : "); Serial.println(stockMesuresDansSD.enregistrement.mesuresT);
        Serial.print("mesuresPh : "); Serial.println(stockMesuresDansSD.enregistrement.mesuresPh);
      }
    }
    Serial.print("taille : ");    // if the file didn't open, print an error:
    Serial.println(sizeof(stockDansSD_t));    // if the file didn't open, print an error:
    myFile.close();// close the file:
  } else {
    Serial.println("error opening ");    // if the file didn't open, print an error:
  }
}

void loop() {
  // nothing happens after setup
}
Initializing SD card...initialization done.
horodatage : 1559809657
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809667
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809677
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809687
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809697
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809707
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809717
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809727
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809737
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809747
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809757
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809767
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809777
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809787
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809797
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809807
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809817
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809827
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809836
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809846
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809856
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809866
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809876
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809886
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809896
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809906
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809916
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809926
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809936
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809946
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809956
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809966
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809976
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809986
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559809996
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559810006
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559810016
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559810026
mesuresT : 26.50
mesuresPh : 801
horodatage : 1559810036
mesuresT : 26.50
mesuresPh : 801

Si tu a un endroit en Français ou il explique union
parsque

  stockDansSD_t enregistrement;

stockDansSD_t est le TYPE et enregistrement le nom

 uint8_t lesOctetsAssocies[sizeof(stockDansSD_t)];

le on declare autemps de uint8_t que la taille du TYPE stockDansSD_t

mais je comprend pas le tour de pass pass :

stockMesuresDansSD.enregistrement.mesuresPh

et avec ca :

       uint8_t  hours = stockMesuresDansSD.enregistrement.horodatage / 3600 % 24;
        uint8_t  minutes = stockMesuresDansSD.enregistrement.horodatage / 60 % 60;
        uint8_t  seconds = stockMesuresDansSD.enregistrement.horodatage % 60;
        DateTime dateDesMesures = stockMesuresDansSD.enregistrement.horodatage;
        Serial.print("date : ");  Serial.print(dateDesMesures.day()); Serial.print("/"); Serial.print(dateDesMesures.month()); Serial.print("/"); Serial.print(dateDesMesures.year());
        Serial.print(" a "); Serial.print(hours); Serial.print("h"); Serial.print(minutes); Serial.print(" et "); Serial.print(seconds); Serial.println(" secondes");
        Serial.print("mesuresT : "); Serial.println(stockMesuresDansSD.enregistrement.mesuresT);
        Serial.print("mesuresPh : "); Serial.println(stockMesuresDansSD.enregistrement.mesuresPh);

j'ai :

date : 6/6/2019 a 8h27 et 37 secondes
mesuresT : 26.50
mesuresPh : 801
date : 6/6/2019 a 8h27 et 47 secondes
mesuresT : 26.50
mesuresPh : 801
date : 6/6/2019 a 8h27 et 57 secondes
mesuresT : 26.50
mesuresPh : 801

j'ai mérité un gros dodo :) a+

Bravo :)

Voici un lien pour les unions

En gros Les unions sont un autre type de structure. La différence entre les structures et les unions est que les différents champs d'une union occupent le même espace mémoire. On ne peut donc, à tout instant, n'utiliser qu'un des champs de l'union.

Donc le tour de passe passe comme vous dites c’est d’avoir dans l’union une structure comme vous aviez avant mais aussi un buffer d’octets qui a exactement la même taille que la structure. comme ça quand vous remplissez le buffer d’octets, ça remplit aussi la structure puisque l’espace mémoire est le même. donc quand ensuite vous appelez un des éléments de la structure, le compilateur connaît son type, donc sait combien d’octets lire et c’est magique :)

Là où c’est un peu limite c’est que la spec C ou C++ ne garantit le bin fonctionnement que si vous stockez et relisez avec le même attribut de l’union - or là on stocke avec le tableau d’octets et on relit avec la structure... GCC ne met pas le souc et au final les octets sont bien rangés au bin endroit mais un compilateur un peu plus malin ou sur une autre architecture pourrait décider de réorganiser les champs ou d’insérer des octets vides pour aligner des adresses mémoire dur 32 bits ou faire d’autres modifs qui rendraient la lecture par octets incompatibles avec l’adressage par la structure

Merci pour l'exemple c'est un (petit) peu plus claire Pour te remercier j'ai mis ton Karma en binaire :)

Donc maintenant il faut que j'analyse la date/heure lu pour voir si il y a bien 15 min entre chaque, si il n'y a pas eu de "trou" ect … je devrais y arriver :) si tout va bien le prochain message sera avec le graphique :) :) :)

Merci Je vous ai sorti du binaire en échange :)

Pour analyser la date et calculer des différences puisque vous utilisez si je le souviens bien une DS3231 et la librairie RTCLib vous pouvez créer un objet DateTime à partir de l’heure unix et ensuite faire des operation de soustraction entre deux dates pour obtenir un objet de Time TimeSpan que vous pouvez interroger sur le nombre de minutes etc

J-M-L: Merci Je vous ai sorti du binaire en échange :)

Pour analyser la date et calculer des différences puisque vous utilisez si je le souviens bien une DS3231 et la librairie RTCLib vous pouvez créer un objet DateTime à partir de l’heure unix et ensuite faire des operation de soustraction entre deux dates pour obtenir un objet de Time TimeSpan que vous pouvez interroger sur le nombre de minutes etc

oui c'est se que je pensait faire. faudra que je creuse timespan pour comprendre.

J'ai inclus la lecture et écriture binaire dans l'aquabouns et en voulant commencer l'analyse de la date puis le graphique je me suis aperçu d'un "problème" l'écriture sur SD se fait a la suite, du coup, la dernière prise de mesure est a la fin. Quand on lit, on lit du début a la fin donc la première lecture est la mesure la plus ancienne. Quand on dessine le graphique, on envoie une donné qui est décalé d'un cran vers la droite par la deuxième et ainsi de suite (on le vois bien sur la vidéo que j'ai mis avant) donc tout serait inversé. Et d'après se que j'ai chercher, il n'y a pas de lecture a l'envers. Il n'est pas non plus possible d'écrire au début du fichier en décalant la suite. Mais il y a peut être encore un truc au font du chapeau ?

Encore une chose qui me taraude, Quand on lit Sd, on lit par vague d'octets correspondant a la taille de la structure. Mais je me dit, a aucun moment on contrôle que l'on est bien en train de lire calqué sur la vague. imaginons que pendant l'écriture en binaire sur SD il y a un problème (coupure électrique ou autre) on va se retrouvé a lire complètement en décalé. Je ne sait pas si j'ai été claire.

Si si on peut lire le fichier « à l’envers » en utilisant seek() pour se positionner au bon octet

En pratique si tout se passe bien la taille T de votre fichier est un multiple de la taille d’un enregistrement S et N = T/S sera le nombre d'enregistrements Dans le fichier. Si vous voulez vous positionner au début de l’enregistrement index (commençant à zéro) il suffit de faire un seek à la position index x S

Un seek à 0 vous met au début du fichier, un seek à la position T vous met en fin de fichier prêt à rajouter quelque chose si le fichier est aussi ouvert en écriture.

Donc pour lire à l’envers il faut calculer N et pour lire le dernier enregistrement vous faites un seek vers (N-1)*S, puis vous lisez les octets et envoyez à l’écran, ensuite vous faites un seek vers (N-2)*S, puis vous lisez les octets et envoyez à l’écran, ensuite vous faites un seek vers (N-3)*S, puis vous lisez les octets et envoyez à l’écran etc... je pense que vous voyez quel boucle/indice utiliser pour lire les 672 derniers enregistrements :)

arfff pffff j'en est marre de pas y penser lol

et comment être sur que l'on est bien positionner a la lecture bien en face de l'écriture ?

Faut faire confiance aux maths :) Sinon vous pouvez mettre un mot magique en début de structure genre un uint32_t qui vaut 0xDEADBEEF et donc si vous n’êtes pas positionné sur un « bœuf mort » quand vous lisez vous savez que vous avez un soucis dans le code.. bon ça prend un peu de place sur la carte SD mais vu que maintenant elles font des gigas c’est pas un souci

j’ai mis tout ca en forme dans l’aquabouns puis travailler le graph ca donne ca :

pour vérifier l’enregistrement j’ai repris le meme que pour l’eeprom

const uint32_t motClef = 0xBEEFDEAD;

pour ecrire en binaire :

/* ecrit sur la carte SD en binnaire les donner pour le graphique */
void stockMesuresEnBinaire() {
  DateTime now = rtc.now(); // recupere la date et heure
  stockMesuresDansSD.enregistrement.horodatage = now.unixtime(); // stock date et heure en binaire
  stockMesuresDansSD.enregistrement.clef = motClef ; // clef pour controler que l'on lit bien en face
  stockMesuresDansSD.enregistrement.mesuresT = random (2600, 2650); // copie la temperature du bac
  stockMesuresDansSD.enregistrement.mesuresPh = random (780, 820); // copie le Ph du bac
  //DPRINTF("stockMesuresDansSD.horodatage : "); DPRINTLN(stockMesuresDansSD.enregistrement.horodatage);
  // DPRINTF("stockMesuresDansSD.mesuresT : "); DPRINTLN(stockMesuresDansSD.enregistrement.mesuresT);
  //DPRINTF("stockMesuresDansSD.mesuresPh : "); DPRINTLN(stockMesuresDansSD.enregistrement.mesuresPh);
  myFile = SD.open(mesuresEnBinaire, O_WRITE | O_CREAT | O_AT_END); // ouvre le fichier
  DPRINTF("Ouverture du fichier : "); DPRINTLN(mesuresEnBinaire); // debug
  if (myFile) {
    myFile.write(&stockMesuresDansSD, sizeof(stockMesuresDansSD)); // ecrit dans le fichier
    DPRINTF("Ecrit les mesures sur la carte SD"); DPRINTLN(); // debug
  } else {
    DPRINTF("Erreur d'écriture dans le fichier "); DPRINTLN(); // debug
  }
  myFile.close(); // ferme le fichier
}

vous remarquerez que les donner sont générer aléatoirement par un random pour les test

puis pour lire / analyser :

/* lit sur la carte SD les données binnaire pour le graphique */
void lisMesuresEnBinaire() {
  boolean dateDerniereMesure = false;
  uint16_t nbr2MesuresPourLeGraphique = (576 + (variable.Heure * 4) + (variable.minut / 15)) ; // 96 pixels = 1 jours /// 6 jours = 576 pixel + 4 pixels par heure du jour J + 1 pixel toute les 15 min du jour J
  myFile = SD.open(mesuresEnBinaire, O_READ); // ouvre le fichier
  if (myFile) { // fichier ouvert
    uint8_t i = 0; // index pour lire
    uint8_t indexErreur = 0; // index pour decaler la lecture si erreur/clef non conforme
    uint16_t indexMesure = 1; // index des mesures
    uint16_t nbr2MesureMax = (myFile.fileSize() / sizeof(stockDansSD_t)) + indexMesure; // stock le nombre de mesure maximum qu'il y a sur la carte sd
    uint16_t quinzeMinutes = 900; // en secondes
    DateTime now = rtc.now(); // recupere la date et heure actuel
    uint32_t tempsEntreLesMesures = now.unixtime();
    DPRINTF("taille du fichier mesuresEnBinaire : "); DPRINTLN(myFile.fileSize()); // debug
    while (indexMesure < nbr2MesuresPourLeGraphique) { // on boucle autent de fois qu'il faut de pixel pour le graphique
      if (indexMesure >= nbr2MesureMax) {
        graphique(zero, zero); // lance la fonction
        indexMesure++;
        DPRINTF("pas assez de mesures a lire"); DPRINTF("indexmesure : "); DPRINT(indexMesure); DPRINTF(" nbr2MesureMax : "); DPRINT(nbr2MesureMax); DPRINTLN(); // debug
      }
      else {
        DPRINTF("emplacement : "); DPRINTLN((myFile.fileSize() - (indexErreur + (sizeof(stockDansSD_t)*indexMesure)))); // debug
        myFile.seek(myFile.fileSize() - (indexErreur + (sizeof(stockDansSD_t)*indexMesure))); // on se place dans le fichier
        while (i < sizeof(stockDansSD_t)) { // on lit une vague correspondant a la taille de stockDansSD_t
          stockMesuresDansSD.lesOctetsAssocies[i] = myFile.read(); // lit le fichier
          i++; // incremente l'index
        }
        i = zero; // remet l'index a zero pour la prochaine lecture
        DPRINTF("clef : "); DPRINTLN(stockMesuresDansSD.enregistrement.clef); // debug
        if (stockMesuresDansSD.enregistrement.clef == motClef) { // si la clef lu correspond a la clef de controle
          if (dateDerniereMesure == false) { // pour lire la date une fois
            DateTime dateDesMesures = stockMesuresDansSD.enregistrement.horodatage; // convertie l'unixtime en date
            DPRINTF("date : ");  DPRINT(dateDesMesures.day()); DPRINTF("/"); DPRINT(dateDesMesures.month()); DPRINTF("/"); DPRINTLN(dateDesMesures.year()); // debug
            dateDerniereMesure = true; // pour lire la date une fois
          }
          if (tempsEntreLesMesures - quinzeMinutes < stockMesuresDansSD.enregistrement.horodatage) {
            graphique(stockMesuresDansSD.enregistrement.mesuresT, stockMesuresDansSD.enregistrement.mesuresPh); // lance la fonction
            DPRINTF("heure : "); DPRINT(stockMesuresDansSD.enregistrement.horodatage / 3600 % 24); DPRINTF("h"); DPRINT(stockMesuresDansSD.enregistrement.horodatage / 60 % 60); // debug
            DPRINTF(" et "); DPRINT(stockMesuresDansSD.enregistrement.horodatage % 60); DPRINTF(" secondes"); // debug
            DPRINTF("mesuresT : "); DPRINT(stockMesuresDansSD.enregistrement.mesuresT); // debug
            DPRINTF(" et mesuresPh : "); DPRINTLN(stockMesuresDansSD.enregistrement.mesuresPh); // debug
            indexMesure++; // increment l'index des mesures
          }
          else {
            graphique(zero, zero); // lance la fonction
            nbr2MesuresPourLeGraphique--; // on decremente le nbr de mesure pour le graph puisque on a dessiner un pixel sans incrementer l'index
            DPRINTF("trou dans la chronologie des mesures"); DPRINTLN(); // debug
          }
          tempsEntreLesMesures = tempsEntreLesMesures - quinzeMinutes;
          DPRINTF("tempsEntreLesMesures : "); DPRINT(tempsEntreLesMesures); DPRINTLN(); // debug
          DPRINTF("stockMesuresDansSD.enregistrement.horodatage : "); DPRINT(stockMesuresDansSD.enregistrement.horodatage); DPRINTLN(); // debug
        }
        else {
          DPRINTLN("erreur dans la lecture binaire des mesures"); // debug
          indexErreur++; // increment l'index des erreurs
        }
      }
      wdt_reset(); // indique que le loop est OK, pas de bug, remise a zero du compteur "reboot" du watchdog car la lecture de toute les mesure pourait depasser le timer du watchdog
    }
    DPRINTF("taille : "); DPRINTLN(sizeof(stockDansSD_t)); // debug
    myFile.close(); // ferme le fichier
  }
  else { // erreur a l'ouverture du fichier
    DPRINTLN("Erreur a l'ouverture du fichier mesuresEnBinaire"); // debug
  }
}

détecte les vides dans l’écriture ecris une mesure nul puis décale de 15 min ect …
décale d’un octect la lecture si on tombe pas sur le motClef

sa fait 24h que ca tourne, ca a l’air ok.

Très bien :)

Notez que maintenant que vous avez compris pour les octets vous n’avez pas vraiment besoin de l’union pour lire les octets de la structure les uns après les autres, la fonction read() sait fait comme la fonction write() et supporte la formefile.read(buf, len) donc vous pouvez lui passer l’adresse de votre buffer et sa taille et ça lit tout d’un coup :)

oula :/ sa voudrait dire faire un truc comme sa :

stockDansSD_t = myFile.read((myFile.fileSize() - (indexErreur + (sizeof(stockDansSD_t)*indexMesure))),sizeof(stockDansSD_t));

Euh presque - un truc du genre :)

Il faut faire un seek au bon endroit et ensuite read(adresse_buffer, nombre_octets)

facile mdr

jme penche dessus ;)

J’ai rajouté un peu d’info

c'était pas facile ton histoire ;D

Cc,

très très pris par le taf en se moment …

:confused:

je continu les évolutions quand je peux et je me suis pencher sur le problème des module PH analogique, peu fiable a moyen / long therme. j'en est pourtant testé de diffèrent model plus ou moins chère avec le même résultat au bout de quelque semaine / mois, des dérives importante.

j'ai donc commander un module EZO PH de chez atlas. les premier test son très concluant ! une précision incomparable ! ces module fonction via port série ou i2c. mes test on été faire en port série car j'ai lu qu'en i2c il fallait mettre un filtre pour avoir des donné "propre" j'ai testé avec la librairie SoftwareSerial mais les pin utilisable sont limité et beaucoup de personne dise que ces la pire des librairie de port série virtuel apres recherhce j'en est trouvé 2 autres, mieux "noté" : AltSoftSerial NeoSWSerial

quelle serait la meilleur alternative ?

ps : autre avantage des module ezo de chez Atlas, sur le même format il ont un module REDOX et CONDUCTIVITE. l'idée est de prévoir 3 emplacements pour module et de laisser libre chois avant téléversement sur les module utilisé, exemple : 1 PH bac, 1 PH RAC , 1 REDOOX ou 1 PH bac, 1 REDOX, 1 CONDUCTIVITE et bien sur les 3 mesures des modules visualisable sur un graphique :)

Vous n’avez plus de port série matériel sur le MEGA?

J-M-L: Vous n’avez plus de port série matériel sur le MEGA?

et non puisque j'ai l'écran, le sim800l et le d1mini