Servicios JSON y M5Stack
Material requerido.
Un M5Stack
|
|
Un cable USB C |
Dando sentido a JSON
Conozco a algunos colegas a quienes el formato JSON les intima lo bastante como para negarse a reconocerlo y además lo evitan como alma que sigue el diablo, aunque no entiendo porque. Comprendo que ese chorro de texto que veíamos en la sesión previa, intimida un poco a cualquiera pero os aseguro que si le dais una oportunidad os sorprenderá por su potencia y elegancia.
Por eso he querido hacer esta sesión separada de la última, porque la vamos a dedicar a ver como sacar sentido de ese torrente de texto y veréis que, como siempre, no era para tanto si le dedicáis un minuto y se presenta de forma didáctica.
En la última sesión nos registramos con Openweather y vimos que obtener la respuesta en bruto del servidor en formato JSON era más simple que el mecanismo de un chupete y llegamos a recibir la el mensaje que nos envía a una variable.
En esta sesión vamos a ver cómo accedemos a los datos de la respuesta de un modo que nos permita explotar la información. Como estamos haciendo un ejemplo de un reloj con nuestro M5stack, vamos a ver como mostramos en pantalla cosas como la temperatura externa, la humedad o la presión atmosférica.
Podríamos buscar directamente en el String de respuesta ciertas pautas o keys como “temp” o “humidity” para conseguir eso y estaría bien pero mi vida social quedaría inmediatamente arruinada pues mis amigos más Frikis no me volverían a dirigir la palabra, porque eso sería muy poco elegante, y en este mundo el concepto de elegancia es clave para tu status social (Curioso, porque es gente de dudosa higiene, que no se afeita bien y visten como mendigos)
Y para que pueda seguir hablando con ellos vamos a utilizar un sistema mucho más elegante que seguro que ya adivinas: Una librería de acceso a JSON que nos va a hacer todo el trabajo, porque, no te lo vas a creer, alguien se ha trabajado una librería que nos permite serializar y deserializar los mensajes JSON el mundo Arduino que por supuesto funciona con ESP32 M5Stack y se llama (Qué sorpresa) Arduino JSON library.
Por tanto hay que empezar por descargar la librería e instalarla desde aquí: ArduinoJson library.Una vez instalada siguiendo el procedimiento habitual, vamos a incluirla en nuestro programa:
#include <M5Stack.h> #include <NTPClient.h> #include <WiFi.h> #include <WiFiUdp.h> #include <HTTPClient.h> #include <ArduinoJson.h>
Aunque cada vez estamos incluyendo más librerías en nuestro programa, no es grave, nuestro ESP32 interno tiene bastante memoria disponible, y solo es nueva la última línea. Necesitamos varias constantes y definiciones para el programa, como claves de acceso a internet, y APIkeys para Openweather y alguna variable instrumental
const char *password = "contrase"; const long utcOffsetInSeconds = 3600; long Interval =600; // tiempo entre llamadas al servicio WheatherMap 10 min * 60 segs = 1800 time_t TimeLast = 0 ; const String key = "eebb481d48efecf2fefdeb3c671687e7"; const String endpoint = "https://api.openweathermap.org/data/2.5/weather?q=Bilbao, ES,IN&APPID=";
Nada que no hayamos comentado anteriormente en las sesiones previas. Al igual que la sesión anterior vamos a seguir usando NTPClient como la base de tiempo por lo que necesitamos las líneas ya vistas:
WiFiUDP ntpUDP; // Define NTP Client to get time NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
El setup varía poco con respecto al anterior:
void setup() { M5.begin(); WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); M5.Lcd..print ( "." ); } Serial.begin(115200); timeClient.begin(); }
La diferencia es que hemos añadido una línea para iniciar la librería M5 para usarla como salida de nuestro Nuevo programa. El loop será casi igual que nuestro último programa
void loop() { if (M==0); { timeClient.update(); M = timeClient.getMinutes(); } // printClock(); Serial.println("-----------------------------"); time_t rawtime = timeClient.getEpochTime(); if (rawtime >= TimeLast + Interval ) //Si ha pasado el tiempo especificado { GetJsonMeteo(); time_t TimeLast = timeClient.getEpochTime(); } }
Básicamente he comentado la línea que llamaba a imprimir el reloj en pantalla, para que no se nos complique la elección entre la hora y los datos meteo. En esta sesión nos vamos a concentrar en la función GetJsonMeteo () para obtener los datos meteorológicos mediante JSON. Veamos que hay dentro de esta función:
void GetJsonMeteo () { http.begin(endpoint + key); //construct the URL int httpCode = http.GET(); //send request
Como en la sesión anterior, invocamos la página web de OpenWeather y obtenemos el código de respuesta de la operación y después vamos a comprobar que la sesión ha sido correcta y nos vamos si hay errores
if (httpCode > 0) { String payload = http.getString(); //Serial.println(httpCode); Serial.println(payload); Serial.println(".................."); ……………………………………… else Serial.println("Error on HTTP request"); http.end();
Seguimos en terreno conocido. Es a partir de ahora cuando vamos a ver cómo extraemos la información de payload que contiene un texto en formato JSON. Es a esto que en la jerga llamamos deserializar el mensaje, y para ello vamos a ver que la Librería Arduino JSON nos ofrece todo lo necesario para extraer, incluir y operar con el formato JSON y vamos a emplear algunas de esas funciones a nivel de ejemplo.
Tengo que comentar aquí, que cuando he buscado ejemplos por la web de deserializar mensajes JSON, me ha sorprendido lo complicado que se puede hacer una cosa de lo más sencilla. En esta humilde casa vamos a tirar por el método más simple que llevo un tiempo usando porque creo que me ayudara a convencernos de no es para tanto.
Vamos a usar la librería para crear un documento tipo JSON:
DynamicJsonDocument doc(1024);
Creamos un documento dinámico (No te preocupes mucho de qué es esto) llamado doc y le asignamos 1024 caracteres
Y ahora hay que asignar el contenido de paylod (La respuesta del servidor JSON) a el documento que acabamos de crear:
deserializeJson(doc, payload);
¿Complicado,a que no? Y ahora solo falta leer los datos, y para eso tenemos que hablar un poco más de la estructura de un fichero JSON.
Estructura de un fichero JSON
Vamos a empezar hablando un poco de la estructura de un mensaje JSON y como acceder a ella de una forma simple. Imagina por un momento que tenemos un fichero JSON sencillo como este:
doc { "lon":-2.94, "lat":43.26 },
Podríamos extraer el los valores lon y lat de forma muy fácil:
float Longitud = doc [“lon”] ; float Latirud = doc[“lat”] ;
Fíjate que sencillo sería. Esta es la maravilla de la librería que estamos usando. Una vez que hemos creado y asignado el documento JSON es capaz de sacar los datos como si fueran un simple array. Basta con dar el nombre de la clave entre comillas como si fuera el índice de una matriz.
Pero en un documento JSON, siempre que puedas dar un valor, también puedes dar una lista de valores sin más que cumplir la misma regla (Dicho de otro modo, la estructura es recursiva y cualquier valor puede a su vez ser un nuevo documento o estructura JSON)
{ "main": { "temp":280.71, "feels_like":277.32, "temp_min":279.15, "temp_max":283.71, "pressure":1021, "humidity":81 }, "wind": { "speed":3.1, "deg":240 },
Ahora tenemos dos claves principales: main y wind, pero cada uno de ellas tiene a su vez nuevos valores internos múltiples, cada uno con su clave propia, como “temp”,”pressure”, “humidity, por lo que ahora,
doc[“wind”] = ":{ "speed":3.1, "deg":240 },
Es decir es una nueva lista que cumple con el formato JSON. Si quiere obtener solamente la velocidad del viento sería:
doc[“wind”][“speed”:] ----> 3.1
Parece muy lioso pero es una auténtica tontería de puro fácil. Suponte que quiero sacar la temperatura. De la estructura de arriba. Puedo hacer esto
long temp = doc["main"]["temp"]; ----> 280.71
Aquí lo he convertido a un tipo long porque la temperatura trae un par de decimales que no quiero mostrar (y viene por defecto en grados Kelvin ) ¿Y el resto? Podemos acceder directamente de igual forma
long temp = doc["main"]["temp"]; ----> 280.71 long pressure = doc["main"]["pressure"]; ----> 1021 long humidity = doc["main"]["humidity"]; ----> 81 long viento = doc[“wind”][“speed”] ; -----> 3.1
Otra vez, estamos en una situación en que extraer la información que deseamos se parece mucho a indexar un elemento en un array C++, cuyo contenido es siempre texto (Lo que nos Evita las engorrosas discusiones de cuantos digitos me envías o si lleva o no signo)
La enorme virtud de la librería JSON de Arduino es que nos permite abstraernos de la necesidad de rebuscar entre la cadena de texto que contiene la respuesta y acceder directamente a las claves que nos interesan de forma directa como si fuera un array de C++.
Deserializando el fichero JSON
Después de la última disertación podemos entrar ya con el programa que nos obtenga distintos valores del fichero JSON que hemos recibido de OpenWeather. ¿Cómo queda nuestra función después de todo esto? Pues algo así:
Programa JSON_10
void GetJsonMeteo() { http.begin(endpoint + key); //construct the URL int httpCode = http.GET(); //send request if (httpCode > 0) { String payload = http.getString(); Serial.println(payload); M5.Lcd.print(".................."); DynamicJsonDocument doc(1024); deserializeJson(doc, payload); long temp = doc["main"]["temp"]; long pressure = doc["main"]["pressure"]; long humidity = doc["main"]["humidity"]; M5.Lcd.fillScreen(BLACK); // Limpia la pantalla M5.Lcd.setCursor(0, 110); M5.Lcd.setTextColor(BLUE); M5.Lcd.setTextSize(5); M5.Lcd.printf("%2d %4d %2d" , temp-273, pressure, humidity); } else Serial.println("Error on HTTP request"); http.end(); delay(500); }
Si enviamos este programa a nuestro M5 obtenemos este resultado, que son temperatura, presión atmosférica y humedad ambiente, respectivamente:
Mejorando un poco la presentación de datos
Lo de arriba funciona pero da asco verlo. Habría que pintar la temperatura de mayor tamaño, el doble ya puestos, y mover la presión y la humedad a la primera línea del display y ya que estamos, añadir el símbolo de ºC y la humedad en porcentaje con un símbolo “%”
El caso es que aquí es donde todo se complica, porque la fuente TrueType que hemos usado no dispone de ninguno de los dos símbolos y la cosa no tiene buen remedio que no sea modificar el font para añadirle ese par de símbolos.
La verdad es que nunca había tenido que hacer semejante trabajo, pero tras unas consultas por internet enseguida encontré un programa Typelight que nos permite hacer eso rápida y limpiamente. Como esto no es un tutorial sobre como modificar fuentes TrueType, bastará con decir que podéis descargar aquí el fichero TTF y el header de 20 puntos, ya convertido para que podáis usarlo sin más problemas: fonts digital11
Digital_Numbers_400_20pt7b.ttf Digital_Numbers_400_20pt7b.h
Tened en cuenta que el símbolo de “%”está en su sitio (Escribiendo %), pero en cambio para invocar el símbolo de grados º tienes que escribir “@” porque no encontré su lugar adecuado. Vamos a retocar un poco la función para imprimir la temperatura en bonito, mas presión atmosférica, humedad relativa y velocidad del viento. Podemos hacer algo como esto:
Programa JSON_11
void GetJsonMeteo() { http.begin(endpoint + key); //construct the URL int httpCode = http.GET(); //send request if (httpCode > 0) { String payload = http.getString(); DynamicJsonDocument doc(1024); deserializeJson(doc, payload); long temp = doc["main"]["temp"]; long pressure = doc["main"]["pressure"]; long humidity = doc["main"]["humidity"]; String clima = doc["weather"]["main"]; float viento = doc["wind"]["speed"]; // En metros por segundo viento = viento * 3.6 ; M5.Lcd.fillScreen(BLACK); // Limpia la pantalla M5.Lcd.setFreeFont(&Digital_Numbers_400_20pt7b); M5.Lcd.setCursor(80, 120); // ...........................temp M5.Lcd.setTextColor(BLUE); M5.Lcd.setTextSize(2); M5.Lcd.printf("%ld@", temp - 273); M5.Lcd.setCursor(0, 30); //........................................presion / humedadM5.Lcd.setTextColor(BLUE); M5.Lcd.setTextSize(1); M5.Lcd.printf("%ld %ld%%", pressure, humidity); M5.Lcd.setCursor(0, 200); //........................................tiempo M5.Lcd.setTextColor(BLUE); //M5.Lcd.setTextSize(1); M5.Lcd.printf("%.1f %s" , viento, clima); Serial.println(viento); } else Serial.println("Error on HTTP request"); http.end(); delay(500); }
La única diferencia es que hemos leído algún dato más del JSON y que hemos calculado la velocidad del viento en km/hora en lugar de metros por segundo (Multiplicando por 3.6) Y sin más obtendremos un resultado tal que así: