ronbentley1
Published

Arduino Programmatic Timed Reminder Alerting Framework

Framework providing multiple concurrent elapsed and real-time reminder alerting to support applications with demanding timing needs.

AdvancedProtip1,531
Arduino Programmatic Timed Reminder Alerting Framework

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
or other boards, eg Mega 2560
×1
DS1307 64 x 8, Serial, I²C Real-Time Clock
Maxim Integrated DS1307 64 x 8, Serial, I²C Real-Time Clock
or other compatible RTC
×1
Wire, Hook Up
Wire, Hook Up
standard connect wires
×1

Software apps and online services

Arduino IDE
Arduino IDE
developed using v1.8.12 on windows 10

Story

Read more

Schematics

REM_SYS Conceptual Framework Diagram

To assist in the understanding of the overall structure and concepts behind REM_SYS.

Code

Introduction - A00_Remsys_v2_00.ino

C/C++
Introduction segment
/*   version 1.00 rev 1, Ron D Bentley (Stafford UK) March 2020.
 *   version 1.00 rev 2, July 2020 - moved print strings to program storage space to reduce
 *                                   amount of dynamic storage used. Plus improved efficiency in 
 *                                   heart beat coding.
 *   version 2.0,        Aug 2020  - this version allows REM_SYS to be configured without the real-time clock                                
 *                                   module, so that ETR only applications can be designed.
This framework provides a basis for the development of applications that would benefit from timed 
reminder alerting.  Timed reminder alerts can be defined/created as elapsed time reminders (ETRs)or
as real-time reminders (RTRs) linked to a real-time clock (RTC).  Any number of reminders can be 
active at any one time, dependent on the size of the Reimder List initialised.
____________________________________________________________________________________________________
Copyright (c) Ron D Bentley (UK)
The extent this licence shall be limited to Non-profit Use.
Permission is hereby granted, free of charge, for non-profit purposes to any person obtaining
a copy of this software and associated documentation files (the "Software"), to deal in the 
Software without restriction, including without limitation the rights to use, copy, modify, merge, 
publish, distribute, sublicense copies of the Software, and to permit persons to whom the Software 
is furnished to do so, subject to the following conditions:

The above copyright notice, acknowledgements and this permission notice shall be included in all 
copies or substantial portions of the Software. 
 
THE SOFTWARE IS LIMITED TO NON-PROFIT USE AND IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR 
A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
____________________________________________________________________________________________________

See User Guide for full capabilities and instructions for implementation.

 */ 

Configuration - C00_Configurations.ino

C/C++
Configuration segment
// Copyright (c) Ron D Bentley (UK), see copyright notice
//
//  ****************************************************************************************************
//  USER CONFIGURABLE PARAMETERS ARE REFERENCED HERE (see User Guide):
//
#define diags_on                        true  // leave set to true whilst developing/testing

#define RTC_enabled                     true  // Real-time clock enabled if true, false, if not

int ETR_timer_number                   = 0;   // 0 for timer0, 2 for timer2

#define ETR_R_list_scan_freq            100   //time (msecs) between scans of the ETR list (see User Guide)
//
//  The timer_drift_adjustment variable allows the inaccuracy of timer0/2 to be
//  compensated for, but only as far as the drift per hour in seconds.
//  This is ONLY relevant for ETRs, see User Guide.
long signed int timer_drift_adjustment =  1;  // number of seconds (+/-) per hour to adjust elapsed times

#define max_RQ_free_chain_blocks          16  // size of the RQ free chain in blocks

#define max_R_list_entries                10  // number of reminder list entries
//
// END OF USER CONFIGURABLE DATA/PARAMETERS.
// *******************************************************************************************************
//
#define ETR_timer_freq         1000                            // 1 msec timer interrupt frequency
#define scans_per_sec   ETR_timer_freq / ETR_R_list_scan_freq  // number of R_list scans per second
#define scans_per_min            60 * scans_per_sec            // number of R_list scans per minute
#define scans_per_hr             60 * scans_per_min            // number of R_list scans per hour

//
//  The timer_drift_adjustment variable allows the inaccuracy of timer1 to be
//  compensated for, but only as far as the drift per hour.  See User Guide.
//

volatile long unsigned int Seconds_since_midnight;        // used by RTR scan process

//  ************************************
//  Reminder List variables/definitions:

volatile boolean reminders_suspended = true;  //  allows reminder list scans to be halted/started

#define success                    1
#define fail                      -1

#define inactive                   0          // if a reminder entry R_type is 0 it is inactive
#define final_alert                1
//Reminder types:
//   1-3 are elapsed remider types and
//   4-6 are real-time reminder times
#define ET_oneoff_type              1  // Elapsed time (ETR) R_type values
#define ET_recurring_type           2  //     ....
#define ET_repeat_duration_type     3  //     ....

#define RT_oneoff_type              4  // Real-time R_type (RTR) valiues
#define RT_recurring_type           5  //      ....
#define RT_repeat_duration_type     6  //      .... 

#define ETR                         0  // ETR = Elapsed Time Reminder
#define RTR                         1  // RTR = Real Time Reminder

// definitions used by the create reminder routines
#define invalid_R_type            -1
#define invalid_R_subtype         -2
#define invalid_R_start_in        -3
#define invalid_start_in_mins     -4
#define invalid_start_in_secs     -5
#define invalid_start_in_subsecs  -6
#define invalid_duration_mins     -7
#define invalid_duration_secs     -8
#define invalid_duration_subsecs  -9
#define invalid_freq_mins         -10
#define invalid_freq_secs         -11
#define invalid_freq_subsecs      -12
#define invalid_freq              -13
#define invalid_RT_hrs            -14  // pertinent to real-time reminders only
#define invalid_RT_mins           -15  // ...
#define invalid_RT_secs           -16  // ...
#define reminder_list_full        -99
#define RTC_not_configured        -100

volatile struct Reminder_List {
  // Reminder List variables for managing reminder entries
  int R_type;     //  R_type = 0 if entry unused,
  int R_subtype;  //  end user definable
  union {
    long unsigned int R_remind_at;  // used for RTR processing
    long unsigned int R_start_in;   // used for ETR processing
  };
  long unsigned int R_freq;
  long unsigned int R_duration;
  long unsigned int R_count_down;
  //  End user variables held in a reminder entry
  int R_user1;
  int R_user2;
  int R_user3;
  int R_user4;
} R_list[max_R_list_entries];

//  ****************************************
//  Reminder Queue variables
#define free_chain_entry_size          6
#define end_of_chain_value            -1

//  When allocated to a triggered/elapsed reminder,a block from the free chain will be allocated
//  to the active RQ and be set up as follows:
//  word [0]          - forward chain pointer, or end of chain value
//  word [1]          - a compound integer that contains:
//                          bit 15      - set to 1 if this entry represents the last reminder alert for the
//                                        triggered/elapsed reminder or,
//                                        set to 0 otherwise
//                          bits 11-14  - not used
//                          bits 8-10   - the reminder type that was triggered/elasped
//                          bits 0-7    - the reminder subtype that was triggered/elapsed
//   words [2] to [5] - the end user parameters defined at reminder creation time

