Internet de les coses amb ESP32 i ESP8266

Exemples Referència Plaques   Recursos CITCEA
Projectes Programació Perifèrics   Inici

Llegim dades en format JSON

A internet hi ha diversos serveis que faciliten dades. Molts d'ells fan servir el format JSON per empaquetar-les. Podríem tractar manualment les dades rebudes per trobar el valor que ens interessa però la biblioteca ArduinoJson.h (desenvolupada per Benoît Blanchon) ens facilita la feina.

Per fer l'exemple, cercarem el preu del kWh de la tarifa del preu voluntari per al petit consumidor (PVPC) que ens facilita el web de Red Eléctrica. En aquesta pàgina trobarem els preus, hora per hora, del dia d'avui i de dies precedents però el disseny d'aquesta pàgina és adequat per als humans però no ho és per a l'Arduino.

En aquesta altra pàgina trobarem la manera de generar una URL que ens permeti recuperar les dades que ens interessin. Si mirem la pàgina, observarem que el widget que ens cal és precios-mercados-tiempo-real que està a la category mercados. Ens indiquen que el format de la URL ha de ser:

/{lang}/datos/{category}/{widget}?[query]

Per tant, la URL que hem de fer servir és:

https://apidatos.ree.es/es/datos/mercados/precios-mercados-tiempo-real

Ara ens falta posar-li els paràmetres, que són les dates d'inici i final i el tipus de truncament. Per a les dades del dia 10-7-2020 tindríem:

https://apidatos.ree.es/es/datos/mercados/precios-mercados-tiempo-real?start_date=2020-07-10T00:00&end_date=2020-07-10T23:00&time_trunc=hour

En aquest cas no ens donen detalls de com processar les dades i, per tant, les haurem d'analitzar una vegada manualment per poder saber com llegir un valor concret. Si posem aquesta URL al navegador, obtindrem un conjunt de dades en format JSON que no tenen cap salt de línia. Les hem copiat a continuació però afegint-hi salts de línia i sagnats per tal de poder veure millor l'estructura de les dades. També hem posat uns símbols · per tenir clars els nivells de sagnat. Imaginem que ens interessa el preu de les 11 h, que és el que hem marcat en groc. Amb color verd hem marcat els indicadors dels nivells que haurem de posar, com a paràmetres, a json_doc. Fixem-nos que els paràmetres i valors textuals estan entre cometes, en el cas dels numèrics no es posen cometes.

