--- title: 'Automatic Watering Plant System' disqus: hackmd --- ###### tags: `ESP32C3` `Node-Red` `Mosquitto` `MQTT` `MicroPython` `Tutorial` `ESP32` `Dashboard` `Thonny IDE` `Wi-Fi` `Flashing` Automatic Watering Plant System === > Author: Nu11ifier > Summer 2022 This project tutorial explains how to set up an automatic watering plant system using a microcontroller ESP32-C3 and Wi-Fi as the connectivity. The microcontroller will be publishing sensor data to a specific topic to the MQTT-broker and Node-RED will be subscribing to a specific topic from the MQTT-broker and then displaying the sensor information in a dashboard in the form of graphs/charts. The time to implement the whole project takes approximately 1-3 hours, not considering any problems in the process using this tutorial. Initially the project took around 20-30 hours to implement. # Table of Contents [TOC] # Objective The automatic watering plant system project build was choosen so that watering plants becomes automatic and also to keep better track of plants health and enviroment. The purpose of this project is to make your plants love you more without doing any hard work. This tutorial will aid individuals in setting up an automatic watering plant system and learn how the code, MQTT-broker and NodeRed works to be able to publish and subscribe to sensor data and then display the data visually through graphs/charts. # Material --- * **ESP32-C3-DevKitM-1U**: Main microcontroller for collecting and publishing sensor data ![](https://i.imgur.com/skm98S0.png =250x) * **HC-SR04 Ultrasonic sensor**: Measure water levels based on the sound reflection from the sensor to the water and back ![](https://i.imgur.com/MtLSlt6.png =250x) * **DHT11 Temperature/Humidity sensor**: Measure the temperature and humidity ![](https://i.imgur.com/K5ap6sF.png =250x) * **Soil Moisture Sensor**: Measure how moist the soil is ![](https://i.imgur.com/APZ6rmB.png =250x) * **3-5V Water Pump**: Pump inside the tank ![](https://i.imgur.com/LQ8sG3V.png =250x) * **5V Relay Module**: Trigger the water pump when relay on ![](https://i.imgur.com/TTAU910.jpg =250x) * **Clear Vinyl Tube**: For the 5V water pump ![](https://i.imgur.com/el150AC.png =250x) * **Jumper Wires**: To connect the circuit ![](https://i.imgur.com/8GqQDjW.png =250x) * **Breadboard**: Makes wiring circuit easier ![](https://i.imgur.com/CwSiSll.jpg =250x) | Part | Price(€/kr) | Purchased Link | | -------- | -------- | -------- | | **ESP32-C3-DevKitM-1U** | 9.9€/106kr | [ESP32-C3-DevKitM-1U](https://www.digikey.com/en/products/detail/espressif-systems/ESP32-C3-DEVKITM-1U/15198974 ) | | **HC-SR04** | 5.3€/57kr | [HC-SR04](https://www.digikey.se/sv/products/detail/sparkfun-electronics/SEN-15569/10384560?s=N4IgTCBcDaIBYGMC0BnATgBgCwgLoF8g ) | | **DHT11** | 2.6€/28kr | [DHT11](https://sizable.se/P.WGXR4/DHT11-Temperatur-och-luftfuktighetssensor) | | **Soil Moisture Sensor** | 3.2€/34kr | [Soil Moisture Sensor](https://sizable.se/P.8AS66/Capacitive-Soil-Moisture-Sensor ) | | **3-5V Water Pump (Package (1))** | 10.9€/117kr | [3-5V Water Pump (Package*)](https://www.amazon.com/Submersible-Flexible-Aquariums-Fountain-Hydroponics/dp/B09SF1C9DQ/ref=sr_1_17?keywords=5v+water+pump&qid=1656611592&sr=8-17 ) | | **Clear Vinyl Tube (Package (1))** | 10.9€/117kr | [Clear Vinyl Tube (Package*)](https://www.amazon.com/Submersible-Flexible-Aquariums-Fountain-Hydroponics/dp/B09SF1C9DQ/ref=sr_1_17?keywords=5v+water+pump&qid=1656611592&sr=8-17 ) | | **5V Relay Module** | 7.7€/82kr | [5V Relay Module](https://www.amazon.com/WINGONEER-KY-019-Channel-Module-arduino/dp/B06XHJ2PBJ/ref=pd_di_sccai_cn_sccl_2_11/134-0898683-4936248?pd_rd_w=ZtWq1&content-id=amzn1.sym.c32bd24a-4ef0-4e42-bb31-8027e5f6a185&pf_rd_p=c32bd24a-4ef0-4e42-bb31-8027e5f6a185&pf_rd_r=T339Z4VXTQVW2VAGR130&pd_rd_wg=Esjx7&pd_rd_r=ae3012f2-8666-4b36-97b0-dae39202cf8d&pd_rd_i=B06XHJ2PBJ&psc=1 ) | | **Jumper Wires Male-Male (20CM)** | 2.2€/24kr | [Jumper Wires Male-Male (20CM)](https://sizable.se/P.K2HUW/Dupont-kopplingskablar-Hane-Hane-40-pin ) | | **Jumper Wires Male-Female (20CM)**| 2.2€/24kr | [Jumper Wires Male-Female (20CM)](https://sizable.se/P.3V1BE/Dupont-kopplingskablar-Hane-Hona-40-pin ) | | **Jumper Wires Female-Female (20CM)**| 2.2€/24kr | [Jumper Wires Female-Female (20CM)](https://sizable.se/P.4LTWU/Dupont-kopplingskablar-Hona-Hona-40-pin ) | | **Breadboard** | 3.2€/34kr | [Breadboard](https://sizable.se/P.SFGYT/Kopplingsdack-med-400-punkter)| ## Computer and Device Setup > Operative System: Windows 10 ### ESP32-C3 WiFi Security ESP32-C3 supports different Wi-Fi security protocols called Protected Access (WPA). The newest Wi-Fi security protocol that the ESP32-C3 supports is WPA3 which prevents e.g dictionary attacks, which WPA2 do not. ### Prerequisites (Download and Install Required Software) The setup will only be ran locally. To allow remote setup, please refer to the documentation of each service below. Download and install the prerequisities before continuing with other steps: :::info - **MicroPython Firmware (ESP32-C3)** - [Reference Page](http://www.micropython.org/download/esp32c3/) - **NodeJS (Latest Current Version)** - [Reference Page](https://nodejs.org/en/) - **Python (Latest)** - [Reference Page]((https://www.python.org/downloads/)) ::: ### Flashing the ESP32-C3 with MicroPython ESP32-C3 does not come with MicroPython out of the box, therefore flashing the ESP32-C3 with MicroPython firmware is required to be able to program it with python. The steps for flashing MicroPython are shown below: Download and install the latest MicroPython firmware for the ESP32-C3 found on the official page (*Check Prerequisites - MicroPython Firmware*). Go to [ESP WEB FLASHER](https://nabucasa.github.io/esp-web-flasher/). Connect a USB cable between the ESP32-C3 and the PC, a pop up will appear with the port which the device is connected to and connect. ![](https://i.imgur.com/Bj2S1Jr.png =250x) Set the baud rate to 921600 and and press connect. ![](https://i.imgur.com/LFSIk0O.png) After connecting the ESP32-C3 press 'Erase' and wait until the operation is complete in the terminal. Thereafter on the first row press 'Choose a file...' and choose the previously downloaded ESP32-C3 MicroPython firmware. Set the offset to 0 and press 'Program'. ![](https://i.imgur.com/g2DgPf8.png) When the terminal displays that the flashing is complete, unplug the ESP32-C3 usb cable and close the flashing website and then replug the ESP32-C3 usb cable. Flashing MicroPython on the ESP32-C3 microcontroller is now done! Here is a MicroPython quick reference for ESP32 [MicroPython Quick Reference](https://docs.micropython.org/en/latest/esp32/quickref.html#networking). ### Setting up Thonny IDE :::info **Thonny IDE (Python)** - [Reference Page](https://thonny.org/) ::: After Thonny IDE is downloaded, make sure the USB cable is connected and press 'Run' in Thonny IDE then 'Select interpreter...' then navigate to 'Interpreter' tab and choose MicroPython (ESP32) as the interpreter and the connected ESP32-C3 port then press 'OK'. Thonny IDE is now setup. ![](https://i.imgur.com/YWtHOil.png) Code can then be written and uploaded to the ESP32-C3 by pressing 'File' then 'Save as...' which will display two options of where you want to save the python file, the same window prompt will be displayed if you want to open a file with 'Open...'. ![](https://i.imgur.com/U8GtGwp.png) ### Setting up Node-RED :::info **Node-RED** - [Reference Page](https://nodered.org/docs/getting-started/local) ::: Open a new CMD/terminal in Windows and type the command: > npm install -g --unsafe-perm node-red After the download has finished run the command below to start Node-RED: > node-red This will start a local instance of Node-RED which can be opened by typing the local IP and the port number (127.0.0.1:1880) in a browser. ### Setting up Moquitto MQTT broker :::info **Mosquitto MQTT** - [Reference Page](https://mosquitto.org/download/) ::: Download the appropriate Mosquitto MQTT binary from the reference page. Follow the steps of the installer. Before running mosquitto, locate the mosquitto configuration file 'moquitto.conf' and create a copy of the default configuration file and add these lines: > listener 1883 0.0.0.0 > allow_anonymous true > max_keepalive 0 Then open a new CMD/terminal in Windows and navigate to the mosquitto folder and type the command: > mosquitto -v -c "(path to the configuration file)" This will start the mosquitto MQTT broker. ## Putting everything together ### Circuit Diagram ESP32-C3-DevKitM-1 [Hardware Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html#user-guide-c3-devkitm-1-v1-board-front). ESP32-C3-DevKitM-1 Pinout diagram: ![](https://i.imgur.com/rPLPCmN.png) :::info The image below displays the wiring/circuit diagram used for this project. (Note: place the ESP32-C3 at the end of the breadboard with the usb port directed towards the outer side and follow the pinout diagram found here [Hardware Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html#user-guide-c3-devkitm-1-v1-board-front) or use the pinout image above) - **Black Wire - GND** - **Red Wire - VCC** - **Yellow Wire - Signal** - **Green Wire - Echo** - **Brown Wire - Trigger** **Sensor/Module Pins to ESP32-C3-DevKitM-1U Pins:** * **HC-SR04 Ultrasonic sensor**: GND <-> GND Echo <-> PIN 7 Trigger <-> PIN 6 VCC <-> 5V * **DHT11 Temperature/Humidity sensor**: GND <-> GND VCC <-> 5V Signal <-> Pin 3 * **Soil Moisture Sensor**: GND <-> GND VCC <-> 3.3V Signal <-> ADC Pin 2 * **3-5V Water Pump**: GND <-> GND VCC <-> Relay Module (NO - Normally Open) * **5V Relay Module**: COM <-> GND VCC <-> 5V Signal <-> Pin 0 ![](https://i.imgur.com/qUScv4S.png) ::: ## The code The main.py file contains functions that handle each sensor and the neccessary logic of the program. The functions are commented with some explanation of what they do. :::info The program first connects to an Wi-Fi access point. Five different topics are created for each data piece that are going to be published to the MQTT-broker. The program then tries to connect to the MQTT-broker that was previously setup. Thereafter each sensor is initialized with the correct pins. The while loop takes measurments with each sensor and stores it in variables which are then published to the MQTT-broker. The relay module will be triggered which activates the water pump only when the soil moisture percentage is lower than 20% (can be changed) and if the water levels are not 0%. Each time the relay module is triggered it also adds to a pump counter. The code that is running excluding functions and imports is displayed below: ::: ```python= #Main #Wifi Connect do_connect() #MQTT # The MQTT topics that we publish data to topic_pub_temperature = 'esp32c3/sensors/temperature' topic_pub_soil_moisture = 'esp32c3/sensors/soil_moisture' topic_pub_water_level = 'esp32c3/sensors/water_level' topic_pub_humidity = 'esp32c3/sensors/humidity' topic_pub_pump_counter = 'esp32c3/sensors/pump_counter' try: client = connect_mqtt(config.CLIENT_ID, config.MQTT_BROKER) except OSError as e: restart_and_reconnect() #Sensor initialize try: print('-- Initializing sensors... --') ultra_sonic_sensor = HCSR04(trigger_pin=6, echo_pin=7, echo_timeout_us=10000) print('> Ultra Sonic sensor connected!') soil_moisture_sensor = ADC(Pin(2)) # Analog-Digital Converter soil_moisture_sensor.atten(ADC.ATTN_11DB) # This means we want to read voltage from 0 to 3.3V. This corresponds to setting the attenuation ratio of 11db. For that, we use the atten() method and pass as argument: ADC.ATTN_11DB. print('> Soil Moisture sensor connected!') humidity_temperature_sensor = dht.DHT11(Pin(3)) print('> Humidity/Temperature sensor connected!') relay_module = Pin(0, Pin.OUT) print('> Relay Module connected!') print("-- All sensors connected! --") except OSError as e: print('Failed to initialize sensors! Error:', e) sleep(3) # wait 3 secodns before starting #cycles = 0 # cycles of the loop pump_counter = 0 while True: humidity_temperature_sensor.measure() humidity = measure_humidity(humidity_temperature_sensor) client.publish(topic=topic_pub_humidity, msg=str(humidity)) client.check_msg() sleep(0.2) temperature = measure_temperature(humidity_temperature_sensor,2) client.publish(topic=topic_pub_temperature, msg=str(temperature)) client.check_msg() sleep(0.2) soil_moisture = measure_soil_moisture(soil_moisture_sensor) #Before sleep(0.2) water_level = measure_water_level(ultra_sonic_sensor,2, 16) #Before sleep(0.2) if(pump_water(relay_module, 3, water_level, soil_moisture)): #turn on for 3 seconds pump_counter += 1 soil_moisture = measure_soil_moisture(soil_moisture_sensor) #After client.publish(topic=topic_pub_soil_moisture, msg=str(int(soil_moisture))) client.check_msg() sleep(0.2) water_level = measure_water_level(ultra_sonic_sensor,2, 16) #After client.publish(topic=topic_pub_water_level, msg=str(int(water_level))) client.check_msg() sleep(0.2) client.publish(topic=topic_pub_pump_counter, msg=str(pump_counter)) client.check_msg() #cycles += 1 sleep(600) #sleep for 10 minutes #deepsleep(10000) #Can be set to deepsleep instead of sleep to reduce power consumption (It will restart the micro-controller after the time has expired) ``` #### Thonny IDE main.py Console Output ![](https://i.imgur.com/DSgfgTm.png) #### Configuration file: config.py :::info Use 2.4 GHz frequency band Wi-Fi since micro controllers often only support 2.4 GHz and not 5 GHz. ::: ```python= # WiFi credentials WIFI_SSID = 'Your WiFi Name' WIFI_PASS = 'Your WiFi Password' # MQTT MOSQUITTO CLIENT_ID = 'Name of the client that will connect to the MQTT-broker' MQTT_BROKER = 'IP of the local MQTT-broker Example: 192.168.0.144' ``` #### Main file: main.py ```python= import os from machine import Pin, ADC, reset, deepsleep from time import sleep import dht from hcsr04 import HCSR04 from mqtt import MQTTClient import boot import config import gc gc.collect() #The remap function takes a value which is in between a range of two values and turns it into percentage, based on the two value range and the initial given value. def remap(value, leftMin, leftMax, rightMin, rightMax): leftSpan = leftMax - leftMin rightSpan = rightMax - rightMin valueScaled = float(value - leftMin) / float(leftSpan) return rightMin + (valueScaled * rightSpan) #sensor - sensor used #sensor_offset - the distance between a full water tank and the sensor #water_contrainer_height - height of the water tank container def measure_water_level(sensor,sensor_offset, water_container_height): distance_value = sensor.distance_cm() #print('Distance:', distance_value, 'cm') water_level_value = remap(distance_value - sensor_offset,1, water_container_height, 100, 1) if(water_level_value >= 100): water_level_value = 100 print('Water Level:', water_level_value, '%') elif(water_level_value <= 0): water_level_value = 0 print('Water Level:', water_level_value, '%') else: print('Water Level:', water_level_value, '%') return water_level_value #sensor - sensor used def measure_soil_moisture(sensor): soil_moisture_value = remap(sensor.read(),2075, 4095, 100, 0) #soil_moisture_value = soil_moisture_sensor.read() if(soil_moisture_value >= 100): soil_moisture_value = 100 print('Soil Moisture:',soil_moisture_value,'%') elif(soil_moisture_value <= 0): soil_moisture_value = 0 print('Soil Moisture:',soil_moisture_value,'%') else: print('Soil Moisture:',soil_moisture_value,'%') return soil_moisture_value #sensor - sensor used def measure_humidity(sensor): humidity_value = sensor.humidity() print('Humidity: %3.1f %%' %humidity_value) return humidity_value #sensor - sensor used #sensor_offset - margin differnce def measure_temperature(sensor, sensor_offset): temperature_value = sensor.temperature() - sensor_offset print('Temperature: %3.1f C' %temperature_value) return temperature_value #module - the module used #state - On(1), Off(0) #timer - for how long the module should be active in seconds #water_level - the amount of water left def pump_water(module, timer, water_level_value, soil_moisture_value): if(water_level <= 0): print("Not enough water to pump!") elif(soil_moisture_value <= 20): module.value(1) print("Relay: ON! - Pumping Water...") sleep(timer) module.value(0) print("Relay: OFF! - Water pump stopped.") return True def sub_cb(topic, msg): print(msg) def connect_mqtt(client_id, mqtt_broker): print("Attempting to connect to MQTT broker...") client = MQTTClient(client_id, mqtt_broker, port=1883) client.set_callback(sub_cb) client.connect() print('Connected to %s MQTT broker' % (mqtt_broker)) return client def restart_and_reconnect(): print('Failed to connect to MQTT broker. Reconnecting...') sleep(10) reset() #Main #Wifi Connect do_connect() #MQTT # The MQTT topics that we publish data to topic_pub_temperature = 'esp32c3/sensors/temperature' topic_pub_soil_moisture = 'esp32c3/sensors/soil_moisture' topic_pub_water_level = 'esp32c3/sensors/water_level' topic_pub_humidity = 'esp32c3/sensors/humidity' topic_pub_pump_counter = 'esp32c3/sensors/pump_counter' try: client = connect_mqtt(config.CLIENT_ID, config.MQTT_BROKER) except OSError as e: restart_and_reconnect() #Sensor initialize try: print('-- Initializing sensors... --') ultra_sonic_sensor = HCSR04(trigger_pin=6, echo_pin=7, echo_timeout_us=10000) print('> Ultra Sonic sensor connected!') soil_moisture_sensor = ADC(Pin(2)) soil_moisture_sensor.atten(ADC.ATTN_11DB) print('> Soil Moisture sensor connected!') humidity_temperature_sensor = dht.DHT11(Pin(3)) print('> Humidity/Temperature sensor connected!') relay_module = Pin(0, Pin.OUT) print('> Relay Module connected!') print("-- All sensors connected! --") except OSError as e: print('Failed to initialize sensors! Error:', e) sleep(3) # wait 3 secodns before starting #cycles = 0 # cycles of the loop pump_counter = 0 while True: humidity_temperature_sensor.measure() humidity = measure_humidity(humidity_temperature_sensor) client.publish(topic=topic_pub_humidity, msg=str(humidity)) client.check_msg() sleep(0.2) temperature = measure_temperature(humidity_temperature_sensor,2) client.publish(topic=topic_pub_temperature, msg=str(temperature)) client.check_msg() sleep(0.2) soil_moisture = measure_soil_moisture(soil_moisture_sensor) #Before sleep(0.2) water_level = measure_water_level(ultra_sonic_sensor,2, 16) #Before sleep(0.2) if(pump_water(relay_module, 3, water_level, soil_moisture)): #turn on for 3 seconds pump_counter += 1 soil_moisture = measure_soil_moisture(soil_moisture_sensor) #After client.publish(topic=topic_pub_soil_moisture, msg=str(int(soil_moisture))) client.check_msg() sleep(0.2) water_level = measure_water_level(ultra_sonic_sensor,2, 16) #After client.publish(topic=topic_pub_water_level, msg=str(int(water_level))) client.check_msg() sleep(0.2) client.publish(topic=topic_pub_pump_counter, msg=str(pump_counter)) client.check_msg() #cycles += 1 sleep(600) #sleep for 10 minutes #deepsleep(10000) ``` #### Boot file (Network Connection): boot.py ```python= def do_connect(): import network import config sta_if = network.WLAN(network.STA_IF) # Put modem on Station mode if not sta_if.isconnected(): # Check if already connected print('connecting to network...') sta_if.active(True) # Activate network interface sta_if.connect(config.WIFI_SSID, config.WIFI_PASS) # Your WiFi Credential # Check if it is connected otherwise wait while not sta_if.isconnected(): pass # Print the IP assigned by router print('network config:', sta_if.ifconfig()) ``` #### MQTT file: mqtt.py ```python= import usocket as socket import ustruct as struct from ubinascii import hexlify class MQTTException(Exception): pass class MQTTClient: def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0, ssl=False, ssl_params={}): if port == 0: port = 8883 if ssl else 1883 self.client_id = client_id self.sock = None self.addr = socket.getaddrinfo(server, port)[0][-1] self.ssl = ssl self.ssl_params = ssl_params self.pid = 0 self.cb = None self.user = user self.pswd = password self.keepalive = keepalive self.lw_topic = None self.lw_msg = None self.lw_qos = 0 self.lw_retain = False def _send_str(self, s): self.sock.write(struct.pack("!H", len(s))) self.sock.write(s) def _recv_len(self): n = 0 sh = 0 while 1: b = self.sock.read(1)[0] n |= (b & 0x7f) << sh if not b & 0x80: return n sh += 7 def set_callback(self, f): self.cb = f def set_last_will(self, topic, msg, retain=False, qos=0): assert 0 <= qos <= 2 assert topic self.lw_topic = topic self.lw_msg = msg self.lw_qos = qos self.lw_retain = retain def connect(self, clean_session=True): self.sock = socket.socket() self.sock.connect(self.addr) if self.ssl: import ussl self.sock = ussl.wrap_socket(self.sock, **self.ssl_params) msg = bytearray(b"\x10\0\0\x04MQTT\x04\x02\0\0") msg[1] = 10 + 2 + len(self.client_id) msg[9] = clean_session << 1 if self.user is not None: msg[1] += 2 + len(self.user) + 2 + len(self.pswd) msg[9] |= 0xC0 if self.keepalive: assert self.keepalive < 65536 msg[10] |= self.keepalive >> 8 msg[11] |= self.keepalive & 0x00FF if self.lw_topic: msg[1] += 2 + len(self.lw_topic) + 2 + len(self.lw_msg) msg[9] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3 msg[9] |= self.lw_retain << 5 self.sock.write(msg) #print(hex(len(msg)), hexlify(msg, ":")) self._send_str(self.client_id) if self.lw_topic: self._send_str(self.lw_topic) self._send_str(self.lw_msg) if self.user is not None: self._send_str(self.user) self._send_str(self.pswd) resp = self.sock.read(4) assert resp[0] == 0x20 and resp[1] == 0x02 if resp[3] != 0: raise MQTTException(resp[3]) return resp[2] & 1 def disconnect(self): self.sock.write(b"\xe0\0") self.sock.close() def ping(self): self.sock.write(b"\xc0\0") def publish(self, topic, msg, retain=False, qos=0): pkt = bytearray(b"\x30\0\0\0") pkt[0] |= qos << 1 | retain sz = 2 + len(topic) + len(msg) if qos > 0: sz += 2 assert sz < 2097152 i = 1 while sz > 0x7f: pkt[i] = (sz & 0x7f) | 0x80 sz >>= 7 i += 1 pkt[i] = sz #print(hex(len(pkt)), hexlify(pkt, ":")) self.sock.write(pkt, i + 1) self._send_str(topic) if qos > 0: self.pid += 1 pid = self.pid struct.pack_into("!H", pkt, 0, pid) self.sock.write(pkt, 2) self.sock.write(msg) if qos == 1: while 1: op = self.wait_msg() if op == 0x40: sz = self.sock.read(1) assert sz == b"\x02" rcv_pid = self.sock.read(2) rcv_pid = rcv_pid[0] << 8 | rcv_pid[1] if pid == rcv_pid: return elif qos == 2: assert 0 def subscribe(self, topic, qos=0): assert self.cb is not None, "Subscribe callback is not set" pkt = bytearray(b"\x82\0\0\0") self.pid += 1 struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid) #print(hex(len(pkt)), hexlify(pkt, ":")) self.sock.write(pkt) self._send_str(topic) self.sock.write(qos.to_bytes(1, 'little')) while 1: op = self.wait_msg() if op == 0x90: resp = self.sock.read(4) #print(resp) assert resp[1] == pkt[2] and resp[2] == pkt[3] if resp[3] == 0x80: raise MQTTException(resp[3]) return # Wait for a single incoming MQTT message and process it. # Subscribed messages are delivered to a callback previously # set by .set_callback() method. Other (internal) MQTT # messages processed internally. def wait_msg(self): res = self.sock.read(1) self.sock.setblocking(True) if res is None: return None if res == b"": raise OSError(-1) if res == b"\xd0": # PINGRESP sz = self.sock.read(1)[0] assert sz == 0 return None op = res[0] if op & 0xf0 != 0x30: return op sz = self._recv_len() topic_len = self.sock.read(2) topic_len = (topic_len[0] << 8) | topic_len[1] topic = self.sock.read(topic_len) sz -= topic_len + 2 if op & 6: pid = self.sock.read(2) pid = pid[0] << 8 | pid[1] sz -= 2 msg = self.sock.read(sz) self.cb(topic, msg) if op & 6 == 2: pkt = bytearray(b"\x40\x02\0\0") struct.pack_into("!H", pkt, 2, pid) self.sock.write(pkt) elif op & 6 == 4: assert 0 # Checks whether a pending message from server is available. # If not, returns immediately with None. Otherwise, does # the same processing as wait_msg. def check_msg(self): self.sock.setblocking(False) return self.wait_msg() ``` #### HCSR04 Ultrasonic sound sensor file: hcsr04.py ```python= import machine, time from machine import Pin __version__ = '0.2.0' __author__ = 'Roberto Sánchez' __license__ = "Apache License 2.0. https://www.apache.org/licenses/LICENSE-2.0" class HCSR04: """ Driver to use the untrasonic sensor HC-SR04. The sensor range is between 2cm and 4m. The timeouts received listening to echo pin are converted to OSError('Out of range') """ # echo_timeout_us is based in chip range limit (400cm) def __init__(self, trigger_pin, echo_pin, echo_timeout_us=500*2*30): """ trigger_pin: Output pin to send pulses echo_pin: Readonly pin to measure the distance. The pin should be protected with 1k resistor echo_timeout_us: Timeout in microseconds to listen to echo pin. By default is based in sensor limit range (4m) """ self.echo_timeout_us = echo_timeout_us # Init trigger pin (out) self.trigger = Pin(trigger_pin, mode=Pin.OUT, pull=None) self.trigger.value(0) # Init echo pin (in) self.echo = Pin(echo_pin, mode=Pin.IN, pull=None) def _send_pulse_and_wait(self): """ Send the pulse to trigger and listen on echo pin. We use the method `machine.time_pulse_us()` to get the microseconds until the echo is received. """ self.trigger.value(0) # Stabilize the sensor time.sleep_us(5) self.trigger.value(1) # Send a 10us pulse. time.sleep_us(10) self.trigger.value(0) try: pulse_time = machine.time_pulse_us(self.echo, 1, self.echo_timeout_us) return pulse_time except OSError as ex: if ex.args[0] == 110: # 110 = ETIMEDOUT raise OSError('Out of range') raise ex def distance_mm(self): """ Get the distance in milimeters without floating point operations. """ pulse_time = self._send_pulse_and_wait() # To calculate the distance we get the pulse_time and divide it by 2 # (the pulse walk the distance twice) and by 29.1 becasue # the sound speed on air (343.2 m/s), that It's equivalent to # 0.34320 mm/us that is 1mm each 2.91us # pulse_time // 2 // 2.91 -> pulse_time // 5.82 -> pulse_time * 100 // 582 mm = pulse_time * 100 // 582 return mm def distance_cm(self): """ Get the distance in centimeters with floating point operations. It returns a float """ pulse_time = self._send_pulse_and_wait() # To calculate the distance we get the pulse_time and divide it by 2 # (the pulse walk the distance twice) and by 29.1 becasue # the sound speed on air (343.2 m/s), that It's equivalent to # 0.034320 cm/us that is 1cm each 29.1us cms = (pulse_time / 2) / 29.1 return cms ``` ## Data transmission, connectivity and platform Everything was setup and hosted locally both the MQTT-broker and Node-RED. Local setup was chosen because it is a controlled enviroment, data is not required to be sent outside the network which makes it more secure and data is sent faster since it is in a WLAN (Wireless Local Area Network) and does not require any hops through the internet. The data in the dashboard can still be displayed outside the WLAN by using either e.g VPN/routing or even tools like ngrok to serve a web page. The data is transmitted from the ESP32-C3 to the Mosquitto MQTT-broker every 10 minutes and then fetched by a subscribing Node-RED MQTT-node. (The data publishing time can be changed to a higher value to have less updates of values but also lowers the energy consumption) There could be better options for the platform but Node-RED seems very simple with drag and drop which makes the project more managable, scalable and visually appealing. Wi-Fi was used as the wireless communication protocol, WPA2 as the security protocol and MQTT as the transport protocol. MQTT as a transport protocol was choosen because it is optimized and resource efficient for low power devices. It has low data overhead and is simple when transmitting/fetching data. Wi-Fi (802.11ac) as connectivity protocol was choosen because it is a more controlled enviroment and data can mostly be manipulated by users/devices that are connected to the network. The drawback with using Wi-Fi is the energy consumption, other connectivty protocols could be use to further decrease the energy consumption. 2.4 GHz Frequency Band was used since it is supported by the ESP32-C3 and most of the micro-controllers out there. The choosen security protocol was WPA2 but WPA3 could also be used to improve the security of the micro-controller since IoT devices are primarly built with the focus on performance and energy consumption and not security which makes them vulnerable to alot of attacks. (Keep in mind that WPA3 have a larger data overhead compared to WPA2 because of the encryption algorithms that require larger key-size e.g 192-bit) ## Presenting the data To present the data visually, a dashboard has to be created through Node-RED. ### Creating a Node-RED flow and dashboard After starting Node-RED in the terminal go to a browser of your choosing and type 127.0.0.1:1880 in the browser address field. After the page opens up, press the three lines on the top right corner and choose 'Manage Palette' then choose the 'Install' tab and type '*node-red-dashboard*' then install. Pick the MQTT subscriber node ![](https://i.imgur.com/zchHgsA.png) and drag it to the grid. Then pick ![](https://i.imgur.com/kcIroTb.png) and drag them to the grid. Then click on the 'MQTT in' node and change the settings. *Server* should be the IP that the Mosquitto MQTT-broker is running on and the default unencrypted port 1883 for MQTT. Port 8883 for encrypted. ![](https://i.imgur.com/h2aOfUb.png =450x) Press the Pencil Button in the image above, this will provide additional settings which can be changed. The additonal settings are displayed in the image below. ![](https://i.imgur.com/Jy0ueqJ.png =450x) Then change the settings for each chart/gauge nodes as displayed in the image below. ![](https://i.imgur.com/AsHBlLV.png =400x) The final flow should look like the image below. ![](https://i.imgur.com/I2GVK5n.png) (The connection with Node-RED broker node will also be shown in the terminal) ![](https://i.imgur.com/r0BwMUU.png) Then press ![](https://i.imgur.com/ekgsoq8.png) which will open a new page with a dashboard of all the charts/gauges created. Image below displays the dashboard that was created during this project. ![](https://i.imgur.com/2eynUau.png) The transmited 'publish' data and the associated devices can be confirmed throught Moquitto MQTT-broker. ![](https://i.imgur.com/5vyTftV.png) ## Finalizing the design I think the project went better than expected. Everything worked without any issues. The project could be improved by mounting better sensors with better accuracy and precision. For example, instead of DHT11 temperature/humidity sensor a better version could be used instead becuase of the the accuracy margin +/- 2 °C and humidity 5%, while DHT22 has a accuracy margin of +/- 0.5 °C and humidity 2%. The sensors could also be calibrated better. The project is not limited to a single soil moisture or a single-channel relay module. Multi-channel relay module and multiple soil moisture sensors could be used to monitor multiple plants. Here are some images and videos of the automatic water plant system in action: --- ![](https://i.imgur.com/6zzvywh.jpg =350x)![](https://i.imgur.com/GVp2UdP.jpg =350x) ![](https://i.imgur.com/7iEFDLf.jpg =700x) --- <div style="width:100%;height:0px;position:relative;padding-bottom:56.250%;"><iframe src="https://streamable.com/e/i7c36r" frameborder="0" width="100%" height="100%" allowfullscreen style="width:100%;height:100%;position:absolute;left:0px;top:0px;overflow:hidden;"></iframe></div> --- <div style="width:100%;height:0px;position:relative;padding-bottom:56.250%;"><iframe src="https://streamable.com/e/06e1t5" frameborder="0" width="100%" height="100%" allowfullscreen style="width:100%;height:100%;position:absolute;left:0px;top:0px;overflow:hidden;"></iframe></div>