Sincronizando la Hora en Internet

Objetivos

 

  • El protocolo NTP.
  • Sincronizar la hora mediante un servidor NTP.
  • Mostrar un ejemplo de uso que podamos reutilizar en el futuro.
  •  

    Material requerido

     

    Vista principal Un NodeMCU con ESP8266
    vista lateral del esp32 Un ESP32

    El protocolo NTP

     

    La sincronización entre los relojes de diferentes sistemas informáticos, es una de esas cuestiones que recurrentemente vuelven a la palestra, cada poco tiempo, como uno de esos monstruos de películas malas, que no hay manera de matar, hagas lo que hagas.

    Una cuestión que en principio parece sencilla, se puede complicar de formas insospechadas, porque al final todo es una cuestión de la precisión con la que necesitemos esa sincronización.

    Para poner un poco de orden en este asunto y a raíz de la popularización de Internet ha ido surgiendo una norma de sincronización en red que llamamos NTP pos sus siglas de Network Time Protocolo, o Protocolo de Tiempo en Red.

    Sin entrar en grandes detalles (Que se me escapan), diremos que la idea básica es un conjunto de relojes en Internet sincronizados con un reloj de máxima precisión (Como GPS o reloj atómico) que llamaremos de estrato 1.

    A su vez este grupo sincroniza a un segundo grupo de relojes de menor nivel que llamaremos de estrato 2 y así sucesivamente hasta llegar a los estratos de gran público (o sea a nosotros).

    La ventaja de este sistema, es que todos los relojes se sincronizan contra una hora estándar central y los errores que se generan no serán acumulativos.

    Como la idea de fondo es enviar un mensaje solicitando la hora a un servidor central, que nos responde con la hora exacta… de cuando envió el mensaje, se pueden hacer ajustes calculando este retraso para mayor exactitud del valor, pero en la mayor parte de las aplicaciones civiles, basta con una precisión de algunos milisegundos (Porque si se repite la sincronización periódicamente, no habrá deriva de la hora, evitando la acumulación de errores).

    Por eso, para la mayor parte de las aplicaciones normales, en las que podemos ignorar estas pequeñas diferencias, se desarrolló un protocolo simple de NTP que se llamó SNTP, con la S de simple, y que es el que más uso general tiene, pues evita la complicación de calcular los retrasos estadísticos y su corrección, y se conforma con el tiempo que recibe.

    Destacar, aquí, que el protocolo NTP y SNTP se implementa en UDP que un protocolo de transporte diferente a TCPIP, que también corre sobre Ethernet ( Y que por supuesto, nuestro Arduino soporta con una librería).

    Las firmas de tiempo que se usan en NTP, son de 32 bits indicando la parte entera en segundos desde  el 1 de Enero de 1900, y una parte fraccionaria también de 32 bits. Por ello la resolución teórica de NTP sería de 232 segundos =136 años, y una resolución teórica de 2-32 segundos, o sea 0,233 nanosegundos.

    La norma define una colección de mensajes para interrogar a un servidor NTP y especifica las respuestas de vuelta. Utilizaremos esta norma para poner en hora nuestro humilde Arduino a través de una petición UDP a un servidor NTP.

    La norma define también, una serie de nombres de servidores NTP que pueden ser cambiantes para garantizar su integridad, pero para nuestro Sketch recurriremos a algo un poco más sencillo, usando una dirección IP del servidor a sabiendas de que en algún momento podría no estar activo. Por eso incluiremos una lista de varios servidores en USA y España.

    Arduino incluye un ejemplo de NTP en la sección de ejemplos Ethernet llamado UDPNtpClient, pero me temo que no funciona, (o por lo menos yo no he sido capaz de hacerlo funcionar),

  • Parece un problema de las versiones del IDE por lo que he podido averiguar, pero no he encontrado la forma de resolverlo.
  •  

    Así que buscando por Internet, encontré una versión que si funciona, que será la que usaremos en esta sesión, porque estoy seguro de que nos vendrá bien más de una vez en cualquier proyecto que incluya una tarjeta Ethernet.
     

    El programa de interrogación NTP

     

    Aquí tenéis el programa completo que usaremos:

    Prog_67_1

    Para empezar, necesitamos tener nuestro Arduino con conexión a Internet mediante un shield Ethernet, y después incluir algunas librerías.

    #include <SPI.h>
    #include <Ethernet.h>
    #include <EthernetUdp.h>

    Ya estamos habituados a las dos primeras, pero es la primera vez que usamos la librería UDP, por requerimiento del Protocolo NTP.

    byte mac[] = {  0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD};
    unsigned int localPort = 80;            //  Puerto local para escuchar UDP
    
    IPAddress timeServer(193,92,150,3);       // time.nist.gov NTP server (fallback)
    //IPAddress timeServer(130,206,3,166);    // Red Iris Bilbao
    //IPAddress timeServer(130,206,206,248);  // Red Iris Madrid

    Usaremos el primer servidor, que parece que funciona sin problemas, aunque también he probado los otros dos sin incidentes y los pongo más por si acaso que por otra razón ( Ya se sabe que los ordenadores los carga el diablo).

    const int NTP_PACKET_SIZE= 48;   // La hora son los primeros 48 bytes del mensaje
    byte packetBuffer[ NTP_PACKET_SIZE];  // buffer para los paquetes
    
    EthernetUDP Udp;       // Una instancia de UDP para enviar y recibir mensajes

    Definimos el tamaño de los paquetes y un buffer  para recibirlos y por fin creamos una instancia de un puerto UDP para el tráfico de mensajes.

    En cuanto al setup, simplemente iniciamos el puerto serie para sacar mensajes y nos aseguramos de que Ethernet se ha configurado adecuadamente antes de seguir, y por ultimo inicializamos el puerto UDP con begin.

    void setup()
       { 
           Serial.begin(9600);
           if (Ethernet.begin(mac) == 0)
               {   Serial.println("Fallo al configurar por DHCP");
                   while(true);         // No sigas
               }
           Udp.begin(localPort);
        }

    Cuando entramos al loop, imprimimos la IP del servidor NTP activo ( Por si acaso) e invocamos la función de envío de petición (cuyo código esta más abajo y no entiendo gran cosa porque no me he estudiado el formato, pero funciona):

     Serial.println(timeServer); 
     sendNTPpacket(timeServer);          // Enviar una peticion
    
     delay(1000);                        // Damos tiempo a la respuesta

    Ahora, si hay respuesta

    if ( Udp.parsePacket() )
        { 
             Serial.println("Hemos recibido un paquete");
             Udp.read(packetBuffer,NTP_PACKET_SIZE);           // Leer el paquete

    Cuando el servidor NTP nos envia la respuesta, la firma de tiempo empieza en el byte 40 y son dos palabras de 2 bytes cada una. Por eso vamos a montar las dos palabras de segundos y fracciones:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);

    Y ahora tenemos que combinar las dos partes en un unico long entero sin signo:

                 unsigned long secsSince1900 = highWord << 16 | lowWord;
                 Serial.print("Segundos desde 1 Enero de 1900 = " );
                 Serial.println(secsSince1900);

    Recordad de cuando vimos las operaciones con bits que highWord << 16 desplaza los bits de highWord 16 puestos a la izquierda y al hacer después el OR con lowWord crea un long ya montado, que contiene el número de segundos transcurridos desde 1900.

    Pero en Arduino seguimos el modelo Unix de contar el tiempo desde el 1 de enero de 1970, asi que vamos a tener que corregir la hora. El número de segundos entre el 1 de enero de 1900 y el de 1970 es exactamente de 2.208.988.800 (Contadlos si no me creéis).

    Serial.print("Unix time = ");
    const unsigned long seventyYears = 2208988800UL;
    unsigned long epoch = secsSince1900 - seventyYears;
    Serial.println(epoch);

    Fijaros que al asignar la variable seventyYears al final del número de segundos lleva UL para indicar que lo trate como Unsigned Long.

    Ahora ya tenemos la respuesta a nuestra pregunta, ¿Qué hora es? Bien, la respuesta viene en formato UTC (o tiempo universal) que corresponde al tiempo medido en el meridiano de Greenwich o GMT (Greenwich Meridian Time).

    En el caso de España nos viene al pelo porque estamos exactamente en el meridiano Greenwich, con excepción de las Canarias cuyos relojes van una hora atrás. Pero me imagino que los que leáis esto en otro meridiano tendréis que corregir la hora por una cantidad fija correspondiente.

  • También en España tendremos que corregir el horario de invierno que supone añadir una hora más, durante varios meses al año (Algo que simepre consigue deprimirme).
  • ¿Por cierto, alguien sabe para que sirve esto?
  •  

    Para mostrar la hora en un formato legible podemos hacer las conversiones habituales a partir del número de segundos

     

    Serial.print("The UTC time is ");          // UTC es el tiempo universal o GMT
    Serial.print((epoch  % 86400L) / 3600);    // Horas (86400 segundos por dia)
    Serial.print(':');
    
    if ( ((epoch % 3600) / 60) < 10 )
       {   // Añadir un 0 en los primeros 9 minutos
           Serial.print('0');
       }
    
    Serial.print((epoch  % 3600) / 60);          // Minutos
    Serial.print(':');
    if ( (epoch % 60) < 10 )
       {    // Añadir un 0 en los primeros 9 minutos
            Serial.print('0');
       }
    Serial.println(epoch %60); //  Segundos

     

    Una vez que tenemos el tiempo en formato Unix en la variable epoch, podemos poner en hora el reloj interno de Arduino utilizando la librería Time.h, y mediante el comando que vimos en sesiones pasadas

    #include <Time.h>
    setTime(epoch);

    La idea de todo esto, es que si disponemos de conexión a Internet podemos poner en hora el reloj interno de Arduino sin más que una petición a un servidor NTP. Incluso ante un apagado, al reiniciar volveremos a sincronizar la hora interna de forma automática
     

    Resumen de la sesión

     

  • Hemos presentado lo que es el protocolo NTP.
  • Hemos visto algunas características propias y tambien como usarlo para interrogar un servidor NTP
  • Usamos un programa de Arduino para interrorgar  y recibir la respuesta de un servidor NTP y vimos alguna funcion para decodificar la respuesta
  •  



    Deja una respuesta