Decision Matrix

I have written a program for a product; it is a pill taking reminder box. It is set to remind the user to take their pills at a certain time every 24 hours. It prompts them via audio and a flashing LED. The inputs are a magnetic switch on the door to reveal their pill tray, an alarm via a RTC and a button that they push to confirm that they have taken their pill. (there is another part of the product that is a book that sequences audio pages along with volume control and language, but that is rather straightforward - so lets ignore that for now).

The sum of these three inputs (RTC alarm, door switch and pill confirm button) determines the current state: A) time to take pill but door closed B) time to take pill and door it opened C) pill-confirm button pushed but door still opened D) pill-confirm button pushed and door closed etc. etc Well you get the idea. There are more than 10 different scenarios and if they don't do anything, it needs to go to sleep and conserve power but flash the LED every so often to deplict a state.

I have some code working but not perfectly and to be truthful, it is a mess. I would like your opinion on how I can make this more simple and straighforward. It makes me think of an Arduino function that is a "Decision Matrix" where a number of different inputs, alarm states etc are in a 2x2 matrix (array?) and can that matrix can be tested continually in a loop and if any one of them is true then it is executed.

BTW, what makes this even harder, is that I'm running audio prompts that require that there be no delays() - so almost every function needs to be in the loop and the use of millis() needs to be incorporated.

Thoughts? Code here for reference:

/* sketch for READ prototype Humanity Press using FireBeetle esp32 DFR0478  
Board: esp32/Firebeetle-ESP32 on COM7 Programmer: ESPtool Upload Speed 921600  
DS3231 RTC breakout on SLC and SDA
Sparkfun ST25DV64KC Dynamic RFID Tag Breakout
adafruit 254 MicroSD card reader
MAX98357A I2S audio output
*/


#include "RTClib.h"
#include "SparkFun_ST25DV64KC_Arduino_Library.h"  // Click here to get the library:  http://librarymanager/All#SparkFun_ST25DV64KC
#include "Arduino.h"
#include "Audio.h"
#include "SD.h"
#include "FS.h"
#include "esp_sleep.h"
#include <ezButton.h>

#define BUTTON_PIN_BITMASK 0x1000008010  // GPIOs , 36(lid switch), 15(pwr button) and  4(A5) SQW TIMER TRIGGER

#define SD_CS 5      // D9 of Firebeetle microSD Card Reader connections
#define SPI_MOSI 23  // DI of SD Card
#define SPI_MISO 19  // DO of SD Card
#define SPI_SCK 18

#define I2S_DOUT 25  //(D2) DAC0 I2S output for firebeetle
#define I2S_BCLK 14
#define I2S_LRC 17

Audio audio;     // Create Audio object
RTC_DS3231 rtc;  // create RTC object
DateTime nowDT;
SFE_ST25DV64KC tag;  // create RFID object

RTC_DATA_ATTR int bootCount = 0;  // for sleep


const int MAX_ANALOG_VAL = 4095;
const float MAX_BATTERY_VOLTAGE = 4.1;  // Max LiPoly voltage of a 3.7 battery is 4.2
const int MIN_BATT_THRESHOLD = 1865;

#define uS_TO_S_FACTOR 1000000  // Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 10        // Time ESP32 will sleep (in seconds)  and then wake up

////////////////////////// PIN ASSIGNMENTS ///////////////////////////////////////////////
#define CLOCK_INTERRUPT_PIN A3  //A3 Physical Pin on Firebeetle  The number of the pin for monitor alarm status on SQW DS3231
int const red_LED = 0;          // GPIO 0
int const green_LED = 3;
int const blue_LED = 12;
int const batt_input = A0;
int const power_pin = 15;
int const lid_sw = A1;  //reed switch   all buttons pulled UP via 10K resistor
int pwr_button(A2);

ezButton pwr_but(A2);
ezButton lang_select_but(26);
ezButton pill_confirm_but(4);
ezButton page_back_but(13);
ezButton page_forw_but(27);
//ezButton vol_up_but(2);      // digitalRead not working for GPIO 2 use analogRead instead
int vol_up_button = 2;
ezButton vol_dn_but(16);


bool lid_sw_val;
bool pwr_state = false;  // initial set to false
bool lang_state = false;
bool pill_confirm_toggle = false;
bool warning_pill_not_taken = false;
bool vol_up_but_state = false;
int pwr_but_state;

const char* audiofilenames[6][24] = {  //various audio files on MicroSD card
  /* 0 */ { "tick.mp3", "turntone.mp3", "am.mp3", "pm.mp3", "silence.mp3" },
  /* 1 */ { "1-AM.mp3", "2-AM.mp3", "3-AM.mp3", "4-AM.mp3", "5-AM.mp3", "6-AM.mp3", "7-AM.mp3", "8-AM.mp3", "9-AM.mp3", "10-AM.mp3", "11-AM.mp3", "12-PM.mp3", "1-PM.mp3", "2-PM.mp3", "3-PM.mp3", "4-PM.mp3", "5-PM.mp3", "6-PM.mp3", "7-PM.mp3", "8-PM.mp3", "9-PM.mp3", "10-PM.mp3", "11-PM.mp3", "12-PM.mp3" },
  /* 2 */ { "day14inst.mp3", "end_book.mp3", "INSTRUCT.mp3", "intro_lg.mp3", "intro_sht.mp3", "med_close.mp3", "CLOSELID.mp3", "CLSELID2.mp3", "WELCOME.mp3", "DOORSTP2.mp3", "DOORSTOP.mp3" },
  /* 3 */ { "TAKEMED.mp3", "NEXTTAKE.mp3", "NOT_MED.mp3", "NOT_MED2.mp3", "silence.mp3", "ENGLISH.mp3", "ISIXHOSA.mp3", "ISIZULU.mp3", "takemed3.mp3" },
  /* 4 */ { "DAY_1.mp3", "DAY_2.mp3", "DAY_3.mp3", "DAY_4.mp3", "DAY_5.mp3", "DAY_6.mp3", "DAY_7.mp3", "DAY_8.mp3", "DAY_9.mp3", "DAY_10.mp3", "DAY_11.mp3", "DAY_12.mp3", "DAY_13.mp3", "DAY14INST.mp3" },
  /* 5 */ { "page1.mp3", "page2.mp3", "page3.mp3", "page4.mp3", "page5.mp3", "page6.mp3", "page7.mp3", "page8.mp3", "page9.mp3", "page10.mp3", "page11.mp3", "page12.mp3", "page13.mp3", "page14.mp3", "page15.mp3", "page16.mp3", "page17.mp3", "page18.mp3", "END_BOOK.mp3" }
};