volatile int RQ[max_RQ_free_chain_blocks][free_chain_entry_size];

//  Pointers to manage the free blocks in the RQ
volatile int start_of_free_RQ_chain;
volatile int num_free_RQ_blocks;

//  Pointers to manage the allocated blocks in the RQ
volatile int start_of_RQ_chain =  -1;
volatile int end_of_RQ_chain =    -1;
volatile int num_RQ_reminders =    0;

//  general values to manage RQ processes
#define no_reminder_requests  -1
#define no_entry              -1

//  The following R_xxx variables are set up by scan_RQ so that they can be referenced from the main
//  end user code following a timed reminder alert being taken off the queue.
volatile int  R_status;      // used to indicate if the reminder alert is a final one or otherwise
volatile int  R_type;        // used to indicate what the reminder type was that triggered/elapsed
volatile int  R_subtype;     // used to indicate what the reminder subtype was that triggered/elapsed
volatile int  R_user1;       // end user parameters defined at reminder creation time
volatile int  R_user2;       // ditto
volatile int  R_user3;       // ditto
volatile int  R_user4;       // ditto

Timers - D00_Timers_Segment.ino

C/C++
Timer segment
// Copyright (c) Ron D Bentley (UK), see copyright notice

// This segment comprises the timer setups and ISRs that underpin this framework.
// They are used to provide the timimg for both ETRs and RTRs.
//
// timer0 and timer2 are used as the source timer for processing ETRs, and
// timer1 is used as the source timer for processing RTRs.
//
// Note that the timer frequency for RTR processing is fixed (timer1).
// However, the frequency for processing ETRs is end user configurable (see User Guide).
// 
// **************************************************************************************
// timer initialisation routines for timer0, timer1 and timer2:
//
// ETR timer:
// Set up timer0 interrupt for processimg ETRs for 1000 cycles per second,
// 1kHz interrupts, ie for 1 millisecond interrupts
void initialise_timer0() {
  //set timer0 interrupt at 1kHz
  noInterrupts();
  TCCR0A = 0;
  TCCR0B = 0;
  TCNT0  = 0;
  // set compare match register for 1khz increments
  OCR0A = 249;            // (16*10^6) / (1000*64) - 1 (must be <256)
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS01 and CS00 bits for 64 prescaler
  TCCR0B |= (1 << CS01) | (1 << CS00);
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);
  interrupts();
}

// RTR timer:
// Set up timer1 interrupt for processing RTRs for 2 cycles per second,
// 2Hz interrupts, ie for 1/2 second interrupts
//
void initialise_timer1() {
  noInterrupts();
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  // set Compare Match Register (CMR) for 1msec (2hz) increments
  OCR1A = 31249;                          // (16,000,000)/(256*2)) - 1
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);                 // WGM12 = 3
  // Set CS11 bit for 256 prescaler
  //TCCR1B |= (1 << CS11) | (1 << CS10);  // CS11 = 1 and CS10 = 0
  TCCR1B |= (1 << CS12);                  // CS12 = 2
  // enable timer1 compare interrupt
  TIMSK1 |= (1 << OCIE1A);                // OCIE1A = 1
  interrupts();
}
//
// ETR timer:
// Alternative source timer for processimg ETRs for 1000 cycles per second,
// 1kHz interrupts, ie for 1 millisecond interrupts.
// This is provided if end user needs to use delay() function which makes
// use of timer0, hence a possible conflict.  End user encouraged not to
// use delay()!
void initialise_timer2() {
  noInterrupts();
  // Clear registers
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2 = 0;
  // 1000 Hz (16000000/((124+1)*128))
  OCR2A = 124;
  // CTC
  TCCR2A |= (1 << WGM21);
  // Prescaler 128
  TCCR2B |= (1 << CS22) | (1 << CS20);
  // Output Compare Match A Interrupt Enable
  TIMSK2 |= (1 << OCIE2A);
  interrupts();
}
//
//  ***************************************************************************
//  timer0, 1 and 2 ISRs:
//
//  timer0 is used as the timing source for ETR processing exclusively.
//  timer0 interrupt routine will be entered at ETR_timer_freq interrupt frequency
//  defined during the initialisation of timer0.
//  However, processing of the Remind List will only occur every R_List_scan_feq 'ticks'
//  of timer0.
//
ISR(TIMER0_COMPA_vect) {
  static int timer0_tick = 0;   // counts out the defined R_list scan frequency for ETRs.
  if (!reminders_suspended)
  { timer0_tick++;
    if (timer0_tick == ETR_R_list_scan_freq) {
      timer0_tick = 0;          // reset for next timer1 interrupt
      // scan ETR entries in reminder list
      scan_R_list(ETR);
    }
  }
}

//  Timer1 is used as the timing source for RTR processing exclusively.
//  Timer1 is initialised for 2 cycles per second (2hz) so is entered
//  every 1/2 second.
//  Note that this routne is only active if the RTC is enabled
//
ISR(TIMER1_COMPA_vect) {
  if (!reminders_suspended)
  {
    // scan RTR entries in reminder list
    interrupts(); // not normally the right thing, but this works and allows RTC lib to be accessed
    RTR_Processor();
  }
}

//  timer2 is an alternative timer interrupt routine to timer0.
//  It is offered to replace timer0 if end user wishes to use the delay() function!
//  It is used as the timing source for ETR processing exclusively.
//  timer2 interrupt routine will be entered at the ETR_timer_freq interrupt frequency
//  defined during the initialisation of timer2.
//  However, processing of the Remind List will only occur every R_List_scan_feq 'ticks'
//  of timer2.
//
ISR(TIMER2_COMPA_vect) {
  static int timer2_tick = 0;   // counts out the defined R_list scan frequency for ETRs.
  if (!reminders_suspended)
  { timer2_tick++;
    if (timer2_tick == ETR_R_list_scan_freq) {
      timer2_tick = 0;          // reset for next timer1 interrupt
      // scan ETR entries in reminder list
      scan_R_list(ETR);
    }
  }
}

// The framework supports one of two timers for processing ETRs, either timer0 or timer2.
// Both timers are initialised for 1khz (1000 cycles per second, or ETR_timer_freq).
// If end user wishes to use delay() function, choose to initialise timer2 NOT timer0.
//
void initialise_timers() {
  if (ETR_timer_number == 0) initialise_timer0();
  else initialise_timer2();
  if (RTC_enabled) {initialise_timer1();} // timer1 deals with the RTC
}

//
//  Routine sets the reminders_suspended flag to halt reminder list scans by ISR.
//
void suspend_reminders() {
  noInterrupts();
  reminders_suspended = true;
  interrupts();
}

//
//  Routine clears the reminders_suspended flag to resume reminder list scans by ISR.
//
void resume_reminders() {
  noInterrupts();
  reminders_suspended = false;
  interrupts();
}

Queuing - D10_Queue_Segment.ino

C/C++
Queue segment
// Copyright (c) Ron D Bentley (UK), see copyright notice
//
//  This tab contains the routines to establish and maintain reminder
//  queue management.
//
//  A free chain of blocks is set up that are then allocated to reminder alert requests
//  as they decome due.
//
//  'RQ' is used as the blocks of memory to be allocated/deallocated on request.
//  'RQ' comprises a number of free blocks each of 5 integers, the first word of each block
//  contains a forward pointer to next block or end of chain value(free/used).

//  When a free block is allocated to a reminder request, it is inserted at the beginning of the
//  reminder queue with the first word of the block being a forward pointer to next allocated
//  block and the remainder of block containing user values defined as part of the remider set up.
//
//  Note:
//      1.  The number of free blocks in the RQ free chain is defined by 'max_RQ_free_chain_blocks'.
//      2.  The implementation is as a queue, first in first out (FIFO).
//

//  Set up the free RQ chain as defined by 'max_RQ_free__chain_blocks'
//  This is called just once per start/reset from te setup() process.
//
void  create_RQ_free_chain() {
  int  ptr, last_RQ_block;
  last_RQ_block = max_RQ_free_chain_blocks - 1;
  for (ptr = 0; ptr < max_RQ_free_chain_blocks; ptr++) {
    if (ptr == last_RQ_block) {
      RQ[ptr][0] = end_of_chain_value; // set end of chain
    }
    else                     {
      RQ[ptr][0] = ptr + 1; // set forward block pointer
    }
  }
  num_free_RQ_blocks = max_RQ_free_chain_blocks;
  start_of_free_RQ_chain = 0;
}
//
//  Allocates a block from the free chain, if one exists.
//  NOTE: assumes that interrupts are disabled by calling function(s).
//
int acquire_block_from_free_RQ() {
  int  block;
  if (num_free_RQ_blocks > 0) {
    //  There is at least 1 free block left.
    //  Take the next block off the free chain.
    num_free_RQ_blocks--;  //  reduce free blocks available.
    block = start_of_free_RQ_chain;
    start_of_free_RQ_chain = RQ[block][0];  //  take the first free block off the free chain.
    RQ[block][0] = end_of_chain_value;      //  set the forward pointer in this free block to out of range.
    return block;         //  return with the 'address' of the free block provided to calling routine.
  }
  return  fail;           //  no free blocks! Return error condition.
}

//
// Returns the given block back to the free chain.
// NOTE: assumes that reminders are disabled by calling function(s).
//
int relinquish_block_to_free_RQ(int block) {
  if (num_free_RQ_blocks < max_RQ_free_chain_blocks)  {
    //  there is space to add this block back to the free chain.
    num_free_RQ_blocks++;  //  increase number of free blocks on free chain by 1
    RQ[block][0] = start_of_free_RQ_chain;
    start_of_free_RQ_chain = block;
    return  success;      //  relinquish was successful.
  }
  return  fail;           //  free chain seems to be full of free blocks! No space to add another.
}

//
//  The function creates an entry in the interrupt queue for the given interrupt.
//
int insert_into_RQ(int R_status, int R_user1, int R_user2, int R_user3, int R_user4) {
  int  block;
  block = acquire_block_from_free_RQ();
  if (block == fail) {
    //  no free block available!
    return fail;
  }
  //  we have a free block, so chain it into the reminder queue
  //  placing it at the end of the current queue.
  if (num_RQ_reminders == 0) {
    // queue is empty, so set start pointer
    start_of_RQ_chain = block;
  }  else  {
    // at least on entry in RQ queue, so modify forward pointer of last block
    RQ[end_of_RQ_chain][0] = block;
  }
  //  now deal with rest of updates
  num_RQ_reminders++;
  end_of_RQ_chain = block;
  RQ[block][0] = end_of_chain_value;  // set new end of chain
  RQ[block][1] = R_status;
  RQ[block][2] = R_user1;
  RQ[block][3] = R_user2;
  RQ[block][4] = R_user3;
  RQ[block][5] = R_user4;
  return success;
}

//
//  See if there are any outstanding (unprocessed) reminder requests in the
//  reminder queue.  If so, take the first one on the queue and return it to the
//  free chain.  The answer to the function is either reminder found
//  or that no outstanding reminder requests exists.
//
//  Note that this function is not entered via any interrupt routine, so we do need to
//
int scan_RQ() {
  int RQ_block, interrupt;
  //noInterrupts();   //  ensure exclusive access of the RQ and its pointers
  if (num_RQ_reminders == 0)  {
    //  Clear down the end user reminder parameters, if no RQ entry to be processed.
    R_status  = 0;
    R_type    = 0;
    R_subtype = 0;
    R_user1   = 0;
    R_user2   = 0;
    R_user3   = 0;
    R_user4   = 0;
    //interrupts();
    return no_reminder_requests;
  }
  // take the first entry off the RQ as this is the next interrupt to be processed.
  noInterrupts();
  num_RQ_reminders--;
  RQ_block = start_of_RQ_chain;
  start_of_RQ_chain = RQ[RQ_block][0];    //  point to next interrupt to be processed, or end of chain
  if (num_RQ_reminders == 0)  {
    // last used block to be relinquished, so adjust end of chain pointer
    end_of_RQ_chain = end_of_chain_value;
  }
  //  set up the interrupt parameters for this interrupt,
  //  solely for end user reference if required
  R_status  = RQ[RQ_block][1];          // active/inactive, R_type and R_subtype
  R_type    = (R_status & 0x7ff) >> 8;  // the type of reminder
  R_subtype = R_status & 0xff;          // the reminder subtype value
  R_status  = bitRead(R_status, 15);    // indicates if last reminder alert recieved, or not
  R_user1   = RQ[RQ_block][2];
  R_user2   = RQ[RQ_block][3];
  R_user3   = RQ[RQ_block][4];
  R_user4   = RQ[RQ_block][5];
  relinquish_block_to_free_RQ(RQ_block);  //  put this block back on the free RQ chain
  interrupts();
  return success; // return with the internal value of the interrupt handler that was assigned to this pin
}
//
// Utility to print the reminder queue (RQ)
//
void print_RQ() {
  int ptr, count, i;
  if (diags_on) {
    noInterrupts();
    count = num_RQ_reminders;
    ptr = start_of_RQ_chain;
    Serial.println(F("_______________________________"));
    Serial.println(F("       REMINDER QUEUE"));
    Serial.print(F("          Num in RQ = "));
    Serial.println(count);
    Serial.print(F(" start of RQ chain = "));
    Serial.println(ptr);
    Serial.print(F("   end of RQ chain = "));
    Serial.println(end_of_RQ_chain);
    if (count > 0) {
      do {
        for (i = 0; i < free_chain_entry_size; i++) {
          Serial.print(F("RQ["));
          Serial.print(ptr);
          Serial.print(F("]"));
          Serial.print(i);
          Serial.print(F("] = "));
          Serial.println(RQ[ptr][i]);
        }
        Serial.print(F("\n"));
        ptr = RQ[ptr][0]; //  look at next entry/block
        count --;
      }
      while ((ptr != end_of_chain_value) && (count > 0));
    }
    Serial.println(F(""));
    Serial.println(F("_______________________________\n"));
    Serial.flush();
    interrupts();
  }
}
//
// Utility to print the free chain
//
void print_free_chain() {
  int ptr, count;
  if (diags_on) {
    noInterrupts();
    count = num_free_RQ_blocks;
    ptr = start_of_free_RQ_chain;
    Serial.println(F(""));
    Serial.println(F("_______________________________"));
    Serial.println(F("         FREE CHAIN"));
    Serial.print(F("  Num in free chain = "));
    Serial.println(count);
    Serial.print(F("start of free chain = "));
    Serial.println(ptr);
    if (count > 0) {
      do {
        Serial.print(F("RQ["));
        Serial.print(ptr);
        Serial.print(F("][0] = "));
        Serial.println(RQ[ptr][0]);
        ptr = RQ[ptr][0]; //  look at next entry/block
        count --;
      }
      while ((ptr != end_of_chain_value) && (count > 0));
    }
    Serial.println(F("_______________________________\n"));
    Serial.flush();
    interrupts();
  }
}

