Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
ronbentley1
Published

ESP 32 Elapsed Timer Alerting Framework

A totally flexible and general timer interrupt framework for designing solutions needing one or many individual elapsing and separate timers

IntermediateProtip933
ESP 32 Elapsed Timer Alerting Framework

Things used in this project

Hardware components

ESP32S
Espressif ESP32S
Any ESP 32 variant should be okay.
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

ESP 32 to PC

Standard connection to PC via USB cable

Code

Example 1: An ESP 32 sketch to configure an interrupt timer

C/C++
A sketch to demonstrate configuring an ESP 32 interrupt timer
//
// Basic example of ESP 32 interrupt timer sketch
//
// This example and code is in the public domain and
// may be used without restriction and without warranty.
//
volatile int interrupt_counter;
volatile int current_interrupt_counter;
int total_interrupt_counter;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  interrupt_counter++;
  portEXIT_CRITICAL_ISR(&timerMux);
}

void setup() {
  Serial.begin(115200);
  timer = timerBegin(0, 80, true);    // prescale down to microsecond timing
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 1000, true); // step down to millisecond interrupts
  timerAlarmEnable(timer);
}

void loop() {
  // Take a copy of the current interrupt_counter value
  // so we may test it in our if/then process without
  // running into a conflict between our ISR and main
  // loop processing
  portENTER_CRITICAL(&timerMux);
  current_interrupt_counter = interrupt_counter;
  portEXIT_CRITICAL(&timerMux);
  if (current_interrupt_counter > 0) {
    // A timer interrupt has occurred
    portENTER_CRITICAL(&timerMux);
    interrupt_counter--;
    portEXIT_CRITICAL(&timerMux);
    total_interrupt_counter++;
    // report every 1000 interrupts, ie every 1 second
    if (total_interrupt_counter % 1000 == 0) {
      Serial.print("A timer interrupt has occurred. Total number: ");
      Serial.println(total_interrupt_counter);
      Serial.flush();
    }
  }
}

Example 2: The ETA Framework Sketch

C/C++
The starting sketch for developing your own elapsed timer projects
//
// R D Bentley (Stafford, UK), May 2022
//
// This example and code is in the public domain and
// may be used without restriction and without warranty.
//
// ESP 32 Multiple Elapsed Timer Alerting Framework
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ESP 32 timer driven sketch to process requirements for ANY number of
// user defined elapsed timer alerts (ETAs).
//
// The sketch provides for any number of ETAs with a resolution of
// 1 millisecond or multiples thereof. The alerts are handled asynchronously.
// The ESP 32 timer can be specified (0-3).
//
// Summary of key functions:
//
// create_ETA    - has 3 parameters - ETA_type, ETA_alert_id and ETA_interval (elapse interval timein milliseconds)
//                 ETA_type     - 'one_off_ETA' or 'recurring_ETA'
//                 ETA_alert_id - user defined value
//                 ETA_interval - elapsed time of alert in milliseconds
//                 eg result = create_ETA(recurring_ETA, 25, 1000);
// delete_ETA    - has 2 parameters - ETA_type and ETA_alert_id
//                 eg result = delete_ETA(recurring_ETA, 25);
// print_ETAs    - no parameters - prints the ETA structure contents for each defined/created ETA
//                 eg print_ETAs();
// update_ETAs   - no parameters - function that determines if any of the defined/created
//                 ETAs have elapsed.  It performs a count down for each ETA every timer cycle. When
//                 an ETA has reached its count down its details are inserted into a list which
//                 will be processed by the get_ETA_alert function.  These two functions work
//                 together and provide asynchronicity of processing
//                 eg update_ETAs();
// get_ETA_alert - no parameters - retrieves elapsed ETAs from the alert list providing
//                 their ETA type and ETA alert id which can then be used for decision control
//                 and specific to ETA processing
// create_and_start_timer
//               - creates and starts the EPS 32 given timer
//
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Timer declarations and timer ISR  %
// % Initialisation occurs in setup()  %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//
volatile int interrupt_counter; // used to signify a timer interrupt event has occurred
volatile int current_interrupt_counter;
hw_timer_t * timer = NULL;      // establish timer structure
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

//
// The timer_ISR simply increments the varuable interrupt_counter.
// This variable is decremented by the main loop which will deal with
// timer interrupt events until they have all been processed, ie
// until interrupt_counter = 0
//
void IRAM_ATTR timer_ISR() {
  portENTER_CRITICAL_ISR(&timerMux);
  interrupt_counter++;
  portEXIT_CRITICAL_ISR(&timerMux);
}

#define max_num_timers      4   // 4 timers on ESP 32 - 0-3
#define default_timer       0   // used if specified timer is invalid

