673 lines
23 KiB
C++
673 lines
23 KiB
C++
|
//#define DEBUG /* Uncomment this line to enable Serial debug output. */
|
||
|
|
||
|
#include <AnalogButtons.h>
|
||
|
#define USE_STANDARD_LCD
|
||
|
#include <LcdMenu.h>
|
||
|
#include <ThreeWire.h>
|
||
|
#include <RtcDS1302.h>
|
||
|
#ifdef DEBUG
|
||
|
#define DHT_DEBUG
|
||
|
#endif
|
||
|
#include <DHT.h>
|
||
|
#include <toneAC.h> /* This means only pin 9 and 10 can be used by the buzzer. */
|
||
|
#include "DebugUtil.h"
|
||
|
|
||
|
/* Command key for each button. This signifies which button is currently pressed. */
|
||
|
#define CMD_IGNORE 0 /* This means no button is pressed. */
|
||
|
#define CMD_ENTER 1 /* Enter button is pressed. */
|
||
|
#define CMD_BACK 2 /* Back button is pressed. */
|
||
|
#define CMD_UP 3 /* Up button is pressed. */
|
||
|
#define CMD_DOWN 4 /* Down button is pressed. */
|
||
|
#define CMD_LEFT 5 /* Left button is pressed. */
|
||
|
#define CMD_RIGHT 6 /* Right button is pressed. */
|
||
|
|
||
|
#define SCREEN_CLOCK 0 /* Currently showing clock. */
|
||
|
#define SCREEN_MENU 1 /* Currently in the menu system. */
|
||
|
#define SCREEN_SET_TIME 2 /* Currently executing menu item "Set Time". */
|
||
|
#define SCREEN_SET_ALARM_TIME 3 /* Currently executing menu item "Set Alarm Time". */
|
||
|
|
||
|
#define SCREEN_TRANSITION 250 /* Screen transition delay in millisecond. */
|
||
|
|
||
|
/* Edit positions. Used in SCREEN_SET_TIME and SCREEN_SET_ALARM_TIME. */
|
||
|
#define EDIT_YEAR 0 /* Editing year. */
|
||
|
#define EDIT_MONTH 1 /* Editing month. */
|
||
|
#define EDIT_DAY 2 /* Editing day of month.*/
|
||
|
#define EDIT_HOUR 3 /* Editing hour. */
|
||
|
#define EDIT_MINUTE 4 /* Editing minute. */
|
||
|
#define EDIT_SECOND 5 /* Editing second. */
|
||
|
|
||
|
void cbBtnEnter();
|
||
|
void cbBtnLeft();
|
||
|
void cbBtnUp();
|
||
|
void cbBtnDown();
|
||
|
void cbBtnRight();
|
||
|
void cbBtnBack();
|
||
|
void cbBtnNone();
|
||
|
|
||
|
void cbMenuSetTime();
|
||
|
void cbMenuToggleAlarm(uint8_t);
|
||
|
void cbMenuSetAlarm();
|
||
|
void cbMenuSetAlarmDuration(uint8_t);
|
||
|
|
||
|
void screenClock();
|
||
|
void screenMenu();
|
||
|
void screenSetTime();
|
||
|
void screenSetAlarmTime();
|
||
|
void alarm();
|
||
|
void resetAlarmRun();
|
||
|
|
||
|
void showClock(const RtcDateTime&, uint8_t, uint8_t, uint8_t, uint8_t);
|
||
|
void showDHT();
|
||
|
void showSetAlarmTime(uint8_t, uint8_t);
|
||
|
|
||
|
#define MAD_ALARM_ENABLE (uint8_t) 0 /* RTC memory address for "Alarm Enable/Disable" flag. */
|
||
|
#define MAD_ALARM_HOUR (uint8_t) 1 /* RTC memory address for "Alarm Time (Hour)". */
|
||
|
#define MAD_ALARM_MINUTE (uint8_t) 2 /* RTC memory address for "Alarm Time (Minute)". */
|
||
|
#define MAD_ALARM_DURATION (uint8_t) 3 /* RTC memory address for "Alarm Duration" (in minute). */
|
||
|
|
||
|
void restoreAlarm();
|
||
|
|
||
|
uint8_t rollOver(uint8_t, uint8_t, uint8_t, boolean);
|
||
|
|
||
|
#define BTN_HOLD_DURATION 250 /* Button hold duration in millisecond. */
|
||
|
#define BTN_HOLD_INTERVAL 100 /* Button hold interval in millisecond. */
|
||
|
|
||
|
AnalogButtons analogButtons(PIN_A0, INPUT, 1, 26);
|
||
|
Button btnEnter = Button(883, &cbBtnEnter, NULL, BTN_HOLD_DURATION, BTN_HOLD_INTERVAL);
|
||
|
Button btnLeft = Button(738, &cbBtnLeft , NULL, BTN_HOLD_DURATION, BTN_HOLD_INTERVAL);
|
||
|
Button btnUp = Button(584, &cbBtnUp , NULL, BTN_HOLD_DURATION, BTN_HOLD_INTERVAL);
|
||
|
Button btnDown = Button(439, &cbBtnDown , NULL, BTN_HOLD_DURATION, BTN_HOLD_INTERVAL);
|
||
|
Button btnRight = Button(293, &cbBtnRight, NULL, BTN_HOLD_DURATION, BTN_HOLD_INTERVAL);
|
||
|
Button btnBack = Button(144, &cbBtnBack , NULL, BTN_HOLD_DURATION, BTN_HOLD_INTERVAL);
|
||
|
|
||
|
/* alarmOn and alarmOff is only to prevent the compiler warning: ISO C++ forbids converting a string constant to 'char*' */
|
||
|
char alarmOn[] = "Yes";
|
||
|
char alarmOff[] = "No";
|
||
|
String alarmRunStr[] = { "1m", "5m", "10m", "15m" }; /* Array of allowable alarm runs (displayed in menu). */
|
||
|
uint8_t alarmRunMin[] = { 1, 5, 10, 15 }; /* Array of allowable alarm runs (actual minutes). */
|
||
|
|
||
|
MenuItem mainMenu[] = {
|
||
|
ItemHeader(),
|
||
|
ItemCommand("Date/Time", cbMenuSetTime),
|
||
|
ItemToggle("Alarm", alarmOn, alarmOff, cbMenuToggleAlarm),
|
||
|
ItemCommand("Alarm Time", cbMenuSetAlarm),
|
||
|
ItemList("Alarm Run", alarmRunStr, 4, cbMenuSetAlarmDuration),
|
||
|
ItemFooter()
|
||
|
};
|
||
|
|
||
|
#define LCD_ROWS 2
|
||
|
#define LCD_COLS 16
|
||
|
|
||
|
LcdMenu menu(LCD_ROWS, LCD_COLS);
|
||
|
ThreeWire rtcWire(7, 8, 6); // DAT, CLK, RST
|
||
|
RtcDS1302<ThreeWire> rtc(rtcWire);
|
||
|
RtcDateTime clockTime; /* The currently displayed clock time. */
|
||
|
uint8_t prevSec = 61; /* Pick a value that's not 0 to 60. */
|
||
|
uint8_t cmdKey = CMD_IGNORE; /* Which button is currently pressed. */
|
||
|
uint8_t currScreen = SCREEN_CLOCK; /* Which screen is currenly active. */
|
||
|
boolean editTimeInit = false; /* Is editTime already initialized? */
|
||
|
RtcDateTime editTime; /* The time shown when in SCREEN_SET_TIME and SCREEN_SET_ALARM_TIME mode. */
|
||
|
boolean alarmActive = false; /* Whether the alarm is currently sounding or not. */
|
||
|
unsigned long alarmActiveStart = 0; /* Record the millis() start of alarmActive. */
|
||
|
boolean alarmEnabled = false; /* Trigger the alarm when the alarmHour and alarmMinute is reached. */
|
||
|
uint8_t alarmHour = 0; /* Alarm time hour. */
|
||
|
uint8_t alarmMinute = 0; /* Alarm time minute. */
|
||
|
uint8_t alarmRunIdx = 0; /* Index of alarmRunMin. */
|
||
|
uint8_t editPos = EDIT_YEAR; /* Which field is being edited. */
|
||
|
boolean editChanged = false; /* If time has been edited, this is the flag to allow repainting. */
|
||
|
LiquidCrystal* lcd = NULL; /* Hold reference to menu.lcd .*/
|
||
|
|
||
|
#define DHT11_INIT 255 /* Value outside DHT11 range (and also not NaN) to signify "please read the sensor value". */
|
||
|
|
||
|
DHT dht(PIN_A2, DHT11);
|
||
|
float temperature = DHT11_INIT;
|
||
|
float humidity = DHT11_INIT;
|
||
|
|
||
|
void setup() {
|
||
|
#ifdef DEBUG
|
||
|
Serial.begin(115200, SERIAL_8N1);
|
||
|
#endif
|
||
|
|
||
|
menu.setupLcdWithMenu(12, 11, 5, 4, 3, 2, mainMenu);
|
||
|
menu.hide();
|
||
|
lcd = menu.lcd;
|
||
|
|
||
|
rtc.Begin();
|
||
|
if (rtc.GetIsWriteProtected()) {
|
||
|
rtc.SetIsWriteProtected(false);
|
||
|
}
|
||
|
if (!rtc.GetIsRunning()) {
|
||
|
rtc.SetIsRunning(true);
|
||
|
}
|
||
|
|
||
|
restoreAlarm();
|
||
|
|
||
|
dht.begin();
|
||
|
|
||
|
analogButtons.add(btnEnter);
|
||
|
analogButtons.add(btnLeft);
|
||
|
analogButtons.add(btnUp);
|
||
|
analogButtons.add(btnDown);
|
||
|
analogButtons.add(btnRight);
|
||
|
analogButtons.add(btnBack);
|
||
|
}
|
||
|
|
||
|
void loop() {
|
||
|
clockTime = rtc.GetDateTime(); /* Used by the clock display and the alarm trigger. */
|
||
|
analogButtons.check();
|
||
|
if (currScreen == SCREEN_CLOCK) {
|
||
|
screenClock();
|
||
|
} else if (currScreen == SCREEN_MENU) {
|
||
|
screenMenu();
|
||
|
} else if (currScreen == SCREEN_SET_TIME) {
|
||
|
screenSetTime();
|
||
|
} else if (currScreen == SCREEN_SET_ALARM_TIME) {
|
||
|
screenSetAlarmTime();
|
||
|
}
|
||
|
alarm();
|
||
|
cmdKey = CMD_IGNORE; /* Must be reset at the end of each loop to prevent button press "lock". */
|
||
|
}
|
||
|
|
||
|
/* Callback when the user pressed the ENTER button. */
|
||
|
void cbBtnEnter() {
|
||
|
cmdKey = CMD_ENTER;
|
||
|
DEBUG_PRINTLN(F("Pressed ENTER"));
|
||
|
}
|
||
|
|
||
|
/* Callback when the user pressed the LEFT button. */
|
||
|
void cbBtnLeft() {
|
||
|
cmdKey = CMD_LEFT;
|
||
|
DEBUG_PRINTLN(F("Pressed LEFT"));
|
||
|
}
|
||
|
|
||
|
/* Callback when the user pressed the UP button. */
|
||
|
void cbBtnUp() {
|
||
|
cmdKey = CMD_UP;
|
||
|
DEBUG_PRINTLN(F("Pressed UP"));
|
||
|
}
|
||
|
|
||
|
/* Callback when the user pressed the DOWN button. */
|
||
|
void cbBtnDown() {
|
||
|
cmdKey = CMD_DOWN;
|
||
|
DEBUG_PRINTLN(F("Pressed DOWN"));
|
||
|
}
|
||
|
|
||
|
/* Callback when the user pressed the RIGHT button. */
|
||
|
void cbBtnRight() {
|
||
|
cmdKey = CMD_RIGHT;
|
||
|
DEBUG_PRINTLN(F("Pressed RIGHT"));
|
||
|
}
|
||
|
|
||
|
/* Callback when the user pressed the BACK button. */
|
||
|
void cbBtnBack() {
|
||
|
cmdKey = CMD_BACK;
|
||
|
DEBUG_PRINTLN(F("Pressed BACK"));
|
||
|
}
|
||
|
|
||
|
/* Callback when the user activates the "Set Time" menu item. */
|
||
|
void cbMenuSetTime() {
|
||
|
menu.hide();
|
||
|
currScreen = SCREEN_SET_TIME;
|
||
|
delay(SCREEN_TRANSITION);
|
||
|
}
|
||
|
|
||
|
/* Callback when the user toggles the "Alarm" enable/disable menu item. */
|
||
|
void cbMenuToggleAlarm(const uint8_t yesNo) {
|
||
|
alarmEnabled = yesNo == (uint8_t) 1;
|
||
|
rtc.SetMemory(MAD_ALARM_ENABLE, alarmEnabled ? (uint8_t) 1 : (uint8_t) 0);
|
||
|
/* If the alarm is currently sounding, disabling alarm will stop it. */
|
||
|
if (!alarmEnabled && alarmActive) {
|
||
|
alarmActive = false;
|
||
|
noToneAC();
|
||
|
DEBUG_PRINTLN(F("Alarm stopped"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Callback when the user activates the "Set Alarm Time" menu item. */
|
||
|
void cbMenuSetAlarm() {
|
||
|
menu.hide();
|
||
|
currScreen = SCREEN_SET_ALARM_TIME;
|
||
|
delay(SCREEN_TRANSITION);
|
||
|
}
|
||
|
|
||
|
/* Callback when the user commits (pressed ENTER) the alarm duration in the menu system. */
|
||
|
void cbMenuSetAlarmDuration(const uint8_t pos) {
|
||
|
alarmRunIdx = pos;
|
||
|
rtc.SetMemory(MAD_ALARM_DURATION, alarmRunIdx);
|
||
|
|
||
|
DEBUG_PRINT(F("Alarm duration pos = "));
|
||
|
DEBUG_PRINT(pos);
|
||
|
DEBUG_PRINT(F(", str = "));
|
||
|
DEBUG_PRINTLN(alarmRunStr[pos]);
|
||
|
}
|
||
|
|
||
|
/* The clock screen. This displays the current date/time and DHT sensor readings. */
|
||
|
void screenClock() {
|
||
|
if (cmdKey == CMD_ENTER) {
|
||
|
currScreen = SCREEN_MENU;
|
||
|
lcd->clear(); /* Only necessary to make the transition uniform. */
|
||
|
delay(SCREEN_TRANSITION);
|
||
|
menu.show();
|
||
|
return;
|
||
|
} else if (cmdKey == CMD_BACK) {
|
||
|
if (alarmActive) {
|
||
|
alarmActive = false;
|
||
|
noToneAC();
|
||
|
DEBUG_PRINTLN(F("Alarm stopped"));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (clockTime.IsValid()) {
|
||
|
const uint8_t sec = clockTime.Second();
|
||
|
if (sec != prevSec) { /* Only need to redraw when something changed. */
|
||
|
prevSec = sec;
|
||
|
showClock(clockTime, 0, 0, 0, 1);
|
||
|
}
|
||
|
} else {
|
||
|
lcd->setCursor(0, 0);
|
||
|
lcd->print(F("RTC err "));
|
||
|
lcd->setCursor(0, 1);
|
||
|
lcd->print(F("Check batt "));
|
||
|
}
|
||
|
showDHT();
|
||
|
}
|
||
|
|
||
|
/* The menu screen. */
|
||
|
void screenMenu() {
|
||
|
if (cmdKey == CMD_ENTER) {
|
||
|
DEBUG_PRINTLN(F("Menu ENTER"));
|
||
|
menu.enter();
|
||
|
} else if (cmdKey == CMD_BACK) {
|
||
|
DEBUG_PRINTLN(F("Menu BACK"));
|
||
|
resetAlarmRun();
|
||
|
menu.hide();
|
||
|
temperature = DHT11_INIT; /* If not reset, it will not be redrawn. */
|
||
|
humidity = DHT11_INIT; /* If not reset, it will not be redrawn. */
|
||
|
currScreen = SCREEN_CLOCK;
|
||
|
delay(SCREEN_TRANSITION);
|
||
|
} else if (cmdKey == CMD_UP) {
|
||
|
DEBUG_PRINTLN(F("Menu UP"));
|
||
|
resetAlarmRun();
|
||
|
menu.up();
|
||
|
} else if (cmdKey == CMD_DOWN) {
|
||
|
DEBUG_PRINTLN(F("Menu DOWN"));
|
||
|
resetAlarmRun();
|
||
|
menu.down();
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
DEBUG_PRINTLN(F("Menu LEFT"));
|
||
|
menu.left();
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
DEBUG_PRINTLN(F("Menu RIGHT"));
|
||
|
menu.right();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The "Set Time" screen. */
|
||
|
void screenSetTime() {
|
||
|
if (cmdKey == CMD_ENTER) { /* Apply date/time adjustments. */
|
||
|
rtc.SetDateTime(editTime);
|
||
|
}
|
||
|
if (cmdKey == CMD_ENTER || cmdKey == CMD_BACK) { /* Exit this screen. */
|
||
|
lcd->noBlink();
|
||
|
editTimeInit = false;
|
||
|
editChanged = false;
|
||
|
currScreen = SCREEN_MENU;
|
||
|
lcd->clear(); /* Only necessary to make the transition uniform. */
|
||
|
delay(SCREEN_TRANSITION);
|
||
|
menu.show();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
boolean posChanged = false;
|
||
|
if (!editTimeInit) {
|
||
|
editTimeInit = true;
|
||
|
editTime = clockTime;
|
||
|
lcd->blink();
|
||
|
editChanged = true;
|
||
|
editPos = EDIT_YEAR;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
|
||
|
if (editPos == EDIT_YEAR) {
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
const uint8_t year = rollOver(editTime.Year() - c_OriginYear, 0, 99, cmdKey == CMD_UP);
|
||
|
editTime = RtcDateTime(year, editTime.Month(), editTime.Day(), editTime.Hour(), editTime.Minute(), editTime.Second());
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
editPos = EDIT_SECOND;
|
||
|
posChanged = true;
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_MONTH;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
} else if (editPos == EDIT_MONTH) {
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
const uint8_t month = rollOver(editTime.Month(), 1, 12, cmdKey == CMD_UP);
|
||
|
editTime = RtcDateTime(editTime.Year(), month, editTime.Day(), editTime.Hour(), editTime.Minute(), editTime.Second());
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
editPos = EDIT_YEAR;
|
||
|
posChanged = true;
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_DAY;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
} else if (editPos == EDIT_DAY) {
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
const uint8_t dayOfMonth = rollOver(editTime.Day(), 1, 31, cmdKey == CMD_UP);
|
||
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), dayOfMonth, editTime.Hour(), editTime.Minute(), editTime.Second());
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
editPos = EDIT_MONTH;
|
||
|
posChanged = true;
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_HOUR;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
} else if (editPos == EDIT_HOUR) {
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
const uint8_t hour = rollOver(editTime.Hour(), 0, 23, cmdKey == CMD_UP);
|
||
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), editTime.Day(), hour, editTime.Minute(), editTime.Second());
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
editPos = EDIT_DAY;
|
||
|
posChanged = true;
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_MINUTE;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
} else if (editPos == EDIT_MINUTE) {
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
const uint8_t minute = rollOver(editTime.Minute(), 0, 59, cmdKey == CMD_UP);
|
||
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), editTime.Day(), editTime.Hour(), minute, editTime.Second());
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
editPos = EDIT_HOUR;
|
||
|
posChanged = true;
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_SECOND;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
} else { // editPos == EDIT_SECOND
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
const uint8_t second = rollOver(editTime.Second(), 0, 59, cmdKey == CMD_UP);
|
||
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), editTime.Day(), editTime.Hour(), editTime.Minute(), second);
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT) {
|
||
|
editPos = EDIT_MINUTE;
|
||
|
posChanged = true;
|
||
|
} else if (cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_YEAR;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (editChanged) { /* In the "Set Time" screen, the LCD only need to be updated when something has been changed. */
|
||
|
uint8_t dayMax = 31;
|
||
|
while (!editTime.IsValid()) { /* Needed to prevent edge case such as 2020-02-31. */
|
||
|
dayMax--;
|
||
|
const boolean inc = editPos == EDIT_DAY ? cmdKey == CMD_UP : false;
|
||
|
const uint8_t dayOfMonth = rollOver(editTime.Day(), 1, dayMax, inc);
|
||
|
editTime = RtcDateTime(editTime.Year(), editTime.Month(), dayOfMonth, editTime.Hour(), editTime.Minute(), editTime.Second());
|
||
|
}
|
||
|
showClock(editTime, 3, 0, 4, 1); /* This changes cursor position, so the posChanged need to be adjusted. */
|
||
|
editChanged = false;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
|
||
|
if (posChanged) { /* Change cursor coordinate to the new edit position. */
|
||
|
uint8_t lcdX, lcdY;
|
||
|
if (editPos == EDIT_YEAR) {
|
||
|
lcdX = 6;
|
||
|
lcdY = 0;
|
||
|
} else if (editPos == EDIT_MONTH) {
|
||
|
lcdX = 9;
|
||
|
lcdY = 0;
|
||
|
} else if (editPos == EDIT_DAY) {
|
||
|
lcdX = 12;
|
||
|
lcdY = 0;
|
||
|
} else if (editPos == EDIT_HOUR) {
|
||
|
lcdX = 5;
|
||
|
lcdY = 1;
|
||
|
} else if (editPos == EDIT_MINUTE) {
|
||
|
lcdX = 8;
|
||
|
lcdY = 1;
|
||
|
} else { // editPos == EDIT_SECOND
|
||
|
lcdX = 11;
|
||
|
lcdY = 1;
|
||
|
}
|
||
|
lcd->setCursor(lcdX, lcdY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The "Set Alarm Time" screen. */
|
||
|
void screenSetAlarmTime() {
|
||
|
if (cmdKey == CMD_ENTER) { /* Write alarm time adjustments to RTC memory. */
|
||
|
rtc.SetMemory(MAD_ALARM_HOUR, alarmHour);
|
||
|
rtc.SetMemory(MAD_ALARM_MINUTE, alarmMinute);
|
||
|
} else if (cmdKey == CMD_BACK) { /* Reset alarm time to the one already set in RTC memory. */
|
||
|
alarmHour = rtc.GetMemory(MAD_ALARM_HOUR);
|
||
|
alarmMinute = rtc.GetMemory(MAD_ALARM_MINUTE);
|
||
|
if (alarmHour > 23 || alarmMinute > 59) {
|
||
|
/* Garbage from RTC RAM, so we set default alarm: 7 o'clock. */
|
||
|
alarmHour = 7;
|
||
|
alarmMinute = 0;
|
||
|
}
|
||
|
}
|
||
|
if (cmdKey == CMD_ENTER || cmdKey == CMD_BACK) { /* Exit this screen. */
|
||
|
lcd->noBlink();
|
||
|
editTimeInit = false;
|
||
|
editChanged = false;
|
||
|
currScreen = SCREEN_MENU;
|
||
|
lcd->clear(); /* Only necessary to make the transition uniform. */
|
||
|
delay(SCREEN_TRANSITION);
|
||
|
menu.show();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
boolean posChanged = false;
|
||
|
if (!editTimeInit) {
|
||
|
editTimeInit = true;
|
||
|
lcd->blink();
|
||
|
editChanged = true;
|
||
|
editPos = EDIT_HOUR;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
|
||
|
if (editPos == EDIT_HOUR) {
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
alarmHour = rollOver(alarmHour, 0, 23, cmdKey == CMD_UP);
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT || cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_MINUTE;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
} else { // editPos == EDIT_MINUTE
|
||
|
if (cmdKey == CMD_UP || cmdKey == CMD_DOWN) {
|
||
|
alarmMinute = rollOver(alarmMinute, 0, 59, cmdKey == CMD_UP);
|
||
|
editChanged = true;
|
||
|
} else if (cmdKey == CMD_LEFT || cmdKey == CMD_RIGHT) {
|
||
|
editPos = EDIT_HOUR;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (editChanged) { /* In the "Set Alarm Time" screen, the LCD only need to be updated when something has been changed. */
|
||
|
showSetAlarmTime(alarmHour, alarmMinute); /* This changes cursor position, so the posChanged need to be adjusted. */
|
||
|
editChanged = false;
|
||
|
posChanged = true;
|
||
|
}
|
||
|
|
||
|
if (posChanged) { /* Change cursor coordinate to the new edit position. */
|
||
|
uint8_t lcdX, lcdY;
|
||
|
if (editPos == EDIT_HOUR) {
|
||
|
lcdX = 7;
|
||
|
lcdY = 0;
|
||
|
} else { // editPos == EDIT_MINUTE
|
||
|
lcdX = 10;
|
||
|
lcdY = 0;
|
||
|
}
|
||
|
lcd->setCursor(lcdX, lcdY);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Start the alarm according to the alarmEnabled, alarmHour, and alarmMinute settings.
|
||
|
The alarm tone is played continuously until the user stopped the alarm, or the alarm runtime is over.
|
||
|
*/
|
||
|
void alarm() {
|
||
|
boolean stopAlarm = false;
|
||
|
const unsigned long now = millis();
|
||
|
if (alarmEnabled) {
|
||
|
if (clockTime.Hour() == alarmHour && clockTime.Minute() == alarmMinute && clockTime.Second() >= 00 && clockTime.Second() <= 1) {
|
||
|
alarmActive = true;
|
||
|
alarmActiveStart = now;
|
||
|
DEBUG_PRINTLN(F("Alarm start"));
|
||
|
}
|
||
|
} else {
|
||
|
if (alarmActive) {
|
||
|
stopAlarm = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (alarmActive) {
|
||
|
if (now - alarmActiveStart <= 1000UL * 60 * (unsigned long) alarmRunMin[alarmRunIdx]) {
|
||
|
/*
|
||
|
The alarm tone is alternating between 2400Hz and 2850Hz, each played for 250ms.
|
||
|
The tone is one of the "General Purpose" from here:
|
||
|
https://www.eaton.com/gb/en-gb/products/safety-security-emergency-communications/alarms-and-signaling-devices/fire-rated/fire-alarm-tones-and-sounds.html
|
||
|
*/
|
||
|
toneAC((now / 250UL) % 2 == 0 ? 2400 : 2850, 10, 0, true); /* Play tone continuously in background. */
|
||
|
} else {
|
||
|
stopAlarm = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (stopAlarm) {
|
||
|
alarmActive = false;
|
||
|
noToneAC();
|
||
|
DEBUG_PRINTLN(F("Alarm stopped"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Reset the menu if the selected alarm position is not committed yet.
|
||
|
Resetting is necessary because the menu system has no indication for committed/uncommitted list position.
|
||
|
This should be called when in the menu screen and the user pressed: BACK, UP, or RIGHT buttons.
|
||
|
*/
|
||
|
void resetAlarmRun() {
|
||
|
if (mainMenu[4].itemIndex != alarmRunIdx) {
|
||
|
mainMenu[4].itemIndex = alarmRunIdx;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Display the current RTC time to the LCD. */
|
||
|
void showClock(const RtcDateTime& dt, const uint8_t dateX, const uint8_t dateY, const uint8_t timeX, const uint8_t timeY) {
|
||
|
const size_t len = 11; /* 10 chars max + 1 null terminator. */
|
||
|
char dateString[len];
|
||
|
snprintf_P(dateString, len, PSTR("%04u-%02u-%02u"), dt.Year(), dt.Month(), dt.Day());
|
||
|
lcd->setCursor(dateX, dateY);
|
||
|
lcd->print(dateString);
|
||
|
snprintf_P(dateString, len, PSTR("%02u:%02u:%02u"), dt.Hour(), dt.Minute(), dt.Second());
|
||
|
lcd->setCursor(timeX, timeY);
|
||
|
lcd->print(dateString);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Read the DHT sensor and show the readings to the LCD.
|
||
|
Unlike RTC, the DHT info is only needed on the main screen.
|
||
|
So there is no point in sampling the sensor when not in the main screen.
|
||
|
*/
|
||
|
void showDHT() {
|
||
|
char str[6]; /* 5 char max + 1 null terminator */
|
||
|
float val = dht.readTemperature();
|
||
|
if (val != temperature) { /* Only need to refresh the LCD when the reading has changed. */
|
||
|
temperature = val;
|
||
|
lcd->setCursor(11, 0);
|
||
|
if (isnan(temperature)) {
|
||
|
lcd->print(F("T err"));
|
||
|
} else {
|
||
|
dtostrf(temperature, 4, 1, str);
|
||
|
lcd->print(str);
|
||
|
lcd->print(F("C"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
val = dht.readHumidity();
|
||
|
if (val != humidity) { /* Only need to refresh the LCD when the reading has changed. */
|
||
|
humidity = val;
|
||
|
lcd->setCursor(11, 1);
|
||
|
if (isnan(humidity)) {
|
||
|
lcd->print(F("H err"));
|
||
|
} else {
|
||
|
dtostrf(humidity, 4, 1, str);
|
||
|
lcd->print(str);
|
||
|
lcd->print(F("%"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Show the alarm time being edited to the LCD. */
|
||
|
void showSetAlarmTime(const uint8_t hour, const uint8_t minute) {
|
||
|
const size_t len = 6; /* 5 chars max + 1 null terminator. */
|
||
|
char dateString[len];
|
||
|
snprintf_P(dateString, len, PSTR("%02u:%02u"), hour, minute);
|
||
|
lcd->setCursor(6, 0);
|
||
|
lcd->print(dateString);
|
||
|
}
|
||
|
|
||
|
/* Restore the alarm settings from the RTC RAM. */
|
||
|
void restoreAlarm() {
|
||
|
const size_t count = 4;
|
||
|
uint8_t rtcRam[count];
|
||
|
const uint8_t got = rtc.GetMemory(rtcRam, count);
|
||
|
alarmEnabled = rtcRam[MAD_ALARM_ENABLE] == (uint8_t) 1;
|
||
|
alarmHour = rtcRam[MAD_ALARM_HOUR];
|
||
|
alarmMinute = rtcRam[MAD_ALARM_MINUTE];
|
||
|
alarmRunIdx = rtcRam[MAD_ALARM_DURATION];
|
||
|
|
||
|
if (alarmHour > 23 || alarmMinute > 59) {
|
||
|
/* Garbage from RTC RAM, so we set default alarm: 7 o'clock. */
|
||
|
alarmHour = 7;
|
||
|
alarmMinute = 0;
|
||
|
}
|
||
|
if (alarmRunIdx >= sizeof(alarmRunMin) / sizeof(alarmRunMin[0])) {
|
||
|
/* Garbage from RTC RAM, so we set default alarm duration: 1 minute. */
|
||
|
alarmRunIdx = 0;
|
||
|
}
|
||
|
|
||
|
mainMenu[2].isOn = alarmEnabled;
|
||
|
mainMenu[4].itemIndex = alarmRunIdx;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
if (got != count) {
|
||
|
DEBUG_PRINT(F("RTC RAM problem, count = "));
|
||
|
DEBUG_PRINT(count);
|
||
|
DEBUG_PRINT(", got = ");
|
||
|
DEBUG_PRINTLN(got);
|
||
|
}
|
||
|
DEBUG_PRINT(F("RTC RAM read ("));
|
||
|
DEBUG_PRINT(got);
|
||
|
DEBUG_PRINT(F(") = alarm "));
|
||
|
DEBUG_PRINT(alarmEnabled ? F("enabled") : F("disabled"));
|
||
|
DEBUG_PRINT(F(", alarm time = "));
|
||
|
DEBUG_PRINT(alarmHour);
|
||
|
DEBUG_PRINT(F(":"));
|
||
|
DEBUG_PRINT(alarmMinute);
|
||
|
DEBUG_PRINT(F(", alarm run = "));
|
||
|
DEBUG_PRINTLN(alarmRunStr[alarmRunIdx]);
|
||
|
#else
|
||
|
UNUSED(got);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/* Roll over a numeric value when the user incremented it up or down. */
|
||
|
uint8_t rollOver(const uint8_t now, const uint8_t min, const uint8_t max, const boolean increment) {
|
||
|
if (increment) {
|
||
|
return now < max ? now + 1 : min;
|
||
|
} else {
|
||
|
return now > min ? now - 1 : max;
|
||
|
}
|
||
|
}
|