Date/Time User Functions - D15_DateTime_Segment.ino

C/C++
Date/Time User Functions segment
/* Ron D Bentley (UK) (c) 2020
   This tab contains additional routines (and more) to support real-time reminders
   these being in addition to the base framework which privides elapsed time reminder (ETR)
   functionality.
*/
//****************************************************************************************
//                                    Date Functions
//****************************************************************************************
// Define the start date (origin) for elapsed days calculations.
// There is a constraint on the origin date:
//    1. it MUST be of the form 01/01/LEAP_YEAR(or pseudo Leap year, eg divisible by 100)
//    2. Also, if this is changed then change day_of_week_offset too.
//
int origin_day   = 1;
int origin_month = 1;
int origin_year  = 2020;

int today_day_number;   // used to keep a note of the day_number for today in midnight process
int today_day_of_week;  // used to keep a note of the day_of_week for today in midnight process

#define day_of_week_offset  2 // offset value for referencing days_of_week array as
// 1/1/2020 is a Wednesday, so offset provides
// correcting adjustment for day_of_week function,
// as day_number(1,1,2020) gives 1
#define valid_date     1
#define invalid_day   -1
#define invalid_month -2
#define invalid_year  -3

#define Jan            1
#define Feb            2
#define Mar            3
#define Apr            4
#define May            5
#define Jun            6
#define Jul            7
#define Aug            8
#define Sep            9
#define Oct           10
#define Nov           11
#define Dec           12

#define Sunday         0
#define Monday         1
#define Tuesday        2
#define Wednesday      3
#define Thursday       4
#define Friday         5
#define Saturday       6

char days_of_week[7][12] =
{
  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
};
byte days_in_month[13] =
{
  0,  // entry 0 not used
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31        //  Jan, Feb, March, etc
};
int accumulated_days_in_month[13] =
{
  0, // entry 0 not used
  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 // Jan, Feb, March, etc
};
//
// Checks the given day,month,year being a valid date
//
int check_date(int day, int month, int year) {
  int Days_in_month;
  if (year < origin_year) {
    return invalid_year;
  }
  if (month < Jan || month > Dec) {
    return invalid_month;
  }
  Days_in_month = days_in_month[month];
  if (month == Feb && leap_year(year)) {
    Days_in_month++; // adjust for leap year
  }
  if (day < 1 || day > Days_in_month) {
    return invalid_day;
  }
  return valid_date;
}
//
// leap_year will return whether the given year is a leap year
// and takes into account the leap year rule:
//    if divisible by   4 then it IS a leap year, UNLESS
//    divisible by 100 then it is NOT a leap year, UNLESS
//    divisible by 400 the it IS a leap year.
//
int leap_year(int year) {
  if (year % 400 == 0) {
    return true;
  }
  if (year % 100 == 0) {
    return false;
  }
  if (year % 4   == 0) {
    return true;
  }
  return false;
}
//
// day_number will return the number of inclusive elapsed days from the
// origin date of the given parameter date, taking ino account leap years.
//
int day_number(int day, int month, int year) {
  int num_leaps, yr;
  // start by counting the number of leap years from the origin year
  // to the given year.
  num_leaps = 0;
  for (yr = origin_year; yr <= year; yr = yr + 4) {
    if (leap_year(yr)) num_leaps++;
  }
  // final adjustment if given year being a leap
  // year and given month is Jan or Feb.
  if (month < Mar && leap_year(year)) {
    num_leaps--;
  }
  // now calculate elapsed days...
  return (year - origin_year) * 365
         + accumulated_days_in_month[month]
         + day
         + num_leaps;
}
//
// Given a day_number, function will determine the date as day, month, year.
//
void date_from_day_number(int day_number, int &day, int &month, int &year) {
  int days_in_year, Days_in_month;
  // determine what year the day_number falls in, count up from origin_year
  // in full years as days.
  year = origin_year;
  if (leap_year(year)) {
    days_in_year = 366;
  } else {
    days_in_year = 365;
  }
  do {
    if (day_number > days_in_year) {
      // move year on
      day_number = day_number - days_in_year;
      year++;
      if (leap_year(year)) {
        days_in_year = 366;
      } else {
        days_in_year = 365;
      }
    }
  }
  while (day_number > days_in_year);
  // now step through days_in_month to determine month.  What is
  // left as the remainder is the day of the month, and we are done.
  month = 1;
  Days_in_month = days_in_month[1];
  while (month <= Dec && day_number > Days_in_month) {
    if (month == Feb && leap_year(year)) {
      Days_in_month = 29;
    } else {
      Days_in_month = days_in_month[month];
    }
    day_number = day_number - Days_in_month;
    month++;
    if (month <= Dec) { // this set up for next while cycle test condition
      Days_in_month = days_in_month[month];
      if (month == Feb && leap_year(year)) {
        Days_in_month++;
      }
    }
  }
  day = day_number; // what is left in day_number is now that day of the month
}
//
// returns the index for the day of the week in the days_of_week array
//
int day_of_week(int day, int month, int year) {
  return (day_number(day, month, year)
          + day_of_week_offset) % 7; // map to 0 (Sunday), 1 (Monday), ... , 6 (Saturday)
}

//******************************************************************************************
//                  date & time funcions required for other ETR/RTR code
//                                  DO NOT REMOVE
//******************************************************************************************
//  RTC libraries and declarations
#include <RTClib.h>

RTC_DS1307 rtc;

