Tuto bien fait pour OTA sur esp32

Bonjour à tous ,

Pouvez vous me conseiller un tuto ou site qui explique comment mettre en place le mécanisme de mise à jour OTA sur un esp32S.
Je pense que par OTA, on a plus besoin des bouton boot et reset pour faire la mise a jour ?

Je sais qu'il y a plein de site qui existe, mais vous avez certainement un avis avisé sur un ou deux bien expliqué et avec des exemples.

Merci d'avance

Random Nerd Tutorials fournit des tutos de bonne qualité :

Et une adaptation en Français :

Merci @lesept ,

Je vais regarder ça, j'étais en train de chercher sur le net , du coup je vais pouvoir comparer un peu avec ce que j'ai vu.

Je suis en train de suivre le tuto, mais il n'y a pas OTAWebUpdater dans ArduinoOTA, il n'y a que BasicOTA.

Que dois je faire pour avoir OTAWebUpdater ?

1 Like

En effet, c'est bizarre, le tuto doit être ancien et la bibliothèque a dû être mise à jour depuis. Mais le tuto te donne le code :

/*
 * OTAWebUpdater.ino Example from ArduinoOTA Library
 * Rui Santos 
 * Complete Project Details http://randomnerdtutorials.com
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

WebServer server(80);

/*
 * Login page
 */
const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";
 
/*
 * Server Index Page
 */
 
const char* serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * setup function
 */
void setup(void) {
  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);
}

Sinon, il y a aussi ElegantOTA qui présente une interface pour la mise à jour :

Encore merci @lesept,

Je pense que jusque là je m'en sortais pas mal, mais je viens de m'apercevoir que L'OTA est normalement incompatible avec un deep sleep.

Par contre je viens de tomber sur ce site, mais j'aurais besoin d'aide pour décrypter, car je ne vois pas vraiment comment procéder.

Lien :

Pouvez vous m'aider à le décrypter ?

Je viens de voir également ce fil, dont certain doivent s'en rappeler, mais j'aurais aussi besoin d'un peu d'aide pour décrypter.

Le principe du bouton ne me dérange pas, mais je ne vois pas vraiment comment construire le programme, car la partie deep sleep n'as besoin d'un loop et la partie OTA en a besoin.
Est ce qu'une construction comme ceci est concevable :

if bouton appuyé ; //OTA
void setup(){
......
}
void loop(){
.....
}
}
else{ //deep sleep
void setup(){
.......
}
void loop(){
......
}
}

ça ne me parait pas correct, car je pense que le if va faire planter le compilateur.

Ne peut on pas utiliser des #if ?
Par contre je ne connais pas comment il fonctionnent.

Ha bon, cela gêne en quoi ?
Pour moi, il faut juste laisser le temps avant de repasser en sommeil, de faire la requête si la mise à jour est necessaire.
Par contre effectivement ton programme ne sera pas mis à jour quelque instants après avoir envoyer ton nouveau programme sur le serveur.
Pour garder l'effet autonomie de la mise en sommeil, si tu te reveille souvent, il peut être interressant de faire le test de mise à jour qu'une fois par jour.

C'est à dire, peut tu décrires ce qui te semble confus ?

Oui terwal, mon esp se réveille toutes les minutes, donc je ne veux pas faire attendre le deep sleep à chaque réveil.
Pour le passage en OTA je voudrais que cela ce fasse quand j'en ai besoin et mon ESP est accessible facilement, c'est pour cela que le bouton peut être une solution acceptable.

Pour le site du post #6, apparemment il faut installer une appli supplémentaire et utiliser ESPHome que je ne connais pas, c'est bien ça?

[Treza88] Treza88 https://forum.arduino.cc/u/treza88
May 5

Je viens de voir également ce fil, dont certain doivent s'en rappeler,
mais j'aurais aussi besoin d'un peu d'aide pour décrypter.

Mise à jour OTA sur ESP32 et DeepSleep
<https://forum.arduino.cc/t/mise-a-jour-ota-sur-esp32-et-deepsleep/1093994>
Français <https://forum.arduino.cc/c/international/francais/49>

Bonjour ! J'avance dans mon projet de mini-station météo (merci
encore à terwal et J-L-M qui m'ont bien aidé). Mon module ESP32
déporté alimenté par batterie consommant pas mal, je lui ai
programmé un deep sleep entre les mesures, et cela fonctionne. A
ce sujet : Mon projet utilise une minuscule carte XIAO ESP32-C3 de
chez Seeed qui embarque de quoi charger une batterie, et j'ai eu
l'agréable surprise qu'elle ne consomme qu'environ 40 µA en deep
sleep alimentée sur batterie. Par contre comm…

Le principe du bouton ne me dérange pas, mais je ne vois pas vraiment
comment construire le programme, car la partie deep sleep n'as besoin
d'un loop et la partie OTA en a besoin.
Est ce qu'une construction comme ceci est concevable :

