bug in the timealarm library

re debugging, I like to use printf. This consumes just under 5% of the RAM on a 328 and I find it very convenient.

If you think it useful, save the following to a file named printf.h in a directory named printf beneath the libraries directory.

 // printf

extern "C" {
#include <inttypes.h>
#include <stdio.h>	
}
static char _buf[100];
#define printf(...) 						\
	do {							\
		sprintf(_buf, __VA_ARGS__); Serial.write(_buf);	\
	} while (0))

You can then do this:

 #include <Time.h>
#include <TimeAlarms.h>
#include <printf.h>

void setup()
{
  Serial.begin(57600);
  printf("Initializing...\n");
  delay(2000);
  setTime(0,0,0,1,1,1971);  
  printf("Begin test, count= %d\n", Alarm.count());
  // digital clock display of the time
  printf("%d:%d:%d %d/%d/%d", hour(),minute(),second(),month(),day(),year() );

 rest of code goes here ...

The tests on the new version are going well.
The random alarm creation was very handy although I changed the method for incrementing the clock so it did not skip over an alarm trigger.
Here is the code I used in loop:

  Alarm.delay(0);
  time_t nextTrigger = Alarm.getNextTrigger();
  if(nextTrigger > now() + 3)  
  {
     setTime(nextTrigger - 2);
     printf(" Set Time to %s %d:%d:%d %d/%d/%d\n",dayStr(weekday(now())), hour(),minute(),second(),day(),month(),year());  
  }

nextTrigger is a new method that can be exposed for this kind of testing:

// returns the absolute time of the next scheduled alarm, or 0 if none
 time_t TimeAlarmsClass::getNextTrigger()
 {
 time_t nextTrigger = 0xffffffff;  // the max time value
 
    for(uint8_t id = 0; id < dtNBR_ALARMS; id++)
    {
      if(Alarm[id].Mode.isAllocated )
      {
          if(Alarm[id].nextTrigger <  nextTrigger)
             nextTrigger = Alarm[id].nextTrigger;	
      }      
    }
    return nextTrigger == 0xffffffff ? 0 : nextTrigger;  	
 }

If the tests continue to go well I will post the revised library source later today.

My tests of the new code are looking good.
One arduino is running weekly alarms on Saturday and a random day and time – it gone a simulated 10 years without reporting an error
The other test is running the TimeAlarmExample sketch that uses timers set for every 30 minutes and time of day alarms for 8:30 and 17:45. This test has triggered 50 alarms a day for a simulated month.

Here is the Time.h file

/*
  time.h - low level time and date functions
*/

#ifndef _Time_h
#define _Time_h

#include <inttypes.h>

typedef unsigned long time_t;

typedef enum {timeNotSet, timeNeedsSync, timeSet
}  timeStatus_t ;

typedef enum {
    dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday
} timeDayOfWeek_t;

typedef enum {
    tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields
} tmByteFields;	   

typedef struct  { 
  uint8_t Second; 
  uint8_t Minute; 
  uint8_t Hour; 
  uint8_t Wday;   // day of week, sunday is day 1
  uint8_t Day;
  uint8_t Month; 
  uint8_t Year;   // offset from 1970; 
} 	tmElements_t, TimeElements, *tmElementsPtr_t;

//convenience macros to convert to and from tm years 
#define  tmYearToCalendar(Y) ((Y) + 1970)  // full four digit year 
#define  CalendarYrToTm(Y)   ((Y) - 1970)
#define  tmYearToY2k(Y)      ((Y) - 30)    // offset is from 2000
#define  y2kYearToTm(Y)      ((Y) + 30)   

typedef time_t(*getExternalTime)();
//typedef void  (*setExternalTime)(const time_t); // not used in this version


/*==============================================================================*/
/* Useful Constants */
#define SECS_PER_MIN  (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY  (SECS_PER_HOUR * 24UL)
#define DAYS_PER_WEEK (7UL)
#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK)
#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL)
#define SECS_YR_2000  (946684800UL) // the time at the start of y2k
 
/* Useful Macros for getting elapsed time */
#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)  
#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) 
#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR)
#define dayOfWeek(_time_)  ((( _time_ / SECS_PER_DAY + 4)  % DAYS_PER_WEEK)+1) // 1 = Sunday
#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY)  // this is number of days since Jan 1 1970
#define elapsedSecsToday(_time_)  (_time_ % SECS_PER_DAY)   // the number of seconds since last midnight 
// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971
// Always set the correct time before settting alarms
#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY)  // time at the start of the given day
#define nextMidnight(_time_) ( previousMidnight(_time_)  + SECS_PER_DAY )   // time at the end of the given day 
#define elapsedSecsThisWeek(_time_)  (elapsedSecsToday(_time_) +  ((dayOfWeek(_time_)-1) * SECS_PER_DAY) )   // note that week starts on day 1
#define previousSunday(_time_)  (_time_ - elapsedSecsThisWeek(_time_))      // time at the start of the week for the given time
#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK)          // time at the end of the week for the given time


/* Useful Macros for converting elapsed time to a time_t */
#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN)  
#define hoursToTime_t   ((H)) ( (H) * SECS_PER_HOUR)  
#define daysToTime_t    ((H)) ( (H) * SECS_PER_DAY) // fixed on Jul 3 2011
#define weeksToTime_t   ((W)) ( (W) * SECS_PER_WEEK)   

/*============================================================================*/
/*  time and date functions   */
int     hour();            // the hour now 
int     hour(time_t t);    // the hour for the given time
int     hourFormat12();    // the hour now in 12 hour format
int     hourFormat12(time_t t); // the hour for the given time in 12 hour format
uint8_t isAM();            // returns true if time now is AM
uint8_t isAM(time_t t);    // returns true the given time is AM
uint8_t isPM();            // returns true if time now is PM
uint8_t isPM(time_t t);    // returns true the given time is PM
int     minute();          // the minute now 
int     minute(time_t t);  // the minute for the given time
int     second();          // the second now 
int     second(time_t t);  // the second for the given time
int     day();             // the day now 
int     day(time_t t);     // the day for the given time
int     weekday();         // the weekday now (Sunday is day 1) 
int     weekday(time_t t); // the weekday for the given time 
int     month();           // the month now  (Jan is month 1)
int     month(time_t t);   // the month for the given time
int     year();            // the full four digit year: (2009, 2010 etc) 
int     year(time_t t);    // the year for the given time

time_t now();              // return the current time as seconds since Jan 1 1970 
void    setTime(time_t t);
void    setTime(int hr,int min,int sec,int day, int month, int yr);
void    adjustTime(long adjustment);

/* date strings */ 
#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null)
char* monthStr(uint8_t month);
char* dayStr(uint8_t day);
char* monthShortStr(uint8_t month);
char* dayShortStr(uint8_t day);
	
/* time sync functions	*/
timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized
void    setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider
void    setSyncInterval(time_t interval); // set the number of seconds between re-sync

/* low level functions to convert to and from system time                     */
void breakTime(time_t time, tmElements_t &tm);  // break time_t into elements
time_t makeTime(tmElements_t &tm);  // convert time elements into time_t


#endif /* _Time_h */

the TimeAlarms.h file:

//  TimeAlarms.h - Arduino Time alarms header for use with Time library

#ifndef TimeAlarms_h
#define TimeAlarms_h

#include <inttypes.h>

#include "Time.h"

#define dtNBR_ALARMS 6   // max is 255
#define USE_SPECIALIST_METHODS  // define this for testing

typedef enum { dtMillisecond, dtSecond, dtMinute, dtHour, dtDay } dtUnits_t;

typedef struct  {
    uint8_t isAllocated            :1 ;  // the alarm is avialable for allocation if false
    uint8_t isEnabled              :1 ;  // the timer is only actioned if isEnabled is true 
    uint8_t isOneShot              :1 ;  // the timer will be de-allocated after trigger is processed 
    uint8_t isAlarm                :1 ;  // time of day alarm if true, period timer if false  
	uint8_t alarmType              :3 ;  // enumeration of daily/weekly (in future: biweekly/semimonthly/monthly/annual)
	                                     // note that the current API only supports daily or weekly alarm periods
 }
    AlarmMode_t   ;

typedef enum  {dtNotAlarm, dtTimer, dtExplicitAlarm, dtDailyAlarm, dtWeeklyAlarm } dtAlarmPeriod_t ; // in future: dtBiweekly, dtMonthly, dtAnnual

typedef uint8_t AlarmID_t;
typedef AlarmID_t AlarmId;  // Arduino friendly name
#define dtINVALID_ALARM_ID 255


class AlarmClass;  // forward reference
typedef void (*OnTick_t)();  // alarm callback function typedef 

// class defining an alarm instance, only used by dtAlarmsClass
class AlarmClass
{  
private:
public:
  AlarmClass();
  OnTick_t onTickHandler;  
  void updateNextTrigger();
  time_t value;
  time_t nextTrigger;
  AlarmMode_t Mode;
};

// class containing the collection of alarms
class TimeAlarmsClass
{
private:
   AlarmClass Alarm[dtNBR_ALARMS];
   void serviceAlarms();
   uint8_t isServicing;
   uint8_t servicedAlarmId; // the alarm currently being serviced
   AlarmID_t create( time_t value, OnTick_t onTickHandler, uint8_t isAlarm, uint8_t isOneShot, dtAlarmPeriod_t alarmType, uint8_t isEnabled=true);
   
public:
  TimeAlarmsClass();
  // functions to create alarms and timers
  AlarmID_t alarmRepeat(time_t value, OnTick_t onTickHandler);                    // trigger daily at given time of day
  AlarmID_t alarmRepeat(const int H,  const int M,  const int S, OnTick_t onTickHandler); // as above, with hms arguments
  AlarmID_t alarmRepeat(const timeDayOfWeek_t DOW, const int H,  const int M,  const int S, OnTick_t onTickHandler); // as above, with day of week 
 
  AlarmID_t alarmOnce(time_t value, OnTick_t onTickHandler);                     // trigger once at given time of day
  AlarmID_t alarmOnce( const int H,  const int M,  const int S, OnTick_t onTickHandler);  // as above, with hms arguments
  AlarmID_t alarmOnce(const timeDayOfWeek_t DOW, const int H,  const int M,  const int S, OnTick_t onTickHandler); // as above, with day of week 
  
  AlarmID_t timerOnce(time_t value, OnTick_t onTickHandler);   // trigger once after the given number of seconds 
  AlarmID_t timerOnce(const int H,  const int M,  const int S, OnTick_t onTickHandler);   // As above with HMS arguments
  
  AlarmID_t timerRepeat(time_t value, OnTick_t onTickHandler); // trigger after the given number of seconds continuously
  AlarmID_t timerRepeat(const int H,  const int M,  const int S, OnTick_t onTickHandler);   // As above with HMS arguments
  
  void delay(unsigned long ms);
   
  // utility methods
  uint8_t getDigitsNow( dtUnits_t Units);         // returns the current digit value for the given time unit
  void waitForDigits( uint8_t Digits, dtUnits_t Units);  
  void waitForRollover(dtUnits_t Units);
 
  // low level methods
  void enable(AlarmID_t ID);
  void disable(AlarmID_t ID);
  AlarmID_t getTriggeredAlarmId();          //returns the currently triggered  alarm id

#ifndef USE_SPECIALIST_METHODS  
private:  // the following methods are for testing and are not documented as part of the standard library
#endif
  void free(AlarmID_t ID);                  // free the id to allow is reause 
  void write(AlarmID_t ID, time_t value);   // write the value (and enable) the alarm with the given ID  
  time_t read(AlarmID_t ID);                // return the value for the given timer  
  dtAlarmPeriod_t readType(AlarmID_t ID);   // return the alarm type for the given alarm ID 
  uint8_t count();                          // returns the number of allocated timers
  time_t getNextTrigger();                  // returns the time of the next scheduled alarm

};

extern TimeAlarmsClass Alarm;  // make an instance for the user

/*==============================================================================
 * MACROS
 *============================================================================*/

/* public */
#define waitUntilThisSecond(_val_) waitForDigits( _val_, dtSecond)
#define waitUntilThisMinute(_val_) waitForDigits( _val_, dtMinute)
#define waitUntilThisHour(_val_)   waitForDigits( _val_, dtHour)
#define waitUntilThisDay(_val_)    waitForDigits( _val_, dtDay)
#define waitMinuteRollover() waitForRollover(dtSecond)
#define waitHourRollover()   waitForRollover(dtMinute)
#define waitDayRollover()    waitForRollover(dtHour)

#define AlarmHMS(_hr_, _min_, _sec_) (_hr_ * SECS_PER_HOUR + _min_ * SECS_PER_MIN + _sec_)

#endif /* TimeAlarms_h */

TimeAlarms.cpp file (no comments)

/*
TimeAlarms.cpp 

 testing version
*/
 
extern "C" {
#include <string.h> // for memset
}

#include <WProgram.h>
#include "TimeAlarms.h"
#include "Time.h"

#define IS_ONESHOT  true
#define IS_REPEAT   false 
#define IS_ALARM    true
#define IS_TIMER    false 


AlarmClass::AlarmClass()
{
  Mode.isAlarm =  Mode.isEnabled = Mode.isOneShot = 0;
  value = nextTrigger = 0;
  onTickHandler = NULL;  // prevent a callback until this pointer is explicitly set 
}

void AlarmClass::updateNextTrigger()
{  
  if( (value != 0) && Mode.isEnabled )
  {
    time_t time = now();
    if(Mode.isAlarm && nextTrigger <= time )  
    {      
      if(Mode.alarmType == dtExplicitAlarm )
      {
        nextTrigger = value;  // yes, trigger on this value   
      }
      else if(Mode.alarmType == dtDailyAlarm) 
      {
        if( value + previousMidnight(now()) <= time)
        {
          nextTrigger = value + nextMidnight(time); 
        }
        else
        {
          nextTrigger = value + previousMidnight(time); 
        }
      }
      else if(Mode.alarmType == dtWeeklyAlarm) 
      {
        if( (value + previousSunday(now())) <= time)
        {
          nextTrigger = value + nextSunday(time);
        }
        else
        {
          nextTrigger = value + previousSunday(time); 
        } 
      }
      else  // its not a recognized alarm type - this should not happen 
      {
        Mode.isEnabled = 0;  // Disable the alarm
      }	  
    }
    if(Mode.isAlarm == false)
    {
      // its a timer
      nextTrigger = time + value; 
    }
  }
  else
  {
    Mode.isEnabled = 0; 
  }
}

TimeAlarmsClass::TimeAlarmsClass()
{
  isServicing = false;
  for(uint8_t id = 0; id < dtNBR_ALARMS; id++)
     free(id);
}

// this method will now return an error if the value is greater than one day - use DOW methods for weekly alarms
AlarmID_t TimeAlarmsClass::alarmOnce(time_t value, OnTick_t onTickHandler){
  if( value <= SECS_PER_DAY)
    return create( value, onTickHandler, IS_ALARM, IS_ONESHOT, dtDailyAlarm );
  else
    return dtINVALID_ALARM_ID; // dont't allocate if the time is greater than one day 	
}

AlarmID_t TimeAlarmsClass::alarmOnce(const int H,  const int M,  const int S,OnTick_t onTickHandler){
   return create( AlarmHMS(H,M,S), onTickHandler, IS_ALARM, IS_ONESHOT, dtDailyAlarm );
}

AlarmID_t TimeAlarmsClass::alarmOnce(const timeDayOfWeek_t DOW, const int H,  const int M,  const int S, OnTick_t onTickHandler){
   unsigned long secondsToGivenDay = (DOW-1) * SECS_PER_DAY;
   return create( secondsToGivenDay + AlarmHMS(H,M,S), onTickHandler, IS_ALARM, IS_ONESHOT, dtWeeklyAlarm );
}

// this method will now return an error if the value is greater than one day - use DOW methods for weekly alarms
AlarmID_t TimeAlarmsClass::alarmRepeat(time_t value, OnTick_t onTickHandler){ // trigger daily at the given time
  if( value <= SECS_PER_DAY)
     return create( value, onTickHandler, IS_ALARM, IS_REPEAT, dtDailyAlarm );
  else
     return dtINVALID_ALARM_ID; // dont't allocate if the time is greater than one day 	
}

AlarmID_t TimeAlarmsClass::alarmRepeat(const int H,  const int M,  const int S, OnTick_t onTickHandler){ // as above with HMS arguments
     return create( AlarmHMS(H,M,S), onTickHandler, IS_ALARM, IS_REPEAT, dtDailyAlarm );
}

AlarmID_t TimeAlarmsClass::alarmRepeat(const timeDayOfWeek_t DOW, const int H,  const int M,  const int S, OnTick_t onTickHandler){
   unsigned long secondsToGivenDay = (DOW-1) * SECS_PER_DAY;
   return create( secondsToGivenDay + AlarmHMS(H,M,S), onTickHandler, IS_ALARM, IS_REPEAT, dtWeeklyAlarm );
}

AlarmID_t TimeAlarmsClass::timerOnce(time_t value, OnTick_t onTickHandler){
  return create( value, onTickHandler, IS_TIMER, IS_ONESHOT, dtTimer );
}

AlarmID_t TimeAlarmsClass::timerOnce(const int H,  const int M,  const int S, OnTick_t onTickHandler){
  return create( AlarmHMS(H,M,S), onTickHandler, IS_TIMER, IS_ONESHOT, dtTimer );
}

AlarmID_t TimeAlarmsClass::timerRepeat(time_t value, OnTick_t onTickHandler){
  return create( value, onTickHandler, IS_TIMER, IS_REPEAT, dtTimer);
}

AlarmID_t TimeAlarmsClass::timerRepeat(const int H,  const int M,  const int S, OnTick_t onTickHandler){
  return create( AlarmHMS(H,M,S), onTickHandler, IS_TIMER, IS_REPEAT, dtTimer);
}

void TimeAlarmsClass::enable(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated){
    Alarm[ID].Mode.isEnabled = (Alarm[ID].value != 0) && (Alarm[ID].onTickHandler != 0) ;
    Alarm[ID].updateNextTrigger();
  }
}

void TimeAlarmsClass::disable(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated)
    Alarm[ID].Mode.isEnabled = false;
}

void TimeAlarmsClass::free(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated)
  {
    Alarm[ID].Mode.isEnabled = Alarm[ID].Mode.isAllocated = false;
	Alarm[ID].Mode.alarmType = dtNotAlarm;
    Alarm[ID].onTickHandler = 0;
	Alarm[ID].value = 0;
	Alarm[ID].nextTrigger = 0;   	
  }
}

// write the given value to the given alarm
void TimeAlarmsClass::write(AlarmID_t ID, time_t value)
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated){
    Alarm[ID].value = value;
    enable(ID);
  }
}

// return the value for the given alarm ID
time_t TimeAlarmsClass::read(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated)
    return Alarm[ID].value ;
  else 	
    return 0l;
}

// return the alarm type for the given alarm ID
dtAlarmPeriod_t TimeAlarmsClass::readType(AlarmID_t ID)
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated)
    return (dtAlarmPeriod_t)Alarm[ID].Mode.alarmType ;
  else 	
    return dtNotAlarm;
}

// returns the number of allocated timers
uint8_t TimeAlarmsClass::count()
{
   uint8_t c = 0;
   for(uint8_t id = 0; id < dtNBR_ALARMS; id++)
   {
     if(Alarm[id].Mode.isAllocated)
      c++;
   }
   return c;
}

AlarmID_t TimeAlarmsClass::getTriggeredAlarmId()  //returns the currently triggered  alarm id
// returns  dtINVALID_ALARM_ID if not invoked from within an alarm handler
{
  if(isServicing)
    return  servicedAlarmId;  // new private data member used instead of local loop variable i in serviceAlarms();
  else
    return dtINVALID_ALARM_ID; // valid ids only available when servicing a callback
}

void TimeAlarmsClass::delay(unsigned long ms)
{
  unsigned long start = millis();
  while( millis() - start  <= ms)
    serviceAlarms();
}

void TimeAlarmsClass::waitForDigits( uint8_t Digits, dtUnits_t Units)
{
  while(Digits != getDigitsNow(Units) )
  {
    serviceAlarms();
  }
}

void TimeAlarmsClass::waitForRollover( dtUnits_t Units)
{
  while(getDigitsNow(Units) == 0  )
    serviceAlarms();
  waitForDigits(0, Units);
}

uint8_t TimeAlarmsClass::getDigitsNow( dtUnits_t Units)
{
  time_t time = now();
  if(Units == dtSecond) return numberOfSeconds(time);
  if(Units == dtMinute) return numberOfMinutes(time);
  if(Units == dtHour) return numberOfHours(time);
  if(Units == dtDay) return dayOfWeek(time);
  return 255;  // This should never happen
}

void TimeAlarmsClass::serviceAlarms()
{
  if(! isServicing)
  {
    isServicing = true;
    for( servicedAlarmId = 0; servicedAlarmId < dtNBR_ALARMS; servicedAlarmId++)
    {
      if( Alarm[servicedAlarmId].Mode.isEnabled && (now() >= Alarm[servicedAlarmId].nextTrigger)  )
      {
        OnTick_t TickHandler = Alarm[servicedAlarmId].onTickHandler;
        if(Alarm[servicedAlarmId].Mode.isOneShot)
          Alarm[servicedAlarmId].Mode.isEnabled = Alarm[servicedAlarmId].Mode.isAllocated = false;
        else
           Alarm[servicedAlarmId].updateNextTrigger();
        if( TickHandler != NULL) {
          (*TickHandler)();     // call the handler
        }
      }
    }
    isServicing = false;
  }
}

// returns the absolute time of the next scheduled alarm, or 0 if none
 time_t TimeAlarmsClass::getNextTrigger()
 {
 time_t nextTrigger = 0xffffffff;  // the max time value

    for(uint8_t id = 0; id < dtNBR_ALARMS; id++)
    {
	  if(Alarm[id].Mode.isAllocated )
	  {
		if(Alarm[id].nextTrigger <  nextTrigger)
		   nextTrigger = Alarm[id].nextTrigger;	
	  }	
	}
	return nextTrigger == 0xffffffff ? 0 : nextTrigger;  	
 }

// attempt to create an alarm and return true if successful
AlarmID_t TimeAlarmsClass::create( time_t value, OnTick_t onTickHandler, uint8_t isAlarm, uint8_t isOneShot, dtAlarmPeriod_t alarmType, uint8_t isEnabled)
{
  if( ! (isAlarm && now() < SECS_PER_YEAR)) // only create alarm ids if the time is at least Jan 1 1971
  {
	for(uint8_t id = 0; id < dtNBR_ALARMS; id++)
	{
		if(Alarm[id].Mode.isAllocated == false)
		{
		// here if there is an Alarm id is available
		  Alarm[id].Mode.isAllocated = true;
		  Alarm[id].onTickHandler = onTickHandler;
		  Alarm[id].Mode.isAlarm = isAlarm;
		  Alarm[id].Mode.isOneShot = isOneShot;
		  Alarm[id].Mode.alarmType = alarmType;
		  Alarm[id].value = value;
		  isEnabled ?  enable(id) : disable(id);
		  return id;  // alarm created ok
		}
	}
  }
  return dtINVALID_ALARM_ID;
}

TimeAlarmsClass Alarm = TimeAlarmsClass() ;

hello mem,
I read your code and you made a huge work on it, you did include everything I need for my sketch, you even made it easier to later extend it with other type of alarm (monthly even/odd each 15th of each month...) and I think this is a must for some people (although I don't need them personaly ).

So a BIG THANKS for your work.

I will include this in my sketch (although it won't be straightforward) and will tell you if everything is working (just give me a few days).

Best regards

Viknet

I think we need an extended Alarm.write function:

void TimeAlarmsClass::write(AlarmID_t ID, time_t value,dtAlarmPeriod_t period) 
{
  if(ID < dtNBR_ALARMS && Alarm[ID].Mode.isAllocated){
    Alarm[ID].value = value;
    enable(ID);
    Alarm[ID].Mode.alarmType=period;
  }
}

Otherwise it is not possible to set the period of an alarm (I need it in my sketch)
we could also add

TimeAlarmsClass::write(AlarmID_t ID, time_t value,dtAlarmPeriod_t period,OnTick_t onTickHandler)

I will use the last one (also not mandatory) on the contrary to the previous on which I definitly need.

Best regards

Yes, I can see that the new structure could need an enhanced write that takes the dtAlarmPeriod_t but I am not sure how it would actually be used.

Can you explain the use case for changing the tick handler callback?

And also can you explain the use case where the period type would be changed?

Well, I can't break it. I'm still running tests, but nothing I've tried is failing. All the capabilities I use work fine. Right now I have about 20 total timers and alarms running from as short as a second to a second less than a day and it just keeps on working. I intend to run this some more and try some random timers to see there may be something hiding, but looks good.

Yes, I can see that the new structure could need an enhanced write that takes the dtAlarmPeriod_t but I am not sure how it would actually be used.

Can you explain the use case for changing the tick handler callback?

And also can you explain the use case where the period type would be changed?

Well I am building a weekly programmer like this one: except having many output and instead of having display and button it will have a serial connection (connected to a wifi computer handling php pages)

Using serial I need to set and reset alarm to the user desire :=) all handler are identical and I have a structure which give me information for each alarmid (which input to turn on or off) which sprinkler to enable for how long......

beeing able to get/set the periodicity enable me to change on the fly alarm, at this moment I only can free an alarm and create a new one.

In this case I can only determine the id after the creation so if a user want to modify the periodicity of alarmid=3,
I have to free alarmid=3 then create anotherone having all parameter (zones/durations) identical except periodicity and give the new id to the user.

Concerning the handler, beeing able to get/write would enable me to have two different tye of program with different logic behind, some program would be simpleon/off the other sprinkler program, If I need to display the program to the user I need to have access to the handler information. at this moment as said before, all program do the same and the program has a type with a big if at the beginning.

on a different subject, the fact that now weelky and daily cannot be set the same way I had to put something like this:

if (day==0)
  Alarm.alarmRepeat(H,M,S,handler);
else 
  Alarm.alarmRepeat(day,H,M,S,handler);

when before I was only doing Alarm.alarmRepeat(day,H,M,S,handler);

I made the "if" to test your lib but I am not that happy with it, it leads to unnecessarly cumbersome code. I don't know what would be the best, expose the create function to the outside or check if the day is 0 and then create a daily alarm.

best regards

viknet

Viknet, thanks for the information.

In your application where the user can create or change any alarm type and period on any alarm, I prefer an approach that rather than allowing any value to be written to any timer, restricting the input to the current alarm type. If the user needs to change the alarm type then he would need to delete the old alarm and create a new one of the required type. This allows some detection if invalid commands are received.

The code is open source so there is nothing stopping you changing your copy of the library to suit your application, but I prefer that the public version provides more error checking capability on changes applied to a timer than you want for your app.

As I mentioned in post #47, the new code differs from the playground version in that it returns an error if time values greater a day are given to the method to create daily alarms.
I think having separate methods for daily and weekly alarms with error checking if the arguments are out of range is worth the extra if statement that may be needed in a sketch.
I would be interested in hearing views on this point from anyone using the current Timealarm library. I suspect that viknet and draythomp are not typical users and they would have no problem adjusting their code to suit, but I wonder if this would be a problem for others?

Michael

michael,

I understand your point of view concerning alarms, but if the write(AlarmID_t ID, time_t value) exist then the write(AlarmID_t ID, time_t value,dtAlarmPeriod_t period) needd to exist, I can understand both way of thinking (also I prefer the last),

But beeing able to read/write the periodicity is a must if you can read/write the value, that was my major fear when you proposed the migration.

Of course I understand the fact that this is opensource and I fully understand that what I need is not necessarly what most programmers need (and don't worry If I need to change the lib I will do it),
your call for comment is the good way of doing it, but seeing that that I was the fisrt to discover the bug concerning Dow, I think that there is not that much people using this feature.
maybe your request for comment, need to be open in another to gain visibility.

regards

Viknet

best regards

Viknet, rest assured that you will be able to have the functionality you need even if its not in the playground version.

I am keen to keep the playground version simple and safe. But I recognise that there are useful capabilities for testing or specialist applications that go beyond this scope and you may have noticed that the alpha code I posted here yesterday had the write, read, readType, free, count and getNextTrigger as private methods unless a #define was used to expose them.
My intention is to hide all of those functions in the standard distribution and discourage their use with sketches that are indented for public distribution. I doubt any of these functions are needed for the kinds of applications that the typical arduino user is making. Experienced users such as yourself can find the #define to turn these on but I would discourage the publishing of sketches that require these methods as I think its very easy for inexperienced users to get into trouble if they play around with this low level alarm logic.
I feel strongly that in the Arduino world its better to provide robust but simple capabilities that address 90% of the likely applications rather than something highly flexible but easy to abuse.
Perhaps there is room for two libraries, one limited to the public methods I have proposed and another with a different name that includes low level functions.
I would be interested to hear comments on this.

Michael

Back when I was looking at how to control things around the house I got most of my ideas from devices available at the store. The more expensive drip water controllers had days of the week and a couple of alarms allotted to each day. So, a total of 14 alarms that I could program for any start time and duration. Others had more alarms per day and multiple outputs. They were all the same type and I couldn't change them to be something else. The price on these things went from around U$20 for one alarm a day up to well over a hundred for more control. None of them was remotely programmable nor reported what they were doing. That's one reason why I started looking for something I could program to do exactly what I want.

I like the simple interface of create it, let it expire and then create another one. But, that makes it hard to actually use because there is no central place to change it, you have to change the code tor create another one as well. So, a repeat alarm is good. Now some folks need to be able to cancel a current one (maybe it rained), but allow it to repeat again day after tomorrow. Other folks want to do other things that I haven't even suspected were possible. So, there is probably no end to the possible uses of alarms. Timers are different in that you set it and it expires and does something. The idea of a repeat is a good one so the programmer doesn't have to keep resetting it. The ability to cancel it is good also so failsafe alarms can be created.

And vicnet has a really good point that he was the one that found the initial problem with the daily alarm. I would have run into it during a future phase of my work, but I was using a daily alarmOnce and recreating it in the callback because I might not have wanted it to go off next time exactly the same way. Alarms and timers can be used as program flow control tools as well as turning on relays. However, this means that few people used AlarmRepeat in a daily fashion and actually tested the code, including me.

This was partly because, at the time I started working on that project, there was practically no mention of TimeAlarm in the playground. This really surprised me because things like sprinkler timers and Christmas lights would have this as a central component. Much of the stuff we do around the house for real applications is alarm based. And, most notably, almost everyone makes an arduino clock as one of their first projects. I was concerned that the library was out of date and even posted a thread that only got two responses since I could have easily googled for it here. The responders didn't realize I was trying to make sure that the library was valid and that I had read almost everything on the darn internet about it already.

Net, the ability to create, modify and delete a daily alarm is necessary. Additionally, being able to count and list them would make it versatile for people that want to create a sophisticated control system. For example, if a user wants a menu interface to the alarms they create for something like a sprinkler controller, they will need to know how many there are, when they expire and what kind to put up a nice display. The guy that did his controller for a dish washer could have easily used this kind of capability.

Please don't misunderstand, I realize that a line has to be drawn somewhere, features can't just be added forever. But a recurring daily alarm is so darn nice to build into real-life control systems that some complexity is not only warranted, it's downright wonderful. And, some of us (well, maybe only me) are using these little devices to do real life stuff. Heck, I'm working on building one into my refrigerator to control it to my tastes instead of what the manufacturer thinks I need.

So, put the primitive features at the top of the list of capabilities and lower down, the more sophisticated ones. The people using it will read it and figure out what they need as they build the devices. Keeping it simple will help the beginner, but people don't stay beginners for very long. Simple is nice, but we out grow it pretty quickly.

Honestly, I've been keeping myself under control. Automatic setback thermostats have a weekday vs weekend alarm. Real expensive ones have holidays built in and are programmable (my power company has these available for a price). Birthday alarms are really fun. Monthly alarms are great for paying the power bill. Odd vs even days make for a nice cycle in some projects. Alarms that calculate sunrise and sunset are great for outdoor lights. Should I go on.

This library has been implemented to suit the typical Arduino user, not an expert programmer.
Here are the basic capabilities supported in the current version for building typical applications:

  • Create alarms for a particular time of day that repeat daily or on a particular day of week.
  • Create a one time only alarms for particular time of day or dates
  • Create timers that trigger once only or repeat after an interval that can range from one second to one week
  • Disable and enable any of the created alarms or timers

Here are capabilities, some supported in the old version that could continue to be supported in the standard version or moved into the advanced category (they will probably be included in the standard version unless there is a good argument otherwise)

  • Determine if a given id is an alarm (time of day based) or a timer (duration based)
  • Read the current duration of a timer or trigger time of an alarm
  • Change the value (duration/trigger time) of an alarm/timer
  • Free up an alarm or timer to allow its resources to be used in the creation of another alarm or timer

And these are the advanced capabilities that are being considered that can be enabled through a hash define:

  • Get the trigger time of the next alarm/timer
  • Get the number of allocated timers
  • Determine if a given alarm/timer id is allocated

My current thinking is that the typical Arduino user of this library will not need the advanced functionality listed above. If a feature is only useful to programmers interested in making sophisticated control systems then IMO it does not belong in a library targeted for Arduino. If you disagree with this point of view then perhaps we could discuss this as a matter of philosophy in a new thread in the general discussion section. But although you have both made the case why you would like these features, I wonder if either of you would say that your needs are representative of typical Arduino users of this library.

I would prefer not to have two libraries, one that is Arduino friendly, another for sophisticated users, but I would rather do that then add complex capabilities into the arduino library that are not relevant to the majority of arduino users.

As said in my earlier post, you are not going to lose the more advanced capabilities, they are available if you enable them through a hash define. But I do want to avoid the advanced features becoming part of the standard API for this library unless there is a strong use case relevant to more than a handful of users. I believe that almost all of the Arduino applications using this library can be built without the need for any of the advanced capabilities.

The first version of this library was posted here almost three years ago and has had 15768 reads.
The updated posting was here with 12947 reads.
I don't think you will a strong case for the features you are looking for in those threads. If there are more recent threads requesting the more advanced functionality then please post the links. If functionality is useful for a lot of arduino users then I will not hesitate to support it.

If it were not for the specialist requirement to test the library and to support you two that have contributed your time to this, I would not be inclined to include those more advanced capabilities at all. I do appreciate the time and effort you have devoted to this topic so please don’t take this post as criticism. Its just that having watched and participated in the Arduino community since 2007 I think simplicity is more important than rich functionality. Although I realize that each person can have a different interpretation of this, I do believe that in a good Arduino API, less is more.

That’s my view, lets see if we get wider feedback.

Michael

A define to turn them on is a reasonable idea IMHO. Especially since anyone using very many alarms will be editing the max number anyway. And, a small number of 'default' capabilities that are available without doing anything is reasonable also. What I guess I'm asking is not to lose any of the features that have come about because someone needed them. Sheltering them behind a define is a pretty good idea to keep complications down and not lose any inventiveness that has gone before. That makes it especially easy for someone, once they get past a certain point in the learning curve, to go augment away.

By the way, I still haven't been able to break it. The only failure so far was when it rolled over 2106 a couple of times. I may be putting this latest code in something that is running for real just to push the envelope.

I have run from 1971 to 2021 without any errors. Are you running the version that increments the time to just below the next trigger or the one that increments by 55 seconds?

Anyway, running in one of your real apps would be a good test.

What I guess I'm asking is not to lose any of the features that have come about because someone needed them

What I was trying to say in my previous post is that a few people needing a feature is not it itself a justification for adding complexity to the API.

I think simplicity is more important than rich functionality

This is the part I prefer I fully agree with you on this and having a two stage complexity that your propose is a very good idea.

I am already using the readType and free method extensively, and will continue to propose/implement myself other feature If I think I need them.

The new version is perfectly woking on my side without issue.

Going a bit further I have an issue with the time_t TimeAlarmsClass::read(AlarmID_t ID) the fact that is returns 01 when failing is a bit strange because if someone set and alarm to 1 seconde after mignight it wont be able to distinguish an error from a real alarm.

unfortunately, I have no answer other than some hackery (for exemple using 111111111 ie 07 / 09 / 73 @ 7:11:51pm EST) which is probably a lot less used than 1 seconde after midnight (but still not the right way of doing it)

This issue is not a big deal for me and I personaly don't care (because user can only set minute precise alarm) but I wanted to share this with you in case you have a better Idea.

Best regards

Vincent

Actually, I've run both. The tests that exceeded the upper limits were the just before trigger time ones. I haven't gotten past 2014 on the 55 second tests. I haven't done any real time testing since it takes so darn long. I have it running right now in a real device, but the first alarm is not for several hours. The timers seem to work fine in it though, I have a lot of them running in it.

Regarding complexity, I can't disagree, but it would be sad to lose working changes that other's might need and subsequently have to reinvent.

Y'know, if you were to post a note in the forum under 'project guidance' I bet a bunch of people would chime in for opinions. Those folks don't seem reluctant to sound off. However, they can be unkind as well at times.

viknet:
Going a bit further I have an issue with the time_t TimeAlarmsClass::read(AlarmID_t ID) the fact that is returns 01 when failing is a bit strange because if someone set and alarm to 1 seconde after mignight it wont be able to distinguish an error from a real alarm.

it returns 0l (thats 0 as a long) not 01

In fact, I have already made a change to define the following in TimeAlarms.h
#define dtINVALID_TIME 0L
my latest version of read returns this constant. Because 0l (0) is not an a valid alarm value it can be distinguished from a real alarm