Go Down

Topic: bug in the timealarm library (Read 12033 times) previous topic - next topic

mem

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


Michael

draythomp

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

Code: [Select]
#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);
 
}
Trying to keep my house under control http://www.desert-home.com/

draythomp

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

Code: [Select]
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);
  }
Trying to keep my house under control http://www.desert-home.com/

viknet

Do we need to feed your test program with banana ?  :smiley-mr-green:

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 :-)

regards

Viknet

mem

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 :
Code: [Select]

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.

viknet

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

Code: [Select]

#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

mem

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.
Code: [Select]
// 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:

Code: [Select]
#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 ...


mem

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:

Code: [Select]
  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:

Code: [Select]

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

mem

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

Code: [Select]
/*
  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 */


mem

the TimeAlarms.h file:

Code: [Select]
//  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 */


mem

TimeAlarms.cpp file (no comments)

Code: [Select]
/*
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() ;


viknet

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

viknet

I think we need an extended Alarm.write function:

Code: [Select]
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

mem

#73
Jul 03, 2011, 11:42 pm Last Edit: Jul 03, 2011, 11:48 pm by mem Reason: 1
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?




draythomp

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.
Trying to keep my house under control http://www.desert-home.com/

Go Up