//
// Create an ESP 32 timer instance for given parameter
//
void create_and_start_timer(uint8_t timer_num) {
  if (timer_num >= max_num_timers) {// valid range is 0 to 3
    Serial.println("!!Invalid timer number - restting to default timer!!");
    Serial.flush();
    timer_num = default_timer;
  }
  // set up the timer interrupt for 1 millisecond interrupts
  timer = timerBegin(timer_num, 80, true);        // 80 Mhz processor, scale down to 1 Mhz
  timerAttachInterrupt(timer, &timer_ISR, true);  // link to our interrupt service routine (ISR)
  timerAlarmWrite(timer, 1000, true);             // define for 1 millisecond interrupts 0.001 Mhz
  timerAlarmEnable(timer);                        // start timer
}

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Elapsed Time Alert(s) declarations and functions %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#define max_ETAs               1 // User defined - at least 1, the heart beat

// ETA function and decision control macros
#define one_off_ETA            1
#define recurring_ETA          2
#define ETA_inserted           1 // positive result for success
#define ETA_insert_failure    -1 // negative result for failure
#define ETA_invalid_type      -2 // ditto
#define ETA_duplicate         -3 // ditto
#define ETA_extracted       true // converse is !ETA_extracted
#define ETA_deleted         true // converse is !ETA_deleted

// ETA_struct is the control structure at the heart of this design
// It will hold details for each ETA created
struct ETA_struct {
  uint8_t   ETA_type;
  uint16_t  ETA_alert_id; // alert id range is 0 - 65,535
  uint32_t  ETA_interval;
  uint32_t  ETA_count_down;
} ETAs[max_ETAs];

// ETA_alerts_struct is used dynamically to record the details of each
// ETA that has elapsed.  It is this structure that is used for processing
// elapsed ETAs.  It has the form that entry [0] holds a count of the number
// of entries that follow in the structure.  ETA details then follow on from
// entry [1], for example:
// [0]:3        (int)
// [1]:1,  23,  (unit8_t/uint16_t)
// [2]:2, 2047, (unit8_t/uint16_t)
// [3]:1,  97   (unit8_t/uint16_t)
struct ETA_alerts_struct {
  uint8_t  ETA_type;
  uint16_t ETA_alert_id;
  union {
    uint16_t ETA_num_alerts;
  };
} ETA_alerts[max_ETAs + 1]; // reserve an entry for the list count

// Creates an ETA with the given parameters.  Validation of the parameters
// is carried out.  The ETA is created if there is available space in the
// ETA structure.  Note also that ETAs must be unique in the ETA structure.
// An attempt to create a duplicate ETA will generate an error
//
int create_ETA(uint8_t   ETA_type,
               uint16_t  ETA_alert_id,
               uint32_t  ETA_interval) {
  if (ETA_type != one_off_ETA &&
      ETA_type != recurring_ETA)return ETA_invalid_type;
  int free_entry = -1;  // used to track first free ETA entry in ETA list
  for (int entry = max_ETAs - 1; entry >= 0; entry--) {
    if (ETAs[entry].ETA_type == 0) {
      // this is an empty slot so remember it
      free_entry = entry;
    } else {
      // this entry is already occupied so check if it is the same
      // as the ETA create requested
      if (ETAs[entry].ETA_type == ETA_type &&
          ETAs[entry].ETA_alert_id == ETA_alert_id) {
        // an ETA entry already exists for the requested ETA create
        return ETA_duplicate;
      }
    }
  }
  // ok, so requested ETA is not already in the ETA list
  // so add it if there is space, ie free_entry != -1
  if (free_entry != -1) {
    ETAs[free_entry].ETA_type       = ETA_type; // eg one off or recurring ETA
    ETAs[free_entry].ETA_alert_id   = ETA_alert_id;
    ETAs[free_entry].ETA_interval   = ETA_interval;
    ETAs[free_entry].ETA_count_down = ETA_interval;
    return ETA_inserted;
  }
  return ETA_insert_failure;
}

//
// Delete the given ETA from the ETA control structure
//
bool delete_ETA(uint8_t  ETA_type,
                uint16_t  ETA_alert_id) {
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    if (ETAs[entry].ETA_type     == ETA_type &&
        ETAs[entry].ETA_alert_id == ETA_alert_id) {
      // found, so delete it
      ETAs[entry].ETA_type = 0;
      return ETA_deleted;
    }
  }
  return !ETA_deleted;
}

//
// Prints the ETA structure contents. Useful for confirming
// that the configuration for ETA is as required
//
void print_ETAs() {
  Serial.begin(115200);
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    Serial.print("\nETA entry = ");
    Serial.print(entry);
    if (ETAs[entry].ETA_type != 0) {
      Serial.print("\n ETA_type = ");
      if (ETAs[entry].ETA_type == one_off_ETA)
        Serial.println("one off ETA");
      else Serial.println("recurring ETA");
      Serial.print(" ETA_alert_id    = ");
      Serial.println(ETAs[entry].ETA_alert_id);
      Serial.print(" ETA_interval    = ");
      Serial.println(ETAs[entry].ETA_interval);
      Serial.print(" ETA_count_down  = ");
      Serial.println(ETAs[entry].ETA_count_down);
    } else {
      Serial.println(" *** entry empty");
    }
  }
  Serial.flush();
}

