7. Anexos - Bedoya1905/iot_challenge2 GitHub Wiki

7.1 Lista de materiales

Material (simulación Wokwi) Material real (prototipo físico)
ESP32 DevKit (board-esp32-devkit-c-v4) ESP32 DevKit de 38 pines
HC-SR04 HC-SR04 (mismo)
LCD1602 con módulo I2C LCD1602 con módulo I2C (mismo)
DHT22 DHT22 (mismo)
Fotorresistor / LDR Fotorresistor / LDR (mismo)
Potenciómetro BMP280 (sensor de presión atmosférica)
LED verde LED verde (mismo)
LED amarillo LED amarillo (mismo)
LED rojo LED rojo (mismo)
Resistencia 220 Ω × 3 Resistencia 220 Ω × 3 (mismas)
— Resistencia 10 kΩ (pull-up del DHT22)
— Resistencia 10 kΩ (divisor del LDR)
Buzzer Buzzer (mismo)
Pulsador Sensor de caudal ZJ-S201
Protoboard × 2 Protoboard × 1
— Carcasa impresa en PLA, 210 × 80 × 130 mm
— Fuente de alimentación externa 5 V por USB

7.2 Consumo eléctrico estimado

Componente Voltaje Corriente típica
ESP32 DevKit 5V (entrada) ~150 mA
HC-SR04 5V ~15 mA
LCD1602 + módulo I2C (con backlight) 5V ~30 mA
Buzzer (sonando) 3.3V ~30 mA
DHT22 3.3V ~1.5 mA
LDR + Resistencia de 10kΩ 3.3V ~0.3 mA
BMP280 3.3V ~0.3 mA
1 LED encendido (solo uno a la vez) 3.3V ~8 mA
Sensor de caudal ZJ-S201 5V ~15 mA

Estos valores son estimaciones a partir de las hojas de datos de cada componente y no incluyen el consumo de la radio Wi-Fi, que en el ESP32 presenta picos durante la transmisión. El consumo real del conjunto no se midió, se documenta como pendiente en la sección 4.3.

7.3 Montaje Físico

  • Carcasa: Estructura impresa en PLA de 210 × 80 × 130 mm que aloja la protoboard y el ESP32.
  • Sensor ultrasónico (HC-SR04): Montado en la cara orientada hacia la superficie del agua, con línea de visión despejada.
  • LCD1602 y DHT22: Ubicados en la misma zona del sensor ultrasónico, para una medición de las variables reales del entorno.
  • Fotorresistor: Ubicado en la parte superior, en la tapa de la carcasa, para una medición directa.
  • Sensor de caudal (ZJ-S201): instalado en línea entre dos tramos de manguera, por fuera de la carcasa.
  • Alimentación: fuente externa de 5 V por USB.
image image image

7.4 Código fuente

El firmware del prototipo está documentado en su totalidad: pines, librerías requeridas, parámetros de calibración, estructura de concurrencia y lógica de fusión.

/*
  Challenge#2 - Internet de las Cosas
  Sistema IoT de monitoreo y alerta de escasez de agua - Sabana Centro (Cundinamarca)
  Universidad de La Sabana, 2026-2
 
  * VERSIÓN PARA EL PROTOTIPO FÍSICO - CHALLENGE #2 *
 
  Cambios respecto al Challenge #1:
  - La medición corre en una tarea FreeRTOS anclada al Core 0, separada del
    hilo principal (loop(), que en ESP32 corre en Core 1). El conteo de
    pulsos de caudal sigue en una ISR.
  - Servidor web embebido en el propio ESP32, conectado a la WLAN local.
    Sirve un tablero de control con valor actual, histórico reciente,
    notificación de eventualidades y botón para silenciar la alarma física.
  - Acceso al tablero protegido con autenticación HTTP Basic.
  - El estado compartido entre la tarea de medición y el servidor web se
    protege con un mutex de FreeRTOS.
  - Sin MQTT y sin dependencias externas: el HTML/CSS/JS del tablero va
    embebido en la memoria del ESP32, por lo que funciona en una WLAN
    sin salida a internet.
  - La evaporacion potencial se calcula con la ecuacion de Priestley-Taylor
    (1972), en mm/dia, en reemplazo del indice heuristico del Challenge #1.
  - La logica de fusion incorpora histeresis: para BAJAR de estado se exige
    superar el umbral por un margen, de modo que la alerta no oscila cuando
    una lectura queda justo sobre el limite.
  - Se agrega un cuarto estado, FALLA, para cuando un sensor no entrega
    lectura valida: el sistema deja de reportar "NORMAL" estando ciego.
  - Se integra el BMP280 para medir la presion atmosferica real, usada en la
    constante psicrometrica de Priestley-Taylor. Si el sensor no responde,
    se usa la presion estimada por altitud (FAO-56) y se indica como estimada.
 
  Componentes:
  - HC-SR04       : nivel de agua
  - ZJ-S201       : caudal por pulsos (ISR)
  - DHT22         : temperatura y humedad relativa
  - BMP280        : presión atmosférica (I2C, comparte bus con la LCD)
  - LDR           : proxy de radiación solar (divisor de voltaje con 10kΩ)
  - LCD1602 I2C   : visualización in situ, rota entre 3 pantallas
  - LEDs verde/amarillo/rojo + buzzer : alarma física in situ
 
  Librerías requeridas:
  - "DHT sensor library for ESPx" (beegee-tokyo)
  - "LiquidCrystal I2C" (Frank de Brabander / John Rickman)
  - "Adafruit BMP280 Library" (Adafruit) - instala también Adafruit Unified
    Sensor y Adafruit BusIO como dependencias
  (WiFi.h y WebServer.h vienen incluidas en el core de ESP32 para Arduino)
*/
 
#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Adafruit_BMP280.h>
#include "DHTesp.h"
 
// ---------- Credenciales de la WLAN (proporcionada por las autoridades locales) ----------
// CAMBIAR antes de desplegar. No subir credenciales reales a un repositorio público.
const char* WIFI_SSID     = " ";
const char* WIFI_PASSWORD = " ";
 
// ---------- Credenciales de acceso al tablero de control ----------
// Restringe el tablero a dispositivos autorizados dentro de la WLAN.
const char* TABLERO_USUARIO = " ";
const char* TABLERO_CLAVE   = " ";
 
// ---------- Pines ----------
#define TRIG_PIN      5
#define ECHO_PIN      18
#define DHT_PIN       15
#define LDR_PIN       35
#define FLOW_PIN      32
#define LED_VERDE     25
#define LED_AMARILLO  26
#define LED_ROJO      27
#define BUZZER_PIN    33
 
// Bus I2C compartido: LCD (0x27) y BMP280 (0x76 o 0x77)
#define I2C_SDA_PIN   21
#define I2C_SCL_PIN   22
 
// ---------- Parámetros físicos del sistema ----------
const float DISTANCIA_VACIO_CM = 12.0;  // lectura con el tanque vacío  -> 0%
const float DISTANCIA_LLENO_CM = 3.8;   // lectura con el nivel máximo  -> 100%
 
const float NIVEL_ALERTA_PCT  = 30.0;
const float NIVEL_CRITICO_PCT = 15.0;
 
//const float FACTOR_PULSOS_POR_HZ = 7.5;
const float FACTOR_PULSOS_POR_HZ = 3.88;
const float CAUDAL_ANOMALO_LMIN  = 15.0;
 
// ---------- Márgenes de histéresis ----------
// Solo se aplican para BAJAR de estado. Entrar en alerta usa el umbral
// nominal; salir de ella exige superarlo por este margen.
const float MARGEN_NIVEL_PCT   = 3.0;   // puntos porcentuales de nivel
const float MARGEN_EVAP_MM_DIA = 0.5;   // mm/día
const float MARGEN_CAUDAL_LMIN = 2.0;   // L/min
 
// ---------- Parámetros de la ecuación de Priestley-Taylor ----------
// Altitud del sitio de despliegue (embalse del Neusa, Cogua/Tausa).
// Solo se usa como respaldo: si el BMP280 no responde, la presión se
// estima a partir de la altitud con la ecuación de FAO-56.
const float ALTITUD_SITIO_M = 3000.0;
 
// Albedo de la superficie monitoreada. 0.06 corresponde a agua abierta;
// NO usar el 0.23 de FAO-56, que es para pasto de referencia.
const float ALBEDO_AGUA = 0.06;
 
// Coeficiente empírico de Priestley-Taylor. 1.26 es el valor original de
// Priestley y Taylor (1972) para superficies saturadas. La literatura
// reporta valores más altos (hasta ~1.74) en climas áridos y semiáridos,
// donde 1.26 subestima la evaporación real. Parámetro a justificar o
// calibrar según las condiciones del sitio.
const float ALFA_PT = 1.26;
 
// Umbral de demanda evaporativa alta usado por la lógica de fusión (mm/día).
// Es un parámetro de diseño, del mismo tipo que los umbrales de nivel:
// debe calibrarse contra datos del sitio.
const float EVAPORACION_ALTA_MM_DIA = 5.0;
 
// ---------- Calibración de dos puntos del LDR -> radiación solar (W/m²) ----------
// AJUSTAR con mediciones propias: anotar la lectura de analogRead(LDR_PIN)
// en exterior con sombra/nublado y en exterior con sol directo al mediodía.
// Los valores de W/m² son de referencia estándar para esas condiciones,
// no mediciones hechas con un piranómetro calibrado.
const float ADC_SOMBRA = 170.0;
const float RS_SOMBRA  = 150.0;
const float ADC_SOL    = 3357.0;
const float RS_SOL     = 900.0;
 
