IFTTT Integration and GMT

I can't seem to locate the answer to this question at IFTTT and I can't find a user forum to ask it there, so maybe someone here knows.

I have a webhook integration between my firmware running on a Boron and IFTTT. My IFTTT applet adds a row to a Google Sheet. The row format is: {{OccurredAt}} ||| {{EventName}} ||| {{JsonPayload}}. The OccurredAt parameter appears in the sheet as GMT time (see attached).

So I actually have two questions:

  1. Is the time as reflected in the sheet the time the Particle.publish command was executed on the Boron or is it the time when IFTTT got around to executing the applet (maybe a few seconds or more later)?

  2. If it is the time that Particle.publish was executed, how can I modify it to reflect my current time zone instead of GMT (I see nothing in the integration string that says anything about time)? If it is a function of IFTTT and not my firmware, how can I add a data element to my publish command that reflects current local time? If I can do that, then I can remove the OccurredAt parameter from my IFTTT applet.

I have reviewed a dozen posts here on how to use the various time and date functions and my head hurts (can't make anything work). Would just like to subtract 4 hours from GMT and add a variable to my publish command.

I'm almost certain {{OccurredAt}} is the timestamp that IFTTT adds when it processes an incoming Webhook.

There is a different timestamp in the Particle event, but that's the time the Particle cloud received the event from the device. It's always UTC, and is not affected by the time set on the device.

So what would the code look like in my firmware to set a variable to the current cloud time, adjusted by the zone offset? If I could set such a variable, e.g. 'mytime', to that adjusted time, then I could simply include it in my Particle.publish command and it will become part of the JSON payload, e.g. "Particle.publish("Well",d +" rec'd @ "+ myTime);" // Send status to log

I have been experimenting all day with Time.zone(-4) and Time.now() and can't get anything to work.

Hey, I added time zone to a small fw and got this:

0000312514 [app] INFO: time: Tue Oct 15 19:58:32 2024

code:

    Time.zone(-4);
    Log.info("time: %s", Time.format(Time.now()).c_str());

Does that work for you?
Cheers,

1 Like

Interesting Gustavo. Since I am not very proficient at programming (learning as I go), how do I convert the line "Log.info("time: %s", Time.format(Time.now()).c_str());" to a variable that I can insert into a Particle.publish statement?

Hey, we are all learning here.
Are you able to use ChatGPT? It can help you with that, I copy-pasted your question into its regular version. I guided it a bit with "particle embedded c++" and "use c strings" (so it does not use the String which may contribute to memory problems in long running embedded programs):

Then:

So, I would try:

char timeStr[64]; // buffer to hold the formatted time string

// Get the formatted time as a C string
snprintf(timeStr, sizeof(timeStr), "%s", Time.format(Time.now(), "%Y-%m-%d %H:%M:%S").c_str());

// Now use the timeStr in Particle.publish
Particle.publish("TimeEvent", timeStr, PRIVATE);

Feel free to ask more questions here and not drop us all in favor of ChatGPT :slight_smile:

2 Likes

I don't have a clue how to use ChatGPD, but that's cool! About to give it a try.I'll report back.

2 Likes

In short, you sign up for an account in chatGPT, then enter this at the prompt:

how do I convert the line "Log.info("time: %s", Time.format(Time.now()).c_str());" to a variable that I can insert into a Particle.publish statement?

particle embedded c++
use c strings

I'm hoping it gives you a decent response.

2 Likes

Working perfectly Gustavo!! I didn't need the year, so I removed that from the format. Many thanks again!

1 Like

Follow-up Gustavo:

I placed the following code immediately after 'myHandler' so every incoming event gets tagged with the time of arrival:

Time.zone(-4);
snprintf(timeStr, sizeof(timeStr), "%s", Time.format(Time.now() , "%m/%d %H:%M:%S").c_str());
TimeNow = String(timeStr);
TimeNow = TimeNow.substring(6);
DateNow = String(timeStr);
DateNow = Date.substring(0,5);

I created separate variables for the date and the time so that I could get creative with my Particle.publish commands, e.g. "Particle.publish("Well", "Timer on for " + String (tempRuntime/60000) + " min. by " + myID + " @ " + TimeNow + " on " + Date);". See attached output sample. Overall, working great.

But now I have discovered an unexpected problem: I have several functions that have Particle.publish commands that include the TimeNow and the DateNow variables. But the dates and times that show up in the resulting output are, of course, those associated with the last incoming event processed by myHandler.

So I've repeated the above 6 lines of code before every Particle.publish command in the entire firmware file. It works, but overly cumbersome to manage.

I'd like to create a subroutine/function with those 6 lines in it. Maybe call it 'CurrentTime'. I'd then replace the 6 lines that precede every Particle.publish with a call to CurrentTime. That routine would then return the current value for TimeNow and DateNow.

Conceptually, that should work. Except I don't know how to create such a routine and how to call it.

Can you offer some advice in that regard?

Screenshot 2024-10-19 at 07.02.51

You could try updating these local time variables every second, or so, in loop(). Then, when you are ready to publish, these local time variables are already set and ready to be used. Here is some example code:

uint32_t timeNowUtcInSeconds;

updateLocalTimeStampInfo(uint32_t theTimeUTC) {
  //... your code
}

void loop() {
  //... your code
  if (Time.isValid()) {
    static uint32_t previous_timeNow = 0;
    timeNowUtcInSeconds = Time.now();
    if (timeNowUtcInSeconds - previous_timeNow >= 1) {//  clock moved ahead 1 second or more
      previous_timeNow = timeNowUtcInSeconds;                

      updateLocalTimeStampInfo(timeNowUtcInSeconds);

      //checkAndRunLocalTimeRK_Schedules();//LocalTimeRK schedules             
    }
  }
  //... your code
}

Hope this helps.

Yup, that appears cumbersome :wink:

Once you study how printf() (and with it snprintf()) works you may find it much simpler to stick with it instead of mix and matching snprintf(), Time.format() and String concatenation and "substringing".

Particle.publish("Well", "Timer on for " + String (tempRuntime/60000) + " min. by " + myID + " @ " + TimeNow + " on " + Date);"

could be written as

  char eventText[256];
  snprintf(eventText, sizeof(eventText)
          , "Timer on for %d min. by %s @ %s"
          , tempRuntime/60000
          , myID
          , (const char*)Time.format("%H:%M:%S on %m/%d") 
          );

  Particle.publish("Well", eventText);
  // since the text is now stored in a variable, it can also be reused
  // e.g. for local logging
  // Log.info(eventText); 

(Time.format() can be used in a similar way as printf() - see strftime())

BTW, when you only provide the format parameter (=omit the time parameter) in the Time.format() call the result will always give you the string for the current time.

Time.zone() also only needs setting once in setup() - not over and over in loop().


Another hint about (const char*) in above sample:
In this case it does the same thing as Time.format(...).c_str() with the advantage that it even works with functions that do not return a String object but rather a vanilla C string - while the .c_str() way would throw a compile time error.
So if you want to save yourself the headache to find out what kind of string a function returns, just use the (const char*) cast.

1 Like

Helps a LOT robc! I thought I had the code in the loop but it turns out I only had the "snprintf(timeStr, sizeof(timeStr), "%s", Time.format(Time.now() , "%m/%d %H:%M:%S").c_str());" in there; not the variables. DUH!!

So I just added the variables with no additional code whatsoever:

void loop(){
   Time.zone(-4);
   snprintf(timeStr, sizeof(timeStr), "%s", Time.format(Time.now() , "%m/%d                %H:%M:%S").c_str());
   TimeNow = String(timeStr);
   TimeNow = TimeNow.substring(6);
   Date = String(timeStr);
   Date = Date.substring  (0,5);
-
-
-

So far it works perfectly. My only possible concern is that I have a couple of routines in the loop that may have a built-in delay before encountering a Particle.publish. If so, then the time stamp might be off by a few seconds. Even if that's the case, I doubt if a few seconds one way or the other would be an issue. But I will test extensively.

Many thanks!!

Sure!

Hey, if you are so inclined, I highly recommend implementing the changes/methods @ScruffR discussed. Down the road, I think you will be glad you did. Good stuff!

1 Like

Good stuff! Writing code for Particle devices is the only programming I've done for many years. I wrote a Pysician's answering service app almost entirely in Unix shell for the AT&T 3B2 back in the 70s. It had only one routine written in C, so to say that I am new to C++ is an understatement. I am learning here as I go due to the courtesy of others who are experts.

So mega thanks for this insight; I will pursue it!

2 Likes

Hey, looks like you got a lot of help.
Now, if we continue the line of thought and practice from before, you can again use chatGPC and tell it this:

c++ embedded, create a function of this code and show Brian how to call it, using c strings

the response:

To refactor the code into a C++ function and demonstrate how to use C-style strings (char arrays) rather than String objects, here's how you can create a function and call it.

1. Function Creation

We'll create a function formatDateTime that takes two output parameters for the time and date strings. We'll use C-style strings for both inputs and outputs.

#include <Particle.h>

void formatDateTime(char* timeNow, size_t timeSize, char* dateNow, size_t dateSize) {
    char timeStr[64];  // Buffer to hold the formatted time

    // Format the current time into the buffer using Time.format()
    snprintf(timeStr, sizeof(timeStr), "%s", Time.format(Time.now(), "%m/%d %H:%M:%S").c_str());

    // Extract the time (substring from index 6 onward)
    snprintf(timeNow, timeSize, "%s", timeStr + 6); // Extract the time (HH:MM:SS)

    // Extract the date (first 5 characters for MM/DD)
    snprintf(dateNow, dateSize, "%.*s", 5, timeStr); // Extract the date (MM/DD)
}

2. How to Call the Function

Here’s how Brian can call this function using C strings:

cpp

Copy code

void setup() {
    Serial.begin(9600);
    
    char timeNow[16];  // Buffer to hold the extracted time
    char dateNow[16];  // Buffer to hold the extracted date
    
    // Call the function
    formatDateTime(timeNow, sizeof(timeNow), dateNow, sizeof(dateNow));
    
    // Display the results
    Serial.println("Time: ");
    Serial.println(timeNow);  // Outputs the time part (HH:MM:SS)
    
    Serial.println("Date: ");
    Serial.println(dateNow);  // Outputs the date part (MM/DD)
}

void loop() {
    // Code to run in loop (if any)
}

Explanation

  • Function Parameters: char* timeNow and char* dateNow are the output buffers where the formatted time and date will be stored.
  • snprintf: This function is used to format strings into C-style string buffers. We extract the time portion by slicing the string from the 6th character onward and the date portion by selecting the first 5 characters.
  • Calling the Function: You create buffers (timeNow and dateNow), pass them to the function, and print the results.

This approach keeps memory usage more efficient for embedded systems by using C strings instead of String objects.

---- end of response.

So, you can even copy-paste your whole firmware and tell it what you need with it, or simply explain parts of it (explain this code is will be what you would write).

Anyhow, feel free to experiment with it, do not be shy, I believe it's gotten very good and can help you understand the little details you might be shy to ask here. I'm telling you all this not because I do it - of course :laughing:
Otherwise, keep asking here! we all learn and have fun in the process.
Best,

2 Likes

Nice tip and a useful tool, Gus! :+1:

Just some food for thought and words of caution:
Currently these LLMs will only give you solutions you specifically ask for but unlike humans will usually not question whether this really is what you need or offer advice that goes beyond the specific problem at hand.

Especially for beginners the reliance on these tools may reduce the need/desire for human tutoring and hence reinforce sub-optimal design decisions rather than point them out and see a task from a completely different viewpoint.

i.e. the given example above, while a perfectly valid and even elegant solution to the question, does not address the question whether the user may ever decide to alter the date/time format.
But without that forethought the provided solution clamps the format down or will require considerably more effort to change it.

e.g. with @blshaw45's "change of mind" in this post

With the simple change of the primary format he'd now also need to adapt another line of code, which is not only extra work but may get forgotten resulting in bad data.

As a quirky human programmer (who always keeps chainging his own mind on text composition and data formats) I'd rather suggest a rather different approach.

e.g. keep anything that is likely to be up for discussion (down the line of initial conception of an idea) flexible.
In this case, I'd lift the text patterns out into centrally defined constants and only use these in the active code - in other words: "keep static text separate from your functional code"

Like this

// possibly prone to future change
const char *evtName[]       = "Well";
const char *dtPattern[]     = "%H:%M:%S on %m/%d"
const char *evtOnPattern[]  = "Timer on for %d min. by %s @ %s";
const char *evtOffPattern[] = "Timer off by %s @ %s"; 
// if you insist in having the off time without date ;-)
// const char *evtOffPattern[] = "Timer off by %s @ %.8s"; 
// this only takes the first 8 characters from "hh:mm:ss on MM/DD"
// (a comment an AI will probably not produce ;-) 
...
char *evtText[256];
...
// functional code portion
  snprintf(evtText, sizeof(evtText)
          , evtOnPattern
          , tempRuntime/60000
          , myID
          , (const char*)Time.format(dtPattern) 
          );

  Particle.publish(evtName, evtText);

This way you will not need to care about substring positions and lengths and cannot forget to change them in case you change your mind on minor details like whether the year or second information is needed, whether you have leading zeros in the days and months or not, or even want days and months names included, whether you want a 12 or 24 hour time, ...
You can even make your code easily translatable to other languages and cultures :wink:

I think AI helpers are currently nowhere near "thinking off the beaten path" like humans.
And if we are not careful, we humans may lose that skill too, when we are not actively exposing ourselves to it and fostering and encouriging it in everybody we may.

5 Likes