{
·    "data":{
·    ·    "type":"Precios mercado peninsular en tiempo real",
·    ·    "id":"mer13",
·    ·    "attributes":{
·    ·    ·    "title":"Precios mercado peninsular en tiempo real",
·    ·    ·    "last-update":"2020-07-09T20:18:31.000+02:00",
·    ·    ·    "description":null
·    ·    },
·    ·    "meta":{
·    ·    ·    "cache-control":{
·    ·    ·    ·    "cache":"MISS"
·    ·    ·    }
·    ·    }
·    },
·    "included":[
·    ·    {
·    ·    ·    "type":"PVPC (\u20ac\/MWh)",
·    ·    ·    "id":"1013",
·    ·    ·    "groupId":null,
·    ·    ·    "attributes":{
·    ·    ·    ·    "title":"PVPC (\u20ac\/MWh)",
·    ·    ·    ·    "description":null,
·    ·    ·    ·    "color":"#ffcf09",
·    ·    ·    ·    "type":null,
·    ·    ·    ·    "magnitude":"price",
·    ·    ·    ·    "composite":false,
·    ·    ·    ·    "last-update":"2020-07-09T20:18:31.000+02:00",
·    ·    ·    ·    "values":[
·    ·    ·    ·    ·    {
·    ·    ·    ·    ·    ·    "value":100.88,
·    ·    ·    ·    ·    ·    "percentage":0.7100225225225226,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T00:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":98.8,
·    ·    ·    ·    ·    ·    "percentage":0.7155790541029913,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T01:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":94.67,
·    ·    ·    ·    ·    ·    "percentage":0.7267214247332463,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T02:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":92.15,
·    ·    ·    ·    ·    ·    "percentage":0.7359635811836115,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T03:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":89.68,
·    ·    ·    ·    ·    ·    "percentage":0.7434303241316422,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T04:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":90.05,
·    ·    ·    ·    ·    ·    "percentage":0.7414573898723755,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T05:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":95.76,
·    ·    ·    ·    ·    ·    "percentage":0.7232628398791541,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T06:00:00.000+02:00"},
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":100.55,
·    ·    ·    ·    ·    ·    "percentage":0.7102995196383158,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T07:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":100.92,
·    ·    ·    ·    ·    ·    "percentage":0.7015153621576532,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T08:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":100.27,
·    ·    ·    ·    ·    ·    "percentage":0.7021217001610531,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T09:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":99.4,
·    ·    ·    ·    ·    ·    "percentage":0.7041654859733636,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T10:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":98.78,
·    ·    ·    ·    ·    ·    "percentage":0.706126242047323,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T11:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":98.74,
·    ·    ·    ·    ·    ·    "percentage":0.7072559272258434,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T12:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":98.19,
·    ·    ·    ·    ·    ·    "percentage":0.7079818299805322,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T13:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":97.46,
·    ·    ·    ·    ·    ·    "percentage":0.7089031131800989,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T14:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":96.8,
·    ·    ·    ·    ·    ·    "percentage":0.7113985448666128,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T15:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":93.2,
·    ·    ·    ·    ·    ·    "percentage":0.7200247218788628,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T16:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":92.99,
·    ·    ·    ·    ·    ·    "percentage":0.7204059497985745,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T17:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":96.24,
·    ·    ·    ·    ·    ·    "percentage":0.7117290341665434,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T18:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":95.3,
·    ·    ·    ·    ·    ·    "percentage":0.71487510314305,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T19:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":93.69,
·    ·    ·    ·    ·    ·    "percentage":0.7210250885023857,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T20:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":97.12,
·    ·    ·    ·    ·    ·    "percentage":0.7134880987364091,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T21:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":96.88,
·    ·    ·    ·    ·    ·    "percentage":0.7156153050672182,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T22:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":87.33,
·    ·    ·    ·    ·    ·    "percentage":0.7473684210526316,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T23:00:00.000+02:00"
·    ·    ·    ·    ·    }
·    ·    ·    ·    ]
·    ·    ·    }
·    ·    },{
·    ·    ·    "type":"Precio mercado spot (\u20ac\/MWh)",
·    ·    ·    "id":"600",
·    ·    ·    "groupId":null,
·    ·    ·    "attributes":{
·    ·    ·    ·    "title":"Precio mercado spot (\u20ac\/MWh)",
·    ·    ·    ·    ·    "description":null,
·    ·    ·    ·    "color":"#df4a32",
·    ·    ·    ·    "type":null,
·    ·    ·    ·    "magnitude":"price",
·    ·    ·    ·    "composite":false,
·    ·    ·    ·    "last-update":"2020-07-09T13:53:47.000+02:00",
·    ·    ·    ·    "values":[
·    ·    ·    ·    ·    {
·    ·    ·    ·    ·    ·    "value":41.2,
·    ·    ·    ·    ·    ·    "percentage":0.28997747747747754,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T00:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":39.27,
·    ·    ·    ·    ·    ·    "percentage":0.2844209458970088,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T01:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":35.6,
·    ·    ·    ·    ·    ·    "percentage":0.27327857526675364,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T02:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":33.06,
·    ·    ·    ·    ·    ·    "percentage":0.26403641881638845,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T03:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":30.95,
·    ·    ·    ·    ·    ·    "percentage":0.25656967586835777,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T04:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":31.4,
·    ·    ·    ·    ·    ·    "percentage":0.25854261012762453,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T05:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":36.64,
·    ·    ·    ·    ·    ·    "percentage":0.2767371601208459,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T06:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":41.01,
·    ·    ·    ·    ·    ·    "percentage":0.28970048036168405,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T07:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":42.94,
·    ·    ·    ·    ·    ·    "percentage":0.2984846378423467,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T08:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":42.54,
·    ·    ·    ·    ·    ·    "percentage":0.29787829983894687,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T09:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":41.76,
·    ·    ·    ·    ·    ·    "percentage":0.2958345140266364,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T10:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":41.11,
·    ·    ·    ·    ·    ·    "percentage":0.2938737579526771,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T11:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":40.87,
·    ·    ·    ·    ·    ·    "percentage":0.2927440727741566,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T12:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":40.5,
·    ·    ·    ·    ·    ·    "percentage":0.2920181700194679,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T13:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":40.02,
·    ·    ·    ·    ·    ·    "percentage":0.29109688681990115,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T14:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":39.27,
·    ·    ·    ·    ·    ·    "percentage":0.28860145513338725,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T15:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":36.24,
·    ·    ·    ·    ·    ·    "percentage":0.2799752781211372,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T16:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":36.09,
·    ·    ·    ·    ·    ·    "percentage":0.2795940502014255,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T17:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":38.98,
·    ·    ·    ·    ·    ·    "percentage":0.28827096583345657,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T18:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":38.01,
·    ·    ·    ·    ·    ·    "percentage":0.28512489685694997,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T19:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":36.25,
·    ·    ·    ·    ·    ·    "percentage":0.2789749114976143,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T20:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":39,
·    ·    ·    ·    ·    ·    "percentage":0.28651190126359094,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T21:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":38.5,
·    ·    ·    ·    ·    ·    "percentage":0.2843846949327818,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T22:00:00.000+02:00"
·    ·    ·    ·    ·    },{
·    ·    ·    ·    ·    ·    "value":29.52,
·    ·    ·    ·    ·    ·    "percentage":0.25263157894736843,
·    ·    ·    ·    ·    ·    "datetime":"2020-07-10T23:00:00.000+02:00"
·    ·    ·    ·    ·    }
·    ·    ·    ·    ]
·    ·    ·    }
·    ·    }
·    ]
}