// ---------- Parámetros de muestreo e histórico ----------
const uint32_t INTERVALO_LECTURA_MS   = 2000;  // periodo de la tarea de medición
const uint8_t  MUESTRAS_POR_HISTORICO = 5;     // 1 punto de histórico cada 5 lecturas (10 s)
const uint8_t  HISTORICO_MAX          = 60;    // 60 puntos x 10 s = 10 minutos
 
DHTesp dht;
LiquidCrystal_I2C lcd(0x27, 16, 2);
Adafruit_BMP280 bmp;
WebServer server(80);
 
bool bmpOk = false;   // true si el BMP280 respondió en el arranque
 
// FALLA: al menos un sensor no entrega lectura válida. No es un nivel de
// riesgo hídrico, sino un estado de diagnóstico del propio dispositivo.
enum NivelAlerta { NORMAL = 0, ADVERTENCIA = 1, CRITICO = 2, FALLA = 3 };
 
// ---------- Estado compartido entre la tarea de medición y el servidor web ----------
struct Lectura {
  float    nivelPct;
  float    tempC;
  float    humedadPct;
  float    presionKPa;
  bool     presionMedida;  // true: BMP280; false: estimada por altitud
  float    radiacionWm2;
  float    evaporacionMMDia;
  float    caudalLmin;
  float    volumenAcumuladoL;
  uint8_t  alerta;
  uint32_t tsSegundos;   // segundos desde el arranque del dispositivo
};
 
Lectura actual = {};   // todo en cero; alerta = 0 = NORMAL
 
Lectura historico[HISTORICO_MAX];
uint8_t historicoCantidad = 0;   // cuántas posiciones están llenas
uint8_t historicoInicio   = 0;   // posición más antigua (buffer circular)
 
bool alarmaSilenciada = false;   // la autoridad silenció la alarma desde el tablero
 
SemaphoreHandle_t mutexDatos;
 
// ---------- Conteo de pulsos de caudal (ISR) ----------
portMUX_TYPE muxCaudal = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t pulsosCaudal = 0;
 
void IRAM_ATTR isrPulsoCaudal() {
  portENTER_CRITICAL_ISR(&muxCaudal);
  pulsosCaudal++;
  portEXIT_CRITICAL_ISR(&muxCaudal);
}
 
// ============================================================================
//  PÁGINA DEL TABLERO DE CONTROL (embebida: no requiere internet)
// ============================================================================
const char PAGINA_HTML[] PROGMEM = R"HTMLPAGE(
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Monitoreo Hidrico - Embalse del Neusa</title>
<style>
  * { box-sizing: border-box; }
  body { margin:0; padding:16px; font-family: system-ui, Arial, sans-serif;
         background:#101418; color:#e6edf3; }
  h1 { font-size:1.15rem; margin:0 0 4px 0; }
  .sub { font-size:0.8rem; color:#8b949e; margin-bottom:16px; }
  #banner { padding:14px; border-radius:8px; font-weight:bold; text-align:center;
            margin-bottom:16px; font-size:1.05rem; }
  .normal      { background:#1a4d2e; color:#7ee2a8; }
  .advertencia { background:#5c4813; color:#ffd866; }
  .critico     { background:#5c1a1a; color:#ff8a8a; }
  .falla       { background:#3d2a5c; color:#c9a3ff; }
  .sinconexion { background:#30363d; color:#8b949e; }
  .grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:10px; }
  .card { background:#1a2027; border-radius:8px; padding:12px; }
  .etq { font-size:0.72rem; color:#8b949e; text-transform:uppercase; letter-spacing:0.5px; }
  .val { font-size:1.5rem; font-weight:bold; margin-top:4px; }
  .uni { font-size:0.85rem; color:#8b949e; font-weight:normal; }
  button { width:100%; margin-top:16px; padding:14px; font-size:0.95rem; font-weight:bold;
           border:none; border-radius:8px; background:#2d5f8a; color:#fff; cursor:pointer; }
  button:disabled { background:#30363d; color:#6e7681; cursor:default; }
  h2 { font-size:0.95rem; margin:24px 0 8px 0; }
  table { width:100%; border-collapse:collapse; font-size:0.8rem; }
  th, td { padding:6px 4px; text-align:right; border-bottom:1px solid #262c33; }
  th:first-child, td:first-child { text-align:left; }
  th { color:#8b949e; font-weight:normal; }
</style>
</head>
<body>
  <h1>Monitoreo hidrico - Embalse del Neusa</h1>
  <div class="sub">Tablero de control local &middot; Sabana Centro (Cundinamarca)</div>
 
  <div id="banner" class="sinconexion">Conectando con el dispositivo...</div>
 
  <div class="grid">
    <div class="card"><div class="etq">Nivel</div><div class="val"><span id="nivel">--</span><span class="uni"> %</span></div></div>
    <div class="card"><div class="etq">Caudal</div><div class="val"><span id="caudal">--</span><span class="uni"> L/min</span></div></div>
    <div class="card"><div class="etq">Volumen acumulado</div><div class="val"><span id="volumen">--</span><span class="uni"> L</span></div></div>
    <div class="card"><div class="etq">Temperatura</div><div class="val"><span id="temp">--</span><span class="uni"> &deg;C</span></div></div>
    <div class="card"><div class="etq">Humedad relativa</div><div class="val"><span id="humedad">--</span><span class="uni"> %</span></div></div>
    <div class="card"><div class="etq" id="etqPresion">Presion</div><div class="val"><span id="presion">--</span><span class="uni"> kPa</span></div></div>
    <div class="card"><div class="etq">Radiacion solar</div><div class="val"><span id="radiacion">--</span><span class="uni"> W/m2</span></div></div>
    <div class="card"><div class="etq">Evaporacion (P-T)</div><div class="val"><span id="evap">--</span><span class="uni"> mm/dia</span></div></div>
  </div>
 
  <button id="btnSilenciar" onclick="silenciar()">Silenciar alarma fisica</button>
 
  <h2>Historico reciente</h2>
  <table>
    <thead><tr><th>Hace</th><th>Nivel %</th><th>Caudal</th><th>Temp</th><th>Hum %</th><th>Estado</th></tr></thead>
    <tbody id="tbody"><tr><td colspan="6">Sin datos aun</td></tr></tbody>
  </table>
 
<script>
var ESTADOS = ["NORMAL", "ADVERTENCIA", "CRITICO", "FALLA"];
var CLASES  = ["normal", "advertencia", "critico", "falla"];
var ultimoEstado = -1;
var tsActual = 0;
 
function f(x, d) { return (x === null || x === undefined) ? "--" : x.toFixed(d); }
 
function hace(ts) {
  var s = tsActual - ts;
  if (s < 60) { return s + "s"; }
  return Math.floor(s / 60) + "m " + (s % 60) + "s";
}
 
function pintar(d) {
  tsActual = d.ts;
  document.getElementById("nivel").textContent     = f(d.nivel, 0);
  document.getElementById("caudal").textContent    = f(d.caudal, 1);
  document.getElementById("volumen").textContent   = f(d.volumen, 1);
  document.getElementById("temp").textContent      = f(d.temp, 1);
  document.getElementById("humedad").textContent   = f(d.humedad, 0);
  document.getElementById("presion").textContent   = f(d.presion, 1);
  document.getElementById("etqPresion").textContent =
    d.presionMedida ? "Presion" : "Presion (estimada)";
  document.getElementById("radiacion").textContent = f(d.radiacion, 0);
  document.getElementById("evap").textContent      = f(d.evap, 2);
 
  var b = document.getElementById("banner");
  b.className = CLASES[d.alerta];
  var txt = "Estado: " + ESTADOS[d.alerta];
  if (d.alerta === 3) {
    txt = "FALLA DE SENSOR - revisar el dispositivo en sitio";
  } else if (d.alerta > 0 && d.alerta !== ultimoEstado) {
    txt = "NUEVA ALERTA - " + ESTADOS[d.alerta];
  }
  if (d.silenciada && d.alerta > 0) { txt += " (alarma silenciada)"; }
  b.textContent = txt;
  ultimoEstado = d.alerta;
 
  var btn = document.getElementById("btnSilenciar");
  btn.disabled = (d.alerta === 0 || d.silenciada);
}
 
function pedirDatos() {
  fetch("/api/datos").then(function (r) { return r.json(); }).then(pintar)
    .catch(function () {
      var b = document.getElementById("banner");
      b.className = "sinconexion";
      b.textContent = "Sin conexion con el dispositivo";
    });
}
 
function pedirHistorico() {
  fetch("/api/historico").then(function (r) { return r.json(); }).then(function (h) {
    var tb = document.getElementById("tbody");
    if (!h.length) { tb.innerHTML = "<tr><td colspan='6'>Sin datos aun</td></tr>"; return; }
    var filas = "";
    for (var i = h.length - 1; i >= 0; i--) {
      var p = h[i];
      filas += "<tr><td>" + hace(p.ts) + "</td><td>" + f(p.nivel, 0) + "</td><td>" +
               f(p.caudal, 1) + "</td><td>" + f(p.temp, 1) + "</td><td>" +
               f(p.humedad, 0) + "</td><td>" + ESTADOS[p.alerta] + "</td></tr>";
    }
    tb.innerHTML = filas;
  }).catch(function () {});
}
 
function silenciar() {
  fetch("/api/silenciar", { method: "POST" }).then(pedirDatos).catch(function () {});
}
 
pedirDatos();
pedirHistorico();
setInterval(pedirDatos, 2000);
setInterval(pedirHistorico, 10000);
</script>
</body>
</html>
)HTMLPAGE";
 
// ============================================================================
//  LECTURA DE SENSORES
// ============================================================================
float medirDistanciaCM() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
 
  long duracion = pulseIn(ECHO_PIN, HIGH, 30000); // timeout 30ms (~5 m)
  if (duracion == 0) return -1;
  return duracion * 0.0343 / 2.0;
}
 
float calcularNivelPct(float distanciaCM) {
  if (distanciaCM < 0) return -1;
  float pct = (DISTANCIA_VACIO_CM - distanciaCM) /
              (DISTANCIA_VACIO_CM - DISTANCIA_LLENO_CM) * 100.0;
  return constrain(pct, 0.0, 100.0);
}
 
// Convierte los pulsos acumulados desde la última lectura a L/min e integra
// el volumen. El acceso al contador se protege con el mismo spinlock de la ISR.
float leerCaudalLmin(uint32_t intervaloMs, float &volumenAcumuladoL) {
  portENTER_CRITICAL(&muxCaudal);
  uint32_t pulsos = pulsosCaudal;
  pulsosCaudal = 0;
  portEXIT_CRITICAL(&muxCaudal);
 
  float intervaloSeg = intervaloMs / 1000.0;
  float frecuenciaHz = pulsos / intervaloSeg;
  float caudalLmin   = frecuenciaHz / FACTOR_PULSOS_POR_HZ;
 
  volumenAcumuladoL += caudalLmin * (intervaloSeg / 60.0);
  return caudalLmin;
}
 
// Convierte la lectura del ADC a radiación solar incidente (W/m²) mediante
// una interpolación lineal entre los dos puntos de calibración del LDR.
float calcularRadiacionWm2() {
  int lectura = analogRead(LDR_PIN); // 0-4095 en ESP32
  float pendiente = (RS_SOL - RS_SOMBRA) / (ADC_SOL - ADC_SOMBRA);
  float rs = RS_SOMBRA + pendiente * (lectura - ADC_SOMBRA);
  Serial.printf("LDR crudo: %d\n", lectura);  // TEMPORAL: calibración del LDR
  return constrain(rs, 0.0, 1200.0);
}
 
// Presión atmosférica estimada a partir de la altitud (FAO-56, ec. 7).
// Respaldo para cuando el BMP280 no responde.
float presionEstimadaKPa() {
  return 101.3 * pow((293.0 - 0.0065 * ALTITUD_SITIO_M) / 293.0, 5.26);
}
 
// Presión atmosférica en kPa. Usa el BMP280 si responde con un valor
// físicamente plausible; si no, cae a la estimación por altitud.
// 'medida' indica cuál de las dos se usó.
float leerPresionKPa(bool &medida) {
  if (bmpOk) {
    float pa = bmp.readPressure();             // Pa
    if (!isnan(pa) && pa > 30000.0 && pa < 110000.0) {
      medida = true;
      return pa / 1000.0;                      // Pa -> kPa
    }
  }
  medida = false;
  return presionEstimadaKPa();
}
 
// ---------- Evaporación potencial: ecuación de Priestley-Taylor (1972) ----------
//   ET = alfa * [ delta / (delta + gamma) ] * (Rn - G) / lambda      [mm/día]
// Aproximaciones documentadas de esta implementación:
//   - Rn se estima como radiación neta de onda corta, (1 - albedo) * Rs;
//     se desprecia el balance de onda larga.
//   - Rs proviene de un LDR calibrado por dos puntos, no de un piranómetro.
//   - G (flujo de calor del suelo) se asume 0, convención de FAO-56 para
//     cálculos a escala diaria.
//   - Se usa temperatura del aire (DHT22), no de la superficie del agua.
//   - La presión (para gamma) proviene del BMP280, o de la altitud si falla.
float calcularEvaporacionMMDia(float tempC, float radiacionWm2, float presionKPa) {
  // Presión de vapor de saturación (kPa) - ecuación de Tetens
  float es = 0.6108 * exp((17.27 * tempC) / (tempC + 237.3));
 
  // Pendiente de la curva de presión de vapor de saturación (kPa/°C)
  float delta = (4098.0 * es) / ((tempC + 237.3) * (tempC + 237.3));
 
  // Constante psicrométrica (kPa/°C)
  float gamma = 0.665e-3 * presionKPa;
 
  // Calor latente de vaporización (MJ/kg)
  float lambda = 2.501 - 0.002361 * tempC;
 
  // Radiación: W/m² -> MJ/m²/día  (1 W/m² = 0.0864 MJ/m²/día)
  float rsMJ = radiacionWm2 * 0.0864;
  float rn   = (1.0 - ALBEDO_AGUA) * rsMJ;
  float g    = 0.0;
 
  float et = ALFA_PT * (delta / (delta + gamma)) * ((rn - g) / lambda);
  return (et < 0.0) ? 0.0 : et;
}
 
// Lógica de fusión con histéresis. La demanda evaporativa se compara contra
// un umbral en mm/día (Priestley-Taylor) en vez de contra un índice 0-100.
//
// Histéresis: si el estado anterior ya era de alerta, los umbrales se
// desplazan a favor de mantener la alerta. Así, con NIVEL_ALERTA_PCT = 30 y
// un margen de 3, se entra en ADVERTENCIA al bajar de 30% pero solo se
// vuelve a NORMAL al superar 33%, evitando que el estado oscile.
NivelAlerta evaluarAlerta(float nivelPct, float evaporacionMMDia,
                          float caudalLmin, NivelAlerta estadoPrevio) {
  if (nivelPct < 0) return NORMAL; // sin nivel válido la evaluación no aplica
 
  bool enAlerta = (estadoPrevio == ADVERTENCIA || estadoPrevio == CRITICO);
 
  float umbralCritico = NIVEL_CRITICO_PCT       + (enAlerta ? MARGEN_NIVEL_PCT   : 0.0);
  float umbralAlerta  = NIVEL_ALERTA_PCT        + (enAlerta ? MARGEN_NIVEL_PCT   : 0.0);
  float umbralEvap    = EVAPORACION_ALTA_MM_DIA - (enAlerta ? MARGEN_EVAP_MM_DIA : 0.0);
  float umbralCaudal  = CAUDAL_ANOMALO_LMIN     - (enAlerta ? MARGEN_CAUDAL_LMIN : 0.0);
 
  bool consumoAnomalo  = caudalLmin > umbralCaudal;
  bool evaporacionAlta = evaporacionMMDia > umbralEvap;
 
  if (nivelPct <= umbralCritico ||
      (nivelPct <= umbralAlerta && evaporacionAlta) ||
      (nivelPct <= umbralAlerta && consumoAnomalo)) {
    return CRITICO;
  }
  if (nivelPct <= umbralAlerta || evaporacionAlta || consumoAnomalo) {
    return ADVERTENCIA;
  }
  return NORMAL;
}
 
// ============================================================================
//  SALIDAS FÍSICAS IN SITU
// ============================================================================
// Los LEDs reflejan siempre el estado real, aunque la alarma esté silenciada:
// silenciar solo apaga el buzzer, nunca la señalización visual.
// FALLA se señaliza encendiendo verde y rojo a la vez: una combinación que
// no corresponde a ningún nivel de riesgo, por lo que es inconfundible en sitio.
void actualizarSalidas(NivelAlerta alerta, bool silenciada) {
  digitalWrite(LED_VERDE,    alerta == NORMAL      || alerta == FALLA);
  digitalWrite(LED_AMARILLO, alerta == ADVERTENCIA);
  digitalWrite(LED_ROJO,     alerta == CRITICO     || alerta == FALLA);
 
  if (silenciada || alerta == NORMAL) {
    noTone(BUZZER_PIN);
    return;
  }
 
  if (alerta == ADVERTENCIA) {
    tone(BUZZER_PIN, 1000, 300); // intermitente
  } else if (alerta == CRITICO) {
    tone(BUZZER_PIN, 2200);      // continuo
  } else {
    tone(BUZZER_PIN, 500, 150);  // pitido corto y grave: falla de sensor
  }
}
 
void mostrarLCD(const Lectura &d) {
  lcd.clear();
  int pantalla = (millis() / 4000) % 3;
 
  if (pantalla == 0) {
    lcd.setCursor(0, 0);
    lcd.print("N:");
    lcd.print(d.nivelPct, 0);
    lcd.print("% T:");
    lcd.print(d.tempC, 1);
    lcd.print((char)223);
    lcd.print("C");
 
    lcd.setCursor(0, 1);
    switch (d.alerta) {
      case NORMAL:      lcd.print("Estado: NORMAL"); break;
      case ADVERTENCIA: lcd.print("ALERTA: SEQUIA"); break;
      case CRITICO:     lcd.print("CRITICO!!");      break;
      case FALLA:       lcd.print("FALLA DE SENSOR"); break;
    }
 
  } else if (pantalla == 1) {
    lcd.setCursor(0, 0);
    lcd.print("Rad:");
    lcd.print(d.radiacionWm2, 0);
    lcd.print(" W/m2");
 
    // Ej: "H:70% P:70.1kPa" (15 caracteres). Un '*' al final indica
    // que la presión es estimada por altitud (BMP280 sin respuesta).
    lcd.setCursor(0, 1);
    lcd.print("H:");
    lcd.print(d.humedadPct, 0);
    lcd.print("% P:");
    lcd.print(d.presionKPa, 1);
    lcd.print(d.presionMedida ? "kPa" : "*");
 
  } else {
    lcd.setCursor(0, 0);
    lcd.print("Caudal:");
    lcd.print(d.caudalLmin, 1);
    lcd.print("L/m");
 
    lcd.setCursor(0, 1);
    lcd.print("Vol.acum:");
    lcd.print(d.volumenAcumuladoL, 1);
    lcd.print("L");
  }
}
 
// ============================================================================
//  TAREA DE MEDICIÓN (Core 0, separada del hilo principal)
// ============================================================================
// Nota: la LCD y el BMP280 comparten el bus I2C y ambos se usan solo desde
// esta tarea, así que no hay accesos concurrentes al bus.
void tareaMedicion(void *parametros) {
  const TickType_t periodo = pdMS_TO_TICKS(INTERVALO_LECTURA_MS);
  TickType_t ultimoDespertar = xTaskGetTickCount();
 
  float    volumenAcumuladoL = 0.0;
  uint8_t  contadorHistorico = 0;
  NivelAlerta alertaPrevia   = NORMAL;   // última evaluación válida (para la histéresis)
  uint8_t  alertaMostradaPrev = NORMAL;  // último estado publicado (para rearmar el silencio)
 
  for (;;) {
    float distancia = medirDistanciaCM();
    float nivelPct  = calcularNivelPct(distancia);
 
    TempAndHumidity datosDHT = dht.getTempAndHumidity();
    float tempC      = datosDHT.temperature;
    float humedadPct = datosDHT.humidity;
 
    bool fallaNivel = (nivelPct < 0);
    bool fallaDHT   = (isnan(tempC) || isnan(humedadPct));
 
    if (fallaDHT) {
      Serial.print("DHT22 SIN LECTURA VALIDA - Estado: ");
      Serial.println(dht.getStatusString());
      tempC      = 0;
      humedadPct = 0;
    }
    if (fallaNivel) {
      Serial.println("HC-SR04 SIN LECTURA VALIDA (eco fuera de rango o sin retorno)");
    }
 
    bool  presionMedida;
    float presionKPa   = leerPresionKPa(presionMedida);
    float radiacionWm2 = calcularRadiacionWm2();
    float evaporacion  = calcularEvaporacionMMDia(tempC, radiacionWm2, presionKPa);
    float caudalLmin   = leerCaudalLmin(INTERVALO_LECTURA_MS, volumenAcumuladoL);
 
    // Sin nivel válido no hay forma de evaluar el riesgo hídrico: FALLA directa.
    // Con nivel válido pero DHT caído se evalúa igual, y la falla solo se
    // reporta si no hay una emergencia hídrica confirmada: un CRITICO real
    // nunca debe quedar oculto tras un aviso de diagnóstico.
    // Una falla del BMP280 NO dispara FALLA: hay respaldo por altitud y su
    // efecto sobre la evaporación es pequeño; se indica como "estimada".
    NivelAlerta alerta;
    if (fallaNivel) {
      alerta = FALLA;
    } else {
      NivelAlerta evaluada = evaluarAlerta(nivelPct, evaporacion, caudalLmin, alertaPrevia);
      alertaPrevia = evaluada; // la histéresis se apoya en la última evaluación válida
      alerta = (fallaDHT && evaluada != CRITICO) ? FALLA : evaluada;
    }
 
    Lectura nueva;
    nueva.nivelPct          = nivelPct;
    nueva.tempC             = tempC;
    nueva.humedadPct        = humedadPct;
    nueva.presionKPa        = presionKPa;
    nueva.presionMedida     = presionMedida;
    nueva.radiacionWm2      = radiacionWm2;
    nueva.evaporacionMMDia  = evaporacion;
    nueva.caudalLmin        = caudalLmin;
    nueva.volumenAcumuladoL = volumenAcumuladoL;
    nueva.alerta            = alerta;
    nueva.tsSegundos        = millis() / 1000;
 
    bool silenciadaLocal;
 
    // --- sección crítica: publica el estado para el servidor web ---
    xSemaphoreTake(mutexDatos, portMAX_DELAY);
 
    // Si el estado de alerta cambió, se rearma la alarma automáticamente:
    // un silenciado no debe ocultar una eventualidad distinta a la silenciada.
    if (alerta != alertaMostradaPrev) {
      alarmaSilenciada = false;
    }
    silenciadaLocal = alarmaSilenciada;
 
    actual = nueva;
 
    contadorHistorico++;
    if (contadorHistorico >= MUESTRAS_POR_HISTORICO) {
      contadorHistorico = 0;
      uint8_t pos = (historicoInicio + historicoCantidad) % HISTORICO_MAX;
      historico[pos] = nueva;
      if (historicoCantidad < HISTORICO_MAX) {
        historicoCantidad++;
      } else {
        historicoInicio = (historicoInicio + 1) % HISTORICO_MAX; // descarta el más antiguo
      }
    }
    xSemaphoreGive(mutexDatos);
    // --- fin de la sección crítica ---
 
    alertaMostradaPrev = alerta;
 
    actualizarSalidas(alerta, silenciadaLocal);
    mostrarLCD(nueva);
 
    Serial.printf(
      "Nivel: %.1f%% | Temp: %.1fC | Hum: %.1f%% | Presion: %.2fkPa%s | "
      "Radiacion: %.0fW/m2 | Evaporacion(PT): %.2fmm/dia | Caudal: %.2fL/min | "
      "VolAcum: %.2fL | Alerta: %d\n",
      nivelPct, tempC, humedadPct, presionKPa, presionMedida ? "" : " (estimada)",
      radiacionWm2, evaporacion, caudalLmin, volumenAcumuladoL, alerta);
 
    vTaskDelayUntil(&ultimoDespertar, periodo);
  }
}
 
// ============================================================================
//  SERVIDOR WEB EMBEBIDO
// ============================================================================
// Exige autenticación antes de atender cualquier ruta del tablero.
bool autorizado() {
  if (server.authenticate(TABLERO_USUARIO, TABLERO_CLAVE)) return true;
  server.requestAuthentication();
  return false;
}
 
void handleRaiz() {
  if (!autorizado()) return;
  server.send_P(200, "text/html", PAGINA_HTML);
}
 
void handleDatos() {
  if (!autorizado()) return;
 
  Lectura d;
  bool silenciada;
  xSemaphoreTake(mutexDatos, portMAX_DELAY);
  d = actual;
  silenciada = alarmaSilenciada;
  xSemaphoreGive(mutexDatos);
 
  char json[384];
  snprintf(json, sizeof(json),
    "{\"nivel\":%.1f,\"caudal\":%.2f,\"volumen\":%.2f,\"temp\":%.1f,"
    "\"humedad\":%.1f,\"presion\":%.2f,\"presionMedida\":%s,"
    "\"radiacion\":%.0f,\"evap\":%.2f,\"alerta\":%u,"
    "\"silenciada\":%s,\"ts\":%u}",
    d.nivelPct, d.caudalLmin, d.volumenAcumuladoL, d.tempC,
    d.humedadPct, d.presionKPa, d.presionMedida ? "true" : "false",
    d.radiacionWm2, d.evaporacionMMDia, d.alerta,
    silenciada ? "true" : "false", d.tsSegundos);
 
  server.send(200, "application/json", json);
}
 
void handleHistorico() {
  if (!autorizado()) return;
 
  String json;
  json.reserve(HISTORICO_MAX * 90 + 8);
  json = "[";
 
  xSemaphoreTake(mutexDatos, portMAX_DELAY);
  for (uint8_t i = 0; i < historicoCantidad; i++) {
    const Lectura &p = historico[(historicoInicio + i) % HISTORICO_MAX];
    char item[110];
    snprintf(item, sizeof(item),
      "%s{\"ts\":%u,\"nivel\":%.1f,\"caudal\":%.2f,\"temp\":%.1f,"
      "\"humedad\":%.1f,\"alerta\":%u}",
      (i == 0) ? "" : ",", p.tsSegundos, p.nivelPct, p.caudalLmin,
      p.tempC, p.humedadPct, p.alerta);
    json += item;
  }
  xSemaphoreGive(mutexDatos);
 
  json += "]";
  server.send(200, "application/json", json);
}
 
// Silencia el buzzer sin apagar los LEDs. Se rearma solo cuando cambia el estado.
void handleSilenciar() {
  if (!autorizado()) return;
 
  xSemaphoreTake(mutexDatos, portMAX_DELAY);
  alarmaSilenciada = true;
  xSemaphoreGive(mutexDatos);
 
  noTone(BUZZER_PIN);
  server.send(200, "application/json", "{\"ok\":true}");
}
 
void handleNoEncontrado() {
  server.send(404, "text/plain", "Recurso no encontrado");
}
 
void conectarWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  WiFi.setSleep(false); // mejora la latencia de respuesta del servidor web
 
  Serial.print("Conectando a la WLAN");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Conectando WLAN");
 
  uint8_t intentos = 0;
  while (WiFi.status() != WL_CONNECTED && intentos < 40) { // hasta 20 s
    delay(500);
    Serial.print(".");
    intentos++;
  }
  Serial.println();
 
  lcd.clear();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Conectado. Tablero disponible en: http://");
    Serial.println(WiFi.localIP());
    lcd.setCursor(0, 0);
    lcd.print("Tablero en:");
    lcd.setCursor(0, 1);
    lcd.print(WiFi.localIP());
  } else {
    // El dispositivo sigue midiendo y alarmando in situ aunque no haya WLAN.
    Serial.println("Sin WLAN. El monitoreo local sigue activo.");
    lcd.setCursor(0, 0);
    lcd.print("Sin WLAN");
    lcd.setCursor(0, 1);
    lcd.print("Monitoreo local");
  }
  delay(3000);
  lcd.clear();
}
 
// Inicializa el BMP280 probando sus dos direcciones I2C posibles (0x76/0x77)
// y los dos IDs de chip (0x58 = BMP280, 0x60 = BME280, que muchas veces se
// vende rotulado como BMP280; para presión ambos son compatibles).
bool iniciarBMP280() {
  const uint8_t direcciones[] = {0x76, 0x77};
  const uint8_t chipIds[]     = {0x58, 0x60};
  for (uint8_t dir : direcciones) {
    for (uint8_t id : chipIds) {
      if (bmp.begin(dir, id)) {
        Serial.printf("BMP280 detectado en 0x%02X (chip ID 0x%02X).\n", dir, id);
        bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
                        Adafruit_BMP280::SAMPLING_X2,     // temperatura
                        Adafruit_BMP280::SAMPLING_X16,    // presión
                        Adafruit_BMP280::FILTER_X16,
                        Adafruit_BMP280::STANDBY_MS_500);
        return true;
      }
    }
  }
  Serial.println("BMP280 NO DETECTADO: se usara la presion estimada por altitud.");
  return false;
}
 
// ============================================================================
//  SETUP / LOOP
// ============================================================================
void setup() {
  Serial.begin(115200);
 
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  pinMode(LED_VERDE, OUTPUT);
  pinMode(LED_AMARILLO, OUTPUT);
  pinMode(LED_ROJO, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LDR_PIN, INPUT);
 
  pinMode(FLOW_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(FLOW_PIN), isrPulsoCaudal, FALLING);
 
  dht.setup(DHT_PIN, DHTesp::DHT22);
 
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Iniciando...");
  lcd.setCursor(0, 1);
  lcd.print("Reto IoT - Agua");
 
  bmpOk = iniciarBMP280();
  delay(2000);
 
  mutexDatos = xSemaphoreCreateMutex();
 
  conectarWiFi();
 
  server.on("/", HTTP_GET, handleRaiz);
  server.on("/api/datos", HTTP_GET, handleDatos);
  server.on("/api/historico", HTTP_GET, handleHistorico);
  server.on("/api/silenciar", HTTP_POST, handleSilenciar);
  server.onNotFound(handleNoEncontrado);
  server.begin();
  Serial.println("Servidor web embebido iniciado en el puerto 80.");
 
  // La medición se ancla al Core 0; loop() (servidor web) corre en el Core 1.
  xTaskCreatePinnedToCore(
    tareaMedicion,     // función de la tarea
    "medicion",        // nombre
    4096,              // tamaño de pila (bytes)
    NULL,              // parámetros
    1,                 // prioridad
    NULL,              // handle
    0                  // núcleo 0
  );
}
 
void loop() {
  server.handleClient();
 
  // Reconexión no bloqueante de la WLAN: la medición y la alarma física
  // siguen funcionando en la otra tarea aunque la red esté caída.
  static uint32_t ultimaRevisionWiFi = 0;
  if (millis() - ultimaRevisionWiFi > 10000) {
    ultimaRevisionWiFi = millis();
    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WLAN caida, reintentando...");
      WiFi.disconnect();
      WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    }
  }
 
  delay(2); // cede tiempo de CPU al scheduler de FreeRTOS
}

Transcripciones de instrucciones enviadas a la IA:

Chat #1:

INSTRUCCIONES ENVIADAS A CLAUDE (ANTHROPIC) - CHALLENGE#1 IOT Sistema de monitoreo y alerta de escasez de agua - Sabana Centro Listado cronológico exacto de las preguntas/instrucciones del usuario en esta conversación.

  1. [Adjunto: PDF del enunciado del reto] "Necesito que me ayudes a diseñar la simulación de este proyecto en wokwi. Por ahora, ayúdame a definir los sensores y los dispositivos que deberíamos usar para la solución, teniendo en cuenta que debe ser una solución de bajo costo. Ya contamos con un lcd 1602, leds, un ultrasonico hc-sr04 y demás componentes básicos"

  2. "Vamos a usar un esp32"

  3. "Vamos con 3 leds individuales y ya tenemos un buzzer"

  4. "Modifica el diagram, los cables están todos superpuestos"

  5. "Dame la lista de todos los componentes que hay"

  6. "Incluye en todos los archivos lo necesario para medir el caudal"

  7. "Cual sería la lista completa ahora?"

  8. "Dame la lista con los materiales en wokwi en una columna y los reales en la otra"

  9. "Actualmente no recibe ninguna lectura de temperatura ni humedad el sensor en la simulación"

  10. "Sigue sin leer nada de temperatura ni humedad"

  11. "DHT22 SIN LECTURA VALIDA - Estado: TIMEOUT"

  12. "Cambiando ese pin ya funcionó"

  13. [Adjunto: foto - módulo sensor de luminosidad LDR de Ferretrónica] "Podrías verificar si este módulo sirve para reemplazar el fotorresistor?"

  14. "Qué voltaje y potencia necesitaría todo el montaje?"

  15. "Necesito el código para la implementación física"

  16. "Dame una guía paso a paso para montarlo en la protoboard"

  17. "Necesito que acomodes tanto la simulación como el código para el montaje físico a un ESP32 AMSIII7 G2A7A, con los mismos materiales de siempre, solo que el sensor de flujo es un ZJ-S201"

  18. [Adjunto: foto de la placa ESP32 física, sin texto adicional]

  19. "Vale, vuelveme a dar la guía de montaje"

  20. "No tengo un buzzer, pero tengo un parlante con dos cables también, sirve?"

  21. "Cuales son los pines gpio5 y gpio18?"

  22. "El sensor tiene +, out y -, el dht22"

  23. "Tengo un fotorresistor comun y corriente, no indica polaridad"

  24. "Cómo harías la maqueta de todo esto"

  25. "O sea, sería conectar el sensor de caudal a una botella con una tubería, y que al otro lado de la tubería salga al recipiente grande de agua verdad?"

  26. "Conecta VCC al riel de 3.3V y GND al riel negativo. Usa solo el pin AO (salida analógica) conectado al GPIO 35 (D35) — el pin DO no se usa en este proyecto, déjalo sin conectar. Colócalo donde vaya a recibir luz directa (idealmente expuesto al exterior). cuales pines son estos"

  27. "el buzzer tiene una pata más larga que la otra, funciona igual que con los leds?"

  28. "La pantalla del lcd empezó a prenderse y apagarse cada 2 segundos"

  29. [Adjunto: foto - módulo GY-BMP280] "Tenemos este sensor para reemplazar el otro que teníamos, cómo lo conectamos"

  30. "Toca cambiar algo en el código?"

  31. "Tenemos un módulo LM393_ 59M BWQ 64 para reemplazar el sensor de luminosidad, como lo conectamos?"

  32. "Solo tiene DO, GND y VCC"

  33. "Y un fotorresistor común y corriente sí debería dar porcentaje?"

  34. "El suelto, pero el tema es que en el lcd solo muestra valores de 1% y 0%"

  35. "La resistencia no está conectada a ground, está en el punto intermedio entre el cable que va a la protoboard y el cable conectado al D35"

  36. "Entonces el fotorresistor directamente nunca toca tierra?"

  37. "Ya está así, pero solo sale 100%"

  38. "La resistencia si tiene que tener una salida a ground?"

  39. "Cómo cambia la fórmula para el ultrasonico dependiendo del envase que esté midiendo?"

  40. "Recuérdame cómo conecto el bmp280"

  41. "El de 4 pines"

  42. "O sea son los mismos sca y sdl verdad?"

  43. "El sensor de la presión sale nanhpa"

  44. "En el serial solo me sale esto con este código: [texto ilegible/caracteres corruptos] Ln 46, Col 21 DOIT ESP32 DEVKIT V1 on COM5 2"

  45. "El ide de arduino se quedo congelado en hard resetting via rts pin"

  46. "El ultrasonido puede fallar por el agua? Al llenar el tanque la medición se queda en 50%"

  47. "El envase es ancho, pero solo tiene 7 cm de profundidad"

  48. [Adjunto: código sketch_fisico.ino pegado en el chat] "Necesito cambiar el código, sobre el último que te mandé, para que no salga la información de la presión por lo que el sensor no se pudo implementar, y arreglar los pines porque cuando se pone en nivel crítico sí se prende el led rojo pero el buzzer suena intermitente, como si estuviera en el estado de alerta y en el amarillo suena constante"

  49. "Para finalizar, me toca hacer las secciones de resumen general y conclusiones para la wiki, me gustaría saber qué información consideras que debería aparecer ahí"

  50. "Vale, hazme un resumen del contexto del proyecto"

  51. "Ahora dame en un .txt todas las instrucciones de este chat por favor, desde el inicio"

Chat #2:

Transcripción de instrucciones y preguntas enviadas a Claude (Anthropic) Uso: asistencia técnica y de redacción para las secciones 2 (Solución propuesta) y 6 (Anexos) de la Wiki del proyecto - Challenge#1 Internet de las Cosas

me toco hacer el inciso 2 y 6, podrias ayudarme con eso? Es un proyecto de IoT, dime que informacion necesitas y te la mando, adicional a eso te voy a mandar el documento donde esta esa seccion que te recorte

restricciones de diseño podria ser que el tamaño del esp32 hizo que el cableado fuera dificil de hacer porque el esp32 ocupaba muy espacio en la protoboard y apenas dejaba una fila de pines libres de un solo lado, lo cual nos dificulto el cableado a los demas sensores y componentes del sistema, ingeniandonosla como pudimos, decidimos realizar una parte del cableado por debajo del esp32 para que pudiera funcionar. Y diria que fue la unica, porque pues del problema esp32 salio tambien el tema del cableado, entiendes?

[Se adjuntó lista de materiales (simulación vs. prototipo real), tabla de consumo eléctrico, y guía de conexiones paso a paso del montaje físico]

Quedo pendiente por si necesitas algo más

aqui esta la simulacion but cambiamos algunas cositas ahi porque no habian ciertos sensores por lo que tubimos que simular su funcionamiento leds o cosas asi.

adicional a eso te voy a pasar el codigo que hicimos para la programacion del circuito. (por cierto el diagrama de bloques no se ve muy bien, tal vez sea por los colores que usaste, usa colores más oscuros para las cajas

[Se adjuntó imagen del diagrama de simulación en Wokwi y el código fuente completo del firmware]

tengo varias preguntas, en el documento decia como debe estar documentado todo esto? En un repositorio de github en un pdf? Adicional a eso, en que formato esta el documento que me mandaste? Y adicional a eso, por que el diagrama UML me lo mandaste con solamente el codigo? Crees que este bien asi?

Yo creo que es mejor ponerlo como un diagrama UML clasico, sabes?

[En respuesta a la pregunta de qué tipo de diagrama UML prefería] Toma tu la decision dependiendo de lo que te parezca mejor para el trabajo

okay, teniendo en cuenta que todo esto lo debo copiar y pegar en la wiki, me gustaria que no se viera tanto como que fue creado con IA entonces tal vez si le quitas los comentarios como dentro de guiones, ya que en español no solemos usarlos y no sé que otra cosa quitarle o cambiarle, todo esto va a ir en el github

una pregunta en la parte de solucion de propuesta en el 2.1 restricciones de diseño identificadas, toca colocar una de cada tipo? Porque realmente solamente tuvimos la restriccion de diseño que te comentamos, la de que el esp32 ocupaba todo el espacio

estonces cumple solamente con lo que te dije, nada más, escogimos esos sensores no realmente porque nos parecieran baratos pero pues tampoco ibamos a comprar los más caros, al ser un prototipo funcional no vamos a gastar mucho de nuestro bolsillo ya que no es un experimento que se vaya a vender o algo por el estilo. si te parecen baratos es cosa tuya

si quieres pasame textualmente como deberia ser el diagrama de clases (conexiones y todo ) y le digo a otra ia que lo cree adecuatamente. adicional dime que IA es buena para desarrollar diagramas o diagramas uml

Ahora si quieres pasame textualmente como deberia ser el diagrama de secuencia(conexiones y todo )

okay, teniendo en cuenta qu todo esto lo debo compiar y pegar en la wiki, me gustaria qu no se viera tanto como que fue creado con IA entonces tal vez si le quitas los comentarios como dentro de guiones, ya que en español no solemos usarlos y no sé que otra cosa quitarle o cambiarle, todo esto va a ir en el github

estamos hablando del del 2.2 Arquitectura propuesta diagrama de bloques(hardware y software)

correcto?

[Se adjuntó imagen generada de un diagrama de bloques] que opinas del diagrama

aunque si te das cuenta, las flechas estan mal, porque se supone que cada uno de los sensores debe desprender una flecha hacia modulo de adquisision de datos y eso no esta pasando

muy bien ayudame con el prompt para generar esa imagen teniendo en cuenta eso que me mandaste: [se pegó la especificación textual del diagrama de bloques generada previamente por la IA]

[Se pegó el texto: "Alarma física: activa el semáforo de LEDs y el buzzer según el nivel de riesgo calculado, cumpliendo la restricción de notificación in situ sin redes de comunicación."] mira lo que escribiste en esta parte, aqui que quisiste decir?

no hables en latin, solamente español, como quedaria?

ahora pasame el prompt para el diagrama de actividades

tengo que pegarlo al github wiki: [se pegó el contenido renderizado de las secciones "Estándares de diseño de ingeniería aplicados" y "Modelo de negocio (resumen)"]

pero me di cuenta que no se pega lo siguiente: [se pegó el contenido renderizado de varias secciones, incluyendo tablas de materiales y consumo eléctrico] nada de eso se pego

si quieres pasame solamente esa parte y ya

me puedes dar un txt de las instrucciones que te pedi? Solo es una transcripcion

Chat #3:

Declaración de Uso de Herramientas de Inteligencia Artificial

  1. Herramienta de IA consultada: Asistente de Inteligencia Artificial (Claude) para apoyo en redacción técnica, corrección de estilo y estructuración de documentación en formato Wiki.

  2. Transcripción de las instrucciones y consultas enviadas a la IA:

    • Consulta 1 (Revisión de estilo y ortografía del Punto 3):"Corrige redaccion/ortografia de este texto: [El estudiante pegó aquí su borrador original sobre el Punto 3: Configuración experimental, resultados y análisis]. Te muestro el contexto para que no cambies nada inusual, pon en negrillas los cambios que hagas." (Nota: Se adjuntó el documento PDF con los lineamientos del Challenge#1).


    • Consulta 2 (Estructuración del Punto 3):"Que mas falta de este tercer punto, y especifcicamente sobre la configuracion, antes de continuar con los resultados y analisis ?"


    • Consulta 3 (Corrección de estructura según el enunciado):"El protocolo de pruebas es exactamente el siguiente punto, el punto 4. Entonces eso debería de estar en el punto 4, no el 3. Por si algo, te estoy hablando de esta parte: [El estudiante copió el texto del PDF correspondiente a los entregables del curso, desde 'Elaborar una documentación...' hasta '...aparecer con cámara encendida']."


    • Consulta 4 (Redacción técnica del montaje de hardware):"Como son las conexiones para la primera parte: [El estudiante pegó una guía de 8 pasos sobre el montaje en protoboard, conexiones del ESP32, sensores y actuadores]. Esta es la guia del montaje completo."


    • Consulta 5 (Extracción de datos del código fuente):"Configuración de Software y Tiempo Real Me puedes decir que librerias usa el codigo, que es este: [El estudiante pegó el código fuente completo en C++/Arduino del prototipo], para esta siguiente parte ?"


    • Consulta 6 (Reubicación de contenido propio):"Si, yo diria que si. Pero entonces en donde estaria la seccion que ya escribi ?"


    • Consulta 7 (Avance al Punto 4):"Si, vayamos a esa parte"


    • Consulta 8 (Ajuste de metodología de trabajo y solicitud de resumen):"Haber haber, dame un resumen general de esta seccion #4 que me toca escribir, porque, aunque correcto, no tuve voz ni voto en lo que acabas de escribir, entonces vayamos lento"


    • Consulta 9 (Revisión del borrador propio del Punto 4):"Entonces: [El estudiante pegó su propio borrador redactado sobre el Punto 4: Autoevaluación del protocolo de pruebas, abarcando la validez de los elementos y las limitaciones del sistema]."


    • Consulta 10 (Solicitud de registro para los entregables):"Yo creo que esta bien. Para terminar esto, puedes transcribir las intrucciones que te hice ? Es obligatorio para ponerla en los anexos"

"Este es el proyecto de IoT del que te hable hace algunos dias. Ya esta hecho el circuito en si, y para la Wiki, soy encargado de escribir el punto 3 y 4. Entonces queria saber, que puedo agregar en estos puntos? Que tengo que poner para que sea de la forma mas completa posible?" (junto con el enunciado del Challenge#1 en PDF)

"Para saber un poco como funciona el circuito, te mando el codigo (fue hecho para un ESP32). Entonces, en que momento se activan/desactivan los leds del estado?" (junto con el código fuente del sketch para ESP32)

"Cuales de estas variables son medidas por estos sensores: 'Módulo I2C, DHT22, Fotorresistor, BMP280, YF-S201 2 de cada uno por si acaso', y que es lo que miden?"

"indiceEvaporacion que exactamente mide?"

"El sensor ultrasonico ya esta obviamente en el circuito, ahora necesito saber como es que funciona en codigo el sonido de la alarma en si"

"Yo creo que esta bien. Ya fue logrado todo (aunque no con todo lo que dijiste). Para terminar esto, puedes transcribir las intrucciones que te hice? Es obligatorio para ponerla en los anexos"


Chat #4:

TRANSCRIPCIÓN DE INSTRUCCIONES ENVIADAS A CLAUDE (ANTHROPIC) - CHALLENGE #2 IoT Sistema IoT de monitoreo y alerta de escasez de agua - Sabana Centro (Cundinamarca) Universidad de La Sabana, 2026-2

Uso: revisión y corrección de la Wiki frente a la retroalimentación del Challenge #1 y al enunciado del Challenge #2, verificación del firmware y diseño del banco de pruebas y guion del video.

Listado cronológico de las instrucciones y preguntas enviadas en esta conversación.

  1. [Adjunto: plan_wiki_challenge2.md + enlace a la wiki del proyecto] "Necesito que leas este archivo y me ayudes con las correcciones que tengo que hacer, vamos por páginas si te parece."

  2. [Adjuntos: Anexo_Evaluacion_Equipo3.pdf y Enunciado_Challenge2_IoT.pdf] "Si puedes evita los cambios que no están en estas correcciones o que no hacen falta en este enunciado."

  3. "Sigamos con la página 2."

  4. [Adjunto: contenido actual de la página 2 de la wiki] "Antes de eso me puedes pasar esto en markdown normal porfa?"

  5. "Ahora sí sigue con la 2.3."

  6. "¿Dice que toca hacer sí o sí un esquemático formal?"

  7. [Sobre la afirmación del divisor de tensión en la línea ECHO] "Esto sí se hace? revisa el chat de diseño de simulación de proyecto en Wokwi si hace falta para verificar."

  8. [Adjunto: imagen de la tabla de conexiones con la placa nueva] "¿Puedes revisar esta tabla y verificar si la que tenemos ya está bien? Nos tocó pasar de un ESP32 de 30 pines a uno de 38."

  9. "Si no estoy mal el de caudal conectaba 3 cables, ¿podría ser eso?"

  10. "Ten en cuenta que se piensa para el embalse del Neusa como se dice en la hoja de trabajo."

  11. "Para el modelo de negocio toca cambiar algo?"

  12. [Adjunto: tabla de conexiones de la wiki] "Esta es la tabla de conexiones equivalente al esquemático, ¿me toca cambiarle algo?"

  13. "¿Me pasas la tabla en markdown para copiarla y pegarla?"

  14. "Pero dice que tiene un pull-up interno, entonces no hace falta el divisor, ¿no?"

  15. "Sigamos con la página 3 porfa."

  16. "¿Me puedes pasar esto en markdown para pegarlo directamente?"

  17. [Adjunto: sketch_challenge2.ino] "Este es el código por si acaso."

  18. "En ese código sí está implementado lo del estado de falla y la histéresis, ¿verdad?"

  19. "Lo de falla ve cualquier sensor, ¿verdad?"

  20. "Pásame todo lo de la página 3 completo en markdown, teniendo en cuenta que sí vamos a implementar el BMP280."

  21. "¿Lo puedes poner en un archivo porfa?"

  22. "¿Aún hace falta la página 4?"

  23. "Arranquemos por la 6."

  24. "¿Cómo se puede calibrar el HC-SR04 por su zona ciega de 2 centímetros?"

  25. "¿Puedes revisar nuevamente la wiki? En teoría la página 3 ya quedó completa."

  26. [Enlace a la página 3 de la wiki] "Revisa en este enlace porfa."

  27. [Adjunto: imagen del historial de revisiones de la página.]

  28. [Adjunto: contenido actualizado de la página 3.]

  29. "Sí porfa, pásame la redacción de eso."

  30. "Si no estoy mal el HC-SR04 se le aplicó la solución que nos dijiste de levantarlo un poco y dejarlo como el rango entre el dispositivo y el agua."

  31. "Recuérdame estos dos más adelante porfa."

  32. "Sí porfa, sigamos con la 4."

  33. [Adjunto: contenido actual de la página 4 de la wiki.]

  34. [Adjunto: sketch_challenge2_1.ino] "No vamos a implementar más mediciones por tiempo, pero acá también está el código, ah, sí, y logramos reducirlo a una sola protoboard."

  35. [Adjunto: borrador propio de la página 4] "Teniendo en cuenta lo que te pasé antes, ¿me puedes dar la página 4 actualizada? Si puedes dámela en puro texto, no en markdown directamente a menos que haya tablas o algo diferente."

  36. "Creo que se decía que se comparó contra los valores de referencia de Bogotá."

  37. "¿Puedes verificar lo de la ecuación? y actualmente me sale que la presión está en 1026 mb."

  38. [Sobre la presión reducida al nivel del mar] "¿Puedes darme una fuente que verifique esto?"

  39. "Dame los párrafos para esas dos secciones que me dijiste, también toca cambiar la tabla, ¿verdad?"

  40. [Adjunto: contenido actualizado de la página 3] "¿En qué parte del 3.1.3?"

  41. [Adjunto: contenido de la página 3] "¿Este es el mismo texto que te pasé antes, verdad?"

  42. "Vale, ahora sí vamos con la página 5."

  43. [Adjunto: contenido actual de la página 5 de la wiki.]

  44. [Sobre el reto de la zona ciega del sensor de nivel] "Cambia esto por algo que solo hable de esta entrega."

  45. "Ahora sí, vamos con la página 6."

  46. [Adjunto: contenido actual de la página de anexos] "Vamos con la página 7 que es la de anexos, ¿qué debo cambiar acá?"

  47. "¿Me recomiendas quitar el código de la simulación?"

  48. [Adjunto: versión actualizada del firmware del Challenge #2] "¿Puedes generar el documento con el código así? Ya está cambiado todo y no tiene las credenciales."

  49. "¿Me puedes pasar el .ino con ese código? Y además las transcripciones de las instrucciones de este chat porfa."

Chat #5

Transcripción del chat — Prototipo IoT de monitoreo de agua

Usuario: estas son mis conexiones para el proyecto de IoT Estas son las conexiones del prototipo físico tal como quedó al final del Challenge #1:

Componente Pin del componente Pin del ESP32
HC-SR04 VCC VIN (5V)
TRIG D5
ECHO D18
GND GND
DHT22 (módulo 3 pines) + 3V3
out D15
− GND
Fotorresistor (LDR) una pata 3V3
otra pata nodo intermedio → D35
Resistencia 10kΩ una pata el mismo nodo intermedio
otra pata GND
LCD1602 I2C VCC VIN (5V)
GND GND
SDA D21
SCL D22
LED verde ánodo (pata larga) → 220Ω D25
cátodo (pata corta) GND
LED amarillo ánodo → 220Ω D26
cátodo GND
LED rojo ánodo → 220Ω D27
cátodo GND
Buzzer pata larga (+) D33
pata corta (−) GND
Sensor de caudal ZJ-S201 señal D32
VCC / GND VIN (5V) / GND — verificar contra el datasheet del sensor

Pero hubo un cambio de ultimo momento y ahora necesito ver que tanto cambian las conexiones ya que cambiamos el esp32 a uno de 38 pines, me puedes ayudar con los pines por favor

Claude: Si tu placa de 38 pines es un ESP32 clásico (ESP32-WROOM-32, tipo DevKit V1 o DevKitC), casi no cambia nada. Los números de GPIO son los mismos que en la de 30 pines, así que tu conexión (D5, D18, D15, D35, D21, D22, D25, D26, D27, D33, D32) sigue funcionando igual. Lo único que cambia es dónde está físicamente cada pin en el header, y eso lo verificas con el pinout de tu placa.

Lo que sí debes revisar:

  • Pin de 5V: en muchas placas de 38 pines la etiqueta es 5V en lugar de VIN.
  • Pines que no debes tocar: D0, D1, D2, D3, CMD y CLK (GPIO 6 a 11), conectados a la memoria flash.
  • Pines de las señales: confirmar la posición de D5, D18, D15, D21, D22, D25, D26, D27, D32, D33 y D35.
  • D35 es solo entrada, correcto para el LDR.
  • D15 y D5 son pines de arranque (strapping), pero con DHT22 y HC-SR04 no dan problema en la práctica.

Dos riesgos que ya venían en el diseño original:

  1. ECHO del HC-SR04 → D18: el HC-SR04 a 5V saca 5V en ECHO, y el ESP32 tolera solo 3.3V. Se recomienda un divisor de voltaje (ej. 1kΩ y 2kΩ) o un level shifter.
  2. Señal del ZJ-S201 → D32: si se alimenta a 5V, su señal de pulsos también puede llegar a 5V. Revisar el datasheet y aplicar el mismo divisor si es necesario.

Se le preguntó al usuario si el nuevo ESP32 era el clásico (WROOM-32) o una variante como el S3, para dar una tabla completa.


Usuario: [Adjuntó 3 fotos del ESP32 físico] Ese es mi esp32, La verdad no estoy seguro de cual sea

Claude: Identificó la placa como una NodeMCU ESP-32S de 38 pines (módulo ESP-32S, chip USB CP2102, conector USB-C), el ESP32 clásico (WROOM-32). Los GPIO son los mismos, no hace falta cambiar el código, solo la posición física de los cables.

Se detalló el orden de pines leído de la serigrafía trasera (header izquierdo y derecho) y se dio la tabla de conexiones actualizada para HC-SR04, DHT22, LDR+resistencia, LCD1602 I2C, LEDs, buzzer y sensor de caudal, indicando en qué header (izquierdo/derecho) está cada pin.

Se advirtió:

  1. No usar los pines SD0, SD1, SD2, SD3, CMD y CLK (memoria flash).
  2. El pin de 5V está al final del header izquierdo, cerca del USB-C, etiquetado "5V" en vez de "VIN".
  3. Los GND están repartidos en varios puntos de los dos headers.
  4. Recordatorio del divisor de voltaje en ECHO (P18) y posiblemente en el sensor de caudal (P32).

Usuario: que es eso del header?

Claude: Explicación de que "header" es la fila de pines metálicos que sale a cada lado de la placa (también llamada "tira de pines" o "regleta de pines"). Se aclaró que el ESP32 tiene un header izquierdo y uno derecho, cada uno con 19 pines, y que así se referenciaban en la tabla anterior (ej. P5 y P18 en el derecho, P25-P27 en el izquierdo). Se sugirió voltear la placa para confirmar cada pin contra la serigrafía.


Usuario: cual es el P15?

Claude: Se ubicó el P15 en el header derecho, cerca del USB-C: contando desde abajo, CLK, SD0, SD1, P15, P2, P0, P4 — es el cuarto pin desde la esquina de abajo. Se advirtió que queda pegado a los pines SD que no se deben usar, y se recomendó verificar con la serigrafía trasera.


Usuario: no entendi las conexiones de LDR, como va? me diste como 3 conexiones y solo tiene 2 patas

Claude: Se aclaró que son dos componentes (LDR y resistencia de 10kΩ) en serie, formando un divisor de voltaje:

3V3 ──── LDR ────┬──── 10kΩ ──── GND
                 │
                 └──── P35

Pasos: pata 1 del LDR a 3V3; pata 2 del LDR + una pata de la resistencia forman el nodo intermedio; la otra pata de la resistencia a GND; un cable desde el nodo a P35. Se explicó cómo hacerlo en protoboard (misma columna) y que el LDR no tiene polaridad.


Usuario: que pasa si conecto el esp32 al enchufe? O sea como si fuera mi cargador

Claude: Se distinguieron dos escenarios:

  1. Conectarlo con un cargador USB a la pared: sí se puede, es seguro. Recomendaciones: usar cargador de 5V (no "carga rápida" de voltaje variable), cable de datos y carga, al menos 2A de margen por los sensores conectados, y que el código ya queda guardado en la placa.
  2. Conectarlo directamente a 110-120V de la pared sin cargador de por medio: NO se puede, dañaría la placa de inmediato.

Se preguntó al usuario cuál de las dos opciones tenía en mente.


Usuario: como va la conexion del ldr

Claude: Repitió la explicación del divisor de voltaje LDR + resistencia de 10kΩ con el diagrama y los pasos de conexión a 3V3, GND y P35.


Usuario: para el LDR es necesario l resistencia?

Claude: Sí, es necesaria. Se explicó que el ESP32 solo mide voltaje, no resistencia directamente, y que sin la resistencia el circuito no se cierra a GND (P35 siempre leería 3.3V). La resistencia cierra el circuito y crea el divisor de voltaje que traduce los cambios de luz en cambios de voltaje legibles. Se mencionó que 10kΩ es un valor típico, ajustable según el datasheet del LDR específico.


Usuario: pero como se conecta el led?

Claude: Se explicó la conexión de cada LED con su resistencia de 220Ω en serie entre el pin del ESP32 y GND (no a VCC):

Pin del ESP32 ──── 220Ω ──── LED (pata larga → pata corta) ──── GND

Con la tabla de pines por color (Verde→P25, Amarillo→P26, Rojo→P27) y la explicación de que el pin de señal en HIGH/LOW enciende/apaga el LED.


Usuario: pero la pata larga donde va o q? Y la pata corta?

Claude: Se precisó: pata larga (ánodo, +) va del lado de la resistencia → pin del ESP32; pata corta (cátodo, −) va directo a GND. Se dieron los pasos para armarlo en protoboard y una pista visual (el borde plano del LED indica el cátodo).


Usuario: es mejor colocar un programa en el esp32 desconectandolo de todo el circuito o como?

Claude: Se recomendó programar con el ESP32 solo (desconectado de sensores) durante las primeras pruebas, para evitar cortos, conflictos en pines de arranque (P0, P2, P15) y confundir errores de cableado con errores de código. Una vez el circuito está armado y estable, se puede subir código con todo conectado, siempre que se desconecte la alimentación externa de 5V mientras se programa por USB.


Usuario: [Adjuntó 3 fotos del ESP32] Ese es mi esp32, La verdad no estoy seguro de cual sea

(Nota: este mensaje con fotos ya está registrado arriba; aparece una sola vez en la conversación real.)


Usuario: tiene 6 entradas, las dos adicionales son CSB y SDO

Claude: Se dio la tabla de conexión del BMP280 de 6 pines: VCC→3V3, GND→GND, SCL→P22, SDA→P21, CSB→3V3 (modo I2C), SDO→GND (dirección 0x76). Se explicó que CSB flotante o a GND activaría modo SPI, y que SDO fija la dirección I2C (0x76 si va a GND, 0x77 si va a 3V3).


Usuario: pero esos pines ya los usamos no?, por lo menos el 22 si lo usamos para una conexion del LCD

Claude: Se explicó que P21 y P22 son un bus I2C diseñado para compartirse entre varios dispositivos (LCD en 0x27/0x3F, BMP280 en 0x76), cada uno con su propia dirección, y que en la protoboard se conectan ambos SDA a la misma columna que P21, y ambos SCL a la misma columna que P22. Se aclaró que los pines de señal normales (LEDs, DHT22, HC-SR04) no se pueden compartir así, el I2C es la excepción.


Usuario: listo, y ahora como van todas las conexiones al LCD

Claude: Se dio la tabla de los 4 pines del LCD1602 I2C: GND→GND, VCC→5V, SDA→P21, SCL→P22. Se advirtió que VCC debe ir a 5V (no 3V3) para buena visibilidad, que SDA/SCL se comparten con el BMP280, que el potenciómetro trasero regula el contraste si la pantalla no muestra texto, y se dio un snippet de código con LiquidCrystal_I2C.


Usuario: Me refiero a todos los sensores

Claude: Se entregó la tabla completa de conexiones con la placa nueva (NodeMCU ESP-32S), incluyendo HC-SR04, DHT22, LDR+10kΩ, BMP280, LCD1602 I2C, LEDs, buzzer y sensor de caudal ZJ-S201, con pin del componente, pin del ESP32 y header. Se sugirió organizar tres rieles en la protoboard (3V3, 5V, GND) y se recordaron los dos puntos de divisor de voltaje pendientes (ECHO y señal del caudal).


Usuario: o sea lo que yo quiero saber es si LCD se conecta a todos los sensores o solamente a eso que me mandaste

Claude: Se aclaró que el LCD solo se conecta al ESP32 (5V, GND, P21, P22), no directamente a ningún sensor. El ESP32 lee los sensores y, desde el código, escribe los datos en el LCD. La única conexión compartida es el bus I2C con el BMP280 (SDA/SCL), pero LCD y BMP280 no se comunican entre sí, cada uno habla solo con el ESP32.


Usuario: que pasa si conecto el esp32 al enchufe? O sea como si fuera mi cargador

(Nota: repetido en el orden real de la conversación; respuesta ya registrada arriba.)


Usuario: como va la conexion del ldr

(Nota: repetido; respuesta ya registrada arriba.)


Usuario: para el LDR es necesario l resistencia?

(Nota: repetido; respuesta ya registrada arriba.)


Usuario: pero como se conecta el led?

(Nota: repetido; respuesta ya registrada arriba.)


Usuario: pero la pata larga donde va o q? Y la pata corta?

(Nota: repetido; respuesta ya registrada arriba.)


Usuario: es mejor colocar un programa en el esp32 desconectandolo de todo el circuito o como?

(Nota: repetido; respuesta ya registrada arriba.)


Usuario: [Adjuntó archivo sketch_challenge2.ino] y teniendo en cuenta que es un esp32 de 38 pines, este codigo me sirve o tengo que cambiarle algo?

Claude: Tras revisar el código (sketch_challenge2.ino, programa para el Challenge #2 con servidor web embebido, tareas FreeRTOS, mutex, histéresis, cálculo de evaporación por Priestley-Taylor, etc.), se confirmó que el código sirve sin cambios de pines:

  1. Todos los #define de pines (TRIG_PIN 5, ECHO_PIN 18, DHT_PIN 15, LDR_PIN 35, FLOW_PIN 32, LED_VERDE 25, LED_AMARILLO 26, LED_ROJO 27, BUZZER_PIN 33, I2C_SDA_PIN 21, I2C_SCL_PIN 22) coinciden con el cableado ya hecho en la placa nueva.
  2. xTaskCreatePinnedToCore(..., 0) funciona porque la NodeMCU ESP-32S es dual-core (WROOM-32 clásico).
  3. El uso de interrupciones en FLOW_PIN (P32) y lectura analógica en LDR_PIN (P35) es compatible con el ESP32 clásico.

Se señaló lo que sí hay que completar (no por el cambio de placa, sino porque son plantillas): WIFI_SSID, WIFI_PASSWORD, TABLERO_USUARIO, TABLERO_CLAVE, y los valores de calibración del LDR (ADC_SOMBRA, ADC_SOL). Se confirmó que todos los pinMode() necesarios están presentes en el setup().


Fin de la transcripción.

Chat #6

Transcripción resumida de la conversación con Claude (Anthropic)

Herramienta: Claude (claude.ai) Fecha: 1 y 2 de octubre de 2026 Propósito: depuración del prototipo físico, calibración de sensores y revisión de redacción de la Wiki.

# Pregunta / instrucción del equipo Respuesta de la IA (resumen)
1 Error de compilación: falta LiquidCrystal_I2C.h. Indicó instalar las librerías LiquidCrystal I2C y DHT sensor library for ESPx desde el gestor de librerías.
2 La radiación del LDR marca 0 W/m². Explicó que la fórmula da 0 con lecturas de ADC bajas y propuso imprimir la lectura cruda para diagnosticar.
3 El HC-SR04 es errático y da valores negativos. Explicó el funcionamiento del sensor, su zona muerta y que el −1 es el indicador de "sin lectura" (estado FALLA).
4 Se compartió el enunciado del Challenge #2. Comparó los requisitos con el estado del prototipo e identificó pendientes: falsos positivos, calibración y presión.
5 Definir el 100% del tanque a 2 cm del sensor. Propuso una calibración de dos puntos (distancia vacío/lleno) y recomendó usar las lecturas reportadas por el sensor.
6 El ESP32 no aparece para subir código. Guió el diagnóstico: puerto incorrecto y cable o conexión USB; recomendó usar un cable de datos.
7 El nivel sigue marcando ~60% a 2 cm. A partir de las lecturas, identificó que el sensor se satura en ~3,8 cm; se ajustó DISTANCIA_LLENO_CM = 3.8.
8 Propuesta de distancias para la tabla de calibración. Sugirió 6 distancias que cubren las zonas CRÍTICO, ADVERTENCIA y NORMAL.
9 Integrar el sensor de presión BMP280. Entregó el código con lectura del BMP280, respaldo por altitud y visualización en tablero, LCD y Serial.
10 Conexión del sensor de caudal. Explicó el cableado; ante la falta de resistencias, se probó a 3,3 V y luego a 5 V.
11 El caudal mide mucho menos que el volumen real. Explicó el cálculo del código y que el error dependía del flujo; se calibró el factor con flujo alto (3,88).
12 ADVERTENCIA con nivel normal. Explicó que la disparó la evaporación (radiación de una lámpara) y señaló la limitación de usar radiación instantánea.
13 Revisión de redacción de las secciones 3.2, 3.3 y 3.4. Corrigió ortografía y unidades, reorganizó el contenido (método, resultados, análisis) y señaló imprecisiones técnicas.

Validación de las respuestas

Las sugerencias de la IA se verificaron directamente sobre el prototipo físico: cada cambio de código se compiló, se subió al ESP32 y se comprobó en el Monitor Serial, la LCD y el tablero. Los valores de calibración (DISTANCIA_LLENO_CM, FACTOR_PULSOS_POR_HZ) no los propuso la IA de antemano: salieron de las mediciones del equipo. La IA solo indicó cómo calcularlos.

⚠️ **GitHub.com Fallback** ⚠️