Friday, November 28, 2014

Pebble Watchface: Parsing JSON Weather

For my Watchface, I wanted to see current conditions (temperature / wind speed) and the forecast conditions (will it rain today or tomorrow?). There are times when the current conditions are clear skies but the forecast for later that day is rain; I wanted to be able to see both pieces of information at the same time.

The weather information on the watch is retrieved from openweathermap.org in the JSON format. I make 2 calls for weather, one for the current conditions and one for forecast.

The URL for current conditions:
  var url = "http://api.openweathermap.org/data/2.5/weather?lat=" +
      pos.coords.latitude + "&lon=" + pos.coords.longitude;

The URL for forecast conditions:
  var forecasturl = "http://api.openweathermap.org/data/2.5/forecast/daily?lat=" +
      pos.coords.latitude + "&lon=" + pos.coords.longitude;

Open Weather Map returns the weather data in JSON format.

For an example, we'll pull the weather from Traverse City, Michigan (N44.7681 W86.6222). Winter time in Michigan should have quite interesting weather.

Current Conditions URL:
http://api.openweathermap.org/data/2.5/weather?lat=44.7681&lon=-85.6222
(You can type this URL in your web browser to see the output.)

Current Conditions Raw Output:
{"coord":{"lon":-85.62,"lat":44.77},"sys":{"type":1,"id":1459,"message":0.0399,"country":"US","sunrise":1417265884,"sunset":1417298637},"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13n"}],"base":"cmc stations","main":{"temp":268.55,"pressure":1017,"humidity":79,"temp_min":267.15,"temp_max":269.15},"wind":{"speed":1.5,"deg":0},"clouds":{"all":90},"dt":1417225020,"id":5012495,"name":"Traverse City","cod":200}

Forecast Conditions URL:
http://api.openweathermap.org/data/2.5/forecast/daily?lat=44.7681&lon=-85.6222
(You can type this URL in your web browser to see the output.)

Forecast Conditions Raw Output:
{"cod":"200","message":0.0066,"city":{"id":5012495,"name":"Traverse City","coord":{"lon":-85.620628,"lat":44.763062},"country":"US","population":0},"cnt":7,"list":[{"dt":1417194000,"temp":{"day":269.15,"min":268.92,"max":270.14,"night":270.14,"eve":269.15,"morn":269.15},"pressure":1004.17,"humidity":100,"weather":[{"id":601,"main":"Snow","description":"snow","icon":"13d"}],"speed":7.88,"deg":170,"clouds":92,"snow":2},{"dt":1417280400,"temp":{"day":278.83,"min":275.01,"max":279.23,"night":278.33,"eve":278.59,"morn":275.01},"pressure":992.92,"humidity":90,"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13d"}],"speed":7.13,"deg":195,"clouds":48,"snow":0.5},{"dt":1417366800,"temp":{"day":279.25,"min":272.88,"max":279.25,"night":272.88,"eve":275.6,"morn":276.1},"pressure":993.15,"humidity":94,"weather":[{"id":800,"main":"Clear","description":"sky is clear","icon":"02d"}],"speed":7.06,"deg":232,"clouds":8},{"dt":1417453200,"temp":{"day":269.83,"min":267.55,"max":270.65,"night":267.55,"eve":269,"morn":270.65},"pressure":1020.68,"humidity":0,"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13d"}],"speed":11.08,"deg":308,"clouds":69,"snow":0.82},{"dt":1417539600,"temp":{"day":271.5,"min":268.24,"max":273.72,"night":273.72,"eve":272.95,"morn":268.24},"pressure":1020.55,"humidity":0,"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13d"}],"speed":8.29,"deg":178,"clouds":0,"snow":0.26},{"dt":1417626000,"temp":{"day":279.39,"min":271.41,"max":279.39,"night":271.41,"eve":276.42,"morn":276.33},"pressure":986.23,"humidity":0,"weather":[{"id":600,"main":"Snow","description":"light snow","icon":"13d"}],"speed":12.83,"deg":208,"clouds":93,"rain":3.44,"snow":0.22},{"dt":1417712400,"temp":{"day":272.6,"min":271.5,"max":272.6,"night":272.49,"eve":272.56,"morn":271.5},"pressure":1003.13,"humidity":0,"weather":[{"id":601,"main":"Snow","description":"snow","icon":"13d"}],"speed":13.59,"deg":285,"clouds":47,"snow":4.52}]}

I found a handy parser online where you could paste JSON data and it shows it to you in an organized fashion: http://json.parser.online.fr/

Paste the raw output above into that parser to see the results.

Parsed Current Conditions:
{
  • "coord":{
    • "lon":-85.62,
    • "lat":44.77
    }
    ,
  • "sys":{
    • "type":1,
    • "id":1459,
    • "message":0.0399,
    • "country":"US",
    • "sunrise":1417265884,
    • "sunset":1417298637
    }
    ,
  • "weather":[
    1. {
      • "id":600,
      • "main":"Snow",
      • "description":"light snow",
      • "icon":"13n"
      }
    ]
    ,
  • "base":"cmc stations",
  • "main":{
    • "temp":268.55,
    • "pressure":1017,
    • "humidity":79,
    • "temp_min":267.15,
    • "temp_max":269.15
    }
    ,
  • "wind":{
    • "speed":1.5,
    • "deg":0
    }
    ,
  • "clouds":{
    • "all":90
    }
    ,
  • "dt":1417225020,
  • "id":5012495,
  • "name":"Traverse City",
  • "cod":200
}

Parsed Forecast Conditions:
{
  • "cod":"200",
  • "message":0.0066,
  • "city":{
    • "id":5012495,
    • "name":"Traverse City",
    • "coord":{
      • "lon":-85.620628,
      • "lat":44.763062
      }
      ,
    • "country":"US",
    • "population":0
    }
    ,
  • "cnt":7,
  • "list":[
    1. {
      • "dt":1417194000,
      • "temp":{
        • "day":269.15,
        • "min":268.92,
        • "max":270.14,
        • "night":270.14,
        • "eve":269.15,
        • "morn":269.15
        }
        ,
      • "pressure":1004.17,
      • "humidity":100,
      • "weather":[
        1. {
          • "id":601,
          • "main":"Snow",
          • "description":"snow",
          • "icon":"13d"
          }
        ]
        ,
      • "speed":7.88,
      • "deg":170,
      • "clouds":92,
      • "snow":2
      }
      ,
    2. {
      • "dt":1417280400,
      • "temp":{
        • "day":278.83,
        • "min":275.01,
        • "max":279.23,
        • "night":278.33,
        • "eve":278.59,
        • "morn":275.01
        }
        ,
      • "pressure":992.92,
      • "humidity":90,
      • "weather":[
        1. {
          • "id":600,
          • "main":"Snow",
          • "description":"light snow",
          • "icon":"13d"
          }
        ]
        ,
      • "speed":7.13,
      • "deg":195,
      • "clouds":48,
      • "snow":0.5
      }
      ,
    3. {
      • "dt":1417366800,
      • "temp":{
        • "day":279.25,
        • "min":272.88,
        • "max":279.25,
        • "night":272.88,
        • "eve":275.6,
        • "morn":276.1
        }
        ,
      • "pressure":993.15,
      • "humidity":94,
      • "weather":[
        1. {
          • "id":800,
          • "main":"Clear",
          • "description":"sky is clear",
          • "icon":"02d"
          }
        ]
        ,
      • "speed":7.06,
      • "deg":232,
      • "clouds":8
      }
      ,
    4. {
      • "dt":1417453200,
      • "temp":{
        • "day":269.83,
        • "min":267.55,
        • "max":270.65,
        • "night":267.55,
        • "eve":269,
        • "morn":270.65
        }
        ,
      • "pressure":1020.68,
      • "humidity":0,
      • "weather":[
        1. {
          • "id":600,
          • "main":"Snow",
          • "description":"light snow",
          • "icon":"13d"
          }
        ]
        ,
      • "speed":11.08,
      • "deg":308,
      • "clouds":69,
      • "snow":0.82
      }
      ,
    5. {
      • "dt":1417539600,
      • "temp":{
        • "day":271.5,
        • "min":268.24,
        • "max":273.72,
        • "night":273.72,
        • "eve":272.95,
        • "morn":268.24
        }
        ,
      • "pressure":1020.55,
      • "humidity":0,
      • "weather":[
        1. {
          • "id":600,
          • "main":"Snow",
          • "description":"light snow",
          • "icon":"13d"
          }
        ]
        ,
      • "speed":8.29,
      • "deg":178,
      • "clouds":0,
      • "snow":0.26
      }
      ,
    6. {
      • "dt":1417626000,
      • "temp":{
        • "day":279.39,
        • "min":271.41,
        • "max":279.39,
        • "night":271.41,
        • "eve":276.42,
        • "morn":276.33
        }
        ,
      • "pressure":986.23,
      • "humidity":0,
      • "weather":[
        1. {
          • "id":600,
          • "main":"Snow",
          • "description":"light snow",
          • "icon":"13d"
          }
        ]
        ,
      • "speed":12.83,
      • "deg":208,
      • "clouds":93,
      • "rain":3.44,
      • "snow":0.22
      }
      ,
    7. {
      • "dt":1417712400,
      • "temp":{
        • "day":272.6,
        • "min":271.5,
        • "max":272.6,
        • "night":272.49,
        • "eve":272.56,
        • "morn":271.5
        }
        ,
      • "pressure":1003.13,
      • "humidity":0,
      • "weather":[
        1. {
          • "id":601,
          • "main":"Snow",
          • "description":"snow",
          • "icon":"13d"
          }
        ]
        ,
      • "speed":13.59,
      • "deg":285,
      • "clouds":47,
      • "snow":4.52
      }
    ]
}

With the data parsed using the handy online tool, it is very easy to visualize how it is structured and how we can read the data inside.

For example on the forecast:
     "list":[

  1. {
    • "dt":1417194000,
    • "temp":{
      • "min":268.92,
      • "max":270.14,
      • },
    • "weather":[
      1. {
        • "id":601,
        • "main":"Snow",
        • "description":"snow",
        • "icon":"13d"
        }
      ]
      ,

To retrieve the dt value for the first day, our JavaScript code will be: json.list[0].dt
To retrieve it for the second day: json.list[1].dt

Min/max temperature is in a group within the primary list.
To retrieve the minimum temperature for the first day: json.list[0].temp.min
To retrieve it for the second day: json.list[1].temp.min

Finally the weather grouping is also an array (note the []s); it's just an array of 1 entry. To get a value from there, we give an index.
To retrieve the "main" weather description for the first day: json.list[0].weather[0].main
To retrieve it for the second day: json.list[1].weather[0].main

Current Weather Conditions

Let's look at the simpler case first, the Current Weather conditions.

The JavaScript code for the query: (Some data has been commented out because I'm not using it in the watch face; however, it may be of value to you.)
  // Construct URL
  var url = "http://api.openweathermap.org/data/2.5/weather?lat=" +
      pos.coords.latitude + "&lon=" + pos.coords.longitude;

  // Send request to OpenWeatherMap
  xhrRequest(url, 'GET', 
    function(responseText) {
      // responseText contains a JSON object with weather info
      var json = JSON.parse(responseText);

      // Temperature in Kelvin requires adjustment
      var temperature = Math.round(json.main.temp - 273.15);

      // Conditions
      //var conditions = json.weather[0].main;      
      
      // Temperature Min
      //var temperatureMin = Math.round(json.main.temp_min - 273.15);
      
      // Temperature Min
      //var temperatureMax = Math.round(json.main.temp_max - 273.15);
      
      // Wind Speed
      var windSpeed = Math.round(json.wind.speed);
      
      // Wind Direction
      var windDirection = Math.round(json.wind.deg);
      
      // Humidity
      //var humidity = Math.round(json.main.humidity);
      
      // Description
      var description = json.weather[0].description;      
      
      // Assemble dictionary using our keys
      var dictionary = {
        "KEY_TEMPERATURE": temperature,
        "KEY_WIND_SPEED": windSpeed,
        "KEY_WIND_DIRECTION": windDirection,
        "KEY_DESCRIPTION": description
      };
//        "KEY_TEMP_MIN": temperatureMin,
//        "KEY_TEMP_MAX": temperatureMax,
//        "KEY_CONDITIONS": conditions,
//        "KEY_HUMIDITY": humidity,

      // Send to Pebble
      Pebble.sendAppMessage(dictionary,
        function(e) {
          //console.log("Weather info sent to Pebble successfully WX!");
        },
        function(e) {
          //console.log("Error sending weather info to Pebble WX!");
        }
      );
    }      
  );

What happens is the JavaScript code is sending out a query for the weather conditions and getting the response in the JSON format. The JavaScript code then pulls out the values it cares about, associates them with keys I have defined, and builds a dictionary array that is sent to the Pebble C code.

As you can see, JSON returns a wealth of data, but I'm only using the current temperature, wind speed, wind direction, and description.

After some research, I found out the current low and high temperatures returned from the current conditions query aren't actually from forecast data; they're formulated by other conditions throughout the day. The values are not reliable and give poor results. To get good values for the min and max temperature of the day, you must make a separate query for the forecast conditions.

The "description" from JSON is far more descriptive than "main". In this example, the "main" is "Snow" but the description is "light snow". I have room on my text layer so I want to be as descriptive as I can so I went with the "description".

I'm using CloudPebble for my watch face development. Under the Settings section of CloudPebble, I have Message Keys that correspond with the variable keys you see above:
KEY_TEMPERATURE = 0
KEY_CONDITIONS = 1
KEY_TEMP_MIN = 2
KEY_TEMP_MAX = 3
KEY_WIND_SPEED = 4
KEY_WIND_DIRECTION = 5
KEY_HUMIDITY = 6
KEY_DESCRIPTION = 7

The corresponding keys are also #defined in the C code:
#define KEY_TEMPERATURE 0
#define KEY_CONDITIONS 1
#define KEY_TEMP_MIN 2
#define KEY_TEMP_MAX 3
#define KEY_WIND_SPEED 4
#define KEY_WIND_DIRECTION 5
#define KEY_HUMIDITY 6
#define KEY_DESCRIPTION 7

There are existing tutorials on how to setup inbox callbacks with the JavaScript code, so I'll mostly gloss over those details.

In the init(), the inbox_received_callback has been setup to receive the call from the JavaScript code:
  app_message_register_inbox_received(inbox_received_callback);

The inbox_received_callback itself:
static void inbox_received_callback(DictionaryIterator *iterator, void *context)
{
  ...
  // Read first item  Tuple *t = dict_read_first(iterator);

  // For all items
  while(t != NULL)
  {
    // Which key was received?
    switch(t->key)
    {
      case KEY_TEMPERATURE:
        currentTemperature_c = t->value->int32;
        break;
//      case KEY_CONDITIONS:
//        // Current conditions (abbreviated).
//        break;
//      case KEY_TEMP_MIN:
//        // Not reliably low temperature.
//        break;
//      case KEY_TEMP_MAX:
//        // Not reliably high temperature.
//        break;
      case KEY_WIND_SPEED:
        // Reported in meters per second, convert to knots.
        currentWindSpeed_metersPerSecond = t->value->int32;
        break;
      case KEY_WIND_DIRECTION:
        currentWindDirection_deg = t->value->int32;
        break;
//      case KEY_HUMIDITY:
//        break;
      case KEY_DESCRIPTION:
        // Similar to conditions, but far more descriptive.
        strncpy(currentConditions, t->value->cstring, 32);
        break;
      ...
      default:
        APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
        break;
    }

    // Look for next item
    t = dict_read_next(iterator);
  }
  ...
  update_weather();
}

In the above code, you can see how the keys that were in the JavaScript code, included in the CloudPebble Settings, and #defined at the top of the C code, are now fully retrieved.

currentTemperature_c, currentWindSpeed_metersPerSecond,currentWindDirection_deg, and currentConditions are defined as global variables at the top of the C code:
static int currentTemperature_c;
static char currentConditions[32];
static int currentWindDirection_deg;
static int currentWindSpeed_metersPerSecond;

The update_weather() function takes those values and updates the text layers on the watch face.

Forecast Weather Conditions

Now for the trickier one, the forecast conditions. The forecast includes 7 days worth of weather data. You can't assume the first weather condition is today, the second weather condition is tomorrow, etc. You must read the date to determine which day the forecast belongs to. Sometimes the first entry of the retrieved data is not in fact today's forecast, it is yesterday's! To be able to get today's and tomorrow's forecast, I have to pull 3 values from the forecast JSON.

Each day's conditions includes a "dt" value which is the date/time of that forecast in UNIX format. We'll be reading that "dt" value to determine what day it belongs to.

For reference, I found a handy UNIX time calculator here: http://www.onlineconversion.com/unix_time.htm Just give it in the "dt" value and it will reply with a more human-friendly date and time.

The JavaScript code:
  // Construct URL
  var forecasturl = "http://api.openweathermap.org/data/2.5/forecast/daily?lat=" +
      pos.coords.latitude + "&lon=" + pos.coords.longitude;

  // Send request to OpenWeatherMap
  xhrRequest(forecasturl, 'GET', 
    function(responseForecastText) {
      // responseText contains a JSON object with weather info
      var json = JSON.parse(responseForecastText);

      var day1Time = json.list[0].dt;
      
      // Conditions
      var day1Conditions = json.list[0].weather[0].main;      
      
      // Temperature in Kelvin requires adjustment
      var day1TemperatureMin = Math.round(json.list[0].temp.min - 273.15);

      // Temperature in Kelvin requires adjustment
      var day1TemperatureMax = Math.round(json.list[0].temp.max - 273.15);

      var day2Time = json.list[1].dt;
      
      // Conditions
      var day2Conditions = json.list[1].weather[0].main;      
           
      // Temperature in Kelvin requires adjustment
      var day2TemperatureMin = Math.round(json.list[1].temp.min - 273.15);

      // Temperature in Kelvin requires adjustment
      var day2TemperatureMax = Math.round(json.list[1].temp.max - 273.15);

      var day3Time = json.list[2].dt;
      
      // Conditions
      var day3Conditions = json.list[2].weather[0].main;      
           
      // Temperature in Kelvin requires adjustment
      var day3TemperatureMin = Math.round(json.list[2].temp.min - 273.15);

      // Temperature in Kelvin requires adjustment
      var day3TemperatureMax = Math.round(json.list[2].temp.max - 273.15);

      // Assemble dictionary using our keys
      var dictionary = {
        "KEY_DAY1_TIME": day1Time,
        "KEY_DAY1_CONDITIONS": day1Conditions,
        "KEY_DAY1_TEMP_MIN": day1TemperatureMin,
        "KEY_DAY1_TEMP_MAX": day1TemperatureMax,
        "KEY_DAY2_TIME": day2Time,
        "KEY_DAY2_CONDITIONS": day2Conditions,
        "KEY_DAY2_TEMP_MIN": day2TemperatureMin,
        "KEY_DAY2_TEMP_MAX": day2TemperatureMax,
        "KEY_DAY3_TIME": day3Time,
        "KEY_DAY3_CONDITIONS": day3Conditions,
        "KEY_DAY3_TEMP_MIN": day3TemperatureMin,
        "KEY_DAY3_TEMP_MAX": day3TemperatureMax
      };

      // Send to Pebble
      Pebble.sendAppMessage(dictionary,
        function(e) {
          //console.log("Weather info sent to Pebble successfully WX!");
        },
        function(e) {
          //console.log("Error sending weather info to Pebble WX!");
        }
      );
    }      
  );

The keys for CloudPebble:
KEY_DAY1_CONDITIONS = 8
KEY_DAY1_TEMP_MIN = 9
KEY_DAY1_TEMP_MAX = 10
KEY_DAY1_TIME = 11
KEY_DAY2_CONDITIONS = 12
KEY_DAY2_TEMP_MIN = 13
KEY_DAY2_TEMP_MAX = 14
KEY_DAY2_TIME = 15
KEY_DAY3_CONDITIONS = 16
KEY_DAY3_TEMP_MIN = 17
KEY_DAY3_TEMP_MAX = 18
KEY_DAY3_TIME = 19

The corresponding keys #defined in the C code:
#define KEY_DAY1_CONDITIONS 8
#define KEY_DAY1_TEMP_MIN 9
#define KEY_DAY1_TEMP_MAX 10
#define KEY_DAY1_TIME 11
#define KEY_DAY2_CONDITIONS 12
#define KEY_DAY2_TEMP_MIN 13
#define KEY_DAY2_TEMP_MAX 14
#define KEY_DAY2_TIME 15
#define KEY_DAY3_CONDITIONS 16
#define KEY_DAY3_TEMP_MIN 17
#define KEY_DAY3_TEMP_MAX 18
#define KEY_DAY3_TIME 19

And now the C code:
static void inbox_received_callback(DictionaryIterator *iterator, void *context)
{
  ...
  // Read first item
  Tuple *t = dict_read_first(iterator);

  int day1Date = 0;
  int day2Date = 0;
  //int day3Date = 0;
  int day1LowTemperature_c = 0;
  int day2LowTemperature_c = 0;
  int day3LowTemperature_c = 0;
  int day1HighTemperature_c = 0;
  int day2HighTemperature_c = 0;
  int day3HighTemperature_c = 0;
  char day1Conditions[32];
  char day2Conditions[32];
  char day3Conditions[32];

  // For all items
  while(t != NULL)
  {
    // Which key was received?
    switch(t->key)
    {
      ...
      case KEY_DAY1_TIME:
        // Usually today's date, but in the morning it's yesterday's!
        day1Date = t->value->int32;
        break;
      case KEY_DAY1_CONDITIONS:
        // Today's condition (abbreviated).
        //snprintf(day1_conditions_buffer, sizeof(day1_conditions_buffer), "%s", t->value->cstring);
        strncpy(day1Conditions, t->value->cstring, 32);
        break;
      case KEY_DAY1_TEMP_MIN:
        //currentLowTemperature_c = t->value->int32;
        day1LowTemperature_c = t->value->int32;
        break;
      case KEY_DAY1_TEMP_MAX:
        //currentHighTemperature_c = t->value->int32;
        day1HighTemperature_c = t->value->int32;
        break;
      case KEY_DAY2_TIME:
        // Usually it's tomorrow's date, but in the morning it's today's!.
        //forecastDate = t->value->int32;
        day2Date = t->value->int32;
        break;
      case KEY_DAY2_CONDITIONS:
        // Forecast condition (abbreviated).
        //strncpy(forecastConditions, t->value->cstring, 32);
        strncpy(day2Conditions, t->value->cstring, 32);
        break;
      case KEY_DAY2_TEMP_MIN:
        //forecastLowTemperature_c = t->value->int32;
        day2LowTemperature_c = t->value->int32;
        break;
      case KEY_DAY2_TEMP_MAX:
        //forecastHighTemperature_c = t->value->int32;
        day2HighTemperature_c = t->value->int32;
        break;
      case KEY_DAY3_TIME:
        // Usually it's in two days, but in the morning it's tomorrow's!
        //day3Date = t->value->int32;
        break;
      case KEY_DAY3_CONDITIONS:
        // Forecast condition (abbreviated).
        strncpy(day3Conditions, t->value->cstring, 32);
        break;
      case KEY_DAY3_TEMP_MIN:
        //day3LowTemperature_c = t->value->int32;
        day3LowTemperature_c = t->value->int32;
        break;
      case KEY_DAY3_TEMP_MAX:
        day3HighTemperature_c = t->value->int32;
        break;
        ...
      default:
        APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
        break;
    }

    // Look for next item
    t = dict_read_next(iterator);
  }

  if (day1Date > 0)
  {
    // Forecast Response
    time_t currentTime = time(NULL);
    struct tm *currentCalendarTime = localtime(&currentTime);
    int dayOfMonthCurrent = currentCalendarTime->tm_mday;
    
    time_t day1Date_t = day1Date;
    struct tm *day1CalendarTime = localtime(&day1Date_t);
    int dayOfMonth1 = day1CalendarTime->tm_mday;
    
    time_t day2Date_t = day2Date;
    struct tm *day2CalendarTime = localtime(&day2Date_t);
    int dayOfMonth2 = day2CalendarTime->tm_mday;
    
    if (dayOfMonthCurrent == dayOfMonth1)
    {
      // Day 1 is Today's Date
      currentDate = day1Date;
      strncpy(currentDayForecastConditions, day1Conditions, 32);
      currentLowTemperature_c = day1LowTemperature_c;
      currentHighTemperature_c = day1HighTemperature_c;
      
      // So Day 2 will be the forecast.
      strncpy(forecastConditions, day2Conditions, 32);
      forecastLowTemperature_c = day2LowTemperature_c;
      forecastHighTemperature_c = day2HighTemperature_c;
    }
    else if (dayOfMonthCurrent == dayOfMonth2)
    {
      // Day 2 is Today's Date 
      currentDate = day2Date;
      strncpy(currentDayForecastConditions, day2Conditions, 32);
      currentLowTemperature_c = day2LowTemperature_c;
      currentHighTemperature_c = day2HighTemperature_c;

      // So Day 3 will be the forecast.
      strncpy(forecastConditions, day3Conditions, 32);
      forecastLowTemperature_c = day3LowTemperature_c;
      forecastHighTemperature_c = day3HighTemperature_c;
    }
  } // (day1Date > 0)

  ...
  update_weather();
}

The key piece above is determining if the first forecast condition is today's date or yesterday's.

I get the current day of the month:
    time_t currentTime = time(NULL);
    struct tm *currentCalendarTime = localtime(&currentTime);
    int dayOfMonthCurrent = currentCalendarTime->tm_mday;

the first forecast day of the month:    
    time_t day1Date_t = day1Date;
    struct tm *day1CalendarTime = localtime(&day1Date_t);
    int dayOfMonth1 = day1CalendarTime->tm_mday;

and the second forecast day of the month:
    time_t day2Date_t = day2Date;
    struct tm *day2CalendarTime = localtime(&day2Date_t);
    int dayOfMonth2 = day2CalendarTime->tm_mday;

If my current day of the month equals the first forecast day:
    if (dayOfMonthCurrent == dayOfMonth1)
    {
Then I know the first value from the JSON data is today's forecast (and the second value is tomorrow's):
      // Day 1 is Today's Date
      currentDate = day1Date;
      strncpy(currentDayForecastConditions, day1Conditions, 32);
      currentLowTemperature_c = day1LowTemperature_c;
      currentHighTemperature_c = day1HighTemperature_c;
      
      // So Day 2 will be the forecast.
      strncpy(forecastConditions, day2Conditions, 32);
      forecastLowTemperature_c = day2LowTemperature_c;
      forecastHighTemperature_c = day2HighTemperature_c;

Else if my current day of the month equals the second forecast day:
    else if (dayOfMonthCurrent == dayOfMonth2)
    {
Then I know the second value from the JSON data is today's forecast (and the third value is tomorrow's):
      // Day 2 is Today's Date 
      currentDate = day2Date;
      strncpy(currentDayForecastConditions, day2Conditions, 32);
      currentLowTemperature_c = day2LowTemperature_c;
      currentHighTemperature_c = day2HighTemperature_c;

      // So Day 3 will be the forecast.
      strncpy(forecastConditions, day3Conditions, 32);
      forecastLowTemperature_c = day3LowTemperature_c;
      forecastHighTemperature_c = day3HighTemperature_c;

Please refer to the first Pebble Watchface post for the full source code. Hopefully this has aided you in parsing JSON / weather data!

13 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi. Thanks for your example, helped a lot. But I'm having a problem I can't solve and maybe you have a suggestion.
    Forecast and Current weather works okay if I delete one or the other of the code, but I cant make them work together. Seems to me if I send a new Dictionary it delete the information from the other. Example:

    My current weather function have:
    var dictionary = {
    "KEY_TEMPERATURE": temperature,
    "KEY_CONDITIONS": conditions,
    "KEY_LOC": locs
    };

    and my Forecast function have:
    var dictionary = {
    "KEY_CONDITIONS2": conditions2,
    };

    If I do that, watchface shows only CONDITION2. And I cant put all variables in Forecast dictionary because they are from the other function. I was thinking in something like:

    var dictionary = {
    "KEY_CONDITIONS2": conditions2,
    "KEY_TEMPERATURE": temperature,
    "KEY_CONDITIONS": conditions,
    "KEY_LOC": locs
    };

    But it can't find variables "temperature", "locs" and "conditions" (yes, make sense). I tried to write it in the keys, like:

    localStorage.setItem(1, temperature);

    But it didnt work.

    Sorry, maybe it's a silly question, but I really don't know how to solve this.

    Many thanks,
    Luiz

    ReplyDelete
  3. Glad it was of help!

    I'm thinking your JavaScript code is fine: You have a function that gets the current conditions and sends them off, and you have a function that gets the forecast conditions and sends them off.

    I bet your problem is on the receiving side in the C code. Are you assuming both the current and forecast conditions are passed in together? Because they're actually going to come in under 2 separate function calls.

    Take a look at my first Pebble Watchface posting, function:
    static void inbox_received_callback(DictionaryIterator *iterator, void *context)

    The current weather conditions are retrieved in the switch:
    case KEY_TEMPERATURE:
    currentTemperature_c = t->value->int32;
    break;
    case KEY_DESCRIPTION:
    // Similar to conditions, but far more descriptive.
    strncpy(currentConditions, t->value->cstring, 32);
    break;

    But currentTemperature_c and currentConditions aren't declared in that function; they're global variables. They're only modified if the parameters passed into inbox_received_callback include KEY_TEMPERATURE and KEY_DESCRIPTION. Those will only be passed in with the local conditions call.

    My forecast conditions are only processed if KEY_DAY1_TIME is passed in.
    At the top of the function I have a local variable day1Date.
    Inside the switch:
    case KEY_DAY1_TIME:
    // Usually today's date, but in the morning it's yesterday's!
    day1Date = t->value->int32;
    break;

    But I only process my forecast variables if:
    if (day1Date > 0)
    {

    If it was the current conditions that were passed in, day1Date would equal 0 and that block of code would get skipped.

    Is that clear? Please feel free to post your inbox_received_callback function, and I'll take a look. That's probably the culprit.

    ReplyDelete
    Replies
    1. Thanks for your help. Let me try what you said. Anyway I can send you the code. Is it okay to paste in here? Won't this big code mess up with your comments section?

      Delete
  4. Here it goes:

    static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
    // Store incoming information
    static char loc_buffer[52];
    static char temperature_buffer[8];
    static char conditions_buffer[152];
    static char weather_layer_buffer[152];
    static char conditions2_buffer[152];


    // Read first item
    Tuple *t = dict_read_first(iterator);

    // For all items
    while(t != NULL) {
    // Which key was received?
    switch(t->key) {
    case KEY_LOC:
    snprintf(loc_buffer, sizeof(loc_buffer), "%s", t->value->cstring);
    break;
    case KEY_TEMPERATURE:
    snprintf(temperature_buffer, sizeof(temperature_buffer), "%dC", (int)t->value->int32);
    break;
    case KEY_CONDITIONS:
    snprintf(conditions_buffer, sizeof(conditions_buffer), "%s", t->value->cstring);
    break;
    case KEY_CONDITIONS2:
    snprintf(conditions2_buffer, sizeof(conditions2_buffer), "%s", t->value->cstring);
    break;
    default:
    APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
    break;
    }

    // Look for next item
    t = dict_read_next(iterator);
    }

    // Assemble full string and display
    snprintf(weather_layer_buffer, sizeof(weather_layer_buffer), "%s, %s", conditions_buffer, conditions2_buffer);
    text_layer_set_text(s_weather_layer, weather_layer_buffer);

    }



    And in my JS I have:

    function setarvariaveis(temperature,conditions,locs,conditions2){

    var dictionary = {
    "KEY_CONDITIONS2": conditions2,
    "KEY_TEMPERATURE": temperature,
    "KEY_CONDITIONS": conditions,
    "KEY_LOC": locs
    };

    console.log(conditions2,temperature,conditions,locs);

    Pebble.sendAppMessage(dictionary,
    function(e) {
    console.log("Weather info sent to Pebble successfully!");
    },
    function(e) {
    console.log("Error sending weather info to Pebble!");
    }
    );

    }




    // Send to Pebble



    function locationError(err) {
    console.log("Error requesting location!");
    }

    function getWeather() {
    navigator.geolocation.getCurrentPosition(
    locationSuccess,
    locationSuccess2,
    locationError,
    {timeout: 15000, maximumAge: 60000}
    );
    }

    ReplyDelete
  5. In your getWeather() function, you're calling:
    navigator.geolocation.getCurrentPosition(
    locationSuccess,
    locationSuccess2,
    locationError,
    {timeout: 15000, maximumAge: 60000}
    );

    The first argument of .getCurrentPosition() is the function it calls if it successfully gets the location. The second argument is the function it calls if it fails to get the location. The third argument is options.

    But your first argument is: locationSuccess
    Second argument (the error) is: locationSuccess2
    Third argument (the options) is: locationError

    Basically your locationSuccess2 is in locationError's spot. I suggest changing it to:
    navigator.geolocation.getCurrentPosition(locationSuccess, locationError, {timeout: 15000, maximumAge: 60000});

    I don't see your locationSuccess() function, but it would then do all the tasks of locationSuccess and locationSuccess2.

    Or you could rename your existing locationSuccess to getWeatherData1 and your existing locationSuccess2 to getWeatherData2. Then create a new function called locationSuccess that calls both getWeatherData1 and getWeatherData2.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  6. This comment has been removed by the author.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. Hi. It worked! Thanks! The problem was second request was in a different function and using the same name. Now it's okay. Thanks a lot. I delete the old comments because they were too big (a big part of my code was there), but if you want I can send them to you anyway.
    Have a great week!

    ReplyDelete