weather forecast - how to parse rain form this api call??

Hello,

I managed to get to Arduino Yun this info via OpenWeatherMap API:

{"city":{"id":7530858,"name":"Poznań","coord":{"lon":16.901541,"lat":52.400631},"country":"PL","population":0,"sys":{"population":0}},"cod":"200","message":0.0029,"cnt":3,"list":[{"dt":1446865200,"main":{"temp":283.19,"temp_min":281.836,"temp_max":283.19,"pressure":1026.61,"sea_level":1035.76,"grnd_level":1026.61,"humidity":93,"temp_kf":1.35},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10n"}],"clouds":{"all":88},"wind":{"speed":3.96,"deg":228},"rain":{"3h":0.0825},"sys":{"pod":"n"},"dt_txt":"2015-11-07 03:00:00"},{"dt":1446876000,"main":{"temp":283.27,"temp_min":281.996,"temp_max":283.27,"pressure":1026.64,"sea_level":1035.93,"grnd_level":1026.64,"humidity":93,"temp_kf":1.27},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":68},"wind":{"speed":3.77,"deg":223.005},"rain":{"3h":0.03},"sys":{"pod":"d"},"dt_txt":"2015-11-07 06:00:00"},{"dt":1446886800,"main":{"temp":285.56,"temp_min":284.361,"temp_max":285.56,"pressure":1026.77,"sea_level":1035.96,"grnd_level":1026.77,"humidity":93,"temp_kf":1.2},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":92},"wind":{"speed":3.46,"deg":211.503},"rain":{"3h":0.5175},"sys":{"pod":"d"},"dt_txt":"2015-11-07 09:00:00"}]}

This is in JSON format. I tried this library, but without luck (even the examples don't work on my Yun): Releases · bblanchon/ArduinoJson · GitHub

The only thing that I want to get from this data is: is there a phrase "rain", "thunderstorm" or "snow".
If yes, than I would like to flag it with some boolean and I will be a happy person:)

Thanks for help.
BTW - I'm terrible while working with text on Arduino (or any programming with text data for this matter).

If you have the data in memory you can use strstr(str1, str2).
The function returns the first occurence of str2 in str1 or NULL.
So the return value can be used to set bool variables:

const char json[] = "{\"city\":{\"id\":7530858,\"name\":\"Poznań\",\"coord\":{\"lon\":16.901541,\"lat\":52.400631},\"country\":\"PL\",\"population\":0,\"sys\":{\"population\":0}},\"cod\":\"200\",\"message\":0.0029,\"cnt\":3,"
                    "\"list\":[{\"dt\":1446865200,\"main\":{\"temp\":283.19,\"temp_min\":281.836,\"temp_max\":283.19,\"pressure\":1026.61,\"sea_level\":1035.76,\"grnd_level\":1026.61,\"humidity\":93,\"temp_kf\":1.35},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":88},\"wind\":{\"speed\":3.96,\"deg\":228},\"rain\":{\"3h\":0.0825},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2015-11-07 03:00:00\"},{\"dt\":1446876000,"
                    "\"main\":{\"temp\":283.27,\"temp_min\":281.996,\"temp_max\":283.27,\"pressure\":1026.64,\"sea_level\":1035.93,\"grnd_level\":1026.64,\"humidity\":93,\"temp_kf\":1.27},"
                    "\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":68},\"wind\":{\"speed\":3.77,\"deg\":223.005},\"rain\":{\"3h\":0.03},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2015-11-07 06:00:00\"},{\"dt\":1446886800,\"main\":{\"temp\":285.56,\"temp_min\":284.361,\"temp_max\":285.56,\"pressure\":1026.77,\"sea_level\":1035.96,\"grnd_level\":1026.77,\"humidity\":93,\"temp_kf\":1.2},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10d\"}],\"clouds\":{\"all\":92},\"wind\":{\"speed\":3.46,\"deg\":211.503},\"rain\":{\"3h\":0.5175},\"sys\":{\"pod\":\"d\"},\"dt_txt\":\"2015-11-07 09:00:00\"}]}"
                    ;
bool rain;
bool thunderstorm;
bool snow;
void setup() {
  Serial.begin(115200);
  
  rain = strstr(json, "\"rain\"");
  thunderstorm = strstr(json, "\"thunderstorm\"");
  snow = strstr(json, "\"snow\"");
  
  Serial.print("rain ");
  Serial.println(rain);
  Serial.print("thunderstorm ");
  Serial.println(thunderstorm);
  Serial.print("snow ");
  Serial.println(snow);
}
void loop() {}
rain 1
thunderstorm 0
snow 0

