bclose

Relojes y Network Time Protocol NTP

Diseñando un reloj que se pone en hora mediante WIFI

Objetivos

 

 

    • Jugar con el WIFI Shield CC3000.
    • Recordar o descubrir los servicios NTP.
    • Consultar un servidor NTP para sincronizar nuestro reloj en Arduino.
    • Crear un programa completo a partir de esta sincronización.

 

Material requerido.

Version R3

Arduino MEGA.

 

Shield basado en CC3000

Un WIFI Shield basado en el chip Texas Instruments CC3000

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.

 
  • Para los que vienen a por nota, recordad que como vimos en la sesión sobre el shield Ethernet Sincronizando la hora en Internet, las consultas NTP se hacen mediante una petición UDP, que devuelve una serie de datos que podemos montar como el Unix time. 

La salida de este programa, Prog_113_2   es así:

 

Consola Arduino

 

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.

 
  • Para los que crean que esta no es una precisión espectacular, recordad que cuando ponéis en hora los relojes digitales solemos usar el reloj de muñeca, lo que no es muy diferente de poner el reloj en hora por el periódico. 

En España son dos horas en verano y una hora en invierno. Y para ajustarlo necesitamos saber cuándo se realiza el cambio.

 
  • No tengo información de cómo se realizan estos cambios en otros países, o siquiera si se hace el ajuste de horario en absoluto, pero seguro que cada uno sabéis como corresponde a vuestro país.
  • Tampoco tengo nada claro que este cambio, cuya justificación oficial es el ahorro de energía en invierno, tenga una utilidad real. Probablemente se debe más a que existen una tropa de funcionarios sin otra cosa que hacer que justificar su inútil existencia mediante este molesto cambio.
  • Nunca he conocido a nadie con conocimiento capaz de justificar razonadamente este tema, más allá de las vaguedades habituales bastante fáciles de rebatir . 

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.

 
  • Por si alguno, os perdisteis la sesión de manejo de fechas con la librería Time.h, puede ser bueno darla un repasito aquí: La nueva librería Time. . 

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 ;
   }
 
  • Ojo con esto. En la documentación de Arduino dice que el domingo es el primer día de la semana y su valor ordinal es 0. Sin embargo a mí la función weekday me devuelve valores de 1 a 7 y no de 0 a 6 como dice el manual. 

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

 

 

    • Hemos visto como hacer una consulta NTP mediante el Shield WIFI basado en CC3000.
    • Recordamos como manejar fechas mediante la libreria Time.
    • Montamos el esqueleto de un reloj que se sincroniza solo sin necesidad de intervencion exterior.