ESP32 and LoRa based Wireless Water Level Monitoring, LoRa Gateway, Blynk
Table of Contents
ESP32 and LoRa Based Water Level Monitoring:
ESP32 and LoRa based Wireless Water Level Monitoring, LoRa Gateway, Blynk- Today, we are going to make version 2 of the Wireless Water Level Indicator using ESP32 WiFi + Bluetooth modules, a pair of Long Range LoRa transceiver modules, and the most versatile, UART version of the A02YYUW Waterproof Ultrasonic Sensor.
Let me first tell you about the Version 1; so that you can better understand why I made version 2 of the same project.
Version 1 of the Wireless Water Level Indicator was based on the TTGO LoRa32. This board has the ESP32, Lora, Oled display module, and so many other things on the board itself. So, I didn’t have to connect everything manually. Because, most of the connections were already done. So, on the transmitter side, I simply connected a waterproof ultrasonic sensor the JSN SR04T and that’s it. And on the receiver side, I didn’t connect anything; you know why, because the development board already has the LoRa and Oled display module.
With this simple setup, I was able to monitor the Water Level within 1 to 2 kilometers. It was based on one-way communication. Therefore, I could only monitor the water level. I could add more features like controlling the water pump automatically or manually over a long distance, I could add a feedback feature, and I could also connect it to an iOT clould platform like Blynk, Ubidots, Thingspeak, etc. But, I didn’t do it on purpose. Because I had to keep it simple and easy to follow.
The reason I decided to make version 2 of the same project is that I have been receiving lots of emails and messages from my followers on different social media platforms. The majority of them requested me not to use the TTGO LoRa32 because they didn’t have much control over the component selection. As you may know, LoRa modules come with different frequency bands: 433MHz, 868MHz, and 918MHz.
433E6 for Asia
866E6 for Europe
915E6 for North America
So, let’s say, if you are from North America or Asia, then you are not allowed to use the 868MHz version of the LoRa module because this frequency band is only permitted in Europe. Since I am from Pakistan and I am also not allowed to use the 868MHz frequency band, that’s why in version 2 of the Wireless Water Level Indicator, I decided to use the 433MHz SX1278 LoRa modules. These are the same LoRa modules I used with the Arduino. They are available in different frequency bands, and the good thing is, all these LoRa modules share the same pin layout. Furthermore, you don’t need to make any changes in the programming except for the frequency band selection.
They also requested me to add more features. So, with version 2 of the Wireless water level indicator, we can not only monitor the Water Level over a long distance using LoRa, but we can also send that information to the Blynk IoT application.
The Water Level information from the transmitter, where there is no WiFi or GPRS connectivity, is transmitted to the Receiver side over a long distance using Long Range LoRa modules, where we have GPRS, 4G LTE, or WiFi connectivity. So, the receiver side also acts as the LoRa Gateway.
Let me tell you, the range depends on different factors, such as the type of antennas you are using on the transmitter and receiver sides. I have a full dedicated video and article on the types of LoRa antennas. It also depends on whether your LoRa modules are in line-of-sight or if there are obstacles between the transmitter and receiver. So, make sure you read my article on the types of LoRa antennas.
Anyway, if you are near the receiver side, you can directly read the Water Level information on the display, and if you are outside, then you can check the water level information using the Blynk application.
Furthermore, you can use a button on the receiver side to control a relay on the transmitter side to turn a water pump ON or OFF. “You can think of this Bulb as the Water Pump”.
For this, first, you will need to switch to the Manual mode on the Blynk application; otherwise, this button won’t work.
It also adds a layer of security to your control system. Because until the control is transferred to the receiver side, nobody would be able to turn the Water Pump ON or OFF.
Safety:
Remember safety first, When the 110/220Vac supply is connected, never touch the relay contacts as it can be extremely dangerous. It is important to note that when working with mains voltage, proper safety precautions should always be taken and it is advisable to consult relevant electrical codes and standards.
And when we change this to the Auto Mode, then we can directly control the relay using the Blynk application.
Amazon Links:
ESP32 WiFi + Bluetooth Module (Recommended)
A02YYUW Waterproof Ultrasonic Sensor (UART)
Disclosure: These are affiliate links. As an Amazon Associate I earn from qualifying purchases.
TX Circuit Diagram:
- Connect the VCC of the A02YYUW ultrasonic sensor with 3.3V of the ESP32
- Connect the GND of the A02YYUW ultrasonic sensor with GND of the ESP32
- Connect the TXpin of the A02YYUW ultrasonic sensor with GPIO16 of the ESP32
- Connect the RXpin of the A02YYUW ultrasonic sensor with GPIO17 of the ESP32
- Connect the VCC pin of the Lora module with 3.3V of the ESP32
- Connect the Ground pin of the Lora module with Ground of the ESP32
- Connect the NSS pin of the Lora module with GPIO18 of the ESP32
- Connect the MOSI pin of the Lora module with GPIO27 of the ESP32
- Connect the MISO pin of the Lora module with GPIO19 of the ESP32
- Connect the SCK pin of the Lora module with GPIO5 of the ESP32
- Connect the RST pin of the Lora module with GPIO14 of the ESP32
- Connect the DIO0 pin of the Lora module with GPIO26 of the ESP32
- Connect the VCC pin of the Relay module with 5V of the ESP32
- Connect the Ground pin of the Relay module with ground pin of the ESP32
- Connect the signal pin of the Relay module with GPIO13 of the ESP32
RX Circuit Diagram:
- Connect the VCC pin of the Lora module with 3.3V of the ESP32
- Connect the Ground pin of the Lora module with Ground of the ESP32
- Connect the NSS pin of the Lora module with GPIO18 of the ESP32
- Connect the MOSI pin of the Lora module with GPIO27 of the ESP32
- Connect the MISO pin of the Lora module with GPIO19 of the ESP32
- Connect the SCK pin of the Lora module with GPIO5 of the ESP32
- Connect the RST pin of the Lora module with GPIO14 of the ESP32
- Connect the DIO0 pin of the Lora module with GPIO26 of the ESP32
- Connect the VCC of the OLED with 3.3V of the ESP32
- Connect the ground of the OLED with the Ground of the ESP32
- Connect the SDA pin of the OLED with the GPIO4 of the ESP32
- Connect the SCL pin of the OLED with GPIO15 of the ESP32
NextPCB:
If you want to order affordable yet high-quality PCBs for your upcoming projects. I highly recommend placing your order on the NextPCB. I myself use their HQDFM tool and free online PCB Gerber Viewer. You can read my article about how to design yourself an ESP32 Development board, in that article, I have explained, how to place your order on the NextPCB, and how to solder SMD components. In this project, I have used the same board on the transmitter side.
Blynk Web Dashboard Setup:
- While you are logged-in into your Blynk account.
- Click on the New Template.
- Write the Template Name.
While ESP32 is selected as the Hardware type and WiFi as the connection type; click on the Done button.
While you are on the Datastreams tab, click on the + New Datastream and select Virtual Pin.
On the Virtual Pin Datastream, select the virtual pin V0, then the type as interger, and define the min and max values. On this virtual pin we will monitor the water level percentage. So, that’s why I selected the min value as 0 and the maximum value of 100. Finally, click on the Create button.
Follow the same exact steps and create two more virtual pins (V1 and V2) and this time select the min value (0) and the Max value (1). Since we will use the virtual pins with the buttons to control the manual and automatic mode; and the Relay ON/OFF. As you can see in the image below, I did it for the virtual pin V1.
Following the same method, create another virtual pin V2 for the other button.
If you face any issues then you can follow the video tutorial available at the end of this article.
Anyway, once all the three datastreams are defined. Then you can go to the Web Dashboard tab and start adding widgets to your dashboard.
You can clearly see, I have added a Gauge for the water level monitoring, and two buttons. You can see, the gauge the is assigned virtual pin V0 and the two buttons are assigned virtual pins V1 and V2 respectively. Let me remind you one more time, if you face any issues, you can watch my video tutorial.
Next, on the Left side you can see Devices, buddy you need to click on it.
Now, on the right side, you will see the + New Device, click on it to add your device.
Choose a way to create new device. We are going to select From Template.
Click on the Choose template and select your template that is “Water level Monitoring”.
When the dashboard is ready, simply copy these credentials, open the receiver side program given below.
Don’t forget to change the GPRS Credentials.
Receiver side Programming:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
#define BLYNK_TEMPLATE_ID "TMPL6cs1CLHy5" #define BLYNK_TEMPLATE_NAME "Water level Monitoring" #define BLYNK_AUTH_TOKEN "3kvElevALIFbXWgFyY9sSc5zw6UP7djF" #include <SPI.h> #include <LoRa.h> #include <WiFi.h> #include <WiFiClient.h> #include <BlynkSimpleEsp32.h> //Libraries for OLED Display #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> char auth[] = BLYNK_AUTH_TOKEN; int control; int pinValue; //define the pins used by the LoRa transceiver module #define SCK 5 #define MISO 19 #define MOSI 27 #define SS 18 #define RST 14 #define DIO0 26 //OLED pins #define OLED_SDA 4 #define OLED_SCL 15 #define OLED_RST 16 #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST); byte msgCount = 0; // count of outgoing messages String outgoing; // outgoing message String LoRaData; byte localAddress = 0xFF; // address of this device byte destination = 0xBB; // destination to send to long lastSendTime = 0; // last send time int interval = 50; // interval between sends String statusmessage = ""; int relay1Status; int button=22; // Set password to "" for open networks. char ssid[] = "AndroidAP3DEC"; char pass[] = "electroniclinic"; void setup() { //initialize Serial Monitor Serial.begin(115200); pinMode(button,INPUT_PULLUP); //reset OLED display via software pinMode(OLED_RST, OUTPUT); digitalWrite(OLED_RST, LOW); delay(20); digitalWrite(OLED_RST, HIGH); //initialize OLED Wire.begin(OLED_SDA, OLED_SCL); if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } display.clearDisplay(); display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,0); display.print("LORA RECEIVER "); display.display(); //Serial.println("LoRa Receiver Test"); //SPI LoRa pins SPI.begin(SCK, MISO, MOSI, SS); //setup LoRa transceiver module LoRa.setPins(SS, RST, DIO0); if (!LoRa.begin(433E6)) { Serial.println("LoRa init failed. Check your connections."); while (true); // if failed, do nothing } // Serial.println("LoRa init succeeded."); // display.setCursor(0,10); // display.println("LoRa Initializing OK!"); // display.display(); Blynk.begin(auth, ssid, pass); } void loop() { if (millis() - lastSendTime > interval) { if(control==HIGH) { relay1Status = digitalRead(button); Serial.println(relay1Status); } else{ relay1Status= pinValue; } statusmessage = statusmessage + relay1Status; sendMessage(statusmessage); delay(10); statusmessage = ""; lastSendTime = millis(); // timestamp the message interval = random(50) + 100; // 2-3 seconds } // parse for a packet, and call onReceive with the result: onReceive(LoRa.parsePacket()); } void onReceive(int packetSize) { if (packetSize == 0) return; // if there's no packet, return // read packet header bytes: int recipient = LoRa.read(); // recipient address byte sender = LoRa.read(); // sender address byte incomingMsgId = LoRa.read(); // incoming msg ID byte incomingLength = LoRa.read(); // incoming msg length String incoming = ""; while (LoRa.available()) { LoRaData = LoRa.readString(); //Serial.print(LoRaData); } String q = getValue(LoRaData, ',', 0); String r = getValue(LoRaData, ',', 1); int distance = q.toInt(); int waterLevelPer = r.toInt(); //Serial.print("distance = "); //Serial.println(distance); //Serial.print("waterLevelPer = "); //Serial.println(waterLevelPer); String rssi = "RSSI " + String(LoRa.packetRssi(), DEC) ; //Serial.println(rssi); delay(10); // Dsiplay information display.clearDisplay(); display.setCursor(5,0); display.setTextSize(2); display.print("W-L-Meter"); display.setCursor(0,25); display.setTextSize(2); // display.print("Dist:"); // display.setCursor(70,15); // display.setTextSize(2); // display.print(distance); display.print("Dist:"+String(distance)+"cm"); display.setCursor(0,50); display.setTextSize(2); //display.print("W-L:"); display.print("W-L:"+String(waterLevelPer)+"%"); // display.setCursor(92,40); // display.setTextSize(2); // display.print(waterLevelPer); display.display(); Blynk.virtualWrite(V0,waterLevelPer); } String getValue(String data, char separator, int index) { int found = 0; int strIndex[] = { 0, -1 }; int maxIndex = data.length() - 1; for (int i = 0; i <= maxIndex && found <= index; i++) { if (data.charAt(i) == separator || i == maxIndex) { found++; strIndex[0] = strIndex[1] + 1; strIndex[1] = (i == maxIndex) ? i+1 : i; } } return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; } void sendMessage(String outgoing) { LoRa.beginPacket(); // start packet LoRa.write(destination); // add destination address LoRa.write(localAddress); // add sender address LoRa.write(msgCount); // add message ID LoRa.write(outgoing.length()); // add payload length LoRa.print(outgoing); // add payload LoRa.endPacket(); // finish packet and send it msgCount++; // increment message ID } BLYNK_WRITE(V2) { pinValue=param.asInt(); } BLYNK_WRITE(V1) { control=param.asInt(); } |
If this is your first time using the ESP32 WiFi + Bluetooth module then you will also need to install the ESP32 board in the Arduino IDE.
For this you can read my getting started article on the ESP32 WiFi + Bluetooth Module.
Next, you will also need to install the entire blynk library package. For this simply go to the Sketch Menu, then to Include Library, and click on the Manage Libraries.
Type Blynk in the search box.
You can see I have also installed this library. It works with over 400 boards.
You will also need to install the required LoRa, Adafruit_GFX, and Adafruit_SSD1306 libraries. For this simply copy the library name, then go to the Sketch Menu, then to Include Library, and click on the Manage Libraries.
Type LoRa in the Search box, scroll down and search for the LoRa library by Sandeep Mistry and install it.
You can see I have already installed this library. Repeat the same steps for the remaining libraries.
Now, type Adafruit_GFX in the search box.
Now, type Adafruit_SSD1306 in the search box.
Transmitter Side Programming:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
#include <SPI.h> #include <LoRa.h> #include <HardwareSerial.h> HardwareSerial Ultrasonic_Sensor(2); // TX2 (pin 17), RX2 (pin 16) // Define connections to sensor int pinRX = 16; // Choose a suitable pin for RX int pinTX = 17; // Choose a suitable pin for TX //define the pins used by the LoRa transceiver module #define SCK 5 #define MISO 19 #define MOSI 27 #define SS 18 #define RST 14 #define DIO0 26 int distance; //packet counter int counter = 0; String LoRaData; String outgoing; // outgoing message int relay1Status; byte msgCount = 0; // count of outgoing messages byte localAddress = 0xBB; // address of this device byte destination = 0xFF; // destination to send to long lastSendTime = 0; // last send time int interval = 50; String Mymessage; // Array to store incoming serial data unsigned char data_buffer[4] = {0}; int Relay=13; //Relay will be connected with digital pin 12 int relayStatus; int buttonstat; // Variable to hold checksum unsigned char CS; void setup() { //initialize Serial Monitor Serial.begin(115200); Ultrasonic_Sensor.begin(9600, SERIAL_8N1, pinRX, pinTX); // Initialize the hardware serial pinMode(Relay,OUTPUT); //SPI LoRa pins SPI.begin(SCK, MISO, MOSI, SS); //setup LoRa transceiver module LoRa.setPins(SS, RST, DIO0); if (!LoRa.begin(433E6)) { Serial.println("LoRa init failed. Check your connections."); while (true); // if failed, do nothing } //Serial.println("LoRa init succeeded."); } void loop() { if (millis() - lastSendTime > interval) { // Run if data available if (Ultrasonic_Sensor.available() > 0) { delay(4); // Check for packet header character 0xff if (Ultrasonic_Sensor.read() == 0xff) { // Insert header into array data_buffer[0] = 0xff; // Read remaining 3 characters of data and insert into array for (int i = 1; i < 4; i++) { data_buffer[i] = Ultrasonic_Sensor.read(); } //Compute checksum CS = data_buffer[0] + data_buffer[1] + data_buffer[2]; // If checksum is valid compose distance from data if (data_buffer[3] == CS) { distance = (data_buffer[1] << 8) + data_buffer[2]; // Print to serial monitor distance= distance / 10; // cm } } } // Serial.print("Distance: "); //Serial.println(distance); // Print the distance on the Serial Monitor (Ctrl+Shift+M): int waterLevelPer = map(distance,22,51, 100, 0); if(waterLevelPer<0) { waterLevelPer=0; } if (waterLevelPer>100) { waterLevelPer=100; } // Serial.print("Distance = "); // Serial.print(distance); // Serial.println(" cm"); // Serial.println(waterLevelPer); Mymessage = Mymessage + distance +","+ waterLevelPer; sendMessage(Mymessage); Mymessage = ""; //Serial.println("Sending " + message); lastSendTime = millis(); // timestamp the message interval = random(50) + 100; } // parse for a packet, and call onReceive with the result: onReceive(LoRa.parsePacket()); } void sendMessage(String outgoing) { LoRa.beginPacket(); // start packet LoRa.write(destination); // add destination address LoRa.write(localAddress); // add sender address LoRa.write(msgCount); // add message ID LoRa.write(outgoing.length()); // add payload length LoRa.print(outgoing); // add payload LoRa.endPacket(); // finish packet and send it msgCount++; // increment message ID } void onReceive(int packetSize) { if (packetSize == 0) return; // if there's no packet, return // read packet header bytes: int recipient = LoRa.read(); // recipient address byte sender = LoRa.read(); // sender address byte incomingMsgId = LoRa.read(); // incoming msg ID byte incomingLength = LoRa.read(); // incoming msg length String incoming = ""; while (LoRa.available()) { incoming += (char)LoRa.read(); } if (incomingLength != incoming.length()) { // check length for error //Serial.println("error: message length does not match length"); ; return; // skip rest of function } // if the recipient isn't this device or broadcast, if (recipient != localAddress && recipient != 0xFF) { // Serial.println("This message is not for me."); ; return; // skip rest of function } String q = getValue(incoming, ',', 0); buttonstat = q.toInt();// button status Serial.print("button state"); Serial.println(buttonstat); if(buttonstat==HIGH) { digitalWrite(Relay,HIGH); } else { digitalWrite(Relay,LOW); } incoming = ""; } String getValue(String data, char separator, int index) { int found = 0; int strIndex[] = { 0, -1 }; int maxIndex = data.length() - 1; for (int i = 0; i <= maxIndex && found <= index; i++) { if (data.charAt(i) == separator || i == maxIndex) { found++; strIndex[0] = strIndex[1] + 1; strIndex[1] = (i == maxIndex) ? i+1 : i; } } return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; } |
Finally, you can upload the transmitter and receiver side programs into your ESP32 development boards. In my case I have already uploaded the Transmitter and receiver programs. Next, you can start with the Blynk IoT Application setup on your Smartphone.
Blynk IoT App setup on Smartphone:
For this, first of all, you will need to install the IoT blynk in your smart phone. Make sure you log in with the same email that you used on your computer. Simply, open your Blynk application on your cell phone and select your project.
After this, things are pretty straightforward, you need to add a Gauge and two Switches and assign them the desired Datastreams. You don’t need to create the Datastreams, we already did it.
I assigned the Datastream V0 to the Gauge and Datastreams V1 and V2 to the Switches. It’s not compulsory to use the same widgets, you can select any other widget and a different button. All you need is to make sure; you assign the correct Datastreams.
If you face any issues then you can watch my video tutorial given below.