//
// Extracts an ETA alert from the ETA alert structure.  Note that the
// entries in this structure have been generated because their respective
// ETAs have elapsed.  The ETA alert structure should be continually
// processed until it is empty. If an entry is removed then its ETA type
// and ETA alert id are returned in ETA_type and ETA_alert_id respectively
// along with a success return value (ETA_extracted). Otherwise these variables
// are set to 0 with a corresponding failure return value (!ETA_extracted)
//
bool get_ETA_alert(uint8_t &ETA_type,
                   uint16_t &ETA_alert_id) {
  uint8_t last_entry = ETA_alerts[0].ETA_num_alerts;
  if (last_entry > 0) {
    ETA_type     = ETA_alerts[last_entry].ETA_type;
    ETA_alert_id = ETA_alerts[last_entry].ETA_alert_id;
    ETA_alerts[last_entry].ETA_type     = 0;
    ETA_alerts[last_entry].ETA_alert_id = 0;
    ETA_alerts[0].ETA_num_alerts--; // reduce list by 1
    return ETA_extracted;
  }
  ETA_type     = 0;
  ETA_alert_id = 0;
  return !ETA_extracted;
}

//
// clear down the ETA structure and reset the alert count
// of the ETA alerts strucure
//
void clear_ETAs() {
  // ETA_type = 0 signifies an empty slot
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    ETAs[entry].ETA_type = 0;
  }
  ETA_alerts[0].ETA_num_alerts = 0; // reset alert list counter
}

//
// The function scans the ETA control, structure and decrements any ETAs
// defined.  If an ETA has reached its count down interval it will be added
// to the ETA_alerts structure so that it may be processed subsequently
// and asynchronously
//
void update_ETAs() {
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    if (ETAs[entry].ETA_type != 0) {
      // this ETA entry is defined
      if (ETAs[entry].ETA_count_down > 0) {
        // decrement count down timer, it has not yet elapsed
        ETAs[entry].ETA_count_down--; // reduce elapse interval by 1
        if (ETAs[entry].ETA_count_down == 0) {
          // add this lapsed ETA alert to the ETA alert list
          int next_entry = ETA_alerts[0].ETA_num_alerts + 1; // next free entry in the list
          ETA_alerts[next_entry].ETA_type     = ETAs[entry].ETA_type;
          ETA_alerts[next_entry].ETA_alert_id = ETAs[entry].ETA_alert_id;
          ETA_alerts[0].ETA_num_alerts        = next_entry;
          // now determine if this ETA needs to be reset or removed/deleted
          if (ETAs[entry].ETA_type == recurring_ETA) {
            // recurring ETA so reset the elapse interval count down
            ETAs[entry].ETA_count_down = ETAs[entry].ETA_interval;
          } else {
            // this must be a one off ETA, so delete it
            ETAs[entry].ETA_type = 0;
          }
        }
      }
    }
  }
}

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Data specific to this use of the ETA framework for this example %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//
// These macros can be used for creating ETAs and are provided
// for convenience of programming standard intervals, as required.
// They may be modified to achieve the desired count down frequencies,
// for example 2 * one_second, 5 * minute + 500, etc (all in milliseconds)
//
const uint32_t one_day    = 86400000; // millisecs in 24 hours
const uint32_t one_hour   =  3600000; // millisecs in 1 hour
const uint16_t one_minute =    60000; // millisecs in 1 minute
const uint16_t one_second =     1000; // millisecs in 1 second

// These macros define our ETA alert IDs for our sketch design/example
#define heart_beat       LED_BUILTIN // ETA alert ID - GPIO pin of in-built LED

// Now put together all of our ETA configs so we may create them more
// easily in our setup process.  The array is defined as follows:
// my_ETA_data[][0] is the ETA_type, one off or recurring
//            [][1] is the ETA alert id defining the function/purpose of the ETA
//            [][2] is the duration for the ETA's elapsed time count down (duration)
//                  in milliseconds
//
int my_ETA_data[max_ETAs][3] = {
  recurring_ETA, heart_beat,     one_second / 2 // heart beat data
};