Dear lord, that's a 1284 byte long string x_x...

Ideally, you would read the stream rather than storing the whole thing as a string and try to parse it. Doing that is actually a pretty big job. I'd recommend you search for "arduino JSON library", pick one that will work for you and use it.

If I was writing it, I'd have a function that takes a stream and a callback function. The callback function would get a picture of the object stack. The key feature would be that it uses malloc and free to only keep the current state of the stack as it is read, this would allow arbitrary-sized JSON to be processed.

The callback would get a 'depth' parameter and an arry of element that look like this:

struct JsonStack {
  enum {
    ARRAY_ELEMENT,
    OBJECT_ITEM,
    CHAR, INT, LONG, STRING // etc
  } type;
  union {
    int arrayElement;
    char *objectItem;
    int pInt;
    long pLong;
    char pChar;
    char *pString;
  } element;
}

The user code then makes use of the callbacks to extract what it wants from the stream.

But its a big job.

It's a shame they used the 32u4 on the yun - if they'd have used a 1284p, you could just throw the string into ram and not think twice about it... 2 or 2.5k sram just isn't enough when dealing with the internet.

I wonder if there's a way to get the yun-side of the board to do that for you?

A (cheating) approach would be to write a simple PHP page that fetched that chunk of json and checked for the appropriate keywords (or maybe even stripped all the junk out and echoed more compact JSON?), and put that onto a cloud hosted webserver (Amazon Web Services will give you 1 year free). I do this to lash together API's that are troublesome to interact with directly.

Ok, thanks for each and every comment. Cool that so many people come up with different solutions to help me. Thanks. PaulMurrayCbr - you made me think about this library more. And I made it work!

The case with Arduino Yun is that the Serial on it is not a true one, but a virtual, and so it doesn't restart the program when you open serial on PC. This is why in setup if you are on Yun you need to add:

  while (!Serial) {
  ; // wait for serial port to connect. Needed for Leonardo only
  }

This fixed the problem for me.
Still I have a problem with the multilevel Json in this API.
The library addresses the probelm here:
(first example) GitHub - bblanchon/ArduinoJson: 📟 JSON library for Arduino and embedded C++. Simple and efficient.

But the call I get is much more complexed

Thanks for help once again.

Now I have another probelem. The API call that i get is a very long one and complex. In fact is is nested multiple times. How to parse with this library the weather forecast name (eg. Rain) or Id? It is there multiple times and on deep level of this array??

I've found on the wiki of this library an example of this but my situation is much more complex:(

Thanks,

Ok I manged to make it work:) One condition - i had to use a smaller version of this api call (only 3 hours ahead not 9). When I try to make the "char json[]" any longer the compiler crashes.

Still there is a new problem:)

Here is my code:

#include <ArduinoJson.h>

char json[] = "{\"city\":{\"id\":7530858,\"name\":\"Poznań\",\"coord\":{\"lon\":16.901541,\"lat\":52.400631},\"country\":\"PL\",\"population\":0,\"sys\":{\"population\":0}},\"cod\":\"200\",\"message\":0.012,\"cnt\":1,\"list\":[{\"dt\":1446940800,\"main\":{\"temp\":286.55,\"temp_min\":286.549,\"temp_max\":286.55,\"pressure\":1024.19,\"sea_level\":1033.33,\"grnd_level\":1024.19,\"humidity\":95,\"temp_kf\":0},\"weather\":[{\"id\":500,\"main\":\"Rain\",\"description\":\"light rain\",\"icon\":\"10n\"}],\"clouds\":{\"all\":80},\"wind\":{\"speed\":5.6,\"deg\":223.502},\"rain\":{\"3h\":0.01},\"sys\":{\"pod\":\"n\"},\"dt_txt\":\"2015-11-08 00:00:00\"}]}";

  
void setup() {
  Serial.begin(9600);
  while (!Serial) {
  ; // wait for serial port to connect. Needed for Yun.
  }
}

void loop() {
  parsing(json);
  delay(15000);
}

void parsing(char json1[]){
  StaticJsonBuffer<800> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(json1);
  JsonArray& list = root ["list"];
  JsonArray& weather0 = list[0] ["weather"];

  int cnt = root ["cnt"]; // this was just for checking
  const  char* id0 = weather0[0] ["id"]; // this is the real data I was looking for
  
  if (!root.success()) {
    Serial.println("parseObject() failed");
    return;
  }
  Serial.println(id0);
  Serial.println(cnt);
  }

