Creating Webhook for Weather Conditions for Fleet Of Products

I am wondering how I should go about creating code that will use a Webhook to have past, current, and future weather conditions sent to my products.

So I will have products that will be located all over the world, and I want each of them to be able to call weather information via a Webhook but when they send that webhook data it will need to include it’s current location via GPS, City State, or zip code info.

I have created a webhook via the Particle Console that has my location hard coded into the webhook.

I assume that there is a way to add the specific location data for the actual webhook sender but it’s all new to me at this point.

Is there a way to craft this code so I do not need to create a webhook for each individual product that I create but instead create a single webhook template that will allow me to use it for all devices we deploy even if that’s 5000 devices?

Any advice will be appreciated!

Are you asking about sending a json body in the request?

Here is my template for an IFTTT webhook, but it can be modified to whatever you need.

"{{SPARK_EVENT_VALUE}}" gets replaced with the string that you pass in Particle.publish when calling the webhook.

@nrobinson2000 Yes that's exactly what I'm trying to do.

I want a single webhook that I can call where the device calling the webhook also includes the Zip Code for it's current location.

I just need to make the last part of the weblink below which contains the zip code something that I can pass in from the Particle device calling the webhook.

http://api.wunderground.com/api/Your-API- Key/conditions/q/46037.json

I can see from your example that your passing info from the device that is calling the webhook via the {{Spark_Event_Value}} which get's labeled as value1 but I do not see how your adding those to the actual weblink URL that get's called.

I have found this link that looks like it may have the solution, I just need to take some more time to try to figure it out.

If I create a single webhook for returning weather, I assume I can have devices that are listening for that webhook return to only use that data if it the device ID matches it's actual device ID?

That's because it POSTs the json to the URL.

I just thought that the {{Spark_Event_Value}} was being added to that actual url.

Or is the Value1 added after the IFTTT_KEY_HERE part of the url?

So why not do:

http://api.wunderground.com/api/Your-API-Key/conditions/q/{{SPARK_EVENT_VALUE}}.json

All your particle would have to do would be to publish the ZIP code.

That makes sense and sounds simple :smiley:

I’ll give it a shot and report back.

value1 is one of the json fields for IFTTT

The magic templates are explained more in this section:

And here

https://docs.particle.io/guide/tools-and-features/webhooks/#webhook-variables

You really want to pass the location, but you also want to the device to subscribe only to weather calls it made!

if you build your web hook using the response topic, each device can trigger the webhook and only respond to those calls it made. I call one webhook from many devices like this:

{
	"event": "current_weather",
	"url": "http://api.wunderground.com/api/yourAPIkey/conditions/q/{{my-state}}/{{my-city}}.json",
	"requestType": "POST",
	"headers": null,
	"query": null,
	"responseTemplate": "{{#response}}{{#current_observation}}{{#estimated}}{{weather}}~{{temp_f}}~{{relative_humidity}}~{{wind_dir}}~{{wind_gust_mph}}~{{dewpoint_f}}~{{/estimated}}{{/current_observation}}{{/response}}",
	"responseTopic": "{{PARTICLE_DEVICE_ID}}_current_weather",
	"json": null,
	"auth": null,
	"coreid": null,
	"deviceid": null,
	"mydevices": true
}

the device must subscribe to all webhooks that contain its device ID in the response topic:

deviceID.toCharArray(responseTopic,125); // responseTopic is a global char array
Particle.subscribe(responseTopic, webhookHandler, MY_DEVICES);
sprintf(publishString, "{\"my-city\": \"%s\", \"my-state\": \"%s\" }", cityLocation, stateLocation);  

calling a webhook like this"

Particle.publish("current_weather", publishString, 60, PRIVATE);

your location specific variables are passed along to the webhook and your device knows it made that request, ignoring any other device’s use of that webhook. You merely sort it all out in the webhook handler:

