IoT Irrigation System
This instructable describes how to build your own IoT irrigation system. The system will provide the functionality of controlling the water pump and lights for the plant, monitoring the system information such as temperature, humidity, water tank level, sunset and sunrise time, and some system configurations.
Interfaces:
- GUI - created with NODE-RED.
- Home
- Stats - this section will display real-time information about temperature, humidity, the water level in the tank (an audio text to speech notification will be played when the level is extremely low), sunset and sunrise time.
- Plant Information - this section will display the name, age, and type of the plant.
- Control - this section allows you to control the water pump and the lights.
- Configurations
- Plant Information form - here you will enter the plant information that will be displayed at the Home page.
- Schedule Water Pump Activity - a form that allows you to schedule the water pump activity for some point in the future.
- Home
- Activation of the water pump via Google Assistant.
Supplies
- Arduino ESP-8266.
- AdafruitIO MQTT server.
- Node-Red.
- Moisture sensor.
- Strong 12V led.
- HC-SR04 Ultrasonic Distance Rangefinde sensor.
- Jumper cables.
- Water tank.
- 3-6V Water pump.
- Silicone tubing.
- DHT sensor.
- Addressable RGB LEDs.
- Relay.
- Transistor.
- LED controller.
Understanding the Architecture
There are 4 main components in this system:
- Adafruit IO MQTT server- this message broker is the heart of all the communication between the components. It works in a publish-subscribe pattern, meaning it will pass publish messages from components to subscribed components. We will have 3 topics/feeds:
- light_btn - turns the light off/on.
- water_btn - will activate the water pump for 5 seconds.
- water_level - will pass the remaining water level information.
- Node-RED - a flow-based programming tool, originally developed by IBM’s Emerging Technology Services team and now a part of the JS Foundation. It will handle data processing, control, configuration and provide a GUI.
- ESP-8266 - An Arduino that can connect to the Internet via WIFI, and will handle the sensors and all the physical components such as the lights and the water pump.
- IFTTT - If This Then That, also known as IFTTT, is a freeware web-based service that creates chains of simple conditional statements, called applets. It will connect your Google Assistant with the Adafruit IO MQTT server.
Adafruit IO
Go to https://io.adafruit.com and create an account.
Now create 3 feeds:
- water_btn
- light_btn
- water_level
Node-RED on IBM Cloud
There is a lot of options where you can run Node-RED, when we build this system we used the IBM cloud. Even though the benefits are obvious, we could all work remotely on Node-RED and got the GUI hosted online for free, there is, of course, a risk when you are connecting your IoT system with a public cloud.
Follow this manual to set up Node-RED on IBM cloud: https://nodered.org/docs/getting-started/ibmcloud.
After installing Node-RED, go to your edit pane (on IBM cloud it will be YOUR_DOPE HOSTNAME.eu-gb.mybluemix.net/red/#), and install some useful modules.
Click the menu in the top right corner, click 'Manage Pallete', then choose the install tab, search for the following modules and install them:
- node-red-dashboard
- node-red-node-openweathermap
Importing a Node-RED Flow
Go to your Node-RED edit pane, if you are using IBM cloud it will be hosted at YOUR_DOPE HOSTNAME.eu-gb.mybluemix.net/red/#
Now, there a menu on the right top corner, click it and then choose 'Import', paste the following JSON, replace every YOUR_ADAFRUIT_IO_USER in the JSON with your Adafruit IO user and click 'Import'.
[ { "id": "38acff13.d3606", "type": "tab", "label": "Flow 1", "disabled": false, "info": "" }, { "id": "b3ccc0d0.b8916", "type": "debug", "z": "38acff13.d3606", "name": "openweather debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 140, "y": 540, "wires": [] }, { "id": "5030b141.40051", "type": "function", "z": "38acff13.d3606", "name": "extractWeatherInfo", "func": "var sunset = msg.payload.sunset;\nvar sunrise = msg.payload.sunrise;\nvar temperature = msg.payload.tempc;\nvar humidity = msg.payload.humidity;\n\nreturn [sunset, sunrise, temperature, humidity].map(s => { return { payload: s } });\n", "outputs": 4, "noerr": 0, "x": 450, "y": 480, "wires": [ [ "6debfd1c.c20ec4", "c3a2b441.dc86c8" ], [ "22d4e26b.c204be", "1f3be7aa.f4e0b8" ], [ "bc23d993.16fde8", "984e914f.e6e1a" ], [ "33beb6a1.e9d1da", "53b1f773.7930d8" ] ], "outputLabels": [ "sunset", "sunrise", "temp", "hum" ] }, { "id": "6debfd1c.c20ec4", "type": "debug", "z": "38acff13.d3606", "name": "sunset debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 420, "y": 540, "wires": [] }, { "id": "3776e016.23ee9", "type": "mqtt out", "z": "38acff13.d3606", "name": "light button publish", "topic": "YOUR_ADAFRUIT_IO_USER/feeds/light_btn", "qos": "1", "retain": "false", "broker": "b3574442.034e38", "x": 530, "y": 380, "wires": [] }, { "id": "c3a2b441.dc86c8", "type": "function", "z": "38acff13.d3606", "name": "msToHourStr", "func": "var hourStr = (new Date(msg.payload*1000)).toLocaleTimeString('he-IL', { hour12: false, timeZone: 'Asia/Jerusalem' });\nvar newMsg = {payload: hourStr};\nreturn newMsg;", "outputs": 1, "noerr": 0, "x": 730, "y": 460, "wires": [ [ "d404311d.5dfe7" ] ] }, { "id": "22d4e26b.c204be", "type": "function", "z": "38acff13.d3606", "name": "msToHourStr", "func": "var hourStr = (new Date(msg.payload*1000)).toLocaleTimeString('he-IL', { hour12: false, timeZone: 'Asia/Jerusalem' });\nvar newMsg = {payload: hourStr};\nreturn newMsg;", "outputs": 1, "noerr": 0, "x": 730, "y": 500, "wires": [ [ "9fd924fd.be2b98" ] ] }, { "id": "61bb1fba.0b53f", "type": "mqtt out", "z": "38acff13.d3606", "name": "water button publish", "topic": "YOUR_ADAFRUIT_IO_USER/feeds/water_btn", "qos": "1", "retain": "false", "broker": "b3574442.034e38", "x": 540, "y": 340, "wires": [] }, { "id": "1f3be7aa.f4e0b8", "type": "debug", "z": "38acff13.d3606", "name": "sunrise debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 420, "y": 580, "wires": [] }, { "id": "43b41361.ead70c", "type": "openweathermap in", "z": "38acff13.d3606", "name": "", "wtype": "current", "lon": "", "lat": "", "city": "Herzliya", "country": "Israel", "language": "en", "x": 120, "y": 480, "wires": [ [ "b3ccc0d0.b8916", "5030b141.40051" ] ] }, { "id": "c3a3e9c9.f83808", "type": "ui_switch", "z": "38acff13.d3606", "name": "light", "label": "Light", "tooltip": "Light ON/OFF", "group": "f51a0a92.f39cc8", "order": 2, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "", "style": "", "onvalue": "true", "onvalueType": "bool", "onicon": "", "oncolor": "", "offvalue": "false", "offvalueType": "bool", "officon": "", "offcolor": "", "x": 310, "y": 380, "wires": [ [ "3776e016.23ee9" ] ] }, { "id": "d404311d.5dfe7", "type": "ui_text", "z": "38acff13.d3606", "group": "1819f01f.6941c", "order": 3, "width": 0, "height": 0, "name": "Sunset", "label": "Sunset:", "format": "{{msg.payload}}", "layout": "col-center", "x": 940, "y": 460, "wires": [] }, { "id": "9fd924fd.be2b98", "type": "ui_text", "z": "38acff13.d3606", "group": "1819f01f.6941c", "order": 4, "width": 0, "height": 0, "name": "Sunrise", "label": "Sunrise:", "format": "{{msg.payload}}", "layout": "col-center", "x": 940, "y": 500, "wires": [] }, { "id": "33beb6a1.e9d1da", "type": "ui_gauge", "z": "38acff13.d3606", "name": "", "group": "1819f01f.6941c", "order": 7, "width": 0, "height": 0, "gtype": "donut", "title": "Humidity", "label": "%", "format": "{{value}}", "min": 0, "max": "70", "colors": [ "#6ab3f3", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "x": 940, "y": 580, "wires": [] }, { "id": "984e914f.e6e1a", "type": "ui_gauge", "z": "38acff13.d3606", "name": "Temperature", "group": "1819f01f.6941c", "order": 6, "width": 0, "height": 0, "gtype": "donut", "title": "Temperature", "label": "c", "format": "{{value}}", "min": "-10", "max": "80", "colors": [ "#0041f7", "#e6e600", "#ca3838" ], "seg1": "10", "seg2": "30", "x": 950, "y": 540, "wires": [] }, { "id": "8c531e0c.7e206", "type": "ui_form", "z": "38acff13.d3606", "name": "Information", "label": "Information", "group": "2570fa5.fbdef06", "order": 0, "width": 0, "height": 0, "options": [ { "label": "Name", "value": "Name", "type": "text", "required": true, "rows": null }, { "label": "Age", "value": "Age", "type": "number", "required": false, "rows": null }, { "label": "Type", "value": "Type", "type": "text", "required": false, "rows": null } ], "formValue": { "Name": "", "Age": "", "Type": "" }, "payload": "", "submit": "submit", "cancel": "cancel", "topic": "", "x": 790, "y": 360, "wires": [ [ "f04ec0e0.28494", "98882e2a.28526", "46e71939.c4b558" ] ] }, { "id": "f04ec0e0.28494", "type": "ui_text", "z": "38acff13.d3606", "group": "8c4c1fe1.62278", "order": 1, "width": 0, "height": 0, "name": "Name", "label": "Name", "format": "{{msg.payload.Name}}", "layout": "row-spread", "x": 1100, "y": 320, "wires": [] }, { "id": "46e71939.c4b558", "type": "ui_text", "z": "38acff13.d3606", "group": "8c4c1fe1.62278", "order": 2, "width": 0, "height": 0, "name": "Type", "label": "Type", "format": "{{msg.payload.Type}}", "layout": "row-spread", "x": 1090, "y": 400, "wires": [] }, { "id": "98882e2a.28526", "type": "ui_text", "z": "38acff13.d3606", "group": "8c4c1fe1.62278", "order": 3, "width": 0, "height": 0, "name": "Age", "label": "Age", "format": "{{msg.payload.Age}}", "layout": "row-spread", "x": 1090, "y": 360, "wires": [] }, { "id": "bc23d993.16fde8", "type": "debug", "z": "38acff13.d3606", "name": "temp debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 410, "y": 620, "wires": [] }, { "id": "53b1f773.7930d8", "type": "debug", "z": "38acff13.d3606", "name": "hum debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 410, "y": 660, "wires": [] }, { "id": "36356138.0452ce", "type": "mqtt in", "z": "38acff13.d3606", "name": "water level subscribe", "topic": "YOUR_ADAFRUIT_IO_USER/feeds/water_level", "qos": "2", "datatype": "auto", "broker": "b3574442.034e38", "x": 140, "y": 720, "wires": [ [ "7cf6a9ab.8925d8", "bc539584.0503c8" ] ] }, { "id": "7cf6a9ab.8925d8", "type": "ui_gauge", "z": "38acff13.d3606", "name": "Water Level", "group": "1819f01f.6941c", "order": 1, "width": 0, "height": 0, "gtype": "wave", "title": "Water Level", "label": "%", "format": "{{value}}", "min": 0, "max": "100", "colors": [ "#6ab3f3", "#e6e600", "#ca3838" ], "seg1": "", "seg2": "", "x": 950, "y": 620, "wires": [] }, { "id": "3fed9eb4.6763c2", "type": "ui_form", "z": "38acff13.d3606", "name": "Schedule Water Pump Activity", "label": "Schedule Water Pump Activity", "group": "2570fa5.fbdef06", "order": 0, "width": 0, "height": 0, "options": [ { "label": "Hours until water pump will be activated", "value": "hours", "type": "number", "required": false, "rows": null }, { "label": "Minutes until water pump will be activated", "value": "minutes", "type": "number", "required": false, "rows": null }, { "label": "Seconds until water pump will be activated", "value": "seconds", "type": "number", "required": false, "rows": null } ], "formValue": { "hours": "", "minutes": "", "seconds": "" }, "payload": "", "submit": "submit", "cancel": "cancel", "topic": "", "x": 190, "y": 180, "wires": [ [ "d79a91b9.79c42", "305efcd7.226c54" ] ] }, { "id": "305efcd7.226c54", "type": "function", "z": "38acff13.d3606", "name": "calcTimeUntilWaterMS", "func": "msg.payload = (msg.payload.hours || 0) * 3.6e+6 + (msg.payload.minutes || 0) * 60000 + (msg.payload.seconds || 0) * 1000;\nreturn msg;\n", "outputs": 1, "noerr": 0, "x": 480, "y": 180, "wires": [ [ "616bc7f9.217c38" ] ] }, { "id": "d79a91b9.79c42", "type": "debug", "z": "38acff13.d3606", "name": "pump schedule debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 180, "y": 240, "wires": [] }, { "id": "cc6f70e6.402c4", "type": "ui_button", "z": "38acff13.d3606", "name": "water", "group": "f51a0a92.f39cc8", "order": 2, "width": 0, "height": 0, "passthru": false, "label": "Acivate Water Pump", "tooltip": "Water the plant for a duration of 5s", "color": "", "bgcolor": "", "icon": "", "payload": "true", "payloadType": "bool", "topic": "", "x": 310, "y": 340, "wires": [ [ "61bb1fba.0b53f" ] ] }, { "id": "616bc7f9.217c38", "type": "function", "z": "38acff13.d3606", "name": "setWaterPumpTimeout", "func": "setTimeout(function() {\n msg.payload = true;\n node.send(msg);\n node.done();\n}, msg.payload)\nreturn;\n", "outputs": 1, "noerr": 0, "x": 760, "y": 180, "wires": [ [ "61bb1fba.0b53f" ] ] }, { "id": "bc539584.0503c8", "type": "function", "z": "38acff13.d3606", "name": "setNotfiyLowWater", "func": "msg.payload = [0, 5, 10].indexOf(7) !== -1 ? 'The water level is low, current level is: ' + msg.payload + '%' : null;\nreturn msg;", "outputs": 1, "noerr": 0, "x": 430, "y": 740, "wires": [ [ "55d0d529.b85b9c" ] ] }, { "id": "55d0d529.b85b9c", "type": "ui_audio", "z": "38acff13.d3606", "name": "Notify Low Water", "group": "1819f01f.6941c", "voice": "en-US", "always": true, "x": 970, "y": 660, "wires": [] }, { "id": "b3574442.034e38", "type": "mqtt-broker", "z": "", "name": "", "broker": "io.adafruit.com", "port": "1883", "clientid": "", "usetls": false, "compatmode": true, "keepalive": "15", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "f51a0a92.f39cc8", "type": "ui_group", "z": "", "name": "Control", "tab": "b729edc1.88227", "order": 3, "disp": true, "width": "6", "collapse": false }, { "id": "1819f01f.6941c", "type": "ui_group", "z": "", "name": "Stats", "tab": "b729edc1.88227", "order": 1, "disp": true, "width": "6", "collapse": false }, { "id": "2570fa5.fbdef06", "type": "ui_group", "z": "", "name": "Plant Information", "tab": "c8c04562.c6ea08", "order": 1, "disp": true, "width": "6", "collapse": false }, { "id": "8c4c1fe1.62278", "type": "ui_group", "z": "", "name": "Plant Information", "tab": "b729edc1.88227", "order": 2, "disp": true, "width": "6", "collapse": false }, { "id": "b729edc1.88227", "type": "ui_tab", "z": "", "name": "Home", "icon": "dashboard", "order": 1, "disabled": false, "hidden": false }, { "id": "c8c04562.c6ea08", "type": "ui_tab", "z": "", "name": "Configurations", "icon": "dashboard", "order": 2, "disabled": false, "hidden": false } ]
Connecting Node-RED With OpenWeatherMap API
Create an account at https://openweathermap.org/, you will receive an API key to your email.
Go to the edit pane and double click on the openweathermap node, now fill your API key in the form.
Set the location to your plant location and click 'Done'.
Connectiong Node-RED With Adafruit IO
In your edit pane go to the menu and click on 'Configuration nodes', double click on 'adafruit.io.com:1883', select the 'Security' tab and fill your adafruit IO user name and key.
Adjusting the MsToHourStr Node
The msToHourStr is a js code function that converts the time in ms that we've got from the weather API to a human readble hour string.
In order for it to be accurate for you, please double click on the node and change the timeZone in the code from timeZone: 'Asia/Jerusalem' to your time zone.
Checking the GUI
By now you can test some functionalities in the GUI.
Validate that the gauges are showing the temperature and humidity and that you have a valid sunrise and sunset times.
Now play with the control button and see the communication in the feed page, for example, go to io.adafruit.com/YOUR_USERNAME/feeds/light-btn, and see the logs at the bottom of the page.
Creating IFTTT Applet
When you will ask Google Assistant to 'water the plant' it will send data to the water_btn feed at Adafruit.
To make this connection, first, create an account at IFFFT.
Now, create a new applet, the first service will be Google Assistant, pick the first trigger of 'Say a simple phrase' and let the phrase be 'water the plant', you can also choose the assistant response.
Save the action.
The second service will be Adafruit IO, configure the feed name to be 'water_btn' and the data to be 'true'.
Save the applet.
Arduino Part
All the code has been segmented into chunks of logic, all the sensors have a system that takes care of all their needs, the watering segment as well's the decoration lights have their own code chunks.
An overall explanation for the Arduino part:
The Arduino (which in our case is an esp8266 wemos r1 ) connects to the internet, regularly updating the sensors depending on how fast we want them to update, and if data has changed then publishes to the server, the Arduino also listens for the server waiting for instructions to water the plant or to turn the light on and off.
The Arduino has some extra features of its own, which are mainly the decoration lights, as well as a warning system in case the plant is in a too hot sun temperature.
Putting It All Together
For ease of use we segmented the elements of the system into parts.
The attached picture describes a diagram of how to connect all of the components with the ESP8266.
Light System
The light system is fairly easy.
The Arduino (esp) will receive from the system when to turn the light on, it can be either a manual turn-on of the light or a scheduled one, to keep the flower generating energy even after sunset.
We built our light system by soldering some strong LEDs on board, all light are connected in parallel, and there is a switch added to it, to manually turn the lights off if needed.
The lights are connected to a relay:
The relay is connected in one part to the Arduino (pin D5) that can turn the light on or off depending on the received message (in the readFromServer function in the MQTT segment of the code).
The other side of the relay is connected to the 12-volt power connector.
We connected the LEDs to a led controller before connecting it directly to a 12v power source, the led controller prevents the light from too much current and overheating.
Water System
The watering system has multiple elements, two sensors, and a pump.
The first sensor is the moisture, which checks how wet the soil is and waters the plant if the moisture drops below the desired level (in the waterIfNeeded() function, as part of the wateringSystem code part).
After the checking of the soil, the pump will water the plant, all of the watering and pump code are in the wateringSystem class.
The pumping can be done either manually through the app, or through google assist, or automatically from the app or from the moisture sensor.
The water pump is put in the bucket, and when it gets the power it will start pumping water to the plant.
Since the Arduino / ESP pins don't have enough current to turn the motor on, we connect the motors + sign directly to the 5V in the Arduino and the minus part to the collector pin of a small transistor.
The gate pin of the transistor will connect to D12, the emitter pin connected to ground.
The third part of the watering system is the ultrasonic sensor.
The user can define when the bucket is full and when it's empty, measuring the distance, the sensor can tell how much percentage of water is left, it gets updated from the sensors code batch and reports to the server about the percentage of how much water left.
Extra Monitoring
Connect the DHT sensor to the D11 pin.
This sensor will measure both temperature and humidity (since our DHT broke while submitting this project, we only collect the information from the sensor locally, preferring the temperature and humidity data we got from the weather API via Node-RED).
The updating of the sensors is done in the plant sensor of the code.
The DHT is used for warning when the plant is overheated, if its so, a red light will glow to warn the user.
Extras
A strip of addressable RGB leds is added, with one single pin, we can have a huge range of effects.
The positive side of the strip should be connected to the VCC since a normal pin will not have enough power to drive the led strip.
The led strip usually plays an animation from the animation options in the led strip code segment.
When the temperature is higher than the user's submitted degree, the led lights will glow in an eye catching red pattern.
CODE: Main
In here there are the connection of all the other code segments
Main points of the code :
- Connect all the systems together.
- Initializes for everything.
- A call to publish, and a call to read from the server.
- LEDs effect initialize & plant overheating warning.
<p>mqttClass MQTT;// = new mqttClass();</p><p>void setup() { Serial.begin(9600); Serial.println("=========Main setup============"); MQTT.setupMQTT(); sensors.setup(); setupLedStrip(); }</p><p>void publishSensors() { for(int i=0;i</p><p>void choosePropperEffect() { if (sensors.readRawTemp() > tooHotLevel) { showEffect(Fire,3); } else { showEffect(1, 1); }</p><p>}</p><p>void loop() {</p><p> MQTT.loopMQTT(); water.updateWateringSystem(); sensors.updateSensors(); publishSensors(); //choosePropperEffect(); }</p>
CODE: Internet Connection
We added this code above the main code, it can also be segmented into another file.
You need to replace the "-----" in the code with your wifi username & pass as well as the Adafruit IO servers credentials.
The desired temperature of the plant can be changed here as well in the tooHotLevel variable. We chose it to be 35.
<p><br>#include "plantSensors.h" #include "ESP8266WiFi.h" #include "Adafruit_MQTT.h" #include "Adafruit_MQTT_Client.h" #include "wateringSystem.h" #include "ledStrip.h" <br></p><p>#define WLAN_SSID "--" #define WLAN_PASS "---"</p><p>// Adafruit IO #define AIO_SERVER "io.adafruit.com" #define AIO_SERVERPORT 1883 #define AIO_USERNAME "-----" #define AIO_KEY "-----"</p><p>#define LED 3</p><p>int tooHotLevel = 35;</p><p>//-----effects</p><p>//------------for light #define LIGHT_PIN D5 void updateLight(boolean isOn) {pinMode(LIGHT_PIN,OUTPUT); digitalWrite(LIGHT_PIN,isOn ? HIGH : LOW);}</p><p>/*************************** Sketch Code ************************************/</p><p>void connect();</p><p>WiFiClient client;</p><p>const char MQTT_SERVER[] = AIO_SERVER; const char MQTT_CLIENTID[] = AIO_KEY __DATE__ __TIME__; const char MQTT_USERNAME[] = AIO_USERNAME; const char MQTT_PASSWORD[] = AIO_KEY;</p><p>Adafruit_MQTT_Client mqtt(&client, MQTT_SERVER, AIO_SERVERPORT, MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD); //------ publishing Adafruit_MQTT_Publish temperature = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/temperature"); Adafruit_MQTT_Publish humidity = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/humidity"); Adafruit_MQTT_Publish moisture = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/moisture"); Adafruit_MQTT_Publish waterLevel = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME "/feeds/water_level"); //receiving Adafruit_MQTT_Subscribe waterBtn = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/water_btn"); Adafruit_MQTT_Subscribe lightBtn = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/light_btn"); Adafruit_MQTT_Subscribe readingTemp = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME "/feeds/temperature");</p><p>class mqttClass { public: mqttClass(){} const String HUMIDITY_PUBLISHING_FEED = "HUMIDITY_PUBLISHING_FEED"; const String TEMPERATURE_PUBLISHING_FEED = "TEMPERATURE_PUBLISHING_FEED"; const String MOISTURE_PUBLISHING_FEED = "MOISTURE_PUBLISHING_FEED"; const String WATER_BUTTON_SUBSCRIPTION_FEED = "WATER_BUTTON_SUBSCRIPTION_FEED"; const String DECORATION_LIGHTS_BUTTON_SUBSCRIPTION_FEED = "DECORATION_LIGHTS_BUTTON_SUBSCRIPTION_FEED"; void setupMQTT() { Serial.println("Starting the connection"); Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(WLAN_SSID);</p><p> WiFi.begin(WLAN_SSID, WLAN_PASS); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); delay(10); }</p><p> Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); Serial.println();</p><p> connect();</p><p>//-------------- start subscriptions</p><p> Serial.println("subscribeing to channels for first time"); mqtt.subscribe(&waterBtn); mqtt.subscribe(&lightBtn); mqtt.subscribe(&readingTemp);</p><p>}</p><p>bool PublishReturnsIfSuccessful(String sensor_type,int value) { Serial.print("publishing "); Serial.print(sensor_type); Serial.print(":"); Serial.println(value); if(sensor_type.equals("HUMIDITY")) {return humidity.publish(value);} else if(sensor_type.equals("TEMPERATURE")) {return temperature.publish(value);} else if(sensor_type.equals("MOISTURE")) {return moisture.publish(value);} else if(sensor_type.equals("WATER-LEVEL")) {return waterLevel.publish(value);} }</p><p>void reConnectIfNeeded() { // ping adafruit io a few times to make sure we remain connected if (! mqtt.ping(3)) { // reconnect to adafruit io if (! mqtt.connected()) connect(); } }</p><p>void readFromServer() { Adafruit_MQTT_Subscribe *subscription; while ((subscription = mqtt.readSubscription(5000))) { Serial.print("R");//todo: remove this if (subscription == &waterBtn) { Serial.print(F("Got water btn: ")); water.turnFaucetOn(); } if (subscription == &lightBtn) { String recivedMessage =(char *)lightBtn.lastread; Serial.print(F("light button: ")); Serial.println(recivedMessage); updateLight(recivedMessage.equals("true")); } }</p><p> }</p><p>void loopMQTT() { reConnectIfNeeded(); readFromServer(); if(! mqtt.ping()) { mqtt.disconnect();} }</p><p>// connect to adafruit io via MQTT void connect() { Serial.print("Connecting to Adafruit IO... "); int8_t ret; while ((ret = mqtt.connect()) != 0) { switch (ret) { case 1: Serial.println("Wrong protocol"); break; case 2: Serial.println("ID rejected"); break; case 3: Serial.println("Server unavail"); break; case 4: Serial.println("Bad user/pass"); break; case 5: Serial.println("Not authed"); break; case 6: Serial.println("Failed to subscribe"); break; default: Serial.println("Connection failed"); break; } if (ret >= 0) { mqtt.disconnect(); } Serial.println("Retrying connection..."); delay(5000); } Serial.println("Adafruit IO Connected!"); }</p><p> };</p>
CODE: Watering System
This part will start watering when getting a message from the server, that can happen from a manual press or an automatic scheduled watering.
It knows to open and close the water pump parallelly so it won't hog the thread.
#define k_water_pump_PIN D12
#define MOSITURE_PIN A0 #define DESIRED_MOISTURE_PERCENT 40
class wateringSystem {
int openFaucetPeriod = 3000; unsigned long faucetOpenedOn;
bool isFaucetOpen = false; public: wateringSystem() { }
void waterIfNeeded() { float moisture = analogRead(MOSITURE_PIN)/10 ; if(moisture < DESIRED_MOISTURE_PERCENT) { boolean shouldWaterOrWait(millis() > (faucetOpenedOn + openFaucetPeriod*4)); if(shouldWaterOrWait){turnFaucetOn();} } }
void turnFaucetOff() { isFaucetOpen= false; Serial.println("faucet is off"); digitalWrite(k_water_pump_PIN,LOW); }
void turnFaucetOn() { faucetOpenedOn = millis(); isFaucetOpen = true; Serial.println("faucet is on"); digitalWrite(k_water_pump_PIN,HIGH); }
void updateWateringSystem() { if(isFaucetOpen) { bool shouldFaucetClose = millis() > faucetOpenedOn + openFaucetPeriod; if(shouldFaucetClose) { turnFaucetOff(); }} waterIfNeeded(); }};
wateringSystem water;
CODE: Led Strip
#include
#include "FastLED.h" #define PIN D8 #define NUM_LEDS 40
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);
void showStrip() { #ifdef ADAFRUIT_NEOPIXEL_H // NeoPixel strip.show(); #endif #ifndef ADAFRUIT_NEOPIXEL_H // FastLED FastLED.show(); #endif }
void setPixel(int Pixel, byte red, byte green, byte blue) { #ifdef ADAFRUIT_NEOPIXEL_H // NeoPixel strip.setPixelColor(Pixel, strip.Color(red, green, blue)); #endif #ifndef ADAFRUIT_NEOPIXEL_H // FastLED leds[Pixel].r = red; leds[Pixel].g = green; leds[Pixel].b = blue; #endif }
void setAll(byte red, byte green, byte blue) { for(int i = 0; i < NUM_LEDS; i++ ) { setPixel(i, red, green, blue); } showStrip(); }
//------------------- effects
void setPixelHeatColor (int Pixel, byte temperature) { // Scale 'heat' down from 0-255 to 0-191 byte t192 = round((temperature/255.0)*191); // calculate ramp up from byte heatramp = t192 & 0x3F; // 0..63 heatramp <<= 2; // scale up to 0..252 // figure out which third of the spectrum we're in: if( t192 > 0x80) { // hottest setPixel(Pixel, 255, 255, heatramp); } else if( t192 > 0x40 ) { // middle setPixel(Pixel, 255, heatramp, 0); } else { // coolest setPixel(Pixel, heatramp, 0, 0); } }
void fadeToBlack(int ledNo, byte fadeValue) { #ifdef ADAFRUIT_NEOPIXEL_H // NeoPixel uint32_t oldColor; uint8_t r, g, b; int value; oldColor = strip.getPixelColor(ledNo); r = (oldColor & 0x00ff0000UL) >> 16; g = (oldColor & 0x0000ff00UL) >> 8; b = (oldColor & 0x000000ffUL); r=(r<=10)? 0 : (int) r-(r*fadeValue/256); g=(g<=10)? 0 : (int) g-(g*fadeValue/256); b=(b<=10)? 0 : (int) b-(b*fadeValue/256); strip.setPixelColor(ledNo, r,g,b); #endif #ifndef ADAFRUIT_NEOPIXEL_H leds[ledNo].fadeToBlackBy( fadeValue ); #endif }
#define RGBLoop 0 #define RandomColorTwinkle 1 #define SnowSparkle 2 #define Fire 3 #define Meteor 4 void showEffect(int effect_num, int repeats) {
for(int i =0 ; i < repeats;i++) { Serial.print("effect:"); Serial.println(effect_num); int Count = 20; int SpeedDelay = 100; boolean OnlyOne= false; int Cooling =55; int Sparking = 120; static byte heat[NUM_LEDS]; int cooldown; int Pixel = random(NUM_LEDS); byte red = 0x10; byte green = 0x10; byte blue =0x10; int SparkleDelay = 20; byte meteorSize =10; byte meteorTrailDecay = 64; boolean meteorRandomDecay = true; switch(effect_num) { case RGBLoop: for(int j = 0; j < 3; j++ ) { // Fade IN for(int k = 0; k < 256; k++) { switch(j) { case 0: setAll(k,0,0); break; case 1: setAll(0,k,0); break; case 2: setAll(0,0,k); break; } showStrip(); delay(3); } // Fade OUT for(int k = 255; k >= 0; k--) { switch(j) { case 0: setAll(k,0,0); break; case 1: setAll(0,k,0); break; case 2: setAll(0,0,k); break; } showStrip(); delay(3); } } break; case RandomColorTwinkle: Count = 20; SpeedDelay = 100; OnlyOne= false; setAll(0,0,0); for (int i=0; i
red = 0x10; green = 0x10; blue =0x10; SparkleDelay = 20; SpeedDelay = random(100,1000); setAll(red,green,blue); Pixel = random(NUM_LEDS); setPixel(Pixel,0xff,0xff,0xff); showStrip(); delay(SparkleDelay); setPixel(Pixel,red,green,blue); showStrip(); delay(SpeedDelay); break; case 3: Cooling =55; Sparking = 120; SpeedDelay = 15; static byte heat[NUM_LEDS]; // Step 1. Cool down every cell a little for( int i = 0; i < NUM_LEDS; i++) { cooldown = random(0, ((Cooling * 10) / NUM_LEDS) + 2); if(cooldown>heat[i]) { heat[i]=0; } else { heat[i]=heat[i]-cooldown;}} for( int k= NUM_LEDS - 1; k >= 2; k--) { heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3; } // Step 3. Randomly ignite new 'sparks' near the bottom if( random(255) < Sparking ) { int y = random(7); heat[y] = heat[y] + random(160,255); //heat[y] = random(160,255); } // Step 4. Convert heat to LED colors for( int j = 0; j < NUM_LEDS; j++) { setPixelHeatColor(j, heat[j] ); } showStrip(); delay(SpeedDelay); break; case Meteor: red = 0xff; green =0xff; blue =0xff; meteorSize =10; meteorTrailDecay = 64; meteorRandomDecay = true; SpeedDelay = 30; setAll(0,0,0); for(int i = 0; i < NUM_LEDS+NUM_LEDS; i++) { // fade brightness all LEDs one step for(int j=0; j5) ) { fadeToBlack(j, meteorTrailDecay ); } } // draw meteor for(int j = 0; j < meteorSize; j++) { if( ( i-j =0) ) { setPixel(i-j, red, green, blue); } } showStrip(); delay(SpeedDelay); } break; } }
}
void setupLedStrip() { strip.begin();strip.show(); // Initialize all pixels to 'off'} }
void loopLedStrip() {
}
CODE: Sensor Manager
Here there is a manager for the sensors, this code makes it easy to extend and add new sensors.
Also, the fetching is done in parallel, thus not hogging the Arduino processor allowing it to do other things, each sensor can have a timeout between each reading.
Since we had a problem with the DHT, its code was put in comments.
You should calibrate your water tank measures in the code, update when the water tank is full and when is empty with the EMPTY_WATER_LEVEL and MAX_WATER_LEVEL variables.
#include "DHT.h"<br>int EMPTY_WATER_LEVEL=14; int MAX_WATER_LEVEL = 2;
#define DHT_TYPE DHT11 #define TEMPERATURE_TYPE "TEMPERATURE" #define HUMIDITY_TYPE "HUMIDITY" #define MOISTURE_TYPE "MOISTURE" #define WATER_LEVEL_TYPE "WATER-LEVEL" #define TEMPERATURE_IN_SENSORS 0 #define HUMIDITY_IN_SENSORS 1 #define MOISTURE_IN_SENSORS 2 #define WATER_LEVEL_IN_SENSORS 3
//#define DHT_PIN_1 2 #define MOSITURE_PIN A0 //#define DHT_TYPE DHT22 //#define DHT_PIN 2
#define echoPin D7 // ultra sonic sensor #define trigPin D6 // ultra sonic sensor int NUM_OF_SENSORS=4;
int counter = 0; DHT dht(D11, DHT11); //---------------------- moisture sensor
typedef struct { String sensorType; unsigned long lastDataFetch; int updatePeriod; int lastRead = -2; int currentRead = -1; boolean hasPublished = false; boolean hasLastPublished = false; } sensorData;
//-------------------- moisture sensor
class plantSensors { sensorData sensors[4]; public:
//================== general int readRawTemp() { // return dht.readTemperature(); } int readRawWaterMeter() { long duration, distance; // Duration used to calculate distance, ultra sonic sensor
digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); duration = pulseIn(echoPin, HIGH); distance = duration/58.2; //Serial.println(distance); delay(50); return distance; } float getValue(int index_in_sensors) { return sensors[index_in_sensors].currentRead; } void updateHasPublished(int index_in_sensors, boolean value) { sensors[index_in_sensors].hasPublished = value; } String getSensorType(int index_in_sensors) { return sensors[index_in_sensors].sensorType; } boolean getHasPublished(int index_in_sensors) { return sensors[index_in_sensors].hasPublished; } boolean hasChanged(sensorData d) { return (d.lastRead != d.currentRead); } boolean hasChanged(int index_in_sensors) { return (sensors[index_in_sensors].lastRead != sensors[index_in_sensors].currentRead);
} void defineSensor(int positionInSensors, String type, long lastDataFetched, int updatePeriod, int lastRead, int currentRead, bool hasBuplished, bool hasLastPublished) { sensors[positionInSensors].sensorType = type; sensors[positionInSensors].lastDataFetch = lastDataFetched; sensors[positionInSensors].updatePeriod = updatePeriod; sensors[positionInSensors].lastRead = lastRead; sensors[positionInSensors].currentRead = currentRead; sensors[positionInSensors].hasPublished = hasBuplished; sensors[positionInSensors].hasLastPublished = hasLastPublished; } void resetSensors() { Serial.println("reset sensors!"); defineSensor(TEMPERATURE_IN_SENSORS, TEMPERATURE_TYPE, 0, 5000, 0, 0, false, false); defineSensor(HUMIDITY_IN_SENSORS, HUMIDITY_TYPE, 0, 5000, 0, 0, false, false); defineSensor(MOISTURE_IN_SENSORS, MOISTURE_TYPE, 0, 10000, 0, 0, false, false); defineSensor(WATER_LEVEL_IN_SENSORS, WATER_LEVEL_TYPE, 0, 4000, 0, 0, false, false); } void setup() {
Serial.begin(9600); // dht.begin();
pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT);//ultrasonic
int counter = 0; Serial.println("setup in the plant sensors");
// dht.begin(); resetSensors();
}
//==================== updating boolean isUpdateNeeded(int index_in_array) { return (millis() >= sensors[index_in_array].lastDataFetch + sensors[index_in_array].updatePeriod); } void updateIfPublishingIsNeeded(int index_in_array) { if (!sensors[index_in_array].hasPublished) { sensors[index_in_array].hasPublished = sensors[index_in_array].hasLastPublished && (!hasChanged(index_in_array)); } }
void updateSensorWithoutFetchingValue(int index_in_sensors) {
sensors[index_in_sensors].lastDataFetch = millis(); sensors[index_in_sensors].lastRead = sensors[index_in_sensors].currentRead; sensors[index_in_sensors].hasLastPublished = sensors[index_in_sensors].hasPublished; sensors[index_in_sensors].hasPublished = false;
} void updateHumidity() { updateSensorWithoutFetchingValue(HUMIDITY_IN_SENSORS); // sensors[HUMIDITY_IN_SENSORS].currentRead = dht.readHumidity(); updateSensorWithoutFetchingValue(HUMIDITY_IN_SENSORS); // Serial.print("Humidity : "); // Serial.println(dht.readTemperature()); } void updateTemp() { updateSensorWithoutFetchingValue(TEMPERATURE_IN_SENSORS); sensors[TEMPERATURE_IN_SENSORS].currentRead = dht.readTemperature(); updateIfPublishingIsNeeded(TEMPERATURE_IN_SENSORS); //Serial.print("Temp : "); //Serial.println(dht.readTemperature()); } void updateMoisture() { updateSensorWithoutFetchingValue(MOISTURE_IN_SENSORS); int percentage = ((int)(analogRead(MOSITURE_PIN) / 50)) * 5; sensors[MOISTURE_IN_SENSORS].currentRead = min(percentage, 100); updateIfPublishingIsNeeded(MOISTURE_IN_SENSORS); //Serial.print("Moisture : "); //Serial.println(sensors[MOISTURE_IN_SENSORS].currentRead);
} void updateWaterMeter() { updateSensorWithoutFetchingValue(WATER_LEVEL_IN_SENSORS); int raw = readRawWaterMeter(); int percentage = map(-raw, -EMPTY_WATER_LEVEL, -MAX_WATER_LEVEL, 0, 99); sensors[WATER_LEVEL_IN_SENSORS].currentRead = percentage; updateIfPublishingIsNeeded(WATER_LEVEL_IN_SENSORS); //Serial.print("Water-Destance : "); //Serial.print(sensors[WATER_LEVEL_IN_SENSORS].currentRead); } void updateSensors() { for (int i = 0; i < NUM_OF_SENSORS; i++) { if (isUpdateNeeded(i)) { switch (i) { case TEMPERATURE_IN_SENSORS: updateTemp(); printSensorIfChanged(i); break; case HUMIDITY_IN_SENSORS: updateHumidity(); printSensorIfChanged(i); break; case MOISTURE_IN_SENSORS: updateMoisture(); printSensorIfChanged(i); break; case WATER_LEVEL_IN_SENSORS: updateWaterMeter(); printSensorIfChanged(i); break; default: // statements break; } } } }
//=================== printing void printSensor(int positionInSensors) { Serial.print(sensors[positionInSensors].sensorType); Serial.print(": last:"); Serial.print(sensors[positionInSensors].lastRead); Serial.print(" current:"); Serial.print(sensors[positionInSensors].currentRead); Serial.print(" published?"); Serial.println(sensors[positionInSensors].hasPublished);} void printSensorIfChanged(int positionInSensors) { if (hasChanged(sensors[positionInSensors])) printSensor(positionInSensors); } void printSensors() {for (int i = 0; i < NUM_OF_SENSORS; i++) printSensorIfChanged(i);} };
plantSensors sensors;