// El proyecto que me sirvió de orientación está en esta url:
// https://wokwi.com/projects/402195388533321729
//
//Se programa sin clalses para poder ser modificado y completado por alumnado de cursos de iniciación.
// En el archivo json aparece definido el funcionamiento de los relés, según la ayuda de Wokwi (lo que pone de ayuda para el propio relay):
// Setting the "transistor" attribute to "pnp" inverts the logic: when IN is high, COM is connected to NO, and when IN is low / disconnected, COM is connected to NC.
// Yo tengo relés npn, asique on HIGH se apagan.
#include <Arduino.h>
#include <AiEsp32RotaryEncoder.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
//**********************************************************************************
//PARA LA PARTE DEL RELOJ RCT NTP (fecha y hora leída desde internet):
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-date-time-ntp-client-server-arduino/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
//**********************************************************************************
#include <WiFi.h>
#include "time.h"
#include <ESP32Time.h>
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* ntpServer = "es.pool.ntp.org"; //<-JULIAN : Pongo el NTP de Madrid - España
const long gmtOffset_sec = 3600; //<-JULIAN : Creo que estos dos valores siguientes ajustan al horario de Madrid - España
const int daylightOffset_sec = 3600;
//**********************************************************************************
//PARA LA PARTE DEL ENCONDER ROTATIVO:
//Un posible ejemplo simple de uso de encoder rotativo (que NO UTILIZO) está en:
//https://www.keyestudio.com/blog/how-to-control-rotary-encoder-with-esp32-129
//
//Y digo que no uso lo anterior, porque lo que yo utilizo es la LIBRERIA Ai Esp32 Rotary Encoder:
//https://github.com/igorantolic/ai-esp32-rotary-encoder
//
//**********************************************************************************
/*
connecting Rotary encoder
Rotary encoder side MICROCONTROLLER side
------------------- ---------------------------------------------------------------------
CLK (A pin) any microcontroler intput pin with interrupt -> in this example pin 32
DT (B pin) any microcontroler intput pin with interrupt -> in this example pin 21
SW (button pin) any microcontroler intput pin with interrupt -> in this example pin 25
GND - to microcontroler GND
VCC microcontroler VCC (then set ROTARY_ENCODER_VCC_PIN -1)
***OR in case VCC pin is not free you can cheat and connect:***
VCC any microcontroler output pin - but set also ROTARY_ENCODER_VCC_PIN 25
in this example pin 25
*/
// Definición de pines para el encoder rotativo y el botón
#define ROTARY_ENCODER_A_PIN 36
#define ROTARY_ENCODER_B_PIN 39
#define ROTARY_ENCODER_BUTTON_PIN 34
#define ROTARY_ENCODER_VCC_PIN -1 /* 27 put -1 of Rotary encoder Vcc is connected directly to 3,3V; else you can use declared output pin for powering rotary encoder */
//depending on your encoder - try 1,2 or 4 to get expected behaviour
//#define ROTARY_ENCODER_STEPS 1
//#define ROTARY_ENCODER_STEPS 2
#define ROTARY_ENCODER_STEPS 4
//instead of changing here, rather change numbers above
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, ROTARY_ENCODER_VCC_PIN, ROTARY_ENCODER_STEPS);
//-------------------------------------------------------------------------------------------
//Variables para la lógica de funcionamiento del programa
//-------------------------------------------------------------------------------------------
const byte parameter_length = 4;
//Estos cuatro parámetros se corresponden con las horas de arranque y paro por defecto
const char *parameters[parameter_length] = {
"Inicio [H]",
"Inicio [m]",
"Paro [H]",
"Paro [m]"
};
//Estos cuatro valores se corresponden con los valores por defecto de los parámetros anteriores
byte values[parameter_length] = {
18,
30,
21,
00
};
byte cursor = 0; //Para mover el cursos del menú de selección, desde la posición 0 (arriba por defecto), a la 4
bool selected = false; //Para almacenar si hemos entrado en una de las opciones del menú o no
bool processing = false; //Para almacenar si estamos ejecutando el proceso o no
//Array ordenado para la asignación de los pines para los relés 1 a 16
byte pinCount = 16;
byte relayPins[] = {32, 33, 25, 26, 27, 14, 12, 13, 19, 18, 5, 17, 16, 4, 0, 2};
void rotary_onButtonClick()
{
static unsigned long lastTimePressed = 0; //La variable lastTimePressed se declara estática, por lo tanto su valor se transmite a través de las llamadas a la función. La variable no se inicializa cada vez que se llama a la función.
//ignore multiple press in that time milliseconds
if (millis() - lastTimePressed < 500)
{
return;
}
lastTimePressed = millis();
Serial.print("button pressed ");
Serial.print(millis());
Serial.println(" milliseconds after restart");
if(!selected){
switch (cursor)
{
//Opción para si se pulsa sobre HORA de Inicio
case 0:
rotaryEncoder.setBoundaries(0, 23, true); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
rotaryEncoder.setEncoderValue(values[0]);
break;
//Opción para si se pulsa sobre MINUTO de Inicio
case 1:
rotaryEncoder.setBoundaries(0, 60, true); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
rotaryEncoder.setEncoderValue(values[1]);
break;
//Opción para si se pulsa sobre HORA de Fin
case 2:
rotaryEncoder.setBoundaries(0, 23, true); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
rotaryEncoder.setEncoderValue(values[2]);
break;
//Opción para si se pulsa sobre MINUTO de Fin
case 3:
rotaryEncoder.setBoundaries(0, 60, true); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
rotaryEncoder.setEncoderValue(values[3]);
break;
//Opción para si se pulsa sobre el "BOTÓN STAR/STOP" (cursor en la posición 4)
case 4:
processing = true;
break;
default:
break;
}
}
else{
rotaryEncoder.setBoundaries(0, 4, false); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
rotaryEncoder.setEncoderValue(cursor);
processing = false;
}
selected = !selected;
}
void rotary_loop()
{
//dont print anything unless value changed
if (rotaryEncoder.encoderChanged())
{
if(!processing){
if(!selected){
cursor = rotaryEncoder.readEncoder();
}
else{
values[cursor] = rotaryEncoder.readEncoder();
}
}
Serial.print("Encoder value: ");
Serial.println(rotaryEncoder.readEncoder());
Serial.print("Cursor value: ");
Serial.println(cursor);
}
if (rotaryEncoder.isEncoderButtonClicked())
{
rotary_onButtonClick();
}
}
void IRAM_ATTR readEncoderISR()
{
rotaryEncoder.readEncoder_ISR();
}
// Definición para la pantalla OLED
// Una página sencilla donde comenzar con esta pantalla es esta: https://programarfacil.com/blog/arduino-blog/ssd1306-pantalla-oled-con-arduino/
#define SCREEN_I2C_ADDR 0x3C // or 0x3C
#define SCREEN_WIDTH 128 // Ancho de la pantalla OLED en píxeles
#define SCREEN_HEIGHT 64 // Alto de la pantalla OLED en píxeles
#define OLED_RESET -1 // Pin de reset de la pantalla OLED (no se usa)
/*
// Prototipos de las funciones utilizadas más adelante
bool displayBegin();
void displayUpdate();
void drawIndicators();
void drawParameters();
void drawButton();
void printCtr(const String &buf, int16_t x, int16_t y);
void printRg(const String &buf, int16_t x, int16_t y);
*/
// Inicialización del objeto de la pantalla OLED
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool displayBegin() {
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
bool initialized = display.begin(SSD1306_SWITCHCAPVCC, SCREEN_I2C_ADDR);
if (initialized) {
// display.setRotation(1); //<-Esta línea rota la visualización 90 grados a la izquierda
display.setRotation(0); //<-Esta línea deja la visualización en formato "apaisado"
displayUpdate();
}
return initialized;
}
void displayUpdate() {
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
drawActualTime();
drawIndicators();
drawParameters();
drawButton();
display.display();
}
// 'illustration', 30x52
// https://www.digole.com/tools/PicturetoC_Hex_converter.php
const unsigned char bm_wire [] PROGMEM = {
0x00,0x00,0x00,0x00
,0x1f,0xff,0xff,0xe0
,0x20,0x00,0x00,0x10
,0x40,0x0f,0xc0,0x08
,0x40,0x30,0x30,0x08
,0x40,0xc0,0x0c,0x08
,0x41,0x01,0x02,0x08
,0x42,0x01,0x01,0x08
,0x44,0x01,0x00,0x88
,0x48,0x01,0x00,0x48
,0x48,0x01,0x00,0x48
,0x50,0x01,0x00,0x28
,0x50,0x01,0x00,0x28
,0x50,0x01,0xfe,0x28
,0x50,0x00,0x00,0x28
,0x50,0x18,0xc8,0x28
,0x50,0x24,0xc8,0x28
,0x48,0x42,0xa8,0x48
,0x48,0x42,0xa8,0x48
,0x44,0x24,0x98,0x88
,0x42,0x18,0x99,0x08
,0x41,0x00,0x02,0x08
,0x40,0xc0,0x0c,0x08
,0x40,0x30,0x30,0x08
,0x40,0x0f,0xc0,0x08
,0x20,0x00,0x00,0x10
,0x1f,0xff,0xff,0xe0
,0x20,0x00,0x00,0x10
,0x40,0x0f,0xc0,0x08
,0x40,0x30,0x30,0x08
,0x40,0xc0,0x0c,0x08
,0x41,0x01,0x02,0x08
,0x42,0x01,0x01,0x08
,0x44,0x01,0x00,0x88
,0x48,0x01,0x00,0x48
,0x48,0x01,0x00,0x48
,0x50,0x01,0x00,0x28
,0x50,0x01,0xfe,0x28
,0x50,0x00,0x00,0x28
,0x50,0x63,0xde,0x28
,0x50,0x92,0x10,0x28
,0x49,0x0a,0x10,0x48
,0x49,0x0b,0x9c,0x48
,0x44,0x92,0x10,0x88
,0x42,0x62,0x11,0x08
,0x41,0x00,0x02,0x08
,0x40,0xc0,0x0c,0x08
,0x40,0x30,0x30,0x08
,0x40,0x0f,0xc0,0x08
,0x20,0x00,0x00,0x10
,0x1f,0xff,0xff,0xe0
,0x00,0x00,0x00,0x00
};
// 'lead-indicator', 11x3px
const unsigned char bm_lead [] PROGMEM = {
0x80, 0x20, 0xaa, 0xa0, 0x80, 0x20
};
// 'length-indicator', 44x3px
const unsigned char bm_length [] PROGMEM = {
0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xb0, 0x80, 0x00, 0x00, 0x00,
0x00, 0x10
};
void drawActualTime() {
// Dibujar línea horizontal
display.drawLine(0, 18, display.width(), 18, SSD1306_WHITE);
struct tm timeinfo; //<-La estructura timeinfo del tipo tm viene documentada aquí: https://cplusplus.com/reference/ctime/tm/. Los minutos son tm.tm_min(int 0-59), y las horas tm.tm_hour(int 0-23)
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return;
}
// Dibujar texto tiempo
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("24:24");
}
void drawIndicators() {
display.drawBitmap(0, 0, bm_wire, 30, 52, 1);
}
#define OFFSET 0
void drawParameters() {
for (int i = 0; i < parameter_length; i++) {
int y = OFFSET + (i * 13);
if (cursor == i) {
if (selected) {
display.fillRoundRect(32, y, display.width()-34, 12, 3, SSD1306_WHITE);
display.setTextColor(SSD1306_BLACK);
} else {
display.drawRoundRect(32, y, display.width()-34, 12, 3, SSD1306_WHITE);
}
}
display.setCursor(36, y + 2);
display.print(parameters[i]);
printRg(String(values[i]), 3, y + 2);
if (selected)
display.setTextColor(SSD1306_WHITE);
}
}
void drawButton() {
display.fillRoundRect(2, 52, display.width()-4, 11, 3, SSD1306_WHITE);
if (cursor == parameter_length)
display.drawRoundRect(2, 52, display.width()-4, 11, 3, SSD1306_BLACK);
display.setTextColor(SSD1306_BLACK);
String text = "Run";
if (processing) {
text = "Stop";
}
printCtr(text, 4, 54);
display.setTextColor(SSD1306_WHITE);
}
void printCtr(const String &buf, int16_t x, int16_t y) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calculate width of new string
display.setCursor((x + (display.width() - w) / 2), y);
display.print(buf);
}
void printRg(const String &buf, int16_t x, int16_t y) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calculate width of new string
display.setCursor((display.width() - x - w), y);
display.print(buf);
}
void printLocalTime(){
struct tm timeinfo; //<-La estructura timeinfo del tipo tm viene documentada aquí: https://cplusplus.com/reference/ctime/tm/. Los minutos son tm.tm_min(int 0-59), y las horas tm.tm_hour(int 0-23)
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return;
}
//Imprimimos la fecha y hora obtenida en formato texto legible para a un humano
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
Serial.print("Day of week: ");
Serial.println(&timeinfo, "%A");
Serial.print("Month: ");
Serial.println(&timeinfo, "%B");
Serial.print("Day of Month: ");
Serial.println(&timeinfo, "%d");
Serial.print("Year: ");
Serial.println(&timeinfo, "%Y");
Serial.print("Hour: ");
Serial.println(&timeinfo, "%H");
Serial.print("Hour (12 hour format): ");
Serial.println(&timeinfo, "%I");
Serial.print("Minute: ");
Serial.println(&timeinfo, "%M");
Serial.print("Second: ");
Serial.println(&timeinfo, "%S");
Serial.println("Time variables");
char timeHour[3];
strftime(timeHour,3, "%H", &timeinfo);
Serial.println(timeHour);
char timeWeekDay[10];
strftime(timeWeekDay,10, "%A", &timeinfo);
Serial.println(timeWeekDay);
Serial.println();
/*
//Almaceno la hora y minuto actual leido desde Internet en las variables correspondientes
hour_actual = timeinfo.tm_hour;
min_actual = timeinfo.tm_min;
Serial.println("Hora y minuto obtenidos desde Internet:");
Serial.println(hour_actual);
Serial.println(min_actual);
Serial.println();
*/
}
//-----------------------------------------------------------------------------------------
//Parte relaccionada con las Secuencias de funcionamiento de los relés
//-----------------------------------------------------------------------------------------
//Por la forma en la que lo programo, necesito saber en que ciclo de secuencia me encuentro
byte cicloSecuencia = 1;
//La frase "FELIZ NAVIDAD" consta de 12 letras, por eso estas secuencias hacen bucles sólo con los 12 primeros relés, que dando los 4 restantes hasta 16 sin uso
byte numRelesSecuencia = 12;
//El número de relé no tiene nada que ver con el numero de pin. Por ej el relé 1 lo gobierna el pin 32.
byte thisRele = 0;
//Para las secuencias 3 y 4 necesito memorizar un valor de relé que uso en cada pasada. Comienzo en 12
byte memoriaRele = 12;
//Esta función contiene la llamada a las secuencias posteriores, en el orden y con la repetición que deseemos
void secuencias(){
switch (cicloSecuencia) {
case 1:
if(secuencia_1()){
cicloSecuencia++;
}
break;
case 2:
if(secuencia_1()){
cicloSecuencia++;
}
break;
case 3:
if(secuencia_2()){
cicloSecuencia++;
}
break;
case 4:
if(secuencia_2()){
cicloSecuencia++;
}
break;
case 5:
if(secuencia_4()){
cicloSecuencia++; //Apagamos relés
}
break;
case 6:
if(secuencia_3()){
cicloSecuencia++;
}
break;
case 7:
secuencia_5();
cicloSecuencia++;
break;
case 8:
secuencia_6();
cicloSecuencia++;
break;
case 9:
secuencia_5();
cicloSecuencia++;
break;
case 10:
secuencia_6();
cicloSecuencia++;
break;
case 11:
if(secuencia_7()){
cicloSecuencia++;
}
break;
case 12:
if(secuencia_7()){
cicloSecuencia++;
}
break;
case 13:
secuencia_5();
cicloSecuencia++;
break;
case 14:
secuencia_6();
cicloSecuencia = 1;
break;
default:
break;
}
}
//Secuencia 1 - Invierte cada relé de forma secuencial (empieza por el relé 1 hasta numRelesSecuencia)
bool secuencia_1(){
byte thisPin = relayPins[thisRele];
digitalWrite(thisPin, !digitalRead(thisPin));
thisRele++;
if(thisRele >= numRelesSecuencia){
thisRele = 0;
return true;
}
return false;
}
//Secuencia 2 - Invierte cada relé de forma aleatoria (lo hace una cantidad de veces igual a numRelesSecuencia, pero al ser aleatorio, dará el efecto que sea)
bool secuencia_2(){
//Para generar número aleatorio, se usa la función random, que tiene dos sintaxis posibles:
// -random(max) ==> random value from 0 to max: upper bound of the random value, exclusive
// -random(min, max) ==> min : lower bound of the random value inclusive, max: upper bound of the random value, exclusive
byte thisPin = relayPins[random(numRelesSecuencia)];
digitalWrite(thisPin, !digitalRead(thisPin));
thisRele++;
if(thisRele >= numRelesSecuencia){
thisRele = 0;
return true;
}
return false;
}
//Secuencia 3
bool secuencia_3(){
byte thisPin = relayPins[thisRele];
if(digitalRead(thisPin)){
digitalWrite(thisPin,LOW);
}
else{
digitalWrite(thisPin,HIGH);
thisRele++;
}
if(thisRele == memoriaRele){
digitalWrite(thisPin,LOW);
thisRele = 0;
memoriaRele--;
if(memoriaRele == 1){
memoriaRele = 12;
return true;
}
}
return false;
}
//Secuencia 4 - Apagado no instantáneo sino secuencial y con la cadencia y ritmo del resto
bool secuencia_4(){
byte thisPin = relayPins[thisRele];
digitalWrite(thisPin, HIGH); //<-HIGH apagan (modo npn)
thisRele++;
if(thisRele >= numRelesSecuencia){
thisRele = 0;
return true;
}
return false;
}
//Secuencia 5 - Encendio instantáneo de todos
void secuencia_5(){
for(byte i = 0; i < numRelesSecuencia; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,LOW); //<-LOW encienden (modo npn)
}
}
//Secuencia 6 - Apagado instantáneo de todos
void secuencia_6(){
for(byte i = 0; i < numRelesSecuencia; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,HIGH); //<-HIGH apagan (modo npn)
}
}
//Secuencia 7 - Enciende la palabra FELIZ (5 primeros relés), y luego la palabra NAVIDAD (7 siguientes relés), y luego todo
bool secuencia_7(){
switch (thisRele) {
case 0:
for(byte i = 0; i < 5; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,LOW); //<-LOW encienden (modo npn)
}
for(byte i = 5; i < 12; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,HIGH); //<-HIGH apagan (modo npn)
}
break;
case 1:
for(byte i = 0; i < 5; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,HIGH); //<-HIGH apagan (modo npn)
}
for(byte i = 5; i < 12; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,LOW); //<-LOW encienden (modo npn)
}
break;
case 2:
for(byte i = 0; i < 5; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,LOW); //<-LOW encienden (modo npn)
}
for(byte i = 5; i < 12; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,HIGH); //<-HIGH apagan (modo npn)
}
break;
case 3:
for(byte i = 0; i < 5; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,HIGH); //<-HIGH apagan (modo npn)
}
for(byte i = 5; i < 12; i++){
byte thisPin = relayPins[i];
digitalWrite(thisPin,LOW); //<-LOW encienden (modo npn)
}
break;
default:
break;
}
thisRele++;
if(thisRele >= 4){
thisRele = 0;
return true;
}
return false;
}
void initSecuenciasReles(){
//Inicialización general de las variables relaccionadas con la ejecución de las secuencias de relés
cicloSecuencia = 1;
numRelesSecuencia = 12;
thisRele = 0;
memoriaRele = 12;
for (byte thisPin = 0; thisPin < pinCount; thisPin++) {
digitalWrite(relayPins[thisPin], HIGH);
}
}
//-----------------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
//we must initialize rotary encoder
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
//set boundaries and if values should cycle or not
//in this example we will set possible values between 0 and 1000;
bool circleValues = false;
rotaryEncoder.setBoundaries(0, 4, circleValues); //minValue, maxValue, circleValues true|false (when max go to min and vice versa)
/*Rotary acceleration introduced 25.2.2021.
* in case range to select is huge, for example - select a value between 0 and 1000 and we want 785
* without accelerateion you need long time to get to that number
* Using acceleration, faster you turn, faster will the value raise.
* For fine tuning slow down.
*/
rotaryEncoder.disableAcceleration(); //acceleration is now enabled by default - disable if you dont need it
//rotaryEncoder.setAcceleration(250); //or set the value - larger number = more accelearation; 0 or 1 means disabled acceleration
if(!displayBegin()) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.setTextSize(1); //<-1 por defecto. Valor 2 o 3 va agrandando el tamaño (no permite decimales)
// Connect to Wi-Fi
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
// Init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime();
//disconnect WiFi as it's no longer needed
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
//Declaramos cada pin del array de relés como salidas
//y los inicializamos a nivel alto (HIGH) para apagarlos (modo npn)
for (byte thisPin = 0; thisPin < pinCount; thisPin++) {
pinMode(relayPins[thisPin], OUTPUT);
digitalWrite(relayPins[thisPin], HIGH);
}
}
ESP32Time rtc(0); // offset in seconds (GMT+1 Madrid - España)
void loop() {
//in loop call your custom function which will process rotary encoder values
rotary_loop();
if(processing){
int minInicio = values[0] * 60 + values[1]; //Hora de inicio programada, pero puesto en minutos
int minFin = values[2] * 60 + values[3]; //Hora de fin programada, pero puesto en minutos
int minActual = rtc.getHour(true) * 60 + rtc.getMinute(); // Hora actual pero puesto en minutos. getHour() -> (1-12) ; getHour(true) -> (0-23)
if(minActual >= minInicio && minActual < minFin ){
secuencias();
}
else{
initSecuenciasReles();
}
}
else{
initSecuenciasReles();
}
displayUpdate();
delay(50); // Pequeña pausa para evitar lecturas erráticas
}
Relé 1
Relé 2
Relé 3
Relé 4
Relé 5
Relé 6
Relé 7
Relé 8
Relé 9
Relé 10
Relé 11
Relé 12
Relé 13
Relé 14
Relé 15
Relé 16
CONTROLADORA - Luces Navidad IES Fernández VALLÍN