////////////////////////////// Timing ///////////////////////////////
int long current_Millis;
int long prev_button_debounce_millis = 0;
int long prev_LED_millis = 0;
int long prev_RFID_millis = 0;
int long prev_clock_millis = 0;
int long prev_take_meds_millis = 0;
int long prev_lid_close_finish_millis = 0;
int long prev_not_yet_time_millis = 0;
int long prev_pill_confirm_but_millis = 0;
int long prev_sleep_delay_millis = 0;
int long book_pwr_off_delay_millis = 0;

int16_t button_debounce_time = 250;
int16_t RFID_time = 5000;
int16_t clock_time = 3000;
int16_t LED_pause_time = 100;

int16_t take_med_repeat = 1000;             // initially set by alarm and then adjusted in take_med() to be 15 seconds for 10x  sleep
int16_t not_time_yet_repeat = 500;          // first time led opened and not time to take med set to 500ms then after audio repeat every 15 seconds (set by not_yet_time()) then when lid closed
int16_t pill_conf_loop_time = 3000;         // set initially by take_med() to be 3 second
int16_t pill_conf_lid_open_repeat = 15000;  // initially set by take_med() to 500ms and then adjusted to 15 seconds by lid_close_finish()
int16_t pill_conf_lid_close_repeat;         //

int long sleep_delay_time;
int long sleep_delay_time_no_door_open = 120000;             // 5 minutes to sleep if alarm triggered and door not opened
int long sleep_delay_time_pill_confirm_door_closed = 15000;  // go to sleep 15000 after confirm pill door closed
int long book_pwr_off_time = 300000;                         // (300000) 5 minutes

int8_t lid_open_count = 1;

/////////////////////////////// end of timing /////////////////////////////////////////////

int8_t audio_trigger = -1;  // this triggers no. of audio prompts for given function - at -1 to keep it false
char audio_array1;          // these hold the variables for for the various audio prompts after the main audio prompt in the function
char audio_array2;
char audio_array3;
char audio_array4;
char audio_array5;
char audio_array6;
char audio_array7;
char audio_array8;

int LED_color;  // variable for storing LED color;
int Red = 0;
int Green = 1;
int Blue = 2;
int Yellow = 3;
bool LED_status = true;    // determines toggle of whether LED is on or off flashing curr_Lang_state
bool LED_trigger = false;  // LED switch ON or FF
bool alarm_fired = false;
bool sleep_mode = false;
bool door_left_open = false;
bool not_yet_time_status = false;

byte startup_block = 0;  //used to bypass lang prompts in Main loop  upon startup

int day_count = 0;  // set at zero
int page_count = 0;
int const total_page_count = 19;  // total pages plus one for end of page

int volume_level = 15;


void setup() {

  pinMode(power_pin, OUTPUT);
  pinMode(pwr_button, INPUT);
  digitalWrite(power_pin, HIGH);
  delay(1000);

  Wire.begin();
  delay(200);
  Serial.begin(115200);

  pinMode(batt_input, INPUT);

  pinMode(red_LED, OUTPUT);
  pinMode(green_LED, OUTPUT);
  pinMode(blue_LED, OUTPUT);
  pinMode(lid_sw, INPUT);

  page_back_but.setDebounceTime(100);
  page_forw_but.setDebounceTime(100);
  lang_select_but.setDebounceTime(100);
  pwr_but.setDebounceTime(100);
  //vol_up_but.setDebounceTime(100);        // pin 2 not working as digitalRead - analogRead instead
  pinMode(vol_up_button, INPUT);
  vol_dn_but.setDebounceTime(100);
  pill_confirm_but.setDebounceTime(50);


  if (!tag.begin(Wire)) {  // check if RFID breakout is connected
    Serial.println(F("ST25 not detected. Freezing..."));
    while (1)
      ;
  }  // Do nothing more
  Serial.println(F("ST25 connected."));


  audio.setVolume(volume_level);

  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }

  ////////////////////////// RTC TIME FUNCTION SETUP ///////////////////////////////////////////
  rtc.disable32K();  //we don't need the 32K Pin, so disable it

  if (rtc.lostPower()) {
    Serial.println("RTC lost power and resetting to time of this upload");  // if power loss following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  // uncomment following to explicitly set date & time, for example to set January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

  pinMode(CLOCK_INTERRUPT_PIN, INPUT_PULLUP);  // Making it so, that the alarm will trigger an interrupt

  attachInterrupt(digitalPinToInterrupt(CLOCK_INTERRUPT_PIN), onAlarm, FALLING);
  //esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK, ESP_EXT1_WAKEUP_ALL_LOW);  // triggered if go LOW on GPIOs 4, 15 and 36  lid switch, pwr button, RTC SQW TIMER TRIGGER
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_35, 0);  //1 = High, 0 = Low GPIO 35 A3 of FireBeetle
  //esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);  // power down flash

  rtc.clearAlarm(1);
  rtc.clearAlarm(2);

  rtc.writeSqwPinMode(DS3231_OFF);  // stop oscillating signals at SQW Pin otherwise setAlarm1 will fail

  rtc.disableAlarm(2);  // turn off alarm 2 (in case it isn't off already) again, this isn't done at reboot, so a previously set alarm could easily go overlooked

  //nowDT = rtc.now();  // set the pill take time at time of reset - regardless of time lapse between 24 hour periods

  DateTime now = rtc.now();                                     // debug
  rtc.setAlarm1(now + TimeSpan(0, 0, 3, 0), DS3231_A1_Minute);  //  debug set alarm for every 2 minutes

  DateTime alarmTime = rtc.getAlarm1();
  int alarm1_hr = alarmTime.hour();        // notDT is set in setup() at time of reset
  int alarm1_min = alarmTime.minute();     // notDT is set in setup() at time of reset
  Serial.print("Alarm time is set to: ");  // display alarm set time
  Serial.print(alarm1_hr);
  Serial.print(":");
  Serial.println(alarm1_min);

  ///////////////////////////////////// END RTC FUNCTIONS ///////////////////////////////////////////////////////
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

  pinMode(SD_CS, OUTPUT);  // Set microSD Card CS as OUTPUT and set HIGH
  digitalWrite(SD_CS, HIGH);
  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);  // Initialize SPI bus for microSD Card
  delay(1000);
  if (!SD.begin(SD_CS)) {  // Start microSD Card
    Serial.println("Error accessing microSD card!");
    while (true)
      ;
  }

  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);  // Setup I2S

  ///////////////////// first time reset sets up to take first day medication
  LED_trigger = true;
  pill_confirm_toggle = false;
  alarm_fired = true;  // on reset trigger alarm to be true
  startup_block = 1;
  digitalWrite(2, HIGH);  // volume up button set back to high
  audio.connecttoFS(SD, "WELCOME.mp3");
}

void loop() {
  current_Millis = millis();

  audio.loop();  // with audio.loop() nothing in the loop can be blocking
  battery_monitor();
  Flash_LED();     // indication for various functions
  read_buttons();  // continually scans switch button states
  power_status();  // monitors power for book function whether on or off
  Turn_page();     // turn page forward of backward funtion

  Alarm_monitor();  // monitors medication side for when to take next pill/s and door open falsely
  //Clock_monitor();     // diagnostic feature
  Language_select();   // toggles selected language with audio prompt
  Volume_control();    // function to adjust volume for audio
  Not_yet_time();      // monitors lid and audio warning if lid is open before time to take meds
  Time_to_take();      // when alarm fires loops into this function to flash neopixel if lid is close or audio prompt if lid is open to push confirm button
  Lid_close_finish();  // monitors whether pill confirm button is pushed and whether lid is still open or close - if closed next alarm time set and goes to sleep
  goto_sleep();
  //RFID_info();         // diagnostic feature
}

void audio_eof_mp3(const char* info) {
  if (audio_trigger > 0) {
    if (audio_trigger == 1) {
      audio.connecttoFS(SD, audiofilenames[audio_array1][audio_array2]);
      audio_trigger--;
    }
    if (audio_trigger == 2) {
      audio.connecttoFS(SD, audiofilenames[audio_array3][audio_array4]);
      audio_trigger--;
    }
    if (audio_trigger == 3) {
      audio.connecttoFS(SD, audiofilenames[audio_array5][audio_array6]);
      audio_trigger--;
    }
    if (audio_trigger == 4) {
      audio.connecttoFS(SD, audiofilenames[audio_array7][audio_array8]);
      audio_trigger--;
    }
  }
}


void read_buttons() {
  lid_sw_val = digitalRead(lid_sw);
  page_back_but.loop();  //MUST call the loop() function first
  page_forw_but.loop();
  lang_select_but.loop();
  pwr_but.loop();
  //vol_up_but.loop();    // pin 2 not working as digitalRead - use analogRead instead
  vol_dn_but.loop();
  pill_confirm_but.loop();

  vol_up_but_state = analogRead(vol_up_button);
  pwr_but_state = digitalRead(pwr_button);
}


void Not_yet_time() {
  if ((current_Millis - prev_not_yet_time_millis > not_time_yet_repeat) && (lid_sw_val == LOW) && (not_yet_time_status == true) && (lid_open_count < 10)) {  //lid is opened but not yet time to take meds
    digitalWrite(power_pin, HIGH);
    sleep_mode = false;
    audio_trigger = 1;
    audio_array1 = 2;
    audio_array2 = 6;  // close the lid
    audio.connecttoFS(SD, audiofilenames[3][3]);
    Serial.println("not yet time - close the lid");
    lid_open_count++;
    if (lid_open_count > 1) {
      not_time_yet_repeat = 15000;  // after first count make repeats time every 15 seconds for 10X
    }
    prev_not_yet_time_millis = current_Millis;
  }

  else if ((current_Millis - prev_not_yet_time_millis > not_time_yet_repeat) && (lid_sw_val == LOW) && (alarm_fired == false) && (lid_open_count >= 10)) {  // lid is opened and 10x audio warnings given
    sleep_mode = true;
    sleep_delay_time = 1000;
    not_yet_time_status = false;  // cancel audio warning after 10x
    prev_not_yet_time_millis = current_Millis;
  }

  else if ((lid_sw_val == HIGH) && (not_yet_time_status == true) && (pwr_state == false)) {  // lid is close and not yet time true as well as power state is off
    sleep_delay_time = 15000;
    sleep_mode = true;
    not_time_yet_repeat = 500;  //  resets to first time lid is opened and not yet time
    lid_open_count = 1;
  }
}


