//Copyright (C) 2023, Kirill ZHivotkov
#include <IRremote.h>
#include <LiquidCrystal_I2C.h>
#include <ezBuzzer.h>
#include <RTClib.h>
ezBuzzer buzzer(12);
LiquidCrystal_I2C lcd(39, 16, 2); //Address = 39
RTC_DS1307 rtc;
//const int pushDelay = 5000;
//Описываем класс "Кнопка пульта"
class Button {
private:
String buttonName = "";
int brightness = 10;
public:
//Задаем название кнопки
void setButtonName(String buttonName) {
this->buttonName = buttonName;
}
//Задаем название кнопки
//Получаем название кнопки
String getButtonName() {
return this->buttonName;
}
//Получаем название кнопки
//Задаем название кнопки через коды на пульте с помощью сеттера
void setRemoteControlButton(unsigned long a) {
if (a == 1570963200) {
setButtonName("POWER"); //Включить/выключить
//timing = millis();
} else if (a == 534839040) {
setButtonName("LEFT"); //Переместить курсор на дисплее влево
//timing = millis();
} else if (a == 1871773440) {
setButtonName("RIGHT"); //Переместить курсор на дисплее влево
//timing = millis();
} else if (a == 1336999680) {
setButtonName("EDIT_ON"); //Включить режим установки времени (мигающий курсор)
//timing = millis();
} else if (a == 1470693120) {
setButtonName("EDIT_OFF"); //Выключить режим установки времени (курсор не мигает)
//timing = millis();
} else if (a == 4161273600) {
setButtonName("MINUS"); //Уменьшить яркость дисплея
//timing = millis();
} else if (a == 3927310080) {
setButtonName("PLUS"); //Увеличить яркость дисплея
//timing = millis();
}
}
//Задаем название кнопки через коды на пульте с помощью сеттера
//Регулировка яркости дисплея (нужно снять jumper на дисплее и подключить LED к 9 PWM)
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);
}
}
}
//Регулировка яркости дисплея (нужно снять jumper на дисплее и подключить LED к 9 PWM)
//Конструктор по умолчанию
Button() {}
//Конструктор по умолчанию
};
//Описываем класс "Кнопка пульта"
//Описываем класс "Курсор"
class Cursor {
private:
int cursor = 0;
int pos = 0;
int row = 0;
bool isSetMode = 0;
float timing = 0;
public:
//Устанавливаем значение для переменной курсора дисплея
void setCursorPos(int cursor) {
this->cursor = cursor;
}
//Устанавливаем значение для переменной курсора дисплея
int getCursorTiming() {
return this->timing;
}
//Устанавливаем позицию курсора на дисплее
void setLCDCursor(int pos, int row) {
if ((pos >= 0 && pos <= 15) && (row == 0 || row == 1)) {
this->pos = pos;
this->row = row;
lcd.setCursor(this->pos, this->row);
}
}
//Устанавливаем позицию курсора на дисплее
//Получаем позицию курсора
int getCursorPos() {
return this->cursor;
}
//Получаем позицию курсора
//Получаем статус установки времени (курсор мигает или нет)
bool getIsSetModeValue() {
return this->isSetMode;
}
//Получаем статус установки времени (курсор мигает или нет)
//Выключаем подсветку курсора
void cursorStopBlink() {
lcd.noBlink();
}
//Выключаем подсветку курсора
//Включаем подсветку курсора
void cursorStartBlink() {
lcd.blink();
this->timing = millis();
}
//Включаем подсветку курсора
//Режим установки времени (для курсора)
void switchEditTimeMode(unsigned long a) {
if (a == 1336999680 && this->getIsSetModeValue() == 0) {
lcd.setCursor(this->getCursorPos(), 0);
this->cursorStartBlink();
this->isSetMode = 1;
} else if (a == 1470693120 && this->getIsSetModeValue() == 1) {
this->cursorStopBlink();
this->isSetMode = 0;
}
}
//Режим установки времени (для курсора)
//Если не вышли из режима установки времени
/*
void resetCursorAfterPause() {
if (((millis() - this->getCursorTiming()) >= 10000) && (this->getIsSetModeValue() == 1)) {
this->cursorStopBlink();
this->isSetMode = 0;
this->setLCDCursor(this->getCursorPos(), 0);
}
}
*/
//Если не вышли из режима установки времени
//Конструктор по умолчанию
Cursor() {}
//Конструктор по умолчанию
};
//Описываем класс "Курсор"
//Объявляем глобальные переменные переменных описанных выше классов
Cursor* cur = new Cursor();
Button* btn = new Button();
//Объявляем глобальные переменные переменных описанных выше классов
//Описываем класс "Дисплей"
class LCD {
private:
int digit = 0;
//Время
int h = 0;
int hh = 0;
int m = 0;
int mm = 0;
//Время
//Дата
int d = 0;
int dd = 1;
int mth = 0;
int mmth = 1;
int y = 2;
int yy = 0;
int yyy = 0;
int yyyy = 0;
//Дата
//День недели
int weekDay = 0;
//День недели
bool isOff = 0;
public:
//Проверяем допустимое значение для часов
bool checkHours(int h) {
if (h >= 0 && h <= 23) {
return true;
}
return false;
}
//Проверяем допустимое значение для часов
//Проверяем допустимое значение для минут
bool checkMinutes(int m) {
if (m >= 0 && m <= 59) {
return true;
}
return false;
}
//Проверяем допустимое значение для минут
//Проверяем допустимое значение для дней
bool checkDays(int d) {
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) {
if (m >= 1 && m <= 12) {
return true;
}
return false;
}
//Проверяем допустимое значение для месяцев
//Проверяем допустимое значение для годов
bool checkYears(int y) {
if (y >= 2000 && y <= 2030) {
return true;
}
return false;
}
//Проверяем допустимое значение для годов
//Проверяем является ли год високосным
bool checkLeapYear(int y) {
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() {
return this->m * 10 + this->mm;
}
//Получаем минуту
//Получаем час
int getHour() {
return this->h * 10 + this->hh;
}
//Получаем час
//Получаем день
int getDay() {
return this->d * 10 + this->dd;
}
//Получаем день
//Получаем месяц
int getMonth() {
return this->mth * 10 + this->mmth;
}
//Получаем месяц
//Получаем год
int getYear() {
return this->y * 1000 + this->yy * 100 + this->yyy * 10 + this->yyyy;
}
//Получаем год
//Проверяем формат введенных данных
bool checkDisplayData() {
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) {
this->isOff = isOff;
}
//Назначаем статус включения/выключения дисплея
//Получаем статус включения/выключения дисплея
bool getIsOff() {
return this->isOff;
}
//Получаем статус включения/выключения дисплея
//Функция установки времени
void setClockTimeMode(unsigned long a) {
if (cur->getIsSetModeValue() == 1) {
if (a == 534839040) { //Перемещаем курсор влево
if (cur->getCursorPos() > 0) {
//Время
if (cur->getCursorPos() == 3) {
cur->setCursorPos(1);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
//Время
//Дата
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);
}
//Дата
}
} else if (a == 1871773440) { //Перемещаем курсор вправо
if (cur->getCursorPos() < 16) {
//Время
if (cur->getCursorPos() == 1) {
cur->setCursorPos(3);
cur->setLCDCursor(cur->getCursorPos(), 0);
}
//Время
//Дата
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);
}
//Дата
}
} else {
if (cur->getCursorPos() >= 0 && cur->getCursorPos() <= 15) {
if (a == 2540240640) { // 0
this->setClockDigit(0);
btn->setButtonName("DIGIT");
} else if (a == 3476094720) { // 1
this->setClockDigit(1);
btn->setButtonName("DIGIT");
} else if (a == 3877175040) { // 2
this->setClockDigit(2);
btn->setButtonName("DIGIT");
} else if (a == 2239430400) { // 3
this->setClockDigit(3);
btn->setButtonName("DIGIT");
} else if (a == 4010868480) { // 4
this->setClockDigit(4);
btn->setButtonName("DIGIT");
} else if (a == 3342401280) { // 5
this->setClockDigit(5);
btn->setButtonName("DIGIT");
} else if (a == 2774204160) { // 6
this->setClockDigit(6);
btn->setButtonName("DIGIT");
} else if (a == 3175284480) { // 7
this->setClockDigit(7);
btn->setButtonName("DIGIT");
} else if (a == 3041591040) { // 8
this->setClockDigit(8);
btn->setButtonName("DIGIT");
} else if (a == 2907897600) { // 9
this->setClockDigit(9);
btn->setButtonName("DIGIT");
}
}
}
} else { //
}
}
//Вводим и выводим цифру 1-9 на дисплее
void setClockDigit(int digit) {
this->digit = digit;
if (this->digit >= 0 && this->digit <= 9) {
if (cur->getCursorPos() == 0) { //Часы
this->h = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->h);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 1) { //Часы
this->hh = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->hh);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 3) { //Минуты
this->m = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->m);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 4) { //Минуты
this->mm = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->mm);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 6) { //Дни
this->d = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->d);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 7) { //Дни
this->dd = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->dd);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 9) { //Месяцы
this->mth = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->mth);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 10) { //Месяцы
this->mmth = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->mmth);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 12) { //Годы
this->y = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->y);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 13) { //Годы
this->yy = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->yy);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 14) { //Годы
this->yyy = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->yyy);
cur->cursorStartBlink();
} else if (cur->getCursorPos() == 15) { //Годы
this->yyyy = this->digit;
cur->cursorStopBlink();
this->setDisplay(this->yyyy);
cur->cursorStartBlink();
}
//Перемещаемся по первой строке дисплея после подтверждения ввода цифры
if (this->checkDisplayData() == true) {
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);
}
}
//Перемещаемся по первой строке дисплея после подтверждения ввода цифры
}
}
//Вводим и выводим цифру 1-9 на дисплее (часы)
//Устанавливаем новое время (часы)
void setAdjustedClockTime() {
if (this->getIsOff() == 0) { //Если дисплей включен
DateTime now = rtc.now();
if (now.hour() < 10) {
this->h = 0;
this->hh = now.hour();
//Serial.print(" h > ");
//Serial.println(0);
//Serial.print(" hh > ");
//Serial.println(now.hour());
} else {
this->h = now.hour() / 10;
this->hh = now.hour() % 10;
//Serial.print(" h > ");
//Serial.println(now.hour() / 10);
//Serial.print(" hh > ");
//Serial.println(now.hour() % 10);
}
if (now.minute() < 10) {
this->m = 0;
this->mm = now.minute();
//Serial.print(" m > ");
//Serial.println(0);
//Serial.print(" mm > ");
//Serial.println(now.minute());
} else {
this->m = now.minute() / 10;
this->mm = now.minute() % 10;
//Serial.print(" m > ");
//Serial.println(now.minute() / 10);
//Serial.print(" mm > ");
//Serial.println(now.minute() % 10);
}
if (now.day() < 10) {
this->d = 0;
this->dd = now.day();
//Serial.print(" d > ");
//Serial.println(0);
//Serial.print(" dd > ");
//Serial.println(now.day());
} else {
this->d = now.day() / 10;
this->dd = now.day() % 10;
//Serial.print(" d > ");
//Serial.println(now.day() / 10);
//Serial.print(" dd > ");
//Serial.println(now.day() % 10);
}
if (now.month() < 10) {
this->mth = 0;
this->mmth = now.month();
//Serial.print(" mth > ");
//Serial.println(0);
//Serial.print(" mmth > ");
//Serial.println(now.month());
} else {
this->mth = now.month() / 10;
this->mmth = now.month() % 10;
//Serial.print(" mth > ");
//Serial.println(now.month() / 10);
//Serial.print(" mmth > ");
//Serial.println(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;
//Serial.print(" y > ");
//Serial.println(this->y);
//Serial.print(" yy > ");
//Serial.println(this->yy);
//Serial.print(" yyy > ");
//Serial.println(this->yyy);
//Serial.print(" yyyy > ");
//Serial.println(this->yyyy );
}
//Serial.print(" weekday > ");
//Serial.println(this->weekDay);
this->weekDay = now.dayOfTheWeek();
this->displayProcessedTime();
this->displayProcessedDate();
this->displayProcessedWeekDay();
Serial.print(now.second());
Serial.println();
delay(1000);
} else { //Иначе
//Сбрасываем время
rtc.adjust((DateTime(2000, 12, 31, 0, 0, 0)));
//Сбрасываем время
}
}
//Устанавливаем новое время (часы)
//Выводим информацию на дисплей (число)
void setDisplay(int data) {
lcd.print((String)data);
}
//Выводим информацию на дисплей (число)
//Выводим информацию на дисплей (строка)
void setDisplay(String data) {
lcd.print(data);
}
//Выводим информацию на дисплей (строка)
//Отобразить расчитанную дату
void displayProcessedDate() {
cur->setLCDCursor(6, 0);
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));
}
this->setDisplay("-");
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));
}
this->setDisplay("-");
//Год
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");
}
//Год
}
//Отобразить расчитанную дату
//Отобразить день недели
void displayProcessedWeekDay() {
cur->setLCDCursor(0, 1);
String week[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thirday", "Friday", "Saturday"};
this->setDisplay(week[this->weekDay]);
}
//Отобразить день недели
//Отобразить расчитанное время
void displayProcessedTime() {
cur->setLCDCursor(0, 0);
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));
}
this->setDisplay(":");
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));
}
}
//Отобразить расчитанное время
//Включаем дисплей
void switchDisplayOn() {
lcd.display();
}
//Включаем дисплей
//Выключаем дисплей
void switchDisplayOff() {
lcd.noDisplay();
}
//Выключаем дисплей
//Включаем подсветку дисплея
void switchBackLightOn() {
lcd.backlight();
}
//Включаем подсветку дисплея
//Выключаем подсветку дисплея
void switchBackLightOff() {
lcd.noBacklight();
}
//Выключаем подсветку дисплея
//Включение/выключение дисплея
void switchDisplayOnOff(unsigned long a) {
if (this->getIsOff() == 0) {
this->switchBackLightOff();
this->switchDisplayOff();
this->setIsOff(1);
cur->cursorStopBlink();
} else if (this->getIsOff() == 1) {
this->switchDisplayOn();
this->switchBackLightOn();
this->setIsOff(0);
}
}
//Включение/выключение дисплея
//Конструктор по умолчанию
LCD() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
//pinMode(9, OUTPUT); //if LCD backlight jumper is removed
//analogWrite(9, 10);
}
//Конструктор по умолчанию
};
//Описываем класс "Дисплей"
//Инициализируем начальные значения
LCD* disp;
void setup() {
disp = new LCD();
rtc.begin();
rtc.adjust((DateTime(2023, 01, 01, 0, 0, 0)));
pinMode(A0, INPUT);
IrReceiver.begin(A0, ENABLE_LED_FEEDBACK);
Serial.begin(9600);
}
//Инициализируем начальные значения
//Описываем класс "Кнопка пульта"
class Sound {
private:
String msg = "";
public:
void setErrorMessage(String msg) {
if (msg.length() == 16) {
this->msg = msg;
}
}
void showErrorMessage() {
cur->cursorStopBlink();
cur->setLCDCursor(0, 1);
disp->setDisplay(this->msg);
cur->setLCDCursor(cur->getCursorPos(), 0);
cur->cursorStartBlink();
}
//Проигрываем звук в случае установки неправильного формата времени HH:MM
void playErrorSound() {
if (disp->checkDisplayData() == false) {
buzzer.beep(100);
this->setErrorMessage("Incorrect format");
this->showErrorMessage();
} else {
this->setErrorMessage(" ");
this->showErrorMessage();
}
}
//Проигрываем звук в случае установки неправильного формата времени HH:MM
};
Sound* snd = new Sound();
//Цикл программы
void loop() {
buzzer.loop();
//Serial.print(" => ");
//Serial.println(disp->checkDays(disp->getDay()) == true);
//Serial.println(disp->checkHours(disp->getHour()) == true);
//Выводим установленное время
if (disp->checkDisplayData() == true) {
if (cur->getIsSetModeValue() == 0) {
disp->setAdjustedClockTime();
} else {
//Выключаем подсветку курсора, если не вышли из режима установки времени
//cur->resetCursorAfterPause();
//Выключаем подсветку курсора, если не вышли из режима установки времени
}
}
//Выводим установленное время
if (IrReceiver.decode()) {
unsigned long a = IrReceiver.decodedIRData.decodedRawData;
IrReceiver.resume();
//Получаем название кнонки по ее коду
btn->setRemoteControlButton(a);
//Получаем название кнонки по ее коду
//Регулировка яркости дисплея
btn->setDisplayBrightness();
//Регулировка яркости дисплея
//Выбираем режим установки времени
disp->setClockTimeMode(a);
//Выбираем режим установки времени
//Переключаем режим редактирования
cur->switchEditTimeMode(a);
//Переключаем режим редактирования
//Устанавливаем новое значение для времени
if (btn->getButtonName() == "EDIT_OFF") {
rtc.adjust((DateTime(disp->getYear(), disp->getMonth(), disp->getDay(), disp->getHour(), disp->getMinute(), 0)));
}
//Устанавливаем новое значение для времени
//Включение/выключение дисплея
if (btn->getButtonName() == "POWER") {
disp->switchDisplayOnOff(a);
}
//Включение/выключение дисплея
//Звуковой сигнал при ошибке ввода
if (btn->getButtonName() == "DIGIT") {
snd->playErrorSound();
}
//Звуковой сигнал при ошибке ввода
}
}
//Цикл программы