void display_now_date_time() {
  if (RTC_enabled) {
    DateTime now = rtc.now();
    Serial.print(now.day());
    Serial.print(F("/"));
    Serial.print(now.month());
    Serial.print(F("/"));
    Serial.print(now.year());
    Serial.print(F(", "));
    Serial.print(days_of_week[now.dayOfTheWeek()]);
    Serial.print(F(", "));
    Serial.print(now.hour());
    Serial.print(F(":"));
    Serial.print(now.minute());
    Serial.print(F(":"));
    Serial.println(now.second());
    Serial.flush();
  }
}

//
// Function returns the number of seconds since midnight.
long unsigned int seconds_since_midnight() {
  long unsigned int secs, mins, hrs;
  if (RTC_enabled) {
    if (rtc.isrunning()) {
      DateTime now = rtc.now();
      hrs  = now.hour();
      mins = now.minute();
      secs = now.second();
      return hrs * 3600 + mins * 60 + secs;
    }  else  {
      Serial.println(F("!secs_since_midnight - RTC is not operating, terminating!"));
      Serial.flush();
      exit(0);
    }
  } else return 0; // RTC not configured
}

Timed Reminders - D20_TR_Segment.ino

C/C++
Timed Reminder Processing segment
// Copyright (c) Ron D Bentley (UK), see copyright notice
//
//  Scan the reminder list to process active reminder entries (ETR and RTR types).
//
void scan_R_list(int E_or_R) {
  int R_entry, R_type, R_status;
  //  scan each possible reminder list entry and process if active according to reminder type
  for (R_entry = 0; R_entry < max_R_list_entries; R_entry++) {
    if (R_list[R_entry].R_type != inactive) {
      // this reminder entry is active
      R_type = R_list[R_entry].R_type;
      R_status = 0;    //  assume the timed remider is not the final one, for now
      // process ETR or RTR entries depending on value of the parameter
      if (E_or_R == ETR && R_type >= ET_oneoff_type && R_type <= ET_repeat_duration_type) {
        // process ETR reminder entry
        if (R_list[R_entry].R_count_down > 0) {
          R_list[R_entry].R_count_down--; // decrement countdown timer
        }
        if (R_list[R_entry].R_count_down == 0) {
          //  count down time elapsed so time to raise the reminder via the
          //  RQ for end user asynchronous processing
          //R_status = 0;    //  assume the timed remider is not the final one, for now
          switch (R_type) {
            case ET_oneoff_type:
              // 'oneoff' reminder type so remove this reminder as it has elapsed
              R_list[R_entry].R_type = inactive;
              R_status = final_alert;
              break;
            case ET_recurring_type:
              //  'recurring' reminder type so reset counter for next wait cycle
              R_list[R_entry].R_count_down = R_list[R_entry].R_freq;
              break;
            case ET_repeat_duration_type:
              if (R_list[R_entry].R_duration == 0)
              { //  no further reminders are due of this reminder type
                //  ('remind_duration') time has now elapsed so remove this reminder
                R_list[R_entry].R_type = inactive;
                R_status = final_alert;
              } else {
                //  'repeat_duration' type, so reset counter for next cycle
                if (R_list[R_entry].R_duration < R_list[R_entry].R_freq) {
                  // less time left than defined by R_freq, so take lesser value
                  R_list[R_entry].R_count_down = R_list[R_entry].R_duration;
                  R_list[R_entry].R_duration = 0;
                } else {
                  //  reduce end time by R_freq
                  if (R_list[R_entry].R_freq == 0) {
                    R_list[R_entry].R_count_down = R_list[R_entry].R_duration;
                    R_list[R_entry].R_duration = 0;
                  } else {
                    R_list[R_entry].R_duration   = R_list[R_entry].R_duration - R_list[R_entry].R_freq;
                    R_list[R_entry].R_count_down = R_list[R_entry].R_freq;
                  }
                }
              }
              break;
          }
          // assemble R_status to be a compound value to store whether this is a final alert
          // for this reminder or not, together with the reminder type and subtype.
          // These calues will be unpicked at set to global variables once the reminder
          // has triggered and scan_RQ takes it off the RQ. This saves a little on space.
          R_status = (R_status << 15) + (R_type << 8) + R_list[R_entry].R_subtype;
          if (insert_into_RQ(R_status,
                             R_list[R_entry].R_user1,
                             R_list[R_entry].R_user2,
                             R_list[R_entry].R_user3,
                             R_list[R_entry].R_user4) == fail) {
            if (diags_on) {
              Serial.println(F("\n!!scan_list ETR insert failure!!"));
              Serial.flush();
            }
          }
        }
      }
      else if (E_or_R == RTR && R_type >= RT_oneoff_type && R_type <= RT_repeat_duration_type) {
        // process RTR reminder entry
        if (R_list[R_entry].R_remind_at == Seconds_since_midnight) {
          switch (R_type) {
            case RT_oneoff_type:
              R_list[R_entry].R_type = inactive; // reminder has now expired so clear it
              R_status = final_alert;
              break;
            case RT_recurring_type:
              // perform modulo 24 hours arithmetic on next reminder time (86400 = 24 hrs in seconds)
              R_list[R_entry].R_remind_at = (R_list[R_entry].R_remind_at + R_list[R_entry].R_freq) % 86400;
              break;
            case RT_repeat_duration_type:

              if (R_list[R_entry].R_duration == 0) {
                //  no further reminders are due of this reminder type
                //  ('remind_duration') time has now elapsed so remove this reminder
                R_list[R_entry].R_type = inactive;
                R_status = final_alert;
              } else {
                //  'repeat_duration' type, so reset counter for next cycle
                if (R_list[R_entry].R_duration < R_list[R_entry].R_freq) {
                  // less time left than defined by R_freq, so take lesser value
                  R_list[R_entry].R_remind_at = (R_list[R_entry].R_remind_at + R_list[R_entry].R_duration) % 86400;
                  R_list[R_entry].R_duration = 0;
                } else {
                  //  reduce end time by R_freq
                  if (R_list[R_entry].R_freq == 0) {
                    R_list[R_entry].R_remind_at = (R_list[R_entry].R_remind_at + R_list[R_entry].R_duration) % 86400;
                    R_list[R_entry].R_duration  = 0;
                  } else {
                    R_list[R_entry].R_duration  = R_list[R_entry].R_duration - R_list[R_entry].R_freq;
                    R_list[R_entry].R_remind_at = (R_list[R_entry].R_remind_at + R_list[R_entry].R_freq) % 86400;
                  }
                }
              }
              break;
          }
          R_status = (R_status << 15) + (R_type << 8) + R_list[R_entry].R_subtype;
          if (insert_into_RQ(R_status,
                             R_list[R_entry].R_user1,
                             R_list[R_entry].R_user2,
                             R_list[R_entry].R_user3,
                             R_list[R_entry].R_user4) == fail) {
            if (diags_on) {
              Serial.println(F("\n!!scan_list RTR insert failure!!"));
              Serial.flush();
            }
          }
        }
      }
    }
  }
}