void Time_to_take() {

  if (pill_confirm_but.isPressed() && !pill_confirm_toggle) {  // time to take and lid opened but pill confirmed pushed
    not_yet_time_status = false;                               // allows for door to be opened normally
    pill_confirm_toggle = true;
    warning_pill_not_taken = false;
    lid_open_count = 1;  // reset for Lid_close_finish to respond immediately first time
    prev_lid_close_finish_millis = current_Millis;
    alarm_fired = false;  // alarm is turned off when pill confirm button pushed
    LED_pause_time = 1000;
    LED_color = 1;
    sleep_mode = true;
    sleep_delay_time = 300000;                 // will sleep in 5 minutes
    prev_sleep_delay_millis = current_Millis;  //resets for above

  } else if ((current_Millis - prev_take_meds_millis > take_med_repeat) && (lid_sw_val == 1) && (alarm_fired == true) && (pill_confirm_toggle == false)) {  // alarmed fired but lid still closed
    Serial.println("time to take medication but lid still closed");
    LED_pause_time = 1000;
    LED_color = 3;                                     //yellow
    sleep_mode = true;
    sleep_delay_time = sleep_delay_time_no_door_open;  // if door not opened but alarm true
    warning_pill_not_taken = true;
    prev_take_meds_millis = current_Millis;
  } else if ((current_Millis - prev_take_meds_millis > take_med_repeat) && (lid_sw_val == 0) && (alarm_fired == true) && (pill_confirm_toggle == false)) {  // alarmed fired and lid open prompts audio
    Serial.println("time to take triggered and lid open");
    LED_pause_time = 1000;
    LED_color = 3;  //yellow
    take_med_repeat = 15000;
    warning_pill_not_taken = true;
    sleep_mode = true;
    sleep_delay_time = sleep_delay_time_no_door_open;
    prev_take_meds_millis = current_Millis;
    audio.connecttoFS(SD, audiofilenames[3][8]);  // time to take med
  }
}

void Lid_close_finish() {
  if (day_count == 13 && pill_confirm_toggle == true) {  // if reached end of 14 days go to end_of_days
    end_of_days();
  }
  if ((current_Millis - prev_lid_close_finish_millis > pill_conf_loop_time) && (pill_confirm_toggle == true) && (alarm_fired == false)) {  // pill confirm button pushed but lid open

    if (lid_sw_val == 0) {  // lid switch is still open but pill confirm button pushed
      Serial.println(" confirm button pushed lid is open");
      if (lid_open_count > 1) {
        pill_conf_loop_time = pill_conf_lid_open_repeat;  // increases the time of repeat past the inital time thru the loop
      }
      lid_open_count++;
      LED_pause_time = 1000;
      LED_color = 1;  //green
      Serial.println("pill confimed but lid still open");
      audio.connecttoFS(SD, audiofilenames[2][6]);  // lid still open
      if (lid_open_count > 10) {
        warning_pill_not_taken = false;
        sleep_delay_time = 1000;
      }
    }

    if ((lid_sw_val == 1) && (pill_confirm_toggle == true)) {  // pill confirm button pushed lid switch is closed
      pill_conf_loop_time = pill_conf_lid_open_repeat;         // increases the time of repeat past the inital time thru the loop
      Serial.println("lid is close and finished");
      warning_pill_not_taken = false;
      LED_trigger = false;          // turn off reoccuring LED flashing
      analogWrite(green_LED, 255);  // green led on constant will go off when sleep

      DateTime alarmTime = rtc.getAlarm1();    // always same time every day
      int alarm1_hr = alarmTime.hour();        // notDT is set in setup() at time of reset
      int alarm1_min = alarmTime.minute();     // notDT is set in setup() at time of reset
      Serial.print("Alarm time is set to: ");  // display alarm set time
      Serial.print(alarm1_hr);
      Serial.print(":");
      Serial.println(alarm1_min);
      audio_trigger = 3;  // loop thru audio_eof_mp3() 1 additional audio prompts

      audio_array1 = 0;
      audio_array2 = 1;            // turntone
      audio_array3 = 1;            // hour am pm
      audio_array4 = (alarm1_hr);  // 24 hours from time when reset was pushed
      audio_array5 = 3;
      audio_array6 = 1;                                     // next time to take                 // 24 hours from time when reset was pushed                                   // turn page tone
      audio.connecttoFS(SD, audiofilenames[4][day_count]);  // next time to take meds is
      day_count++;
      pill_confirm_toggle = false;
      sleep_mode = true;
      sleep_delay_time = sleep_delay_time_pill_confirm_door_closed;  // 15 seconds after closing lid will sleep
      Serial.println("sleep will occur in 15 seconds");
      prev_sleep_delay_millis = current_Millis;
      not_yet_time_status = true;
    }

    if ((lid_sw_val == 0) && (pill_confirm_toggle == false) && (lid_open_count > 10)) {  // lid is open and pill confirm never pushed after 10x warnings and sleeps
      sleep_mode = true;
      door_left_open = true;
      warning_pill_not_taken = true;
    }
    prev_lid_close_finish_millis = current_Millis;
  }
}

