First of all many thanks to @peekay123 @BulldogLowell and @Awake for their help in getting my particular use case to work but hoping that I can distill this down so others might find the answers they need easier than I did. I want to be clear that almost none of this is my own original work but an amalgamation of the previously mentioned people’s work.
What I wanted to accomplish was to query the WeatherUnderground API for Forecast data to include the following; Day of the Month, Max Temp, Min Temp and Max Windspeed. Here is how I accomplished that and have the response values displayed on a 128x64 OLED (I won’t be covering the OLED implementation for this tutorial).
Step 1: Identify the API you wish to call, the response you will get and what values you want from that response. Here is a JSON weatherunderground response for Dallas, TX that has been formatted using a Chrome Extension called JSONView JSONView Chrome Extension
I have collapsed the response and txt_forecast sections to be able to show the data I want to work with.
Once you know what data you want, the “sections” where it is contained you will need to create a file called whatever.JSON that will be used to create your webhook on the Particle.IO cloud. Remembering the data I wanted was the Day of the Month, High and Low Temp and Windspeed…here is my webhook that I named weatherU.json
{
"event": "weatherU_hook",
"url": "http://api.wunderground.com/api/YOURAPIKEYGOESHERE/forecast/q/TX/Allen.json",
"requestType": "POST",
"headers": null,
"query": null,
"responseTemplate": "{{#forecast}}{{#simpleforecast}}{{#forecastday}}{{date.day}}~{{high.fahrenheit}}~{{low.fahrenheit}}~{{maxwind.mph}}~{{/forecastday}}{{/simpleforecast}}{{/forecast}}",
"json": null,
"auth": null,
"mydevices": true
}
Now let’s go through this a bit and explain what is there and why. The event entry is what will be shown on Particle.IO dashboard when this webhook is invoked by Spark.publish or via the CLI for testing (more on this latter). Url is not surprisingly the url of the API you wish to obtain data from. RequestType for this API is POST, some will be GET…read the documentation of the API you are working with to determine which is appropriate. I didn’t need a header or query but again, this is API dependant so my weatherU.json definitely wouldn’t work with OpenWeatherMap example in the Particle Docs which requires query entries. Now the good stuff, the reason we are doing this in the first place…the DATA! If you look at the screenclip with formatted data then the webhook you’ll notice that any section that can be expanded is opened with a # and closed with a / (Example: {{#simpleforecast}}Bunch of other tags{{/simpleforecast}}. I was able to dive into the JSON message and then select only the data I want using the response filter and Mustache. Check out trymustache.com for more on that. Notice the ~ between entries, that is important for later when we need to parse out the response into usable data. In my example I selected the Forecast Section, then Simpleforecast then Forecastday. Refer back to the screenclip again and you’ll see that once in the forecastday section of the response I then selected the exact data I wanted by subsection.item entries in my Response Filter and closed up the sections by working backwards, ‘close’ the last thing you ‘opened’ first and progress like so until you are back to the main (I did not close the main section #forecast but it didn’t affect my results as there was no higher entry to get to). So to get the Day of the Month I entered {{date.day}} and so on for the other data points I needed for my application. The remaining entry that is Particle specific is the "mydevices": true
which tells the Particle.io cloud that this webhook is only for the devices I have claimed. Don’t put webhooks out there for public that have your personal API key and don’t post your API keys for anything!
Step 2. Now take your whatever.json file and save it someplace you will remember and open the particle cli. Login to the particle.io cloud by typing “particle login”, enter your email and password then navigate to the folder where you saved whatever.json. Now you can create your webhook by running “particle webhook create whatever.json”.
You will receive a success or fail message along with your webhook ID (which as far as I can tell you only need if you want to delete it later but you can find that anytime by running “particle webhook list”. Now that you have a webhook it is time to test if you got your {{}} and ~ where they return something you can parse and use. To test your newly minted webhook, open dashboard.particle.io in your browser of choice, run “particle publish your_event_name”, here is my weatherU_hook event being tested from the cli.
Great, we posted an event but where did it go? If you go back to the Particle.io Dashboard you opened earlier you should see two new events, like this.
NOTE:If you don’t have a response that means your webhook was bad and did not return any data to the particle.io cloud, check the API documentation again and adjust accordingly.
Step 3. Now we have a webhook that is returning the data we want with ~ between each entry so we can parse it. In my example the weatherunderground forecast returns current +3 more days worth of forecast data. So when you look at the data in the response you will see dayofmonth~maxtempthatday~mintempthatday~maxwindspeedthatday~ and that is repeated 3 more times. In my example you can see it is the 21st of August so the first piece of data is 21. In our particle firmware there are few things to do so we can “trigger” (proper term is Publish) this webhook, catch the response and parse it.
Step 4, Starting with how to trigger a webhook call. First we define the hook Response and Hook Publish statements (this makes it modular for later, instead of changing lines in code we can just edit the definitions in the beginning of our firmware).
#define HOOK_RESP "hook-response/weatherU_hook"
#define HOOK_PUB "weatherU_hook"
Remember we named our hook event "weatherU_hook and if you look at the data in dashboard you will see how that correlates to the HOOK_RESP entry.
Working our way down our firmware code we need to put an entry in setup to subscribe to our hook response. This tells our device what to do when the particle.io cloud posts a response. My entry looks like so:
Spark.subscribe(HOOK_RESP, gotweatherData, MY_DEVICES);
This will call the function gotweatherData when the particle.io cloud posts a hook response that matches my HOOK_RESP in my definitions. Starting to tie together?
Now lets trigger (Spark.publish) our webhook in our code so the particle.io cloud will reach out to the API we designated and post a response. In my instance the call looks like this:
Spark.publish(HOOK_PUB);
This actually isn’t sufficient though. I have learned that you need to give the cloud some time to respond or depending on how you are calling this your loop will post your Spark.publish over and over. Here is my getWeather function (which is a slightly modified version of @peekay123 getWeather function in his RGB Pong Clock project Peekay123 RGBPong Clock Github).
//Updates Weather Forecast Data
void getWeather() {
Serial.print("in getWeather");
Serial.println();
weatherGood = false;
// publish the event that will trigger our webhook
Spark.publish(HOOK_PUB);
unsigned long wait = millis();
//wait for subscribe to kick in or 5 secs
while (!weatherGood && (millis() < wait + 5000UL))
//Tells the core to check for incoming messages from particle cloud
Spark.process();
if (!weatherGood) {
Serial.print("Weather update failed");
Serial.println();
badWeatherCall++;
if (badWeatherCall > 2) {
//If 3 webhook call fail in a row, Print fail
Serial.print("Webhook Weathercall failed!");
}
}
else
badWeatherCall = 0;
}//End of getWeather function
Step 5. Now we’ve built a webhook, subscribed to that event and have called for particle.io to give us the response which will in turn call our gotWeather function. The gotweatherData function will parse the response from a bunch of numbers with ~ between them to the data I actually want. Here is my gotWeather function which uses the ~ to tokenize the data and maps it to int variables for use elsewhere.
void gotweatherData(const char *name, const char *data) {
Serial.print("Running gotweatherData function");
String str = String(data);
char strBuffer[125] = "";
str.toCharArray(strBuffer, 125); // example: "\"21~99~75~0~22~98~77~20~23~97~74~10~24~94~72~10~\""
int forecastday1 = atoi(strtok(strBuffer, "\"~"));
int maxtempday1 = atoi(strtok(NULL, "~"));
int mintempday1 = atoi(strtok(NULL, "~"));
int maxwindday1 = atoi(strtok(NULL, "~"));
int forecastday2 = atoi(strtok(NULL, "~"));
int maxtempday2 = atoi(strtok(NULL, "~"));
int mintempday2 = atoi(strtok(NULL, "~"));
int maxwindday2 = atoi(strtok(NULL, "~"));
int forecastday3 = atoi(strtok(NULL, "~"));
int maxtempday3 = atoi(strtok(NULL, "~"));
int mintempday3 = atoi(strtok(NULL, "~"));
int maxwindday3 = atoi(strtok(NULL, "~"));
if (forecastday1 == Time.day()) {
currentFcastday = forecastday1;
currentFcastmax = maxtempday1;
currentFcastmin = mintempday1;
currentFcastwind = maxwindday1;
Serial.print("Selected forecastday1");
}
if (forecastday2 == Time.day()){
currentFcastday =forecastday2;
currentFcastmax = maxtempday2;
currentFcastmin = mintempday2;
currentFcastwind = maxwindday2;
Serial.print("Selected forecastday2");
}
bool weatherGood = true;
updateweatherhour = Time.hour();
}
In my example since I received multiple days worth of data but only want the current day so I am making sure that the day of the month matches the current system day of the month then maps them to different int vars I am using elsewhere in my code.
Just to round this out my application only requires that I update the forecast once an hour so I am tracking if the forecast has been updated in the current hour and only calling for my getWeather function when the current hour doesn’t match the updateweatherhour var. Here is what that looks like in my loop.
if (Time.hour() != updateweatherhour){
getWeather();
}
@peekay123’s RGBPongClock has a much more elegant way of doing all of this but I simplified it down for my needs. If you are parsing data that isn’t an int value you can find examples of float values and other things in this thread: Community Thread: Execute function once a day OR after reset/reboot
Thanks everyone for a great community and all the help, hopefully this will help others in the future.