#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <WebServer.h>
#include <DHT.h>
#include <LiquidCrystal_I2C.h>
// Configuración WiFi
const char *ssid = "Wokwi-GUEST";
const char *password = "";
// Pines y sensores
#define PIN_BOMBA_AGUA 13 // Usaremos este pin de forma virtual (sin motor físico)
#define PIN_HUMEDAD 35
#define PIN_DHT 4
#define TIPO_DHT DHT22
// Umbrales de humedad del suelo
#define HUMEDAD_SECA 30
#define HUMEDAD_HUMEDA 70
// Intervalos de tiempo
#define INTERVALO_LECTURA 2000
#define INTERVALO_RIEGO 10000
#define INTERVALO_VERIFICACION 3000 // Reducido a 3 segundos para la simulación
#define INTERVALO_NUBE 15000 // Enviar a la nube cada 15 seg
// Pantalla LCD
LiquidCrystal_I2C lcd(0x27, 16, 4);
DHT dht(PIN_DHT, TIPO_DHT);
// Variables de control y estado
unsigned long ultimaLectura = 0;
unsigned long ultimoRiego = 0;
bool riegoActivo = false;
String ipAddress = "No Conectado";
// Datos de sensores (Globales para ser usados entre funciones)
float temperaturaVal = 0;
float humedadAmbienteVal = 0;
float humedadSueloVal = 0;
// Servidor web
WebServer server(80);
// --- PASO 3: FUNCIONES MODULARES ---
void initWiFi() {
WiFi.begin(ssid, password);
Serial.println("\n[WiFi] Conectando...");
unsigned long inicio = millis();
while (WiFi.status() != WL_CONNECTED && millis() - inicio < 10000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
ipAddress = WiFi.localIP().toString();
Serial.println("\n[WiFi] Conectado. IP: " + ipAddress);
} else {
Serial.println("\n[WiFi] Error de conexion.");
}
}
void readSensors() {
// Lectura DHT22
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) temperaturaVal = t;
if (!isnan(h)) humedadAmbienteVal = h;
// Lectura Humedad Suelo (Promediada)
int suma = 0;
for (int i = 0; i < 5; i++) {
suma += analogReadMilliVolts(PIN_HUMEDAD);
delay(10);
}
int valorBruto = suma / 5;
float hSuelo = map(valorBruto, 3300, 0, 0, 100);
humedadSueloVal = constrain(hSuelo, 0, 100);
}
void controlActuators() {
// Lógica de riego automático
if (humedadSueloVal <= HUMEDAD_SECA && !riegoActivo) {
iniciarRiego();
} else if (humedadSueloVal >= HUMEDAD_HUMEDA && riegoActivo) {
detenerRiego();
}
// Seguridad: Apagar bomba si pasa el tiempo máximo de riego
if (riegoActivo && (millis() - ultimoRiego >= INTERVALO_RIEGO)) {
detenerRiego();
}
}
void sendData() {
// 1. Mostrar en LCD
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temp: " + String(temperaturaVal, 1) + "C");
lcd.setCursor(0, 1);
lcd.print("Hum.A: " + String(humedadAmbienteVal, 1) + "%");
lcd.setCursor(0, 2);
lcd.print("Hum.S: " + String(humedadSueloVal, 1) + "%");
lcd.setCursor(0, 3);
lcd.print("Bomba: " + String(riegoActivo ? "ENCENDIDA" : "APAGADA"));
// 2. Formato JSON (Requisito Paso 4)
String json = "{";
json += "\"temperatura\":" + String(temperaturaVal, 1) + ",";
json += "\"humedad_ambiente\":" + String(humedadAmbienteVal, 1) + ",";
json += "\"humedad_suelo\":" + String(humedadSueloVal, 1) + ",";
json += "\"bomba\":" + String(riegoActivo ? "true" : "false");
json += "}";
// Imprimir localmente
Serial.println(json);
// 3. Enviar a Railway (Nube Global)
static unsigned long ultimaNube = 0;
if (millis() - ultimaNube >= INTERVALO_NUBE) {
ultimaNube = millis();
if (WiFi.status() == WL_CONNECTED) {
WiFiClientSecure *client = new WiFiClientSecure;
if(client) {
client->setInsecure(); // No validar el certificado SSL de Railway para evitar problemas
HTTPClient http;
http.begin(*client, "https://railway-riego-production.up.railway.app/api/telemetria");
http.addHeader("Content-Type", "application/json");
int httpResponseCode = http.POST(json);
if (httpResponseCode > 0) {
Serial.println("[Nube] Datos enviados a Railway OK: " + String(httpResponseCode));
} else {
Serial.println("[Nube] Error enviando: " + String(httpResponseCode) + " -> " + http.errorToString(httpResponseCode));
}
http.end();
delete client;
}
}
}
}
void handleCommands() {
server.handleClient();
// Simulación de comando por Serial (Opcional del Paso 4)
if (Serial.available() > 0) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd == "REGAR_ON") iniciarRiego();
if (cmd == "REGAR_OFF") detenerRiego();
}
}
// --- FUNCIONES DE SOPORTE ---
void configurarServidorWeb() {
server.on("/", []() {
String html = "<html><head><title>Simulacion IoT</title>";
html += "<meta http-equiv='refresh' content='5'></head><body>";
html += "<h1>Monitoreo Hidroponico</h1>";
html += "<p>Estado Bomba: " + String(riegoActivo ? "ENCENDIDA" : "APAGADA") + "</p>";
html += "<p>Temperatura: " + String(temperaturaVal) + "C</p>";
html += "<p>Hum. Suelo: " + String(humedadSueloVal) + "%</p>";
html += "<button onclick=\"location.href='/toggle'\">Alternar Bomba</button>";
html += "</body></html>";
server.send(200, "text/html", html);
});
server.on("/toggle", []() {
if (riegoActivo) detenerRiego();
else iniciarRiego();
server.sendHeader("Location", "/", true);
server.send(303);
});
// API JSON para el Dashboard Local
server.on("/api/data", []() {
String json = "{";
json += "\"temperatura\":" + String(temperaturaVal) + ",";
json += "\"humedad_ambiente\":" + String(humedadAmbienteVal) + ",";
json += "\"humedad_suelo\":" + String(humedadSueloVal) + ",";
json += "\"bomba\":" + String(riegoActivo ? "true" : "false");
json += "}";
server.sendHeader("Access-Control-Allow-Origin", "*");
server.send(200, "application/json", json);
});
server.on("/api/toggle", []() {
if (riegoActivo) detenerRiego();
else iniciarRiego();
server.sendHeader("Access-Control-Allow-Origin", "*");
server.send(200, "application/json", "{\"status\":\"ok\",\"bomba\":" + String(riegoActivo ? "true" : "false") + "}");
});
server.begin();
Serial.println("[Servidor] Iniciado.");
}
void iniciarRiego() {
digitalWrite(PIN_BOMBA_AGUA, LOW);
riegoActivo = true;
ultimoRiego = millis();
Serial.println("[Actuador] Bomba ACTIVADA");
}
void detenerRiego() {
digitalWrite(PIN_BOMBA_AGUA, HIGH);
riegoActivo = false;
Serial.println("[Actuador] Bomba DESACTIVADA");
}
void checkWiFi() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] Reconectando...");
initWiFi();
}
}
// --- SETUP Y LOOP PRINCIPAL ---
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Cargando Sistema...");
dht.begin();
pinMode(PIN_BOMBA_AGUA, OUTPUT);
digitalWrite(PIN_BOMBA_AGUA, HIGH); // Apagado por defecto
initWiFi();
configurarServidorWeb();
Serial.println("Sistema Modular Listo.");
}
void loop() {
checkWiFi();
handleCommands();
unsigned long tiempoActual = millis();
// Ejecutar tareas cada intervalo definido
if (tiempoActual - ultimaLectura >= INTERVALO_LECTURA) {
ultimaLectura = tiempoActual;
readSensors(); // Responsabilidad 1: Leer
controlActuators(); // Responsabilidad 2: Decidir
sendData(); // Responsabilidad 3: Informar
}
}