WiFi Mesh Powered Plant Watering System

by GuillenG in Circuits > Electronics

69 Views, 1 Favorites, 0 Comments

WiFi Mesh Powered Plant Watering System

cover1.JPG

ESP-MESH is a networking protocol built atop the Wi-Fi protocol, and allows several ESP32S3 devices (referred to as nodes) spread over a large physical area (both indoors and outdoors) to be interconnected under a single WLAN (Wireless Local-Area Network). This networs allows me to monitor my plants with humidity, temperature, light, and sound sensors, and when they need water I can activate a water pump remotely.

Main advantage of ESP-MESH to other technologies is self-organizing and self-healing meaning the network can be built and maintained autonomously, so I can't worry about losing contact with my plants.

Supplies

block_diagram.png

Hardware components:

  1. XIAO ESP32S3 (2 pcs)
  2. 2.4GHz FPC Antenna (1.16dBi) for XIAO ESP32-S3 (2 pcs)
  3. Grove Shield for XIAO
  4. Grove - LCD RGB Backlight
  5. Grove - Temp and Humi Sensor (SHT31)
  6. Grove - Light Sensor
  7. Grove - Sound Sensor
  8. Grove- Relay
  9. Grove - Touch Sensor
  10. Moisture Sensor
  11. Water Pump (5v)

Software:

  1. Arduino IDE
  2. Visual Studio Code

Hand Tools:

  1. Silicone gun
  2. Clear PVC tubing
  3. Support base
  4. Water container (1L)
  5. Box (2pcs)

Building the Water Supplier

water_supplier1.JPG
water_supplier2.JPG

This is the electromechanical part of the system that supplies water to our plant. We'll use the following devices: a water container, a support base, clear PVC tubing, and a water pump. In my system, the support is 30 cm high and made of wood. I also glued a bit of silicone to the support's top to adhere the clear PVC tubing, which aims directly at the plant's stem. The other end of the hose is connected to the water pump, which is powered by 5v.

Node 1 - Hardware

schematic_diagram_node1.jpg
node-1_a.JPG
node-1_b.JPG

In this Node 1, its operation is divided into two parts: the first is as a transmitter, and the second is as a receiver.

Node 1's functions as a transmitter are:

  1. The Grove - Temp and Humidity Sensor monitors the temperature in degrees Celsius and the humidity percentage of the environment where our plant is located.
  2. The Grove - Light Sensor monitors the ambient light. In this project, I have configured a scale of 0 to 100, and it helps me know the amount of light my plant is exposed to.
  3. The Grove - Sound Sensor monitors the ambient noise, and it helps me know if there is any natural or artificial noise near my plant that could put my plant at risk.
  4. The moisture sensor measures the humidity of my plant's soil, and I have calibrated it to give me values ​​between 0 and 100%. This way, I can know if my plant is at risk of having dry soil.
  5. The XIAO ESP32S3 board captures and packages data on environmental temperature and humidity, soil moisture, light, and sound.
  6. Node 1 sends this data to Node 2 over our Mesh WiFi network.


Node 1's functions as a receiver are:

  1. It receives data from Node 2.
  2. This data contains the information to determine whether the Grove Relay should be activated or deactivated.
  3. If the data received is 100, then the Grove Relay is activated, and the mini water pump turns on for 5 seconds.
  4. If the data received is 50, then the Grove Relay is deactivated.

Node 1 - Software

As I said, advantage of ESP-MESH, the nodes don’t need to connect to a central node. Nodes are responsible for relaying each others transmissions. This allows multiple devices to spread over a large physical area as you wish.


All the information and details about to install the XIAO ESP32S3 board, firmware, the painlessMesh library, and other Grove sensors libraries you can follow the next link: https://wiki.seeedstudio.com/xiao_esp32s3_wifi_usage/


Tips:

  1. Please, before uploading the code, you can set up the MESH_PREFIX (it’s like the name of the MESH network) and the MESH_PASSWORD variables (you can set it to whatever you like).
  2. It is recommended to avoid using delay() in the mesh network code. To maintain the mesh, some tasks need to be performed in the background. Using delay() will stop these tasks from happening and can cause the mesh to lose stability/fall apart.
  3. The MESH_PORT refers to the the TCP port that you want the mesh server to run on. The default is 5555.
  4. The sendMessage() function sends a message to all nodes in the message network (broadcast).
  5. Every time a new message is sent, the code changes the interval between messages (one to five seconds).
  6. The receivedCallback() function prints the message sender (from) and the content of the message (msg.c_str()).


Node1.ino


