Mise à jour OTA sur ESP32 et DeepSleep

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 comme je le craignais, j'ai maintenant un problème avec l'OTA qui fonctionnait très bien avant que je ne programme le deep sleep. (L'IDE Arduino me propose toujours le port réseau pour uploader)
Vu que la carte est ON pendant juste 2 à 3 secondes, le temps de se connecter, d'envoyer sa requête, et de repartir en deep sleep, ça ne marche plus même si pour l'instant je n'ai mis que 15 secondes de sommeil, et que la carte se réveille plusieurs fois pendant que l'IDE Arduino envoie l'invitation à l'adresse IP de la carte après la compilation.
Bien sûr je peux toujours reprogrammer la carte en mode boot en USB.

Je vous mets le programme, au cas où...
Pour info, j'ai aussi essayé en plaçant les deux lignes qui sont dans loop() à la fin du setup, mais cela n'a rien changé.

/* Station de mesure de température et d'hygrométrie avec envoi des données par un HHTP_GET sur un serveur
 * et une mise à jour par OTA, autour d'un Seeed XIAO ESP32C3 */

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoOTA.h>                    // Librairie permettant la mise à jour OTA du croquis par le Wifi
#include <ESPmDNS.h>                       // Librairie nécessaire pour le fonctionnement de la précédente
#include <WiFiUdp.h>                       // Aussi nécessaire pour le fonctionnement de ArduinoOTA.h
#include "DHT.h"                           // Bibliothèque pour sondes DHT

#define DHTTYPE DHT11                      // Type de capteur DHT utilisé
#define uS_TO_S_FACTOR 1000000             // Conversion des µsecondes en secondes
#define TIME_TO_SLEEP  15                  // Time ESP32 will go to sleep (in seconds)
 
const char* ssid = "xxxx";
const char* mdp = "yyyy";
const int LedPin = 20;                     // GPIO de la led de signalisation
const int TimeOut_connexion =40;           // Nb d'essais de connexion au Wifi
int timeOut_counter =0;                    // Compteur de nombre d'essais de connexion.
bool Blink = false;
unsigned long prevBlinkMillis;
float Temp;
int Hum;
uint32_t Vbat;
float Vbatf;
float Vstop = 3.4;                         // Tension à laquelle la carte se met en deep sleep
 
uint8_t DHTPin = 7;                        // GPIO attribué au capteur DHT
DHT dht(DHTPin, DHTTYPE);                  // Initialisation du capteur

WiFiClient client;

void setup() 
{
  setCpuFrequencyMhz(80);                  // réglage de la fréquence de la CPU à 80 Mhz, en dessous le WiFi ne marche pas

  pinMode(A1, INPUT);                      // Positionne le GPIO de l'entrée analogique A1 en entrée
  pinMode(LedPin, OUTPUT);                 // Positionne le GPIO de la led en sortie
  pinMode(DHTPin, INPUT);                  // Positionne le GPIO de la sonde en entrée

  dht.begin();

  vbat();                                  // Mise en deep sleep si batterie trop faible
 
  WiFi.begin(ssid, mdp);                   // Etablissement de la connexion WiFi
  while (WiFi.status() != WL_CONNECTED)    // Boucle de vérification de l'établissement de la liaison
   {
    delay(250);
    timeOut_counter++;                     // redémarrage de l'ESP si pas de connexion pendant 10 sec
    if (timeOut_counter > TimeOut_connexion) ESP.restart();
   }
   
  initOTA();                               // Renvoie à la routine de mise à jour du croquis par le Wifi (OTA)
 
  envoi();                                 // Renvoie vers la fonction d'envoi des données par HTTP_GET
  for(int i = 0; i < 5; i++)               // clignotement led avant d'aller en deep sleep
  {Blink =!Blink; digitalWrite(LedPin, Blink); delay(100);}
}

void loop()
{
  ArduinoOTA.handle();                                     // Surveillance des demandes de mise à jour OTA (au début de loop)
  Sleep_ESP();
}
 
void envoi()
{
 Temp = dht.readTemperature()-0.8;        // Lecture de la température avec correction
 Hum = dht.readHumidity();                // Lecture de l'humidité
 vbat();                                  // vers la routine de mesure de Vbat

 String mon_url = "http://192.168.1.152/get?data=" + String(Vbatf) + "-" + String(Hum) + "-" + String(Temp);
 HTTPClient http;
 http.begin(mon_url);
 int httpResponseCode = http.GET();
 http.end(); 
}

void vbat()                                // Routine de mesure de la tension de la batterie
{
  Vbat = 0;
  for(int i = 0; i < 10; i++)              // 10 boucles de mesure pour moyenner la valeur
  {                                        // Lecture des millivolts présents sur l'entrée, la commande
    delay(20);                             // "analogReadMilliVolts" utilise une table de correction intégrée à la puce
    Vbat += analogReadMilliVolts(A1);      // Délai de 20 msec entre mesures et addition des valeurs
  }
  Vbatf = Vbat *2 /10/977.0;               // calcul de la tension en volts, le x2 à cause du pont diviseur par 2
                                           // le 977 devrait être en théorie 1000, mais tolérances des résistances  
  if (Vbatf<Vstop) esp_deep_sleep_start(); // met l'ESP en sommeil profond si la batterie descend sous Vstop
}

void   Sleep_ESP()
{
  WiFi.disconnect(true);
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  esp_deep_sleep_start();
}

/********************* Partie du programme qui permet la mise à jour du sketch au travers du WiFi *****************
  * 
  * Port defaults to 3232
  * ArduinoOTA.setPort(3232);
  *
  * Hostname defaults to esp3232-[MAC]
  * ArduinoOTA.setHostname("myesp32");
  *
  * No authentication by default
  * ArduinoOTA.setPassword("admin");
  *
  * Password can be set with it's md5 value as well
  * MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  * ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
  */
void initOTA() 
{
  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println();
  Serial.println("Maj OTA paramétrée !");
//  Serial.print("IP address: ");
//  Serial.println(WiFi.localIP());
}

Si jamais quelqu'un a une idée, merci !

Roland

Bonsoir @rollmops67

Pas évident de combiner OTA et deepsleep !

L'intérêt du deepsleep est d'activer le moins possible l'ESP32 pour augmenter l'autonomie , c'est antagonique avec la disponibilté nécessaire de l'ESP32 , connecté en WiFi, pour réagir correctement à une demande de mise à jour par OTA

suggestion 1 (facile à faire) ajouter un interrupteur sur un GPIO pour autoriser ou pas le passage en deepsleep. Une position pour le fonctionnement avec OTA, une pour le fonctionnement avec deepsleep.

suggestion 2 : (facile à dire ...) lors de la connection au serveur pour envoyer les données en profiter (par get ?) pour lire l'état d'une demande de mise à jour déposée au prélable sur ce serveur (comment ?) En sa présence ne pas activer le deepsleep tant que la mise à jour par OTA n'est pas terminée

@al1fch Si tu as un site web, plutôt que de faire une demande d'ota. Tu peux simplement mettre le firmware sur ce serveur non ?

@terwal

oui , il me semble que c'est possible , je n'en sais pas plus n'ayant jamais pratiqué (exemple HTTPupdate sans doute...)

@al1fich :

Citation
suggestion 1 (facile à faire) ajouter un interrupteur sur un GPIO pour autoriser ou pas le passage en deepsleep. Une position pour le fonctionnement avec OTA, une pour le fonctionnement avec deepsleep.

Bonjour !
C'est effectivement ce que j'ai fait, rajouter un interrupteur, pas la solution la plus noble, mais quand même la plus rapide.
On ne va pas non plus se masturber le cerveau des heures juste pour ça !

J'ai juste modifié mon loop() comme ceci :

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();
}

