#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP280.h>
#include <DHT.h>
// ==========================================
// CONFIGURACIÓN DE PINES (HARDWARE MAP)
// ==========================================
#define PIN_DHT 4 // Pin digital para el DHT22
#define DHTTYPE DHT22
#define PIN_ANEMOMETRO 14 // Pin con interrupción para sensor IR herradura
#define PIN_PIR 12 // Pin con interrupción para el HC-SR501
#define PIN_REED 13 // Pin digital para sensor magnético de puerta
#define PIN_KEYES_LLUVIA 25 // Pin digital para el sensor de lluvia de la cerradura
#define PIN_TESTIGO_SOLAR 26 // Pin digital para detectar si la placa carga (LOW = Cargando)
// Pines Analógicos (ADC1 - Seguros para usar con Wi-Fi en el ESP32)
#define PIN_MHRD_LLUVIA 32 // Entrada analógica del sensor de lluvia del tejado
#define PIN_BAT_MONITOR 33 // Entrada analógica del divisor de voltaje de la batería
// Dirección I2C de los sensores
#define ADDR_BMP280 0x76 // Dirección estándar del barómetro
#define ADDR_AS5600 0x36 // Dirección fija de fábrica del codificador magnético
// ==========================================
// INSTANCIACIÓN DE OBJETOS Y VARIABLES
// ==========================================
DHT dht(PIN_DHT, DHTTYPE);
Adafruit_BMP280 bmp;
// Variables Globales Volátiles para Interrupciones (ISR)
volatile unsigned long contadorAnemometro = 0;
volatile bool movimientoDetectado = false;
// Variables de Control de Tiempo (No-bloqueante / millis)
unsigned long tiempoAnteriorSensores = 0;
const unsigned long INTERVALO_LECTURA = 3000; // Muestreo cada 3 segundos
// Constantes de Calibración
const float FACTOR_ANEMOMETRO = 2.4; // Ajustar según las ranuras y radio de las cazoletas
const float VOLTAJE_REFERENCIA = 3.3;
const float FACTOR_DIVISOR_BAT = 2.0; // Divisor 1:1 (2 resistencias iguales) reduce el voltaje a la mitad
// ==========================================
// FUNCIONES DE INTERRUPCIÓN (ISR)
// ==========================================
// Guarda esta lógica en la memoria RAM interna (IRAM_ATTR) para máxima velocidad
void IRAM_ATTR ISR_Anemometro() {
contadorAnemometro++;
}
void IRAM_ATTR ISR_PIR() {
movimientoDetectado = true;
}
// ==========================================
// SETUP: CONFIGURACIÓN INICIAL
// ==========================================
void setup() {
Serial.begin(115200);
Serial.println(F("\n============================================="));
Serial.println(F(" INICIALIZANDO SISTEMA VIGÍA DE LA AZOTEA "));
Serial.println(F("=============================================\n"));
// 1. Inicializar el Bus I2C de la placa
Wire.begin();
// 2. Inicializar Sensores I2C y SPI
dht.begin();
if (!bmp.begin(ADDR_BMP280)) {
Serial.println(F("[ERROR] ¡BMP280 no detectado! Revisa cableado I2C."));
} else {
Serial.println(F("[OK] BMP280 inicializado correctamente."));
}
// 3. Configurar Pines Digitales de Entrada
pinMode(PIN_REED, INPUT_PULLUP); // Pull-up para evitar ruidos cuando el imán se aleje
pinMode(PIN_KEYES_LLUVIA, INPUT); // Entrada digital del sensor Keyes
pinMode(PIN_TESTIGO_SOLAR, INPUT_PULLUP);// Pull-up para leer colector abierto del cargador
// 4. Configurar Interrupciones Físicas
pinMode(PIN_ANEMOMETRO, INPUT_PULLUP); // Sensor IR herradura normalmente en alto
attachInterrupt(digitalPinToInterrupt(PIN_ANEMOMETRO), ISR_Anemometro, FALLING);
pinMode(PIN_PIR, INPUT); // El PIR entrega 3.3V directamente al detectar
attachInterrupt(digitalPinToInterrupt(PIN_PIR), ISR_PIR, RISING);
Serial.println(F("[OK] Pines e Interrupciones configuradas con éxito."));
}
// ==========================================
// LOOP: BUCLE PRINCIPAL DE EJECUCIÓN
// ==========================================
void loop() {
// --- Gestión de Eventos en Tiempo Real (Fuera del temporizador) ---
if (movimientoDetectado) {
Serial.println(F("\n[ALERTA INTRUSIÓN] ¡Movimiento detectado en el tejado por el HC-SR501!"));
movimientoDetectado = false; // Resetear bandera
}
// --- Gestión de Lecturas Cíclicas (Muestreo no bloqueante) ---
if (millis() - tiempoAnteriorSensores >= INTERVALO_LECTURA) {
tiempoAnteriorSensores = millis();
Serial.println(F("\n---------------- Lectura de Sensores ----------------"));
// 1. LECTURA DHT22
float humedad = dht.readHumidity();
float tempDHT = dht.readTemperature();
// 2. LECTURA BMP280 (Presión y Altitud Relativa)
float presion = bmp.readPressure() / 100.0F; // Conversión a Hectopascales (hPa)
float tempBMP = bmp.readTemperature();
// 3. CÁLCULO DEL ANEMÓMETRO (Velocidad del viento)
noInterrupts(); // Pausa Crítica: Evita que el contador cambie mientras calculamos
unsigned long pulsosCopiados = contadorAnemometro;
contadorAnemometro = 0; // Resetear para el próximo ciclo
interrupts(); // Reanudamos interrupciones de inmediato
// Convertir pulsos en el intervalo de tiempo a velocidad calculada
float velocidadViento = (pulsosCopiados / (INTERVALO_LECTURA / 1000.0)) * FACTOR_ANEMOMETRO;
// 4. LECTURA ENCODER AS5600 I2C (Dirección del Viento - Veleta)
int anguloVeleta = leerAnguloAS5600();
// 5. SENSORES DE LLUVIA (Tejado y Cerradura)
int lecturaMHRD = analogRead(PIN_MHRD_LLUVIA); // 0 (Inundado) a 4095 (Seco)
int porcentajeLluvia = map(lecturaMHRD, 4095, 1000, 0, 100); // Mapeo inverso a porcentaje
porcentajeLluvia = constrain(porcentajeLluvia, 0, 100); // Forzar límites de seguridad
bool lluviaCerradura = (digitalRead(PIN_KEYES_LLUVIA) == LOW); // LOW significa agua detectada
// 6. SENSOR DE APERTURA (Trampilla / Puerta Azotea)
bool puertaAbierta = (digitalRead(PIN_REED) == HIGH); // HIGH significa imán separado
// 7. ENERGÍA: MONITOR DE BATERÍA Y ESTADO SOLAR
bool cargandoSolar = (digitalRead(PIN_TESTIGO_SOLAR) == LOW); // Si pin baja a tierra, está cargando
float voltajeBateria = calcularVoltajeBateria();
int porcentajeBateria = calcularPorcentajeBateria(voltajeBateria);
// ==========================================
// DESPLIEGUE DE DATOS POR MONITOR SERIE
// ==========================================
Serial.print(F("Temperatura Ambiente: ")); Serial.print(tempDHT); Serial.println(F(" °C"));
Serial.print(F("Humedad Relativa: ")); Serial.print(humedad); Serial.println(F(" %"));
Serial.print(F("Presión Barométrica: ")); Serial.print(presion); Serial.println(F(" hPa"));
Serial.print(F("Temp. Interna Barómetro: ")); Serial.print(tempBMP); Serial.println(F(" °C"));
Serial.print(F("Velocidad Viento: ")); Serial.print(velocidadViento); Serial.println(F(" km/h"));
Serial.print(F("Dirección Viento (Veleta): ")); Serial.print(anguloVeleta); Serial.println(F("°"));
Serial.print(F("Intensidad Lluvia Tejado: ")); Serial.print(porcentajeLluvia); Serial.println(F(" %"));
Serial.print(F("Agua en Cerradura: ")); Serial.println(lluviaCerradura ? F("¡SÍ! RIESGO CORROSIÓN") : F("NO"));
Serial.print(F("Trampilla Azotea: ")); Serial.println(puertaAbierta ? F("ABIERTA (Alerta)") : F("CERRADA"));
// Datos de Energía del Sistema
Serial.print(F("Estado Panel Solar: ")); Serial.println(cargandoSolar ? F("CARGANDO [⚡]") : F("BATERÍA EN DESCARGA [🔋]"));
Serial.print(F("Voltaje Celda: ")); Serial.print(voltajeBateria); Serial.println(F(" V"));
Serial.print(F("Capacidad Batería: ")); Serial.print(porcentajeBateria); Serial.println(F(" %"));
Serial.println(F("-----------------------------------------------------"));
}
}
// ==========================================
// FUNCIONES AUXILIARES DE INGENIERÍA
// ==========================================
// Lee los registros internos del AS5600 a través del bus I2C sin librerías pesadas
int leerAnguloAS5600() {
Wire.beginTransmission(ADDR_AS5600);
Wire.write(0x0C); // Registro de inicio del ángulo en bruto de 12 bits (Raw Angle High Byte)
if (Wire.endTransmission() != 0) return -1; // Error en bus I2C
Wire.requestFrom(ADDR_AS5600, 2); // Pedimos los 2 bytes del ángulo
if (Wire.available() >= 2) {
uint8_t alto = Wire.read();
uint8_t bajo = Wire.read();
int bruto = (alto << 8) | bajo; // Combinar los dos bytes
// Convertir resolución de 12 bits (0 a 4095) a grados geográficos (0 a 359)
return (bruto * 360) / 4096;
}
return -1;
}
// Lee el ADC, compensa la atenuación y calcula el voltaje real antes del divisor
float calcularVoltajeBateria() {
// Tomar un promedio de 10 lecturas analógicas para estabilizar el ruido eléctrico
long sumaADC = 0;
for(int i = 0; i < 10; i++) {
sumaADC += analogRead(PIN_BAT_MONITOR);
delay(2);
}
float promedioADC = sumaADC / 10.0;
// El ESP32 mapea de 0 a 4095 para voltajes de 0 a 3.3V
float voltajePin = (promedioADC * VOLTAJE_REFERENCIA) / 4095.0;
// Multiplicamos por el factor del divisor de tensión para recuperar el valor real de la batería
return voltajePin * FACTOR_DIVISOR_BAT;
}
// Mapeo no lineal aproximado para celdas de Litio de 3.7V (Lipo / 18650)
int calcularPorcentajeBateria(float voltios) {
int porcentaje = 0;
if (voltios >= 4.20) porcentaje = 100;
else if (voltios >= 4.05) porcentaje = 90;
else if (voltios >= 3.96) porcentaje = 80;
else if (voltios >= 3.88) porcentaje = 70;
else if (voltios >= 3.82) porcentaje = 60;
else if (voltios >= 3.78) porcentaje = 50;
else if (voltios >= 3.75) porcentaje = 40;
else if (voltios >= 3.71) porcentaje = 30;
else if (voltios >= 3.65) porcentaje = 20;
else if (voltios >= 3.60) porcentaje = 10;
else if (voltios >= 3.50) porcentaje = 5;
else porcentaje = 0; // Umbral crítico de corte para proteger la química del litio
return porcentaje;
}
No hay comentarios:
Publicar un comentario