Un WEB server ARDUINO sur Ethernet + graphes HighCharts

Ce topic a éveillé ma curiosité : Afficher un graphique sur une page WEB

Ayant déjà pratiqué HighCharts sur un serveur PYTHON CherryPy, j'ai voulu expérimenter la chose avec une NANO.

Un WEB server ARDUINO sur Ethernet

Un peu galère à écrire mais ça fonctionne plutôt pas mal.

Mise à jour : utilisation de templates HTML avec des tags :

#include <Arduino.h>

const char PROGMEM tempHumTemplate[] = "<script src = \"https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js\"></script>\n"
"<script src=\"https://code.highcharts.com/highcharts.js\"></script>\n"
"<script type=\"text/javascript\">\n"
"jQuery(document).ready(function() {\n"
"  var optionsTemp = {\n"
"    chart: {\n"
"      renderTo: 'containerTemp',\n"
"      type: 'line'\n"
"    },\n"
"    title: {\n"
"      text: 'Temperature'\n"
"    },\n"
"    xAxis: {\n"
"      categories: [$(TEMP_CATEGORIES)]\n"
"    },\n"
"    yAxis: {\n"
"      title: {\n"
"        text: 'Temperature (°C)'\n"
"      }\n"
"    },\n"
"    series: [{\n"
"      name: 'Temperature',\n"
"      data: [$(TEMP_DATA)]\n"
"    }]\n"
"  };\n"
"  var optionsHum = {\n"
"    chart: {\n"
"      renderTo: 'containerHum',\n"
"      type: 'line'\n"
"    },\n"
"    title: {\n"
"      text: 'Humidity'\n"
"    },\n"
"    xAxis: {\n"
"      categories: [$(HUM_CATEGORIES)]\n"
"    },\n"
"    yAxis: {\n"
"      title: {\n"
"        text: 'Humidity (%)'\n"
"      }\n"
"    },\n"
"    series: [{\n"
"      name: 'Humidity',\n"
"      data: [$(HUM_DATA)]\n"
"    }]\n"
"  };\n"
"  var chartTemp = new Highcharts.Chart(optionsTemp);\n"
"  var chartHum = new Highcharts.Chart(optionsHum);\n"
"});</script>\n"
"<div id=\"containerTemp\" style=\"width:100%; height:400px;\"></div>\n"
"<div id=\"containerHum\" style=\"width:100%; height:400px;\"></div>\n";

Prochaine étape : lecture de templates à partir d'une SD.

@+

Je sais, le sujet a été traité 1000 fois sur ce forum et ailleurs.

Je possède cette carte :

L'alimentation se fait en 5V.

Librairie utilisée : Ethernet dernière version.
CS = 10
Il fonctionne sans problème avec une NANO (serveur HTTP).

Le module SDcard :

Sur la photo on ne voit pas les pins DO DI : la sérigraphie SPI se trouve derrière.
L'alimentation se fait en 3.3V.
8 résistances sur le module (adaptation des niveaux je suppose).

Librairie utilisée : SdFat dernière version.
CS = 4

Il fonctionne sans problème avec une NANO et l'exemple QuickStart.

Mais vous l'aurez sans doute deviné: les deux ensemble ne cohabitent pas.

Les deux branchés ensemble :
W5100 fonctionne sans problème.
SDCARD ne fonctionne pas.

La réponse logique est : mettre les CS du W5100 à 1.
Et beaucoup de gens ont résolu leur problème comme ça.
J'ai même tiré le CS à 1 avec un fil : nada

Le symptôme le plus visible : à l'oscilloscope la pin 12 MISO est tirée à ZÉRO par le W5100, quelque soit la position de son CS.
Quand j'essaie d'initialiser la SD La pin MISO ne dépasse pas 800 mV.

Il faut que je débranche le MISO du W5100 pour que la SD soit vue.

Même chose avec une autre carte Ethernet.

IDE 1.8.5

Pendant que j'écrivais ces lignes j'ai trouvé : ;D

Il faut absolument initialiser la carte Ethernet si l'on désire utiliser la SdCard seule.

#include <Ethernet.h>

#define IP_ADDR   "192.168.1.177"
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip;

void setup() {
  Serial.begin(115200);

  ip.fromString(IP_ADDR);
  Ethernet.begin(mac, ip);
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println(F("Ethernet shield was not found.  Sorry, can't run without hardware. :("));
    while (true) {
      delay(1);
    }
  }
  if (!sd.begin(4, SPI_SPEED)) {
    if (sd.card()->errorCode()) {
      Serial.println("SD initialization failed");
    }
  }
  if (sd.exists("template.txt")) {
      Serial.println(F("template.txt found OK"));
  }

Et cela suffit. La pin MISO remonte.
Je pense qu'avec un Ethernet shield avec SD intégrée, on doit avoir le même problème.

