El servicio NTP

Objetivos

 

  • Hablaremos del NTP.
  • Veremos como cceder a sincronizar la hora con NTP.
  • Lo usareos en nuestro reloj interno.
  •  

    Material requerido.

    MOdelo basico Un M5Stack

     

     Detalle de conectores Un cable USB C

     

    M5STACK y NTP

     

    Cada vez que alguien me desenchufa el reloj de mi mesilla y me pierde la hora yo pierdo los nervios. Si, vale, es un reloj chino barato de esos que marcan los dígitos con un display LED de poco pelo y entiendo que por lo que cuesta, no podemos pedir virguerías, pero ¿En serio es tan complicado hacer un reloj WIFI, que se ponga en hora automáticamente cada vez que arranca usando NTP? ¿Por qué los relojes, aún los digitales siguen en la tecnología del siglo XIX?

    Además dos veces al año, la dichosa comisión europea nos hace cambiar la hora por razones bastante absurdas, que nadie comprende y que resulta muy dudoso que sirva para nada que no sea garantizar la vida de los varios cientos de funcionarios que viven del cuento este.

    Dicho de otro modo ¿Podemos usar el M5Stack que trae WIFI de serie para hacer un reloj que se ponga en hora solo cada vez que arranca?  Y ya que estamos, que compruebe la hora periódicamente para que cuando llega el momento de cambiar la hora lo haga solo como los Pcs o los teléfonos móviles.

    Si alguien ha pensado que no, que se ponga de cara a la pared un par de minutos como castigo por memo. ¡Claro que se puede! y dado que en el M5stack tenemos pantalla + Wifi + Batería tiene que estar chupado. Y de eso va esta sesión, veamos cómo hacer ese dichoso reloj desatendido.

     

     

    El servicio NTP en Internet

     

    Ya hemos hablado antes del servicio NTP (Network Time Protocol) al que podemos acceder desde Internet. Básicamente es un sistema que sincroniza la hora interna de nuestro sistema con un reloj maestro de referencia a través de internet.

    A la gente que hemos nacido en los últimos años, la idea de un tiempo universal es inherente a nuestra concepción del mundo y por eso mis amigos (Los pocos que me aguantan)  se suelen sorprender cuando les digo que ese tiempo universal es un invento del siglo 19. Piénsalo. Antes de que los ingleses inventaran el tren no había necesidad de que dos ciudades más o menos distantes tuvieran la misma. ¿Quién iba a comprobarlo, cuando los viajes se hacían en carruaje y el transporte de máxima velocidad eran los caballos?

    Cuando un señorito (Los pobres no viajaban y punto) iba en carruaje de caballos a otra ciudad, al llegar, simplemente ponía en hora su reloj con el de la torre del ayuntamiento y listo. Que este estuviera sincronizado con el de su pueblo era una idea bastante absurda ¿Para qué? ¿Qué sentido tendría?

    Pero al inventar los ferrocarriles, un tren podría cruzar el país por la noche y varios husos horarios, y era imprescindible saber a qué hora iba a salir o a llegar, porque había gente y mercancías esperando y a partir de esto, sincronizar los relojes, entre ciudades distantes, se convirtió en una necesidad, al menos entre las ciudades a las que llegaba el tren.

    Como fueron los ingleses los primeros en tener esta necesidad, Crearon una hora propia y un lugar arbitrario a partir del cual se medía la distancia en horas, el meridiano 0 de Greenwich (Un pueblo Inglés) y un tiempo medido en este punto o GMT (Greenwich Mean Time)

    Y a partir de este tema también surge la diferencia horaria entre distintos puntos del país o continente, ya hay una diferencia horaria que se basa en el tiempo en que la tierra tarda en girar desde el punto 0, Greenwich, hasta el meridiano que pasa por otra ciudad o punto dado. A este tiempo se le llama diferencia horaria y por eso cuando saltas en avión de un país a otro, te puedes encontrar con una diferencia de varias horas, lo que descoloca bastante hasta que te aclimatas.

  • Algunas personas se sorprenden de que se mida la distancia entre puntos distantes en horas. Pero tienes que tener en cuenta que el mundo hace una revolución completa cada 24 horas, lo que permite medir la distancia por el tiempo que la tierra cambia en girar de un punto a otro.
  • Este tiempo es precisamente el desfase horario entre los dos puntos, que debemos corregir ajustando los relojes  si vamos de uno a otro (Y suponiendo que no hay funcionarios de por medio decidiendo jugar con los relojes públicos)
  •  

    Por eso en la era de los aviones, necesitábamos un tiempo mundial, ya que si antes poner en hora relojes de las estaciones de tren era un lío, Hoy poner en hora los relojes de los aeropuertos de todo el mundo era una pesadilla (Si es que hay gente que no deja de inventar tonterías) y por eso era necesario el concepto de tiempo universal común o coordinado a nivel planetario y a esa hora mundial se le llama UTC (Coordinated Universal Time )

    El UTC es el tiempo universal común (Que se cuenta desde Greenwich) y cada punto del globo calcula su propia hora local como una desviación respecto al UTC. España por ejemplo (Donde estoy escribiendo esto) va una hora adelantado al UTC.

    ¿Y qué servidores tenemos disponibles por ahí para sincronizar la hora?  Bueno, aunque te parezca increíble no eres el único buscando la hora en internet. Por ello, el servicio NTP está organizado jerárquicamente para poder dar respuesta a una demanda global, de forma que en el primer estrato tenemos relojes atómicos muy precisos, que pasan la hora a los del segundo nivel y estos a su vez a los verdaderos servidores NTP que son los que nos hacen caso a los pringaos como tú y yo de clase turista.

    Jerarquia de servidores NTP

    Bien, una vez que os he metido el rollo teórico de la hora, podemos ya pasar a ver cómo podemos usar el Servicio NTP de internet, que nos proporciona acceso a un reloj UTC, y al que después podemos sumar o restar nuestro desfase horario para obtener nuestro tiempo local.

     

    Acceder al NTP con M5Stack

     

    Veamos como programar acceso al servicio NTP desde nuestro M5stack. Necesitamos descargar la   librería NTPClient-master.zip desde  en instalarla según el procedimiento habitual, y empezamos declarando los includes necesarios:

    #include <M5Stack.h>
    #include <NTPClient.h>
    #include <WiFi.h>
    #include <WiFiUdp.h>
    
    const char *ssid     = "XXXXXX";
    const char *password = "YYYYYYY";

    Necesitamos incluir las Librería NTPClient.h y WIFIUdp.h porque la comunicación con NTP se realiza a través de UDP y no de TCP/IP. Asegúrate de poner el nombre tu Wifi y contraseña para poder conectar. Como desde España hay una hora de diferencia respecto UTC que son 3600 segundos:

    const long utcOffsetInSeconds = 3600;

    Ahora ya podemos empezar con el programa:

    WiFiUDP ntpUDP;     // Define NTP Client to get time
    NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

    La primera línea crea una instancia de cliente UDP con WIFI y con la segunda creamos un cliente del servicio NTP a partir de la instancia UDP anterior, un pool de servidores NTP:  «pool.ntp.org» y una diferencia horaria en segundos.

    ¿Qué es esa película del pool de servidores NTP? Del mismo modo que tu PC encuentra la dirección de cualquier servidor en Internet, como Google.com o mucho más importante Prometec.net mediante un servicio DNS global, en cualquier servicio publico, como el NTP,  es que hay diferentes niveles de atención a los demandantes y para nosotros (La clase turista) es buena idea entrar por un servidor NTP genérico «pool.ntp.org» que nos asignara otro servidor NTP disponible de modo transparente  a toda velocidad (Y además gratis así que no protestes)

  • Es un poco como llamar a una servicio de atención telefónica. EL sistema te pasa con el primer operador disponible.
  • Hay jerarquías de servidores a nivel global, continental, Estatal y hasta de universidades y sitios así, pero no vale la pena complicarnos mas.[/fancy-ul] [/three-fourth]
  •  

    void setup()
    {  WiFi.begin(ssid, password);
       while ( WiFi.status() != WL_CONNECTED )
            {  delay ( 500 );
            }
    
        timeClient.begin();
        M5.begin();
    }

    Iniciamos el WIFI con el nombre de tu red y password y esperamos hasta que el status del WIFI sea conectado y al conectar iniciamos timeCLient, la instancia del cliente NTP y claro esta iniciamos las librerías del M5.

    Voy a crear una función printClock() que dibuje en la pantalla LCD del M5 la hora. Para ello podríamos hacer algo como esto:

    Void printClock()
    {    int H = timeClient.getHours();
         int M = timeClient.getMinutes();
         int S = timeClient.getSeconds();
    
         M5.Lcd.fillScreen(BLACK);   // Borra la pantalla
         M5.Lcd.setCursor(20, 110);
         M5.Lcd.setTextColor(RED);
         M5.Lcd.setTextSize(6);
    
         M5.Lcd.printf("%02d:%02d:%02d", H, M, S );
    }

    Podemos acceder a timeClient para sacar las horas minutos y segundos  e imprimirlos con un printf con formato de 2 dígitos, pero como sé que los amigos de Arduino suelen alucinar con estas instrucciones y además es una lata escribir tanto, sería más fácil usar otra función disponible que nos devuelve la hora ya formateada:

    Void printClock()
    {   M5.Lcd.fillScreen(BLACK);   // Limpia la pantalla
        M5.Lcd.setCursor(20, 110);
        M5.Lcd.setTextColor(RED);
        M5.Lcd.setTextSize(6);
    
        M5.Lcd.print(timeClient.getFormattedTime());
    }

    ¿Que porque no he empezado por aquí? Pues básicamente  porque getFormattedTime() devuelve un string con el formato de lo más mono pero puede pasar que queráis sacar las horas y minutos pero no los segundos (Al final es un reloj)y os conviene saber cómo hacerlo. Y ya solo faltaría llamar a la función desde el loop:

    void loop()
    {   timeClient.update();
        printClock();
        delay(999;
    }

    Si cargas el programita veréis como ya tienes el reloj en hora.

     

    Refinando el programa

     

    El programa anterior es válido pero tiene el inconveniente que llama al servidor NTP cada segundo y si todo el mundo hiciese eso podría causar problemas.

    En realidad nuestro ESP32 dispone de un reloj interno que funciona bastante potablemente y lo único que necesitamos es ponerlo en hora con NTP y después se apaña él solo. Simplemente basta con volver de tarde en tarde a sincronizar de nuevo el reloj interno para asegurarnos que sigue preciso mediante NTP, especialmente en esos días en que la hora cambia.

    Por tanto podemos modificarlo un poco para que llame, cada hora por ejemplo al saltar los minutos a cero. Bastaría con cambiar un poco el loop tal que  así:

    Void loop()
    {  if (M==0);
       {  timeClient.update();
          M = timeClient.getMinutes();
        }
        printClock();
        delay(999);
    }

    De esta forma solo sincronizamos con el servidor NTP cuando los minutos lleguen a 0. También podríamos añadir el día de la semana escrito abajo: NTP2 Cliente NTP con M5stack

    Void printClock()
    {    M5.Lcd.fillScreen(BLACK);   // Limpia la pantalla
         M5.Lcd.setCursor(20, 110);
         M5.Lcd.setTextColor(RED);
         M5.Lcd.setTextSize(6);
         M5.Lcd.print(timeClient.getFormattedTime());
    
         M5.Lcd.setCursor(20, 200);     M5.Lcd.setTextColor(BLUE); 
         M5.Lcd.setTextSize(4);     
         M5.Lcd.print(daysOfTheWeek[timeClient.getDay()]);
    }

     

    Añadiendo la fecha al display

     

    Hemos añadido tranquilamente la hora del reloj y hasta el día de la semana a nuestro reloj  autoajustable, y quedaría de lo más mono que además debajo del día de la semana, nos pusiera fecha, mes y año para rematar.

    Si anteriormente hemos usado las funciones getHours(), getMinutes(),getSeconds() lo lógico sería pensar que la librería NTPClient dispondrá también de las funciones getDay(), getMonth() y getYear() para rematar la faena, pero ésta es una de esas ocasiones en que la vida demuestra que es un asco, porque por alguna oscura razón, los benditos programadores de la librería no han considerado necesario incluirlas.

    Joer, y ahora qué hacemos. Bueno, no pasa nada, nos las programamos nosotros y listo. De hecho en cuanto he buscado en Google me ha salido un alma caritativa que ya se lo ha currado (Y yo he copiado miserablemente),  no tenemos más que copiar y pegar, pero… (Ay con los peros) tenéis que entender que la librería NTClient está programada como una clase y para añadir nuevas funciones vamos a tener que tocar los ficheros originales de la librería.

    No es grave, pero para la gente de corazón débil, esto ya parece cirugía mayor y suelen salir discretamente de clase como si tuvieran algo urgente que hacer. Vamos a ir por lo sencillo y como

    Para incluir estas funciones, he modificado el fichero NTPCLient.cpp y NTPCLient.h para incluir estas funciones (Descárgalas aquí NTPClient modified) y ahora solo tenéis que pisar estos ficheros en el directorio de la Liberia.  Para ello en Windows lo normal es que tengas las librerías Arduino en

    \\Este equipo\Documentos\Arduino\libraries\ NTPClient-master

    Imagen del explorador de archivos

    Descomprime la librería modificada y copia los ficheros NTPCLient.cpp y NTPCLient.h a este directorio pisando los originales y listo, ya podemos usar las funciones que nos faltaban.

  • Este no es el lugar para discutir como ampliamos las funciones disponibles en la clase por lo que simplemente os vamos a llevar de la mano para hacerlo sin entrar en más detalles.
  • De hecho, esto de tocar los ficheros originales de la clase se considera una chapuza que roza lo herético en el mundo puro de la programación orientada a objetos. Recordad que en la OOP se trata de evitar hacer esto para no provocar errores inadvertidos al modificar unas clases que ya están probadas.
  • Lo suyo hubiera sido derivar una clase de NTPCLient y llamarla NTPClientMax, por ejemplo, y añadir a esta nueva clase las funciones que necesitamos, pero como la clase original tiene 4 constructores que tenía que reescribir,  no me apetecía estudiarme la librería, así que me harté y tiré por la calle de enmedio.
  • Mis amigos más puretas me han amenazado con no volverme a dirigir la palabra por la chapuza anterior de modificar la clase original ya que esto es prácticamente un pecado mortal en el mundo de la OOP, pero prometo solemnemente no volverlo a hacer so pena de excomunión. 
  •  

     

    EL programa final

    Una vez que ya hemos modificado la programación de la clase podemos pasar directamente a la nueva versión del reloj

    Para usar las nuevas funciones en nuestro programa es tan sencillo como añadir unas líneas a la función printClock()

    NTP client esp32 M5STACK

    M5.Lcd.setCursor(20, 200);
    M5.Lcd.setTextColor(BLUE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.print(daysOfTheWeek[timeClient.getDay()]);
    
    int Dia = timeClient.getDate();
    int Mes = timeClient.getMonth();
    int Ano = timeClient.getYear();
    
    M5.Lcd.printf( "%d/%d/%d", Dia, Mes, Ano);

    Como veis simplemente hemos cambiado el tamaño de la letra de la última línea para tratar de que entre todo. También veréis que en este caso la plantilla de printf es %d/%d/%d y no %02d/%02d/%d/porque no nos sobra espacio en la línea. La primera escribirá cosas como 2/12/2019 o 21/3/2020 mientras que la segunda rellena siempre a 2 decimales el día y el mes.

    ¡Accede al contenido!

    Aquí os dejo una foto con el resultado:

    Imagen del reloj digital con M5stack