How to Easily Parse JSON Payload from Webhook Using JsonParserGeneratorRK

I have some code that uses webhooks to retrieve the current weather conditions, sunrise and sunset times, and the 4-day forecast as shown in the screenshot below:

I have heard that using String’s is a bad idea when it comes to the stability of the Microcontroller.

Can you guys look at the code I use to parse the weather response from the webhook and let me know if you think using this process is a bad Idea of if it’s acceptable in this case? Is there a better approach to doing the same thing?

Here is the code:

// called once on startup
 const char* cityLocation = "Fishers"; //City for my Photon
 const char* stateLocation = "IN"; // State for my Photon

 char publishString[128];



 void setup() {
 // For simplicity, we'll format our weather data as text, and pipe it to serial.
 // but you could just as easily display it in a webpage or pass the data to another system.

 Serial.begin(115200);

 Particle.subscribe(System.deviceID(), deviceWebhookHandler, MY_DEVICES);  // every webhook preceeded by your deviceID gets handled by deviceWebhookHandler()!!

 // Lets give ourselves 10 seconds before we actually start the program.
 // That will just give us a chance to open the serial monitor before the program sends the request
 for(int i=0;i<5;i++) {
 Serial.println("waiting " + String(5-i) + " seconds before we publish");
 delay(1000);
 }
 }


 // called forever really fast
 void loop() {

 // Let's request the weather, but no more than once every 60 seconds.
 Serial.println("");
 Serial.println("-------------------");
 Serial.println("Requesting Weather!");
 Serial.println("");

sprintf(publishString, "{\"my-city\": \"%s\", \"my-state\": \"%s\" }", cityLocation, stateLocation);

 // publish the event that will trigger our Webhook
Particle.publish("GetCurrentWeatherConditions", publishString, PRIVATE);
delay(4000);
Particle.publish("GetSunRise_SunSetTimes", publishString, PRIVATE);
delay(4000);
Particle.publish("Get3DayWeatherForecast", publishString, PRIVATE);



 // and wait at least 60 seconds before doing it again
 delay(60000 * 5 );
 }


///////////////////////////////////////////////////////////////////////////

 void deviceWebhookHandler(const char* event, const char* data)
 {
   if(strstr(event, "GetCurrentWeatherConditions"))
   {
     GetCurrentWeatherConditions(event, data);
   }
   else if(strstr(event, "Get3DayWeatherForecast"))
   {
     Get3DayWeatherForecast(event, data);
   }
   else if(strstr(event, "GetSunRise_SunSetTimes"))
   {
      GetHoursOfDayLight(event, data);
   }
   else
   {
     Serial.println("recieved some unknown webhook");
   }
 }


///////////////////////////////////////////////////////////////////////////

 // This function will get called when weather data comes in
 void GetCurrentWeatherConditions(const char *name, const char *data) {
 // Important note! -- Right now the response comes in 512 byte chunks.
 // This code assumes we're getting the response in large chunks, and this
 // assumption breaks down if a line happens to be split across response chunks.

 String str = String(data);
 String locationStr = tryExtractString(str, "<Location>", "</Location>");
 String timeStr = tryExtractString(str, "<Time>", "</Time>");
 String weatherdiscriptionStr = tryExtractString(str, "<WeatherDescription>", "</WeatherDescription>");
 String tempStr = tryExtractString(str, "<Temp>", "</Temp>");
 String humidityStr = tryExtractString(str, "<Humidity>", "</Humidity>");
 String windStr = tryExtractString(str, "<Wind>", "</Wind>");
 String heatindexStr = tryExtractString(str, "<HeatIndex>", "</HeatIndex>");
 String windchillStr = tryExtractString(str, "<WindChill>", "</WindChill>");
 String feelslikeStr = tryExtractString(str, "<FeelsLike>", "</FeelsLike>");
 String visibilityStr = tryExtractString(str, "<Visibility>", "</Visibility>");
 String sunradiationStr = tryExtractString(str, "<SunRadiation>", "</SunRadiation>");
 String uvindexStr = tryExtractString(str, "<UvIndex>", "</UvIndex>");
 String totalrainStr = tryExtractString(str, "<TotalRainInches>", "</TotalRainInches>");
 String iconStr = tryExtractString(str, "<Icon>", "</Icon>");

 if (locationStr != NULL) {
 Serial.println("Weather for: " + locationStr);
 }

 if (timeStr != NULL) {
 Serial.println("Timestamp: " + timeStr);
 }

 if (weatherdiscriptionStr != NULL) {
 Serial.println("Weather Conditon: " + weatherdiscriptionStr);
 }

 if (tempStr != NULL) {
 Serial.println("The Temp is: " + tempStr + String(" F"));
 }

 if (feelslikeStr != NULL) {
 Serial.println("The Real Feel Temp is: " + feelslikeStr + String(" F"));
 }

 if (humidityStr != NULL) {
 Serial.println("The Humidity is: " + humidityStr );
 }

 if (windStr != NULL) {
 Serial.println("Wind Conditions: " + windStr);
 }

 if (heatindexStr != NULL) {
 Serial.println("The Heat Index is: " + heatindexStr + String(" F"));
 }

 if (windchillStr != NULL) {
 Serial.println("The Wind Chill Index is: " + windchillStr + String(" F"));
 }

 if (visibilityStr != NULL) {
 Serial.println("The Visibility is: " + visibilityStr + String(" Miles"));
 }

 if (sunradiationStr != NULL) {
 Serial.println("The Solar Radiation is: " + sunradiationStr + String(" W/m2"));
 }

 if (uvindexStr != NULL) {
 Serial.println("The UV Index is: " + uvindexStr );
 }

 if (totalrainStr != NULL) {
 Serial.println("Total Daily Rain Fall: " + totalrainStr + String(" Inches"));
 }

 if (iconStr != NULL) {
 Serial.println("Icon for current weather: " + iconStr);
 //Serial.println("");
 }

 }