void Turn_page() {
  if ((page_forw_but.isReleased()) && (page_count < total_page_count)) {
    Serial.println("page forward");
    book_pwr_off_delay_millis = current_Millis;            // resets book power off delay
    audio.connecttoFS(SD, audiofilenames[5][page_count]);  // Play page no.
    page_count++;
  } else if ((page_back_but.isReleased()) && (page_count > 0)) {
    Serial.println("page back");
    book_pwr_off_delay_millis = current_Millis;  // resets book power off delay
    page_count--;
    audio.connecttoFS(SD, audiofilenames[5][page_count]);  // Play click
  }
}

void Language_select() {
  if (lang_select_but.isReleased()) {
    lang_state = !lang_state;  // toggle state whenever entering loop
    if (lang_state == true) {
      Serial.println("isiZul language selected ");
      audio.connecttoFS(SD, audiofilenames[3][7]);  // isiZulu
    } else {
      Serial.println("english language seelected ");
      audio.connecttoFS(SD, audiofilenames[3][5]);  // english
    }
  }
}

void power_status() {
  if ((pwr_but.isReleased()) || (pwr_but_state == LOW)) {  //when press or is woke up via power button GPIO
    pwr_state = !pwr_state;                                // toggle state whenever entering loop
    if (pwr_state == true) {
      digitalWrite(power_pin, HIGH);
      sleep_mode = false;
      book_pwr_off_delay_millis = current_Millis;
      Serial.println("power is ON ");
      audio.connecttoFS(SD, audiofilenames[2][2]);  // instruction
    } else if (pwr_state == false) {
      Serial.println("power is OFF ");
      audio.connecttoFS(SD, audiofilenames[0][1]);  // turn tone to signal off
      sleep_mode = true;
      sleep_delay_time = 3000;  // 3 second delay before sleep
      prev_sleep_delay_millis = current_Millis;
    }
  }
  if ((current_Millis - book_pwr_off_delay_millis > book_pwr_off_time) && (!audio.isRunning())) {  // automatically goes into sleep after preset time
    sleep_mode = true;
    pwr_state = false;
    sleep_delay_time = 3000;
    audio.connecttoFS(SD, audiofilenames[0][1]);  // turn tone to signal off
  }
}

void goto_sleep() {
  if ((current_Millis - prev_sleep_delay_millis > sleep_delay_time) && (sleep_mode == true)) {
    pill_confirm_toggle = false;
    digitalWrite(red_LED, LOW);
    analogWrite(green_LED, 0);
    digitalWrite(blue_LED, LOW);
    pwr_state = false;
    digitalWrite(power_pin, LOW);
    Serial.println("Going to sleep now");
    delay(1000);
    esp_light_sleep_start();
    prev_sleep_delay_millis = current_Millis;
  }
}

void Volume_control() {
  if ((vol_up_but_state == 0) && (volume_level < 22)) {  // vol up state taken from analogRead
    Serial.println("Volume UP");
    audio.setVolume(volume_level);      // 0...21
    audio.connecttoFS(SD, "tick.mp3");  // Play tick
    volume_level++;                     // set volume level up
  }
  if (vol_dn_but.isPressed() && volume_level > 0) {
    Serial.println("Volume DOWN");
    audio.setVolume(volume_level);      // 0...21
    audio.connecttoFS(SD, "tick.mp3");  // Play tick
    volume_level--;                     // set volume level down
  }
}


void Flash_LED() {
  if ((current_Millis - prev_LED_millis > LED_pause_time) && (LED_trigger == true)) {
    LED_status = !LED_status;  // toggle state
    if (LED_status) {
      if (LED_color == 3) {
        digitalWrite(red_LED, HIGH);  // Yellow
        analogWrite(green_LED, 120);
        digitalWrite(blue_LED, 0);
      } else if (LED_color == 0) {  //red
        digitalWrite(red_LED, HIGH);
        digitalWrite(green_LED, 0);
        digitalWrite(blue_LED, LOW);
      } else if (LED_color == 1) {  // green
        digitalWrite(red_LED, LOW);
        analogWrite(green_LED, 255);
        digitalWrite(blue_LED, LOW);
      } else if (LED_color == 2) {  //blue
        digitalWrite(red_LED, LOW);
        digitalWrite(green_LED, LOW);
        digitalWrite(blue_LED, HIGH);
      }
    } else if (!LED_status) {
      digitalWrite(red_LED, LOW);  //all LEDs off
      analogWrite(green_LED, 0);
      digitalWrite(blue_LED, LOW);
    }
    prev_LED_millis = current_Millis;
  }
}


void Alarm_monitor() {
  if (rtc.alarmFired(1)) {
    if (sleep_mode == true) {
      digitalWrite(power_pin, HIGH);
      Serial.println(rtc.alarmFired(1));
      DateTime now = rtc.now();  // Get the current time
      char buff[] = "Alarm triggered at hh:mm:ss DDD, DD MMM YYYY";
      Serial.println(now.toString(buff));
      Serial.println();
      rtc.clearAlarm(1);
      delay(100);
      not_yet_time_status = false;  //  allows for door to be opened
      LED_trigger = true;
      pwr_state = true;
      alarm_fired = true;
      sleep_mode = false;
      pill_confirm_toggle = false;
      pill_conf_loop_time = 3000;  //resets for next time to confirm audio
      take_med_repeat = 1000;
    }
  }
}

void end_of_days() {
  day_count = 0;
  pill_confirm_toggle = false;
  rtc.clearAlarm(1);
  digitalWrite(blue_LED, HIGH);
  audio.connecttoFS(SD, audiofilenames[4][13]);  // end of days to take pills
  sleep_mode = true;
  sleep_delay_time = 15000;  // go to sleep 15 seconds after audio prompt
  prev_sleep_delay_millis = current_Millis;
}

