Wireless Water Level Indicator using LoRa and ESP32
Table of Contents
Wireless Water Level Indicator:
Wireless Water Level Indicator using LoRa and ESP32- In today’s article, you will learn how to make a long range Wireless Water Level Indicator using a waterproof Ultrasonic Sensor JSN SR04T/AJ-SR04M and a pair of LILYGO TTGO LoRa32 modules.
TTGO LoRa32 is 868MHz and is based on the LoRa SX1276 chip combined with ESP32 WiFi + Bluetooth and an I2C supported 0.96-inches OLED display module. So many things on this tiny board, its quite impressive. You don’t have to do any soldering, just upload the desired code and start monitoring anything you want.
When these modules are not in line-of-sight means when there are lots of obstacles in between then the range is 77 meters.
And when both the modules are in line-of-sight then the wireless communication range is more than 1Km. I practically demonstrated this in my getting started video on the LoRa32. So, if you have got any questions, regarding its technical specifications, how to install the required libraries, and how to write your first program, then I recommend you should read my getting started article on the TTGO LoRa32 Modules.
HC-SR04 Vs Waterproof Ultrasonic Sensor:
HC-SR04 and the waterproof JSN SR04T are both popular ultrasonic sensors that are commonly used in distance measurement applications. However, there are some differences between them that may make one a better choice depending on your specific needs.
The HC-SR04 is a low-cost ultrasonic sensor that can measure distances from 2cm to 4 meters with an accuracy of about 3mm. It has a working voltage range of 5V DC and consumes very low power. The HC-SR04 is easy to use, it can be easily interfaced with microcontrollers, and can be easily found in the market.
The JSN SR04T, on the other hand, is a more advanced ultrasonic sensor that has a longer sensing range of up to 6 meters and a higher accuracy of up to 1mm. It operates at a wider voltage range of 3-5V DC and has a built-in temperature compensation feature that helps to improve accuracy even in varying temperatures. However, the JSN SR04T is relatively more expensive and may be harder to find in the market.
In short, if you need a low-cost ultrasonic sensor with a shorter range and don’t require high accuracy, then the HC-SR04 may be a good choice. However, if you need a more advanced ultrasonic sensor with a longer range and higher accuracy, and you are willing to spend more, then the JSN SR04T could be a better option. For more technical specifications, features, and applications; read my article on the Waterproof Ultrasonic Sensor.
A few days ago, I created an IoT-based water level monitoring system using the same waterproof ultrasonic sensor. In that system, the water pump would automatically turn on and off. However, this time, I don’t want the water pump to automatically turn on/off. I want the control of the water pump to be in my hands and the hands of my family members, and I also don’t want us to be dependent on the internet.
The switch for the water pump is installed here and next to it I can install my water level indicator or monitor. Then, all the family members can check the water level in the water tank and turn the water pump on/off accordingly. So, this is exactly, what I am going to do. So, without any further delay, let’s get started!!!
Amazon Links:
*Disclosure: These are affiliate links. As an Amazon Associate I earn from qualifying purchases.
Wireless Water Level Indicator, Circuit Diagram:
Transmitter side circuit diagram:
The 5v and GND wires of the Waterproof Ultrasonic Sensor are connected to the 5V and GND pins on the LoRa32 module. Whereas the Trigger and Echo pins are connected to the GPIO pins 12 and 13 respectively.
TTGO LoRa32 Pins Diagram:
You can see the SSD1306 Oled display module SDA and SCL pins are connected to the Pins 04 and 15. And the SX1276 LoRa transceiver module MOSI, SCLK, CS, DIO, RST, and MISO pins are connected to pins 27, 5, 18, 26, 14, and 19. All the other pins are clearly labeled. You can see the 5V and 3.3V pins are available on both sides. We have got three GND pins. And all these other GPIO pins can be used for connecting input and output devices.
Receiver Side Circuit Diagram:
On the receiver side, we don’t need to do anything as the LoRa and Oled display module are already wired up. Now, let’s go ahead and take a look at the transmitter and receiver side programming.
Altium Designer + Altium 365 + Octopart:
Altium 365 lets you hold the fastest design reviews ever. Share your designs from anywhere and with anyone with a single click. it’s easy, leave a comment tagging your teammate and they’ll instantly receive an email with a link to the design. Anyone you invite can open the design using a web browser. Using the browser interface, you’re able to comment, markup, cross probe, inspect, and more. Comments are attached directly to the project, making them viewable within Altium designer as well as through the browser interface. Design, share, and manufacture, all in the same space with nothing extra to install or configure. Connect to the platform directly from Altium Designer without changing how you already design electronics. Altium 365 requires no additional licenses and comes included with your subscription plan.
Get real-time component insights as you design with Octopart built into Altium 365. Octopart is the fastest search engine for electronic parts and gives you the most up-to-date part data like specs, datasheets, cad models, and how much the part costs at different amounts etc. Right in the design environment so you can focus on your designs. Start with Altium Designer and Activate Altium 365. Search for electronic parts on Octopart.
Wireless Water Level Indicator Programming:
If you already have an ESP32 board installed, then there shouldn’t be any problem, you just need to install these libraries.
But if you don’t have an ESP32 board installed, then you need to install it first, and the complete process is explained in the article Getting started with ESP32 WiFi + Bluetooth Module.
Anyway, once you install the ESP32 board then you can go to the Tools menu, Board, ESP32 Arduino, and from the boards list you can select TTGO LoRa32-OLED.
Transmitter Side LoRa32 Code:
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 |
#include <SPI.h> #include <LoRa.h> //Libraries for OLED Display #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> //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 #define trigPin 12 #define echoPin 13 int distance; //433E6 for Asia //866E6 for Europe //915E6 for North America #define BAND 866E6 //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 //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; Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST); void setup() { //initialize Serial Monitor Serial.begin(115200); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); //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(1); display.setCursor(0,0); display.print("LORA SENDER "); display.display(); Serial.println("LoRa Sender Test"); //SPI LoRa pins SPI.begin(SCK, MISO, MOSI, SS); //setup LoRa transceiver module LoRa.setPins(SS, RST, DIO0); if (!LoRa.begin(BAND)) { Serial.println("Starting LoRa failed!"); while (1); } Serial.println("LoRa Initializing OK!"); display.setCursor(0,10); display.print("LoRa Initializing OK!"); display.display(); delay(2000); } void loop() { if (millis() - lastSendTime > interval) { // Clear the trigPin by setting it LOW: digitalWrite(trigPin, LOW); delayMicroseconds(5); // Trigger the sensor by setting the trigPin high for 10 microseconds: digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // Read the echoPin. pulseIn() returns the duration (length of the pulse) in microseconds: int duration = pulseIn(echoPin, HIGH); // Calculate the distance: // distance = duration*0.034/2; distance = (duration/2) / 29.1; // 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); display.clearDisplay(); display.setCursor(0,0); display.println("LORA Transmitter"); display.setCursor(0,20); display.setTextSize(1); display.print("LoRa packet Sending"); display.setCursor(0,30); display.print(Mymessage); display.display(); delay(1000); delay(100); Mymessage = ""; //Serial.println("Sending " + message); lastSendTime = millis(); // timestamp the message interval = random(50) + 100; } } 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 } |
If you watch my previous videos or read my articles on the LoRa32 and IoT based Water Level monitoring system, then you would know how I combined the two codes.
You can clearly see, I am using the same pins definitions. I am using the same local address and destination address. If you want to monitor multiple water tanks then you will have to use those addresses. You can read my article on how to use multiple LoRa transmitters with a single LoRa receiver.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
if (millis() - lastSendTime > interval) { // Clear the trigPin by setting it LOW: digitalWrite(trigPin, LOW); delayMicroseconds(5); // Trigger the sensor by setting the trigPin high for 10 microseconds: digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); // Read the echoPin. pulseIn() returns the duration (length of the pulse) in microseconds: int duration = pulseIn(echoPin, HIGH); // Calculate the distance: // distance = duration*0.034/2; distance = (duration/2) / 29.1; // Print the distance on the Serial Monitor (Ctrl+Shift+M): int waterLevelPer = map(distance,22,51, 100, 0); |
This set of instructions is used to calculate the water level distance and then using the map function we convert the distance information into water level percentage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Mymessage = Mymessage + distance +","+ waterLevelPer; sendMessage(Mymessage); display.clearDisplay(); display.setCursor(0,0); display.println("LORA Transmitter"); display.setCursor(0,20); display.setTextSize(1); display.print("LoRa packet Sending"); display.setCursor(0,30); display.print(Mymessage); display.display(); delay(1000); delay(100); Mymessage = ""; //Serial.println("Sending " + message); lastSendTime = millis(); // timestamp the message interval = random(50) + 100; |
Then we make a message consisting of the distance and water level percentage. You can see I am using comma as the delimiter. It will help me in splitting the message to retrieve the distance and water level percentage values. Anyways, finally I send the message, and at the same time I also print this message on the Oled display module. Now, let’s go ahead and take a look at the receiver side programming.
Receiver Side LoRa32 Code:
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 |
#include <SPI.h> #include <LoRa.h> //Libraries for OLED Display #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> //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 #define led 12 //433E6 for Asia //866E6 for Europe //915E6 for North America #define BAND 866E6 //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 = ""; void setup() { //initialize Serial Monitor Serial.begin(115200); //reset OLED display via software pinMode(OLED_RST, OUTPUT); pinMode(led,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(BAND)) { Serial.println("Starting LoRa failed!"); while (1); } Serial.println("LoRa Initializing OK!"); display.setCursor(0,10); display.println("LoRa Initializing OK!"); display.display(); } void loop() { // 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(); } 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]) : ""; } |
On the receiver side we have got the same libraries and the same pins definitions. So, let’s go to the loop() function.
We simply read the packet, and then using the getValue() function we simply split the message using comma as the delimiter and the retrieved values are stored in variables distance and waterLevelPer. And then finally, these values are printed on the Oled display module.
getValue() function is a user-defined function and its job is to split the message. You can select any character as the delimiter. So, that’s all about the programming. I have already uploaded these programs and now let’s start our practical demonstration.
Wireless Water Level Indicator Practical Demonstration:
I have powered up the transmitter side using my designed 5V and 3A power supply and my created 4S lithium Ion Battery. This setup makes the transmitter side completely portable. You can use any 5V regulated power supply. And additionally, you can also connect a lithium-ion battery, LoRa32 has an onboard charging circuit. So, if in case there is any power failure the communication won’t be interrupted.
You can also use a solar panel. All you need is to connect your solar panel to this 5V and 3A power supply, it can take input voltages up to 28 volts, and at the output, it gives regulated 5V and 3A. So, during the day time, its going to use Solar energy and during the night time or cloudy weather, it’s going to use a lithium-ion battery as the power source.
Since nothing is connected on the receiver side except the LoRa and Oled display module, so we can use a single cell lithium ion battery or even a cell phone charger or any other 5V power supply.
Anyways, you can already see the distance and water level percentage on the receiver side. Let me move my hand in front of the Ultrasonic Sensor and you will see a change in the distance and water level percentage.
When I started to move my hand in front of the Ultrasonic Sensor the distance and Water Level Percentage values started to change, as you can see in the image above.
Let’s think of this Bucket as the Water Tank and we have to measure the water level inside this Tank. The Ultrasonic Sensor is in its place. So, let’s go ahead and fill this Water Tank.
So, that’s all about the Wireless Water Level Indicator.
Watch Video Tutorial:
Good day Shahzada,
It is working perfecty!
Thank you so much,
Kind regards,
Kobus
Hello Shahzada, I’m experimenting with a similar item but wondering if there is a way to have these working only on a battery and take the reading and transmit it every 5-15 minutes to save on power. I realize in your example you use an Arduino but can these stand alone with a timer set to do this on just a battery.
My aim is to have many of these transmitting to a central Raspberry Pi that will transmit via cell internet.
Great project you have especially for those of us who have basements and like to check on their sump occasionally via remote location
Hi Shahzada, excellent and very useful project indeed. One question, How can I send the level signal to the Blynk platform from the receiver module? Appreciate if you can modify the receiver code and include this feature also.
Hi great project. Have you considered making this send to multiple sender/receivers so they can send over longer distances. This might be especially good for where the line of sight is shorter like in cities. I’m currently working on a similar project so would be interested in sharing thoughts. I got the battery this working