Tenir clars els nivells és fonamental, ja que en cas contrari no obtindrem cap resultat de json_doc. Si ens fixem en les dades rebudes, veiem que les claus delimiten parelles de l'estil paràmetre:valor i els claudàtors delimiten conjunts tancats entre claus. En el primer nivell el paràmetre és included; en el segon nivell estem en el primer element del claudàtor, o sigui 0; en el tercer nivell el paràmetre és attributes; en el quart és values; en el cinquè tornem a estar en un claudàtor i és l'onzè valor, 11, i en el sisè, i darrer, el paràmetre és value. Els índexs de json_doc ens quedaran així:

    json_doc["included"][0]["attributes"]["values"][11]["value"]

Un detall important és que en les dades rebudes hi surt un caràcter unicode (\u20ac\, que correspon a €), per això cal avisar a la biblioteca que tenim dades unicode. Ara ja podem passar a fer el programa.

// Aquest programa està parcialment basat en un exemple que es troba a 
// partir de la pàgina 106 del número 29 de la revista HackSpace magazine
//
// Avisem que hi ha caràcters unicode (ha d'estar abans de carregar la biblioteca)
#define ARDUINOJSON_DECODE_UNICODE 1
#include <ArduinoJson.h>    // Carreguem la biblioteca json
#include <SPI.h>    // Carreguem la biblioteca SPI
#include <WiFiNINA.h>    // Carreguem la biblioteca WiFiNINA
const char idXarxa[] = "xarxa-wifi";    // Nom del punt d'accés 
const char contrasenya[] = "contrasenya-wifi";    // Contrasenya de connexió 
char server[] = "apidatos.ree.es";
char pagina[] = "/es/datos/mercados/precios-mercados-tiempo-real?start_date=2020-07-11T00:00&end_date=2020-07-11T24:00&time_trunc=hour";
unsigned long darreraConnexio = 0;
const unsigned long periodeConnexio = 900000UL;    // 15 min
String line = ""; 
int status = WL_IDLE_STATUS;
WiFiSSLClient client;
void setup(){
    Serial.begin(9600);    // Monitor sèrie
    while (!Serial) {
        ;    // Esperem que l'usuari obri el monitor sèrie
    }
    if (WiFi.status() == WL_NO_MODULE) {
        Serial.println("No s'ha trobat el dispositiu Wi-Fi");
        while (true);    // Bloquegem el programa
    }
    String versio = WiFi.firmwareVersion();
    if (versio < "1.0.0") {
        Serial.println("Convindria actualitzar el firmware");
    }
    while (status != WL_CONNECTED) {
        Serial.print("Connectant a la xarxa ");
        Serial.println(idXarxa);
        status = WiFi.begin(idXarxa, contrasenya);
        delay(10000);    // Ho tornarem a intentar passats 10 s
    }
    Serial.print("Connectat a "); 
    Serial.println(WiFi.SSID());
    Serial.print("Estat de la connexió: ");
    Serial.println(WiFi.status()); 
    Serial.print("Adreça IP del dispositiu: ");
    Serial.println(WiFi.localIP()); 
    Serial.print("Intensitat del senyal: ");
    Serial.print(WiFi.RSSI()); 
    Serial.println(" dBm");
    Serial.println(); 
    Serial.println("Anem a connectar al servidor");
}
void loop(){
  while (client.available()) {
    line = client.readStringUntil('\r');
    Serial.println(line);
    if (line.indexOf('{')>=0) {
      Serial.println("Tractem les dades"); 
      DynamicJsonDocument json_doc(8000);
      DeserializationError json_error = deserializeJson(json_doc, line);
      if (json_error) { 
        Serial.print("Error   ");
        Serial.println(json_error.c_str());
      } else {
        Serial.print("Preu 11 h [€/MWh]:  ");
        String preu=json_doc["included"][0]["attributes"]["values"][11]["value"];
        Serial.println(preu);
      }
    }
  }
  if (millis() - darreraConnexio > periodeConnexio) {
    client.stop();
    if (client.connect(server, 443)) {
      Serial.println("S'ha fet la connexió al servidor");
      client.print("GET ");
      client.print(pagina);
      client.println(" HTTP/1.1");
      client.print("Host: ");
      client.println(server);
      client.println("Connection: close");
      client.println();
      // Guardem quan hem fet la connexió
      darreraConnexio = millis();
    } else {
      Serial.println("Ha fallat la connexió");
    }
  }
}

La mida de json_doc és difícil de saber. Si posem un valor massa petit no hi cabrà tot i obtindrem un error però si el posem massa gran ens podem quedar sense memòria. En aquest cas hem posat 8000 després d'haver provat 7000 i haver obtingut un error.

En aquest programa hi ha la condició que el monitor sèrie estigui obert per a que el programa comenci a funcionar. Això ens permet seguir tot el procés encara que triguem a obrir el monitor sèrie. Quan el microcontrolador hagi de funcionar de manera independent de l'ordinador caldrà eliminar les línies següents per evitar aquest bloqueig.

    while (!Serial) {
        ;    // Esperem que l'usuari obri el monitor sèrie
    }

Podríem millorar el programa fent que consultés la data i l'hora i les fes servir per construir la URL i per saber quin dels valors ha de cercar en les dades rebudes.

 

 

 

 

 

 

 

 

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.