void setup() {
  Serial.begin(115200);
  // clear down the ETA structures
  clear_ETAs();
  // create ETAs before the start of the timer interrupt process.
  // We use the ETA data defined above to create all the ETAs
  // we require.  Note the error checking
  int result;
  uint8_t entry;
  for (entry = 0; entry < max_ETAs; entry++) {
    result = create_ETA(my_ETA_data[entry][0], // ETA type
                        my_ETA_data[entry][1], // ETA alert id
                        my_ETA_data[entry][2]);// ETA alert interval (elapse/count down)
    if (result < 0) {
      Serial.print("create_ETA error:");
      if (result == ETA_invalid_type) Serial.println(" invalid ETA type");
      else {
        if (result == ETA_duplicate) Serial.println(" duplicate ETA");
        else Serial.println(" ETA insert failure");
      }
      Serial.flush();
    }
  }
  print_ETAs(); // confirm the ETA set ups
  //
  // initialise the heart beat GPIO pin
  pinMode(heart_beat, OUTPUT);
  digitalWrite(heart_beat, LOW); // set output to low
  //
  // finally, set up and start default timer
  create_and_start_timer(default_timer);
}

void loop() {
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  // % This main loop design ensures that all timner inetrrupts get processed %
  // % non-timer interrupt handling code may be added at the end of the main  %
  // % timer interrupt processing section                                     %
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  // Take a copy of the current interrupt_counter value
  // so we may test it in our while/do process without
  // running into a conflict between our ISR and main
  // loop processing
  portENTER_CRITICAL(&timerMux);
  current_interrupt_counter = interrupt_counter;
  portEXIT_CRITICAL(&timerMux);
  while (current_interrupt_counter > 0) {
    // at least one timer event has occurred so process the ETAs
    // while we have unprocessed timer interrupt events
    portENTER_CRITICAL(&timerMux);
    interrupt_counter--;
    current_interrupt_counter = interrupt_counter;
    portEXIT_CRITICAL(&timerMux);
    //
    // Update the ETAs to decrement their count downs for this
    // timer interrupt cycle and create a list of elapsed ETAs
    update_ETAs();
    //
    // Now process any ETA alert list of elasped ETAs. Keep processing
    // the ETA_alert list until it has been emptied
    uint8_t ETA_type;
    uint16_t ETA_alert_id;
    while (get_ETA_alert(ETA_type, ETA_alert_id) == ETA_extracted) {
      // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      // % Put ETA elapsing code here, use 'ETA_type' and/or 'ETA_alert_id'%
      // % for decision control and management                             %
      // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      //
      // We have an alert to process.
      // In this design the ETA_alert_id returned tells us which ETA alert we
      // need to process - we may ignore the ETA type as all are of type recurring
      //
      switch (ETA_alert_id) {// switch on ETA_alter_id
        case heart_beat:
          // Toggle the on board LED to show sketch operating
          // The on board LED pin is defined as the ETA_alert_id
          digitalWrite(ETA_alert_id, digitalRead(ETA_alert_id) ^ 1); // invert current status
          break;
        default:
          // Shoud never arrive here!
          Serial.println("Invalid ETA_alert_id encountered!!");
          break;
      }
      Serial.flush(); // flush the buffer
    }
  }
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  // % Put any none ETA handling code here %
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


}

Example 3: A Home Environmental Monitor based on the ETA Framework

C/C++
An example of how we can use the ETA Framework for developing projects needing multiple elapsing timers. This dummy example will monitor temp, humidity, soil moisture and light levels.
//
// R D Bentley (Stafford, UK), May 2022
//
// This example and code is in the public domain and
// may be used without restriction and without warranty.
//
// ESP 32 Multiple Elapsed Timer Alerting - Home Environmental Monitoring Example
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//
// This example shows how the ETA framework can be used to define elapsed time
// alerts that can be used to process specific event requirements, here a home
// environment monitoring requirement.
// The example does not provide a complete solution, rather it provides the hooks
// and eyes for fully integrated solution for the monitoring and adjustments of
// temperature, humidity, light and soil moisture that my be necessary for maintaining
// for example, garden plants, etc.
//
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Timer declarations and timer ISR  %
// % Initialisation occurs in setup()  %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//
volatile int interrupt_counter; // used to signify a timer interrupt event has occurred
volatile int current_interrupt_counter;
hw_timer_t * timer = NULL;      // establish timer structure
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

//
// The timer_ISR simply increments the varuable interrupt_counter.
// This variable is decremented by the main loop which will deal with
// timer interrupt events until they have all been processed, ie
// until interrupt_counter = 0
//
void IRAM_ATTR timer_ISR() {
  portENTER_CRITICAL_ISR(&timerMux);
  interrupt_counter++;
  portEXIT_CRITICAL_ISR(&timerMux);
}

#define max_num_timers      4   // 4 timers on ESP 32 - 0-3
#define default_timer       0   // used if specified timer is invalid