The problem is - it works only for the first time, and than ... it prints "parseObject() failed"

I figured this one out.

  1. I decided to go with a different API from Weather Underground because it's more accurate in my (Poland) location. This made the problem even worse ~47 kb per API request.
  2. And so i use the Temboo. Theire HTTP get service makes the API call, and theire output filter, gives Yun just the data I need.

Below you have the full code, I use right now.

#include <Bridge.h>
#include <Temboo.h>
#include "TembooAccount.h" // contains Temboo account information, as described below
#include "LedControl.h"
//#include "dod_funkcje.h"

#define LDR 1

LedControl lc = LedControl(12, 11, 10, 1);

int state = 0; //  1 = nie pada, nie pada; 2 = pada, pada; 3 = nie pada, pada; 4 = pada, nie pada
int last_state = 0;

byte sstop[4] = {0x77, 0x77, 0x77, 0x00}; // pada
byte play[4] = {0x44, 0x66, 0x44, 0x00}; //nie pada

boolean Agata;  //0 nie pada, 1 pada
boolean Jachu;  //0 nie pada, 1 pada

unsigned long previousMillis = 0;        // will store last time LED was updated
unsigned long interval = 600000; // 10 min

int num = 0;

void setup() {
  Serial.begin(9600);

  // For debugging, wait until the serial console is connected
  delay(4000);
  while (!Serial);
  Bridge.begin();
  pinMode(LDR, INPUT);
  lc.shutdown(0, false);
  /* Set the brightness to a medium values */
  lc.setIntensity(0, 8);
  /* and clear the display */
  lc.clearDisplay(0);
}

void loop() {
  if (analogRead(LDR) >= 100) {
    unsigned long currentMillis = millis();
    if ((state == 0) || (currentMillis - previousMillis >= interval)) {
      previousMillis = currentMillis;
      num = num + 1;
      Serial.print("Running GetChoreo ");
      Serial.println(num);
      delay(100);
      
      int pogoda1;
      int pogoda2;
      int pogoda3;
      //    int pogoda4;
      //    int pogoda5;
      //    int pogoda6;
      int pogoda7;
      int pogoda8;
      int pogoda9;
      

      
      TembooChoreo GetChoreo;

      // Invoke the Temboo client
      GetChoreo.begin();

      // Set Temboo account credentials
      GetChoreo.setAccountName(TEMBOO_ACCOUNT);
      GetChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
      GetChoreo.setAppKey(TEMBOO_APP_KEY);

      // Set Choreo inputs
      GetChoreo.addInput("Password", "<myPASSWORD>");
      GetChoreo.addInput("Debug", "<myEMAILaddress>");
      GetChoreo.addInput("URL", "http://api.wunderground.com/api/<myAPIkey>/hourly/q/PL/Poznan.json");

      // Identify the Choreo to run
      GetChoreo.setChoreo("/Library/Utilities/HTTP/Get");

      // add an output filter to extract the date and time of the last report.
      GetChoreo.addOutputFilter("fctcode1", "hourly_forecast[1]/fctcode", "Response");
      GetChoreo.addOutputFilter("fctcode2", "hourly_forecast[2]/fctcode", "Response");
      GetChoreo.addOutputFilter("fctcode3", "hourly_forecast[3]/fctcode", "Response");
      //    GetChoreo.addOutputFilter("fctcode4", "hourly_forecast[4]/fctcode", "Response");
      //    GetChoreo.addOutputFilter("fctcode5", "hourly_forecast[5]/fctcode", "Response");
      //    GetChoreo.addOutputFilter("fctcode6", "hourly_forecast[6]/fctcode", "Response");
      GetChoreo.addOutputFilter("fctcode7", "hourly_forecast[7]/fctcode", "Response");
      GetChoreo.addOutputFilter("fctcode8", "hourly_forecast[8]/fctcode", "Response");
      GetChoreo.addOutputFilter("fctcode9", "hourly_forecast[9]/fctcode", "Response");
      // Run the Choreo; when results are available, print them to serial

      GetChoreo.run();

      while (GetChoreo.available()) {
        // read the name of the next output item
        String name = GetChoreo.readStringUntil('\x1F');
        name.trim(); // use “trim” to get rid of newlines

        // read the value of the next output item
        String data = GetChoreo.readStringUntil('\x1E');
        data.trim(); // use “trim” to get rid of newlines

        if (name == "fctcode1") {
          pogoda1 = data.toInt();
        } else if (name == "fctcode2") {
          pogoda2 = data.toInt();
        } else if (name == "fctcode3") {
          pogoda3 = data.toInt();
        } /*else if (name == "fctcode4") {
             pogoda4 = data.toInt();
         } else if (name == "fctcode5") {
             pogoda5 = data.toInt();
         } else if (name == "fctcode6") {
             pogoda6 = data.toInt();
         }*/else if (name == "fctcode7") {
          pogoda7 = data.toInt();
        } else if (name == "fctcode8") {
          pogoda8 = data.toInt();
        } else if (name == "fctcode9") {
          pogoda9 = data.toInt();
        } else if (name == "Error") {
          state = 5;
        }
      }
      GetChoreo.close();
      if ( pogoda1 >= 10 || pogoda2 >= 10 || pogoda3 >= 10) { //sprawdzam w ciągu trzech godzin czy bedzie padać
        Agata = 1;
      }
      else if ( pogoda1 < 10 || pogoda2 < 10 || pogoda3 < 10) {
        Agata = 0;
      }
      if (pogoda7 >= 10 || pogoda8 >= 10 || pogoda9 >= 10) { //sprawdzam w ciągu trzech godzin czy bedzie padać
        Jachu = 1;
      } else if ( pogoda7 < 10 || pogoda8 < 10 || pogoda9 < 10) {
        Jachu = 0;
      }
    Serial.print("W ciagu najbliższych trzech godzin: ");
    Serial.println(Agata);
    Serial.print("Od 6 do 9 godziny: ");
    Serial.println(Jachu);
    Serial.println("Waiting...");
    }
    if (state < 5) {
      if (Agata == 0 && Jachu == 0) {
        state = 1;
      }
      if (Agata == 1 && Jachu == 1) {
        state = 2;
      }
      if (Agata == 0 && Jachu == 1) {
        state = 3;
      }
      if (Agata == 1 && Jachu == 0) {
        state = 4;
      }
    }

    // zmieniamy wynik na matrycy
    if (state != last_state) {
      if (state == 1) {
        sstatus(0, 0); //nie pada, nie pada
      }
      if (state == 2) {
        sstatus(1, 1); // pada pada
      }
      if (state == 3) {
        sstatus(0, 1); // nie pada, pada
      }
      if (state == 4) {
        sstatus(1, 0); // pada, nie pada
      }
      if (state == 5) {
        error(); //error
      }
      last_state = state;
    }
    delay(2000); // wait 2 seconds

  } else if (analogRead(LDR) <= 25) {
    cleanMatrix();
    state = 0;
    last_state = 1;
    delay(1000);
  }
}

void sstatus(boolean a, boolean b){
  if (a == 0){
  lc.setRow(0,0,play[0]);
  lc.setRow(0,1,play[1]);
  lc.setRow(0,2,play[2]);
  lc.setRow(0,3,play[3]);
    }
    else if (a == 1){
        lc.setRow(0,0,sstop[0]);
        lc.setRow(0,1,sstop[1]);
        lc.setRow(0,2,sstop[2]);
        lc.setRow(0,3,sstop[3]);
      }
  if (b == 0){
  lc.setRow(0,4,play[0]);
  lc.setRow(0,5,play[1]);
  lc.setRow(0,6,play[2]);
  lc.setRow(0,7,play[3]);
    }
    else if (b == 1){
        lc.setRow(0,4,sstop[0]);
        lc.setRow(0,5,sstop[1]);
        lc.setRow(0,6,sstop[2]);
        lc.setRow(0,7,sstop[3]);
      }
  }

void error(){
  lc.setRow(0,0,0);
  lc.setRow(0,1,0);
  lc.setRow(0,2,0);
  lc.setRow(0,3,0);
  lc.setRow(0,4,0);
  lc.setRow(0,5,0);
  lc.setRow(0,6,0);
  lc.setRow(0,7,1);
  }
void cleanMatrix(){
  lc.setRow(0,0,0);
  lc.setRow(0,1,0);
  lc.setRow(0,2,0);
  lc.setRow(0,3,0);
  lc.setRow(0,4,0);
  lc.setRow(0,5,0);
  lc.setRow(0,6,0);
  lc.setRow(0,7,0);
  }