diff --git a/VERSION b/VERSION index 82f24fd..8eb3891 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v4.0.1 +v5.0.0 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 4d9c656..1ad678b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,37 @@ -# ESP32 Plant Monitoring / Watering System +# ESP32 Plant Monitoring System ![ESP32 Plant Monitoring Project Image](/static/img/project.png) --- +## Table of Contents + + +- [ESP32 Plant Monitoring System](#esp32-plant-monitoring-system) + - [Table of Contents](#table-of-contents) + - [Overview](#overview) + - [Shopping List](#shopping-list) + - [Setup](#setup) + - [Circuit Schematic](#circuit-schematic) + - [ESP32 Pinout](#esp32-pinout) + - [30 Pin](#30-pin) + - [38 Pin](#38-pin) + - [Configuration](#configuration) + - [WiFI Configuration](#wifi-configuration) + - [Other Configuration](#other-configuration) + - [Libraries](#libraries) + - [External Libraries](#external-libraries) + - [Uploading](#uploading) + - [Images](#images) + - [3D Print](#3d-print) + - [DHT22 Case](#dht22-case) + - [MQ-135 Case](#mq-135-case) + - [TODO](#todo) + - [Links](#links) + + +--- + ## Overview This project implements a monitoring system using an ESP32 microcontroller to @@ -17,19 +45,23 @@ This is designed to be used to monitor the environment when growing plants Here is a list of parts used in this project +> [!NOTE] > *Amazon links are non-affiliate* -| NAME | PRICE | COUNT | DESCRIPTION | LINK | NOTE | -|-----------------|-------|-------|-----------------------------------|--------|----------------------------------------------------| -| ESP32 | $5 | 1 | Microcontroller | [Amazon](https://www.amazon.com/ESP-WROOM-31-Development-Microcontroller-Integrated-Compatible/dp/B08D5ZD528) | [USB-C ESP32 Amazon](https://www.amazon.com/dp/B0CR5Y2JVD) | -| DHT22 | $7 | 1 | Temp/Humidity Sensor | [Amazon](https://www.amazon.com/gp/product/B0795F19W6) | | -| MQ-135 | $3 | 1 | CO2 Sensor | [Amazon](https://www.amazon.com/Ximimark-Quality-Hazardous-Detection-Arduino/dp/B07L73VTTY) | | -| OLED Display | $3 | 1 | 0.96" OLED Display 128x64 SSD1306 | [Amazon](https://www.amazon.com/Hosyond-Display-Self-Luminous-Compatible-Raspberry/dp/B09C5K91H7) | | -| 1K Ω Resistor | $0.25 | 1 | 1K Ohm Resistor | [Amazon](https://www.amazon.com/California-JOS-Carbon-Resistor-Tolerance/dp/B0BR66ZN6B) | MQ-135 analog pin | -| 10K Ω Resistor | $0.25 | 0/1 | 10K Ohm Resistor | [Amazon](https://www.amazon.com/California-JOS-Carbon-Resistor-Tolerance/dp/B0BR67DJHM) | Most DHT22 sensor modules have a built-in resistor | -| Wire/Breadboard | $1 | 0/1 | Jumper wire or breadboard | [Amazon](https://www.amazon.com/DEYUE-breadboard-Set-Prototype-Board/dp/B07LFD4LT6) | Optional depending on setup | -| Breakout Board | $4 | 0/1 | ESP32 Breakout Board GPIO | [Amazon](https://www.amazon.com/dp/B0BNQ8V65G) | Optional depending on setup | - +| NAME | PRICE | COUNT | DESCRIPTION | LINK | NOTE | +|-----------------|---------|-------|-----------------------------------|--------|----------------------------------------------------| +| ESP32 | $5 | 1 | Microcontroller | [Amazon](https://www.amazon.com/ESP-WROOM-31-Development-Microcontroller-Integrated-Compatible/dp/B08D5ZD528) | [USB-C ESP32 Amazon](https://www.amazon.com/dp/B0CR5Y2JVD) | +| DHT22 | $3 | 1 | Temp/Humidity Sensor | [Amazon](https://www.amazon.com/dp/B0CPHQC9SF) | | +| MQ-135 | $3 | 1 | CO2 Sensor | [Amazon](https://www.amazon.com/Ximimark-Quality-Hazardous-Detection-Arduino/dp/B07L73VTTY) | | +| OLED Display | $3 | 1 | 0.96" OLED Display 128x64 SSD1306 | [Amazon](https://www.amazon.com/Hosyond-Display-Self-Luminous-Compatible-Raspberry/dp/B09C5K91H7) | | +| Kasa Smart Plug | $7.50 | 2 | TP-Link Kasa Smart Plug HS103 | [Amazon](https://www.amazon.com/dp/B07RCNB2L3) | Intake and exhaust fans smart plugs | +| 1K Ω Resistor | $0.25 | 1 | 1K Ohm Resistor | [Amazon](https://www.amazon.com/California-JOS-Carbon-Resistor-Tolerance/dp/B0BR66ZN6B) | MQ-135 analog pin | +| 10K Ω Resistor | $0.25 | 0/1 | 10K Ohm Resistor | [Amazon](https://www.amazon.com/California-JOS-Carbon-Resistor-Tolerance/dp/B0BR67DJHM) | Most DHT22 sensor modules have a built-in resistor | +| Wire/Breadboard | $1 | 0/1 | Jumper wire or breadboard | [Amazon](https://www.amazon.com/DEYUE-breadboard-Set-Prototype-Board/dp/B07LFD4LT6) | Optional depending on setup | +| Breakout Board | $4 | 0/1 | ESP32 Breakout Board GPIO | [Amazon](https://www.amazon.com/dp/B0BNQ8V65G) | Optional depending on setup | +| **TOTAL** | ~$34.25 | | | | | + +> [!TIP] > *Lower prices can be found if you shop around* --- @@ -51,6 +83,7 @@ Once you have all the required parts in hand, you can start wiring | 21 | OLED SCL | SCL | 3.3 | 3.3V | GND | | 22 | OLED SDA | SDA | 3.3 | 3.3V | GND | +> [!NOTE] > *Feel free to change the pins used on the ESP32, just note, if you change the pins, you will need to update the values in the code.* ### ESP32 Pinout @@ -84,6 +117,19 @@ Here is a list of WiFi variables that **must** be defined and their descriptions Other variables in the code can be changed to suite your needs +### Libraries + +You will need to install additional libraries. + +In Arduino IDE, go to library manager and search and install the +required libraries listed in the `DEPENDENCIES` section in [`main.ino`](/src/main/main.ino) + +#### External Libraries + +- [KasaSmartPlug Github](https://github.com/kj831ca/KasaSmartPlug) + +--- + ### Uploading I use Arduino IDE to upload the code to the board. I had to install additional drivers ***[(Download)](https://www.silabs.com/documents/public/software/CP210x_Universal_Windows_Driver.zip)*** to get connected to my board. @@ -92,6 +138,17 @@ I installed the ESP32 boards and selected ESP32dev board. --- +## Images + + + +| | | +|--------------------------------------|--------------------------------------| +| ![ESP32 Plant Monitoring Project Image](/static/img/project.png) | ![ESP32 Plant Monitoring Project Image](/static/img/project.png) | +| ![ESP32 Plant Monitoring Project Image](/static/img/project.png) | ![ESP32 Plant Monitoring Project Image](/static/img/project.png) | + +--- + ## 3D Print ![ESP32 Plant Monitoring Housing Image](/static/img/3d-housing.png) @@ -134,5 +191,6 @@ The 3D model files are located under the [3D-models](/3D-models) directory. * https://www.thingiverse.com/thing:4521313 * https://www.thingiverse.com/thing:2893581 * https://javl.github.io/image2cpp +* https://github.com/kj831ca/KasaSmartPlug --- diff --git a/src/main/.theia/launch.json b/src/main/.theia/launch.json new file mode 100644 index 0000000..1c48c5b --- /dev/null +++ b/src/main/.theia/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + "version": "0.2.0", + "configurations": [ + + { + "cwd": "${workspaceFolder}", + "executable": "./bin/executable.elf", + "name": "Debug with JLink", + "request": "launch", + "type": "cortex-debug", + "device": "", + "runToEntryPoint": "main", + "showDevDebugOutput": "none", + "servertype": "jlink" + } + ] +} diff --git a/src/main/config.h b/src/main/config.h index 0f1a68a..7e2d55f 100644 --- a/src/main/config.h +++ b/src/main/config.h @@ -33,10 +33,21 @@ bool SHOW_STARTUP = true; // Set to true to show bool SHOW_BITMAP = true; // Set to true to show the bitmap bool SHOW_CUSTOM_TEXT = true; // Set to true to show custom text bool SHOW_IP_INFO = true; // Set to true to show IP information -constexpr char STARTUP_TEXT[] = "HI"; // Startup custom text +constexpr char STARTUP_TEXT[] = ":)"; // Startup custom text constexpr char CUSTOM_TEXT[] = ""; // OLED custom text "_____________________" -bool INTERUPT_WITH_BITMAP = true; // Periodically show bitmap -constexpr int INTERUPT_BITMAP_TIME = 300000; // Check WiFi time (ms) +bool INTERRUPT_WITH_BITMAP = true; // Periodically show bitmap +constexpr int INTERRUPT_BITMAP_TIME = 300000; // Check WiFi time (ms) + +// SMART PLUG CONFIGURATION +const char *intakePlugAlias = "plug_intake"; // Kasa plug alias (Intake) +const char *exhaustPlugAlias = "plug_exhaust"; // Kasa plug aliases (Exhaust) +constexpr int SMARTPLUG_UPDATE_TIME = 30000; // Update smart plugs time (ms) +constexpr float DESIRED_TEMP = 75.0; // Desired temperature in F +constexpr float DESIRED_HUMIDITY = 50.0; // Desired humidity in percentage +constexpr float DESIRED_CO2 = 800.0; // Desired CO2 level (ppm) +constexpr float TEMP_HYSTERESIS = 1.0; // Temperature hysteresis to prevent rapid switching +constexpr float HUMIDITY_HYSTERESIS = 5.0; // Humidity hysteresis to prevent rapid switching +constexpr float CO2_HYSTERESIS = 100.0; // CO2 hysteresis to prevent rapid switching // SERIAL CONFIGURATION constexpr int BAUD_RATE = 115200; // Baud rate diff --git a/src/main/main.ino b/src/main/main.ino index f7450ca..7994d22 100644 --- a/src/main/main.ino +++ b/src/main/main.ino @@ -2,7 +2,9 @@ * File: ./main.ino */ -// Dependencies +// ============================================================================ +// DEPENDENCIES ----------------------------------------------------------- +// ============================================================================ #include "WiFi.h" #include "ESPAsyncWebServer.h" #include "DHTesp.h" @@ -10,9 +12,10 @@ #include #include #include - -#include "config.h" // Include config header file -#include "bitmap.h" // Include bitmap header file +#include "KasaSmartPlug.h" // KASA TP-link smart plug library: https://github.com/kj831ca/KasaSmartPlug +#include "config.h" // Include config header file +#include "bitmap.h" // Include bitmap header file +// ============================================================================ // ============================================================================ // GLOBAL INSTANCES ----------------------------------------------------------- @@ -20,6 +23,9 @@ DHTesp dht; // DHT Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins) AsyncWebServer server(SERVER_PORT); // Define server on port +KASAUtil kasaUtil; // Kasa utility object +KASASmartPlug *intakePlug = NULL; // Smart plug pointers (Intake) +KASASmartPlug *exhaustPlug = NULL; // Smart plug pointers (Exhaust) // ============================================================================ // ============================================================================ @@ -75,12 +81,103 @@ float celsiusToFahrenheit(float celsius) { return fahrenheit; } +// ------------------------------------- +// KASA TP-Link Smart Plug Functions +// ------------------------------------- +// Function to initialize smart plugs +void initSmartPlugs() { + int found = kasaUtil.ScanDevices(); + Serial.printf("Found %d Kasa devices\n", found); + + display.clearDisplay(); + display.setCursor(0, 0); + display.print("KASA devices found: "); + display.println(found); + display.display(); + delay(500); + + // Loop through found devices and match aliases + for (int i = 0; i < found; i++) { + KASASmartPlug *plug = kasaUtil.GetSmartPlugByIndex(i); + if (plug == NULL) continue; + + Serial.printf("Found Plug: %s (IP: %s, State: %d)\n", plug->alias, plug->ip_address, plug->state); + + display.clearDisplay(); + display.setCursor(0, 0); + display.print("KASA Smart Plugs"); + display.setCursor(0, 16); + display.print("Alias: "); + display.println(plug->alias); + display.print("IP : "); + display.println(plug->ip_address); + display.print("State : "); + display.println(plug->state); + display.display(); + delay(1000); + + // Match plugs by their aliases + if (strcmp(plug->alias, intakePlugAlias) == 0) { + intakePlug = plug; + Serial.println("Intake plug initialized."); + } else if (strcmp(plug->alias, exhaustPlugAlias) == 0) { + exhaustPlug = plug; + Serial.println("Exhaust plug initialized."); + } + } + + // Check if plugs were found + if (intakePlug == NULL) { + Serial.println("Error: Intake plug not found!"); + + display.clearDisplay(); + display.setCursor(0, 0); + display.print("ERROR: Intake plug not found"); + display.display(); + delay(1000); + } + if (exhaustPlug == NULL) { + Serial.println("Error: Exhaust plug not found!"); + + display.clearDisplay(); + display.setCursor(0, 0); + display.print("ERROR: Exhaust plug not found"); + display.display(); + delay(1000); + } +} + +// Function to turn a plug on or off +void setPlugState(KASASmartPlug* plug, bool state) { + // Setting variable for printing. otherwise dont need this + //TODO: Figure out how to turn this into a string + KASASmartPlug *plugAlias = kasaUtil.GetSmartPlug(plug->alias); + + if (plug == NULL) { + //Serial.printf("Error: Could not find plug with alias '%s'\n", plugAlias); + return; + } + + if (state) { + //Serial.printf("Turning ON plug: %s\n", plugAlias); + plug->SetRelayState(1); // Turn on + } else { + //Serial.printf("Turning OFF plug: %s\n", plugAlias); + plug->SetRelayState(0); // Turn off + } +} + +//TODO: Function to get plug state + // ------------------------------------- // WIFI Functions // ------------------------------------- // Function to connect to Wi-Fi void connectToWiFi() { Serial.println(F("Setting up Wi-Fi...")); + + WiFi.mode(WIFI_STA); // Station mode only + if (!WiFi.setHostname(WIFI_HOSTNAME)) { Serial.printf("Error: Failed to set Wi-Fi hostname: %s\n", WIFI_HOSTNAME); } @@ -198,13 +295,13 @@ void showIPInfo() { if (SHOW_IP_INFO) { display.clearDisplay(); display.setCursor(0, 16); - display.print("SSID: "); + display.print("SSID: "); display.println(WIFI_SSID); display.setCursor(0, 26); - display.print("Hostname: "); + display.print("Host: "); display.println(WiFi.getHostname()); display.setCursor(0, 36); - display.print("IP: "); + display.print("IP: "); display.println(WiFi.localIP().toString().c_str()); // Custom Text @@ -265,11 +362,12 @@ void updateOLED(float co2, float temperature, float temperatureF, float humidity // SETUP ---------------------------------------------------------------------- // ============================================================================ void setup() { - pinMode(CO2_PIN, INPUT); // Set co2 pin mode + pinMode(CO2_PIN, INPUT); // Set co2 pin mode dht.setup(DHT_PIN, DHTesp::DHT_MODEL_t::DHT22); // Initialize DHT sensor - Serial.begin(BAUD_RATE); // Initialize Serial for debugging - initOLED(); // Initialize OLED - connectToWiFi(); // Connect to Wi-Fi + Serial.begin(BAUD_RATE); // Initialize Serial for debugging + initOLED(); // Initialize OLED + connectToWiFi(); // Connect to Wi-Fi + initSmartPlugs(); // Initialize Smart Plugs // Define the root endpoint server.on(SERVER_PATH, HTTP_GET, [](AsyncWebServerRequest* request) { @@ -309,8 +407,10 @@ void loop() { static unsigned long lastUpdateTime = 0; static unsigned long lastWiFiCheck = 0; static unsigned long lastBitmapCheck = 0; + static unsigned long lastPlugCheck = 0; unsigned long currentTime = millis(); + // Update OLED display if (currentTime - lastUpdateTime >= SCREEN_UPDATE_TIME) { lastUpdateTime = currentTime; @@ -325,12 +425,63 @@ void loop() { Serial.printf("Temperature: %.2f F\n", temperatureF); Serial.printf("Humidity : %.2f %%\n", humidity); Serial.printf("CO2 : %.2f ppm\n", co2); - Serial.println("Updating display..."); + Serial.println(F("Updating display...")); // Update OLED display with latest sensor data updateOLED(co2, temperature, temperatureF, humidity); } +/* + * Temperature Too High -> Turn on the exhaust fan, consider additional cooling (e.g., A/C). + * Temperature Too Low -> Turn off the exhaust fan, possibly add a heater. + * Humidity Too High -> Turn on the exhaust fan to vent humid air, or use a dehumidifier. + * Humidity Too Low -> Turn off the exhaust fan, reduce intake, and add a humidifier if needed. + * CO2 Too High -> Turn on the exhaust fan to vent excess CO2. + * CO2 Too Low -> Turn off the exhaust fan to retain CO2, or add a CO2 source. +*/ + + // Update Smart plugs + if (currentTime - lastPlugCheck >= SMARTPLUG_UPDATE_TIME) { + lastPlugCheck = currentTime; + + // Get sensor values + float co2 = readCO2(); + float temperature = readTemperature(); + float temperatureF = celsiusToFahrenheit(temperature); + float humidity = readHumidity(); + + // Temperature control logic + if (temperatureF > DESIRED_TEMP + TEMP_HYSTERESIS) { // Temp too high + Serial.println(F("Temperature too high! Turning on intake and exhaust fans...")); + setPlugState(exhaustPlug, true); // Turn on exhaust fan + setPlugState(intakePlug, true); // Turn on intake fan + } else if (temperatureF < DESIRED_TEMP - TEMP_HYSTERESIS) { // Temp too low + Serial.println(F("Temperature too low! Turning off intake and exhaust fans...")); + setPlugState(exhaustPlug, false); // Turn off exhaust fan + setPlugState(intakePlug, false); // Turn off intake fan + } else { + // If temperature is within the target range, turn off fans to save energy + setPlugState(exhaustPlug, false); // Turn off exhaust fan + setPlugState(intakePlug, false); // Turn off intake fan + } + +// // Humidity control logic +// if (humidity > DESIRED_HUMIDITY + HUMIDITY_HYSTERESIS) { // Humidity too high +// Serial.println(F("Humidity too high! Turning on exhaust fan...")); +// setPlugState(exhaustPlug, true); // Turn on exhaust fan +// } else if (humidity < DESIRED_HUMIDITY - HUMIDITY_HYSTERESIS) { // Humidity too low +// Serial.println(F("Humidity too low! Turning off intake and exhaust fans...")); +// setPlugState(intakePlug, false); // Turn off intake fan +// setPlugState(exhaustPlug, false); // Turn off exhaust fan +// // Optionally, activate a humidifier if available +// } else { +// // If humidity is within the target range, turn off fans to save energy +// setPlugState(exhaustPlug, false); // Turn off exhaust fan +// setPlugState(intakePlug, false); // Turn off intake fan +// } + //TODO: Add CO2 levels + } + // Periodically check Wi-Fi status if (currentTime - lastWiFiCheck >= WIFI_CHECK_INTERVAL) { lastWiFiCheck = currentTime; @@ -349,8 +500,8 @@ void loop() { } // Periodically show bitmap - if (INTERUPT_WITH_BITMAP) { - if (currentTime - lastBitmapCheck >= INTERUPT_BITMAP_TIME) { + if (INTERRUPT_WITH_BITMAP) { + if (currentTime - lastBitmapCheck >= INTERRUPT_BITMAP_TIME) { lastBitmapCheck = currentTime; showBitmap(); }