En rajoutant une fonction flash() qui me signale que je suis prêt à faire une maj OTA en faisant clignoter une led...

Parfois les solutions les plus simples sont les meilleures ! :wink:

Roland

hey au top,

j'avais commencé une station météo sous 33IOT mais point de vue autonomie c'est vraiment pas satisfaisant alors je suis passé sous la Xiao aussi, j'ai fini une partie de mes soudures aujourd'hui!
J'espère pouvoir m'inspirer de ton code pour la partie deep-sleep!

Sinon, désolé, pas de bonne idée magique pour rendre compatible le deep sleep avec le OTA...

Le petit interrupteur comme cité dans le post 5 ça le fait très bien !
Sinon pas facile de lancer l'OTA sur un ESP qui est en marche 3 secondes et dort 3 minutes !
Pour l'autonomie en effet pas d'autre solution que le deep sleep, le XIAO consomme environ 60 mA en marche (avec la fréquence baissée à 80 MHz au lieu de 160 MHz), à part mettre une batterie de voiture pour l'autonomie je ne vois pas.
Sinon pour mesurer la tension de la batterie, j'ai trouvé sur cette page (au 2/3 environ) comment on peut faire, et ça marche :

Là ça fait 24 h environ que le mien tourne, et la tension de la batterie (2450 mAh) est passée de 4,17 à 4,14 V, c'est prometteur !

J'ai encore fait de petites modifs aujourd'hui (interrupteur et programmation d'un mois de deep sleep si la batterie est déchargée et que je ne suis pas là pour la charger), voici le code modifié, en fait le deep sleep avec timer c'est très facile à programmer !

/* Station de mesure de température et d'hygrométrie avec envoi des données par un HHTP_GET sur un serveur
 * et une mise à jour par OTA, autour d'un Seeed XIAO ESP32C3 */

#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoOTA.h>                    // Librairie permettant la mise à jour OTA du croquis par le Wifi
#include <ESPmDNS.h>                       // Librairie nécessaire pour le fonctionnement de la précédente
#include <WiFiUdp.h>                       // Aussi nécessaire pour le fonctionnement de ArduinoOTA.h
#include "DHT.h"                           // Bibliothèque pour sondes DHT

#define DHTTYPE DHT11                      // Type de capteur DHT utilisé
#define uS_TO_S_FACTOR 1000000ULL          // Conversion des µs en s, ULL signifie que le nombre est unsigned long long (64 bits)
#define TIME_TO_SLEEP 180                  // Durée du sommeil entre mesures (en secondes)
const char* ssid = "xxxx";
const char* mdp = "yyyy";
const int LedPin = 20;                     // GPIO de la led de signalisation
const int OTAPin = 5;                      // GPIO de l'interrupteur empêchant le deep sleep pour la maj OTA
const int TimeOut_connexion =40;           // Nb d'essais de connexion au Wifi
int timeOut_counter =0;                    // Compteur de nombre d'essais de connexion.
bool Blink = false;
unsigned long prevBlinkMillis;
const int cycle = 1500;                    // Intervalle entre flashs de la led
const int temps_flash = 15;                // Durée du flash de la led
float Temp;
int Hum;
uint32_t Vbat;
float Vbatf;
float Vstop = 3.4;                         // Tension à laquelle la carte se met en deep sleep
 
uint8_t DHTPin = 7;                        // GPIO attribué au capteur DHT
DHT dht(DHTPin, DHTTYPE);                  // Initialisation du capteur

WiFiClient client;

void setup() 
{
  setCpuFrequencyMhz(80);                  // réglage de la fréquence de la CPU à 80 Mhz, en dessous le WiFi ne marche pas

  pinMode(A1, INPUT);                      // Positionne le GPIO de l'entrée analogique A1 en entrée
  pinMode(LedPin, OUTPUT);                 // Positionne le GPIO de la led en sortie
  pinMode(DHTPin, INPUT);                  // Positionne le GPIO de la sonde en entrée
  pinMode(OTAPin, INPUT_PULLUP);                  // Positionne le GPIO de la sonde en entrée
 

  dht.begin();

  vbat();                                  // Mise en deep sleep si batterie trop faible
 
  WiFi.begin(ssid, mdp);                   // Etablissement de la connexion WiFi
  while (WiFi.status() != WL_CONNECTED)    // Boucle de vérification de l'établissement de la liaison
   {
    delay(250);
    timeOut_counter++;                     // redémarrage de l'ESP si pas de connexion pendant 10 sec
    if (timeOut_counter > TimeOut_connexion) ESP.restart();
   }
   
  initOTA();                               // Renvoie à la routine de mise à jour du croquis par le Wifi (OTA)
 
  envoi();
  for(int i = 0; i < 5; i++)               // clignotement led avant d'aller en deep sleep
  {Blink =!Blink; digitalWrite(LedPin, Blink); delay(100);}

}

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();
}
 
void envoi()
{
 Temp = dht.readTemperature()-0.8;        // Lecture de la température avec correction
 Hum = dht.readHumidity();                // Lecture de l'humidité
 vbat();                                  // vers la routine de mesure de Vbat

 String mon_url = "http://192.168.1.152/get?v=" +String(Vbatf)+ "&h=" +String(Hum)+ "&t=" +String(Temp);
 HTTPClient http;
 http.begin(mon_url);
 int httpResponseCode = http.GET();
 http.end(); 
}

void vbat()                                // Routine de mesure de la tension de la batterie
{
  Vbat = 0;
  for(int i = 0; i < 10; i++)              // 10 boucles de mesure pour moyenner la valeur
  {                                        // Lecture des millivolts présents sur l'entrée, la commande
    delay(20);                             // "analogReadMilliVolts" utilise une table de correction intégrée à la puce
    Vbat += analogReadMilliVolts(A1);      // Délai de 20 msec entre mesures et addition des valeurs
  }
  Vbatf = Vbat *2 /10/977.0;               // calcul de la tension en volts, le x2 à cause du pont diviseur par 2
                                           // le 977 devrait être en théorie 1000, mais tolérances des résistances  
  if (Vbatf<Vstop)                         // met l'ESP en sommeil profond si la batterie descend sous Vstop
   {
     esp_sleep_enable_timer_wakeup(2592000 * uS_TO_S_FACTOR);
     // 30 jours de deep sleep en cas de batterie faible. On ne peut pas effacer un wake-up time, mais juste
     // le changer, c'est donc nécessaire pour ne pas rester à TIME_TO_SLEEP dans le cas de batterie faible
     esp_deep_sleep_start();
   }
}

void flash()  // Fait flasher la led brièvement à intervalles réguliers comme signe de vie quand la liaison WiFi est OK
{
  if (WiFi.status() == WL_CONNECTED)         // ne flashe qui si connecté au réseau
   {if (millis() - prevBlinkMillis >= cycle) 
    {Blink = !Blink;
     if (Blink) prevBlinkMillis = millis() - (cycle - temps_flash);
     else prevBlinkMillis = millis();
     digitalWrite(LedPin, Blink);
    }
   }
  else digitalWrite(LedPin, false);
}

void Sleep_ESP()
{
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  esp_deep_sleep_start();
}

/********************* Partie du programme qui permet la mise à jour du sketch au travers du WiFi *****************
  * 
  * Port defaults to 3232
  * ArduinoOTA.setPort(3232);
  *
  * Hostname defaults to esp3232-[MAC]
  * ArduinoOTA.setHostname("myesp32");
  *
  * No authentication by default
  * ArduinoOTA.setPassword("admin");
  *
  * Password can be set with it's md5 value as well
  * MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  * ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
  */
void initOTA() 
{
  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println();
  Serial.println("Maj OTA paramétrée !");
//  Serial.print("IP address: ");
//  Serial.println(WiFi.localIP());
}

Roland

oh oui! j'ai vu et fait, même si il m'a fallu 2 exemplaires en guise de sacrifice (des soudures malheureuses et des gros doigts).
Par contre la 3ème fois est la bonne et ça tourne depuis cette après-midi (avec une batterie de récup, capacité inconnue).

J'ai aussi un montage "chargeur solaire de batterie Li-ion", j'attends les beaux jours pour le tester. Et il faudra bien sur avoir maitrisé la question du sleep d'ici là (l'argument qui m'a fait acheter une Xiao).

Je vois dans ton code qu'il n'y a pas de grande subtilité dans les fonctions de mise en sommeil (sans offense hein!), ça me rassure un peu.

Ne t'en fais pas je ne me sens pas offensé, d'ailleurs je me considère comme débutant avec l'ESP.
Le deep sleep avec un timer est effectivement celui qui est le plus facile à mettre en oeuvre.

J'ai aussi en commande 2-3 petits panneaux solaires avec les circuits qui vont bien (électronique à brancher sur le panneaux), régulateurs 3,3 V LDO, etc...
Tout cela m'est assez familier vu que l'électronique faisait partie de mon métier.

Roland

au top!

écoute après ton message ça m'a regonflé et j'ai fais quelques essais très rapides et faciles avec le deep sleep.
J'ai aussi passé direct le processeur à 80Hz, rien que la batterie a vu la différence!

A propos de panneaux solaires, et si tu es donc tronicien, que penses tu d'un capteur à effet Hall sur la sortie du panneau et branché sur la carte pour avoir une visualisation des périodes de charge?

Bonjour @GrandPete

les modules I2C avec des INA219 (mesure du courant par shunt) sont une bonne alternative aux capteurs de courant à effet Hall pour le suivi des tension, courant et puissance (le module donne les 3 infos !) dans ce genre d'application
Soit en sortie du panneau solaire, soit en série avec l'accu pour suivre sa charge ET sa décharge
image

Plusieurs bibliothèques disponibles pour ce module dont celle d'Adafruit

Oui baisser la fréquence fait baisser la conso. Mais attention, si on descend sous les 80 MHz le WiFi ne marche plus.
Dans le principe un capteur à effet hall devrait marcher, mais reste à vérifier si c'est linéaire et s'il n'y a pas d'influence d'autres champs électriques aux alentours.

Roland

top, merci pour le tuyaux, j'avais vraiment en tete que les petits capteur Hall pour ce genre d'usage.

Les INA219 et autres composants de la même famiille chez Texas Instruments sont bidirectionnels .
Ils mesurent la tension de part et d'autre du shunt puis calculent courant et puissance, sont faciles à gérer avec leur accès par le bus I2C

juste en guise de précision, sur cette histoire de vitesse de processeur pour gagner en autonomie: le arduino cloud a un réglage de base sur 160MHz.
Je pense que le réglage est forcé par le cloud au moment de l'établissement de la connexion (je retrouve le paramètre dans le fichier .json du sketch), et donc écrase mon réglage à 80MHz dans le setup.
Par précaution, je demande au cloud de fonctionner par défaut en 80MHz.


Merci al1fch pour l'info sur le petit circuit de mesure-comptage électrique, ça peut toujours servir un jour pour un projet !
Sais-tu s'il y a une isolation électrique entre ce qu'on mesure et le circuit du Arduino ou ESP ?

Roland

Non pas d'isolation galvanique avec les circuits INA219 , INA226.... il servent pour des mesures où cela n'est pas nécessaire , c'est le cas de la surveillance de petits panneaux photovoltaiques et d'accus alimentant un ESP32