bug in the timealarm library

draythomp and viknet, do you have test sketches that you can post here that exercise the alarms, particularly the daily and weekly alarms?

Michael

I threw this one together and modify it as necessary to try various items.

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

AlarmID_t morningAlarmID=99, afternoonAlarmID=99, eveningAlarmID=99;
AlarmID_t tuesdayAlarmID=99, wednesdayAlarmID=99, thursdayAlarmID=99, 
          fridayAlarmID=99, saturdayAlarmID=99, sundayAlarmID=99;
char buf[100];

void setup()
{
  Serial.begin(57600);
  Serial.println("Initializing...");
  delay(2000);
  setTime(0,0,0,1,1,1971);
//  morningAlarmID   = Alarm.alarmRepeat(8,30,0, onAlarm);
//  afternoonAlarmID = Alarm.alarmRepeat(13,25,0,onAlarm);
//  eveningAlarmID   = Alarm.alarmRepeat(17,45,0,onAlarm);
//  tuesdayAlarmID   = Alarm.alarmRepeat(dowTuesday,17,45,0,onAlarm);
//  wednesdayAlarmID = Alarm.alarmRepeat(dowWednesday,17,45,0,onAlarm);
//  thursdayAlarmID = Alarm.alarmRepeat(dowThursday,17,45,0,onAlarm);
//  fridayAlarmID    = Alarm.alarmRepeat(dowFriday,17,45,0,onAlarm);
//  sundayAlarmID    = Alarm.alarmRepeat(dowSunday,17,45,0,onAlarm);
  saturdayAlarmID  = Alarm.alarmRepeat(dowSaturday,17,45,0,onAlarm);
  pinMode(13, OUTPUT);     
  Serial.println("Begin test...");
}

void onAlarm()
{
  // alarm callback function
  AlarmId id = Alarm.getTriggeredAlarmId();
  time_t tnow = now();

  if (id == tuesdayAlarmID){
    if(weekday(tnow) != dowTuesday || hour(tnow) != 17 || minute(tnow) != 45){
      showMe("Tuesday error", tnow);
    }
    else{
 //     showMe("Tuesday OK", tnow);
    }
  }
  if (id == wednesdayAlarmID){
    if(weekday(tnow) != dowWednesday || hour(tnow) != 17 || minute(tnow) != 45){ 
 //     showMe("Wednesday error", tnow);
    }
     else{
//      showMe("Wednesday OK", tnow);
    }
 }
  if (id == thursdayAlarmID){
    if(weekday(tnow) != dowThursday || hour(tnow) != 17 || minute(tnow) != 45){ 
      showMe("Thursday error", tnow);
    }
    else{
//      showMe("Thursday OK", tnow);
    }
  }
  if (id == fridayAlarmID){
     if(weekday(tnow) != dowFriday || hour(tnow) != 17 || minute(tnow) != 45){
      showMe("Friday error", tnow);
    }
    else{
 //     showMe("Friday OK", tnow);
    }
  }
  if (id == saturdayAlarmID){
    if(weekday(tnow) != dowSaturday || hour(tnow) != 17 || minute(tnow) != 45){
      showMe("Saturday error", tnow);
    }
    else{
      showMe("Saturday OK", tnow);
    }
  }
  if (id == sundayAlarmID){
    if(weekday(tnow) != dowSunday || hour(tnow) != 17 || minute(tnow) != 45){
      showMe("Sunday error", tnow);
    }
    else{
//      showMe("Sunday OK", tnow);
    }
  }
  if (id >= dtNBR_ALARMS){
    showMe("Creepy ID", tnow);
  }
  if (id == dtINVALID_ALARM_ID) {
    showMe("Invalid Alarm ID", tnow);
  }
}

void showMe(char* type, time_t tnow){
  sprintf(buf,"%s alarm %s, %d:%d:%d %d/%d/%d",type,dayStr(weekday(tnow)),
       hour(tnow),minute(tnow),second(tnow),month(tnow),day(tnow),year(tnow));
  Serial.println(buf);
}


int savedMonth = 0;
int savedDay = 0;

