[Tuto] Utiliser influxDB avec un Arduino Nano33 IOT

Posté sur: juil. 6, 2022

Catégories: Arduino

Auteur: Fabien

Tags: Influxdb , cloud , api , data , données , arduino , nano , 33 , iot

Dans ce tutoriel nous verrons comment créer une base de données InfluxDB et puis uploader des informations via un Arduino Nano33 IOT.

Créer la base de données

1.1) Inscription sur influxData avec le choix influxDb Cloud

Dans cette première partie de ce tutoriel nous allons tout d’abord nous inscrire et créer la base de données sur le site InfluxData.

Inscrivez- vous en choisissant la méthode d’inscription de votre choix (Google, Microsoft, mail) ou connectez en renseignant vos identifants si vous avez déjà un compte.

1.2) Création du bucket

Afin de créer le bucket il faut cliquer sur la flèche vers le haut puis choisir Buckets.

Une fenêtre de dialogue « Create Bucket » va s'ouvrir. Pour terminer la création donnez un nom au bucket et cliquez sur le bouton « Create ».

1.3) Création de la clef d’API

Nous allons maintenant créer une clé d’API (token d’authentification). Ce token permet à l'Arduino Nano33 de communiquer avec le serveur. Sans cette clé le serveur refuserait toutes interactions.

Démarche à suivre pour créer le token :

  • Rendez vous dans l'onglet “API TOKENS”.
  • Ouvrez le menu déroulant “Generate API Token” puis cliquez sur “All access API Token”.
  • Dans la fenêtre suivante donnez un nom pour la nouvelle clef.
  • Enregistrez la nouvelle clef en cliquant sur le bouton "Save”
  • La clef générée est alors affichée dans la fenêtre suivante.
  • Sauvegardez-la ! Elle sera impérative dans les softs accédants à influxDB

1.4) Test de la configuration à l’aide de Curl en ligne de commande

Après avoir configuré la base de données nous allons communiquer avec celle-ci afin de s’assurer que la configuration est bonne. Voici la ligne de commande à rentrer dans un terminal Windows.

curl -i --request POST "https://europe-west1-1.gcp.cloud2.influxdata.com/api/v2/write?org=tutoriel@letmeknow.fr&bucket=NomDuBucket&precision=ns"   --header "Authorization: Token xxx...xxx" --header "Content-Type: text/plain; charset=utf-8" --header "Accept: application/json" --data--binary "PZEM,sensor_id=idPZEM voltage=230,courant=1,power=230,energy=1,frequence=50,pf=1"

Copiez la partie de code précédente et modifiez les informations suivantes.

  • L’organisation.

Dans notre cas se trouve le champ "org=tutoriel@letmeknow.fr". Il s’agit ici de remplacer l’adresse mail par la vôtre (celle servant au login sur influxData).

  • Le bucket auquel ajouter des données

Dans notre cas on retrouve “bucket=NomDuBucket”. Il faut remplacer le nom par celui que vous avez donné au bucket lors de sa création.

  • Le token généré précédemment.

Il faut ici remplacer dans le champ “Authorization : Token xxx…xxx” la partie “xxx...xxx” par la clef d’API générée précédemment.

  • Les data à transmettre 

Exemple : PZEM,sensor_id=idPZEM voltage=230,courant=1,power=230,energy=1,frequence=50,pf=1

  • PZEM  ⇒ un nom arbitraire d’identification du type de mesure.
  • sensor_id ⇒ un nom arbitraire permettant d’identifier le groupe de mesure suivant.
  • “voltage=230”, “courant=1”, “power=230” sont les paires “clé=valeur” qui seront remontées et stockées dans la base de données.

Après l’envoi de cette commande, si tout est bien configuré et que les commandes sont complètes alors le serveur enverra un code 204 sans erreur. Sinon il transmettra un message d ‘erreur.

        Requête OK

Erreur d'authentification – clef d’API HS

1.5) visualisation des datas

  1. Ouvrez les buckets disponible et en particulier celui-ci sur lequel vous faites remonter les données.
  2. Choisissez le bucket utilisé (défini par son nom).
  3. Choisissez le type de mesure (measurement).
  4. Sélectionnez une ou plusieurs valeurs (voltage, courant, power…)
  5. Sélectionnez l’id, ici “sensor_id”.
  6. Enfin appuyez sur RUN.

    Si tout fonctionne correctement les valeurs remontées sont affichées dans la table.

                               

                                                  

    2) Communication entre l’arduino et influxDB

    Dans cette deuxième partie nous allons voir comment communiquer avec l’Arduino Nano 33 IOT.

    2.1) Initialisation

    Dans cette première partie nous allons inclure les librairies et initialiser des valeurs qui serviront à interagir avec la base de données :

    • Le nom du wifi (wifiSsid).
    • Le mot de passe du wifi (wifiPass).
    • Le token (token).
    • Le nom du bucket (bucket).
    • L’organisation (org).

    Il faut donc remplacer les valeurs par défaut par vos valeurs à vous.

      
     #include "Arduino.h"
    #include "SPI.h"
     #include "WiFiNINA.h"
     #include "WiFiUdp.h"
     #include "wiring_private.h"
     #include "PZEM004Tv30.h"
     #define DEBUG
     //#define USENTP //recup heure par NTP

     //wifi
     const char wifiSsid[] = "NomDuWifi";
     const char wifiPass[] = "MotDePasseDuWifi";
     String macAdresse = "";
     //ntp udp
     const unsigned int localPort = 2390;
     IPAddress ntpServer(37,187,104,44); // serveur ntp fr
     const int NTP_PACKET_SIZE = 48;
     byte packetBuffer[ NTP_PACKET_SIZE]; //buffer ntp
     WiFiUDP Udp;

     //influx data
     const char influxServer[]="europe-west1-1.gcp.cloud2.influxdata.com";
     const String influxPath="/api/v2/write";
     const String influxBucket="nomDuBucket";
     const String influxOrg="tutoriel@letmeknow.fr";
     const String influxPrecision="ns";
     const String influxToken="xxx...xxx";
     const String sensorName="PZEM";
     const String sensorId="idPZEM";

     //PZEM
     Uart mySerial (&sercom0, 5, 6, SERCOM_RX_PAD_1, UART_TX_PAD_0);
     boolean firstLoop = true;
     const uint32_t delayMesure = 10000;
     uint32_t lastMesure = 0;
     boolean send_NAN=true; // envoi sur influx même si mesures invalides
     WiFiSSLClient client;

     void setup() {
      #ifdef DEBUG
      Serial.begin(115200);
      #endif
      pinPeripheral(5, PIO_SERCOM_ALT);
      pinPeripheral(6, PIO_SERCOM_ALT);
      initWifi();
      Udp.begin(localPort);
     }

    2.2) Uploader des valeurs

    Pour gérer les valeurs et uploader sur le serveur il est nécessaire de copier à la suite le code suivant. Pour les ’envoyer au bon format il est nécessaire de suivre ces étapes :

    • Récupérer les données.
    • Formater les données.
    • Envoyer sur le serveur les données en wifi.

    Les valeurs récupérées dans notre cas sont des valeurs fournies par l’objet “pzem”.

    Afin de les encoder nous devons les sauvegarder dans une chaîne de caractères vierge.

    Attention : Une fois la base de données créée nous n’avons plus besoin de communiquer avec le format “clef=valeur”. Il suffit d’envoyer les valeurs en les séparant d’une virgule. Cependant il faut bien respecter l’ordre sinon les données seront incohérentes.

    Pour ce faire nous utilisons la fonctions suivante

    String();

    Si aucune donnée n’est disponible alors nous donnerons la valeur par défaut “NAN”.

    Nous pouvons ajouter plusieurs valeurs à la suite dans la chaîne de caractères (stringValue) en les séparant d’une virgule.

    void loop() {  
    if(firstLoop) {
    PZEM004Tv30 pzem(&mySerial);
    #ifdef DEBUG
    Serial.print("Current address:");
    Serial.println(pzem.getAddress());
    Serial.println();
    #endif
    firstLoop = false;
    }else{
    if (millis()-lastMesure>delayMesure){
    String vPZEM=getPzemValue();
    lastMesure=millis();
    if (vPZEM.length()>0){
    sendInfluxData(vPZEM);
    }
    }
    }
    }
    //###########################################################

    // WIFI
    //###########################################################
    void initWifi(){
    if (WiFi.status() == WL_NO_MODULE) {
    #ifdef DEBUG
    Serial.println("Module wifi KO !!!");
    #endif
    return;
    }else{
    #ifdef DEBUG
    Serial.println("Connexion au module wifi OK");
    #endif
    }
    String fv = WiFi.firmwareVersion();
    #ifdef DEBUG
    Serial.println("Version wifi: "+fv+" dernière version: "+WIFI_FIRMWARE_LATEST_VERSION);
    #endif
    if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    #ifdef DEBUG
    Serial.println("Mettre à jour le firmware Wifi !!!");
    #endif
    }
    if (!connectWifi()){
    #ifdef DEBUG
    Serial.println("Connexion au Wifi impossible !!!");
    #endif
    }else{
    #ifdef DEBUG
    Serial.println("Connexion au wifi OK");
    #endif
    IPAddress IP = WiFi.localIP();
    #ifdef DEBUG
    Serial.print("IP=");
    Serial.println(IP);
    #endif
    }
    }
    boolean connectWifi(){
    uint8_t nb=0;
    uint8_t status;
    #ifdef DEBUG
    Serial.println("Connexion au wifi");
    #endif
    while (status != WL_CONNECTED) {
    delay(200);
    #ifdef DEBUG
    Serial.print(".");
    #endif
    status = WiFi.begin(wifiSsid, wifiPass);
    nb++;
    if (nb>29){
    return false;
    break;
    }
    }
    #ifdef DEBUG
    Serial.println("");
    #endif
    byte mac[6];
    WiFi.macAddress(mac);
    char buf[1];
    for (int i=0;i<6;i++){
    itoa(mac[i],buf,16);
    macAdresse=macAdresse+String(buf);
    if (i<5) macAdresse=macAdresse+":";
    }
    macAdresse.toUpperCase();
    #ifdef DEBUG
    Serial.println(macAdresse);
    #endif
    return true;
    }
    //#################################################################
    // NTP
    //#################################################################
    void sendNTPpacket(IPAddress& address) {
    memset(packetBuffer, 0, NTP_PACKET_SIZE);
    packetBuffer[0] = 0b11100011;
    packetBuffer[1] = 0;
    packetBuffer[2] = 6;
    packetBuffer[3] = 0xEC;
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;
    Udp.beginPacket(address, 123); //port ntp udp = 123
    Udp.write(packetBuffer, NTP_PACKET_SIZE);
    Udp.endPacket();
    }
    unsigned long decodeNtp(){
    Udp.read(packetBuffer, NTP_PACKET_SIZE);
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    //timestamp depuis 1900
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    //timestamp Unix depuis 1970
    const unsigned long since70 = 2208988800UL;
    unsigned long epoch = secsSince1900 - since70;
    return epoch;
    }
    String getUnixTime(){
    sendNTPpacket(ntpServer);
    delay(1000);
    String ut="";
    if (Udp.parsePacket()){
    ut=String(decodeNtp());
    }else{
    ut="0";
    }
    return ut;
    }
    //##################################################################
    // INFLUXDB
    //##################################################################
    void sendInfluxData(String pzemValues){
    #ifdef DEBUG
    Serial.println("sendInfluxData");
    #endif
    //commande curl
    //curl -i --request POST "https://europe-west1-1.gcp.cloud2.influxdata.com/api/v2/write?org=tutoriel@letmeknow.fr&bucket=NomDuBucket&precision=ns" --header "Authorization: Token xxx...xxx" --header "Content-Type: text/plain; charset=utf-8" --header "Accept: application/json" --data-binary "PZEM,sensor_id=idPZEM voltage=230,courant=1,power=230,energy=1,frequence=50,pf=1 1656486702000000000"
    if (client.connectSSL(influxServer, 443)) {
    #ifdef DEBUG
    Serial.println("connexion ok");
    #endif
    const String cmdPost="POST "+influxPath+"?org="+influxOrg+"&bucket="+influxBucket+"&precision="+influxPrecision+" HTTP/1.1";
    client.println(cmdPost);
    client.println("Host: "+String(influxServer));
    client.println("Authorization: Token "+influxToken);
    String ts="";
    #ifdef USENTP
    ts=" "+String(getUnixTime())+"000000000";
    #ifdef DEBUG
    Serial.println(ts);
    #endif
    #endif
    //valeurs pour test
    //String datas="PZEM,sensor_id=idPZEM
    String datas=sensorName+",sensor_id="+sensorId+" "+pzemValues+ts;
    int cl = datas.length();
    #ifdef DEBUG
    Serial.println(datas);
    Serial.println("cl="+String(cl));
    #endif
    client.println("Connection: close");
    client.println("Content-Type: text/plain; charset=utf-8");
    client.println("Accept: application/json");
    client.println("Content-Length: "+String(cl));
    client.println();
    client.println(datas);
    getInfluxResponse();
    client.stop();
    }else{
    #ifdef DEBUG
    Serial.println("connexion ko");
    #endif
    }
    }
    void getInfluxResponse(){
    int secu=0;
    #ifdef DEBUG
    Serial.println("attente reponse");
    #endif
    while (!client.available()){
    #ifdef DEBUG
    Serial.print(".");
    #endif
    secu++;
    if (secu>100){
    #ifdef DEBUG
    Serial.println();
    Serial.println("pas de reponse du serveur");
    #endif
    return;
    }
    delay(100);
    }
    #ifdef DEBUG
    Serial.println();
    #endif
    while (client.available()) {
    char c = client.read();
    #ifdef DEBUG
    Serial.write(c);
    #endif
    }
    }
    //##########################################################
    //PZEM
    //##########################################################
    void SERCOM0_Handler()
    {
    mySerial.IrqHandler();
    }
    String getPzemValue(){
    String V="";
    PZEM004Tv30 pzem(&mySerial);
    //voltage
    float voltage = pzem.voltage();
    if(!isnan(voltage)){
    #ifdef DEBUG
    Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
    #endif
    V="voltage="+String(voltage)+",";
    }else{
    #ifdef DEBUG
    Serial.println("Error reading voltage");
    #endif
    V="voltage=-1,";
    }
    //courant
    float current = pzem.current();
    if(!isnan(current)){
    #ifdef DEBUG
    Serial.print("Current: "); Serial.print(current); Serial.println("A");
    #endif
    V=V+"courant="+String(current)+",";
    }else{
    #ifdef DEBUG
    Serial.println("Error reading current");
    #endif
    V=V+"courant=-1,";
    }
    //puissance
    float power = pzem.power();
    if(!isnan(power)){
    #ifdef DEBUG
    Serial.print("Power: "); Serial.print(power); Serial.println("W");
    #endif
    V=V+"power="+String(power)+",";
    }else{
    #ifdef DEBUG
    Serial.println("Error reading power");
    #endif
    V=V+"power=-1,";
    }
    //energy
    float energy = pzem.energy();
    if(!isnan(energy)){
    #ifdef DEBUG
    Serial.print("Energy: "); Serial.print(energy,3); Serial.println("kWh");
    #endif
    V=V+"energy="+String(energy)+",";
    }else{
    #ifdef DEBUG
    Serial.println("Error reading energy");
    #endif
    V=V+"energy=-1,";
    }
    //frequence
    float frequency = pzem.frequency();
    if(!isnan(frequency)){
    #ifdef DEBUG
    Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
    #endif
    V=V+"frequence="+String(frequency)+",";
    }else{
    #ifdef DEBUG
    Serial.println("Error reading frequency");
    #endif
    V=V+"frequence=-1,";
    }
    //mystere !
    float pf = pzem.pf();
    if(!isnan(pf)){
    #ifdef DEBUG
    Serial.print("PF: "); Serial.println(pf);
    #endif
    V=V+"pf="+String(pf);
    }else{
    #ifdef DEBUG
    Serial.println("Error reading power factor");
    #endif
    V=V+"pf=-1";
    }
    if (send_NAN){
    return V;
    }else{
    if (V.indexOf("=-1")>-1){
    return "";
    }else{
    return V;
    }
    } }

    laissez un commentaire

    Se connecter