/**
* @file sketch.ino
* @brief Firmware for HarvestPod Advance (FreshPlot)
* @details IoT device for automated crop control using ModestIoT architecture.
* @author Fabiola Saldaña
* @date 2026-07-15
*/
#include <Wire.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include "RTClib.h"
#include <ModestIoT.h>
// ==========================================
// Constants (Avoiding Magic Numbers/Strings)
// ==========================================
const char* COMPANY_NAME = "FreshPlot";
const char* DEVELOPER_NAME = "Fabiola Saldaña";
const char* STUDENT_CODE_SUFFIX = "3773"; // TODO: Cambiar por tus 4 digitos
const String MAC_ADDRESS = String(STUDENT_CODE_SUFFIX) + ":56:78:9A:BC:DE";
// Pin Definitions
const uint8_t PIN_DHT = 15;
const uint8_t PIN_LDR = 34;
const uint8_t PIN_BTN_UP = 12;
const uint8_t PIN_BTN_DOWN = 14;
const uint8_t PIN_LED_GREEN = 2;
const uint8_t PIN_LED_RED = 4;
const uint8_t PIN_RELAY = 5;
const uint8_t PIN_SERVO = 18;
// Thresholds & Configurations
const uint32_t SERIAL_BAUD_RATE = 115200;
const uint16_t LDR_NIGHT_THRESHOLD = 200;
const float TEMP_VENT_THRESHOLD = 28.0;
const float HUMIDITY_VENT_THRESHOLD = 85.0;
const int HUMIDITY_TARGET_MIN = 40;
const int HUMIDITY_TARGET_MAX = 80;
const int HUMIDITY_STEP = 5;
const uint32_t REPORT_INTERVAL_MS = 5000;
const int SERVO_CLOSED_POS = 0;
const int SERVO_OPEN_POS = 90;
const char* MODE_DAY = "DAY_MODE";
const char* MODE_NIGHT = "NIGHT_MODE";
const char* STATE_RIEGO = "RIEGO";
const char* STATE_OPTIMAL = "OPTIMAL";
const char* STATE_VENTILANDO = "VENTILANDO";
// ==========================================
// ModestIoT Architecture Classes
// ==========================================
/**
* @class EnvironmentSensor
* @brief ModestIoT Sensor implementation for DHT22 (Temp/Humidity).
*/
class EnvironmentSensor : public Sensor {
private:
DHT dht;
float currentTemp;
float currentHum;
public:
EnvironmentSensor(uint8_t pin) : Sensor(pin), dht(pin, DHT22), currentTemp(0), currentHum(0) {}
void init() { dht.begin(); }
void readData() {
currentTemp = dht.readTemperature();
currentHum = dht.readHumidity();
}
float getTemperature() { return currentTemp; }
float getHumidity() { return currentHum; }
void processEvent(Event& event) override {}
};
/**
* @class LightSensor
* @brief ModestIoT Sensor for Photoresistor.
*/
class LightSensor : public Sensor {
private:
uint8_t pin;
public:
LightSensor(uint8_t sensorPin) : Sensor(sensorPin), pin(sensorPin) {}
void init() { pinMode(pin, INPUT); }
int getLightLevel() {
int rawValue = analogRead(pin);
// 1. Escala el valor del ESP32 (0-4095) a lo solicitado (0-1023).
// 2. Invierte la escala (1023 a 0) para que oscuridad = valor bajo (< 200).
return map(rawValue, 0, 4095, 1023, 0);
}
void processEvent(Event& event) override {}
};
/**
* @class UserButton
* @brief ModestIoT Sensor for tactile switches.
*/
class UserButton : public Sensor {
private:
uint8_t pin;
bool lastState;
public:
UserButton(uint8_t sensorPin) : Sensor(sensorPin), pin(sensorPin), lastState(HIGH) {}
void init() { pinMode(pin, INPUT_PULLUP); }
bool isPressed() {
bool currentState = digitalRead(pin);
bool pressed = (currentState == LOW && lastState == HIGH);
lastState = currentState;
return pressed;
}
void processEvent(Event& event) override {}
};
/**
* @class VentilationActuator
* @brief ModestIoT Actuator for the Servo Motor.
*/
class VentilationActuator : public Actuator {
private:
Servo servoMotor;
uint8_t pin;
public:
VentilationActuator(uint8_t actuatorPin) : Actuator(actuatorPin), pin(actuatorPin) {}
void init() {
servoMotor.attach(pin);
close();
}
void open() { servoMotor.write(SERVO_OPEN_POS); }
void close() { servoMotor.write(SERVO_CLOSED_POS); }
void executeCommand(Command command) override {}
};
/**
* @class IrrigationActuator
* @brief ModestIoT Actuator for Relay.
*/
class IrrigationActuator : public Actuator {
private:
uint8_t pin;
public:
IrrigationActuator(uint8_t actuatorPin) : Actuator(actuatorPin), pin(actuatorPin) {}
void init() {
pinMode(pin, OUTPUT);
stop();
}
void start() { digitalWrite(pin, HIGH); }
void stop() { digitalWrite(pin, LOW); }
void executeCommand(Command command) override {}
};
// ==========================================
// Main Device Integration
// ==========================================
/**
* @class HarvestPodDevice
* @brief Main system class representing the IoT device.
*/
class HarvestPodDevice : public Device {
private:
EnvironmentSensor envSensor;
LightSensor lightSensor;
UserButton btnUp;
UserButton btnDown;
VentilationActuator ventilation;
IrrigationActuator irrigation;
LiquidCrystal_I2C lcd;
RTC_DS1307 rtc;
int targetHumidity;
String currentIrrigationState;
String currentMode;
uint32_t lastReportTime;
void updateTargetHumidity() {
if (btnUp.isPressed() && targetHumidity < HUMIDITY_TARGET_MAX) {
targetHumidity += HUMIDITY_STEP;
}
if (btnDown.isPressed() && targetHumidity > HUMIDITY_TARGET_MIN) {
targetHumidity -= HUMIDITY_STEP;
}
}
void handleDayMode(float temp, float hum) {
currentMode = MODE_DAY;
lcd.backlight();
// LCD Display - Primera línea
lcd.setCursor(0, 0);
lcd.print(String(temp, 1) + "C " + String(hum, 1) + "% ");
// Humidity Rules (Misma lógica que ya tenías)
if (hum < (targetHumidity - HUMIDITY_STEP)) {
irrigation.start();
currentIrrigationState = STATE_RIEGO;
digitalWrite(PIN_LED_GREEN, LOW);
digitalWrite(PIN_LED_RED, HIGH);
} else if (hum >= (targetHumidity - HUMIDITY_STEP) && hum <= (targetHumidity + HUMIDITY_STEP)) {
irrigation.stop();
currentIrrigationState = STATE_OPTIMAL;
digitalWrite(PIN_LED_GREEN, HIGH);
digitalWrite(PIN_LED_RED, LOW);
} else {
irrigation.stop();
currentIrrigationState = STATE_VENTILANDO;
digitalWrite(PIN_LED_GREEN, LOW);
digitalWrite(PIN_LED_RED, LOW);
}
lcd.setCursor(0, 1);
// Imprimimos el Target y el Estado actual. Agregamos espacios al final para limpiar caracteres viejos.
lcd.print("T:" + String(targetHumidity) + "% " + currentIrrigationState + " ");
// Ventilation Rules (Misma lógica que ya tenías)
if (temp > TEMP_VENT_THRESHOLD || hum > HUMIDITY_VENT_THRESHOLD) {
ventilation.open();
} else {
ventilation.close();
}
}
void handleNightMode() {
currentMode = MODE_NIGHT;
irrigation.stop();
ventilation.close();
lcd.noBacklight();
lcd.clear();
digitalWrite(PIN_LED_GREEN, LOW);
digitalWrite(PIN_LED_RED, LOW);
currentIrrigationState = "NONE";
}
void reportStatus(float temp, float hum, int light) {
if (millis() - lastReportTime >= REPORT_INTERVAL_MS) {
lastReportTime = millis();
StaticJsonDocument<256> doc;
doc["deviceMacAddress"] = MAC_ADDRESS;
doc["operationMode"] = currentMode;
doc["currentHumidity"] = hum;
doc["currentTemperature"] = temp;
doc["lightLevel"] = light;
doc["irrigationState"] = currentIrrigationState;
DateTime now = rtc.now();
char timeBuffer[25];
snprintf(timeBuffer, sizeof(timeBuffer), "%04d-%02d-%02dT%02d:%02d:%02d",
now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
doc["createdAt"] = timeBuffer;
String jsonOutput;
serializeJson(doc, jsonOutput);
Serial.println(jsonOutput);
}
}
public:
HarvestPodDevice() :
Device(100, 0),
envSensor(PIN_DHT), lightSensor(PIN_LDR),
btnUp(PIN_BTN_UP), btnDown(PIN_BTN_DOWN),
ventilation(PIN_SERVO), irrigation(PIN_RELAY),
lcd(0x27, 16, 2), targetHumidity(60),
currentMode(MODE_DAY), lastReportTime(0) {}
void init() {
Serial.println("===================================");
Serial.printf("Company: %s\n", COMPANY_NAME);
Serial.printf("Developer: %s\n", DEVELOPER_NAME);
Serial.println("===================================");
envSensor.init();
lightSensor.init();
btnUp.init();
btnDown.init();
ventilation.init();
irrigation.init();
pinMode(PIN_LED_GREEN, OUTPUT);
pinMode(PIN_LED_RED, OUTPUT);
lcd.init();
lcd.backlight();
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
} else {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
Serial.println("HarvestPod Advance - READY.");
}
void run() {
envSensor.readData();
float currentTemp = envSensor.getTemperature();
float currentHum = envSensor.getHumidity();
int lightLevel = lightSensor.getLightLevel();
updateTargetHumidity();
if (lightLevel < LDR_NIGHT_THRESHOLD) {
handleNightMode();
} else {
handleDayMode(currentTemp, currentHum);
}
reportStatus(currentTemp, currentHum, lightLevel);
}
void on(Event event) override {
// Requerido por la arquitectura ModestIoT (EventHandler)
}
};
// ==========================================
// Application Entry Points
// ==========================================
HarvestPodDevice harvestPod;
void setup() {
Serial.begin(SERIAL_BAUD_RATE);
while (!Serial) { delay(10); }
harvestPod.init();
}
void loop() {
harvestPod.run();
delay(100);
}