Un dernier problème :
La carte SD ne s'initialise pas toujours.

Après raccourcissement des fils du SPI, toujours le même problème.

Solution : un condensateur 10µF entre VCC et GND du module SD.

@+

Certaines µSD donnent de mauvais résultats.
La Kingston SDHC 4Gb que j'utilisais au début produisait des défauts aléatoires : fichier non trouvé, présence de caractères étranges lors de lectures.

Avec une Sandisk Extreme SDHC le résultat est bien plus fiable.

@+

J'ai ajouté différentes possibilités :

  • démo de récupération des arguments d'une URL
  • traiter des requêtes en méthode POST
  • démo formulaire POST
  • conversion des caractères échappés (%20 par exemple) dans l'URL

Un WEB server ARDUINO sur Ethernet

Bonjour et merci à hbachetti d'avoir réouvert le sujet.
J'ai un projet sensiblement identique:
au final je dois avoir 3 graphes:
1 graphe humidité
1 graphe température
1 graphe pour le poids
Ces données sont stockées sur une carte SD car j'ai besoin d'un historique assez long, plusieurs mois.
j'utilise un ESP32 en mode access point et web server.

J'y vais étape par étape, mon capteur DHT22 fonctionne, ma carte SD aussi. et sur un autre sketch je vais avoir la partie Access point.

Il me reste la partie web server que j'aimerais "pomper" sur le projet de hbachetti.
Avant cela, que me conseillez vous concernant la sauvegarde des données? un fichier par variable ? quel format, CSV?
une fois ces questions répondues, je posterai un premier sketch que je ferai évoluer avec votre aide.

Merci pour votre aide

L'idéal serait de sauvé les données dans un format directement lisible par tes graphes.
Comment va tu faire pour afficher les graphes dans ta page HTML?

Salut. Je verrais plutôt un seul fichier, avec 4 champs par ligne : heure et date, humidité, température, poids.
Peu importe le format, il suffit d'écrire un parser qui décompose chaque ligne et range les 4 données dans des variables.

HighCharts fournit des démos : Highcharts Demos and Examples | Highcharts

Clique sur la démo dont le look te convient, et ensuite sur "View Options" pour visualiser le code JS duquel il faudra t'inspirer.

Merci pour ton retour.
Est-ce que je n'aurais pas interer a utiliser un fichier JSON dans ce cas?
j'ai toujours eu du ma avec cela mais il existe des librairies arduino

Aussi, bien sûr. Cela risque d'être plus lourd à décortiquer, mais l'ESP32 est costaud.

Je n'ai pas regardé HighCharts, mais il y a de grande chance qu'il prenne du json.
Comme tu ne compte pas forcément interpréter les donnes avec ton esp32, tu n'a pas besoin de le parser, sauf si tu veux le filtrer sur l'esp.
Et le json n'est pas plus compliqué à faire qu'un csv :grin::grin:
Sur quoi tu pêche ?

ne connaissant rien au JS, je me suis dit que JSON était plus facilement utilisable avec JS.

J'ai finalement opté pour un le standard JSONLines qui est un bon compromis pour moi, facile à lire et pas trop difficile à écrire.
https://jsonlines.org/

Je n'ai pas de module RTC et l'ESP ne sera pas connecté à internet. Donc au lieu de donner à chaque point une date, je lui donne juste un indice, ca me suffit. A voir par la suite comment je peux intégrer une date sans module RTC... ca semble compliqué si a chaque reboot il faut editer le code pour changer la date etc.

Pour le moment j'arrive donc à un JSONL de la sorte:

{"point":1,"temp":23.2,"humidity":45.7,"weight1":0,"weight2":0}
{"point":2,"temp":23.1,"humidity":45.6,"weight1":0,"weight2":0}
{"point":3,"temp":23.1,"humidity":45.5,"weight1":0,"weight2":0}
{"point":4,"temp":23.1,"humidity":45.5,"weight1":0,"weight2":0}
{"point":5,"temp":23.1,"humidity":45.5,"weight1":0,"weight2":0}

Stocké sur la SD /data/data.jsonl

J'ai la partie access point qui fonctionne également mais je ne trouve pas d'exemple pour lequel on a un Access point avec des fichier html/CSS et JS sur une carte SD

Actuellement mon sketch ressemble à ca:

#include "DHT.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <ArduinoJson.h>
#include <WiFi.h>

#define DHTPIN 17     // Digital pin connected to the DHT sensor
#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321

long pointold = 0;
long pointnew = 0;
float weight1 = 0;
float weight2 = 0;
const char* ssid     = "ESP32-Access-Point";
const char* password = "123456789";
WiFiServer server(80);
String header;
DHT dht(DHTPIN, DHTTYPE);

double round2(double value) {
   return (int)(value * 100 + 0.5) / 100.0;
}

void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(9600);
  
  dht.begin();
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Setting AP (Access Point)…");
  // Remove the password parameter, if you want the AP (Access Point) to be open
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
  
  server.begin();
  delay(2000);
  if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }

    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }
 
  File file1 = SD.open("/data/data.jsonl", FILE_READ);
 
  while (true) {
  StaticJsonDocument<2000> doc;

  DeserializationError err = deserializeJson(doc, file1);
  if (err) break;
  pointnew = doc["point"];
  if (pointnew < pointold) {
    break;
  }
  
  pointold = pointnew;
  }
  
  file1.close();
  Serial.println(pointnew);
  
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(2000);
  WiFiClient client = server.available();
  float h = dht.readHumidity();
  float t = dht.readTemperature();

  if (isnan(h) || isnan(t)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  } 

  Serial.print(F("Humidity: "));
  Serial.print(h);
  Serial.print(F("%  Temperature: "));
  Serial.print(t);
  Serial.println(F("°C "));
  pointnew = pointnew +1;
  StaticJsonDocument<2000> doc;
  doc["point"] = pointnew;
  doc["temp"] = round2(t);
  doc["humidity"] = round2(h);
  doc["weight1"] = round2(weight1);
  doc["weight2"] = round2(weight2);

  File file2 = SD.open("/data/data.jsonl", FILE_APPEND);
  serializeJson(doc, file2);
  file2.println();
  file2.close();

}

Je n'ai pas tout lu ton code, mais je pense que tu confond deux choses.
WifiClient c'est pour faire une requête à un serveur HTTP

De ce que j'ai compris de ce que tu veux faire.
ton ESP32 stocke des relever de mesures dans sa carte SD(peu un porte le format).
L'esp32 héberge un serveur HTPP, qui à pour mission de donner une page HTML qui affiche les mesures relevée et stocké dans la carte SD.
Cette page HTML sert à faire un rendu graphique des mesures via Highcharts.
Cette librairie de rendu graphique doit par contre, elle prendre les données dans un format déterminé.
Si tout ceci est correcte, le plus simple est donc de stocker les données sur la carte SD directement dans le format attendu par Highcharts.
Par contre il y a peu d'intérêt, d'un point de vue ressources nécessaires de stocker les données en sérialisant un tableau au format JSON, pour le désérialiser lorsqu'il faut le restituer.
Par contre si on néglige les ressources consommées, si il n'existe pas un équivalent en CSV, pourquoi pas :slight_smile:

Oui c'est exactement ce que je cherche a faire.
Effectivement la ligne Wificlient etait tirée d'un autre exemple que j'ai cherché à détourner sans succès. je vais l'enlever.

Compris pour la partie stockage des infos. je vais me pencher de nouveau dessus lorsque j'aurais réussi à faire de mon ESp32 un serveur HTTP.

Pour le moment je n'ai trouvé que des exemples de WEb serveur, l'ESP se connecte à un WIFI existant fait office de server HTTP
Soit Access Point avec une page HTTP de bienvenue, mais cette page est toujours dans le void loop et non sur la carte SD. Ca parait pas enorme comme différence mais pour le moment je suis incapable de faire cette modification

Alleuia; j'ai reussi à trouver le morceau de code dont j'avais besoin

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SD, "/index.html", "text/html");
  });

  server.serveStatic("/", SD, "/");

Maintenant tout fonctionne jusqu'à l'affichage de la page HTML
il me reste donc la fameuse partie ou j'interface les charts avec mes données

Oui, idéalement il faudrait voir si tu peux donner une URL à ta librairie de charts.
du coup tu aurais juste à faire la même chose que pour la racine :slight_smile:

AsyncWebServer est capable de gérer des templates. Il faut que tu te documentes sur le sujet.

Le principe est le suivant :
Ton fichier HTML contient des balises spéciales : %INDEX% , %TEMP% , %HUM%, etc.
Le template processor remplace les balises par des chaînes que tu fournis à l'aide d'une fonction processor :
Pour %INDEX% : "1,2,3,4,5, etc."
Pour %TEMP% : "25.2, 26.4, 27.5, etc."
Pour %HUM% : "50, 55, 53, 60, etc."

Pour %INDEX% HighCharts peut faire un auto-incrément :

    xAxis: {
        accessibility: {
            rangeDescription: 'Range: 1 to 100'
        }
    },```
100, ou une autre valeur, étant l'index maximal dans ton fichier.

j'ai continué de bosser sur le sujet et je me suis rendu compte d'une évidence un peu stupide: je voulais utiliser Highcharts mais mon ESP32 n'a pas d'accès internet. il faut donc que je mette tout en local. Je ne sais pas si c'est possible mais je ne peux pas faire appel à un template exterieur