//
// Create an ESP 32 timer instance for given parameter
//
void create_and_start_timer(uint8_t timer_num) {
  if (timer_num >= max_num_timers) {// valid range is 0 to 3
    Serial.println("!!Invalid timer number - restting to default timer!!");
    Serial.flush();
    timer_num = default_timer;
  }
  // set up the timer interrupt for 1 millisecond interrupts
  timer = timerBegin(timer_num, 80, true);        // 80 Mhz processor, scale down to 1 Mhz
  timerAttachInterrupt(timer, &timer_ISR, true);  // link to our interrupt service routine (ISR)
  timerAlarmWrite(timer, 1000, true);             // define for 1 millisecond interrupts 0.001 Mhz
  timerAlarmEnable(timer);                        // start timer
}

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Elapsed Time Alert(s) declarations and functions %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#define max_ETAs               7 // User defined - we are configuring for 7 ETAs

// ETA function and decision control macros
#define one_off_ETA            1
#define recurring_ETA          2
#define ETA_inserted           1 // positive result for success
#define ETA_insert_failure    -1 // negative result for failure
#define ETA_invalid_type      -2 // ditto
#define ETA_duplicate         -3 // ditto
#define ETA_extracted       true // converse is !ETA_extracted
#define ETA_deleted         true // converse is !ETA_deleted

// ETA_struct is the control structure at the heart of this design
// It will hold details for each ETA created
struct ETA_struct {
  uint8_t   ETA_type;
  uint16_t  ETA_alert_id; // alert id range is 0 - 65,535
  uint32_t  ETA_interval;
  uint32_t  ETA_count_down;
} ETAs[max_ETAs];

// ETA_alerts_struct is used dynamically to record the details of each
// ETA that has elapsed.  It is this structure that is used for processing
// elapsed ETAs.  It has the form that entry [0] holds a count of the number
// of entries that follow in the structure.  ETA details then follow on from
// entry [1], for example:
// [0]:3        (int)
// [1]:1,  23,  (unit8_t/uint16_t)
// [2]:2, 2047, (unit8_t/uint16_t)
// [3]:1,  97   (unit8_t/uint16_t)
struct ETA_alerts_struct {
  uint8_t  ETA_type;
  uint16_t ETA_alert_id;
  union {
    uint16_t ETA_num_alerts;
  };
} ETA_alerts[max_ETAs + 1]; // reserve an entry for the list count

// Creates an ETA with the given parameters.  Validation of the parameters
// is carried out.  The ETA is created if there is available space in the
// ETA structure.  Note also that ETAs must be unique in the ETA structure.
// An attempt to create a duplicate ETA will generate an error
//
int create_ETA(uint8_t   ETA_type,
               uint16_t  ETA_alert_id,
               uint32_t  ETA_interval) {
  if (ETA_type != one_off_ETA &&
      ETA_type != recurring_ETA)return ETA_invalid_type;
  int free_entry = -1;  // used to track first free ETA entry in ETA list
  for (int entry = max_ETAs - 1; entry >= 0; entry--) {
    if (ETAs[entry].ETA_type == 0) {
      // this is an empty slot so remember it
      free_entry = entry;
    } else {
      // this entry is already occupied so check if it is the same
      // as the ETA create requested
      if (ETAs[entry].ETA_type == ETA_type &&
          ETAs[entry].ETA_alert_id == ETA_alert_id) {
        // an ETA entry already exists for the requested ETA create
        return ETA_duplicate;
      }
    }
  }
  // ok, so requested ETA is not already in the ETA list
  // so add it if there is space, ie free_entry != -1
  if (free_entry != -1) {
    ETAs[free_entry].ETA_type       = ETA_type; // eg one off or recurring ETA
    ETAs[free_entry].ETA_alert_id   = ETA_alert_id;
    ETAs[free_entry].ETA_interval   = ETA_interval;
    ETAs[free_entry].ETA_count_down = ETA_interval;
    return ETA_inserted;
  }
  return ETA_insert_failure;
}

//
// Delete the given ETA from the ETA control structure
//
bool delete_ETA(uint8_t  ETA_type,
                uint16_t  ETA_alert_id) {
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    if (ETAs[entry].ETA_type     == ETA_type &&
        ETAs[entry].ETA_alert_id == ETA_alert_id) {
      // found, so delete it
      ETAs[entry].ETA_type = 0;
      return ETA_deleted;
    }
  }
  return !ETA_deleted;
}

//
// Prints the ETA structure contents. Useful for confirming
// that the configuration for ETA is as required
//
void print_ETAs() {
  Serial.begin(115200);
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    Serial.print("\nETA entry = ");
    Serial.print(entry);
    if (ETAs[entry].ETA_type != 0) {
      Serial.print("\n ETA_type = ");
      if (ETAs[entry].ETA_type == one_off_ETA)
        Serial.println("one off ETA");
      else Serial.println("recurring ETA");
      Serial.print(" ETA_alert_id    = ");
      Serial.println(ETAs[entry].ETA_alert_id);
      Serial.print(" ETA_interval    = ");
      Serial.println(ETAs[entry].ETA_interval);
      Serial.print(" ETA_count_down  = ");
      Serial.println(ETAs[entry].ETA_count_down);
    } else {
      Serial.println(" *** entry empty");
    }
  }
  Serial.flush();
}

//
// Extracts an ETA alert from the ETA alert structure.  Note that the
// entries in this structure have been generated because their respective
// ETAs have elapsed.  The ETA alert structure should be continually
// processed until it is empty. If an entry is removed then its ETA type
// and ETA alert id are returned in ETA_type and ETA_alert_id respectively
// along with a success return value (ETA_extracted). Otherwise these variables
// are set to 0 with a corresponding failure return value (!ETA_extracted)
//
bool get_ETA_alert(uint8_t &ETA_type,
                   uint16_t &ETA_alert_id) {
  uint8_t last_entry = ETA_alerts[0].ETA_num_alerts;
  if (last_entry > 0) {
    ETA_type     = ETA_alerts[last_entry].ETA_type;
    ETA_alert_id = ETA_alerts[last_entry].ETA_alert_id;
    ETA_alerts[last_entry].ETA_type     = 0;
    ETA_alerts[last_entry].ETA_alert_id = 0;
    ETA_alerts[0].ETA_num_alerts--; // reduce list by 1
    return ETA_extracted;
  }
  ETA_type     = 0;
  ETA_alert_id = 0;
  return !ETA_extracted;
}

//
// clear down the ETA structure and reset the alert count
// of the ETA alerts strucure
//
void clear_ETAs() {
  // ETA_type = 0 signifies an empty slot
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    ETAs[entry].ETA_type = 0;
  }
  ETA_alerts[0].ETA_num_alerts = 0; // reset alert list counter
}

//
// The function scans the ETA control, structure and decrements any ETAs
// defined.  If an ETA has reached its count down interval it will be added
// to the ETA_alerts structure so that it may be processed subsequently
// and asynchronously
//
void update_ETAs() {
  for (uint8_t entry = 0; entry < max_ETAs; entry++) {
    if (ETAs[entry].ETA_type != 0) {
      // this ETA entry is defined
      if (ETAs[entry].ETA_count_down > 0) {
        // decrement count down timer, it has not yet elapsed
        ETAs[entry].ETA_count_down--; // reduce elapse interval by 1
        if (ETAs[entry].ETA_count_down == 0) {
          // add this lapsed ETA alert to the ETA alert list
          int next_entry = ETA_alerts[0].ETA_num_alerts + 1; // next free entry in the list
          ETA_alerts[next_entry].ETA_type     = ETAs[entry].ETA_type;
          ETA_alerts[next_entry].ETA_alert_id = ETAs[entry].ETA_alert_id;
          ETA_alerts[0].ETA_num_alerts        = next_entry;
          // now determine if this ETA needs to be reset or removed/deleted
          if (ETAs[entry].ETA_type == recurring_ETA) {
            // recurring ETA so reset the elapse interval count down
            ETAs[entry].ETA_count_down = ETAs[entry].ETA_interval;
          } else {
            // this must be a one off ETA, so delete it
            ETAs[entry].ETA_type = 0;
          }
        }
      }
    }
  }
}

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Data specific to this use of the ETA framework for this example %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//
// These macros can be used for creating ETAs and are provided
// for convenience of programming standard intervals, as required.
// They may be modified to achieve the desired count down frequencies,
// for example 2 * one_second, 5 * minute + 500, etc (all in milliseconds)
//
const uint32_t one_day    = 86400000; // millisecs in 24 hours
const uint32_t one_hour   =  3600000; // millisecs in 1 hour
const uint16_t one_minute =    60000; // millisecs in 1 minute
const uint16_t one_second =     1000; // millisecs in 1 second

// These macros define our ETA alert IDs for our sketch design/example
#define heart_beat       LED_BUILTIN // ETA alert ID - GPIO pin of in-built LED
#define check_temp      10 // ETA alert ID - an arbitary but unique value
#define check_moisture  20 // ETA alert ID - an arbitary but unique value
#define check_humidity  30 // ETA alert ID - an arbitary but unique value
#define check_light     40 // ETA alert ID - an arbitary but unique value
#define watchdog       100 // ETA alert ID - an arbitary but unique value
#define report_stats   200 // ETA alert ID - an arbitary but unique value

