Step 7a: Using Arduino IDE with ESP32

So we've already wired up the ESP32, but what exactly does it do? In short, an ESP32 is very similar to an Arduino, but it has WiFi and Bluetooth built in. Because of this, we can actually use the Arduino IDE to program our ESP32. Before we can start, we do have to install the board, so I've provided a link on how to set that up, here.

Step 7b: Installing the Required Libraries

After the board is installed, let's get the libraries installed. For this project, we will need the ESPAsyncWebServer and ESPAsyncTCP libraries. Download both.

To install a library in the Arduino IDE, go to Sketch -> Include Library -> Add .ZIP Library... and then select the zip files of the libraries you just downloaded. Restart the IDE once you are done.

Step 7c: Pushing the Code

Download the attached .INO file and open it up in the Arduino IDE. It will prompt you to move it into a folder that has the same name as the project. You should see all the code, and we will want to modify a couple lines.

For both the SSID and Password, you will want to replace them with your own networks credentials:

const char* ssid = "Network SSID";<br>const char* password = "Network Password";

Once these values are updated, you can hit the check mark to verify everything is working. Once you have verified it, plug in the ESP32 to your computer via a Micro USB cable. Go to Tools -> Serial Monitor. (We will need this open for the next step) Click the arrow button to begin pushing the code.

This part is very important. When the console text turns red and begins to print out a pattern of "....___....___", hold down the BOOT button on the ESP32. If you do not do this, you will receive a header error.

Step 7d: Testing it!

Now that the code is pushed, look at the Serial Monitor. It should print the IP Address of the board, as well as current readings of the sensors every few seconds. Open up a web browser, either on your laptop or phone, and type in the IP Address

Make sure you are on the same WiFi network as the ESP32.

You should see an interface load. If you plug in the lamp and hit the Toggle Light button, you should see the light switch on and off. You can also see the sensor values, although they should say "Empty" and "Dry" at the moment. The buzzer will also occasionally beep, as a warning that there is no water detected.

Step 7e: In-Depth Breakdown

For anyone interested in the actual code, here's the nitty gritty:

We'll start from the top:

// Import required libraries<br>#include "WiFi.h" #include "ESPAsyncWebServer.h" // Replace with your network credentials const char* ssid = "Network SSID"; const char* password = "Network Password"; #define relay_pin 15 #define soil_moist 36 #define water_level 39 //Variables for Buzzer int freq = 261.63; //Set frequency of the buzzer, this is a C note int channel = 0; int resolution = 8; //Boolean for light status bool light = true; //Set thresholds for sensors (Tweak depending on plant/soil type) float highWater = 1000; float lowWater = 700; float highMoist = 1200; float lowMoist = 500; // Create AsyncWebServer object on port 80 AsyncWebServer server(80);

This section is all about declaring the variables and including libraries. Pretty basic stuff, but one thing to note, you may want to change the threshold values for the water sensors depending on your planter. Another interesting thing is the frequency of the buzzer. If you change this value, it will change the pitch of the buzz. Right now it's set to C5.

//Returns soil moisture reading<br>String readSoilMoisture() { float m = analogRead(soil_moist); if (isnan(m)) { Serial.println("Failed to read from Soil Moisture sensor!"); return "--"; } else { Serial.println(m); //Sets text depending on values if(m > highMoist){ return String("High"); } else if (m > lowMoist) { return String("Low"); } else { //Beeps the buzzer for 250ms ledcWriteTone(channel, freq); delay(250); ledcWriteTone(channel, 0); Serial.println("Buzzer Activated"); return String("Dry"); } } }

//Returns water level reading String readWaterLevel() { float l = analogRead(water_level); if (isnan(l)) { Serial.println("Failed to read from Water Level sensor!"); return "--"; } else { Serial.println(l); //Sets text depending on values if(l > highWater){ return String("High"); } else if (l > lowWater) { return String("Low"); } else { //Beeps the buzzer for 250ms ledcWriteTone(channel, freq); delay(250); ledcWriteTone(channel, 0); Serial.println("Buzzer Activated"); return String("Empty"); } } }

//Toggles the light status String toggleLight() { if(light){ light = false; //Activates relay digitalWrite(relay_pin, LOW); return String("OFF"); } else if (!light){ light = true; //Deactivates relay digitalWrite(relay_pin, HIGH); return String("ON"); } }

These three sections are all functions that return a string value. This is how we derive our sensor data, and also how we will toggle the light.

After this is the HTML bit. I won't get into how HTML works, but I want to mention that the button tag calls a JavaScript function, and this is what toggles the light. You could possibly implement this to do more complex interfaces, like adding/setting timers. Another note, two of the JavaScript functions in this code are within a SetInterval(); This means that they will repeat every "interval" which we currently have set to 1000ms. These functions are what constantly load the sensor values and refresh the page.

//Updates variable text with current values<br>String processor(const String& var){ //Serial.println(var); if(var == "SOILMOISTURE"){ return readSoilMoisture(); } else if(var == "WATERLEVEL"){ return readWaterLevel(); } return String(); }

Further down the line, we get this code, which is what sets the sensor values every time the web page is reloaded.

And lastly, we have our setup function, which really just starts up the Asynchronous Web Server, and handles the page navigation (routing):

//Runs at the start<br>void setup(){ // Serial port for debugging purposes Serial.begin(115200); //Setup the buzzer ledcSetup(channel, freq, resolution); ledcAttachPin(25, channel); //Setup the pinMode for the relay pinMode (relay_pin, OUTPUT); // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print ESP32 Local IP Address Serial.println(WiFi.localIP()); // Route for root / web page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/html", index_html, processor); }); server.on("/soil-moisture", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/plain", readSoilMoisture().c_str()); }); server.on("/water-level", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/plain", readWaterLevel().c_str()); }); server.on("/toggle-light", HTTP_GET, [](AsyncWebServerRequest *request){ request->send_P(200, "text/plain", toggleLight().c_str()); }); // Start server server.begin(); }

For more information on using the Asynchronous Web server, be sure to check out their GitHub page. Also, huge shout out to Rui Santos, who wrote the tutorial this code is based on. (He explains all the code much better than I can.)

And make sure to check out my GitHub for all of the files.

Step 7f: Future Possiblities

Using the ESP32 gives us a huge range of optional functionality. I tried to leave it a pretty basic template so that you can go in and modify the code yourselves, add features, and hopefully show me what you've made!

Some possible features I considered were:

Email notifications (when the planter is out of water)

Custom buzzer noises/tones (plays Africa by Toto when dry)

Using the built in RTC to set timers for the light

I'd love to see what you come up with for your planter!