mercredi 3 mai 2023

ESP32 : Micro-irrigation Connectée (2ème partie)

 


ESP32 : Micro-irrigation Connectée (2ème partie)


Cet article fait suite à celui-ci :

ESP32 : Micro-irrigation Connectée (1ère partie)

Démarré en novembre, ce projet a bien progressé ces derniers jours. C'est l'occasion de donner un état d'avancement.

Pour rappel, le projet se trouve ici :

https://bitbucket.org/henri_bachetti/esp32-sprinkle-timer

1. La configuration

Les fichiers de configuration sont lus grâce à cette librairie :

SPIFFSIniFile

Elle est directement installable depuis le gestionnaire de bibliothèques de l'IDE.

1.1. config.ini

La configuration, un fichier config.ini chargé dans le système de fichier SPIFFS, n'a pas fondamentalement évolué, ce qui est bon signe. Par contre le logiciel exploite de nouvelles données : la configuration des zones d'arrosage.

Avant tout, il s'agit de bien comprendre les paramètres de ce fichier :

  • [relays] modules : la liste des modules :
    • gpio-1 : gestion des GPIOs de l'ESP32
    • mcp23017-1(0x20) : gestion des GPIOs d'un MCP23017 (adresse I2C 0x20)
    • mcp23017-2(0x21) : gestion des GPIOs d'un autre MCP23017 (adresse I2C 0x21)
  • [relays] gpio-1=GPIO-L(4), GPIO-L(5), etc. : liste des GPIOs de l'ESP32
  • [relays] mcp23017-1=I2C-H(0-15) : liste des sorties du MCP23017 N°1
  • [relays] mcp23017-2=I2C-H(0-15) : liste des sorties du MCP23017 N°2
  • [valve] main=gpio-1.0 : la sortie sur laquelle se trouve la vanne principale
  • [zones] zones=potager, courges, massif : la liste des zones
  • [potager] ways=tomates(gpio-1.2), etc. : la liste des voies d'arrosage pour cette zone

Les relais sont décrits avec une syntaxe donnant divers renseignements :

  • type-niveau(gpio) :
    • type : le type du module (GPIO ou I2C)
    • niveau : bas ou haut (L ou H) qui représente le niveau de commande du relais
    • gpio : le N° de la GPIO. Il peut s'agir d'une liste

Les modules relais du commerce sont activables par un niveau haut ou bas :

  • digitalWrite(pin, HIGH);
  • ou :
  • digitalWrite(pin, LOW);

Certains relais possèdent même un cavalier permettant de changer le niveau. Cette configuration permet d'indiquer au logiciel le type de relais que l'on utilise.

Exemples : 

  • GPIO-L(4), GPIO-L(5), GPIO-L(13), GPIO-L(14), GPIO-L(15), GPIO-L(16), GPIO-L(17), GPIO-L(18) : 8 GPIO de l'ESP32 commandent des relais activable par un niveau bas
  • I2C-H(0-15) : les 16 GPIOs d'un module I2C commandent des relais activables par un niveau haut

Par la suite, chaque voie d'arrosage va être associée à un relais. Pour ce faire, on ne va pas utiliser la même syntaxe, ce qui serait redondant et source d'erreurs. Les relais sont donc nommés comme ceci : 

  • module.index
    • module : le nom du module
    • index : l'index à partir de ZERO
Exemples : 

  • gpio-1.0 : la première GPIO du module gpio-1. Dans l'exemple précédent il s'agit donc de GPIO-L(4), GPIO-L(5) étant la deuxième (gpio-1.1), etc.
  • mcp23017-1.3 : la quatrième GPIO du module mcp23017-1

Une voie d'arrosage est le moyen physique d'arroser un point précis d'une zone. Elle est forcément associée à une GPIO, à un relais (ou un MOSFET), et à une vanne avec ses tuyaux et goutteurs.

Une zone est une zone géographique, normalement alimentée par sa propre arrivée d'eau. Elle regroupe un certain nombre de voies d'arrosage. Cette notion est totalement inutile d'un point de vue logiciel, sauf que plusieurs voies d'arrosage peuvent porter le même nom, si elles sont situées dans des zones différentes :

[zones]
zones=potager1, potager2

[potager1]
ways=tomates(gpio-1.2), courgettes(gpio-1.3), menthe(gpio-1.4), salades(gpio-1.5)

[potager2]
ways=tomates(gpio-1.6), courgettes(gpio-1.7)

On voit ici que deux voies tomates et deux voies courgettes existent, commandées par des GPIOs différentes bien sûr.

Dans la description des horaires d'arrosage (voir plus loin 1.3. schedule.ini), ces différentes voies seront nommées par rapport à leur zone d'appartenance (ce qui permet de les différencier) : 

  • potager1.tomates
  • potager2.tomates
  • potager1.courgettes
  • potager2.courgettes

La configuration des multiples voies d'une zone a été simplifiée par rapport à celle décrite dans l'article précédent. Elle se fait maintenant sur une seule ligne : 

[zones]
zones=potager, courges, massif

[potager]
ways=tomates(gpio-1.2), courgettes(gpio-1.3), menthe(gpio-1.4), salades(gpio-1.5)

[courges]
ways=voie1(gpio-1.6)

[massif]
ways=voie1(gpio-1.7)

Enfin, le dernier paramètre est la durée d'arrosage manuelle par défaut, en minutes :

[manual] duration=10

1.1.1 Exemples

Deux exemples de configuration sont fournis (dossier examples) : 

config.small.ini : un module 8 relais

config.mcp23017.ini : un module 8 relais + un module MCP23017

Un module MCP23017 peut piloter 16 MOSFETs ou 16 relais. Pour essayer le logiciel dans cette configuration, le module MCP23017 suffit. Il doit être relié aux broches I2C de l'ESP32 : GPIO21 & GPIO22.

Il suffit de copier l'un ou l'autre dans le répertoire data et de le renommer en config.ini. Renseigner ensuite la variable permettant l'accès au réseau WIFI : 

[WIFI]
access-point=SSID:ABCDEFGHIJ

Charger les fichiers du dossier data à l'aide du menu de l'IDE : "ESP32 sketch data upload".

Voici un exemple simple et commenté :

[WIFI]
' remplacer SSID et ABCDEFGHIJ par vos identifiants
access-point=SSID:ABCDEFGHIJ

[relays]
' un seul module gpio-1, gérant 8 GPIO de l'ESP32 (4, 5, et 13 à 18)
modules=gpio-1
gpio-1=GPIO-L(4), GPIO-L(5), GPIO-L(13), GPIO-L(14), GPIO-L(15), GPIO-L(16), GPIO-L(17), GPIO-L(18)

[valve]
' vanne principale sur les deux premières GPIO 4 et 5
main=gpio-1.0, gpio-1.1

[zones]
' 3 zones d'arrosage
zones=potager, courges, massif

[potager]
' 4 voies d'arrosage pour cette zone sur les GPIOs 13 à 16
ways=tomates(gpio-1.2), courgettes(gpio-1.3), menthe(gpio-1.4), salades(gpio-1.5)

[courges]
' 1 voie d'arrosage pour cette zone sur la GPIO 17
ways=voie1(gpio-1.6)

[massif]
' 1 voie d'arrosage pour cette zone sur la GPIO 18
ways=voie1(gpio-1.7)

[manual]
' durée d'arrosage manuel : 10 minutes
duration=10

1.2. Erreurs

Si une erreur est détectée dans le fichier de configuration, celle-ci est signalée sur le moniteur série : 

way::create voie1gpio-1.6)
voie1gpio-1.6): bad format, missing parenthesis
voie1gpio-1.6): error creating way
configuration failed

On voit bien ici qu'il manque une parenthèse :

voie1gpio-1.6)

La bonne syntaxe est : voie1(gpio-1.6)

1.3. schedule.ini

Un nouveau fichier a été créé : schedule.ini

Ce fichier décrit la programmation des zones d'arrosage :

  • heure de début
  • durée
  • arrosage systématique ou non en fonction du capteur d'humidité

[potager.tomates]
schedule1=06:00,15,*
schedule2=20:00,15,*

Cette section par exemple, décrit l'arrosage des tomates dans le potager :

  • un premier arrosage à 6H00 pendant 15 minutes
  • un deuxième arrosage à 20H00 pendant 15 minutes

Le caractère * signifie que l'arrosage est systématique, indépendant du capteur d'humidité. Si ce caractère est absent, l'arrosage sera dépendant du capteur :

[potager.courgettes]
schedule1=06:30,15
schedule2=20:30,15

1.4. Vanne motorisée

Une vanne motorisée (3 fils) nécessite d'être pilotée par deux relais, un pour l'ouverture, l'autre pour la fermeture. Ce genre de vanne met un certain temps à s'ouvrir et se fermer, environ 10 secondes, ce qui fait que le code nécessite un timer (la librairie ESP32 Ticker convient très bien).

Le logiciel est capable de prendre en compte une vanne motorisée comme vanne principale :

[valve]
main=gpio-1.0

Ce paramètre indique que la vanne principale est non motorisée, commandée par la GPIO-1.0

[valve]
main=gpio-1.0, gpio-1.1

Ce paramètre indique que la vanne principale est motorisée, commandée par les GPIO-1.0 et GPIO-1.1.

2. Arrosage automatique

La fonction loop() vérifie toutes les minutes qu'un arrosage automatique est nécessaire ou non, et le stoppe au moment adéquat.

L'interface HTML décrit les différentes voies d'arrosage configurées :

3. Arrosage manuel

L'interface graphique HTML montre également un bouton DEMARRER en face de chaque voie d'arrosage. Il suffit de cliquer sur ce bouton pour démarrer un arrosage manuel, après avoir saisi la durée dans le champ de saisie en haut à droite.

Lorsqu'un arrosage manuel est lancé, le bouton change de libellé (ARRETER), et un compteur affiche le temps restant, en minutes et secondes : 

Un clic sur le bouton ARRETER permet d'arrêter l'arrosage manuel.

On peut lancer plusieurs arrosages manuels simultanés.

3.1. Quelques explications techniques

Le pilotage de l'arrosage manuel est fait de manière autonome par l'ESP32, c'est à dire que l'on peut sans problème fermer le navigateur une fois l'arrosage lancé, ce qui ne serait pas le cas si j'avais choisi de piloter l'arrosage dans le code JavaScript. Ici, le code JavaScript se contente de demander régulièrement à l'ESP32 quel est le temps restant.

Pour les connaisseurs, le code JavaScipt envoie une requête HTTP à l'ESP32 via une WEB socket. Voir la fonction getData() de index.html.

4. HTML

Dans sa première version ce logiciel a été développé avec le librairie WebServer. Je me suis vite aperçu que sans AsyncWebServer et sa gestion de fichiers stockés en FLASH (SPIFFS), et surtout sa gestion des templates, le développement serait beaucoup trop complexe.

Installez cette librairie :

https://github.com/me-no-dev/ESPAsyncWebServer

Il s'en suit que le code HTML n'est plus intégré dans le code C++ du projet, mais dans le filesystem SPIFFS : index.html

4.1. Quelques explications tecniques

On peut remarquer deux choses dans ce code :

La présence de deux balises spéciales (place holders) :
  • %PLACEHOLDER_MANUAL_DURATION% : durée de l'arrosage manuel
  • %PLACEHOLDER_WATERINGS% : la liste des arrosages, sous forme de table HTML
Certains caractères % sont doublés, par exemple :

.mytable th { background-color:#BDB76B; color:white; width:50%%; }

Il s'agit probablement d'un bug de la gestion des templates AsyncWebServer, que j'ai contourné en utilisant cette astuce, un peu comme on le ferait avec printf() ou sprintf().

Pour résumer le principe, AsyncWebServer, après avoir lu le fichier index.html, va remplacer les place holders par leur vraie valeur :

  • %PLACEHOLDER_MANUAL_DURATION% : 10
  • %PLACEHOLDER_WATERINGS% : la table HTML des arrosages

Le remplacement est fait par une fonction située dans le fichier html.cpp

String templateProcessor(const String& var)
{
  if (var == "PLACEHOLDER_MANUAL_DURATION") {
    return String(Watering::manualDuration());
  }
  else if (var == "PLACEHOLDER_WATERINGS") {
    return wateringsTable;
  }
  return "";
}

5. Essayer

Pour essayer ce logiciel, il est nécessaire de connaître certains détails : 

Si la carte choisie est l'ESP32 DevKitC, La carte a sélectionner dans le menu Outils / Type de Carte est  la suivante : ESP32 Dev Module.

La configuration hardware actuelle est la suivante :
  • un module à 8 relais sur GPIO4, GPIO5, GPIO13 à GPIO18
  • un module MCP23017 connecté au bus I2C (si l'exemple config.mcp23017.ini est choisi)

Ceux qui sont à l'aise avec ces petits détails n'auront aucun mal à essayer ce logiciel dans sa version actuelle.

6. Conclusion

Ce projet n'est pas terminé. Il ne va pas cesser d'évoluer pendant les semaines à venir.

Il reste encore pas mal de travail, en particulier sur l'interface graphique :

  • modifier les heures d'arrosage, les durées
  • ajouter des heures d'arrosage
  • ajouter des zones, des voies
  • capteur d'humidité, de débit
  • écran OLED
  • etc.

Je suis parfaitement conscient que le code c++ et JavaScript de ce projet est complexe et certainement incompréhensible pour un débutant, mais il est difficile de faire simple quand on fait du développement WEB. L'essentiel est de bien comprendre la syntaxe des fichiers de configuration.

Si certaines notions sont incomprises, il est recommandé de poser des questions, même si je ne suis pas censé donner des cours de base de C++, HTML ou JavaScript en ligne.

Les développeurs expérimentés en JavaScript trouveront probablement que mon code JavaScript n'est pas celui d'un vrai professionnel JS, et qu'il ressemble trop à du C++, mais qu'ils me pardonnent, JS n'est pas mon langage de prédilection, je préfère C++ et PYTHON.

Pour conclure, le contenu de cet article risque d'être augmenté dans les prochains jours. Ne pas hésiter à revenir le consulter (voir 7. Mises à jour).


Cordialement

Henri

7. Mises à jour

04/05/2023 : paragraphes ajoutés ou fortement remaniés :
  • 1.1. config.ini
  • 1.1.1 Exemples
  • 1.2. Erreurs
  • 3.1. Quelques explications techniques
  • 4.1. Quelques explications techniques

10 commentaires:

  1. Bonjour, c'est un super projet que vous faites là.
    Moi même je suis en train de réaliser un projet semblable avec quelques différences.
    Je vous rejoins que l'utilisation du HTLM plus le JS ne simplifie pas les choses car je découvre ces langages mais avec le temps c'est surmontable.

    Bonne continuation.

    RépondreSupprimer
  2. Ce commentaire a été supprimé par l'auteur.

    RépondreSupprimer
  3. Non, je n'ai aucun délai à proposer pour ce système d'irrigation, par manque de temps pour m'y consacrer pleinement.

    RépondreSupprimer
  4. Bonjour, je suis en train d'essayer de télécharger votre code. Mais étant plus habitué aux Arduino qu'aux ESP je ne sais pas quelle carte choisir dans l'IDE Arduino ou VS Code. J'ai comme vous une carte ESP32 DevKitC. Quelle référence de carte sélectionnez-vous dans l'IDE ? Merci.

    RépondreSupprimer
    Réponses
    1. Tout simplement ESP32 Dev Module

      Supprimer
    2. Merci beaucoup. Je viens de compiler le code. Avec Arduino IDE 1.8 pas de problème. Mais j'utilise beaucoup VS Code. Du coup j'ai essayé avec et cela m'a fait remonter quelques erreurs de compilation :
      1- Sur VS Code par défaut la ESP32 lib est une V2. J'avais des erreurs dans main.cpp. Visiblement il faut modifier le code ainsi :
      Ligne 64
      // Serial.println(info.disconnected.reason);
      Serial.println(info.wifi_sta_disconnected.reason);
      Ligne 82
      // WiFi.onEvent(WiFiStationConnected, SYSTEM_EVENT_STA_CONNECTED);
      // WiFi.onEvent(WiFiGotIP, SYSTEM_EVENT_STA_GOT_IP);
      // WiFi.onEvent(WiFiStationDisconnected, SYSTEM_EVENT_STA_DISCONNECTED);
      WiFi.onEvent(WiFiStationConnected, ARDUINO_EVENT_WIFI_STA_CONNECTED);
      WiFi.onEvent(WiFiGotIP, ARDUINO_EVENT_WIFI_STA_GOT_IP);
      WiFi.onEvent(WiFiStationDisconnected, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
      Je mets la librairie ESP32 V1.0.6 pas d'erreur à ce niveau là...

      2- En compilant avec V1.0.6, il semblerait qu'il y ait une erreur d'ordre dans les lignes dans les fichiers relay.h et relay.cpp
      J'ai modifié relay.cpp et l'erreur a disparu mais je suis loin d'être un pro du C++ :
      Pas de modification de relay.h
      private:
      static Relay m_relay[MAX_WAY];
      static int m_searchIndex;
      Module *m_module;
      moduleType m_access;
      uint8_t m_index;
      uint8_t m_id;
      int8_t m_onPin;
      int8_t m_offPin;
      int8_t m_level;
      relayState m_state;

      On constate que dans relay.cpp dans les lignes 9 à 15, les éléments sont dans un ordre différent :
      Initialement :
      Relay::Relay() :
      m_module(0),
      m_index(0),
      m_id(0),
      m_access(IO),
      m_onPin(-1), m_offPin(-1),
      m_level(HIGH)
      Modifié par :
      Relay::Relay() :
      m_module(0),
      m_access(IO), // Move here
      m_index(0),
      m_id(0),
      // From here
      m_onPin(-1), m_offPin(-1),
      m_level(HIGH)
      Par contre en compilant avec la V2 pas d'erreur.

      3-Après ces motifs le code se compile mais j'ai encore deux alertes jaunes. Apparemment il faudrait un return dans les fonctions
      module.cpp

      bool Module::begin()
      {
      create("gpio");
      }

      et

      watering.cpp
      bool Watering::run(time_t t)
      ...

      Voici les messages d'erreur :
      src/module.cpp: In static member function 'static bool Module::begin()':
      src/module.cpp:20:1: warning: no return statement in function returning non-void [-Wreturn-type]
      }

      et

      src/watering.cpp: In static member function 'static bool Watering::run(time_t)':
      src/watering.cpp:121:1: warning: no return statement in function returning non-void [-Wreturn-type]
      }

      Merci pour votre aide...

      Supprimer
  5. Ce commentaire a été supprimé par l'auteur.

    RépondreSupprimer
  6. Bonjour Henri,
    Bonjour, tout d’abord merci pour tes articles et tes liens toujours au top.
    J’ai vu les circuits que tu développes pour tes prototypes, pour certaines liaisons tu es à 0,35mm ce qui est plus que correcte pour un développement amateur. As-tu déjà fait un article sur le comment tu procèdes pour ces prototypes ou peux-tu nous expliquer ce que tu utilises. Merci d’avance pour ta réponse.

    RépondreSupprimer
    Réponses
    1. Bonjour.
      Ce n'est qu'une question de qualité de PCB. Il y a longtemps que je n'utilise plus de plaques de la marque CIF, très décevantes. Depuis 2019, j'utilise exclusivement des Bungard.
      https://riton-duino.blogspot.com/2019/03/minuterie-pour-insoleuse-de-pcb.html

      Supprimer