Objetivos
Material requerido
|
Arduino MEGA |
Estoy harto de poner relojes en hora
Sí. Yo sé que esto no es un buen título, pero ayer me saltó el diferencial de casa. Últimamente me ha pasado varias veces, y cuando no es por un exceso de consumo es porque alguien está haciendo obras alrededor.
Al final no solo te saltan los ordenadores ( y tengo muchos creedme), si no que todos los relojes digitales como despertadores, microondas y demás enchufados a la corriente se ponen a cero y hay que volverlos a poner en hora, con mucha paciencia y mentándoles los muertos al anormal que diseñó el sistema de puesta en hora de estos relojes.
Nosotros que somos Makers, sabemos que no es fácil diseñar un sistema de interacción con el usuario a base de botones, pero lo de estos relojes raya en el sadismo.
No me extraña que las personas de una cierta edad sean incapaces de poner en hora un sencillo reloj, sino que nosotros mismos, técnicos competentes en rollos digitales, podemos pasar un mal rato haciéndolo, mientras se nos oye murmurar cosas como ¿Pero quién es el tarado que ha diseñado este sistema? O bien ¡Esta mierda de reloj no va a poder conmigo!
Pero la experiencia demuestra que al final hay que dedicar un tiempo considerable a peregrinar por los varios relojes como alma en pena, y resignarse a pelear con los cacharritos a una velocidad de 15 improperios por minuto.
Y si los cortes de corriente, no son especialmente frecuentes, al menos un par de veces al año hay cambiar todos los relojes de horario de invierno al de verano y viceversa.
Y como esta ultima vez me he hartado, ha llegado el momento de diseñar un reloj como es debido, acorde al momento tecnológico que vivimos y crear un reloj que se ponga en hora solo y no nos maree cada vez que se reinicia, enciende o cambia al horario de verano o invierno.
Debo confesar que esta sesión arranca de un irreprimible exabrupto del ¡Hasta aquí hemos llegado! Vamos a ver como diseñamos un reloj que se ponga en hora el solo y a prueba de impacientes, jubilados e idiotas.
Un reloj que se ajusta sin intervención
Pensemos ¿Cómo hacemos para que el reloj que vamos a diseñar se ponga en hora automáticamente sin intervención manual?
En sesiones previas hemos visto varios ejemplos de cómo crear relojes con nuestros Arduinos e incluso como poner en hora nuestro reloj interno haciendo una llamada a un servidor NTP (Network Time Protocol) en Internet. Parece que tenemos toda las piezas necesarias.
Como estamos usando el Shield WIFI CC3000, tendríamos que adaptarle consulta NTP, pero resulta que hay otro ejemplo de la librería que hace esto precisamente. Formatear el tiempo conseguido no tiene ya secretos para nosotros gracias a la librería time.h.
Solo nos quedaría ajustar el horario de verano/invierno en función de fechas que tampoco debería ser complicado, ya que tiene unas reglas definidas (No creo que vaya a llover, dijo el vecino de Noé)
Y por último buscar un display en el que sacar los datos del modo que más nos guste.
Un programa del reloj automático
Comencemos con el programa para consultar el servidor NTP. La primera parte que es conectar a nuestra WIFI es completamente similar a lo visto en las sesiones previas del CC3000, por lo que iremos rápido:
Incluir las librerías y defines correspondientes, así como el SSID y contraseña de la WIFI:
#include <Adafruit_CC3000.h> #include <SPI.h> #include <Time.h> #define ADAFRUIT_CC3000_IRQ 3 // MUST be an interrupt pin! #define ADAFRUIT_CC3000_VBAT 5 #define ADAFRUIT_CC3000_CS 10 Adafruit_CC3000_Client client; Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT, SPI_CLOCK_DIVIDER); // you can change this clock speed #define WLAN_SSID "charly" // cannot be longer than 32 characters! #define WLAN_PASS "contrase" #define WLAN_SECURITY WLAN_SEC_WPA2
He incluido la Liberia Time, porque seguro que nos vendrá bien para manejar las fechas y horas. La parte de conectar a la WIFI la hemos visto repetidamente con anterioridad
Serial.begin(115200); Serial.print("Shield CC3000!"); Serial.println(" Inicializando..."); if (!cc3000.begin()) { Serial.println("Error"); while(1); } Serial.print("\nConectando a "); Serial.print(WLAN_SSID); if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) { Serial.println("Failed!"); while(1); } Serial.println("...OK!"); /* // Solo es necesario si antes no te has conectado Serial.println("Request DHCP"); while (!cc3000.checkDHCP()) delay(100); // ToDo: Insert a DHCP timeout! */ while (! displayConnectionDetails()) delay(1000);
Dado que estos servicios NTP son gratuitos y puestos amablemente a disposición de la comunidad, conviene no abusar para reducir su carga, y por eso el ejemplo de Adafruit, hace una sola consulta al día.
if(countdown == 0) { // ¿Tiempo agotado? unsigned long t = getTime(); // Query time server if(t) { // OK? lastPolledTime = t; // guardar la hora sketchTime = millis(); // Guardar la hora de la ultima consulta valida countdown = 24*60*4-1; // Reset counter: 24 hours } } else countdown--; unsigned long currentTime = lastPolledTime + (millis() - sketchTime) / 1000;
Usamos countdown, en segundos, para ajustar el reloj una vez cada 24 horas. Cuando llega a cero tocar volver a hacer la consulta al servidor NTP mediante la función getTime().
Si no toca hacer la consulta NTP, calculamos la hora sumando al último tiempo valido, el tiempo transcurrido desde esa consulta mediante el reloj interno de Arduino con millis().
Ahora podemos imprimir la hora actual en la consola por ejemplo
Serial.print("Fecha y hora: "); time_t ajuste = 7200 ; // Ajuste = 2 * 60 * 60 = 7200 segundos formatTime(currentTime + ajuste); delay(1000); // Pause 1 seconds
Ya vimos la función formatTime() en la sesión anterior:
void formatTime( time_t t1) { Serial.print(day(t1)) ; Serial.print(":"); Serial.print(month(t1)) ; Serial.print(":"); Serial.print(year(t1)) ; Serial.print(" / "); Serial.print(hour(t1)) ; Serial.print(":"); Serial.print(minute(t1)) ; Serial.print(":"); Serial.println(second(t1)) ; }
Para la función getTime() he copiado directamente la función del ejemplo de Adafruit (Porque funciona magníficamente) que simplemente nos devuelve el resultado de la consulta al servidor NTP.
Para entender lo que hace, tendríamos que hablar de cómo hacer la consulta al servidorNTP y de cómo es el formato de la respuesta en una trama de texto. Como no es este el objetivo de esta sesión, ignoraremos elegantemente los detalles y la usaremos alegremente, como cualquier otra función, que nos devuelve un Unsigned long representando la hora universal en formato Unix.
La salida de este programa, Prog_113_2 es así:
Ajustando al horario de verano / invierno
Con el programa anterior podemos obtener la hora automáticamente de la WIFI cuando arranca con una precisión bastante aceptable (Como de 1 seg) pero aún nos queda el asunto de ajustar el horario local.
En España son dos horas en verano y una hora en invierno. Y para ajustarlo necesitamos saber cuándo se realiza el cambio.
Pero como las cosas son como son, vamos a ver como se ajusta el horario. Creo recordar que el cambio se hace en el último domingo de marzo a las 3 AM y en el último domingo de octubre a las 2 AM.
Por tanto el programa de ajuste debe decidir si estamos entre estas dos fechas en el año en curso, (Que recibimos en la consulta NTP), y si es así en el caso de España el ajuste al horario de verano es de 2 horas (Porque en España vamos una hora por delante del tiempo universal siempre. Somos así) y si estamos fuera de estas fechas el ajuste de es de solo una hora.
La teoría es (Según creo recordar) que el último domingo de marzo se adelanta el reloj, y a las 2:00 AM serán la 3:00 AM. Y el último domingo de Octubre se retrasa el reloj de modo que a las 3:00 AM pasan a ser las 2:00 AM.
Para hacer un programa que nos consiga esas fechas vamos a dar un pequeño rodeo, porque el convertir caracteres como años y meses en texto o integres a formato fecha es uno de los típicos dolores de muelas en C++ porque depende de donde estas programando, así que vamos a hacer una función de propósito general para ello.
Para manejar las fechas en Arduino disponemos de la nueva librería Time.h, que es muy conveniente pero que para hacer la conversión que queremos nos fuerza a definir una estructura y rellenarla, que como lo vamos a tener que hacer más veces, nos compensa crear una pequeña función:
time_t toTime( int dia, int mes, int anyo, int hora, int minuto, int segundo) { tmElements_t Fecha ; // Estructura para time Fecha.Second = segundo; Fecha.Minute = minuto ; Fecha.Hour = hora ; Fecha.Day = dia ; Fecha.Month = mes ; Fecha.Year = anyo -1970 ; return makeTime(Fecha) ; }
Como veis, es de lo más simple. Rellenamos los valores de la estructura tmElements con los valores numéricos que le pasamos como argumentos. Calcula la fecha y nos la devuelve como respuesta.
El resto, ya no tiene perdida. ¿Cómo sabemos cuál es el último domingo de Marzo?
Fácil. Empezamos en el día 31 y recurrimos a la función weekday():
time_t cambio_1, cambio_2 ; for (int i = 31 ; i >0 ; i--) { cambio_1 = toTime(i, 3, 2015, 2, 0, 0) ; // A las 2 am seran las 3 if ( weekday( cambio_1) == 1 ) // Domingo = 1 break ; }
Fijares que pasamos la hora de las 2AM en que hay que realizar el cambio, para tener un valor fecha / hora completo. Y para el último domingo de Octubre:
for (int i = 31 ; i >0 ; i--) { cambio_2 = toTime(i, 10, 2015, 3, 0, 0) ; // A las 3 am seran las 2 if ( weekday( cambio_2) == 1 ) // Domingo = 1 break ; }
Estas fechas con sus horas nos indican básicamente si el ajuste horario que hacemos es de 1 hora o de dos en España (Los que estáis en otros países corregid este ajuste en función de la costumbre) y básicamente significa que en horario de verano el ajuste es de 2 horas y en el de invierno de 1 hora.
O en lenguaje C++:
if ( fecha_hoy > cambio_1 && fecha_hoy < cambio_2) ajuste = 2 * 3600 ; // dos horas en segundos else ajuste = 1 * 3600 ; // Una hora en segundos currentTime = currentTime + ajuste ; // Ajustamos el tiempo calculado
Podeís montar el programa vosotros mismos sin dificultad.
Conclusión
Ya tenemos el código funcional para conseguir un reloj que se ajusta en hora solo en cuanto arranca. La salida de este programa se envía a la consola, a falta de un mejor destino que lógicamente sería una pequeña pantalla TFT de digamos 3,5” en la que dibujásemos los números digitales o las agujas del reloj y algunos otros ajustes.
Pero como el capítulo se ha ido alargando más de lo previsto, es mejor dejar esto aquí y volver sobre el asunto en otro momento.
He estado peleando con varios displays SPI para hacer pruebas y hasta la fecha me han dejado a la altura del betún porque no he sido capaz de hacerlas funcionar junto el Shield WIFI que también es SPI y que por más que lo intento no he podido evitar que se interfieran.
Estoy aún peleando con los pines de chip select (CS) que se supone son para evitar este tipo de problemas, pero me tiene aburrido, así que también esto ayuda mucho a dejar el tema aquí por ahora.
Resumen de la sesión