// Now put together all of our ETA configs so we may create them more
// easily in our setup process.  The array is defined as follows:
// my_ETA_data[][0] is the ETA_type, one off or recurring
//            [][1] is the ETA alert id defining the function/purpose of the ETA
//            [][2] is the duration for the ETA's elapsed time count down (duration)
//                  in milliseconds
//
int my_ETA_data[max_ETAs][3] = {
  recurring_ETA, heart_beat,     one_second / 2, // heart beat data
  recurring_ETA, check_temp,     10 * one_second, //5 * one_minute, // temperature check data
  recurring_ETA, check_moisture, 5 * one_second,  //20 * one_minute,// soil moisture check data
  recurring_ETA, check_humidity, 20 * one_second, //5 * one_minute, // humidity check data
  recurring_ETA, check_light,    15 * one_second, //one_hour,       // light level check data
  recurring_ETA, watchdog,       3 * one_second,     // watchdog data
  recurring_ETA, report_stats,   one_minute //one_day         // statistics data
};

//
// statistics variables
//
uint16_t temp_cycles,  moisture_cycles, humidity_cycles,
         light_cycles, watch_cycles,    stats_cycles;

// Establish the starting parameters for temp, moisture, humidity and light levels
// These will be modified as the various sensors are read
float min_temp = 25;
float max_temp = 25;
float alert_high_temp = 50;
float alert_low_temp  = -15;

float min_moisture = 65;
float max_moisture = 65;
float alert_high_moisture = 90;
float alert_low_moister   = 25;

float min_humidity = 80;
float max_humidity = 80;
float alert_high_humidity = 95;
float alert_low_humidity  = 10;

float min_light = 75;
float max_light = 75;
//
// resets any gathered statistics
//
void reset_stats(bool reset_all) {
  temp_cycles     = 0;
  moisture_cycles = 0;
  humidity_cycles = 0;
  light_cycles    = 0;
  watch_cycles    = 0;
  if (reset_all) stats_cycles = 0;
}

//
// Dummy function to read temp
//
float read_temp() {
  static float temp = 25;
  temp = pow(-1, random(1, 3)) + temp;
  // check for min and max values
  if (temp > max_temp) max_temp = temp;
  else if (temp < min_temp) min_temp = temp;
  return temp;
}

//
// Dummy function to read moisture
//
float read_moisture() {
  static float moisture = 65;
  moisture = pow(-1, random(1, 3)) + moisture;
  // check for min and max values
  if (moisture > max_moisture) max_moisture = moisture;
  else if (moisture < min_moisture) min_moisture = moisture;
  return moisture;
}

//
// Dummy function to read humidity
//
float read_humidity() {
  static float humidity = 80;
  humidity = pow(-1, random(1, 3)) + humidity;
  // check for min and max values
  if (humidity > max_humidity) max_humidity = humidity;
  else if (humidity < min_humidity) min_humidity = humidity;
  return humidity;
}

//
// Dummy function to read light level
//
float read_light() {
  static float light = 75;
  light = pow(-1, random(1, 3)) + light;
  // check for min and max values
  if (light > max_light) max_light = light;
  else if (light < min_light) min_light = light;
  return light;
}

void print_stats() {
  Serial.println("\nStatistics:");
  Serial.print("number stats cycles: ");
  Serial.println(stats_cycles);
  Serial.print("number temperture cycles : ");
  Serial.print(temp_cycles);
  Serial.print(",\tmin/max temperature = ");
  Serial.print(min_temp);
  Serial.print("/");
  Serial.println(max_temp);
  Serial.print("number moisture cycles   : ");
  Serial.print(moisture_cycles);
  Serial.print(",\tmin/max moisture    = ");
  Serial.print(min_moisture);
  Serial.print("/");
  Serial.println(max_moisture);
  Serial.print("number humidity cycles   : ");
  Serial.print(humidity_cycles);
  Serial.print(",\tmin/max humidity    = ");
  Serial.print(min_humidity);
  Serial.print("/");
  Serial.println(max_humidity);
  Serial.print("number light cycles      : ");
  Serial.print(light_cycles);
  Serial.print(",\tmin/max light level = ");
  Serial.print(min_light);
  Serial.print("/");
  Serial.println(max_light);
  Serial.print("number watchdog cycles   : ");
  Serial.println(watch_cycles);
  Serial.println();
  Serial.flush();
}

void setup() {
  Serial.begin(115200);
  // clear down the ETA structures
  clear_ETAs();
  // create ETAs before the start of the timer interrupt process.
  // We use the ETA data defined above to create all the ETAs
  // we require.  Note the error checking
  int result;
  uint8_t entry;
  for (entry = 0; entry < max_ETAs; entry++) {
    result = create_ETA(my_ETA_data[entry][0], // ETA type
                        my_ETA_data[entry][1], // ETA alert id
                        my_ETA_data[entry][2]);// ETA alert interval (elapse/count down)
    if (result < 0) {
      Serial.print("create_ETA error:");
      if (result == ETA_invalid_type) Serial.println(" invalid ETA type");
      else {
        if (result == ETA_duplicate) Serial.println(" duplicate ETA");
        else Serial.println(" ETA insert failure");
      }
      Serial.flush();
    }
  }
  reset_stats(true);  // reset all stats variables
  print_ETAs(); // confirm the ETA set ups
  //
  // initialise the heart beat GPIO pin
  pinMode(heart_beat, OUTPUT);
  digitalWrite(heart_beat, LOW); // set output to low
  //
  // finally, set up and start default timer
  create_and_start_timer(default_timer);
}