//
//  This routine drives the Real-Time scan process and is initiated via timer0 interrupts
//
int RTR_Processor() {
  static  int last_seconds = 60; // a start value that now_seconds cant achieve
  static int now_second = 0;
  if (rtc.isrunning()) {
    DateTime now = rtc.now();
    now_second = now.second();   // get RTC second now
    if (now_second != last_seconds) {
      // must have moved on by at least 1 second so scan RT reminder list entries
      last_seconds = now_second; // ready for next pass
      Seconds_since_midnight = seconds_since_midnight(); // used in scan_R_list for RTR checking
      scan_R_list(RTR);
      return 1;                  // return that remind list was scanned this pass
    }
  } else  {
    Serial.println(F("!RTR_Processor - RTC not operating, terminating!"));
    Serial.flush();
    exit(0);
  }
  return 0;                      // return that remind list not scanned this pass
}

//
//  create_ET_reminder() - routine creates a new Elapsed Time (ET) reminder in R_list if
//  there is space and parameters are valid!
//
//  Paramerters in scope, by R_type:
//  one_off, R_type = 1
//  R_start_in: Yes
//  R_freq:     n/a, ignored if set
//  R_duration: n/a, ignored if set
//
//  recurring, R_type = 2
//  R_start_in: Yes
//  R_freq:     Yes
//  R_duration: n/a, ignored if set
//
//  for a duration, R_type = 3
//  R_start_in: Yes
//  R_freq:     Yes
//  R_duration: Yes
//
//  Note that:
//  1.  the parameters for start, frequency and for (duration) all follow same format:
//      hrs, mins, secs, subsecs
//  2.  the subsecs value is the number of cycles per second the remind list is scanned.
//      It is 0..'scans_per_sec'-1.  'scans_per_sec' is 'timer1_freq' / 'ETR_R_list_scan_freq'.
//
int create_ET_reminder(int R_type, int R_subtype,
                       long signed int R_start_in_hrs,   long signed int R_start_in_mins,
                       long signed int R_start_in_secs,  long signed int R_start_in_subsecs,
                       long signed int R_freq_hrs,       long signed int R_freq_mins,
                       long signed int R_freq_secs,      long signed int R_freq_subsecs,
                       long signed int R_duration_hrs,   long signed int R_duration_mins,
                       long signed int R_duration_secs,  long signed int R_duration_subsecs,
                       int R_user1, int R_user2, int R_user3, int R_user4) {
  int R_entry, R_list_status;
  long signed int R_start_in, R_freq, R_duration;
  //  start by validating the parameters
  if (R_type < ET_oneoff_type || R_type > ET_repeat_duration_type) {
    return invalid_R_type;
  }
  if (R_subtype < 0 || R_subtype > 255) {
    return invalid_R_subtype;
  }
  // R_start_in is required for all R_types, so validate
  if (R_start_in_mins < 0 || R_start_in_mins > 59) {
    return invalid_start_in_mins;
  }
  if (R_start_in_secs < 0 || R_start_in_secs > 59) {
    return invalid_start_in_secs;
  }
  if (R_start_in_subsecs < 0 || R_start_in_subsecs >= scans_per_sec) {
    return invalid_start_in_subsecs;
  }
  //  validate R_freq
  if (R_type != ET_oneoff_type) {
    //  R_frequency is required for recurring_type or repeat_duration_type, so validate
    //  and set up the interval count
    if (R_freq_mins < 0 || R_freq_mins > 59) {
      return invalid_freq_mins;
    }
    if (R_freq_secs < 0 || R_freq_secs > 59) {
      return invalid_freq_secs;
    }
    if (R_freq_subsecs < 0 || R_freq_subsecs >= scans_per_sec) {
      return invalid_freq_subsecs;
    }
    R_freq_secs = R_freq_secs + timer_drift_adjustment * R_freq_hrs; // add the number of secs per hour adjustment

    R_freq =  R_freq_hrs   * scans_per_hr  +
              R_freq_mins  * scans_per_min +
              R_freq_secs  * scans_per_sec +
              R_freq_subsecs;
    if (R_type == ET_recurring_type && R_freq == 0) {
      return invalid_freq;
    }
  } else {
    R_freq = 0;
  }
  //  validate R_duration time if R_type is repeat_duration_type
  if (R_type == ET_repeat_duration_type) {
    if (R_duration_mins < 0 || R_duration_mins > 59) {
      return invalid_duration_mins;
    }
    if (R_duration_secs < 0 || R_duration_secs > 59) {
      return invalid_duration_secs;
    }
    if ( R_duration_subsecs < 0 || R_duration_subsecs >= scans_per_sec) {
      return invalid_duration_subsecs;
    }
    R_duration_secs = R_duration_secs + timer_drift_adjustment * R_duration_hrs; // add the number of secs per hour adjustment
    R_duration =  R_duration_hrs * scans_per_hr    +
                  R_duration_mins * scans_per_min  +
                  R_duration_secs * scans_per_sec  +
                  R_duration_subsecs;
    if (R_freq > R_duration) {
      return invalid_freq;
    }
  } else {
    R_duration = 0;
  }
  R_start_in_secs = R_start_in_secs + timer_drift_adjustment * R_start_in_hrs; // add the number of secs per hour adjustment
  R_start_in = R_start_in_hrs * scans_per_hr    +
               R_start_in_mins * scans_per_min  +
               R_start_in_secs * scans_per_sec  +
               R_start_in_subsecs;
  //  now look for an empty slot...
  R_entry = 0; //  start with first reminder entry and scan until empty entry found, or not
  do {
    // R_list_status=R_list[R_entry].R_type;
    if ( R_list[R_entry].R_type == inactive) {
      //  this entry is not used, so set up it...
      R_list[R_entry].R_subtype     = R_subtype;
      R_list[R_entry].R_start_in    = R_start_in;     //  used for all R_types
      R_list[R_entry].R_freq        = R_freq;         //  only used if R_type is recurring_type or repeat_duration_type
      R_list[R_entry].R_duration    = R_duration;     //  only used if R_type is repeat_duration_type
      R_list[R_entry].R_count_down  = R_start_in;
      //  End user variables held in a reminder entry
      R_list[R_entry].R_user1 = R_user1;
      R_list[R_entry].R_user2 = R_user2;
      R_list[R_entry].R_user3 = R_user3;
      R_list[R_entry].R_user4 = R_user4;
      //  set this reminder as active
      noInterrupts();
      R_list[R_entry].R_type = R_type;  //  note, setting this last variable will trigger the timer scan
      interrupts();
      return  R_entry;  //  return with the entry number of the reminder
    }
    R_entry++;  // look at next entry
  }
  while ( R_entry < max_R_list_entries);
  return reminder_list_full;
}
//
//  create_RT_reminder() - routine creates a new Real-Time (RT)reminder in R_list if
//  there is space and parameters are valid!
//
//  Paramerters in scope, by R_type:
//  one_off, R_type = 4
//  R_remind_at:  Yes
//  R_freq:       n/a, ignored if set
//  R_duration:   n/a, ignored if set
//
//  recurring, R_type = 5
//  R_remind_at:  Yes
//  R_freq:       Yes
//  R_duration:   n/a, ignored if set
//
//  for a duration, R_type = 6
//  R_remind_at:  Yes
//  R_freq:       Yes
//  R_duration:   Yes
//
//  Note that:
//  1.  the parameters for remind_at, frequency and for (duration) all follow same format:
//      hrs, mins, secs (no subsecs)
//
int create_RT_reminder(int R_type, int R_subtype,
                       long signed int R_remind_at_hrs,  long signed int R_remind_at_mins,
                       long signed int R_remind_at_secs,
                       long signed int R_freq_hrs,       long signed int R_freq_mins,
                       long signed int R_freq_secs,
                       long signed int R_duration_hrs,   long signed int R_duration_mins,
                       long signed int R_duration_secs,
                       int R_user1, int R_user2, int R_user3, int R_user4) {
  int R_entry;
  long signed int R_remind_at, R_freq, R_duration;
  if (RTC_enabled) {
    //  start by validating the parameters
    if (R_type < RT_oneoff_type || R_type > RT_repeat_duration_type) {
      return invalid_R_type;
    }
    if (R_subtype < 0 || R_subtype > 255) {
      return invalid_R_subtype;
    }
    if (R_remind_at_hrs  < 0 || R_remind_at_hrs  > 23) {
      return invalid_RT_hrs;
    }
    if (R_remind_at_mins < 0 || R_remind_at_mins > 59) {
      return invalid_RT_mins;
    }
    if (R_remind_at_secs < 0 || R_remind_at_secs > 59) {
      return invalid_RT_secs;
    }
    //  validate R_freq
    if (R_type != RT_oneoff_type) {
      //  R_frequency is required for recurring_type or repeat_duration_type, so validate
      //  and set up the interval count
      if (R_freq_mins < 0 || R_freq_mins > 59) {
        return invalid_freq_mins;
      }
      if (R_freq_secs < 0 || R_freq_secs > 59) {
        return invalid_freq_secs;
      }
      R_freq =  R_freq_hrs * 3600 +
                R_freq_mins * 60 +
                R_freq_secs;
      if (R_type == RT_recurring_type && R_freq == 0) {
        return invalid_freq;
      }
    } else {
      R_freq = 0;
    }
    //  validate R_duration time if R_type is repeat_duration_type
    if (R_type == RT_repeat_duration_type) {
      if (R_duration_mins < 0 || R_duration_mins > 59) {
        return invalid_duration_mins;
      }
      if (R_duration_secs < 0 || R_duration_secs > 59) {
        return invalid_duration_secs;
      }
      R_duration =  R_duration_hrs * 3600 +
                    R_duration_mins * 60 +
                    R_duration_secs;
      if (R_freq > R_duration) {
        return invalid_freq;
      }
    } else {
      R_duration = 0;
    }
    // now calculate the numer of seconds since last midnight of this real-time
    R_remind_at = R_remind_at_hrs   * 3600  +
                  R_remind_at_mins  * 60    +
                  R_remind_at_secs ;
    //  now look for an empty slot...
    R_entry = 0; //  start with first reminder entry and scan until empty entry found, or not
    do {
      if ( R_list[R_entry].R_type == inactive) {
        //  this entry is not used, so set up it...
        R_list[R_entry].R_subtype     = R_subtype;
        R_list[R_entry].R_remind_at   = R_remind_at;    // real-time of reminder in secs from last midnight
        R_list[R_entry].R_freq        = R_freq;         // only used if R_type is recurring_type or repeat_duration_type
        R_list[R_entry].R_duration    = R_duration;     // only used if R_type is repeat_duration_type
        R_list[R_entry].R_count_down  = 0;              // not used for RTRs
        //  End user variables held in a reminder entry
        R_list[R_entry].R_user1 = R_user1;
        R_list[R_entry].R_user2 = R_user2;
        R_list[R_entry].R_user3 = R_user3;
        R_list[R_entry].R_user4 = R_user4;
        //  set this reminder as active
        noInterrupts();
        R_list[R_entry].R_type = R_type;  //  note, setting this last value/field will trigger the scan process
        interrupts();
        return  R_entry;  //  return with the entry number of the reminder
      }
      R_entry++;  // look at next entry
    }
    while ( R_entry < max_R_list_entries);
    return reminder_list_full;
  }
  return RTC_not_configured;
}