void loop()
{
  if(savedDay != day()){  //just to let me know it's alive
    savedDay = day();
    digitalWrite(13, digitalRead(13)==HIGH?LOW:HIGH);   // flip the led
  }
  if(savedMonth != month()){  // how far it's gotten
    savedMonth = month();
    sprintf(buf,"Time %d:%d:%d %d/%d/%d", hour(),minute(),second(),day(),month(),year());
    Serial.println(buf);
  }

  Alarm.delay(0);
  adjustTime(55);
//  sprintf(buf,"Time %d:%d:%d %d/%d/%d", hour(),minute(),second(),day(),month(),year());
//  Serial.println(buf);
  
}

OK, I have no life. I admit it. However, I put this little bit of code in the test sketch and am running it now. I'll let this go on and off until something else is ready to test. I think I can modify this to work with timers too and add them in as well. An extended run of random timers and alarms with vicnet's trick to speed up time should beat the heck out of the code. Sure, it isn't perfect, but it'll test it more than a room full of monkeys and typewriters (do they still exist? typewriters that is).

 if (id == randomAlarmID){
    if(weekday(tnow) != rday || hour(tnow) != rhour || 
         (rmin-1 > minute(tnow) && minute(tnow) > rmin+1)){
      sprintf(buf,"Random Failure, parameters %d, %d, %d, %d ",
              (timeDayOfWeek_t)rday, rhour, rmin, rsec);
      Serial.println(buf);
      showMe("Failed at", tnow);
    }
    else {
     showMe("Random", tnow);
    }
    Alarm.reset(id);           //reset it so I can make it again
    rday = random(1,8);
    rhour = random(0,24);
    rmin = random(0,60);
    rsec = random(0,60);
    randomAlarmID = Alarm.alarmRepeat((timeDayOfWeek_t)rday,rhour,rmin,rsec,onAlarm);
  }

Do we need to feed your test program with banana ? :grin:

it is a very good idea to test like you did, I hope it will work and I am very happy you used the reset alarm :slight_smile:

regards

Viknet

That random test is useful.

I added a global errorCount variable that is incremented if an error is detected and modified showMe to print this :

void showMe(char* type, time_t tnow){
  sprintf(buf,"%s alarm %s, %d:%d:%d %d/%d/%d errors=%ld",type,dayStr(weekday(tnow)),
       hour(tnow),minute(tnow),second(tnow),month(tnow),day(tnow),year(tnow), errorCount);
  Serial.println(buf);
}

I am running the new library code that uses enumerations for the alarm types and it is looking good so far. I will leave it running for a while and see if any errors crop up. If all is well I should be able to post the new code here later today. I have included a method to return the number of allocated ids and a method to free a given alarm.

by the way, if some of you are using serial to debug, I have these usefull function

#define DEBUG 1

#include <avr/pgmspace.h>

void printDigits(int digits)
{
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(':');
  if(digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

void digitalClockDisplay()
{
  // digital clock display of the time
  Serial.print(' ');
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(' ');
  Serial.print(dayStr(weekday()));
  Serial.print(" the ");
  Serial.print(day());
  Serial.print(' ');
  Serial.print(month());
  Serial.print(' ');
  Serial.print(year()); 
}


void showString (PGM_P s)
{
  char c;
  while ((c = pgm_read_byte(s++)) != 0)
    Serial.print(c);
}

#ifdef DEBUG
#define DEBUG_STAT_P(x) Serial.print('['); \
digitalClockDisplay(); \
Serial.print(':'); \
Serial.print(__FUNCTION__); \
Serial.print(':'); \
Serial.print(__LINE__); \
showString(PSTR("] ")); \
showString(PSTR(x)); \
Serial.println();

#define DEBUG_DYN_P(x,y) Serial.print('['); \
digitalClockDisplay(); \
Serial.print(':'); \
Serial.print(__FUNCTION__); \
Serial.print(':'); \
Serial.print(__LINE__); \
showString(PSTR("] ")); \
showString(PSTR(x)); \
Serial.println(y);

#else
#define DEBUG_STAT_P(x)
#define DEBUG_DYN_P(x,y)
#endif

you can then use
DEBUG_STAT_P("some static string");
DEBUG_DYNP_P("some static string",dynamic_variable);

the advantages is that any debug string take very little memory (SRAM) space and that you can disable all debug easily

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.