Surveillance des niveaux d'eau des torrents

Une idée, un nouveau projet

Mon précédent projet, on a permis d'entrevoir les nombreuses possibilités de ce module LoRa couplé à un ESP32. De plus, les nombreuses précipitations de ce printemps et les différentes laves torrentielles observées ou découvertes par des reportages photos et vidéos en Maurienne m'ont amené à imaginer un système permettant de remonter une notification en cas de variation importante niveau d'un torrent.

=== Je suis donc parti dans l'idée de réaliser un montage compact et autonome en énergie. En gardant une certaine modularité. Cette modularité me permet de remplacer différents parties constituants le capteur par une simple action Plug and Play directement sur le terrain. Le module LoRa ESP 32 est donc complété par une petite platine d'alimentation pouvant accueillir une source solaire, une batterie à base de 18650 et un capteur de distance à ultrasons. Le capteur de distance fonctionne en deux parties : un émetteur et un récepteur, permettant mathématiquement de définir la distance à l'obstacle. Le modèle choisi est un HC-SR04 . Je l'ai choisi pour son faible coût vû que le développement tient plus du Proof of Concept que du module pérenne et passe-partout. Je me suis quand même très vite retrouvé dans les limites de la plage de mesure : de 0 à 4m.

L'idée est également de développer un code qui préserve au maximum la batterie par la limitation des passages en émission.

Côté software

1. émetteur

Le développement logiciel suit la même logique que celle que j'ai écrit pour la surveillance des relais. La mesure est faite à intervalles réguliers. Si la distance atteint une valeur critique, l'envoi de cette information se fera en LoRa. Le message émit comprendra le nom du torrent ainsi que la distance mesurée.

code

#include <Wire.h>
#include <SSD1306Wire.h>
#include <SPI.h>
#include <LoRa.h>

String location="Poucet";
const int trigPin = 2;
const int echoPin = 15;

//define sound speed in cm/uS
#define SOUND_SPEED 0.034

long duration;
float distanceCm;

//Déclaration des pins de l'écran
SSD1306Wire display(0x3c, SDA, SCL); 

//Définition pour le LoRa
const long frequency = 869500000;  //869.500MHz
#define SCK     5    // GPIO5  -- SX1278's SCK
#define MISO    19   // GPIO19 -- SX1278's MISnO
#define MOSI    27   // GPIO27 -- SX1278's MOSI
#define SS      18   // GPIO18 -- SX1278's CS
#define RST     14   // GPIO14 -- SX1278's RESET
#define DI0     26   // GPIO26 -- SX1278's IRQ(Interrupt Request)

void Affichage(String distanceSol) {
  display.clear();
  display.setFont(ArialMT_Plain_16);
  display.drawString(0, 0, location);
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 30, "Distance : " + distanceSol + "cm");
  display.display();
}

void LoRa_sendMessage(String message) {
  //LoRa_txMode();                        // set tx mode
  LoRa.beginPacket();                     // start packet
  LoRa.print(message);                    // add payload
  LoRa.endPacket();                       // finish packet and send it
}

void setup() {
  Serial.begin(115200); // Starts the serial communication
  pinMode(trigPin, OUTPUT); // Sets the trigPin as an Output
  pinMode(echoPin, INPUT); // Sets the echoPin as an Input

  //Module Lora 
  SPI.begin(SCK,MISO,MOSI,SS);
  LoRa.setPins(SS,RST,DI0);
  if (!LoRa.begin(frequency)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  LoRa.setTxPower(20);

  display.init();
  display.clear();
  display.flipScreenVertically();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_16);
  display.drawString(0, 0, location);
  display.display();
  Serial.println("Démarrage surveillance " + location);
  delay(2000);
}

void loop() {  // Clears the trigPin
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Reads the echoPin, returns the sound wave travel time in microseconds
  duration = pulseIn(echoPin, HIGH);

  // Calculate the distance
  distanceCm = duration * SOUND_SPEED/2;

  // Prints the distance in the Serial Monitor
  Serial.print("Distance (cm): ");
  Serial.println(distanceCm);

  //envoi en LoRa
  if (distanceCm < 400 && distanceCm > 5)
  {
    String distanceSol = String(distanceCm);
    String MessageLora = location + "/" + distanceSol + "#";
    LoRa_sendMessage(MessageLora);
    Affichage(distanceSol);
  }

  delay(5000);
}

1. récepteur

Côté réception, la getaway reprendra également le même schéma que pour le projet de surveillance relais. Elle est simplement composée d'un module LoRa ESP32. Elle a pour but de recevoir les données des capteurs terrains, de les enregistrer dans une base de données SQL d'un site internet dédié et de remonter une notification Telegram sur un groupe de discussion. En plus des données reçues, la Gateway ajoutera aux informations la date et l'heure de réception ainsi que le niveau du signal reçu.

Étant donné qu'un capteur terrain peut ne pas envoyer d'informations pendant plusieurs jours (tant que le niveau du torrent reste à sa valeur nominale), j'ai imaginé un système de ping via une demande sur le groupe Télégram pour un capteur donné. Cela permet de s'assurer du bon fonctionnement logiciel et/ou de la bonne alimentation électrique. Cette fonctionnalité est en cours de développement.

code

#include <Wire.h>
#include <SSD1306Wire.h>
#include <SPI.h>
#include <LoRa.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <UniversalTelegramBot.h>
#include <ArduinoJson.h>
#include <string>
#include <iostream>
#include "time.h"

using namespace std;

SSD1306Wire display(0x3c, SDA, SCL); //Déclaration des pins de l'écrans

const long frequency = 869500000;  //868.5005MHz
#define SCK     5    // GPIO5  -- SX1278's SCK
#define MISO    19   // GPIO19 -- SX1278's MISnO
#define MOSI    27   // GPIO27 -- SX1278's MOSI
#define SS      18   // GPIO18 -- SX1278's CS
#define RST     14   // GPIO14 -- SX1278's RESET
#define DI0     26   // GPIO26 -- SX1278's IRQ(Interrupt Request)
const char* ssid = "WiFi SSID";
const char* password = "PasswordWiFI";
#define BOTtoken "*********************************"
#define CHAT_ID "*********************" //Groupe DataTorrents
WiFiClientSecure client;
UniversalTelegramBot bot(BOTtoken, client);

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 3600;

const char* serverName = "http://datatorrent.de-folleville.fr/dataRX.php";
String apiKeyValue = "*****************";
String sensorLocation = "";

void Affichage(String DateTimeRX, String message) {
  display.clear();
  display.setFont(ArialMT_Plain_16);
  display.drawString(0, 0, DateTimeRX);
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 15, message + "\n" + "RSSI : " + (String)LoRa.packetRssi());
  display.display();
}

String GetDateTimeRX(){
  char DateTimeRX[50];
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return "Erreur get date";
  }
  strftime(DateTimeRX, 50, "%d-%m-%g | %H:%M", &timeinfo);
  return DateTimeRX;
}

String GetDateTimeSQL(){
  char DateTimeSQL[50];
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return "Erreur get date";
  }
  strftime(DateTimeSQL, 50, "%Y-%m-%d %H:%M:%S", &timeinfo);
  return DateTimeSQL;
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  //Ecran de démarrage
  Serial.println("Démarrage");
  display.init();
  display.clear();
  display.flipScreenVertically();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_16);

  //Module Lora 
  SPI.begin(SCK,MISO,MOSI,SS);
  LoRa.setPins(SS,RST,DI0);
  if (!LoRa.begin(frequency)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 20, "LoRa démarré");
  display.display();
  LoRa.receive();

  // Attempt to connect to Wifi network:
  Serial.print("Connecting Wifi: ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // Add root certificate for api.telegram.org
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 40, "WiFi connecté");
  display.display();
  delay(2000);

  // Init and get the time
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  GetDateTimeRX();
  GetDateTimeSQL();

  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 10, "Surveillance torrents \n \n     Attente RX Lora");
  display.display();
  delay(1000);

  //bot.sendMessage(CHAT_ID, "Démarrage gateway");
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.begin(115200);
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    while (LoRa.available()) {
      String DateTimeRX = GetDateTimeRX();
      String DateTimeSQL = GetDateTimeSQL();

      String message = LoRa.readString();
      int pos1 = message.indexOf('/');
      int pos2 = message.indexOf('#');
      sensorLocation = message.substring(0, pos1);
      String distance = message.substring(pos1 +1, pos2);
      int intdistance = atoi(distance.c_str());
      String messagedisplay = sensorLocation + "\nDistance au sol :\n   " + distance + " cm";
      Affichage(DateTimeRX, messagedisplay);
      Serial.println(DateTimeRX);
      Serial.println(message);
      Serial.println("extract lieu : " + sensorLocation);
      Serial.println("extract dist : " + distance);
      Serial.println("RSSI : " + (String)LoRa.packetRssi() + "\n");

      if (intdistance < 1000 && intdistance > 0)
      {
        WiFiClient client;
        HTTPClient http;

        // Your Domain name with URL path or IP address with path
        http.begin(client, serverName);

        // Specify content-type header
        http.addHeader("Content-Type", "application/x-www-form-urlencoded");

        // Prepare your HTTP POST request data
        String httpRequestData = "api_key=" + apiKeyValue + "&lieu=" + sensorLocation + "&datetime=" + DateTimeSQL + "&distance=" + distance + "&RSSI=" + (String)LoRa.packetRssi() +"";
        Serial.print("httpRequestData: ");
        Serial.println(httpRequestData);

        // Send HTTP POST request
        int httpResponseCode = http.POST(httpRequestData);

        if (httpResponseCode>0) {
          Serial.print("HTTP Response code: ");
          Serial.println(httpResponseCode);
        }
        else {
          Serial.print("Error code: ");
          Serial.println(httpResponseCode);
        }
        // Free resources
        http.end();

        //Alerte télégram
        if (intdistance < 390)
        {
          Serial.println ("-> Alerte Télégram envoyée");
          bot.sendMessage(CHAT_ID, "<b>⚠️⚠️ Alerte ⚠️⚠️\n\n" + sensorLocation + "\n" + "distance=" + distance + "cm</b>\n\nRSSI : " + (String)LoRa.packetRssi() + "dBm", { parseMode: "html" });
        }
      }
    }
  }
}

Mise en place sur sites

à venir après la mise en coffret

Retour d'expérience et perspectives d'évolution

Après plusieurs jours de test durant une période très pluvieuse, j'ai pu valider le fonctionnement et accumuler de la donner afin d'en faire un bon Proof of Concept présentable plus largement.

Les améliorations à prévoir se situent au niveau du capteur à ultrason. Un modèle avec une plage de mesure allant de 0 à 10 m serait un plus non négligeable compte tenu des hauteurs que peuvent atteindre les laves torrentielles. Une autre réside dans la fabrication d'un support comprenant une boîte étanche pour l'électronique, un support orientable pour le panneau solaire et un support dégagé pour l'antenne LoRa permettant de s'adapter facilement à tout type de support tel qu'un rocher, un arbre, un potelet ... Il pourrait également être imaginé de déporter le capteur à ultrason afin que celui-ci soit au plus près niveau théorique maximal d'une lave torrentielle tout en protégeant le reste des éléments constituant ce projet. Mais il n'est pas exclu qu'une lave dépasse cette côte théorique et emporte le capteur à ultrason. Le déport de celui-ci permettrait de limiter les pertes matériels dans ce cas.

Article précédent