//
//  Routine deletes a reminder entry of the specified parameters
//  That is, for the given R_id and R_id_subtype values
//  if 'lockout' true then interrupts are first disabled and then reenabled
//  otherwise.
//  if called from within a routine where interrupts are disabled, eg and
//  interrupt routine then 'lockout' should be set to false as interrupts
//  will already be disabled.
//
int delete_reminder(int R_type, int R_subtype) {
  int R_entry;
  noInterrupts();
  for (R_entry = 0; R_entry < max_R_list_entries; R_entry++) {
    if ( R_list[R_entry].R_type != inactive) {
      if (R_type == R_list[R_entry].R_type && R_subtype == R_list[R_entry].R_subtype) {
        //  match on this entry, so remove it, ie make inactive
        R_list[R_entry].R_type = inactive;
        interrupts();
        return success;
      }
    }
  }
  interrupts();
  return fail;
}

//
//  Print given Reminder details
//
void print_reminder(int R_entry) {
  int R_type;
  long unsigned int R_remind_at, hrs, mins, secs;
  noInterrupts();
  if (R_list[R_entry].R_type != inactive) {
    Serial.println(F("============ Reminder Parameters ================"));
    Serial.print(F("Reminder entry no: ")); Serial.println(R_entry);
    R_type = R_list[R_entry].R_type;
    Serial.print(F("R_type: ")); Serial.print(R_type);
    Serial.print(F("  R_subtype:  ")); Serial.println(R_list[R_entry].R_subtype);
    if (R_type >= ET_oneoff_type && R_type <= ET_repeat_duration_type) {
      Serial.print(F("R_start_in: ")); Serial.print(R_list[R_entry].R_start_in);
      Serial.print(F(" "));
    } else {
      R_remind_at = R_list[R_entry].R_remind_at;
      hrs = R_remind_at / 3600;
      secs = R_remind_at % 3600;
      mins = secs / 60;
      secs = secs % 60;
      Serial.print(F("R_remind_at: "));
      Serial.print(hrs); Serial.print(F(":"));
      Serial.print(mins); Serial.print(F(":"));
      Serial.print(secs); Serial.print(F(" ("));
      Serial.print(R_remind_at);
      Serial.println(F(")" ));
    }
    Serial.print(F("R_freq: ")); Serial.print(R_list[R_entry].R_freq);
    Serial.print(F(" R_duration: ")); Serial.println(R_list[R_entry].R_duration);
    Serial.print(F("R_count_down: ")); Serial.println(R_list[R_entry].R_count_down);
    Serial.println(F("============== User Parameters =================="));
    Serial.print(F("R_user1: ")); Serial.println(R_list[R_entry].R_user1);
    Serial.print(F("R_user2: ")); Serial.println(R_list[R_entry].R_user2);
    Serial.print(F("R_user3: ")); Serial.println(R_list[R_entry].R_user3);
    Serial.print(F("R_user4: ")); Serial.println(R_list[R_entry].R_user4);
  } else {
    Serial.print(F("\n\n** Reminder "));
    Serial.print(R_entry);
    Serial.println(F(" is inactive **"));
  }
  Serial.flush();
  interrupts();
}

Setup() - H00_Setup.ino

C/C++
Setup() segment
// Copyright (c) Ron D Bentley (UK), see copyright notice
//
// This framework is provided with two standing reminders that provide:
//  1.  a heart beat visual monitor which shows that the code is operating.
//      This is configured for pin 13 (on board LED), but do change if necessary.
//  2.  a midnight processor to allow daily processing/housekeeping to be
//      carried out as necessary ech midnight.a
//
int hb_intensity                = 255; // start by setting to max output level
#define heart_beat_pin    LED_BUILTIN  // digital pin for visible heart beat
#define heart_beat                254  // ETR reminder sub_type for heart beat
#define midnight                  255  // RTR reminder sub_type for midnight processing

//  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//  H00_Setup [DECLARE] - insert any additional declaration requirements here.
//  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%




//
void setup()
{
  int result;
  if (diags_on) {
    Serial.begin(115200);
    if (RTC_enabled) {
      !rtc.begin();
      if (!rtc.isrunning())
      {
        Serial.println(F("!setup() - RTC is not operating, terminating!"));
        Serial.flush();
        exit(0);
      }
    }
  }
  if (RTC_enabled) {
    //  Set up 'today_day_number' to provide ready access to this value at any point
    //  in the program without the need for it to be recalculated.
    //  This will get incremented each midnight to stay in step with the current date.
    DateTime now = rtc.now();
    today_day_number = day_number(now.day(), now.month(), now.year());
    today_day_of_week = (today_day_number + day_of_week_offset) % 7; // map to 0 (Sunday), 1 (Monday),.., 6 (Saturday)
  }
  // **********************************************************************************
  // set up the two standing reminders:
  //  1.  the heart beat visual monitor as an ETR, to show processes are operating, and
  //  2.  the midnight RTR each midnight to deal with any daily processing/housekeeping
  // The alerts for each of the above remnders are dealt with in the main segment.
  // **********************************************************************************
  int freq_secs, freq_subsecs;
  pinMode(heart_beat_pin, OUTPUT);
  // set the ETR reminder for the heart beat to be 1/2 second if possible, if not,
  // set to 1 second.
  freq_subsecs = scans_per_sec / 2;                           // take as 1/2 scan rate per second, if possible
  if (freq_subsecs == 0) freq_secs = 1; else freq_secs = 0;
  result = create_ET_reminder(ET_recurring_type, heart_beat,
                              0, 0, 0, 0,                     // start immediately
                              0, 0, freq_secs, freq_subsecs,  // 1/2 or 1 second frequency
                              0, 0, 0, 0,                     // not used
                              0, 0, 0, 0);                    // not used
  if (result < 0 && diags_on) {
    Serial.print(F("setup() - error creating ETR for heart_beat, error value = "));
    Serial.println(result);
    Serial.flush();
  }
  if (RTC_enabled) {
    //  set up RTR for each midnight, so that any daily processing can be performed
    result = create_RT_reminder(RT_recurring_type, midnight,
                                0, 0, 0,      // start at midnight (00:00:00)
                                24, 0, 0,      // repeat each midnight (24 hrs)
                                0, 0, 0,      // not used
                                0, 0, 0, 0);  // not used
    if (result < 0 && diags_on) {
      Serial.print(F("setup() error creating RTR for midnight, error value = "));
      Serial.println(result);
      Serial.flush();
    }
  }
  //  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  //  H00_Setup [INITIALISE] - insert any additional initialisation
  //  requirements here, but before the timers and remind queues are initialised
  //  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%




  //  ******************************************************************
  //  create free chain and initialise timers (0 or 2 and 1)
  //  ******************************************************************
  create_RQ_free_chain();   //  create free chain of reminder queue blocks
  suspend_reminders();      //  ensure timers do not action the reminder list yet
  initialise_timers();
  resume_reminders();       //  allow timer scans of the reminder list
}

Main Loop - M00_Main_Segment.ino

C/C++
Main Loop segment
// Copyright (c) Ron D Bentley (UK), see copyright notice
//
//  main segment for end user code
//  insert end user code where indicated:
//  1.  if timed reminder alert processing is needed then within the 'do{if (scan_RQ){...}'
//      control block, otherwise.  Use swich case on R_subtype to process alerts.
//  2.  for non-reminder requirements, place code within the 'do{if (scan_RQ)..else {..}'
//      control block

void loop() {
  do {
    if (scan_RQ() != no_reminder_requests) {
      //  *******************************************************************************
      //  the following global data variables are available at this point relating to the
      //  reminder triggered/alerted:
      //  1.  R_type          - reminder type, ETRs: 1, 2, or 3, / RTRs: 4, 5, or 6
      //  2.  R_subtype       - reminder subtype (0 <= R_subtype <= 255)
      //  3.  R_status        - set to 1 if this is the FINAL reminder alert, 0 otherwise.
      //                        Only relevant for oneoff and repeat_duration reminder types.
      //  4.  R_user1-R_user4 - user data values set up when the reminder was created
      //
      //  The above variable are those defined when the timed reminder was created.
      //  These can be 'crafted' to control flow and decision processes.
      //
      //  Insert end user code here to process timed reminder alerts by R_subtype.
      //  *******************************************************************************
      switch (R_subtype) {
        case heart_beat:
          //  ******** provides a visual indication that the program is running
          analogWrite(heart_beat_pin, hb_intensity);
          // toggle heart beat output level for next pass
          hb_intensity = 255 - hb_intensity;  // produces 0 and 255 on alternate passes
          break;
        case midnight:
          // ********* midnight processing. Insert any code relevant for daily housekeeping
          //           Note, this is only entered if the RTC is configured.
          today_day_number++;                              // day_number for today, a new day
          today_day_of_week = (today_day_of_week + 1) % 7; // next day of week value
          break;
        // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        // M00_Main_Segment [ALERT PROCESSING], timer alert processing - 
        // Insert ETR/RTR alert switch case code handling here:
        // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%







        // default switch value 'catcher'
        default:
          Serial.print(F("!Spurious switch value="));
          Serial.println(R_subtype);
          Serial.flush();
          display_now_date_time();
          break;
      }
    } else {
      //  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      //  M00_Main_Segment [GENERAL PROCESSING]. 
      //  Reminder queue is currently empty, so do other things.
      //  Insert end user code here for processing non-reminder alerts
      //  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%






    }
  } while (true);
}

Arduino REM_SYS Asynchronous Timed Reminders

Credits

ronbentley1
25 projects • 13 followers
Contact

Comments

Please log in or sign up to comment.