DIY Hydroponics System using pH Sensor, EC Sensor, Ultrasonic, DS18B20, & ESP32
Table of Contents
DIY Hydroponics:
DIY Hydroponics System using pH Sensor, EC Sensor, Ultrasonic, DS18B20, & ESP32- This is version 2 of my DIY hydroponics system, which uses a pH sensor, EC sensor, A02YYUW Waterproof Ultrasonic Sensor, DS18B20 waterproof one-wire digital temperature sensor, a pair of Gravity Analog signal isolators, the ESP32 WiFi + Bluetooth module, and Blynk application.
If you have read my previous article on the Hydroponics system, you know I have not changed anything on the hardware side. Everything looks the same.
In this article, I won’t talk about the connections and how to install all the required libraries; because I have already done it in the previous article and also in the video available on my YouTube channel “Electronic Clinic”.
Amazon Links:
ESP32 WiFi + Bluetooth Module (Recommended)
DS18B20 one-wire digital waterproof temperature sensor
DFrobot Gravity Analog pH sensor Kit V2
*Disclosure: These are affiliate links. As an Amazon Associate I earn from qualifying purchases.
I made some small changes in the programming and in the Blynk application.
In version 1 of the DIY Hydroponics system, we could not change the sensor limits in real time because all the limits were pre-defined in the code. To change the limits, we had to go into the program and update them each time.
But this time, you do not need to change the code. You can change the limits using sliders in the Blynk application.
The limits you set here will be stored in the ESP32’s EEPROM. So, if the controller turns off or resets, the limits you set will not change. This is a very powerful feature, especially for those who want to use this hydroponics system in multiple locations. Everyone can configure the system as they like. Let me show you how.
In the Blynk application, you can see I have added two tabs. As you can see in the image above “Tab 1 and Tab 2”. On “Tab 1” I have added widgets for monitoring the water level, pH value, TDS value, and temperature.
On the second tab, I have added sliders for setting the limits for those sensors.
As you can see, I have already set the lower and upper limits for monitoring the water level.
For this demonstration, I am using this place as a water tank.
The total distance from the Ultrasonic Sensor to the bottom of the water tank is printed on the Oled display module; which in my case was 100cm.
Note: The distance printed on the Oled display module is the total distance from the Ultrasonic Sensor to the point from where the signal is reflected back. Whereas the water level value displayed on the gauge represents the water percentage. So, while defining the Water tank lower and upper limits make sure you follow the distance value printed on the Oled display module.
So, using the sliders I set the lower limit to 80cm. In the image given below, you can see the water level is 74cm.
When the water level goes below the lower limit, this light will turn on. Instead of turning ON the light, you can also send a notification.
You can see the bulb has turned ON because the Water percentage is dropped below the pre-defined value.
Similarly, I have set a limit for the pH sensor.
The pH limit is 7.28. I want the ESP32 to turn ON the Bulb when the Water pH value drop below 7.28. You can set any limit as per your preference.
Right now, you can see pH value of drinking water that is 7.8. The light is off because the pH value is greater than the defined value.
Let’s make this water a bit acidic by adding some cold drink.
You can clearly see, after adding cold drink the drinking water pH value has dropped below the set pH value, it indicates that the water is going acidic, so that’s why the Light is turned ON to notify the concerned person.
If I want this value to be normal, I can change its limit in the Blynk application.
So, I dropped the pH limit to 5.91. Now you can see the light is off, which means everything is normal as per the set limit.
Now, it will only notify when the water becomes too acidic. So let’s make the water more acidic.
You can see, the light turned on again; as the water pH value has dropped below the defined limit. Although, I mixed pepsi in the water and still it’s too acidic.
In version 3, you won’t see so much wiring because everything will be plug-and-play. I have already placed an online order on NextPCB, and once I receive the PCBs, I will share updates with you.
To check for errors and manufacturability in your PCB design, you can use NextPCB’s online HQDFM tool or their desktop software, both available absolutely free of cost. When you design PCB through EDA, you know what DRC is and believe it is enough for everything. But the reality is more than that. DRC, DFM, and DFA are totally different concepts. DRC is a design rule. DFM and DFA represent a guideline: the manufacturers are able to produce your design into reality and make sure all circuit and assembly works through the whole product life. From what I can tell, a manufacturer with such a tool means they care for the quality of the products.
Additionally, NextPCB provides component sourcing services since they are the largest component distributor in China. And guess what? They offer free PCBs for new customers up to $30. And that’s not all. For 5 PCBs assembly, you can use their assembly services for free if the types of components on your board are 30 or less.
By the way, the PCB board I am currently using, I have also manufactured it from NextPCB. Their PCB quality is quite amazing. The edges are burr-free and polished smooth. The weight and texture feel great to my touch. If you also want to order high-quality PCBs or PCB assembly for your projects or prototypes, what are you waiting for? Quote.
For the connections read my previous article.
Now, let me show you the changes, I made in the programming.
DIY Hydroponics Programming:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
/* * ec sensor, ds18b20, and ph sensor altogether */ #define BLYNK_TEMPLATE_ID "TMPL6ljkiG-BJ" #define BLYNK_TEMPLATE_NAME "Hydroponics system with ph sensor and ec sensor" #define BLYNK_AUTH_TOKEN "uk89pUVr_u0eKs-w59pc5u3LNxV1br_d" #include <WiFi.h> #include <WiFiClient.h> #include <BlynkSimpleEsp32.h> #include <EEPROM.h> #include <OneWire.h> #include <Wire.h> #include <DallasTemperature.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <SimpleTimer.h> #include <HardwareSerial.h> char auth[] = BLYNK_AUTH_TOKEN; // Your WiFi credentials. // Set password to "" for open networks. char ssid[] = "AndroidAP3DEC"; char pass[] = "electroniclinic"; // distance sensor start HardwareSerial Ultrasonic_Sensor(2); // TX2 (pin 17), RX2 (pin 16) // Define connections to sensor int pinRX = 16; // Choose a suitable pin for RX int pinTX = 17; // Choose a suitable pin for TX // Array to store incoming serial data unsigned char data_buffer[4] = {0}; int distance; // Variable to hold checksum unsigned char CS; int Relay = 13; int Relay_Status = 0; int waterLevelPer=0; int Percentage = 0; int previousDistance; unsigned long distanceChangeTime; //end distance sensor SimpleTimer timer; SimpleTimer timer1; float calibration_value = 15.65 - 0.7; //21.34 - 0.7 int phval = 0; unsigned long int avgval; int buffer_arr[10],temp; float ph_act; #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); int lowerlevel; int upperlevel; float phlimit; int templimit; long int tdslimit; namespace pin { const byte tds_sensor = A0; // 36 const byte one_wire_bus = 5; // Dallas Temperature Sensor } namespace device { float aref = 3.3; // Vref, this is for 3.3v compatible controller boards, for arduino use 5.0v. } namespace sensor { float ec = 0; unsigned int tds = 0; float waterTemp = 0; float ecCalibration = 1; } OneWire oneWire(pin::one_wire_bus); DallasTemperature dallasTemperature(&oneWire); void setup() { Serial.begin(115200); // Dubugging on hardware Serial 0 Ultrasonic_Sensor.begin(9600, SERIAL_8N1, pinRX, pinTX); // Initialize the hardware serial dallasTemperature.begin(); Blynk.begin(auth, ssid, pass); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); delay(2000); display.clearDisplay(); display.setTextColor(WHITE); pinMode(Relay,OUTPUT); digitalWrite(Relay,LOW); // Initialize EEPROM EEPROM.begin(512); // Load limits from the EEPROM upperlevel = EEPROM.read(0); lowerlevel = EEPROM.read(1); phlimit = EEPROM.read(2); tdslimit = EEPROM.read(3); templimit = EEPROM.read(4); timer.setInterval(1000L, display_pHValue); timer1.setInterval(5000L, EC_and_ph); } void loop() { Blynk.run(); timer.run(); // Initiates SimpleTimer timer1.run(); A02YYUW_Sensor(); } void readTdsQuick() { dallasTemperature.requestTemperatures(); sensor::waterTemp = dallasTemperature.getTempCByIndex(0); float rawEc = analogRead(pin::tds_sensor) * device::aref / 4096; // read the analog value more stable by the median filtering algorithm, and convert to voltage value float temperatureCoefficient = 1.0 + 0.02 * (sensor::waterTemp - 25.0); // temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0)); sensor::ec = (rawEc / temperatureCoefficient) * sensor::ecCalibration; // temperature and calibration compensation sensor::tds = (133.42 * pow(sensor::ec, 3) - 255.86 * sensor::ec * sensor::ec + 857.39 * sensor::ec) * 0.5; //convert voltage value to tds value Serial.print(F("TDS:")); Serial.println(sensor::tds); Serial.print(F("EC:")); Serial.println(sensor::ec, 2); Serial.print(F("Temperature:")); Serial.println(sensor::waterTemp,2); } void ph_Sensor() { for(int i=0;i<10;i++) { buffer_arr[i]=analogRead(35); delay(30); } for(int i=0;i<9;i++) { for(int j=i+1;j<10;j++) { if(buffer_arr[i]>buffer_arr[j]) { temp=buffer_arr[i]; buffer_arr[i]=buffer_arr[j]; buffer_arr[j]=temp; } } } avgval=0; for(int i=2;i<8;i++) avgval+=buffer_arr[i]; float volt=(float)avgval*3.3/4096.0/6; //Serial.print("Voltage: "); //Serial.println(volt); ph_act = -5.70 * volt + calibration_value; Serial.print("pH Val: "); Serial.println(ph_act); // delay(1000); } void display_pHValue() { // display on Oled display // Oled display display.clearDisplay(); display.setTextSize(2); display.setCursor(0,0); // column row display.print("pH:"); display.setTextSize(2); display.setCursor(55, 0); display.print(ph_act); display.setTextSize(2); display.setCursor(0,20); display.print("EC:"); display.setTextSize(2); display.setCursor(60, 20); display.print(sensor::ec); display.setTextSize(2); display.setCursor(0,40); display.print("D:"); display.setTextSize(2); display.setCursor(60, 40); display.print(distance); display.display(); Blynk.virtualWrite(V0, waterLevelPer); Blynk.virtualWrite(V1, ph_act); Blynk.virtualWrite(V2, sensor::tds); Blynk.virtualWrite(V3, sensor::waterTemp); Serial.print("waterLevelPer : "); Serial.println( waterLevelPer); Serial.print("lower level:"); Serial.println(lowerlevel); Serial.print("upper level:"); Serial.println(upperlevel); Serial.print("PH Limit:"); Serial.println( phlimit); Serial.print("TDS Limit:"); Serial.println( tdslimit); Serial.print("Temp Limit:"); Serial.println(templimit); } void A02YYUW_Sensor() { // Run if data available if (Ultrasonic_Sensor.available() > 0) { delay(4); // Check for packet header character 0xff if (Ultrasonic_Sensor.read() == 0xff) { // Insert header into array data_buffer[0] = 0xff; // Read remaining 3 characters of data and insert into array for (int i = 1; i < 4; i++) { data_buffer[i] = Ultrasonic_Sensor.read(); } //Compute checksum CS = data_buffer[0] + data_buffer[1] + data_buffer[2]; // If checksum is valid compose distance from data if (data_buffer[3] == CS) { distance = (data_buffer[1] << 8) + data_buffer[2]; // Print to serial monitor distance= distance / 10; // cm waterLevelPer = map(distance, upperlevel, lowerlevel, 100, 0); delay(10); } } } // // Serial.print("distance: "); // Serial.print(distance); // Serial.println(" cm"); if ((waterLevelPer <= 20)||(ph_act < phlimit)) { digitalWrite(Relay, HIGH); delay(10); } else { digitalWrite(Relay, LOW); delay(10); } } void EC_and_ph() { readTdsQuick(); // delay(20); ph_Sensor(); // delay(20); } BLYNK_WRITE(V4) { lowerlevel=param.asInt(); EEPROM.write(1, lowerlevel); EEPROM.commit(); } BLYNK_WRITE(V5) { upperlevel=param.asInt(); EEPROM.write(0, upperlevel); EEPROM.commit(); } BLYNK_WRITE(V6) { phlimit=param.asFloat(); EEPROM.write(2, phlimit); EEPROM.commit(); } BLYNK_WRITE(V7) { tdslimit=param.asInt(); EEPROM.write(3, tdslimit); EEPROM.commit(); } BLYNK_WRITE(V8) { templimit=param.asInt(); EEPROM.write(4, templimit); EEPROM.commit(); } |
About the changes in DIY Hydroponics System:
1 2 3 4 5 6 7 8 |
#include <EEPROM.h> I added the EEPROM library. int lowerlevel; int upperlevel; float phlimit; int templimit; long int tdslimit; |
Next, I defined some variables for the Limits.
Inside the void setup() function, I used the EEPROM.begin().
1 2 3 4 5 |
upperlevel = EEPROM.read(0); lowerlevel = EEPROM.read(1); phlimit = EEPROM.read(2); tdslimit = EEPROM.read(3); templimit = EEPROM.read(4); |
Next, I added code to read values from these particular addresses in the EEPROM and assign them to different variables. These addresses will store the values sent from Blynk application. The purpose of using these instructions over here is to ensure that when the controller restarts, it reads these stored values from the EEPROM.
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 |
BLYNK_WRITE(V4) { lowerlevel=param.asInt(); EEPROM.write(1, lowerlevel); EEPROM.commit(); } BLYNK_WRITE(V5) { upperlevel=param.asInt(); EEPROM.write(0, upperlevel); EEPROM.commit(); } BLYNK_WRITE(V6) { phlimit=param.asFloat(); EEPROM.write(2, phlimit); EEPROM.commit(); } BLYNK_WRITE(V7) { tdslimit=param.asInt(); EEPROM.write(3, tdslimit); EEPROM.commit(); } BLYNK_WRITE(V8) { templimit=param.asInt(); EEPROM.write(4, templimit); EEPROM.commit(); } |
For the lower level, upper level, pH limit, TDS limit, and temperature limit, I have defined virtual pins V4, V5, V6, V7, and V8. Through these virtual variables, we send the sensor limits from Blynk application to the ESP32. Then, using EEPROM.write() and EEPROM.commit(), we permanently save these limits to the ESP32’s memory.
After this, you need to use these limits in your program.
1 |
waterLevelPer = map(distance, upperlevel, lowerlevel, 100, 0); |
As you can see, I used the upper level and lower level variables in the map function, and I also used the pH limit in a condition.
1 2 3 4 5 |
if ((waterLevelPer <= 20)||(ph_act < phlimit)) { digitalWrite(Relay, HIGH); delay(10); } |
You can do the same for the other sensors.
So, these are the changes, I made on the ESP32 side.
Next, login into your Blynk account and create 5 more datastreams for the Sensors limits. In the previous article, we created 4 datastreams from V0 to V3. This time you need to create V4, V5, V6, V7, and V8 datastreams for the limits.
If you don’t know how to modify the existing dashboard then you should watch the video tutorial given at the end of this article.
Once all the datastreams are created then add sliders to the dashboard and assign the created datastreams exactly the same way as we assigned to the Gauges in the previous article.
Now, for the Blynk iOT application setup.
You will need to do everything just the same, but this time, you will also need to add tabs to give it more professional look. On one tab, we will add widgets for monitoring.
And on the other Tab, we will add sliders.
If you face any issues, read my previous article, and if you want to learn in detail then I highly recommend reading my article on the the ESP32 and the New Blynk V2.
Now, with this updated Blynk IoT application, we can not only monitor sensors at any time and from any location, but we can also adjust sensor limits according to our preferences. So, that’s all for now.
hello,
love your work! truly remarkable
is your versions 3 board available to download ?
would love to get my hands on it and have a play
let me know
cheers
luke