//Copyright (C) 22.09.2023, Kirill ZHivotkov
#include <IRremote.h>
#include <LiquidCrystal_I2C.h>
#include <ezBuzzer.h>
#include <RTClib.h>
#include <Wire.h>
ezBuzzer buzzer(12);
LiquidCrystal_I2C lcd(39, 16, 2); //Address = 39
RTC_DS1307 rtc;
//Implement class "Remote Control Button"
class Button {
private:
String buttonName = "";
int brightness = 10;
public:
//Set button name
void setButtonName(String buttonName) {
this->buttonName = buttonName;
}
//Set button name
//Get button name
String getButtonName() {
return this->buttonName;
}
//Get button name
//Set button name by means of remote control codes
void setRemoteControlButton(unsigned long a) {
if (a == 1570963200) {
setButtonName("POWER"); //Power LCD on/off
} else if (a == 534839040) {
setButtonName("LEFT"); //Move LCD cursor to the right
} else if (a == 1871773440) {
setButtonName("RIGHT"); //Move LCD cursor to the left
} else if (a == 1336999680) {
setButtonName("EDIT_ON"); //Switch time setting mode on (blinking is cursor)
} else if (a == 1470693120) {
setButtonName("EDIT_OFF"); //Switch time setting mode off (blinking is not cursor)
} else if (a == 4161273600) {
//setButtonName("MINUS"); //Decrease display brightness
} else if (a == 3927310080) {
//setButtonName("PLUS"); //Increase display brightness
}
}
//Set button name by means of remote control codes
//LCD brightness control (take jumper from LCD 1602 and connect it to 9-nth PWM (when using Arduino Uno))
/*
void setDisplayBrightness() {
if (this->getButtonName() == "MINUS") {
if (this->brightness > 0) {
this->brightness -= 5;
//Serial.println(this->brightness);
analogWrite(9, this->brightness);
}
} else if (this->getButtonName() == "PLUS") {
if (this->brightness < 255) {
this->brightness += 5;
//Serial.println(this->brightness);
analogWrite(9, this->brightness);
}
}
}
*/
//LCD brightness control (take jumper from LCD 1602 and connect it to 9-nth PWM (when using Arduino Uno))
//Default contructor
Button() {}
//Default contructor
};
//Implement class "Remote Control Button"
//Implement class "Cursor"
class Cursor {
private:
int cursor = 0;
int pos = 0;
int row = 0;
bool isSetMode = 0;
float timing = 0;
public:
void setCursorPos(int cursor) { //Set LCD cursor value
this->cursor = cursor;
}
void setLCDCursor(int pos, int row) { //Set cursor position on LCD display
if ((pos >= 0 && pos <= 15) && (row == 0 || row == 1)) {
this->pos = pos;
this->row = row;
lcd.setCursor(this->pos, this->row);
}
}
int getCursorPos() { //Get cursor position
return this->cursor;
}
void setIsSetModeValue(bool isSetMode) { //Set time assigning status (cursor is blinking or not)
this->isSetMode = isSetMode;
}
bool getIsSetModeValue() { //Get time assigning status (cursor is blinking or not)
return this->isSetMode;
}
void cursorStopBlink() { //Switch cursor blinking off
lcd.noBlink();
}
void cursorStartBlink() { //Switch cursor blinking on
lcd.blink();
}
void switchEditTimeMode(unsigned long a) { //Describe time setting mode (for cursor)
if (a == 1336999680 && this->getIsSetModeValue() == 0) {
lcd.setCursor(this->getCursorPos(), 0);
this->cursorStartBlink();
this->isSetMode = 1;
} else if (a == 1470693120 && this->getIsSetModeValue() == 1) {
lcd.setCursor(0, 0);
this->cursorStopBlink();
this->isSetMode = 0;
}
}
/*
void resetCursorAfterPause() { //If user didn't leave time setting mode
if (((millis() - this->getCursorTiming()) >= 10000) && (this->getIsSetModeValue() == 1)) {
this->cursorStopBlink();
this->isSetMode = 0;
this->setLCDCursor(this->getCursorPos(), 0);
}
}
*/
//Конструктор по умолчанию
Cursor() {}
//Конструктор по умолчанию
};
//Implement class "Cursor"
//Create instances of the classes described above
Cursor* cur = new Cursor();
Button* btn = new Button();
//Create instances of the classes described above
//Implement class "Display"
class LCD {
private:
int digit = 0;
//Time
int h = 0;
int hh = 0;
int m = 0;
int mm = 0;
//Time
//Date
int d = 0;
int dd = 1;
int mth = 0;
int mmth = 1;
int y = 2;
int yy = 0;
int yyy = 0;
int yyyy = 0;
//Date
//Weekday
int weekDay = 0;
//Weekday
bool isOff = 0;
public:
bool checkHours(int h) { //Check hours range
if (h >= 0 && h <= 23) {
return true;
}
return false;
}
bool checkMinutes(int m) { //Check minutes range
if (m >= 0 && m <= 59) {
return true;
}
return false;
}
bool checkDays(int d) { //Check days range
int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (checkLeapYear(this->getYear()) == true && days[1] == 28) {
days[1]++;
}
if (d >= 1 && d <= days[this->getMonth() - 1]) {
return true;
}
return false;
}
bool checkMonths(int m) { //Check months range
if (m >= 1 && m <= 12) {
return true;
}
return false;
}
bool checkYears(int y) { //Check years range
if (y >= 2000 && y <= 2030) {
return true;
}
return false;
}
bool checkLeapYear(int y) { //Check leap year
if (y % 400 == 0) {
return true;
} else if (y % 100 == 0) {
return false;
} else if (y % 4 == 0) {
return true;
} else {
return false;
}
}
int getMinute() { //Get a minute
return this->m * 10 + this->mm;
}
int getHour() { //Get an hour
return this->h * 10 + this->hh;
}
int getDay() { //Get a day
return this->d * 10 + this->dd;
}
int getMonth() { //Get a month
return this->mth * 10 + this->mmth;
}
int getYear() { //Get a year
return this->y * 1000 + this->yy * 100 + this->yyy * 10 + this->yyyy;
}
int getWeekDay() { //Get a weekday
return this->weekDay;
}
bool checkDisplayData() { //Check input data format
if (this->checkHours(this->getHour()) == true &&
this->checkMinutes(this->getMinute()) == true &&
this->checkDays(this->getDay()) == true &&
this->checkMonths(this->getMonth()) == true &&
this->checkYears(this->getYear()) == true) {
return true;
}
return false;
}
void setIsOff(bool isOff) { //Set LCD status (if display is on or off)
this->isOff = isOff;
}
bool getIsOff() { //Get LCD status (if display is on or off)
return this->isOff;
}
void setClockTimeMode(unsigned long a) { //Time setting function
if (cur->getIsSetModeValue() == 1) {
if (a == 534839040) { //Move cursor to the left
if (cur->getCursorPos() > 0) {
//Time
if (cur->getCursorPos() == 3) {
cur->setCursorPos(1);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
//Time
//Date
else if (cur->getCursorPos() == 6) {
cur->setCursorPos(4);
cur->setLCDCursor(cur->getCursorPos(), 0);
} else if (cur->getCursorPos() == 9) {
cur->setCursorPos(7);
cur->setLCDCursor(cur->getCursorPos(), 0);
} else if (cur->getCursorPos() == 12) {
cur->setCursorPos(10);
cur->setLCDCursor(cur->getCursorPos(), 0);
} else {
cur->setCursorPos(cur->getCursorPos() - 1);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
//Date
}
} else if (a == 1871773440) { //Move cursor to the right
if (cur->getCursorPos() < 16) {
//Time
if (cur->getCursorPos() == 1) {
cur->setCursorPos(3);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
//Time
//Date
else if (cur->getCursorPos() == 4) {
cur->setCursorPos(6);
cur->setLCDCursor(cur->getCursorPos(), 0);
} else if (cur->getCursorPos() == 7) {
cur->setCursorPos(9);
cur->setLCDCursor(cur->getCursorPos(), 0);
} else if (cur->getCursorPos() == 10) {
cur->setCursorPos(12);
cur->setLCDCursor(cur->getCursorPos(), 0);
} else {
cur->setCursorPos(cur->getCursorPos() + 1);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
//Date
}
} else {
if (cur->getCursorPos() >= 0 && cur->getCursorPos() <= 15) {
if (a == 2540240640) { // Key 0
this->setClockDigit(0);
btn->setButtonName("DIGIT");
} else if (a == 3476094720) { // Key 1
this->setClockDigit(1);
btn->setButtonName("DIGIT");
} else if (a == 3877175040) { // Key 2
this->setClockDigit(2);
btn->setButtonName("DIGIT");
} else if (a == 2239430400) { // Key 3
this->setClockDigit(3);
btn->setButtonName("DIGIT");
} else if (a == 4010868480) { // Key 4
this->setClockDigit(4);
btn->setButtonName("DIGIT");
} else if (a == 3342401280) { // Key 5
this->setClockDigit(5);
btn->setButtonName("DIGIT");
} else if (a == 2774204160) { // Key 6
this->setClockDigit(6);
btn->setButtonName("DIGIT");
} else if (a == 3175284480) { // Key 7
this->setClockDigit(7);
btn->setButtonName("DIGIT");
} else if (a == 3041591040) { // Key 8
this->setClockDigit(8);
btn->setButtonName("DIGIT");
} else if (a == 2907897600) { // Key 9
this->setClockDigit(9);
btn->setButtonName("DIGIT");
}
}
}
} else { //
}
}
void setClockDigit(int digit) { //Input and output LCD dispay digits from 1 to 9
this->digit = digit;
if (this->digit >= 0 && this->digit <= 9) {
if (cur->getCursorPos() == 0) { //Hours
this->h = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->h);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 1) { //Hours
this->hh = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->hh);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 3) { //Minutes
this->m = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->m);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 4) { //Minutes
this->mm = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->mm);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 6) { //Days
this->d = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->d);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 7) { //Days
this->dd = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->dd);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 9) { //Months
this->mth = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->mth);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 10) { //Months
this->mmth = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->mmth);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 12) { //Years
this->y = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->y);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 13) { //Years
this->yy = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->yy);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 14) { //Years
this->yyy = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->yyy);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 15) { //Years
this->yyyy = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->yyyy);
cur->cursorStartBlink();
}
if (this->checkDisplayData() == true) { //Move through the first display row to the right after a digit was confirmed to be set
if (cur->getCursorPos() != 1 &&
cur->getCursorPos() != 4 &&
cur->getCursorPos() != 7 &&
cur->getCursorPos() != 10) {
if (cur->getCursorPos() >= 0 && cur->getCursorPos() < 15) {
cur->setCursorPos(cur->getCursorPos() + 1);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
} else {
cur->setCursorPos(cur->getCursorPos() + 1);
cur->setCursorPos(cur->getCursorPos() + 1);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
}
}
}
void setAdjustedClockTime() { //Set a new time (clock)
if (this->getIsOff() == 0) { //If LCD display is on
DateTime now = rtc.now();
if (now.hour() < 10) {
this->h = 0;
this->hh = now.hour();
Serial.print(" h >>> ");
Serial.print(this->h);
Serial.print(" hh >>> ");
Serial.print(this->hh);
} else {
this->h = now.hour() / 10;
this->hh = now.hour() % 10;
Serial.print(" h >>> ");
Serial.print(this->h);
Serial.print(" hh >>> ");
Serial.print(this->hh);
}
if (now.minute() < 10) {
this->m = 0;
this->mm = now.minute();
Serial.print(" m >>> ");
Serial.print(this->m);
Serial.print(" mm >>> ");
Serial.print(this->mm);
} else {
this->m = now.minute() / 10;
this->mm = now.minute() % 10;
Serial.print(" m >>> ");
Serial.print(this->m);
Serial.print(" mm >>> ");
Serial.print(this->mm);
}
Serial.print(" ");
Serial.print(now.second());
Serial.println();
if (now.day() >= 0 && now.day() < 10) {
this->d = 0;
this->dd = now.day();
} else {
if (now.day() >= 1 && now.day() <= 31) {
this->d = now.day() / 10;
this->dd = now.day() % 10;
}
}
if (now.month() >= 0 && now.month() < 10) {
this->mth = 0;
this->mmth = now.month();
} else {
if (now.month() >= 10 && now.month() <= 12) {
this->mth = now.month() / 10;
this->mmth = now.month() % 10;
}
}
if (now.year() >= 2000 && now.year() <= 2030) {
this->y = now.year() / 1000;
this->yy = now.year() / 100 % 10;
this->yyy = now.year() / 10 % 10;
this->yyyy = now.year() % 10;
}
this->weekDay = now.dayOfTheWeek();
this->displayProcessedTime();
this->displayProcessedDate();
this->displayProcessedWeekDay();
delay(1000);
} else { //Else
//Reset time
this->clearDisplay();
rtc.adjust((DateTime(2000, 1, 1, 0, 0, 0)));
//Reset time
}
}
void setDisplay(int data) { //Output info to the display (number)
lcd.print((String)data);
}
void setDisplay(String data) { //Output info to the display (string)
lcd.print(data);
}
void displayProcessedDate() { //Output calculated date
cur->setLCDCursor(6, 0);
//Day
if (this->d * 10 + this->dd < 10) {
this->setDisplay("0" + (String)(this->d * 10 + this->dd));
} else {
this->setDisplay((String)(this->d * 10 + this->dd));
}
//Day
this->setDisplay("-");
//Month
if (this->mth * 10 + this->mmth < 10) {
this->setDisplay("0" + (String)(this->mth * 10 + this->mmth));
} else {
this->setDisplay((String)(this->mth * 10 + this->mmth));
}
//Month
this->setDisplay("-");
//Year
int tmp_year = this->y * 1000 + this->yy * 100 + this->yyy * 10 + this->yyyy;
if (tmp_year >= 2000 && tmp_year <= 2030) {
this->setDisplay((String)(tmp_year));
} else {
this->setDisplay("2000");
}
//Year
}
void displayProcessedWeekDay() { //Output calculated weekday
String week[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
cur->setLCDCursor(0, 1);
this->setDisplay(week[this->weekDay]);
}
void displayProcessedTime() { //Output calculated time
cur->setLCDCursor(0, 0);
//Hour
if (this->h * 10 + this->hh < 10) {
this->setDisplay("0" + (String)(this->h * 10 + this->hh));
} else {
this->setDisplay((String)(this->h * 10 + this->hh));
}
//Hour
this->setDisplay(":");
//Minute
if (this->m * 10 + this->mm < 10) {
this->setDisplay("0" + (String)(this->m * 10 + this->mm));
} else {
this->setDisplay((String)(this->m * 10 + this->mm));
}
//Minute
}
void switchDisplayOn() { //Switch LCD display on
lcd.display();
}
void switchBackLightOn() { //Switch LCD backlight on
lcd.backlight();
}
void switchDisplayOff() { //Switch LCD display off
lcd.noDisplay();
}
void switchBackLightOff() { //Switch LCD backlight off
lcd.noBacklight();
}
void clearDisplay() { //Clear the whole display
cur->cursorStopBlink();
cur->setIsSetModeValue(0);
lcd.clear();
}
void switchDisplayOnOff(unsigned long a) { //Compound function to switch LCD display on/off
if (this->getIsOff() == 0) {
this->switchBackLightOff();
this->switchDisplayOff();
this->setIsOff(1);
cur->cursorStopBlink();
} else if (this->getIsOff() == 1) {
this->switchDisplayOn();
this->switchBackLightOn();
cur->setIsSetModeValue(0);
this->setIsOff(0);
}
}
//Default constructor
LCD() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
//pinMode(9, OUTPUT); //if LCD backlight jumper is removed (for Arduino Uno)
//analogWrite(9, 10);
}
//Default constructor
};
//Implement class "Display"
//Create an instance of the class described above
LCD* disp;
//Create an instance of the class described above
//Setup
void setup() {
disp = new LCD();
disp->clearDisplay();
Wire.begin();
rtc.begin();
rtc.adjust((DateTime(2000, 1, 1, 0, 0, 0)));
pinMode(A0, INPUT);
IrReceiver.begin(A0, ENABLE_LED_FEEDBACK);
Serial.begin(9600);
}
//Setup
//Implement class "Sound"
class Sound {
private:
String msg = "";
public:
void setErrorMessage(String msg) { //Set an error message restriction
if (msg.length() == 16) {
this->msg = msg;
}
}
void showErrorMessage() { //Output an error message (time or date were set incorrectly)
cur->cursorStopBlink();
cur->setLCDCursor(0, 1);
disp->setDisplay(this->msg);
cur->setLCDCursor(cur->getCursorPos(), 0);
cur->cursorStartBlink();
}
void playErrorSound() { //Make an error sound if the clock input data were incorrect
if (disp->checkDisplayData() == false) {
buzzer.beep(100);
this->setErrorMessage("Incorrect format");
this->showErrorMessage();
} else {
this->setErrorMessage(" ");
this->showErrorMessage();
}
}
};
//Implement class "Sound"
Sound* snd = new Sound();
//Program loop
void loop() {
buzzer.loop();
//Output time that was set with RTC
if (disp->checkDisplayData() == true) { //If clock data is correct
if (cur->getIsSetModeValue() == 0) { //If clock is not in edit mode
disp->setAdjustedClockTime();
} else { //Switch cursor highlight off if the user didn't leave time setting mode
//cur->resetCursorAfterPause();
}
}
//Output time that was set with RTC
if (IrReceiver.decode()) {
unsigned long a = IrReceiver.decodedIRData.decodedRawData; //Receive a value from remote control
IrReceiver.resume();
btn->setRemoteControlButton(a); //Get button name by its code (remote control)
//btn->setDisplayBrightness(); //Display brightness control
disp->setClockTimeMode(a); //Set a clock time and date
cur->switchEditTimeMode(a); //Switch between edit mode and running time (cursor)
if (btn->getButtonName() == "EDIT_OFF" && disp->checkDisplayData() == true) { //Set new RTC values if some new time settings' values were confirmed (a user entered new time and date)
disp->clearDisplay();
/*
Serial.print(" year > ");
Serial.println(disp->getYear());
Serial.print(" month > ");
Serial.println(disp->getMonth());
Serial.print(" day > ");
Serial.println(disp->getDay());
Serial.print(" hour > ");
Serial.println(disp->getHour());
Serial.print(" minute > ");
Serial.println(disp->getMinute());
*/
rtc.adjust((DateTime(disp->getYear(), disp->getMonth(), disp->getDay(), disp->getHour(), disp->getMinute(), 0)));
}
if (btn->getButtonName() == "POWER") { //Power LCD on/off
disp->switchDisplayOnOff(a);
}
if (btn->getButtonName() == "DIGIT") { //Emit a sound if some incorrect time settings were applied (wrong time or date formats)
snd->playErrorSound();
}
}
}
//Program loop