// NODE 1
// AUTHOR: GUILLERMO PEREZ GUILLEN

#include "painlessMesh.h"

#include <Arduino.h>
#include <Wire.h>
#include "SHT31.h"

SHT31 sht31 = SHT31();

#define MESH_PREFIX "Seeed-Network"
#define MESH_PASSWORD "123456789"
#define MESH_PORT 5555

Scheduler userScheduler; // to control your personal task
painlessMesh mesh;

char *strings[6]; //used in receive message
char *ptr = NULL; //used in receive message

int WaterPump; // water pump
const int relayPin = D0; // relay pin

// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain

Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

// SEND MESSAGE
void sendMessage() {
// light sensor code
int light = analogRead(A9);
light = map(light, 0, 2500, 0, 100);
String lightString = String(light);
int flame;

// moisture sensor code
int moisture = map(analogRead(A1), 0, 4095, 150, 0);
String moistureString = String(moisture);

// sound sensor code
int sum = 0;
for(int i=0; i<32; i++)
{
sum += analogRead(A2);
}
sum >>= 5;
//Serial.println(sum);
int sound = map(sum, 0, 4095, 150, 0);
String soundString = String(sound);

// humidity and temperature code
int temp = sht31.getTemperature();
String temperatureString = String(temp);

int hum = sht31.getHumidity(); // humidity sensor
String humidityString = String(hum);

String combinedString = temperatureString + "," + humidityString + "," + lightString + "," + soundString + "," + moistureString + ",";
Serial.println(combinedString);
String msg = combinedString; //added
// String msg = "Hello from node 1";
msg += mesh.getNodeId();
mesh.sendBroadcast( msg );
taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
}

char* unconstchar(const char* s) {
if(!s)
return NULL;
int i;
char* res = NULL;
res = (char*) malloc(strlen(s)+1);
if(!res){
fprintf(stderr, "Memory Allocation Failed! Exiting...\n");
exit(EXIT_FAILURE);
} else{
for (i = 0; s[i] != '\0'; i++) {
res[i] = s[i];
}
res[i] = '\0';
return res;
}
}

// RECEIVE MESSAGE
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());

char* dedos = unconstchar(msg.c_str());
Serial.printf("%s",dedos);

byte index = 0;
ptr = strtok(dedos, ","); // delimiter
while (ptr != NULL)
{
strings[index] = ptr;
index++;
ptr = strtok(NULL, ",");
}

Serial.println("The Pieces separated by strtok()");
for (int n = 0; n < index; n++)
{
Serial.print(n);
Serial.print(" ");
Serial.println(strings[n]);
}

// water pump code
WaterPump = atoi(strings[0]);
if(WaterPump==100)
{
// turn Relay on:
digitalWrite(relayPin, HIGH);
}
else
{
// turn Relay off:
digitalWrite(relayPin, LOW);
}
}

void newConnectionCallback(uint32_t nodeId) {
Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
}

void changedConnectionCallback() {
Serial.printf("Changed connections\n");
}

void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
Serial.begin(115200);

// initialize the Relay pin as an output:
pinMode(relayPin, OUTPUT);

while(!Serial);
Serial.println("begin...");
sht31.begin();

mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages

mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

userScheduler.addTask( taskSendMessage );
taskSendMessage.enable();
}

void loop() {
// it will run the user scheduler as well
mesh.update();
}

Node 2 - Hardware

schematic_diagram_node2.jpg
node-2_a.jpg
node-2_b.jpg

Node 2's functions as a receiver are:

  1. It receives data from Node 1.
  2. This data contains the following information: ambient temperature, ambient humidity, soil moisture, ambient light, and sound.
  3. The data is organized to be displayed to the user on the Grove LCD RGB Backlight.
  4. If the soil moisture content of the plant is greater than 40%, the backlight turns green.
  5. If the soil moisture content of the plant is less than 40%, the backlight turns red. This is interpreted as an alarm to the user, letting them know that the soil is drying out.


Node 2's functions as a transmitter are:

  1. The user can press the touch sensor, and then Node 2 sends the number 100 to Node 1. This number is interpreted as the command for Node 2 to activate the water pump.
  2. While the touch sensor is not pressed, Node 2 sends the number 50 to Node 1. In this case, the water pump is not activated.
  3. Once the plant is watered, the backlight will return to green.

Node 2 - Software

The code for Node 2 is similar to the code for Node 1. The difference is as follows:

  1. We have configured Node 2 to display the data sent to us by Node 1 on a screen.
  2. We also configured the touch sensor to activate the water pump located on Node 1.
  3. Don't forget to set up the MESH_PREFIX and the MESH_PASSWORD variables.