void battery_monitor() {
  if ((pwr_state == true) && (lid_sw_val == 1) && (!audio.isRunning())) {  // make sure it is not in medication cycle
    int rawValue = analogRead(batt_input);                                 // Reference voltage on ESP32 is 1.1V  voltage divider = upper/lower 1M resistors
    float voltageLevel = (rawValue / 4095.0) * 2 * 1.1 * 3.6;              // calculate voltage level  refence voltage is 1.1 and factor is 3.6
    float batteryFraction = voltageLevel / MAX_BATTERY_VOLTAGE;
    Serial.println((String) "Raw:" + rawValue + " Voltage:" + voltageLevel), delay(1000);  // for debug
    if (rawValue < MIN_BATT_THRESHOLD) {                                                   // if battery level is below threshold
      digitalWrite(power_pin, HIGH);
      for (int i = 0; i < 3; i++) {
        digitalWrite(0, HIGH);  //flash red
        delay(150);
        digitalWrite(0, LOW);
        delay(150);
        Serial.println("below battery threshold");
      }
      if (warning_pill_not_taken == true && alarm_fired == true) {  // flashed 5 times if alarmed fired and pill not take after going to sleep
        for (int i = 0; i < 5; i++) {
          digitalWrite(2, HIGH);  //flash blue
          delay(120);
          digitalWrite(2, LOW);
          delay(120);
          Serial.println("below battery threshold");
        }
      }
    } else {
      digitalWrite(power_pin, LOW);
      esp_light_sleep_start();
    }
  }
}

void Clock_monitor() {
  if (current_Millis - prev_clock_millis > clock_time) {
    char date[10] = "hh:mm:ss";  // print current time
    rtc.now().toString(date);
    Serial.print(date);

    Serial.print("] SQW (if = 0 it is triggered) = ");  // the value at SQW-Pin (because of pullup 1 means no alarm)
    Serial.println(digitalRead(CLOCK_INTERRUPT_PIN));

    DateTime alarm1 = rtc.getAlarm1();  // the stored alarm value + mode
    Ds3231Alarm1Mode alarm1mode = rtc.getAlarm1Mode();
    char alarm1Date[12] = "DD hh:mm:ss";
    alarm1.toString(alarm1Date);
    Serial.print(" [Alarm1: ");
    Serial.print(alarm1Date);
    Serial.print(", Mode: ");
    switch (alarm1mode) {
      case DS3231_A1_PerSecond: Serial.print("PerSecond"); break;
      case DS3231_A1_Second: Serial.print("Second"); break;
      case DS3231_A1_Minute: Serial.print("Minute"); break;
      case DS3231_A1_Hour: Serial.print("Hour"); break;
      case DS3231_A1_Date: Serial.print("Date"); break;
      case DS3231_A1_Day: Serial.println("Day"); break;
    }
    prev_clock_millis = current_Millis;
    print_wakeup_reason();  // for prompt of cause of wake up
    print_GPIO_wake_up();
    if (LED_trigger == true) {
      Serial.println("LED Trigger is true");
    }
  }
}

void onAlarm() {
  // Serial.println("Alarm occured!");
}

void print_wakeup_reason() {  // for debug
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch (wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break;
    default: Serial.printf("Wakeup was not caused by light sleep: %d\n", wakeup_reason); break;
  }
}

void print_GPIO_wake_up() {  // Method to print the GPIO that triggered the wakeup
  uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status();
  Serial.print("GPIO that triggered the wake up: GPIO ");
  Serial.println((log(GPIO_reason)) / log(2), 0);
}

/*
void RFID_info() {
  if (current_Millis - prev_RFID_millis > RFID_time) {
    uint8_t values[8] = { 0 };
    if (tag.getDeviceUID(values)) {
      Serial.print(F("RFID UID: "));
      for (uint8_t i = 0; i < 8; i++) {
        if (values[i] < 0x0a)
          Serial.print(F("0"));
        Serial.print(values[i], HEX);
        Serial.print(F(" "));
      }
    }
    Serial.println(" ");
    prev_RFID_millis = current_Millis;
  }
}
*/

That sounds like a job for a finite state machine. Start by listing all of the possible states, the condition or conditions that would cause a change of state to occur and the destination state in each case

Put the code for each state in a switch/state block in the loop() function and the common code that must run in all states in the loop() function

2 Likes

@UKHeliBob thanks for the suggestion. I'm not familar with "finite state machine". I am familar with the switch/case but don't see how that is a matrix but rather a list of cases...?

Do you have any example that you can refer me to?

thanks

Mark

You can represent a finite state machine with a matrix. Or at least the state-transition part of it.

But, in general, the matrix will be pretty sparse because many events won't happen in all states or will be ignored even if they do.

Using case a statement is usually more flexible and concise.

In this case, the state machine sounds quite simple, so maybe either approach could be used. Try the matrix approach and let us know how you get on.

Here's a code I whipped up for someone here trying to make controls for an aquarium. He had sensors to change the things to do, but I didn't feel like building the whole circuit so i just used the Serial Monitor to simulate sensor inputs by typing in letters. So to @UKHeliBob point, in this sketch it's just the arrival of a new listed char in Serial that provides the condition to tell the state machine to move out of one case and into the next.
So the common code here is simply the Arduino constantly listening in for new chars.
So a statement like this

  switch (mode) {
    case '0':
      modeZero();
      break;
    case '1':
      modeOne();
      break;
    case '2':
      modeTwo();
      break;
  }

might easily be this using a temp sensor where the actual settings are passed to state variables representing some temp range

  switch (tempSetting) {
    case HOT:
      modeZero();
      break;
    case COLD:
      modeOne();
      break;
    case JUSTRIGHT:
      modeTwo();
      break;
  }