//////////////////////////////////////////////////////////////////////////////
 void Get3DayWeatherForecast(const char* event, const char* data)
 {

   static char weatherData[1548] = "";
   if(strstr(event, "_Get3DayWeatherForecast/0"))
   {
     strcpy(weatherData, "");  // empty the string
     strcpy(weatherData, data);
     //Serial.println(weatherData);
   }
   else if(strstr(event, "_Get3DayWeatherForecast/1"))
   {
     strcat(weatherData, data);  //concatenate the first return with the second
     //Serial.println(weatherData);

     String WeatherIcon0 = strtok(weatherData, "~");
     String Period0 = strtok(NULL, "~");
     String Period0Forcast = strtok(NULL, "~");
     int POP0 = atoi(strtok(NULL, "~"));
     String WeatherIcon1 = strtok(NULL, "~");
     String Period1 = strtok(NULL, "~");
     String Period1Forcast = strtok(NULL, "~");
     int POP1 = atoi(strtok(NULL, "~"));
     String WeatherIcon2 = strtok(NULL, "~");
     String Period2 = strtok(NULL, "~");
     String Period2Forcast = strtok(NULL, "~");
     int POP2 = atoi(strtok(NULL, "~"));
     String WeatherIcon3 = strtok(NULL, "~");
     String Period3 = strtok(NULL, "~");
     String Period3Forcast = strtok(NULL, "~");
     int POP3 = atoi(strtok(NULL, "~"));
     String WeatherIcon4 = strtok(NULL, "~");
     String Period4 = strtok(NULL, "~");
     String Period4Forcast = strtok(NULL, "~");
     int POP4 = atoi(strtok(NULL, "~"));
     String WeatherIcon5 = strtok(NULL, "~");
     String Period5 = strtok(NULL, "~");
     String Period5Forcast = strtok(NULL, "~");
     int POP5 = atoi(strtok(NULL, "~"));
     String WeatherIcon6 = strtok(NULL, "~");
     String Period6 = strtok(NULL, "~");
     String Period6Forcast = strtok(NULL, "~");
     int POP6 = atoi(strtok(NULL, "~"));
     String WeatherIcon7 = strtok(NULL, "~");
     String Period7 = strtok(NULL, "~");
     String Period7Forcast = strtok(NULL, "~");
     int POP7 = atoi(strtok(NULL, "~"));



    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon0);
    Serial.print("Period: ");
    Serial.println(Period0);
    Serial.print("Forcast: ");
    Serial.println(Period0Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP0);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon1);
    Serial.print("Period: ");
    Serial.println(Period1);
    Serial.print("Forcast: ");
    Serial.println(Period1Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP1);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon2);
    Serial.print("Period: ");
    Serial.println(Period2);
    Serial.print("Forcast: ");
    Serial.println(Period2Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP2);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon3);
    Serial.print("Period: ");
    Serial.println(Period3);
    Serial.print("Forcast: ");
    Serial.println(Period3Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP3);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon4);
    Serial.print("Period: ");
    Serial.println(Period4);
    Serial.print("Forcast: ");
    Serial.println(Period4Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP4);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon5);
    Serial.print("Period: ");
    Serial.println(Period5);
    Serial.print("Forcast: ");
    Serial.println(Period5Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP5);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon6);
    Serial.print("Period: ");
    Serial.println(Period6);
    Serial.print("Forcast: ");
    Serial.println(Period6Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP6);
    Serial.println("");

    //Serial.print("Weather Icon: ");
    //Serial.println(WeatherIcon7);
    Serial.print("Period: ");
    Serial.println(Period7);
    Serial.print("Forcast: ");
    Serial.println(Period7Forcast);
    //Serial.print("Probability of Precipitation: ");
    //Serial.println(POP7);

    Serial.println("-------------------");

   }
   else if (strstr(event, "_Get3DayWeatherForecast/2"))
   {
     strcat(weatherData, data);  //concatenate the first return with the second
     //Serial.print("Complete String:\t");
     //Serial.println(weatherData);
   }


}




///////////////////////////////////////////////////////////////////////
 // This function will get called when weather data comes in
 void GetHoursOfDayLight(const char *name, const char *data) {
 // Important note! -- Right now the response comes in 512 byte chunks.
 // This code assumes we're getting the response in large chunks, and this
 // assumption breaks down if a line happens to be split across response chunks.

 String str = String(data);

 String sunrisehourStr = tryExtractString(str, "<SunriseHour>", "</SunriseHour>");
 String sunriseminuteStr = tryExtractString(str, "<SunriseMin>", "</SunriseMin>");
 String sunsethourStr = tryExtractString(str, "SunsetHour>", "</SunsetHour>");
 String sunsetminuteStr = tryExtractString(str, "<SunsetMinute>", "</SunsetMinute>");

 uint hourofsunrise = sunrisehourStr.toInt();
 uint minofsunrise = sunriseminuteStr.toInt();
 uint hourofsunset = sunsethourStr.toInt();
 uint sunsetmin = sunsetminuteStr.toInt();

 int adjsunsethour = (hourofsunset-12); //Eliminate the negative sign in the hour number.

 int morningsunhours = 12 - hourofsunrise;
 //int afternoonsunhours = adjsunsethour;
 int sunhoursconvertedtomins = (morningsunhours + adjsunsethour) * 60;
 int totaldaysunmins = (60 - minofsunrise + sunsetmin);
 float totaldaylighthours = (sunhoursconvertedtomins + totaldaysunmins) / 60.0;


 if (sunrisehourStr != NULL) {

 Serial.println("Sunrise at: " + sunrisehourStr + String(":") + sunriseminuteStr + String(" AM") + " / Sunset at: " + adjsunsethour + String(":") + sunsetminuteStr + String(" PM"));
 //Serial.println("Hours of Daylight = " + hoursofdaylight + String(" Hours") minsofdaylight + String(" Mins"));
 Serial.print("Total Hours of Sunlight Today = ");
 Serial.println(totaldaylighthours);
 }

 Serial.println("-------------------");

}


