diff options
Diffstat (limited to 'remihap/remihap.ino')
| -rw-r--r-- | remihap/remihap.ino | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/remihap/remihap.ino b/remihap/remihap.ino new file mode 100644 index 0000000..ad12923 --- /dev/null +++ b/remihap/remihap.ino @@ -0,0 +1,444 @@ +#include <Keypad.h> +#include <LiquidCrystal.h> +#include <Adafruit_NeoPixel.h> + +// LCD + +#define LCD_ROWS 2 +#define LCD_COLS 16 + +#define MSG_ERR_R1 " Error! " +#define ERROR_VISIBILITY_TIMEOUT 2000 + +#define MSG_ERR_DAY " Invalid day " +#define MSG_ERR_HOUR " Invalid hour " +#define MSG_ERR_MIN "Invalid minutes " +#define MSG_ERR_DIS "Invalid dispensr" + +/* Alarm menu */ + +#define MSG_NO_ALRM_R1 " No alarms " +#define MSG_NO_ALRM_R2 " #: Create new " + +#define MSG_EDIT_TOOLS "# A B C D " +#define MSG_HOUR " Hour(0-23) #" +#define MSG_MINUTES " Minutes(0-59) #" +#define MSG_DISPENSER " Dispenser #" +#define MSG_CONFIRM "Sure? Yes:# No:C" + +/* Week menu */ + +#define MSG_DAY_NUM "Day number(1-7) " +#define MSG_WEEK_TOGGLE " A - toggle " + +/* Main menu */ + +#define MSG_MAIN_R1 " A: Week days " +#define MSG_MAIN_R2 " B: Alarms " + +// Keypad + +#define KEYPAD_ROWS 4 +#define KEYPAD_COLS 4 + +// Alarms + +#define ALARMS_PER_DAY 10 +#define WEEK_LENGTH 7 + +//Defining the notes' frequencies for the melody +#define NOTE_C5 523 +#define NOTE_D5 587 +#define NOTE_E5 659 + +//Time in millisecconds between first and second alarm +#define interval 10000 + +#define ALARM_PIN 11 +#define BTN_PIN 12 +#define DISPENSER_PIN 10 + +int i, j; //variables for loops + +/* + * --------------------------------------------- +*/ + +// +// LCD +// + +LiquidCrystal lcd(A0, A1, A2, A3, A4, A5); + +void printToLCD(int rowID, char message[]) +{ + lcd.setCursor(0, rowID); + + for(i = 0; i < LCD_COLS; i++) + { + lcd.write(message[i]); + delay(1); //TinkerCad optimization + } +} + +void printErrorToLCD(char message[]) +{ + printToLCD(0, MSG_ERR_R1); + printToLCD(1, message); + + delay(ERROR_VISIBILITY_TIMEOUT); +} + +// +// Keypad +// + +char keys[KEYPAD_ROWS][KEYPAD_COLS] = +{ + {'1','2','3','A'}, + {'4','5','6','B'}, + {'7','8','9','C'}, + {'*','0','#','D'} +}; +byte rowPins[KEYPAD_ROWS] = {13, 12, 7, 6}; +byte colPins[KEYPAD_COLS] = {5, 4, 3, 2}; + +Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, KEYPAD_ROWS, KEYPAD_COLS); + +// +// Days of week +// + +char weekLetters[] = {' ', 'M', 'T', 'W', 'H', 'F', 'S', 'U'}; + +int toggleDayIndex; + +void toggleWeekDayEnable() +{ + weekLetters[toggleDayIndex] = (isupper(weekLetters[toggleDayIndex])) + ? tolower(weekLetters[toggleDayIndex]) + : toupper(weekLetters[toggleDayIndex]); +} + +// +// Alarms +// + +class TAlarm +{ + public: + char *day, hour, minutes, dispenserId = -1; +}; + +TAlarm alarms[WEEK_LENGTH * ALARMS_PER_DAY]; + +int firstEmptyAlarmIndex() +{ + for(i = 0; i < sizeof(alarms)/sizeof(*alarms); i++) + { + if (alarms[i].dispenserId == -1) break; + } + return (i == sizeof(alarms)/sizeof(*alarms)) ? -1 : i; +} + +int nextAlarmIndex(int currIndex) +{ + for(i = currIndex + 1; i < sizeof(alarms)/sizeof(*alarms); i++) + { + if (alarms[i].dispenserId != -1) break; + } + return (i == sizeof(alarms)/sizeof(*alarms)) ? -1 : i; +} + +int prevAlarmIndex(int currIndex) +{ + for(i = currIndex - 1; i >= 0; i--) + { + if (alarms[i].dispenserId != -1) break; + } + return i; +} + +/* Alarming */ + +bool buttonIsPressed() +{ + return digitalRead(BTN_PIN); +} + +void alarm(unsigned long timeSinceActivation) +{ + while(buttonIsPressed()) + { + digitalWrite(ALARM_PIN, HIGH); + delay(100); + } + digitalWrite(ALARM_PIN, LOW); +} + +/* Dispensing */ + +void dispensePill(char dispenserNumber) { + digitalWrite(DISPENSER_PIN, HIGH); + delayMicroseconds(dispenserNumber * 1000); + digitalWrite(DISPENSER_PIN, LOW); +} + +// +// Menus +// + +void (*menu)(char); +bool confirm; + +int alarmIndex = -1; + +/* Alarm edit submenu */ + +char formatted[LCD_COLS + 1]; + +enum awaitingValue +{ + awaitingNothing = -1, awaitingDay, awaitingHour, awaitingMinutes, awaitingDispenser, awaitingDeletion +}; + +#define AV_CONFIRM 0 +#define AV_AWAITING 1 +#define AV_VALUE 2 + +char awaitVal[3] = {false, awaitingNothing, -1}; + +void awaitValReset() +{ + awaitVal[AV_CONFIRM] = false, awaitVal[AV_AWAITING] = awaitingNothing, awaitVal[AV_VALUE] = -1; +} + +bool validateValue(int value) +{ + switch(awaitVal[AV_AWAITING]) + { + case awaitingDay: return value >= 1 && value <= WEEK_LENGTH; + case awaitingHour: return value >= 0 && value <= 23; + case awaitingMinutes: return value >= 0 && value <= 59; + case awaitingDispenser:return value >= 0; + } +} + +void setAVNum(char key) +{ + if (validateValue((awaitVal[AV_VALUE] * 10) + (key - '0'))) + { + awaitVal[AV_VALUE] *= 10; + awaitVal[AV_VALUE] += key - '0'; + editingSubMenu('#'); + } + else if (awaitVal[AV_VALUE] < 0 && validateValue(key - '0')) + { + awaitVal[AV_VALUE] = key - '0'; + } + else + { + switch(awaitVal[AV_AWAITING]) + { + case awaitingDay: printErrorToLCD(MSG_ERR_DAY); break; + case awaitingHour: printErrorToLCD(MSG_ERR_HOUR); break; + case awaitingMinutes: printErrorToLCD(MSG_ERR_MIN); break; + case awaitingDispenser: printErrorToLCD(MSG_ERR_DIS); break; + } + + awaitValReset(); + alarmMenu('~'); editingSubMenu('~'); + } +} + +void editingSubMenu(char key) +{ + switch(key) + { + case 'A': awaitVal[AV_AWAITING] = awaitingDay; printToLCD(1, MSG_DAY_NUM); + break; + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '0': + if (awaitVal[AV_AWAITING] != awaitingNothing) setAVNum(key); + if (awaitVal[AV_AWAITING] == awaitingDay) editingSubMenu('#'); + + break; + case 'B': awaitVal[AV_AWAITING] = awaitingHour; printToLCD(1, MSG_HOUR); break; + case 'C': + if (awaitVal[AV_CONFIRM]) + { + editingSubMenu('*'); + } + else + { + awaitVal[AV_AWAITING] = awaitingMinutes; + printToLCD(1, MSG_MINUTES); + } + + break; + case 'D': awaitVal[AV_AWAITING] = awaitingDispenser; printToLCD(1, MSG_DISPENSER); break; + case '#': + if (awaitVal[AV_AWAITING] == awaitingNothing) + { + awaitVal[AV_AWAITING] = awaitingDeletion; + } + + if (awaitVal[AV_CONFIRM]) + { + switch(awaitVal[AV_AWAITING]) + { + case awaitingDay: alarms[alarmIndex].day = &weekLetters[awaitVal[AV_VALUE]]; break; + case awaitingHour: alarms[alarmIndex].hour = awaitVal[AV_VALUE]; break; + case awaitingMinutes: alarms[alarmIndex].minutes = awaitVal[AV_VALUE]; break; + case awaitingDeletion: + case awaitingDispenser: alarms[alarmIndex].dispenserId = awaitVal[AV_VALUE]; break; + } + awaitValReset(); + editingSubMenu('*'); + } + else + { + awaitVal[AV_CONFIRM] = true; + printToLCD(1, MSG_CONFIRM); + } + + break; + case '*': awaitValReset(); menu = alarmMenu; menu('~'); break; + case '~': printToLCD(1, MSG_EDIT_TOOLS); break; + } +} + +/* Alarm menu */ + +void printAlarmMenu() +{ + if (alarmIndex < 0) + { + printToLCD(0, MSG_NO_ALRM_R1); + printToLCD(1, MSG_NO_ALRM_R2); + } + else + { + snprintf(formatted, LCD_COLS + 1, "%02i %c %02i:%02i %02i ", alarmIndex, *alarms[alarmIndex].day, alarms[alarmIndex].hour, alarms[alarmIndex].minutes, alarms[alarmIndex].dispenserId); + printToLCD(0, formatted); + + snprintf(formatted, LCD_COLS + 1, "%c C %c", (prevAlarmIndex(alarmIndex) >= 0) ? 'A' : 'a' , (alarmIndex < nextAlarmIndex(alarmIndex)) ? 'B' : 'b'); + printToLCD(1, formatted); + } +} + +void alarmMenu(char key) +{ + switch(key) + { + case 'A': + if (prevAlarmIndex(alarmIndex) >= 0) + { + alarmIndex--; + printAlarmMenu(); + } + + break; + case 'B': + if (alarmIndex < nextAlarmIndex(alarmIndex)) + { + alarmIndex++; + printAlarmMenu(); + } + + break; + case 'C': menu = editingSubMenu; menu('~'); break; + case '#': + alarmIndex = firstEmptyAlarmIndex(); + menu = editingSubMenu; + alarmMenu('~'); menu('D'); + break; + case '*': alarmIndex = 0; menu = mainMenu; menu('~'); break; + case '~': printAlarmMenu(); break; + } +} + +/* Week menu */ + +bool waitForDayNum; + +void printWeekMenu() +{ + sprintf(formatted," %c %c %c %c %c %c %c ", weekLetters[1], weekLetters[2], weekLetters[3], weekLetters[4], weekLetters[5], weekLetters[6], weekLetters[7]); + printToLCD(0, formatted); + + if (waitForDayNum) printToLCD(1, MSG_DAY_NUM); + else if (confirm) printToLCD(1, MSG_CONFIRM); + else printToLCD(1, MSG_WEEK_TOGGLE); +} + +void weekMenu(char key) +{ + switch(key) + { + case 'A': waitForDayNum = true; weekMenu('~'); break; + case 'C': confirm = false; weekMenu('~'); break; + case '#': + if (confirm) + { + toggleWeekDayEnable(); + confirm = false; + } + weekMenu('~'); + + break; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + if (waitForDayNum) + { + toggleDayIndex = key - '0'; + waitForDayNum = false; confirm = true; + } + weekMenu('~'); + + break; + case '*': menu = mainMenu; menu('~'); break; + case '~': printWeekMenu(); break; + } +} + +/* Main menu */ + +void mainMenu(char key) +{ + switch(key) + { + case 'A': menu = weekMenu; weekMenu('~'); break; + case 'B': menu = alarmMenu; alarmMenu('~'); break; + default: printToLCD(0, MSG_MAIN_R1); printToLCD(1, MSG_MAIN_R2); break; + } +} + +// +// Main logic +// + +void setup() +{ + lcd.begin(LCD_COLS, LCD_ROWS); + + menu = mainMenu; + mainMenu('~'); +} + +char key; + +void loop() +{ + key = keypad.getKey(); // getKey function is too quick, once you get the key, the second time you get it it will return null + if (key != '\0') + { + menu(key); + } + else if(buttonIsPressed()) //TODO: if button is pressed AND time for alarm + { + dispensePill(3); // dispense correct pill + alarm(millis()); + } + delay(100); //TinkerCad optimization +} |
