#include <EEPROM.h>
#include <WiFi.h>
#include <PubSubClient.h>
#define EEPROM_SIZE 110
#define sensor_distance 10
//GPIO y canal PWM. Ajustar bien los pines al esp32!!!!
#define pin_temp A0 // Potenciometro de temperatura (el wifi puede llegar a interferir con algunos pines ADC)
#define pin_lights 22 // LED de iluminación (PWM, cualquier GPIO con OUTPUT puede) //23
#define lights_ch 0 // Canal PWM para las luces (acá se mandan los comandos PWM)
#define pin_pump 15 // LED de la bomba
#define pump_button 23 // boton de control de la bomba
#define TRIGGER 32 // Trigger ultrasonido
#define ECHO 33 // Echo ultrasonido
#define pin_dose A3 // Potenciometro de dosis de cloro
//Posiciones de la EEPROM, los params se definen desde 100 en adelante (el ID está en la posicion 0 y no debe tener mas de 99 caracteres)
#define pos_width 100 //Ancho, largo y altura deben ser enteros (Para poder guardarlos en la EEPROM), entre 0 y 255
#define pos_length 101
#define pos_height 102
#define pos_pumptimer 103 //Timer de la bomba, en segundos (0 sin timer)
//-----------------------------------Topics------------------------------------------------
//Topics de estado y usuario
#define t_temp "user/temperatura" //Solo escritura (respecto a la placa) Return: temperatura (int)
#define t_dose "user/dosis" //Solo escritura Return: Valor de dosis
#define t_lights "user/luces" //Solo lectura
#define t_webpump "user/bomba" //Lectura y Escritura Recibe: ON-OFF (String)
#define t_wlevel "user/nivel-agua" //Solo escritura Return: porcentaje (int)
#define t_message "user/messages" //Mensajes en general (logs), solo escritura
#define t_alarm "status/levelalarm" //Solo escritura Return: ACTIVATE (String)
#define t_delay "status/delay" //Solo lectura, muestreo Recibe: segundos en milis 1seg = 1000
#define t_start "status/start" //Solo lectura Recibe: ON-OFF
//Configuraciones EEPROM (Esritura)
#define w_name "config/name" //Solo lectura
#define w_width "config/ancho" //Solo lectura
#define w_length "config/largo" //Solo lectura
#define w_height "config/altura" //Solo lectura
#define w_timer "config/pumptimer" //Solo lectura
//------------------------------Timers, Interrupts, Variables----------------------------------------
int current_timer = 0;
int lights = 0;
boolean pump_running = false;
boolean pump_manual = false;
boolean defrost_mode = false;
int water_level = 0; //Porcentaje de agua
int sample_delay = 2000;
float prev_dose = 0;
boolean system_start = false;
//------------------------Configuración de la red Wi-Fi--------------------------------------
//const char* ssid = "Personal 200 2.4 GHz";
//const char* password = "01427234780307";
const char* ssid = "Wokwi-GUEST";
const char* password = NULL;
//------------------------Configuración del broker MQTT---------------------------------------
//const char* mqtt_server = "broker.hivemq.com";
//const char* mqttUser = "Martin";
//const char* mqttPassword = "123456";
const char* mqtt_server = "test.mosquitto.org";
const char* mqttUser = NULL;
const char* mqttPassword = NULL;
WiFiClient espClient;
PubSubClient client(espClient);
//------------------------------Funciones de la piscina------------------------------------
void setDelay(int delay); //Establece el tiempo entre muestras
void setStart(String msg);
void sendmessage(String msg); //Manda mensajes en formato Log
void eepromloader(String param, String value); //Escritura en EEPROM
void eepromread(String param); //Lectura de parametros en EEPROM
String GetName(); //Obtener nombre en EEPROM
void SetName(String name); //Cambio de Nombre en EEPROM
void UpdateTemp(); //Verifica temperatura, activa modo descongelamiento
void LowTempDefrost(); //Modo descongelamiento a baja temperatura
void SetLights(int intensity); //Control de las luces
void PoolLoad(); //Verifica nivel de agua respecto a la capacidad --- Agregar alarma ---
int PoolCap(); //Obtiene capacidad de la piscina
float WaterLevel(); //Obtiene cantidad de agua en la piscina
void SwitchPump(boolean run); //Cambia el estado de la bomba encendido/apagado, activa timers si aplica
void manualPumpControl(String pos, String source); //Activa la bomba desde web o interruptor
void onTimer(); //Activa Timer para la bomba
void button_interrupt(); //Permite interrumpir el timer si se desactiva la bomba antes
void setup() {
EEPROM.begin(EEPROM_SIZE);
//WIFI y MQTT
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
//Pins y timers
//ledcAttachPin(pin_lights, lights_ch); //Conecta LED al canal para control PWM
//ledcSetup(lights_ch, 1000, 8); //1Khz frecuencia, rango de 8 bits (0-255)
pinMode(pump_button, INPUT_PULLUP);
pinMode(TRIGGER, OUTPUT);
pinMode(ECHO, INPUT);
pinMode(pin_pump, OUTPUT);
pinMode(pin_temp, INPUT);
pinMode(pin_dose, INPUT);
}
void loop() {
//MQTT
if (!client.connected()) {
reconnect();
}
client.loop();
//Actualizacion de estado
if (system_start){
UpdateTemp();
UpdateDose();
PoolLoad();
if (digitalRead(pump_button) == LOW)
button_interrupt();
if (pump_running && !defrost_mode && current_timer > 0){
if (sample_delay > 0)
current_timer -= sample_delay;
else
current_timer -= 100;
if (current_timer <= 0){
onTimer();
}
}
if (defrost_mode)
current_timer = 1000;
}
//Delay
delay(sample_delay);
}
void setup_wifi() { //Establece conexión WIFI
delay(10);
Serial.println();
Serial.print("Conectando a ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi conectado");
Serial.println("Direccion IP: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* message, unsigned int length) { //Recibe mensajes e imprime en serial
Serial.println("Mensaje recibido en topic: ");
Serial.print(topic);
Serial.print(". Mensaje: ");
String messageTemp;
for (int i = 0; i < length; i++) {
Serial.print((char)message[i]);
messageTemp += (char)message[i];
}
Serial.println();
//Invocaciones
if ((String(topic) == (t_lights)) && system_start){
SetLights(messageTemp.toInt());
lights = messageTemp.toInt();
}
else
if ((String(topic) == (t_webpump)) && system_start)
manualPumpControl(messageTemp, "Aplicacion Web");
else
if (String(topic) == (t_delay))
setDelay(messageTemp.toInt());
else
if (String(topic) == (t_start))
setStart(messageTemp);
else
if (String(topic) == (w_name))
eepromloader("ID", messageTemp);
else
if (String(topic) == (w_width))
eepromloader("WIDTH", messageTemp);
else
if (String(topic) == (w_length))
eepromloader("LENGTH", messageTemp);
else
if (String(topic) == (w_height))
eepromloader("HEIGHT", messageTemp);
else
if (String(topic) == (w_timer))
eepromloader("TIMER", messageTemp);
}
void reconnect() { //Conecta a MQTT y reconecta si la conexion falla
while (!client.connected()) {
Serial.print("Intentando conectar a MQTT...");
if (client.connect("Martin", mqttUser, mqttPassword )) {
Serial.println("Conectado");
//client.subscribe("test/topic");
setup_subs();
} else {
Serial.print("Error, rc=");
Serial.print(client.state());
Serial.println(" Reintentando en 5 segundos ");
delay(5000);
}
}
}
void setup_subs(){
client.subscribe(t_lights, 1);
client.subscribe(t_webpump, 1);
client.subscribe(t_delay, 1);
client.subscribe(t_start, 1);
client.subscribe(w_name, 1);
client.subscribe(w_width, 1);
client.subscribe(w_length, 1);
client.subscribe(w_height, 1);
client.subscribe(w_timer, 1);
}
void setStart(String msg){
if ((msg == "ON") && !system_start){
system_start = true;
sendmessage("Sistema activado");
SetLights(lights);
}
else
if (msg == "OFF" && system_start){
if (defrost_mode){
sendmessage("Desactivando modo descongelamiento");
defrost_mode = false;
if (!pump_manual) //Desactivar bomba si se activo automaticamente, si fue manual sigue
SwitchPump(false);
}
if (pump_manual){
pump_manual = false;
SwitchPump(false);
}
SetLights(0);
system_start = false;
sendmessage("Sistema desactivado");
}
}
void setDelay(int delay){
sample_delay = delay;
sendmessage("Tiempo entre muestras cambiado a: " + String(delay) + " milisegundos");
}
void sendmessage(String msg) { //Usar para logs
int minutos = millis()/60000;
int horas = minutos / 60;
int segundos = (millis()/1000) % 60;
String tiempo = timeFormat(horas) + ":" + timeFormat(minutos) + ":" + timeFormat(segundos);
String log = ("Time: " + tiempo + " ---- Evento: " + msg);
client.publish((t_message), log.c_str());
Serial.println(log);
delay(1000);
}
String timeFormat(int val){
if (val < 10)
return ("0" + String(val));
else
return String(val);
}
void eepromloader(String param, String value){ // Carga valores en EEPROM
param.toUpperCase();
if (param == "ID"){
if ((value.length() < 100) && (value.length() > 0)){
SetName(value);
sendmessage("Nombre cambiado a: " + value);
}
else
sendmessage("Error: No se puede cambiar nombre, debe ser menor a 100 caracteres");
}
else
if (param == "WIDTH" && (value.toInt() >= 0) && (value.toInt() < 256)){ //EEPROM en esp32 es Flash, no hay update()
EEPROM.write(pos_width, (byte)value.toInt());
EEPROM.commit();
sendmessage("Ancho cambiado a: " + value);
}
else
if (param == "HEIGHT" && (value.toInt() >= 0) && (value.toInt() < 256)){
EEPROM.write(pos_height, (byte)value.toInt());
EEPROM.commit();
sendmessage("Altura cambiada a: " + value);
}
else
if (param == "LENGTH" && (value.toInt() >= 0) && (value.toInt() < 256)){
EEPROM.write(pos_length, (byte)value.toInt());
EEPROM.commit();
sendmessage("Largo cambiado a: " + value);
}
else
if (param == "TIMER" && (value.toInt() >= 0) && (value.toInt() < 256)){
EEPROM.write(pos_pumptimer, (byte)value.toInt());
EEPROM.commit();
if (value.toInt() != 0)
sendmessage("Timer cambiado a: " + value + " segundos");
else
sendmessage("Timer desactivado");
}
else
if ((value.toInt() < 0) && (value.toInt() >= 256))
sendmessage("Error: El valor ingresado debe ser mayor a 0 y menor a 256");
else
sendmessage("Error: Parametro inexistente");
}
String GetName(){ //Valor en posicion 0 es longitud del nombre
int index = 1;
int length = EEPROM.read(0);
String cad = "";
while (index <= length){
cad += (char)EEPROM.read(index);
index++;
}
return cad;
}
void SetName(String name){ //Valor en posicion 0 es longitud del nombre
int length;
length = name.length();
EEPROM.write(0,length);
int i = 0;
while (i < length){
EEPROM.write(i+1,name.charAt(i));
i++;
}
EEPROM.commit();
}
void UpdateTemp(){
int info = analogRead(pin_temp);
int temp = map(info, 0, 4095, -20, 50);
client.publish(t_temp, String(temp).c_str());
if (temp <= 0){
if (!defrost_mode){
defrost_mode = true;
sendmessage("Temperatura muy baja, activando modo de descongelamiento");
LowTempDefrost(); //Activar bomba si la temperatura baja
}
}
else
if (defrost_mode){
sendmessage("Desactivando modo descongelamiento");
defrost_mode = false;
if (!pump_manual) //Desactivar bomba si se activo automaticamente, si fue manual sigue
SwitchPump(false);
}
}
void UpdateDose(){
int info = analogRead(pin_dose);
int val = map(info, 0, 4095, 5, 30);
float dose = val/10.0;
client.publish(t_dose, String(dose).c_str());
if (prev_dose != dose){
sendmessage("Dosificación cambiada a " + String(dose) + " ppm");
prev_dose = dose;
}
}
void LowTempDefrost(){
if (!pump_running)
SwitchPump(true);
}
void SetLights(int intensity){
if (intensity >= 0 && intensity <= 100){
int light_value = map(intensity, 0, 100, 0, 255);
ledcWrite(lights_ch, light_value);
if (intensity != 0)
sendmessage("Luces ajustadas a intensidad: " + String(intensity) + "%");
else
sendmessage("Luces desactivadas");
}
else
sendmessage("Error: El valor de intensidad de la luz debe estar entre 0 y 100");
}
void PoolLoad(){ //Asume que el sensor se encuentra algunos cm por encima del nivel máximo
long duration;
int distance;
int percentage;
int w_distance;
digitalWrite(TRIGGER, LOW); //Asegurarse de limpiar la señal
delayMicroseconds(2);
digitalWrite(TRIGGER, HIGH); //Trigger por 10 ms
delayMicroseconds(10);
digitalWrite(TRIGGER, LOW); //Desactivar Trigger
duration = pulseIn(ECHO, HIGH); //Activar Echo
distance = duration*0.034/2;
if (distance < 10)
distance = 10;
w_distance = map(distance, 10, EEPROM.read(pos_height)+sensor_distance, 0, 100); //Altura máxima 245cm + 10 del sensor
percentage = 100 - w_distance;
if (percentage < 0)
percentage = 0;
client.publish(t_wlevel, String(percentage).c_str());
//Alarma de nivel
if ((water_level - percentage) >= 10){ //Alarma al bajar 10% o más
client.publish(t_alarm, "ACTIVATE");
sendmessage("Atencion: El nivel de agua en la piscina está reduciéndose");
water_level = percentage;
}
if (water_level < percentage) //Si la pileta sube de nivel se reemplaza
water_level = percentage;
}
void SwitchPump(boolean run){
if (run){
if(!pump_running){
if ((byte)EEPROM.read(pos_pumptimer) > 0 && !defrost_mode){
current_timer = EEPROM.read(pos_pumptimer)*1000;
sendmessage("Encendiendo Timer: " + String(EEPROM.read(pos_pumptimer)) + " segundos");
}
digitalWrite(pin_pump, HIGH);
pump_running = true;
client.publish(t_webpump, "ON");
sendmessage("La bomba entro en funcionamiento");
}
}
else
if (!defrost_mode){
current_timer = 0;
digitalWrite(pin_pump, LOW);
pump_running = false;
client.publish(t_webpump, "OFF");
sendmessage("La bomba se ha desactivado");
}
else{
sendmessage("Modo descongelamiento activado, la bomba se mantendra encendida");
client.publish(t_webpump, "ON");
current_timer = 0;
}
}
void manualPumpControl(String pos, String source){
if(pos == "OFF"){
if(pump_running){
sendmessage("Apagado manual de bomba, Origen: " + source);
pump_manual = false;
SwitchPump(false);
}
}
else
if (pos == "ON"){
if (!pump_running){
sendmessage("Encendido manual de Bomba, Origen: " + source);
pump_manual = true;
SwitchPump(true);
}
}
}
void onTimer(){
sendmessage("Temporizador cumplido, desactivando bomba");
SwitchPump(false);
}
void button_interrupt(){
if (pump_running)
manualPumpControl("OFF", "Switch de bomba");
else
manualPumpControl("ON", "Switch de bomba");
}