if bouton appuyé ; //OTA void setup(){ ...... } void loop(){ ..... }
} else{ //deep sleep void setup(){ ....... } void loop(){ ...... } } |

ça ne me parait pas correct, car je pense que le if va faire planter
le compilateur.

Ne peut on pas utiliser des #if ?
Par contre je ne connais pas comment il fonctionnent.


Voir le sujet
https://forum.arduino.cc/t/tuto-bien-fait-pour-ota-sur-esp32/1256049/7
ou répondre à cet e-mail pour répondre.

Pour vous désabonner de ces e-mails, cliquez ici
https://forum.arduino.cc/email/unsubscribe/c99a134df8192b2342f008a6df0553d2dc3eafb3ad373fc716b144ddd4590bc2.

Bonsoir !

Voir le post#7 du fil.
Toutes les opérations se font au niveau du setup, ensuite le choix du
deep sleep ou de l'attente pour faire une MàJ OTA se passe dans le loop() :

------------------ void loop() { ArduinoOTA.handle(); // Surveillance
des demandes de mise à jour OTA (au début de loop) if
(digitalRead(OTAPin) == HIGH) Sleep_ESP(); // Contournement de la mise
en sommeil pour maj. OTA flash(); } ------------------ C'est là que je
vérifie l'état de mon petit interrupteur, s'il est sur HIGH je passe en
deep sleep direct (appel de la fonction "Sleep_ESP()"), et s'il est sur
LOW je continue à boucler dans mon loop() en attente du lancement d'une
MàJ OTA. Cordialement, Roland |

Ok Merci @rollmops67 pour le complément d'info, je vois mieux comment procéder pour utiliser un bouton.

Oui, c'est effectivement le plus simple pour toi.

Je n'avais pas lu tout ton message, désolé.
Non ce n'est pas bon.

Tu ne peux pas avoir de code en dehors d'une fonction.

Pour contre effectivement si tu prend une OTA immédiate, cela demande que le ESP soit en permance actif, mais normalement c'est en asynchrone, tu n'a pas forcément besoin de la fonction loop.
Si tu fais par bouton ou en sortie de veille, c'est ton ESP qui va vérifier si il y a une mise à jour de disponnible.

Oui ESPHome est un serveur pour la domotique, en générale, il s'installe sur un petit serveur PC ou rasberry

Pas de soucis @terwal, il m'arrive aussi dés fois de rajouter des phrases en modification,donc c'est peut être de ma faute.

Merci aussi pour les infos et je pense réellement passer par un bouton, il va juste faloir que je prévois un boitier à imprimer en 3d pour tout inclure.

Si cela te convient, c'est une très bonne solution.
En impression 3D, il y a moyen de faire des trucs très sympa.

Oui tout à fait, surtout étant utilisateur de SolidWorks, mais en général, je me limite à quelque chose de fonctionnel.

Une petite question :

La mise à jour ce fait par l'ide ou la page web connecté grâce à l'adresse IP de l'esp ?

Apparemment la page web apres avoir exporter le programme en binaire.
Est ce bien le fichier "Deep_Sleep_capteur_AHT21.ino.bin" que je dois utiliser pour faire la mise à jour ?

Screenshot_34

Car si c'est ça, ça ne fonctionne pas.

Sachant que ce programme est dans l'esp :

/*
 * OTAWebUpdater.ino Example from ArduinoOTA Library
 * Rui Santos 
 * Complete Project Details https://randomnerdtutorials.com
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "**********";
const char* password = "*********";

IPAddress ip(192, 168, 0, 38);
WebServer server(80);

/*
 * Login page
 */
const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";
 
/*
 * Server Index Page
 */
 
const char* serverIndex = 
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * setup function
 */
void setup(void) {
  Serial.begin(115200);
WiFi.config(ip);
  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);
}

J'ai essayé d'exporter les binaires compilé avec l'ide 1.8.., et je n'obtiens qu'un seul fichier, alors qu'avec le 2, j'obtiens un dossier build avec plusieurs fichier dedans.
Est ce que les fichier qui sont dans le répertoire build ne devrait pas être rassemblé dans un unique fichier avec l'extension .bin ?

J'ai réussi à compiler le fichier binaire en utilisant l'ide 1.8.

Mais je voudrais que ça fonctionne avec le 2.3..
Il faut apparemment combiner 4 fichiers bin les trois qui sont dans build et un qui est dans arduino15.
Ces 4 fichiers existent, il reste à les combiner.

Il y a une procédure pour combiner les fichier sur le site indiqué ci dessous, mais je n'ai pas réussi a trouver les emplacements hexadécimaux des fichiers bin.

Si quelqu'un arrive a décrypter car je n'ai pas compris toute la procédure ?

Quand tu compiles tu n'a pas une ligne commencant par "Creating BIN file" dans la sortie de la compilation ?
Moi cela compile dans le répertoire "temp" de mon utilisateur et un UID dédié au projet.

Non tout ce que j'ai en sortie c'est au final :

Le croquis utilise 926417 octets (70%) de l'espace de stockage de programmes. Le maximum est de 1310720 octets.
Les variables globales utilisent 44896 octets (13%) de mémoire dynamique, ce qui laisse 282784 octets pour les variables locales. Le maximum est de 327680 octets.

je suis sur la version 2.3.2

Je n'ai pas de répertoire "temp" dans mon utilisateur, au cas ou j'en ai créé un et recompiler mais rien dans "temp".

Toi en faisant exporter les binaires compilés dans une version 2.3.2 tu as un seul fichier ?