Objetivos

 

  • Más cosas con el WIFI Shield CC3000.
  • Conocer el servicio de OpenWeatherMap.
  • Consultar el servicio JSON para conocer el tiempo
  • Desarrollar un pequeño método para consultar servicios JSON  de respuesta reducida.
  •  

    Material requerido.

     

      WIFI CC3000

    Los servicios JSON

     

    En las últimas sesiones hemos jugado un poco con el nuevo Shield WIFI  de Arduino que incorpora el chip de TI CC3000. Es un shield que me gusta. No es caro, se maneja cómodamente con la librería de Adafruit, y es rápido en su operación.

    Hasta la fecha el único defecto que le he encontrado es que tiene una cierta tendencia a colgarse con algunas operaciones, como ya lo advierte Adafruit si no cerramos la conexión al salir, pero por lo demás es un Shield WIFI que os recomiendo con gusto porque le veo grandes ventajas.

    Vamos a seguir un poco más dándole vueltas, y más importante, vamos a usarlo para insistir con la cuestión de los servidores JSON, que nos proporcionan amablemente, información gratuita sobre lo que queramos y que podemos aprovechar en nuestros proyectos para mil y una cosa.

    Podemos interrogar a estos servidores JSON, tanto desde el WIFI shield CC3000 que usaremos aquí, como con cualquiera de los otros medios de conexión a Ethernet que ya vimos en el pasado, como el Shield Ethernet, ESP-8266 o HLK-RM04.

    El concepto de consultar a uno de estos servidores es independiente de la plataforma que usemos para conectarnos a Internet.

  • Aunque naturalmente, habrá que adaptar los programas a la plataforma que usemos para conectar. 
  •  

    En la sesión previa usamos un ejemplo de Adafruit incluido en la librería CC3000 para conocer la posición aproximada de nuestro Arduino, mediante el servicio JSON freegeoip.net; En esta sesión continuaremos probando otro servicio JSON disponible: www.openweathermap.com.

    Este servicio nos proporciona información meteorológica a partir del nombre de una ciudad o de la posición GPS del lugar mediante su latitud y longitud y me ha parecido que es una información bastante útil para más de un proyecto.

    Imagínate por ejemplo que disponemos de un panel solar para alimentar nuestro proyecto en el campo. Podemos usar esta información (Conectándonos un a Internet cada 3 o 4 días, mediante  GSM o GPRS o lo que sea) para saber a qué hora amanece y anochece (incluido en la información de openweathermap) y usándola para orientar un par de servos, que mueven la plataforma del panel.

    Conociendo la latitud y longitud de nuestra situación, que aprendimos a determinar en el programa de la sesión anterior y un mínimo de geometría (¡Ay Dios mío!, no os vayáis aun) podemos orientar desde el primer momento de luz nuestra plataforma al sol, para garantizar la máxima exposición del panel. ¿Qué os parece?

     

    El servicio meteorológico OpenWeatherMap

     

    Hay muchos servicios meteorológicos en Internet, pero este me gusta porque se puede usar gratis (Mientras no te pases) y porque es capaz de darte información amplia de prácticamente cualquier punto del planeta.

    Te entrega un montón de información útil y nos permite configurar la consulta de varias maneras diferentes. Por ejemplo puedes consultar el tiempo de una ciudad concreta por nombre y entre otras cosas te devuelve la posición GPS de la ciudad

    O puedes consultar la posición GPS de un punto mediante latitud y longitud y te devuelve el tiempo además del nombre de la ciudad. Vamos a hacer un par de consultas para jugar con la web. Si vais a la página de openweathermap y consultáis el tiempo de vuestra ciudad, recibiréis algo parecido a esto:

    Servicio meteorologico JSON

     

    Cuando le deis al botón Search, recibiréis una respuesta legible, parecida a esto otro:

    Servicio meteorologico

     

    Pero claro está, esto no es muy conveniente para hacer una consulta automática, por lo que los chicos de OpenWeatherMap, nos proporcionan un servicio de información, un poco más eficaz, a través de consultas JSON, en el que podemos interrogar a la base de datos  meteorológica con ciertas condiciones y opciones. Vamos a ver cómo.

     

    Consultas JSON en OpenWeatherMap

     

    Recordad que JSON es un formato ligero para el intercambio de datos y que al ser simple, es ideal para hacer consultas como las que vamos a hacer aquí, sin complicarnos mucho la vida.

  • Leer una respuesta JSON, puede dejarnos sin memoria bastante deprisa en Arduino, porque no vamos nada sobrados de memoria. Por eso estoy utilizando un Arduino MEGA que va un poco mejor que un UNO, del que tengo serias dudas de que pueda correr este programa.
  • En Internet encontrareis bastantes historias de miedo acerca de que hay que usar librerías específicas para poder leer una respuesta JSON, pero aquí vamos a intentar leer a pelo el mensaje y coger los datos que nos interesan sin más películas.
  •  

    Es importante que entendáis que un cierto servidor como el que nos ocupa, acepta consultas en un cierto formato (que deberéis estudiar) y que nos devuelve una respuesta simple en modo texto plano que deberemos interpretar.

  • Precisamente hemos elegido JSON porque es más simple de leer que el XML.
  •  

    Suponed que queremos saber el tiempo en nuestra ciudad. La mía es Bilbao (Si, del mismo centro ¿Qué pasa?), podríamos hacer la consulta del siguiente modo: Id a vuestro navegador y escribir:

    http://api.openweathermap.org/data/2.5/weather?q=bilbao,es

    La respuesta será algo parecido a esto:

    {«coord»:{«lon»:-2.93,»lat»:43.26},»sys»:{«message»:0.4595,»country»:»ES»,»sunrise»:1431319900,»sunset»:1431372267},»weather»:[{«id»:800,»main»:»Clear»,»description»:»sky is clear»,»icon»:»02d»}],»base»:»stations»,»main»:{«temp»:299.115,»temp_min»:299.115,»temp_max»:299.115,»pressure»:989.67,»sea_level»:1032.61,»grnd_level»:989.67,»humidity»:46},»wind»:{«speed»:0.11,»deg»:21.5034},»clouds»:{«all»:8},»dt»:1431361042,»id»:3128026,»name»:»Bilbao»,»cod»:200}

    Que aunque mucho menos legible, contiene toda la información que nos entrega de forma estructurada. Podéis comprobar que la información ofrecida incluye longitud, latitud, horas de amanecer y oscurecer, tiempo despejado, temperaturas máximas y mínimas, presión humedad, velocidad del viento y más.

    Pero si os fijáis, veréis que nos da una temperatura de 299.115 lo que difícilmente serán grados Centígrados, que a este lado del charco son los únicos que entendemos. Por eso vamos a pasarle alguna parámetro que nos lo ponga en ºC.

    Disponemos de una opción para convertir los valores de temperatura así:

    http://api.openweathermap.org/data/2.5/weather?q=Bilbao,es&units=metric

    Con lo que la respuesta del servidor pasa a ser:

    {«coord»:{«lon»:-2.93,»lat»:43.26},»sys»:{«message»:0.0142,»country»:»ES»,»sunrise»:1431319900,»sunset»:1431372267},»weather»:[{«id»:800,»main»:»Clear»,»description»:»sky is clear»,»icon»:»02d»}],»base»:»stations»,»main»:{«temp»:25.965,»temp_min»:25.965,»temp_max»:25.965,»pressure»:989.67,»sea_level»:1032.61,»grnd_level»:989.67,»humidity»:46},»wind»:{«speed»:0.11,»deg»:21.5034},»clouds»:{«all»:8},»dt»:1431361766,»id»:3128026,»name»:»Bilbao»,»cod»:200}

    Y lo de casi 26ºC con nubes, sí que parece Bilbao hoy.

    O si preferís conocer el tiempo en digamos Veracruz en México basta con escribir

    http://api.openweathermap.org/data/2.5/weather?q=veracruz,mx&units=metric

  • No sé si en México usáis grados centígrados o Fahrenheit, ya me diréis.
  •  

    Si preferís conocer el tiempo en función las coordenadas GPS, que ya vimos como conseguir e la sesión previa, podemos usar una consulta como esta:

    api.openweathermap.org/data/2.5/weather?lat=40.40&lon=-3.68

    Hay muchas más opciones disponibles, pero esta sesión no pretende ser un tutorial sobre OpenWeatherMap, sino más bien un tutorial sobre como leer esta información JSON desde nuestros Arduinos.

    Vamos a ver como montamos un programa que nos haga la consulta.

     

    Consultando JSON desde Arduino

     

    Para poder hacer una consulta a un servidor JSON, lo primero que necesitamos es lógicamente, conocer el servidor y después tener una consulta disponible que nos pueda responder. En nuestro caso consultaremos el servidor de OpenWeatherMap y ya hemos visto un poco la sintaxis que vamos a usar.

    Para ello partiremos del ejemplo WebClient incluido en la librería Adafruit CC3000, que me imagino ya no hace falta insistir en como descargarla y demás.

    Lo primero vienen las declaraciones habituales, que son calcadas a las sesiones anteriores, y no vamos a insistir en ello, pero incluiremos la librería time para manejar fechas: (Aquí tenéis el programa completo GeoLocation2)

    #include <Adafruit_CC3000.h>
    #include <SPI.h>
    #include <Time.h>
    
    #define ADAFRUIT_CC3000_IRQ   3  // MUST be an interrupt pin!
    #define ADAFRUIT_CC3000_VBAT  5
    #define ADAFRUIT_CC3000_CS    10
    
    Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT,SPI_CLOCK_DIVIDER); // you can change this clock speed
    
    #define WLAN_SSID       "charly"    // cannot be longer than 32 characters!
    #define WLAN_PASS       "contrase"
    #define WLAN_SECURITY   WLAN_SEC_WPA2
    
    Adafruit_CC3000_Client client;

    Fijaros que en la última línea creamos una instancia de un cliente web. Usaremos este cliente más adelante para consultar al servidor.

    Vamos a definir la dirección del servidor y la consulta que usaremos y que vimos antes:

    #define WEBSITE "api.openweathermap.org"
    #define WEBPAGE "http://api.openweathermap.org/data/2.5/weather?q=Bilbao,es&units=metric"
    uint32_t ip;

    Vamos a conectarnos a la red igual que en las sesiones previas:

    Serial.print("\nConectando a "); Serial.print(WLAN_SSID);
    if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY))
        {    Serial.println("Failed!");
             while(1) ;
        }
    Serial.println("...OK!");

    Pero esta vez (Y por hacer algo diferente que si no me aburro), vamos a dejar con comentario la parte de solicitar una dirección IP al servidor DHCP, y en su lugar esperaremos a que el CC3000 se conecte a la red WIFI él solito, con los valores que recibió en la última conexión valida.

    while (! displayConnectionDetails())
             delay(1000);

    Como comprobareis la conexión es limpia  y más rápida que volver a solicitar una IP.

  • Claro que para esto es necesario haberse conectado previamente de modo satisfactorio. No es necesario solicitar una y otra vez la conexión al arrancar, ya que el chip recuerda los últimos valores.
  •  

    Vamos ahora, a la parte interesante.  Lo primero resolver la dirección del servidor por el nombre que definimos en WEBSITE arriba:

    Serial.print(WEBSITE); Serial.print(" -> ");
    while (ip == 0)
       {   if (! cc3000.getHostByName(WEBSITE, &ip))
                 Serial.println("Error en la resolucion del nombre!");
           delay(500);
       }

    Ahora vamos a intentar conectar a la página:

    Adafruit_CC3000_Client www = cc3000.connectTCP(ip, 80);
    if (www.connected())
       {    www.print("GET ");
            www.print(WEBPAGE);
            www.print(" HTTP/1.1\r\n");
            www.print("Host: "); www.print(WEBSITE); www.print("\r\n");
            www.print("\r\n");
            www.println();
       }
    else
       {    Serial.println("Error de conexion.");
            return;
       }

    Lo único que hacen estas líneas presentar una petición HTTP 1.1 con el formato adecuado, para no complicarnos la vida  y entregar el comando almacenado en WEBSITE, ahora hay que recoger la respuesta del servidor JSON.

    unsigned long lastRead = millis();
    String S ="";
    
    while (www.connected() && (millis() - lastRead < IDLE_TIMEOUT_MS))
       {   while (www.available())
             {    char c = www.read();
                  Serial.print(c);
                  S = S + c ;
                  lastRead = millis();
             }
       }
    www.close();
    Serial.println( S) ;

    Mientras la sesión este abierta y no salte el timeout, vamos leyendo carácter a carácter la respuesta en c y además de imprimirlo, nos lo añadimos al string S, para procesarlo después.

  • Cuidado: Lo guardo en el String S porque sé que la respuesta es menor de 512 caracteres de largo. Pero si intentáis almacenar una página normal en un String así, lo más probable es que tu Arduino se quede sin memoria y se cuelgue.
  • Si no tienes clara la longitud del mensaje, no intentes hacer esto. Recuerda que la memoria es un bien muy escaso en Arduino y fácilmente puedes agotar toda la disponible.
  •  

    Si el programa acabase aquí, veríamos una salida a consola similar a esto:

    Consola Arduino

  • El mensaje JSON del servidor comienza, en la línea en que se lee {“coord”: y se prolonga bastante hacia la derecha, pero no salta porque no contiene ningún salto de línea.
  •  

    Ya tenemos ya en el String S, el resultado de la consulta con lo que podemos procesarlo a nuestro gusto.  Vamos a interpretar la respuesta y convertirla en información útil. Veamos como es la respuesta:

    {«coord»:{«lon»:-2.93,»lat»:43.26},»sys»:{«message»:0.0142,»country»:»ES»,»sunrise»:1431319900,»sunset»:1431372267},»weather»:[{«id»:800,»main»:»Clear»,»description»:»sky is clear»,»icon»:»02d»}],»base»:»stations»,»main»:{«temp»:25.965,»temp_min»:25.965,»temp_max»:25.965,»pressure»:989.67,»sea_level»:1032.61,»grnd_level»:989.67,»humidity»:46},»wind»:{«speed»:0.11,»deg»:21.5034},»clouds»:{«all»:8},»dt»:1431361766,»id»:3128026,»name»:»Bilbao»,»cod»:200}

    La forma más fácil que se me ocurre de buscar la información que buscamos, es escribir una función que busque donde empieza lo que queremos,  y después donde está la marca de fin del dato.

    Por ejemplo, si quisiera buscar la hora a que amanece necesitaría buscar el String  “sunrise\»:” y para saber dónde acaba el dato, buscaría el próximo “,\””.

  • De nuevo, para buscar el literal- sunrise”:- necesito indicarle a C++ que hay unas comillas dentro de lo que busco y que no debe confundirlas con las de cierre del String. Por eso escribimos -\”-
  • Podríamos buscar solo la coma, pero me quedo más tranquilo buscando un par de caracteres.
  •  

    Vamos a escribir una pequeña función, que busque este par de strings dentro de S y que nos devuelva el texto que hay entre ambas. (Esta es una función muy fácil, intentad escribirla vosotros antes de mirar  mi solución)

    String GetInfo( String &S, String t1, String t2 )
       {   int n = S.indexOf( t1 );
           if (n)
              {    S = S.substring(n);         //Despreciamos la parte inicial de S
                   int k = S.indexOf( t2 );    // Buscamos la siguiente coma
                   String var = S.substring(t1.length() , k);
                   S = S.substring( k+1);  // Recortamos S
                   return(var);
               }
       }

    Le pasamos a la función en primer lugar el String S, por dirección, con lo que podrá modificar el contenido aunque no sea una variable definida en esta función ni pública. ( Si esto os despista, revisad la sesión de punteros Arduino)

    Después le pasamos los dos String que buscamos, en el orden en que va a encontrárselos. Y luego buscamos el t1 en S y si lo encuentra apunta su posición en n, y después asigna la posición de t2 en k. La línea:

    S = S.substring(n);

    Va a cortar todo lo que hay en S, por delante de la etiqueta que buscamos, ya que al pasarle solo un único parámetro, coge desde la posición n hasta el final del String. La instrucción:

    S.substring( 5, k )

    Nos devolvería la etiqueta y el valor que buscamos, pero para quitar la etiqueta nos devolvería un String empezando en el carácter 5 y cogiendo los siguientes k caracteres. Como S aun contiene la etiqueta buscada la tenemos que eliminar de nuestra respuesta, para lo que usamos var:

    String var = S.substring(t1.length() , k);

    Para eliminar la etiqueta basta con empezar a coger más allá de la longitud de la etiqueta, que medimos con t1.length(). Y ahora volvemos a recortar S eliminando la etiqueta inicial y devolvemos el resultado ya limpio.

    S = S.substring( k+1);  // Recortamos S
    return(var);

    Vamos ya a ver como imprimimos la salida buscando los datos que queremos. Para saber la hora a la que amanece podemos hacer:

    sunrise = GetInfo( S, "sunrise\":", ",\"") ;
    Serial.print("Amanece : \t");
    Serial.print(sunrise);

    Llamamos a GetInfo() con los valores que nos interesan y la respuesta en mi caso es:

    Amanece :          1431406232

    La respuesta del servidor nos devuelve la hora GMT ( Greenwich Meridian Time u hora del meridiano Greenwich) que está en formato Unix time, por lo que tenemos que procesarlo un poco más.

    En primer lugar hay que hacer otra función (Si, lo siento) que nos imprima la fecha en un formato comprensible:

    void formatTime( time_t t1)
       { Serial.print(day(t1)) ; Serial.print(":");
         Serial.print(month(t1)) ; Serial.print(":");
         Serial.print(year(t1)) ; Serial.print(":");
         Serial.print(" / ");
         Serial.print(hour(t1)) ; Serial.print(":");
         Serial.print(minute(t1)) ; Serial.print(":");
         Serial.println(second(t1)) ; Serial.print(":");
       }

    Que no creo que necesite explicación para nadie que se haya leído la sesión de la librería time.

    Ahora ya podemos sacar la información de las horas del amanecer y del crepúsculo, pero para que no sea tan fácil, (¿Qué os creíais?)  En el caso de España, en horario de verano llevamos los relojes dos horas por delante del tiempo GMT, por lo que tenemos que corregir la hora:

    time_t ajuste = 7200 ;   // Ajuste = 2 * 60 * 60 = 7200 segundos
    
    sunrise = GetInfo( S, "sunrise\":", ",\"") ;
    Serial.print("Amanece : \t");
    Serial.print(sunrise); Serial.print ("\t");
    formatTime(sunrise.toInt()+ ajuste);
    
    sunrise = GetInfo( S, "sunset\":", "},") ;
    Serial.print("Anochece : \t");
    Serial.print(sunrise);Serial.print ("\t");
    formatTime(sunrise.toInt() + ajuste);          // Convertimos el String a entero

    Podemos sacar algunos otros valores por jugar:

    Descripcion = GetInfo( S, "description\":\"", "\",") ;
    Serial.print("Descripcion : \t");
    Serial.println(Descripcion);Temp = GetInfo( S, "temp\":", ",") ;
    Serial.print("Temperatura : \t");
    Serial.println(Temp);Humedad = GetInfo( S, "humidity\":" , "},") ;
    Serial.print("Humedad : \t");
    Serial.println(Humedad);

    Y para que no vuelva

    Serial.flush();
    exit(0) ;

    El resultado será algo así:

    Leyendo JSON meteorologico con Arduino

    Como veis, hemos conseguido una serie de valores interesantes mediante una consulta sencilla a un servidor JSON de meteorología. No está mal.

    Por si ultimo, si buceáis un poco en la red, encontrareis bastantes servicios de información que os presentaran respuestas en formato JSON y que rápidamente podréis usar en vuestros programas.

     

    Resumen de la sesión

     

  • Hemos seguido jugando con el  Shield WIFI CC3000, y visto algunas cosas nuevas, como solicitar información a una página web.
  • Presentamos un servicio meteorológico JSON Openweathermap.
  • Consultamos el servicio Openweathermap y obtuvimos datos variados  de interés.
  •   

    Deja una respuesta