Handle webhook chunks

Your comments are superfluous, since I demonstrated exactly that technique in the exemplar. :wink:

webhookHandler(); will respond to any event preceded with the deviceID it then looks to narrow down the candidates of webhooks he is using by using the strstr() method and subsequently calling a webhook specific function for that particular webhook/event.

strcpy(weatherData, "");  // empty the string
strcpy(weatherData, data);  // copy the first return to the empty string

A null terminated string with a null at [0] or in all positions is an empty string for the purposes of C strings. The pseudo code was shown as a way of showing what's happening and no other reason.

Not everyone provides examples the same way.

5a6a3bb057a854b6f60ef38d08ae00e5a4b5df99

My response about "superfluous" was meant in answer to above question :wink:

In deed you did :+1:
I just thought to point out the behaviour how the cloud might add "unexpected" extra info to the hook response to be prepared when transitioning from a developer device to a product device.

Thanks to both of you for your help with this :smiley:

Everything makes sense so far, I still have not had the time to test the code just yet but should get to it in the next 24 hours.

@ScruffR What do you mean when you say this:

I'm formatting my Particle.subscribe code like this and it's working just fine. This is how it's handled in Particle Products Webhooks also I think:

Particle.subscribe(System.deviceID() + "_Forecast2Day", gotForecast2, MY_DEVICES);

If you are using “product webhooks” your responses may get decorated with the the device ID again (at least that was the plan)
So if you publish an event <yourDeviceID>_EventName to a webhook, the response for developer devices usually looks like

hook-response/<yourDeviceID>_EventName

but for “product webhooks” it should look like

<yourDeviceID>_hook-response/<yourDeviceID>_EventName

(unless this has changed and I missed the memo :flushed: - I’ll have to double check tho’)

1 Like

@ScruffR That is good to know, I was not aware of that difference :thumbsup:

@BulldogLowell I finally had a chance to get the webhook response template written, so it returns all four days weather forecast which so far is coming back within two separate webhook replies that are posted below:

Return #1=

{"data":""responseTemplate":
"8:55 PM EST~partlycloudy~Friday~Clearing skies late. Lows overnight
in the low 30s.~10~nt_partlycloudy~Friday Night~Cloudy early with some
clearing expected late. Low 31F. Winds WNW at 5 to 10
mph.~10~partlycloudy~Saturday~Partly to mostly cloudy. High 46F. Winds W
at 5 to 10 mph.~10~nt_partlycloudy~Saturday Night~A few clouds. Low
32F. Winds SSW at 5 to 10 mph.~10~partlycloudy~Sunday~Partly cloudy.
High 52F. Winds SSW at 5 to 10 mph.~10~nt_rain~Sunday Night~Partly
cloudy skies early wi","ttl":"60","published_at":"2016-11-26T02:53:04.309Z","coreid":"particle-internal","name":"2f003a000e51353338363333_Test3/0"}

Response #2=

{"data":"ll give way to cloudy skies and rain later during the night. Low 41F. Winds SSE at 10 to 15 mph. Chance of rain 90%.~90~rain~Monday~Cloudy and windy with periods of rain. High near 50F. Winds SSE at 20 to 30 mph. Chance of rain 100%. Rainfall near a quarter of an inch.~100~nt_rain~Monday Night~Rain likely. Low 46F. Winds S at 15 to 25 mph. Chance of rain 100%. Rainfall near a half an inch.~100~ ",","ttl":"60","published_at":"2016-11-26T02:53:04.591Z","coreid":"particle-internal","name":"2f003a000e51353338363333_Test3/1"}

It looks like the response template is fine.

Now here is my webhook handler code:

void gotForecast2(const char* event, const char* data)
{
  String myID = System.deviceID();

  static char weatherData[1024] = "";
  if(strstr(event, "(myID)_Test3/0"))
  {
  strcpy(weatherData, "");
  strcpy(weatherData, data);
  Serial.println(weatherData);
  }
  else if (strstr(event, "(myID)_Test3/1"))
  {
  strcat(weatherData, data); //concatenate the first return with the second
  // tokenize and parse here...
  // strtok(weatherData, "~");
  Serial.println(weatherData);
  }
  else
  {
  // error handling here...
  }
}

I'm trying to serial print what is held in the weatherData variable to make sure it's filling up correctly but it's not printing.

I need to figure out how to properly parse the data between the ~'s . I've watched some youtube videos on the Strtok function and it looks simple enough, I just now am getting the response template setup correctly.

So my question is how do I serial print what gets put into the WeatherData variable as data gets added to it from 2 seperate webhook responses. Any help is will be appreciated :smile:

It's nice to have learned how to join the data from 2 separate webhook responses :smiley:

Neither prints using the function above? That suggests to me that the function isn't being called or your filtering is not working as expected.

Can you post your entire code or a prototype of this effort?

@BulldogLowell

Thanks for helping me along with this. Here is the complete code below.

I’ve verified that the Test3 forecast webhook gets called and returns properly via wathcing the particle console logs. :

      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.
    
      Serial.begin(115200);
    
      // Lets listen for the hook response
      // Subscribe to the integration response event
      Particle.subscribe(System.deviceID() + "_current_weather", gotWeatherData, MY_DEVICES);
      Particle.subscribe(System.deviceID() + "_Test3", gotForecast2, MY_DEVICES);
     //Particle.subscribe(System.deviceID() + "_SunFishersIN", gotSunData, 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);
    
      delay(5000);
      //Particle.publish("SunFishersIN");
      Particle.publish("Test3", 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.
    
      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);
      }
    
      }
    
    
    
    
    //////////////////////////////////////////////////////////////////////////////
      void gotForecast2(const char* event, const char* data)
      {
        String myID = System.deviceID();
    
        static char weatherData[1024] = "";
        if(strstr(event, "(myID)_Test3/0"))
        {
          strcpy(weatherData, "");
          strcpy(weatherData, data);
          Serial.println(weatherData);
        }
        else if (strstr(event, "(myID)_Test3/1"))
        {
          strcat(weatherData, data);  //concatenate the first return with the second
          // tokenize and parse here...
          // strtok(weatherData, "~");
          Serial.println(weatherData);
        }
        else
        {
          // error handling here...
        }
      }
    
    
    
    
    ///////////////////////////////////////////////////////////////////////
      // 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);
      } 

I’d simplify the subscribe to respond to ANY publish preceded by your device ID and then sort it out from there:

 Serial.begin(115200);

  // Lets listen for the hook response
  // Subscribe to the integration response event
  Particle.subscribe(System.deviceID(), deviceWebhookHandler, MY_DEVICES);  // every webhook preceeded by your deviceID gets handled by deviceWebhookHandler()!!
  //Particle.subscribe(System.deviceID() + "_current_weather", gotWeatherData, MY_DEVICES);
  //Particle.subscribe(System.deviceID() + "_Test3", gotForecast2, MY_DEVICES);
  //Particle.subscribe(System.deviceID() + "_SunFishersIN", gotSunData, MY_DEVICES);

then sort it using the handler, you don’t need the deviceID here because you already filtered for that!!

void deviceWebhookHandler(const char* event, const char* data)
{
  if(strstr(event, "current_weather")
  {
    gotWeatherData(event, data);
  }
  else if(strstr(event, "SunFishersIN")
  {
    gotSunData(event, data);
  }
  else if(strstr(event, "Test3")
  {
    gotForecast2(event, data)
  }
  else
  {
    Serial.println("recieved some unknown webhook");
  }
}

finally, let’s verify that the function got called by adding a publish that identifies it:

void gotForecast2(const char* event, const char* data)
{
  Particle.publish("Handling my forecast!!", data);  // so you know you are here... watch the console
  static char weatherData[1024] = "";
  if (strstr(event, "Test3/0"))
  {
    strcpy(weatherData, "");
    strcpy(weatherData, data);
    Serial.println(weatherData);
  }
  else if (strstr(event, "Test3/1"))
  {
    strcat(weatherData, data);  //concatenate the first return with the second
    // tokenize and parse here...
    // strtok(weatherData, "~");
    Serial.println(weatherData);
  }
  else
  {
    // error handling here...
  }
}

entire edit:

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.

  Serial.begin(115200);

  // Lets listen for the hook response
  // Subscribe to the integration response event
  Particle.subscribe(System.deviceID(), deviceWebhookHandler, MY_DEVICES);  // every webhook preceeded by your deviceID gets handled by deviceWebhookHandler()!!
  //Particle.subscribe(System.deviceID() + "_current_weather", gotWeatherData, MY_DEVICES);
  //Particle.subscribe(System.deviceID() + "_Test3", gotForecast2, MY_DEVICES);
  //Particle.subscribe(System.deviceID() + "_SunFishersIN", gotSunData, 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);

  delay(5000);
  //Particle.publish("SunFishersIN");
  Particle.publish("Test3", 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, "current_weather")
  {
    gotWeatherData(event, data);
  }
  else if(strstr(event, "SunFishersIN")
  {
    gotSunData(event, data);
  }
  else if(strstr(event, "Test3")
  {
    gotForecast2(event, data)
  }
  else
  {
    Serial.println("recieved some unknown webhook");
  }
}


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

// 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.

  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);
  }

}

//////////////////////////////////////////////////////////////////////////////
void gotForecast2(const char* event, const char* data)
{
  Particle.publish("Handling my forecast!!", data);  // so you know you are here... watch the console
  static char weatherData[1024] = "";
  if (strstr(event, "Test3/0"))
  {
    strcpy(weatherData, "");
    strcpy(weatherData, data);
    Serial.println(weatherData);
  }
  else if (strstr(event, "Test3/1"))
  {
    strcat(weatherData, data);  //concatenate the first return with the second
    // tokenize and parse here...
    // strtok(weatherData, "~");
    Serial.println(weatherData);
  }
  else
  {
    // error handling here...
  }
}




///////////////////////////////////////////////////////////////////////
// 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);
}

not compiled, not tested

1 Like

@BulldogLowell

Got your code to compile after adding some extra “)”'s to the devicehandler function code.

I can see the handler for the forecast webhook being called in the Particle Console as shown in the screenshot below:

I’m still not seeing the weatherData variable serial printing as it gets filled up. I’m not sure if I can serial print the static char weatherData[1024] = ""; directly or not.

Here is the function where we now know the Publish is working as it should.

void gotForecast2(const char* event, const char* data)
{
  Particle.publish("Handling my forecast!!", data);  // so you know you are here... watch the console
  static char weatherData[1024] = "";
  if (strstr(event, "Test3/0"))
  {
    strcpy(weatherData, "");
    strcpy(weatherData, data);
    Serial.println(weatherData);
  }
  else if (strstr(event, "Test3/1"))
  {
    strcat(weatherData, data);  //concatenate the first return with the second
    // tokenize and parse here...
    // strtok(weatherData, "~");
    Serial.println(weatherData);
  }
  else
  {
    // error handling here...
  }
}

My serial print is looking like this:

@RWB,

I had the chance to test this for you and got it to work with this webhook:

{
	"event": "Weather_Test",
	"url": "http://api.wunderground.com/api/c1a68f78b8712baf/forecast/q/IN/marion.json",  
	"requestType": "POST",
	"headers": null,
	"query": null,
	"responseTemplate": "{{#forecast}}{{#txt_forecast}}{{#forecastday}}{{title}}~{{icon}}~{{fcttext}}~{{/forecastday}}{{/txt_forecast}}{{/forecast}}",
	"responseTopic": "{{SPARK_CORE_ID}}_Weather_Test",
	"json": null,
	"auth": null,
	"coreid": null,
	"deviceid": null,
	"mydevices": true
}

and the test code below. Note that I had to make the buffer (weatherData[]) bigger. You will have to mess around wth that I believe. I tested on a Core:


void setup(void)
{
  Serial.begin(115200);
  Particle.subscribe(System.deviceID(), webhookHandler, MY_DEVICES);
  Particle.function("GetWeather", getWeather);
  Serial.println("Ready");
}

void loop(void)
{

}

void webhookHandler(const char* event, const char* data)
{
  if(strstr(event, "Weather_Test"))
  {
    handleWeatherTest(event, data);
  }
}

void handleWeatherTest(const char* event, const char* data)
{
  static char weatherData[1032] = "";
  if(strstr(event, "_Weather_Test/0"))
  {
    strcpy(weatherData, data);
    //Serial.print("First batch:\t");
    Serial.println(weatherData);
  }
  else if(strstr(event, "_Weather_Test/1"))
  {
    strcat(weatherData, data);
    /*Serial.print("Complete String:\t");
    Serial.println(weatherData);*/
    char* ptr = strtok(weatherData, "~");
    while(ptr)
    {
      Serial.println(ptr);
      ptr = strtok(NULL, "~");
    }
  }
}

int getWeather(String data)
{
  if(data.toInt())
  {
    Serial.println("requesting weather");
    Particle.publish("Weather_Test");
  }
}

and just put a “1” into the getWeather function to call the webhook…

1 Like

@BulldogLowell Just now had a chance to try out your code and it works as it should.

I need to switch over to your way of calling webhook handlers. :smile:

It seems the Device ID was not being populated via this code:

Should I be using a String here?

I inserted my device ID into the webhook return line and it works fine.

yeah, if that work, well, that works!

I just prefer to have one filter for the deviceID and then sort it all out in the webhookHandler() function. I also use some common web-hooks to which many of my devices may respond and I don’t use that filter… rather, I have two subscribe() functions.

Some like broccoli, some Brussels sprouts.

happy to see it is working for you now!

:slight_smile:

@BulldogLowell I’m switching over to your webhook handler function layout because it consumes less code if your processing multiple webhooks.

I did figure out what the problem was with my current code though:

My Device ID was not populating correctly.

This is what did not work:
11d9018f036cbdc86972e10a902c8e04006b5a2b

The code below did work. I was already using it to sort which webhooks had my Device ID attached successfully so I should have noticed that before now.

Always learning something new :smiley:

Thanks for helping me with this! You really did help me out tons :thumbsup:

1 Like

I am wondering if there is a way to know if the webhook “publish” task is completed. We solved for template results that are larger than 512 bytes, but I am wondering what is happening behind the scene.

If to get the chunks 2, 3, n, there are additional calls; we need a deterministic way to know when the calls are done. I am not looking for a “delay(n)”, I am looking for a “waitfor-like” instruction. Thanks.

I’m working on adding some webhook weather forecast code from last year into a LED matrix display to show current weather conditions.

The code compiled fine almost a year ago but now I get the following error on a parsing function when trying to compile the same code for the Photon or Electron.

@ScruffR @BulldogLowell Any idea what has changed to trigger this error?

/////////////////////////////////////////////////////////////////////////
 // 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 issue seems to be with the 3 return NULL lines I think.

return NULL;

:confused:

That “error” was introduced a while ago when the ominous String class was updated (I thinke even pre 0.6.0).

You now need to use "" instead of NULL when refering to a unset/empty String.

1 Like

Thank you very much!

As usual, your 100% correct :slight_smile:

Works now :spark:

Merry Christmas :christmas_tree:

2 Likes

Another alternative is my parser/generator, JsonParserGeneratorRK based on JSMN. It’s really memory-efficient and optimized to handle multi-part webhook responses, including detecting when the last chunk of data arrives.

And for really complicated JSON data, there’s this tool where you paste in your JSON and it pretty-prints it for you. Then you just click on a row and it generates the code you need to access that piece of data, presumably nested within objects and arrays, from C++ code, like this:

parser.getReference().key("list").index(0).key("weather").index(0).key("description").valueString()
8 Likes

@rickkas7 That’s a pretty sweet tool you put together :wink:

I checked out the online tool and the Github page and I will use it for pulling in more weather data I’m displaying on a LED Matrix.

Your library and tool make it much less intimidating to properly setup large Jason responses, especially the ones that are returned in multi-part chunks which I had working for 2 chunks but never did figure out how to get the 3rd chunk for some reason.

Thanks for sharing! :spark:

3 Likes