Objetivos
Material requerido.
Un M5Stack
|
|
Un cable USB C |
El formato JSON
No sé si os habéis dado cuenta, pero el mundo es un sitio bastante complicado, y desde que empezamos a desarrollar Internet hay una explosión en el número de personas que acceden, comparten y comentan información. Esto generó algunos problemas iniciales curiosos como ¿Qué alfabeto usamos para garantizar que la información intercambiada es legible? ¿A qué velocidad enviamos y recibimos la información?
Este tipo de problemas se solventaron prontamente usando el standard UNICODE para el intercambio de información escrita (En lugar del anterior pero mucho más limitado ASCII) y después los protocolos TCPIP que gestionan todo el transporte e intercambio de información, pero a su vez hicieron aparecer otros nuevos.
Por ejemplo si generamos bases de datos públicas ¿Cómo consultamos esa información? ¿En qué formatos? ¿Quién puede acceder a qué? ¿En qué formato envío el resultado?
Como comprenderás mucha gente tiene una idea clara y terminante de cómo se debe hacer todo esto y han ido apareciendo los formatos definitivos para el tema… una y otra vez sin que haya un ganador claro. Me recuerda a aquel chiste del consultor al que acusan de no apoyar los estándares y este respondió que él era tan defensor del tema, que apoyaba todos los que conocía.
Al final alguien se hartó y decidió hacer otro formato, lo más simple posible, y que fuera de texto básico con algunas reglas sencillas y lo llamó JSON, y sorprendentemente ha ido ganando adeptos por lo fácil de usar y lo sencillo de manejar
- JSON viene de JavaScript Object Notation (Descripción de objeto JavaScript) y nació como un formato de intercambio de información desarrollado para Javascript
- Actualmente el JSON se usa habitualmente el lenguaje Javascript pero no es una parte exclusiva y su uso se ha extendido mucho fuera del ámbito Javascript y casi cualquier base de datos pública acepta su uso.
- Y naturalmente nuestros Arduinos / ESP32 disponen de una librería para poder intercambiar información.
Esto ha ido haciendo que cualquier servicio público o privado imaginable, de acceso libre o de pago, disponga de un acceso a su información de forma inmediata a través de un servicio JSON. ¿Qué clase de servicios son estos? Imagínate. Cualquier información que quiera ser pública puede entregarse a través de un servicio JSON. Ejemplos típicos de información disponibles son:
Casi cualquier información que se te ocurra estará disponible para que la consultes con JSON y esto abre un mundo impresionante para estadísticas o simplemente información curiosa si tienes el tiempo y las ganas de consultarlo
Por eso vamos a hacer un ejemplo de cómo acceder a una BBDD usando JSON y nuestro Arduino / ESP32 mediante nuestro imprescindible M5Stack, y de eso va esta sesión
OpenWeather JSON y M5STACK
Cuando planeas una serie de tutoriales como los que nos ocupan, siempre es interesante usar ejemplos que hagan las veces de hilo conductor de las ideas y conceptos que hay que ir presentando. Por eso cuando nos decidimos a escribir una introducción avanzada al M5Stack nos pareció buena idea hacer un reloj auto ajustable como el que hemos ido presentando en los capítulos previos.
Y desde ese ejemplo del reloj parece una idea sensata añadirle la posibilidad de que nos informe del tiempo que hace, si llueve o hace sol y la temperatura que nos vamos a encontrar en la calle o si hace viento, y (No te lo vas a creer) hay servicios JSON públicos y de pago que nos informan puntualmente de todo esto y de mucho más.
Uno de estos servicios es OpenWeatherMap y nos ofrece las cosas más insospechadas con respecto a la previsión meteorológica local y global y es el que vamos a usar para hacer nuestro ejemplo de reloj con barómetro incluido.
OpenWeatherMap nos ofrece un acceso gratuito a esta información siempre que no nos pasemos de consultas y por eso me gusta como ejemplo, pero a cambio, nos exige registro y asigna una API key o identificador que registra nuestras consultas.
Y sin más historias vamos a ver cómo podemos usarlo
Registrándote en OpenWeatherMap
Como ya hemos comentado OpenWeatherMap nos pide que nos registremos para asignarnos un identificador que necesitaremos incluir en todas nuestras consultas. Para ello vamos a empezar accediendo su web: https://openweathermap.org/ y en mi caso como ya estoy registrado (Y sabe dónde vivo) veremos un gráfico de resumen con mis datos:
Como podéis ver con facilidad, hace un día típicamente asqueroso de Bilbao, de frío y lluvia, que tanto nos gustan a los vascos, pero lo que quiero mostraros es que así por las buenas nos da información sobre viento, nubes, lluvia, temperatura, presión atmosférica, humedad y otras lindezas. Y lo interesante es que tenemos acceso a todo esto y más desde JSON (¿Os he convencido ya de que merece la pena?)
Para poder acceder a los servicios de OpenWeather, nos exige que nos registremos en su web y obtengamos una APIKey que servirá como identificador de usuario para los accesos.
Antes permitía acceder gratuitamente sin registrar pero esos días se acabaron ya hace un tiempo. Podemos empezar en su página de enganche: https://home.openweathermap.org/users/sign_up , rellena todos los campos que te vaya pidiendo y cuando el registro sea validado, OpenWeather te enviara un email agradeciendo la suscripción a sus servicios, la ApiKey y algunos ejemplos y links de interés
Te informará también de que la clave tarda al menos una hora en activarse, así que empieza pronto porque si no vas a tener que morderte las uñas mientras esperas a que la clave esté activada y el resto de este tuto no funcionará hasta entonces
¿Cómo podemos probar si la clave está activa? Pues haciendo una consulta directamente desde el navegador como esta:
Fíjate que debes sustituir las xxxxxxxxxxxxxxxx por tu ApiKey (Porque no pienso dejaros la mía, claro) y la ciudad y código de donde vivas. En cuanto pulse Intro verás en tu navegador algo así:
Ya puedes ver que la respuesta aparece como un texto plano bastante extraño entre llaves y comillas, pero también puede ver que se reconocen cosas como longitud y latitud, temperatura actual (En grados Kelvin) y también temperaturas máximas y mínimas previstas, además de presión de aire, humedad y no sé cuántas cosas más. Si organizamos un poco la respuesta tendrá más sentido:
{"coord": { "lon":-2.94, "lat":43.26 }, "weather": [ {"id":802, "main":"Clouds", "description":"scattered clouds", "icon":"03n" } ], "base":"stations", "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 }, "clouds": { "all":40 }, "dt":1576087793, "sys": { "type":1, "id":6438, "country":"ES", "sunrise":1576049618, "sunset":1576082177}, "timezone":3600, "id":3128026, "name":"Bilbao", "cod":200 }
Bueno no voy a pretender que esté muy claro, pero cualquiera acostumbrado a programar en algo como C+ o JavaScript, reconocerá algunos patrones en esta forma de presentar los datos. Parece que existe algo como categorías y subcategorías (Keys en la jerga) delimitadas entre comillas, como “lon” o “country” y luego un símbolo de dos puntos, y también parece que si una categoría va tener subcategorías, éstas se encierran entre llaves balanceadas (Como en C++)
Si miráis el resultado con un poco de atención veréis que enseguida le encontráis el sentido: coor, weather, main, sys son categorías primarias que a su vez encierran entre llaves nuevas categoría y después de los dos puntos el dato que publica.
Este no es el lugar más adecuado para centrarnos en el formato JSON y dar una tesis sobre su sentido y modo de organización, pero sí que nos interesa entender que esta es una típica respuesta de un servidor JSON y que podemos configurar el contenido de la respuesta modulando los parámetros de la consulta.
Por ejemplo si no tienes la suerte inmensa de vivir en Bilbao (Que le vamos a hacer) y estas en, digamos, Valencia en España basta con que cambies la consulta a esa ciudad, pero fíjate que si buscas ese nombre en OpenWeather obtienes no menos de 5 ciudades, en España Valencia ES, Valencia VS (Venezuela), Valencia PH (Filipinas) y Valencia CO (Colombia)
¿Creíais que solo había una? Casi cualquier consulta que hagas te devolverá varios lugares con el mismo nombre, así que hay que afinar y poner el nombre de la ciudad y el estado o país, separados por comas del tipo, Valencia, ES o Laredo, US en la clave de la consulta
https://api.openweathermap.org/data/2.5/weather?q=Bilbao, ES,IN&APPID=XXXXXXXX
La respuesta a la consulta anterior nos devuelve los valores por defecto con la temperatura en grados Kelvin (A los que hay que restar 273 para obtener grados Celsius) pero podemos pedirle que nos dé el resultado en unidades del sistema métrico con algunos otros parámetros, o que nos responde en el idioma que nos interesa: español, o tantas otras cosas en las que no vamos a detenernos
Veamos cómo obtener esta respuesta desde nuestro M5 para que podamos comprobar que no os engaño.
Accediendo al server JSON desde C++ y M5
Como ya hemos visto basta una consulta desde un navegador web con el texto adecuado, y en cuanto tu APIkey este activa, provoca la respuesta automática. Por tanto vamos a hacer lo mismo desde nuestro flamante ESP32 accediendo al servidor web de OpenWeather.
Vamos a presentar un programa de lo más sencillo que nos permita imprimir esto en la pantalla o puerto serie. Necesitaremos unos pocos includes:
#include <M5Stack.h> #include <NTPClient.h> #include <WiFi.h> #include <WiFiUdp.h> #include <HTTPClient.h>
Al primero ya estáis acostumbrados y del resto, ya hemos visto todos menos el último que es auto explicativo: Nos permite acceder como clientes a un servidor Http.
- En puridad no necesitaríamos nada de todo eso ahora, pero como necesitamos controlar el tiempo que hace que llamamos a OpenWeather y que más adelante queremos incrustar este primer programa en el reloj voy a utilizar el mismo NTPClient que ya vimos anteriormente.
- De este modo espero que el programa final sea un poco más consistente.
Tenemos que definir algunas cosas más:
const char *ssid = "xxxxxx"; const char *password = "yyyyyy";
Tenéis que rellenar con los datos con vuestra WIFI y clave de acceso como siempre y después un par de variables instrumentales
long Interval = 600; // tiempo entre llamadas a WheatherMap 30 min * 60 segs = 1800 time_t TimeLast = 0 ;
No podemos llamar a Openweather cada segundo. No tendría sentido. El tiempo no cambia tan rápido y además Openweather nos bloqueará tranquilamente si llamamos más de 60 veces en un minuto (Y él dice que actualiza datos cada 10 minutos) de modo que invocar la actualización cada 10 minutos tiene más sentido y para controlar que no llamemos continuamente necesitaremos controlar la última vez que llamamos y compararlo con el valor actual
const String key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"; const String endpoint = "https://api.openweathermap.org/data/2.5/weather?q=Bilbao, ES,IN&APPID=";
Lo primero es el lugar donde poner nuestra ApiKey cuando la recibas y la segunda línea es la que construye la consulta. Es aquí donde puedes cambiar opciones cuando sepas lo que quieres. Vamos con el setup:
void setup() { M5.begin(); WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) delay ( 500 ); timeClient.begin(); Serial.begin(115200); }
Iniciamos la conexión WIFI con red y contraseña y mantenemos el programa en un bucle hasta que conecte y después avanzamos para iniciar el M5 y el puerto serie para sacar el texto que recibamos.
void loop() { time_t rawtime = timeClient.getEpochTime(); if (rawtime >= TimeLast + Interval ) //Si ha pasado el tiempo especificado { GetJsonMeteo(); TimeLast = timeClient.getEpochTime(); } }
Al arrancar el loop grabamos la fecha y hora actual en rawtime en formato unix como segundos transcurridos desde el 1/1/1970 (No habíamos visto antes esta función pero ya ves)
La primera vez que entremos en el condicional registraremos la fecha unix de ese momento y en el próximo ciclo la variable TimeLast tendrá un valor dado. Como en el condicional comparamos el último tiempo que entramos + los 30 minutos de interval, no entrará a llamar a la función encargado de trabajar que s GetJsonMeteo(). Veamos esta última
void GetJsonMeteo() { http.begin(endpoint + key); //construct the URL int httpCode = http.GET(); //send request
La primera línea compone una string las dos variables que definimos arriba para hacer llamar al servidor web y la siguiente línea recibe el código de respuesta del servidor, no el string de respuesta con información que llega al string payload.
Descargar program: JSON_0
if ( httpCode > 0) { String payload = http.getString(); Serial.println(httpCode); Serial.println(payload); Serial.println(".................."); } else Serial.println("Error on HTTP request"); http.end(); }
Después comprobamos que no hay error en la respuesta, y si hay un error enviamos el mensaje y terminamos. Pero si hay respuesta la recogemos en payload e imprimimos en la consola.
Podemos comprobar que el programa conecta con OpenWeather y recibe datos, pero no os recomiendo quedaros mirando, a la espera de la próxima muestra: Tardará 10 minutos.
En la próxima sesión veremos cómo hacer el parsing de los datos recibidos y cómo mezclarlo con el reloj, que esta sesión ya ha crecido bastante