Non blocking delay $timeout style: need opinion!

Hi.

I just wrote a library about a non blocking delay a little like java script $timeout.

It does run into void loop and when adding them, class them into a container in order of the first to append so the monitoring of the handler is the faster as possible.

Added also a couple of feature:

-Macro of seconds, minutes and hours.
-Option of non-overwrite and non-deletable delay

Wish to have so opinions, cue or tips.

Thank alls !!

Nitrof

Example:

#include <TimeOut.h>



TimeOut timeout0;
TimeOut timeout1;
TimeOut timeout2;
TimeOut timeout3;
TimeOut timeout4;
TimeOut timeout5;
TimeOut timeout6;

/*
    timeOut_Lock            timer can not be overwriten
    timeOut_Undeleable      timer can not be cleared
    timeOut_Lock_Undelable  timer can not be overwriten or cleared

    time macro : sc(number_of_secondes) , mn(number_of_secondes) , hr(number_of_secondes)
*/

void setup() {
  Serial.begin(9600);
  timeout0.timeOut(10000, callback0);
  timeout1.timeOut(20000, callback1);
  timeout4.timeOut(70000, callback4);

  timeout2.timeOut(sc(45), callback2, timeOut_Lock);
  timeout2.timeOut(sc(150), callback2); //will not change timeout2 because it was lock.

  timeout3.timeOut(60000, callback3);
  timeout5.timeOut(mn(1) + sc(24), callback5, timeOut_Undeleable);
  timeout4.clear(); //delete timer
  timeout5.clear(); //timer will not be delete because it have been set to Undelable

  timeout6.timeOut(90000, callback6, timeOut_Lock_Undelable);
  timeout0.printContainer();
}

void loop() {
  timeout0.handler();
}

void callback0() {
  Serial.println("Timer0 have been triggedafter 10 seconde.");
  Serial.println("");
}

void callback1() {
  Serial.println("Timer1 have been trigged after 20 sec.");
  Serial.println("");
}

void callback2() {
  Serial.println("Timer2 have been trigged after 45 sec.");
  Serial.println("");
}

void callback3() {
  Serial.println("Timer3 have been trigged after 1 minute.");
  Serial.println("");
}

void callback4() {
  Serial.println("Timer4 should not have been trigger by cancel it.");
  Serial.println("");
}

void callback5() {
  Serial.println("Timer5 have been trigger after 1 minute and 25 sec, but timer 4 was not trigged because it was cancelled.");
  Serial.println("");
}

void callback6() {
  Serial.println("Timer6 will execute after 90 sec.");
  Serial.println("");
}

TimeOut..cpp (2.99 KB)

TimeOut.h (1.01 KB)

You can do something like this:

function something()
{
    static uint32_t nLastMills = 0;
    const uint32_t nTimeoutSeconds = 10;
    uint32_t nTrigger = millis() + (nTimeoutSeconds * 1000);

    if (millis() >= nTrigger)
    {
         // Do something every 10 seconds.
         nLastMillis = millis();
    }
}

That, in fact, do similar into background. But if you have many timer to run, that make much code. And to check if delay is over pass, you have to check all of them.

My lib class them in order of trigger so it have only one condition to check at each pass.

I have no idea whether the OP's code works (let's assume it does).

What bothers me about this sort of "helper" is that if a newbie user finds a situation where it does not work for him (or if he is misusing it through ignorance) he (or she) will be completely out of his/her depth trying to figure out how the library works.

Whereas if he uses millis() directly in his program all the workings are there to be seen and easily debugged.

...R

Hi Robin2.

Understand your point. Like every program, apply a concept and realize the real project is two different thing.

Perhaps for the help of user, I should put a test function into the example to demonstrate the use and application. Than, user can test there sketch before to check there part of the code and better understand what could goes into the background.

Also I will clean the example. It was that mess to test the code(and yes, until now, it work).

Regards

Nitrof

nitrof:
Perhaps for the help of user, I should put a test function into the example to demonstrate the use and application.

Also write full documentation that explains what the library is for and how to use it using language that will make sense to a Newbie. IMHO while an example is useful it is not sufficient as it can only illustrate one particular situation.

It is also very helpful (but rarely available) to have details of what the library is NOT suitable for.

...R

Robin2:
What bothers me about this sort of "helper" is that if a newbie user finds a situation where it does not work for him (or if he is misusing it through ignorance) he (or she) will be completely out of his/her depth trying to figure out how the library works.

Does that not apply to every 3rd party library that one uses?

sterretje:
Does that not apply to every 3rd party library that one uses?

exactly :slight_smile:

I must admit my approach to delays is pretty basic,

I have a delay checking routine (which i shamelessly lifted from someone far smarter);

boolean delay_check(unsigned long & since, unsigned long delay) { // re-usable routine to check if time has passed on a delayed item
  // return false if we're still "delaying", true if time ms has passed.
  unsigned long currentmillis = millis();
  if (currentmillis - since >= delay) {
    return true;
  }
  return false;
} // delay_check

Then when I want to define objects that i will check for delays, i have a structure to hold them (declared before setup);

// structure to hold timers
struct Timer { // variables related to Timed events
  long refresh; // the interval on which an update can happen
  unsigned long last; // updates to show when last change was made.
} ;

// individual timer declarations
Timer Battery = {10000, 0}; // battery voltage check interval timer
Timer Banner = {5000, 0}; // splash screen duration

Each timer can then be simply checked, eg;

  if (delay_check(Battery.last, Battery.refresh)) { // update battery voltage if timer has elapsed
    BatCheck(); // this is the 'do something' bit, if the timer has elapsed
    Battery.last = millis(); // update structure variable to current time
  }

Im sure there are better ways of doing it, but this is easy for me to understand and expand, without needing to include another library :slight_smile:

sterretje:
Does that not apply to every 3rd party library that one uses?

Yes.

Some of them do things that are not so easy to do without them.

However in many cases if the author had used the time it took him to create the library to write an explanation about how to do the job without the library the end-user would be better off. The difference between teaching a man to fish and giving him a fish.

...R

Will try to do my best Robin2.

I will do my git hub shortly(I hope), after adding interval obj as well.

With a little more complex code, with many timing event, I found hard ether to follow or write the code.
I think it could be cool inside a toolkit.

Regards.

Nitrof

Hi allz.

I just post a git :NitrofMtl/TimeOut

Example and docs are there. @Robin2, Is Readme file complete as it should ?

Most likely that there is some typos... My English still poor...

Let me know if I should made some change.

Regards

Nitrof

boylesg:
You can do something like this:

uint32_t nTrigger = millis() + (nTimeoutSeconds * 1000);
if (millis() >= nTrigger)

There's a big ole bug hiding in these lines. You'll occasionally have a 49 day delay.

You should always handle time with subtraction to account for rollover. Don't try to figure out what the next time point is. Subtract the start time from the time now to see how long it has been and compare to an interval.

the library does this.

nitrof:
the library does this.

Does what?

You should always handle time with subtraction to account for rollover. Don't try to figure out what the next time point is. Subtract the start time from the time now to see how long it has been and compare to an interval.

nitrof:

@nitrof,

Great effort!!! Don't listen to the people puking on your work here. It is a fine start to something. Posting it to Git helps too, keeping it open source could add to some improvement from the community, rather than just sharp criticism!

A forum should provoke constructive contribution not just nit-picking and nay-saying your efforts. It seems that some people here seem to revel in the hardships of other's learning.

Also, in my opinion, all libraries don't need to be made for beginners, so I would forget all that nonsense.

A question and a comment...

Q: (Why) Does handler() have to be attached to a class member instance? What happens if I haven't created a TimeOut object yet?

C: Your examples should/could include a dynamically created timer/callback (I'm thinking Javascript because you put that idea into my head). That would be the real benefit of such a library. In my humble opinion, starting a timer in setup that executes 10seconds later is trivial. You should show off what you are doing and pop in a really special treat.

@BulldogLowell Thank! I don't take thing too personal here. Learning to code, that's what I'm here about.

For your question:

1-

(Why) Does handler() have to be attached to a class member instance? What happens if I haven't created a TimeOut object yet?

It is to keep it into namespace of the timeout. If all pointer are NULL into the container, it just return false.

EDIT[HO]Just realize another point about calling handler. TimeOut object HAVE to be into global scope to work anyway. So they will always be before the call of handler.

2-

hould/could include a dynamically created timer/callback

Yes, it is design to work that way. I didn't do it to keep the example as simple as possible... But you make a point, It doesn't demonstrate the use. At least, I will add comment into readme and examples that set timeout function could be on any function.

@BulldogLowell

I gave a second thought to your comments: yes could be great to have dynamically created timer, I done a new constructor that doing it. Note that it is the first time I play with the heap.. :stuck_out_tongue:

TimeOut::TimeOut(unsigned long _delay, void (*_callback)()){
 TimeOut *onHeap = new TimeOut();
 for ( int i = 0; i < sizeOfTimeOut; i++){
 if (!timerList[i]){ //check for first emty spot 
 onHeap->timeStamp = millis();
 onHeap->callback = _callback;
 onHeap->delay = _delay;
 triage(onHeap); //place the instance into container
 return;
 }
 if(sizeOfTimeOut-1 == i) {
 Serial.println("TimeOut container is full !!! Can't add a new timer.");
 
 }
 }
}

here an example using it timeOut_dynamic_alloc.ino

a little question about objet on the heap to be certain that it will be correctly deleted... when the timeOut is triggered, I just call after it:

delete timerList[0]; //delete timer on heap

It will free up all the memory of the object right ??

Another thought... dynamically created timer cannot be overwritten or cancelled because its pointer is deleted after leaving the function calling it... Still work but have to keep in mind. And I Have some concern about if user call it to often, it create a new instance each time it is called...

Thanks

Nitrof

nitrof:
...And I Have some concern about if user call it to often, it create a new instance each time it is called...

Well, you are likely creating and deleting instances of all kinds of objects, don't let a little class object put you off. Just create and destroy the objects properly. Since you are in MCU-ville, you'll ned a cap on the number of class instances anyways (as you have already done).

If you are eager to continue your learning, you could try to create a static class member array that contains pointers to each class object. You can then call a (i.e. handler()) static member function that iterates over the array of timer objects. You may also need a 2nd static variable to contain the total number of class instances. This would also solve the problem of your wonky handler() function that currently needs a class instance.

just for the fun of it!

ho forgot to mention... already on static handler and container...
you can call TimeOut::handler();

But you cant still link a dynamic timer after leaving the function. I put into the example those limitations in comments.
If you need more flexibility, use global instance instead.Both can work together in the same sketch.

Regards.

Nitrof