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.
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" });
}
}
}
}
}
à venir après la mise en coffret
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.