(El quinto paso)
Llegado a este punto resulta que tenemos que las funciones son propias de la estructura, al igual que las variables que la compone. Y así como se podía usar los mismos nombres para otras variables que no tenían nada que ver con la estructura, igualmente se puede usar el mismo nombre para una función interna de la estructura que para otra estructura o una función global. Dicho esto, ya no tiene sentido que las funciones se llamen alarma_setup y alarma_loop. El prefijo de alarma_ para mí está de más. Así que las puedo llamar setup y loop, igual que nuestras dos famosas funciones de Arduino, que no entrarán en conflicto ya que, por ejemplo, no sería lo mismo:
loop();
alarma_1.loop();
La primera llamará al loop() de toda la vida, mientras que la segunda llamada lo hará al de la estructura Alarma... Bueno, no es del todo cierto, si dentro de una función de la estructura se llama a la función loop(), se estará llamando a su fución loop(), no a la de Arduino. Para llamar al a función de Arduino, que es "ajena" a la estructura, tendría que llamar a la funcion así:
::loop();
Para más información... exacto, San Google.
Pero mira por dónde, aún no estoy contento del todo. Hay una cosa que tiene el C++ que son los llamados constructores. ¿Qué es eso? San Google, como siempre. Porque por ahora sólo me limitaré a explicar lo básico, muy básico.
A grandes rasgos un constructor es una función que se llama justo en el momento de instanciar el objeto ("instanciar un objeto" es la forma elegante de decir "crear una variable"). Y sirve para inicializar los valores internos a nuestro gusto en el momento de crearse.
¿Y cuándo se crea (instancia) el objeto? Pues cuando la ejecución del programa llega a la definición de la variable (objeto), que si se trata de uno global, se crea antes de ejecutar... Bueno, la función main() a la cual no tenemos acceso en Arduino, y es esta la que llama a setup() y loop(). Así que en el caso de los objetos globales sus constructores se ejecutan antes que el main().
Bueno, después de esta escueta introducción a los constructores (en realidad poco he explicado, así que a Google que vas), decir que los constructores (puede haber más de uno por estructura/clase) se han de llamar igual que la estructura (o clase) y admite parámetros como cualquier otra función. Pero no tienen tipo... y cuando digo que no tienen es que no tienen, ni tan siquiera son de tipo void. Así que como quiero hacer que la función hasta ahora conocida como alarma_setup pase a ser el constructor de la estructura, he de cambiar la declaración:
void alarma_setup(byte pinPulsador, byte pinLed);
Por:
Alarma(byte pinPulsador, byte pinLed);
El void ha desaparecido (repito que nunca el constructor ha de tener tipo).
¿Pero cómo se llama al constructor? Pues el constructor se llama cuando se instancia el objeto, y en este caso (puede haber otros casos) es cuando declaramos el objeto:
struct Alarma alarma_1;
struct Alarma alarma_2;
struct Alarma alarma_3;
Ahí es dónde se declaran las tres estructuras, así que ahí es donde "se llama" al constructor. Si no tuviera parámetros, o vamos a usar valores por defecto para todos sus parámetros, no habría que hacer nada. Pero este no es el caso, así que pongamos los parámetros para el constructor:
struct Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1);
struct Alarma alarma_2(PIN_PULSADOR_2, PIN_LED_2);
struct Alarma alarma_3(PIN_PULSADOR_3, PIN_LED_3);
Por supuesto, las llamada a alarma_setup ya no hace falta en la función setup() de Arduino, así que queda vacía.
El otro cambio que quería hacer es sencillo: cambiar toda aparición de alarma_loop por loop.
El código ahora queda así:
const byte PIN_LED_1 = 3;
const byte PIN_PULSADOR_1 = 4;
const byte PIN_LED_2 = 5;
const byte PIN_PULSADOR_2 = 6;
const byte PIN_LED_3 = 7;
const byte PIN_PULSADOR_3 = 8;
const unsigned long INTERVALO_ALARMA = 5000; // 5 seg
const unsigned long INTERVALO_PARPADEO = 250; // 1/4 seg
enum estado_t { // Definimos este tipo de variable, para controlar la máquina de estados
ESTADO_NO_PULSADO_LED_APAGADO, // No está pulsado y el LED ha de estar apagado
ESTADO_PULSADO_LED_ENCENDIDO, // Está pulsado y el LED ha de estar encendido porque no ha "saltado" la alarma
ESTADO_ALARMA_LED_ENCENDIDO, // Está pulsado y el LED ha de estar encendido habiendo "saltado" la alarma
ESTADO_ALARMA_LED_APAGADO // Está pulsado y el LED ha de estar apagado habiendo "saltado" la alarma
};
struct Alarma {
Alarma(byte pinPulsador, byte pinLed);
void loop();
byte pinPulsador;
byte pinLed;
estado_t estado;
unsigned long instanteUltimoCambioEstado;
};
struct Alarma alarma_1(PIN_PULSADOR_1, PIN_LED_1);
struct Alarma alarma_2(PIN_PULSADOR_2, PIN_LED_2);
struct Alarma alarma_3(PIN_PULSADOR_3, PIN_LED_3);
Alarma::Alarma(byte pinPulsador, byte pinLed) {
this->pinPulsador = pinPulsador;
this->pinLed = pinLed;
this->estado = ESTADO_NO_PULSADO_LED_APAGADO; // Inicialmente se considera que no está pulsado
this->instanteUltimoCambioEstado = 0; // Cuándo se ha realizado el último cambio de estado
pinMode(this->pinPulsador, INPUT);
pinMode(this->pinLed, OUTPUT);
digitalWrite(this->pinLed, LOW); // Inicialmente nos aseguramos de que el LED esté apagado
}
void Alarma::loop() {
if (digitalRead(this->pinPulsador)) {
// Está pulsado
if (this->estado == ESTADO_NO_PULSADO_LED_APAGADO) {
// La máquina de estado dice que hasta ahora no estaba pulsado, así que cambiamos de estado y encendemos el LED
this->estado = ESTADO_PULSADO_LED_ENCENDIDO;
digitalWrite(this->pinLed, HIGH);
this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
}
else if (this->estado == ESTADO_PULSADO_LED_ENCENDIDO) {
// La máquina de estado dice que hasta ahora estaba pulsado, pero no ha pasado el tiempo como para que salte la alarma (hasta ahora)
if ( (millis() - this->instanteUltimoCambioEstado) >= INTERVALO_ALARMA ) {
// Ha igualado o superado el tiempo definido para que "salte" la alarma, así que cambiamos de estado y apagamos el LED
this->estado = ESTADO_ALARMA_LED_APAGADO;
digitalWrite(this->pinLed, LOW);
this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
}
}
else {
// Si ha llegado hasta aquí es porque estal en ESTADO_ALARMA_LED_ENCENDIDO o ESTADO_ALARMA_LED_APAGADO
if ( (millis() - this->instanteUltimoCambioEstado) >= INTERVALO_PARPADEO ) {
// Ha igualado o superado el tiempo definido de parpadeo, así que hay que cambiar de estado
if (this->estado == ESTADO_ALARMA_LED_APAGADO) {
// El LED estaba apagado, así que toca encenderlo
this->estado = ESTADO_ALARMA_LED_ENCENDIDO;
digitalWrite(this->pinLed, HIGH);
}
else {
// El LED estaba encendido, así que toca apagarlo
this->estado = ESTADO_ALARMA_LED_APAGADO;
digitalWrite(this->pinLed, LOW);
}
this->instanteUltimoCambioEstado = millis(); // Guardamos el momento en que hemos cambiado de estado
}
}
}
else {
// No está pulsado
if (this->estado != ESTADO_NO_PULSADO_LED_APAGADO) {
// La máqina de estado a de cambiar de estado y asegurarse de que el LED está apagado
this->estado = ESTADO_NO_PULSADO_LED_APAGADO;
digitalWrite(this->pinLed, LOW);
// Nota, en este caso no nos hace falta recordar en qué momento ha cambiado de estado porque para salir de este estado no depende del tiempo
}
}
}
void setup() {
}
void loop() {
alarma_1.loop();
alarma_2.loop();
alarma_3.loop();
}
Recomiendo buscar más información sobre los constructores. Porque hay muchascosas que aquí no he contado que son "interesantes". Una de ellas es construir los elementos del la estructura/clase invocando directamente a sus respectivos constructores, sin necesidad de usarlos en el cuerpo del constructor (lo que va entre las llaves { }).
Una última cosa (que nunca está de más saber):
int a = 10;
es lo mismo que:
int a(10);
Pero no es lo mismo que:
a = 10;
Y las tres no tienen absolutamente nada que ver con:
a(10);
Ahí lo dejo...