void webhookHandler(const char *event, const char *data)
{
   if(strstr(event, "current_weather"))
  {
    gotWeather(event, data);
  }
  else if (strstr(event, "forecast_weather"))
  {
    gotForecast(event, data);
  }
  else if (strstr(event, "sun_time"))
  {
    gotSunTime(event, data);
  }
}
2 Likes

@BulldogLowell Yes, that looks like exactly what I want to do.

I see that the in the Particle Console when you create a product you can then set up webhooks and click a button only to allow the response to be picked up by the device that sent requested the info.

I do like @BulldogLowell 's code though because it does the same thing without the need for paying fees associated with product services once you go over 25 devices. It’s nice that the Product service offers this easy to use a push button to allow only the device that requested the webhook to receive that reply.

Now I have to code this, so it works for me :smiley:

1 Like

@BulldogLowell I got it working using topics!

The code I used is a mix between the latest code in the Particle Documentation which uses a different formatted Particle.subscribe function code.

Here is what I used that is working so far:

@ScruffR Am I using the correct container format to hold the cityLocation & statLocation text? I see you can use strings also, but I just searched around on the forum until I found more code by @BulldogLowell
where he used the const char*

Program Code:

  // called once on startup
  const char* cityLocation = "Marion"; //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.
  
  // Learn more about the serial commands at https://docs.particle.io/reference/firmware/photon/#serial
  // for the Photon, or https://docs.particle.io/reference/firmware/core/#serial for the Core
  // You can also watch what's sent over serial with the particle cli with
  // particle serial monitor
  Serial.begin(115200);
  
  // Lets listen for the hook response
  // Subscribe to the integration response event
  
  Particle.subscribe(System.deviceID() + "_current_weather", gotWeatherData, MY_DEVICES);
  
  
  
  // 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<10;i++) {
  Serial.println("waiting " + String(10-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("current_weather", publishString, PRIVATE);
  
  // and wait at least 60 seconds before doing it again
  delay(60000 * 5 );
  }
  
  // This function will get called when weather data comes in
  void gotWeatherData(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.
  //
  // Sample data:
  // <location>Minneapolis, Minneapolis-St. Paul International Airport, MN</location>
  // <weather>Overcast</weather>
  // <temperature_string>26.0 F (-3.3 C)</temperature_string>
  // <temp_f>26.0</temp_f>
  
  
  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);
  }
  
  
  
  }
  
  
  
  // This function will get called when weather data comes in
  void gotSunData(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.
  //
  // Sample data:
  // <location>Minneapolis, Minneapolis-St. Paul International Airport, MN</location>
  https://build.particle.io/build/5714505f328841b54e0002c2# // <weather>Overcast</weather>
  // <temperature_string>26.0 F (-3.3 C)</temperature_string>
  // <temp_f>26.0</temp_f>
  
  
  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 hourofsunset = sunsethourStr.toInt();
  int adjsunsethour = (hourofsunset-12);
  
  
  if (sunrisehourStr != NULL) {
  Serial.println("Sunrise at: " + sunrisehourStr + String(":") + sunriseminuteStr + String(" AM") + " / Sunset at: " + adjsunsethour + String(":") + sunsetminuteStr + String(" PM"));
  }
  
  
  
  
  
  
  Serial.println("-------------------");
  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 NULL;
  }
  
  int idx = str.indexOf(start);
  if (idx < 0) {
  return NULL;
  }
  
  int endIdx = str.indexOf(end);
  if (endIdx < 0) {
  return NULL;
  }
  
  return str.substring(idx + strlen(start), endIdx);
  }

The Post URL is: http://api.wunderground.com/api/YourAPIkey/conditions/q/{{my-state}}/{{my-city}}.json

1 Like

@RWB, my perspnal preference are also C strings (char* or char[]) since they don’t do anything without you knowing :wink:
String objects offer some more convenience, but also grow and relocate from time to time which might cause unexpected troubles.

2 Likes