Node2.ino

// NODE 2
// AUTHOR: GUILLERMO PEREZ GUILLEN

#include <Arduino.h>
#include "painlessMesh.h"
#include <Wire.h>
#include "rgb_lcd.h"

rgb_lcd lcd;

const int colorR = 0;
const int colorG = 255;
const int colorB = 0;

const int buttonPin = D1; // the number of the pushbutton pin
int buttonState = 0; // variable for reading the pushbutton status

#define MESH_PREFIX "Seeed-Network"
#define MESH_PASSWORD "123456789"
#define MESH_PORT 5555

//const int Buzzer=8; // buzzer
Scheduler userScheduler; // to control your personal task
painlessMesh mesh;

char *strings[6];
char *ptr = NULL;

int Humidity; // humidity sensor
int Temperature; // temperature sensor
int Light; // light sensor
int Sound; // sound sensor
int Moisture; // flame sensor

// User stub
void sendMessage() ; // Prototype so PlatformIO doesn't complain

Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );

// SEND MESSAGE
void sendMessage() {
int message;
// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);

// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
if (buttonState == HIGH) {
// turn Relay on:
message = 100;
} else {
// turn Relay off:
message = 50;
}

String messageString = String(message);
String combinedString = messageString + ",";
String msg = combinedString;

msg += mesh.getNodeId();
mesh.sendBroadcast( msg );
taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
}

char* unconstchar(const char* s) {
if(!s)
return NULL;
int i;
char* res = NULL;
res = (char*) malloc(strlen(s)+1);
if(!res){
fprintf(stderr, "Memory Allocation Failed! Exiting...\n");
exit(EXIT_FAILURE);
} else{
for (i = 0; s[i] != '\0'; i++) {
res[i] = s[i];
}
res[i] = '\0';
return res;
}
}

// RECEIVE MESSAGE
void receivedCallback( uint32_t from, String &msg ) {
Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());

char* dedos = unconstchar(msg.c_str());
Serial.printf("%s",dedos);

byte index = 0;
ptr = strtok(dedos, ","); // delimiter
while (ptr != NULL)
{
strings[index] = ptr;
index++;
ptr = strtok(NULL, ",");
}

Serial.println("The Pieces separated by strtok()");
for (int n = 0; n < index; n++)
{
Serial.print(n);
Serial.print(" ");
Serial.println(strings[n]);
}

Temperature = atoi(strings[0]);
Humidity = atoi(strings[1]);
Light = atoi(strings[2]);
Sound = atoi(strings[3]);
Moisture = atoi(strings[4]);
if(Moisture<=40)
{
lcd.setRGB(255, 0, 0); //red
}
else
{
lcd.setRGB(0, 255, 0); //green
}

// set the cursor to column 0, line 0
// (note: line 0 is the first row, since counting begins with 0):
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("T=");
lcd.setCursor(2, 0);
lcd.print(Temperature);

lcd.setCursor(5, 0);
lcd.print("H=");
lcd.setCursor(7, 0);
lcd.print(Humidity);

lcd.setCursor(10, 0);
lcd.print("MS=");
lcd.setCursor(13, 0);
lcd.print(Moisture);

lcd.setCursor(0, 1);
lcd.print("Lx=");
lcd.setCursor(3, 1);
lcd.print(Light);

lcd.setCursor(7, 1);
lcd.print("dB=");
lcd.setCursor(10, 1);
lcd.print(Sound);

Serial.print("Temperature = ");
Serial.print(Temperature);
Serial.println("°C");
Serial.print("Humidity = ");
Serial.print(Humidity);
Serial.println("%");
Serial.print("Light = ");
Serial.println(Light);
Serial.print("Sound = ");
Serial.println(Sound);
Serial.print("Moisture = ");
Serial.println(Moisture);
}

void newConnectionCallback(uint32_t nodeId) {
Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
}

void changedConnectionCallback() {
Serial.printf("Changed connections\n");
}

void nodeTimeAdjustedCallback(int32_t offset) {
Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
Serial.begin(115200);

// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT_PULLUP);

mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages

mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
mesh.onReceive(&receivedCallback);
mesh.onNewConnection(&newConnectionCallback);
mesh.onChangedConnections(&changedConnectionCallback);
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);

userScheduler.addTask( taskSendMessage );
taskSendMessage.enable();

// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
lcd.setRGB(colorR, colorG, colorB);

}

void loop() {
// it will run the user scheduler as well
mesh.update();
}


Test

WiFi Mesh Powered Plant Watering System