/////////////////////////////////////////////////////////////////////////
 // Returns any text found between a start and end string inside 'str'
 // example: startfooend -> returns foo
 String tryExtractString(String str, const char* start, const char* end) {
 if (str == NULL) {
 return "";
 }

 int idx = str.indexOf(start);
 if (idx < 0) {
 return "";
 }

 int endIdx = str.indexOf(end);
 if (endIdx < 0) {
 return "";
 }

 return str.substring(idx + strlen(start), endIdx);
 }

Can you make your weather API send out the data in JSON instead of XML? Many can if you specify an additional option when you request the data.

Then you can use my JSON Parser to extract the bits of data you want. It’s designed to work with the chunked responses you get from a webhook when the data is spread across multiple responses. And there’s a handy tool that you paste in the JSON and then click on an item and it will generate the code you need to extract that item from the data structure.

1 Like

I’m pulling data from Weather Underground and it looks like it can return data in XML or JSON format.

So I will try to use your tools to replicate this weather data feature without the use of Strings and see how it goes.

I’m guessing you would recommend that using all the String variables is not a good idea and should be avoided?

The way I’d do it is keep the only copy of the data in JsonParserGeneratorRK. The JsonParser object will store the data and keep an index of it to efficiently extract the data.

Then instead of making a copy of every bit of information you want into a separate String, you just request the item you want to use when you need it. It’s usually OK to use a String when the lifetime of the String object is short and you have enough free memory.

Thanks for the clarification.

That sounds a lot cleaner to me.

//////////////

The weather webhooks are already receiving the data from Weather Underground in JSON format.

http://api.wunderground.com/api/c1a68f78b8712baf/conditions/q/indiana/fishers.json

I’m wondering if I can leave my current weather webhook integrations in place that are returning json already or if I will need to replace them also. Just trying to keep the work to a minimum.

I see your Weather Underground Medium example code in your Parser.

I can vouch for @rickkas7 JsonParserGeneratorRK library. I switched to it from the sparkjson library and I have stopped running into stack overflow issues. He added some features I requested which made it easier to use. It’s definitely a better approach to store the raw json string in memory once and then access the sub-objects when needed.

1 Like

@rickkas7 I’m trying to get your JSON library working but after reading the Github & looking at the parsing example code a few things are not clear.

I think I understand how to get the values from the data the webhook returns after looking at the Parsing example but I’m not sure how I should be setting up the custom webhook response template so the data returned is in the format your library needs to work correctly.

The data I want and my current webhook response template is listed below, how should I change the formatting to work with your library?

The actual JSON that I’m getting back from Weather Underground is:

http://api.wunderground.com/api/c1a68f78b8712baf/conditions/q/indiana/fishers.json

I think once I get the formatting of the webhook response correct I will move onto the Subscription handler code.

I know your busy so any help is appreciated :slight_smile:

Your response template should also be valid JSON - your shown template looks like a “mix-and-(not-quite)-match” of XML and JSON.

1 Like

You can either remove the response template and send the whole thing down to the device to be parsed, or you can break only the pieces you need, but you need to format the data as JSON.

For example:

