#include "ESP32Time.h"
#include "LiquidCrystal_I2C_Menu_Btns.h"
#include "WiFiManager.h"
#include "GlobalTimeManager.h"
#include "SmartHomeManager.h"
// объект меню LCD-дисплея
LiquidCrystal_I2C_Menu_Btns lcd(0x27, 20, 4);
// реле и кнопки включения света
#define RELAY_LIGHT 5
#define RELAY_NIGHT 2
#define RELAY_LOAD 15
#define LIGHT_BTN_1 16
#define LIGHT_BTN_2 4
#define MOTION_SENSOR 0
// Пины, к которым подключены кнопки управления конфигурацией
#define pinLeft 12
#define pinRight 14
#define pinEnter 27
#define pinBack 26
// начальные состояния кнопок и датчиков
int lastButton1 = LOW;
int curButton1 = LOW;
int lastButton2 = LOW;
int curButton2 = LOW;
int motionState = LOW;
int motionVal = 0;
int movementReactionTime = 0; // timestamp последнего включения света из-за движения
int lastAlarmTime = 0; // timestamp последнего срабатывания будильника
bool lightIsOnBecauseMovement = false;
// инициализация основной системы
SmartHomeManager home = SmartHomeManager(RELAY_LIGHT, RELAY_NIGHT, RELAY_LOAD);
ESP32Time rtc(10800); // время
WiFiManager* wifi_manager; // WiFi
// пункты меню управления конфигурацией
enum {mkBack, mkRoot,
mkWifi, mkWiFiSsid, mkWiFiPassword,
mkTime,
mkAlarm, mkAlarmEnable, mkAlarmTime, mkAlarmDuration,
mkMotion, mkMotionDay, mkMotionDayEnable, mkMotionDayDuration, mkMotionNight, mkMotionNightEnable, mkMotionNightDuration
};
// хранилища для введенных пользователем параметров конфигурации
char ssid[] = " ";
char password[] = " ";
char time_str[] = "00:00:00";
char alarm_time_str[] = "00:00";
char duration_str[] = "000";
int submenu_item_index = 0;
void userSetWifiSsid() {
// функция обработки ввода SSID пользователем
if (lcd.inputStrVal("Input SSID", ssid, 15, " 0123456789abcdefghijklmnopqrstuvwxyz")) {
wifi_manager->setSsid(ssid); // установка нового SSID
} else {
lcd.print("Input canceled");
}
while (lcd.getButtonsState() == eNone);
}
void userSetWifiPassword() {
// функция обработки ввода пароля WiFi пользователем
if (lcd.inputStrVal("Input password", password, 15, " 0123456789abcdefghijklmnopqrstuvwxyz")) {
wifi_manager->setPassword(password); // установка нового пароля
} else {
lcd.print("Input canceled");
}
while (lcd.getButtonsState() == eNone);
}
void userSetTime() {
// функция обработки ввода текущего времени пользователем
if (lcd.inputStrVal("Input time", time_str, 8, "0123456789")) {
int hours_first = time_str[0] - '0';
int hours_second = time_str[1] - '0';
int hours = hours_first * 10 + hours_second;
int minutes_first = time_str[3] - '0';
int minutes_second = time_str[4] - '0';
int minutes = minutes_first * 10 + minutes_second;
int seconds_first = time_str[6] - '0';
int seconds_second = time_str[7] - '0';
int seconds = seconds_first * 10 + seconds_second;
rtc.setTime(seconds, minutes, hours, 1, 1, 2024); // установка введенного времени
Serial.println(rtc.getTime()); // вывод введенного времени в консоль
} else {
lcd.print("Input canceled");
}
while (lcd.getButtonsState() == eNone);
}
void userSetAlarmTime() {
// функция обработки ввода времени будильника пользователем
if (lcd.inputStrVal("Input time", alarm_time_str, 5, "0123456789")) {
int hours_first = alarm_time_str[0] - '0';
int hours_second = alarm_time_str[1] - '0';
int hours = hours_first * 10 + hours_second;
int minutes_first = alarm_time_str[3] - '0';
int minutes_second = alarm_time_str[4] - '0';
int minutes = minutes_first * 10 + minutes_second;
home.setAlarmTime(hours, minutes); // установка времени будильника
} else {
lcd.print("Input canceled");
}
while (lcd.getButtonsState() == eNone);
}
void userSetDuration(int mode) {
// функция ввода длительности (свечения, работы доп. нагрузки) пользователем
if (lcd.inputStrVal("Input seconds", duration_str, 3, "0123456789")) {
int digit_1 = duration_str[0] - '0';
int digit_2 = duration_str[1] - '0';
int digit_3 = duration_str[2] - '0';
int seconds = digit_1 * 100 + digit_2 * 10 + digit_3;
switch (mode) {
case 1:
// установка длительности дневного освещения
home.setLightDuration(seconds);
break;
case 2:
// установка длительности ночного освещения
home.setNightLightDuration(seconds);
break;
case 3:
// установка длительности работы доп. нагрузки по итогам срабатывания будильника
home.setAlarmDuration(seconds);
break;
default:
break;
}
} else {
lcd.print("Input canceled");
}
while (lcd.getButtonsState() == eNone);
}
// обработчики установок конкретных длительностей в меню
void userSetMotionDayDuration() { userSetDuration(1); }
void userSetMotionNightDuration() { userSetDuration(2); }
void userSetAlarmDuration() { userSetDuration(3); }
void userSetEnabled(int mode) {
// функция обработки включения или выключения чего-либо пользователем
String list[] = {"disable", "enable"};
submenu_item_index = lcd.selectVal("Select item", list, 2, true, submenu_item_index);
lcd.printf("%s selected", list[submenu_item_index].c_str());
Serial.println(submenu_item_index);
switch (mode) {
case 1:
// включение/выключение реакции на движение днем
if (submenu_item_index == 0) { home.turnLinghtOnMovementOff(); }
else { home.turnLinghtOnMovementOn(); };
break;
case 2:
// включение/выключение реакции на движение ночью
if (submenu_item_index == 0) { home.turnNightLinghtOnMovementOff(); }
else { home.turnNightLinghtOnMovementOn(); };
break;
case 3:
// включение/выключение будильника
if (submenu_item_index == 0) { home.turnAlarmOff(); }
else { home.turnAlarmOn(); };
break;
default:
break;
}
}
// обработчики установок конкретных включений/выключений в меню
void userSetMotionDayEnabled() { userSetEnabled(1); }
void userSetMotionNightEnabled() { userSetEnabled(2); }
void userSetAlarmEnabled() { userSetEnabled(3); }
// Меню управления
sMenuItem menu[] = {
{mkBack, mkRoot, "menu"},
{mkRoot, mkMotion, "motion detection"},
{mkMotion, mkMotionDay, "day"},
{mkMotionDay, mkMotionDayEnable, "enable", userSetMotionDayEnabled},
{mkMotionDay, mkMotionDayDuration, "duration", userSetMotionDayDuration},
{mkMotion, mkMotionNight, "night"},
{mkMotionNight, mkMotionNightEnable, "enable", userSetMotionNightEnabled},
{mkMotionNight, mkMotionNightDuration, "duration", userSetMotionNightDuration},
{mkRoot, mkAlarm, "alarm"},
{mkAlarm, mkAlarmEnable, "enable", userSetAlarmEnabled},
{mkAlarm, mkAlarmTime, "time", userSetAlarmTime},
{mkAlarm, mkAlarmDuration, "duration", userSetAlarmDuration},
{mkRoot, mkWifi, "wifi"},
{mkWifi, mkWiFiSsid, "SSID", userSetWifiSsid},
{mkWifi, mkWiFiPassword, "password", userSetWifiPassword},
{mkRoot, mkTime, "time", userSetTime},
{mkRoot, mkBack, "Exit menu"}
};
uint8_t menuLen = sizeof(menu) / sizeof(sMenuItem);
void setup() {
// подключение по Wifi
wifi_manager = new WiFiManager();
wifi_manager->connect();
// настройка LCD-дисплея с меню управления конфигурацией
lcd.begin();
lcd.attachButtons(pinLeft, pinRight, pinEnter, pinBack);
lcd.attachIdleFunc(main_loop);
// кнопки включения света
pinMode(LIGHT_BTN_1, INPUT);
digitalWrite(LIGHT_BTN_1, HIGH);
pinMode(LIGHT_BTN_2, INPUT);
digitalWrite(LIGHT_BTN_2, HIGH);
// реле
pinMode(RELAY_LIGHT, OUTPUT);
pinMode(RELAY_NIGHT, OUTPUT);
pinMode(RELAY_LOAD, OUTPUT);
Serial.begin(115200);
Serial.println("Hello, ESP32!");
// настройка датчика движения
pinMode(MOTION_SENSOR, INPUT);
// получение текущего времени с сервера и установка
GlobalTimeManager time = GlobalTimeManager(wifi_manager);
int currentTimestamp = time.getCurrentTime();
rtc.setTime(currentTimestamp);
Serial.print("Time now: ");
Serial.println(rtc.getTime());
// и проверка времени суток
bool is_day = home.isNowDay(rtc.getHour(true));
if (is_day) {
Serial.println("Now day");
} else {
Serial.println("Now night");
}
}
void loop() {
// в основном цикле работает только функция управлением через меню на LCD-экрана
// функция main_loop подключена к ней, чтобы работать параллельно
menu_work();
delay(100);
}
void main_loop() {
int currentHour = rtc.getHour(true);
bool is_day = home.isNowDay(currentHour);
// нажатие кнопок включения света
curButton1 = debounce(lastButton1, LIGHT_BTN_1);
if (lastButton1 == HIGH && curButton1 == LOW) {
Serial.println("1 pushed");
home.processLightButton();
}
lastButton1 = curButton1;
curButton2 = debounce(lastButton2, LIGHT_BTN_2);
if (lastButton2 == HIGH && curButton2 == LOW) {
Serial.println("2 pushed");
home.processLightButton();
}
lastButton2 = curButton2;
// проверка будильника
if (home.isAlarmOn) {
int currentMinute = rtc.getMinute();
if (currentHour == home.alarmTimeHours && currentMinute == home.alarmTimeMinutes) {
home.turnLightOn();
home.turnAddLoadOn();
Serial.println("ALARM!");
}
}
// обработка сигнала с датчика движения
motionVal = digitalRead(MOTION_SENSOR);
if (motionVal == HIGH) {
home.processMovement(currentHour);
lightIsOnBecauseMovement = true;
movementReactionTime = rtc.getEpoch();
if (motionState == LOW) {
Serial.println("Motion detected!");
motionState = HIGH;
}
} else {
// ничего не делаем, всё погаснет по времени
if (motionState == HIGH) {
Serial.println("Motion ended!");
motionState = LOW;
}
}
// проверка, не пора ли погасить свет, зажженный из-за движения
if (lightIsOnBecauseMovement) {
int lightingTime = rtc.getEpoch() - movementReactionTime;
bool turnedOff = home.processMovementTimeout(lightingTime, currentHour);
if (turnedOff) {
lightIsOnBecauseMovement = false;
movementReactionTime = rtc.getEpoch();
}
}
// проверка, не пора ли погасить нагрузку, включенную по будильнику
if (home.isAddLoadOn) {
int workingTime = rtc.getEpoch() - lastAlarmTime;
bool turnedOff = home.processAlarmTimeout(workingTime, currentHour);
if (turnedOff) {
lastAlarmTime = rtc.getEpoch();
}
}
}
int debounce(int last, int button) {
// подавление дребезга нажатия кнопок
int current = digitalRead(button);
if (last != current) {
delay(5);
current = digitalRead(button);
}
return current;
}
void menu_work() {
// Показываем меню
uint8_t selectedMenuItem = lcd.showMenu(menu, menuLen, 1);
if (selectedMenuItem == mkBack) {
lcd.print("Exit selected");
}
}