void loop() {
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  // % This main loop design ensures that all timner inetrrupts get processed %
  // % non-timer interrupt handling code may be added at the end of the main  %
  // % timer interrupt processing section                                     %
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

  // Take a copy of the current interrupt_counter value
  // so we may test it in our while/do process without
  // running into a conflict between our ISR and main
  // loop processing
  portENTER_CRITICAL(&timerMux);
  current_interrupt_counter = interrupt_counter;
  portEXIT_CRITICAL(&timerMux);
  while (current_interrupt_counter > 0) {
    // at least one timer event has occurred so process the ETAs
    // while we have unprocessed timer interrupt events
    portENTER_CRITICAL(&timerMux);
    interrupt_counter--;
    current_interrupt_counter = interrupt_counter;
    portEXIT_CRITICAL(&timerMux);
    //
    // Update the ETAs to decrement their count downs for this
    // timer interrupt cycle and create a list of elapsed ETAs
    update_ETAs();
    //
    // Now process any ETA alert list of elasped ETAs. Keep processing
    // the ETA_alert list until it has been emptied
    uint8_t ETA_type;
    uint16_t ETA_alert_id;
    while (get_ETA_alert(ETA_type, ETA_alert_id) == ETA_extracted) {
      // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      // % Put ETA elapsing code here, use 'ETA_type' and/or 'ETA_alert_id'%
      // % for decision control and management                             %
      // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      //
      // We have an alert to process.
      // In this design the ETA_alert_id returned tells us which ETA alert we
      // need to process - we may ignore the ETA type as all are of type recurring
      //
      switch (ETA_alert_id) {// switch on ETA_alter_id
        case heart_beat:
          // Toggle the on board LED to show sketch operating
          // The on board LED pin is defined as the ETA_alert_id
          digitalWrite(ETA_alert_id, digitalRead(ETA_alert_id) ^ 1); // invert current status
          break;
        case check_temp:
          // Code for processing temperature levels
          Serial.println("Check temperature process entered");
          temp_cycles++;
          float temp;
          temp = read_temp();
          // now decide if the current temperature is too high or too low
          // respond accordingly...
          Serial.print("  Current temperature = ");
          Serial.print(temp);
          Serial.println(" C.");
          break;
        case check_moisture:
          // Code for processing moisture levels
          Serial.println("Check moisture process entered");
          moisture_cycles++;
          float moisture;
          moisture = read_moisture(); // read current moisture level
          // now decide if the current moisture is too high or too low
          // respond accordingly...
          Serial.print("  Current soil moisture = ");
          Serial.print(moisture);
          Serial.println("%.");
          break;
        case check_humidity:
          // Code for processing humidity levels
          Serial.println("Check humidity process entered");
          humidity_cycles++;
          float humidity;
          humidity = read_humidity(); // read current humidiy level
          // now decide if the current humidity is too high or too low
          // respond accordingly...
          Serial.print("  Current humidity = ");
          Serial.print(humidity);
          Serial.println("%.");
          break;
        case check_light:
          // Code for processing light levels
          Serial.println("Check light process entered");
          light_cycles++;
          float light;
          light = read_light(); // read current ight level
          // now decide if the current light is too high or too low
          // respond accordingly...
          Serial.print("  Current light level = ");
          Serial.println(light);
          break;
        case watchdog:
          // Code for processing the watchdog
          Serial.println("Watchdog process entered");
          watch_cycles++;
          Serial.print("  number of watchdog cycles = ");
          Serial.println(watch_cycles);
          Serial.flush();
          if (max_temp >= alert_high_temp || min_temp <= alert_low_temp
              || max_moisture >= alert_high_moisture || min_moisture <= alert_low_moister
              || max_humidity >= alert_high_humidity || min_humidity <= alert_low_humidity) {
            // The environment is out of spec!  Report via the internet target
            // to raise an alert requiring attention...
            Serial.println("!!!!Environment out of spec!!!!"); // dummy alert
            print_stats();
          }
          break;
        case report_stats:
          // Report daily statistics via the internet to the target agent
          stats_cycles++;
          print_stats();
          reset_stats(false); // reset stats apart from stats cycle count
          break;
        default:
          // Shoud never arrive here!
          Serial.println("Invalid ETA_alert_id encountered!!");
          break;
      }
      Serial.flush(); // flush the buffer
    }
  }
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  // % Put any none ETA handling code here %
  // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


}

Credits

ronbentley1
25 projects • 13 followers
Contact

Comments

Please log in or sign up to comment.