You don't have to nest functions in the cases, I just did for reasons.
You can see the effect though by uploading the whole thing and opening your serial monitor at 115200 baud.
Note this was written to accommodate all the possible states an animal might need devices adjusted to (light, fan, water pump) based on two discrete states of each of those devices, mixed.

char mode;
const int light = 3;
const int pump = 4;
const int fan = 5;

void setup() {
  Serial.begin(115200);
  pinMode(light, OUTPUT);
  pinMode(pump, OUTPUT);
  pinMode(fan, OUTPUT);
  Serial.println(F("charDrivenThreeDeviceStateMachine"));
  Serial.println();
  Serial.println(F("Type 0-7 to change functions"));
  Serial.println();
  mode = '0'; // start state variable at mode 0
  modeZero(); // let us know and init devices to off
}

void loop() {
  if (Serial.available() > 0) {
    mode = Serial.read();
    switch (mode) {
      case '0':
        modeZero();
        break;
      case '1':
        modeOne();
        break;
      case '2':
        modeTwo();
        break;
      case '3':
        modeThree();
        break;
      case '4':
        modeFour();
        break;
      case '5':
        modeFive();
        break;
      case '6':
        modeSix();
        break;
      case '7':
        modeSeven();
        break;
    }
  }
}

void modeZero() {
  Serial.println(F("mode 0: light off, pump off, fan off"));
  Serial.println("");
  allOff();
}
void modeOne() {
  Serial.println(F("mode 1: light on, pump on, fan on"));
  Serial.println("");
  allOn();
}
void modeTwo() {
  Serial.println(F("mode 2: light on, pump on, fan off"));
  Serial.println("");
  lightAndPump();
}
void modeThree() {
  Serial.println(F("mode 3: light off, pump on, fan on"));
  Serial.println("");
  fanAndPump();
}
void modeFour() {
  Serial.println(F("mode 4: light on, pump off, fan on"));
  Serial.println("");
  lightAndFan();
}
void modeFive() {
  Serial.println(F("mode 5: light on, pump off, fan off"));
  Serial.println("");
  justLight();
}
void modeSix() {
  Serial.println(F("mode 6: light off, pump on, fan off"));
  Serial.println("");
  justPump();
}
void modeSeven() {
  Serial.println(F("mode 7: light off, pump off, fan on"));
  Serial.println("");
  justFan();
}
void allOff() {
  digitalWrite(light, LOW);
  digitalWrite(pump, LOW);
  digitalWrite(fan, LOW);
}
void allOn() {
  digitalWrite(light, HIGH);
  digitalWrite(pump, HIGH);
  digitalWrite(fan, HIGH);
}
void lightAndPump() {
  digitalWrite(light, HIGH);
  digitalWrite(pump, HIGH);
  digitalWrite(fan, LOW);
}
void fanAndPump() {
  digitalWrite(light, LOW);
  digitalWrite(pump, HIGH);
  digitalWrite(fan, HIGH);
}
void lightAndFan() {
  digitalWrite(light, HIGH);
  digitalWrite(pump, LOW);
  digitalWrite(fan, HIGH);
}
void justLight() {
  digitalWrite(light, HIGH);
  digitalWrite(pump, LOW);
  digitalWrite(fan, LOW);
}
void justPump() {
  digitalWrite(light, LOW);
  digitalWrite(pump, HIGH);
  digitalWrite(fan, LOW);
}
void justFan() {
  digitalWrite(light, LOW);
  digitalWrite(pump, LOW);
  digitalWrite(fan, HIGH);
}

thanks all. As I said, I'm familiar with Switch/case function and its probably the way I'll go. @hallowed31 - thanks for the example. In my case I have 3 different inputs (RTC alarm, Door switch and confirm pill button) that will determine the case. In your example, you're simply using the serial monitor to define the case but in my project, I have a number of configurations of my inputs - i.e. RTC alarm = LOW, door switch = HIGH, pill confirm button = LOW, etc. which would give me at least 9 different cases (and more depending on whether the does not act on them when alarm is fired and need to go into sleep mode after a certain duration). How do I poll the 3 inputs and use their states with the Switch/case function? Wouldn't I first have to define all the cases in a separate function before "feeding" the case to the switch() ?

Mark

1 Like

Don't think of the set of inputs as defining the case, think of the states/modes/outputs of the device at the state/case of the state machine, and one or more of the three inputs are used in in the transitions from one state to another.

IDLE, ALARMING, DELIVERING, DELIVERED, CLOSED, etc., etc...

You don't have to derive the state wholly from the inputs, you can derive it from the previous state plus whatever input is required to transition to another state.

Those two are a single case "time to take pill". The next state is determined by whether the door is opened or not

They too are a single case "pill-confirm button pushed". The next state to move to is determined by whether the door is currently open or closed

Yeah, my disclaimer said I just used Serial for inputs since it wasn't my project and I didn't feel like building the circuit :joy:

You poll the inputs outside of the switch case and nest switch case structures within other switch case structures. Or sometimes it's easier to if/else inside cases.

The thing I posted has a bunch of output functions at the bottom for three devices. Basically you're just reversing what I have there and instead of waiting for Serial inputs, you use those to set your outputs

@hallowed31 - yes, this is what I suspected. As you can see in my current code I have "read_inputs() in the main loop and I assign a "state" to those inputs - HIGH or LOW. I understand that I can test there state inside the Switch function...

I still thought that there would be an alternative to the Switch/case function for doing this - something along the lines of a "decision matrix" or 2x2 array that could make things more structured...

Mark

You could serialize a matrix of buttons into a single variable for use with a switch-case with something like:

int combinedButtonState = buttonState2 << 2 | buttonState1 << 1 | buttonState0 << 0;

switch (combinedButtonState){
     case 0b000: break;
     case 0b001: break;
     case 0b010: break;
     case 0b011: break;
     case 0b100: break;
     case 0b101: break;
     case 0b110: break;
     case 0b111: break;
   }

Edit: Or maybe use a matrix of functions:

int test1() {
  return 1;
}
int test2() {
  return 2;
}
int test3() {
  return 3;
}
int test4() {
  return 4;
}

int (*(decisionMatrix[2][2]))() = {
  {test1, test2},
  {test3, test4}
};

void setup() {
  Serial.begin(115200);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
}

void loop() {
  Serial.print(decisionMatrix[digitalRead(2)][digitalRead(3)]());
  delay(200);
}

Maybe, let me know if you work it out. I'm just a guy on a forum trying to help

1 Like

Hello mbiasotti

The magic word is "structured".

Design and code a structured array to get a "decision matrix".

Take a view to this C++ example:

void task0()
{
  Serial.println(__func__);
}
void task1()
{
  Serial.println(__func__);
}
void task2()
{
  Serial.println(__func__);
}
void task3()
{
  Serial.println(__func__);
}
struct TASK
{
  void (*task)();
};
TASK myMatrix[2][2]
{
  { task0, task1},
  { task2, task3},
};
void setup()
{
  Serial.begin(115200);
  myMatrix[0][0].task();
  myMatrix[0][1].task();
  myMatrix[1][0].task();
  myMatrix[1][1].task();
}
void loop()
{
}


hth

Have a nice day and enjoy coding in C++.

2 Likes

@DaveX - I like this approach thanks!

Okay, thanks everyone for your suggestions. I think @DaveX suggestion is the best way forward for what I need to accomplish: (substituing RTC alarm with power button)

#include <ezButton.h>

int const lid_sw = A1;  //reed switch   all buttons pulled UP via 10K resistor
ezButton pill_confirm_but(4);
ezButton pwr_but(A2);

bool lid_sw_state = false;
bool pill_confirm_state = false;
bool pwr_but_state = false;

int curr_button_state;

void read_inputs() {
  int lid_sw_val = digitalRead(lid_sw);
  if (lid_sw_val == LOW)
    lid_sw_state = true;
  else
    lid_sw_state = false;

  pill_confirm_but.loop();
  if (pill_confirm_but.isReleased())
    pill_confirm_state = true;
  else
    pill_confirm_state = false;
  pwr_but.loop();

  if (pwr_but.isReleased())
    pwr_but_state = true;
  else
    pwr_but_state = false;

  curr_button_state = lid_sw_state << 2 | pill_confirm_state << 1 | pwr_but_state << 0;
}

void setup() {
  Serial.begin(115200);
  pinMode(lid_sw, INPUT);
}

void loop() {
  read_inputs();
  switch (curr_button_state) {
  case 0b000:
    Serial.println(" no buttons pushed");
  break;
  case 0b001:
    Serial.println(" Pwr button pushed only");
  break;
  case 0b010:
    Serial.println(" pill confirm button pushed only");
  break;
  case 0b100:
    Serial.println(" lid opened only");
  break;
  case 0b101:
    Serial.println(" lid opened and pwr button pushed");
  break;
  case 0b110:
    Serial.println(" lid opened and pill_confirm pushed");
  break;
  case 0b111:
    Serial.println(" lid opened, pill_confirm pushed and pwr_button pushed");
  break;
  }
  delay(200);
}

If it is just printing strings, you could put all the strings into an array and refer to them with a Serial.println(messageArray[curr_button_state])

Your code looks like it has a lot of "slapped together" parts which isn't necessarily bad but leaves me wondering how well you know it.

Decision matrix is not as good as decision tree which a state machine can do cleanly, easier to read and work on.

Listen to those guys!
You need to stop yourself and do a small side project you KEEP small, just learn what a state machine does with a MINIMAL example to understand how state machine works without a load of details confusing that so whatever you do, DO NOT make it the big thing you have going now, only do a little side project to see what you need... and then apply that to what you want.

You should end up using code you have in a completely re-arranged way that you planned out ahead of any writing, not grow a mess that you don't more than guess should work;

and the reason is to save yourself days or weeks of time!

Hard part will be setting you current effort down, stopping that train from going round. Deadlines are imaginary once you pass them anyway, now ones more-so! Save yourself.

1 Like

LOL!

I once did something similar but different, teacher was all up my arm about inefficiency and stuff. But it did mean my loop evaluating a polynomial was very clear about what it was doing and how.

And trust me, I don't, but technically one should

int lid_sw_val = (digitalRead(lid_sw) == LOW) ? 0 : 1;

digitalRead() returns the mysterious LOW and HIGH, so this is what TPTB have stuck us with.

a7

1 Like

Yes - that is why I reached out to you all with more experience. I knew it was going down the wrong path and that’s why I’m going to rip it apart and do something more structure like what has been suggested with the switch/case instead of what I have currently with multiple function in the loop with nested if statements.

Thanks

Mark

1 Like

The switch-case thing I posted computes the case directly from the inputs.

It would probably be good to learn the state-machine use of a switch-case statement because you can often simplify problems considerably using the knowledge of the past.

If you try to compute the state from the inputs, adding inputs becomes combinatorially complex and hard to manage and maintain. With a state machine, you can simplify large sections of the input variable space, easily ignoring all the inputs that do not matter under certain conditions. Using a persistent state variable, it is easy to: