j'ai un ESP32 avec un serveur web (ESPAsyncWebServer) qui enregistre des données dans un tableau (j'en ai causé tantôt, mais le problème n'est plus au même point)
J'aimerai maintenant que dans l'interface web on puisse télécharger les données brutes dans un fichier (CSV, mais je suppose que ça n'est pas le premier problème).
Comment faire envoyer des données (non statiques par nature) par le serveur web ?
Pour les fichiers statiques (pages web) j'ai des instructions de type
Comment le serveur c'est qu'il doit envoyer quelque chose?
En fait tu doit définir qui est serveur, qui est client, pour savoir en faite qui amorce la communication.
J'ai cru comprendre que tu veux dans ton interface web, avoir un bouton qui appelle une URL de ton serveur WEB et que celui-ci te renvois le contenu d'un fichier CSV.
C'est cela ?
Tu veux en faire quoi de ton fichier dans ton UI, uniuqement le sauver sur ton PC?
Exactement : l'ESP32 est le serveur web, il fait l'acquisition des données. Le navigateur est le client, un clic sur un bouton (ou un lien) déclenche le téléchargement des données. Donc dans le sens Serveur --> Client
Dans un premier temps oui.
Dans un deuxième, j'espère pouvoir formater les données correctement pour les donner à un script JS comme https://www.chartjs.org/ pour tracer directement des courbes dans le navigateur (client-side donc)
Bon en farfouillant depuis tout à l'heure je commence à trouver des mots clés peut-être plus pertinents et notamment plein de choses à comprendre à : ESPAsyncWebServer | Async Web Server for ESP8266 and ESP32 mais j'ai du mal à y voir clair...
Un détail que j'avais omis initialement, j’utilise par ailleurs un websocket pour l'interface web mais si j'ai bien compris, de toute façon le WS n'est pas adapté au transfert de données un peu volumineuses.
Si tu veux simplement télécharger, tu peux faire un lien standard en HTML, sans forcément faire de l'ajax.
Dans ce cas là, il faudra surement faire une requête AJAX, pour donner les données au composant charJS.
Bien que je crois que charJS peut directement télécharger les données, mais je ne me rappel plus comment fonctionne le rafraichissement de donnée dynamique.
Par contre ce n'est pas du CSV qui faudra, je crois qu'il utilise du JSon comme c'est souvent le cas en HTML5.
Après faire du JSon, n'est pas vraiment plus compliqué que tu CSV
Je n'ai pas compris ce que tu veux dire ?
Pourquoi ça?
une websocket est une socket normal, hormis la façon d'établir la connexion, qui passe par du HTPP, avant de se convertir en socket standard.
Pourquoi as tu besoin d'une websocket, ton serveur WEB, doit te renvoyer les données en continue.
La websocket est idéeal, pour avertir un client, que quelques chose c'est passé sur le serveur, sans que le client doivent "poller" pour être avertit.
ce n'est pas tout à fait ce que je cherche : manifestement il y a un passage par le FS. Je préférerai ne rien stocker sur la carte et faire l'opération en RAM.
De ce que j'ai lu, c'est assez lent et surtout je souhaite pouvoir passer (idéalement, je n'ai encore rien testé) les données à chartJS. Le format n'est pas le même (c'est du JSON si je ne me trompe pas) et ça va multiplier inutilement les fichiers.
par exemple testez cela (écrit ici donc non testé)
#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
const char* ssid = "xxx";
const char* password = "xxx";
AsyncWebServer server(80);
const char contenu[] PROGMEM = u8R"rawliteral(
<html>
<head>
<meta charset="UTF-8">
<title>Serveur de fichiers ESP32</title>
</head>
<body>
<h1>Bienvenue sur le serveur de fichiers ESP32 !</h1>
<p><a href='/download'>Télécharger le fichier</a></p>
</body>
</html>
)rawliteral";
int toto = 42;
int tutu = 123;
void setup() {
Serial.begin(115200);
// Se connecter au Wi-Fi
WiFi.begin(ssid, password);
Serial.print("Connexion au Wi-Fi en cours...");
while (WiFi.status() != WL_CONNECTED) {
Serial.write('.');
delay(500);
}
Serial.println(" => Connecté");
Serial.print("Joindre le serveur web sur http://");
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", contenu);
});
server.on("/download", HTTP_GET, [](AsyncWebServerRequest * request) {
// Préparer le contenu du fichier
String contenuFichier = "Fichier génré dynamiquement en mémoire";
contenuFichier += "\r\ntoto = "; contenuFichier += toto;
contenuFichier += "\r\ntutu = "; contenuFichier += tutu;
contenuFichier += "\r\n";
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", contenuFichier);
response->addHeader("Content-Disposition", "attachment; filename=fichier.txt");
request->send(response);
// on modifie pour la prochaine fois
toto++;
tutu++;
});
server.begin();
}
void loop() {}
On fabrique ici le contenu du fichier d'abord sous forme de String puis on balance tout d'un coup
C'est OK si le fichier est petit. Si c'est un gros CSV dont on ne connait pas la longueur à priori, il faudra utiliser le mécanisme HTTP1.1 de Chunked Response.
Chuncked est effectivement une des pistes rencontrées pendant mes recherches, ça n'a pas l'air d'être super simple à manipuler mais je vais voir ça.
Deux questions quand-même :
« petit » : quelle est la limite (et à quoi est-elle due ?)
« dont on ne connaît pas la longueur à priori » Je peux la connaître à l'exécution : je compte les acquisition au fur et à mesure mais je ne la connais pas à la compilation. Comment faut-il comprendre « à priori » ?
Dans mon exemple je construis une String en RAM, donc il faut de la RAM dispo. ça dépend donc de ce que fait le reste de votre code et de la mémoire disponible dans le tas au moment de bâtir la réponse.
imaginez que vous vouliez lire 1000 fois la valeur analogique de A0 au moment ou la requête "/download" est reçue et que vous génériez un fichier JSON en ASCII qui dit juste
comme les valeurs lues sur A0 peuvent varier enter 0 et 4095 vous ne savez pas à priori quelle sera la taille de votre réponse (certaines entrées ont juste 1 chiffre comme la valeur 1 par exemple mais d'autres peuvent avoir 2, 3 ou 4 chiffres (10, 100, 1000 par exemple) .
vous pourriez faire les 1000 lectures puis calculer la longueur mais si vous voulez remplir petit à petit la réponse en effectuant les lectures alors là vous ne savez pas à combien d'octets vous allez terminer
Si vous générez un fichier en binaire, c'est plus facile à calculer car chaque entrée tient alors sur 2 octets si vous avez lu la valeur dans un uint16_t par exemple mais ce n'est plus un CSV.
➜ si vous avez déjà les données en mémoire au moment de la requête, oui vous pouvez calculer (c'est un peu laborieux) la taille de la réponse. Mais un chunk sera plus simple à gérer.
sinon au lieu d'utiliser une grosse String, faites un buffer de taille fixe correspond au max attendu et remplissez le avec les fonctions habituelles des cString