{
{{#current_observation}}
"location":"{{display_location.full}}",
"time":"{{observation_time}}",
"weatherDescription":"{{weather}}",
"temp":{{temp_f}},
"humidity":{{relative_humidity}},
"wind":"{{wind_string}}"
{{/current_observation}}
}

Just keep doing that, adding the rest of the fields in. This will make the data much smaller, and also make it easy to retrieve the values.

The mustache tester makes it easy to test. Just paste in the JSON you got from the weather server, and paste the template above in, and just keep adding fields, making sure the right values are retrieved. Then copy and paste the template into your webhook.

Make sure you quote the tags and any strings. Numbers, like temperature or relative humidity, should not be quoted.

3 Likes

Perfect, that helped out a lot!

The Mustache Tester is very easy to use, thanks for making it :+1:

I created the new response template, so it is JSON formatted.

The webhook returns data in this format:

{"data":"\"Location\":\"Fishers, IN\",\"Time\":\"Last Updated on May 4, 10:54 PM EDT\",\"WeatherDescription\":\"Clear\",\"Temp\":57.6,\"Humidity\":92%,\"Wind\":\"Calm\",\"FeelsLike\":57.6,\"Visibility\":10.0,\"SunRadiation\":0,\"UvIndex\":0.0,\"TotalRainInches\":0.07,\"Icon\":\"clear\",","ttl":60,"published_at":"2018-05-05T02:57:38.423Z","coreid":"particle-internal","name":"29003a001647343432313031_GetCurrentWeatherConditionsRK/0"}

So after looking at your library examples, the Parser Example and the Subscription Example code it looks like I need to use your Subscription Example code to get this working.

In your Subscribe example, you included the ability to Print the received data out for testing, but it seems like all the Print functions/code can be removed if it’s not needed and you can use the commands to pull data out of the received response? Do I have that correct?

Also if I have three different webhook subscriptions do I need three subscriptionHandler’s that are uniquely named for each separate webhook subscription?

My current webhook subscription is triggered when the DeviceID matches the Photons Device ID so one Webhook integration can work for multiple different devices.

Particle.subscribe(System.deviceID(), deviceWebhookHandler, MY_DEVICES);  // every webhook preceeded by your deviceID gets handled by deviceWebhookHandler()!!

Then the deivceWebhookHandler will trigger a function based on the name of the webhook it received as shown below. This is different than your setup, and I’m wondering what you think about this method?

void deviceWebhookHandler(const char* event, const char* data)
 {
   if(strstr(event, "GetCurrentWeatherConditions"))
   {
     GetCurrentWeatherConditions(event, data);
   }
   else if(strstr(event, "Get3DayWeatherForecast"))
   {
     Get3DayWeatherForecast(event, data);
   }
   else if(strstr(event, "GetSunRise_SunSetTimes"))
   {
      GetHoursOfDayLight(event, data);
   }
   else
   {
     Serial.println("recieved some unknown webhook");
   }
 }

Sorry for all the questions, it’s just all new to me. :slight_smile:

Using JsonParserGeneratorRK, you first declare a parser and buffer as a global variable

// Create a parser to handle 2K of data and 100 tokens
JsonParserStatic<2048, 100> jsonParser;

then in your subscription handler, do something like:

void subscriptionHandler(const char *event, const char *data) {
	int responseIndex = 0;

	const char *slashOffset = strchr(event, '/');
	if (slashOffset) {
		responseIndex = atoi(slashOffset + 1);
	}

	if (responseIndex == 0) {
		jsonParser.clear();
	}
	jsonParser.addString(data);

	if (jsonParser.parse()) {
		// Looks valid (we received all parts)

		// This printing thing is just for testing purposes, you should use the commands to
		// process data
		printJson(jsonParser);
	}
}

Then if you want to use a value, you do something like this in place of printJson above.

String strValue;
float floatValue;

if (jsonParser.getOuterValueByKey("Location", strValue)) {
    Serial.printlnf("Location: %s", strValue.c_str());
}
if (jsonParser.getOuterValueByKey("Temp", floatValue)) {
    Serial.printlnf("Temp: %f", floatValue);
}

I have spent hours trying to get this working but with no luck.

Here is the code I’m testing: https://go.particle.io/shared_apps/5aedfe8350f02d51c90000eb

Here is the webhook response:

{“data”:"“Location”:“Fishers, IN”,“Time”:“Last Updated on May 5, 3:00 PM EDT”,“WeatherDescription”:“Clear”,“Temp”:73,“Humidity”:38%,“Wind”:“Calm”,“FeelsLike”:73.0,“Visibility”:10.0,“SunRadiation”:274,“UvIndex”:2.0,“TotalRainInches”:0.00,“Icon”:“clear”,",“ttl”:60,“published_at”:“2018-05-05T19:03:15.614Z”,“coreid”:“particle-internal”,“name”:“jsonParserTest/0”}

My custom webhook looks like this:

The only serial output I get is:

Not really sure what I’m doing wrong :weary:

You have a few problems in your Webhook template.

  • There needs to be {} surrounding the responseTemplate. It should start with { and end with }
  • There’s an extra , at the end after Icon that needs to be removed
  • The humidity has a % sign in it, so it has to be a string, not a number
1 Like

Yes, that worked! :smile:

Now I get a response:

@rickkas7 I got it working now! Thanks for your help.

I have merged your webhook parsing code with my code that only processes the incoming webhook if the device ID matches the ID of the Photon it’s being received by. This way I have one weather webhook that will work for multiple products.

I also learned that all I needed to add to have three separate webhooks parsed and stored I just needed to add more JsonParserStatic variables as shown below. I need to adjust the size and token quantity to maximize memory savings.

// Create a parser to handle 2K of data and 100 tokens
JsonParserStatic<2048, 100> jsonParser;  //This Holds Current Weather Data Returned from Webhook. 
// Create a parser to handle 2K of data and 100 tokens
JsonParserStatic<2048, 100> jsonParser2; //This Holds Sunrise/Sunset Data Returned from Webhook.
// Create a parser to handle 2K of data and 100 tokens
JsonParserStatic<2048, 100> jsonParser3; //This Holds 3 Day Weather Forcast Data Returned from Webhook.

I also needed three separate Subscription Handlers as shown below:

void deviceWebhookHandler(const char* event, const char* data) 
{
   
   if(strstr(event, "WeatherCurrentConditions"))
   {
     subscriptionHandler(event, data);
   }
   else if(strstr(event, "SunRise-SunsetTimes"))
   {
    subscriptionHandler2(event, data);   
   }
   else if(strstr(event, "3DayForcast"))
   {
    subscriptionHandler3(event, data);     
   }
   else
   {
     Serial.println("recieved some unknown webhook");
   }
   
 }



void subscriptionHandler(const char *event, const char *data) {
	int responseIndex = 0;

	const char *slashOffset = strchr(event, '/');
	if (slashOffset) {
		responseIndex = atoi(slashOffset + 1);
	}

	if (responseIndex == 0) {
		jsonParser.clear();
	}
	jsonParser.addString(data);
	
	//jsonParser.parse(); //Run this to parse the incoming webhook data so we can call it up later. 
    
    if (jsonParser.parse()) {
		// Looks valid (we received all parts)

		// This printing thing is just for testing purposes, you should use the commands to
		// process data
		printJson(jsonParser);
	}
	
}


void subscriptionHandler2(const char *event, const char *data) {
	int responseIndex = 0;

	const char *slashOffset = strchr(event, '/');
	if (slashOffset) {
		responseIndex = atoi(slashOffset + 1);
	}

	if (responseIndex == 0) {
		jsonParser2.clear();
	}
	jsonParser2.addString(data);
    
    if (jsonParser2.parse()) {
		// Looks valid (we received all parts)

		// This printing thing is just for testing purposes, you should use the commands to
		// process data
		printJson(jsonParser2);
	}
	
}

void subscriptionHandler3(const char *event, const char *data) {
	int responseIndex = 0;

	const char *slashOffset = strchr(event, '/');
	if (slashOffset) {
		responseIndex = atoi(slashOffset + 1);
	}

	if (responseIndex == 0) {
		jsonParser3.clear();
	}
	jsonParser3.addString(data);
    
    if (jsonParser3.parse()) {
		// Looks valid (we received all parts)

		// This printing thing is just for testing purposes, you should use the commands to
		// process data
		printJson(jsonParser3);
	}
	
}

The tools @rickkas7 has created makes pulling data from webhooks as easy as it gets, especially when I compare it to the amount of work it took to build the original weather forecast code without these tools.

It takes a second to figure out how to use this library but once you get the hang of the tools it makes working with webhooks and JSON much quicker and easier, plus you end up with more efficient and cleaner looking code.

Thanks for the help with using your library. @rickkas7 :+1: :spark:

@rickkas7 Quick question.

I’m trying to use a few pieces of data being returned in a webhook to calculate some data.

I’ve tried everything I thought would work but all attempts returned 1.

Below is one example of what I thought might work.

 int hourofsunrise = (jsonParser2.getOuterValueByKey("SunriseHour", intValue));
 int minofsunrise = (jsonParser2.getOuterValueByKey("SunriseMin", intValue));
 int hourofsunset = (jsonParser2.getOuterValueByKey("SunsetHour", intValue));
 int sunsetmin = (jsonParser2.getOuterValueByKey("SunsetMin", intValue));

I’m trying to run the math below, once the variables above are stored correctly:

 int adjsunsethour = (hourofsunset-12); //Eliminate the negative sign in the hour number.
 int morningsunhours = 12 - hourofsunrise;
 int sunhoursconvertedtomins = (morningsunhours + adjsunsethour) * 60;
 int totaldaysunmins = (60 - minofsunrise + sunsetmin);
 float totaldaylighthours = (sunhoursconvertedtomins + totaldaysunmins) / 60.0;

Any help is appreciated!

some advice would be:

  1. use your objects to make your program easier to understand
  2. use Time Stamps to make the maths easier

example:

void myParseFunction() {
  // other stuff....
  //
  struct SunTime {
    uint8_t hour;
    uint8_t minute;
    uint32_t ts;
  };
  SunTime sunrise;
  sunrise.hour = (jsonParser2.getOuterValueByKey("SunriseHour", intValue));
  sunrise.minute = (jsonParser2.getOuterValueByKey("SunriseMin", intValue));
  sunrise.ts =  tmConvert_t(Time.year(), Time.month(), Time.day(), sunrise.hour, sunrise.minute, 0);
  SunTime sunset;
  sunset.hour = (jsonParser2.getOuterValueByKey("SunsetHour", intValue));
  sunset.minute = (jsonParser2.getOuterValueByKey("SunsetMin", intValue));
  sunset.ts =  tmConvert_t(Time.year(), Time.month(), Time.day(), sunrise.hour, sunrise.minute, 0);
  uint32_t seconds = sunset.ts - sunrise.ts;
  uint8_t hoursInDay = seconds / 3600;
  seconds = seconds % 3600;  // seconds remaining
  uint8_t minutesInDay = seconds / 60;
  Serial.printf("Day's duration: %2dhours and %02dminutes" , hoursInDay, minutesInDay);
}

time_t tmConvert_t(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh, uint8_t mm, uint8_t ss) {
  struct tm t;
  t.tm_year = YYYY-1900;
  t.tm_mon = MM - 1;
  t.tm_mday = DD;
  t.tm_hour = hh;
  t.tm_min = mm;
  t.tm_sec = ss;
  t.tm_isdst = 0;
  time_t t_of_day = mktime(&t);
  return t_of_day;
}
2 Likes

The problem is that getOuterValueByKey returns a Boolean value indicating if the key exists and the retrieval was successful. That’s why all your values are 1. The actual JSON integer value is stored in intValue. I think you need to retrieve your values like this:

int hourofsunrise = 0;
if (jsonParser2.getOuterValueByKey("SunriseHour", hourofsunrise)) {
    //do something if you need to but value is already in hourofsunrise
}
1 Like

I never would have thought of that :slight_smile:

I’ve never really dealt with the Time functions yet so this is new but I do like that it’s easier to read.

The code I posted I needed to be cleaned up so it makes more sense, that was planned. I was just copying and pasting from a previous working example and trying to get correct data before tidying up.

I appreciate the suggestion for sure :+1:

That makes sense, and your example did work so Thank You for helping me figure this out :slight_smile: