#include <Arduino.h> // Стандартна бібліотека Arduino, що містить базові функції
#include <WiFi.h> // Бібліотека для роботи з Wi-Fi на пристроях Arduino
#include <PubSubClient.h> // Бібліотека для взаємодії з брокером MQTT
#include <LiquidCrystal_I2C.h> // Бібліотека для управління LCD-дисплеєм через I2C
#include <DHT.h> // Бібліотека для підключення датчика DHT22
#include <ESP32Servo.h> // Бібліотека для підключеня servo адаптована для esp32
Servo servo1;
Servo servo2;
#define WIFI_SSID "Wokwi-GUEST" // Ім'я (SSID) Wi-Fi мережі для підключення
#define WIFI_PW "" // Пароль для доступу до Wi-Fi мережі
const char* mqttServer = "broker.hivemq.com"; // Адреса MQTT-сервера для підключення
const int mqttPort = 1883; // Порт MQTT-сервера
const char* mqttUser = "GrUser"; // ім'я користувача MQTT
const char* mqttPassword = "Gr1"; // пароль MQTT
DHT dht(16, DHT22);
#define BUTTON_PIN 13
#define NEW_BUTTON_PIN 33 // Пін тактової кнопки
#define MOTION_SENSOR_PIN 4
#define NTP_SERVER "pool.ntp.org" // Адреса сервера часу (NTP), який використовується для синхронізації часу
#define UTC_OFFSET 2 * 3600 // часовий пояс
#define UTC_OFFSET_DST 3600 // перехід на літній час
int new_led_flag = 0;
int led_flag = 0; // Змінна для визначення стану світлодіода
WiFiClient espClient; // Клієнт для роботи з Wi-Fi
PubSubClient client(espClient); // Клієнт для взаємодії з брокером MQTT
LiquidCrystal_I2C LCD = LiquidCrystal_I2C (0x27, 20, 4) ; // Об'єкт для управління LCD-дисплеєм через I2C
boolean blinkWiFi = true; // Флаг для мігaтіння стану Wi-Fi
boolean blinkMqtt = true; // Флаг для мігaтіння стану MQTT
float humidity;
float temperature;
float prevHumidity = 0.0; // попереднє значення вологості
float prevTemperature = 0.0; // попереднє значення температури
int rotary_swich_state[2] = {1, 1};
int time_off_leds = 2;
int time_off_leds_pz[3] = {1, 1, 1}; // Масив для визначення часу вимкнення трьох світлодіодів (в секундах)
const int NEW_LED_PINS[3] = {27, 26, 25}; // Припустимо, що світлодіоди підключені до пінів 27, 26, 25
const int LED_PINS[3] = {14, 12, 15};
int previous_leds_state[3] = {-1, -1, -1}; // Масив для зберігання попереднього стану світлодіодів
int new_previous_leds_state[3] = {-1, -1, -1};
const String topics[3] = {"redled1", "redled2", "redled3"};
const String new_topics[3] = {"newled1", "newled2", "newled3"};
bool leds_state_mas[3] = {0, 0, 0}; // масив стану кнопок
bool new_leds_state_mas[3] = {0, 0, 0}; // масив стану кнопок
const char* topicsArr[] = {"button", "pz1", "pz2", "pz3", "rs1", "rs2", "buttonnew"};//масив топіків
byte customChar0[] = {
0x00, // Шаблон для створення символу на LCD-дисплеї
0x00,
0x00,
0x00,
0x00,
0x1F,
0x0E,
0x04
};
byte customChar1[] = {
0x00,
0x00,
0x00,
0x1F,
0x00,
0x0E,
0x00,
0x04,
};
byte customChar2[] = {
0x05,
0x06,
0x07,
0x00,
0x00,
0x1C,
0x0C,
0x14,
};
byte customChar3[] = {
0x01,
0x0A,
0x0C,
0x0E,
0x0E,
0x06,
0x0A,
0x10,
};
void setup()
{
pinMode(NEW_BUTTON_PIN, INPUT_PULLUP);
pinMode(BUTTON_PIN, INPUT_PULLUP); // Пін кнопки встановлюєм як вхід
for (int i = 0; i < 3; i++) { // Налаштування пінів як виходів
pinMode(LED_PINS[i], OUTPUT); // Налаштування пінів для LED-індикаторів
}
// Пін кнопки встановлюєм як вхід
for (int i = 0; i < 3; i++) { // Налаштування пінів як виходів
pinMode(NEW_LED_PINS[i], OUTPUT); // Налаштування пінів для LED-індикаторів
}
pinMode(4, INPUT_PULLUP); // Налаштування піна 4 як вхідного з підтягнутим до напруги
Serial.begin(9600); // Ініціалізація послідовного порту з швидкістю передачі даних 9600 біт/с
LCD.init(); // Ініціалізація LCD-дисплею
LCD.backlight(); // Увімкнення підсвічування
LCD.createChar(0, customChar0); // Створення власного символу 0 на дисплеї
LCD.createChar(1, customChar1); // Створення власного символу 1 на дисплеї
LCD.createChar(2, customChar2); // Створення власного символу 2 на дисплеї
LCD.createChar(3, customChar3); // Створення власного символу 3 на дисплеї
WiFi.begin(WIFI_SSID, WIFI_PW); // Початок процедури підключення до Wi-Fi мережі
LCD.setCursor(0, 0); // Встановлення позиції курсору на LCD
LCD.print("Connecting to "); // Вивід тексту на LCD
LCD.setCursor(0, 1); // Перехід до другого рядка
LCD.print("Wifi..."); // Вивід тексту на LCD
while (WiFi.status() != WL_CONNECTED) { // Встановлення підключення до Wi-Fi
delay(500);
Serial.print(".");// Вивід крапки в послідовний порт, показуючи процес підключення
}
Serial.println("Hello from ESP8266"); // Вивід тексту в послідовний порт
Serial.print("Connected to hotspot: "); // Вивід тексту в послідовний порт
Serial.println(WIFI_SSID); // Вивід імені підключеної мережі в послідовний порт
Serial.print("IP address is: "); // Вивід тексту в послідовний порт
Serial.println(WiFi.localIP()); // Вивід локальної IP-адреси в послідовний порт
Serial.println("................"); // Вивід розділювача в послідовний порт
LCD.setCursor(19, 0); // Встановлення позиції курсору на LCD
LCD.write(0); // Вивід створеного власного символу на LCD
client.setServer(mqttServer, mqttPort); // Встановлення серверу та порту для клієнта MQTT
client.setCallback(callback); // Встановлення зворотнього виклику для клієнта MQTT
while (!client.connected()) { // Поки клієнт не під'єднаний до серверу MQTT
Serial.println("Connecting to MQTT..."); // Вивід повідомлення в послідовний порт
if (client.connect("ESP_GM", mqttUser, mqttPassword )) { // Підключення до серверу MQTT з іменем користувача та паролем
Serial.println("Connected to MQTT Broker!"); // Вивід повідомлення про успішне підключення
} else {
Serial.print("Failed with state "); // Вивід повідомлення про невдачу підключення
Serial.print(client.state()); // Вивід стану підключення
delay(2000);
}
}
if (client.connect("ESP_GM1")) { // Якщо клієнт знову підключився
for (int i = 0; i < 7; i++) {
client.subscribe(topicsArr[i]); // підписка на теми
}
}
LCD.setCursor(0, 0); // Встановлення позиції курсору на LCD
LCD.println("Updating time..."); // Вивід повідомлення про оновлення часу на LCD
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER); // Налаштування часу через NTP сервер
LCD.clear(); // Очистка дисплею LCD
client.publish("redled1", "0");
client.publish("redled2", "0");
client.publish("redled3", "0");
client.publish("newled1", "0");
client.publish("newled2", "0");
client.publish("newled3", "0");
servo1.attach(18);
servo2.attach(5);
servo1.write(0);
servo2.write(0);
}
//проаналізовано, нічого не придумано
void blink() {
static unsigned long previousMillis = 0; // Зберігає час останнього оновлення дисплея
const long interval = 1000; // Інтервал між миготіннями (у мілісекундах)
unsigned long currentMillis = millis(); // Отримуємо поточний час
if (currentMillis - previousMillis >= interval) {
// Зберігаємо час останнього миготіння дисплея
previousMillis = currentMillis; // Оновлення попереднього часу до поточного, якщо виконана умова інтервалу
// Миготіння статусу WiFi
if (WiFi.status() == WL_CONNECTED) {
static bool blinkWiFi = false; // Зберігає поточний стан миготіння WiFi
LCD.setCursor(19, 0); // Встановлюємо позицію курсора
LCD.write(blinkWiFi ? 0 : 1); // Виводимо символ в залежності від стану blinkWiFi
blinkWiFi = !blinkWiFi; // Змінюємо стан blinkWiFi
}
// Миготіння статусу MQTT клієнта
if (client.connected()) {
static bool blinkMqtt = false; // Зберігає поточний стан миготіння MQTT
LCD.setCursor(18, 0); // Встановлюємо позицію курсора
LCD.write(blinkMqtt ? 2 : 3); // Виводимо символ в залежності від стану blinkMqtt
blinkMqtt = !blinkMqtt; // Змінюємо стан blinkMqtt
}
}
}
//оброблено
void send_temp_hum() {
float currentHumidity = dht.readHumidity();
float currentTemperature = dht.readTemperature();
if (currentHumidity != prevHumidity) {
upd_temp_hum(&prevHumidity, "Hum", currentHumidity);
}
if (currentTemperature != prevTemperature) {
upd_temp_hum(&prevTemperature, "Temp", currentTemperature);
}
LCD.setCursor(0, 2);
LCD.print("Temp:" + String(currentTemperature, 1) + "C Hum:" + String(currentHumidity, 1) + "%");
}
//оброблено
void upd_temp_hum(float* prevParam, const char* nameParam, float newParam) {
client.publish(nameParam, String(newParam).c_str());
*prevParam = newParam;
}
//оброблено
void servo_state() {
for (int i = 0; i < 2; i++) {
int pos = 0;
if (rotary_swich_state[i] > 0 && rotary_swich_state[i] < 4) {
pos = (rotary_swich_state[i] - 1) * 90;
} else if (rotary_swich_state[i] == 4) {
if (i == 0) {
if (temperature <= 14) {
if (humidity >= 60 && humidity <= 100) {
pos = map(humidity, 60, 100, 0, 25);
}
} else if (temperature > 14 && temperature <= 18) {
if (humidity >= 40 && humidity < 60) {
pos = map(humidity, 40, 60, 10, 65);
} else if (humidity >= 60 && humidity <= 100) {
pos = map(humidity, 60, 100, 25, 120);
}
} else {
if (humidity >= 0 && humidity < 40) {
pos = map(humidity, 0, 40, 45, 90);
} else if (humidity >= 40 && humidity < 60) {
pos = map(humidity, 40, 60, 65, 180);
} else if (humidity >= 60 && humidity <= 100) {
pos = map(humidity, 60, 100, 120, 180);
}
}
} else if (i == 1) {
if (temperature > 14 && temperature <= 18) {
pos = map(humidity, 0, 100, 0, 180);
} else if (temperature > 18) {
pos = map(humidity, 0, 100, 90, 180);
}
}
}
if (i == 0) {
servo1.write(pos);
LCD.setCursor(0, 3);
LCD.print("Servo1:");
client.publish("sr1", String(pos).c_str());
}
else {
servo2.write(pos);
LCD.setCursor(10, 3);
LCD.print("Servo2:");
client.publish("sr2", String(pos).c_str());
}
LCD.print((pos < 10) ? String(pos) + " " : (pos < 100) ? String(pos) + " " : String(pos));
}
}
//оброблено
void callback(char* topic, byte* payload, unsigned int length) {
String valueString; // Створення рядка для зберігання декодованого повідомлення
Serial.print("Message arrived in topic: "); // Виведення інформації про топік, в якому отримано повідомлення
Serial.println(topic); // Виведення назви топіка в послідовний порт
Serial.print("Message: "); // Виведення підказки про повідомлення
String topicStr = String(topic);
for (int i = 0; i < length; i++) { // Цикл для обробки отриманого байтового масиву
valueString += (char)payload[i]; // Додавання кожного байта до рядка як символу
}
if (String(topic) == "button") {
if (valueString == "1") {
leds_state_mas[led_flag] = !leds_state_mas[led_flag];
led_flag = (led_flag + 1) % 4;
leds_state(leds_state_mas, previous_leds_state, topics, LED_PINS);
}
} else if (String(topic) == "buttonnew") {
if (valueString == "1") {
new_leds_state_mas[new_led_flag] = !new_leds_state_mas[new_led_flag];
new_led_flag = (new_led_flag + 1) % 4;
leds_state(new_leds_state_mas, new_previous_leds_state, new_topics, NEW_LED_PINS);
}
}
else {
if (topicStr.startsWith("pz")) {
int index = topicStr.substring(2).toInt() - 1;
if (index >= 0 && index < 3) {
time_off_leds_pz[index] = valueString.toInt();
}
} else if (topicStr.startsWith("rs")) {
int index = topicStr.substring(2).toInt() - 1;
if (index >= 0 && index < 2) {
rotary_swich_state[index] = valueString.toInt();
}
}
Serial.println(valueString);
}
}
//проаналізовано, нічого особливого не придумано
void printLocalTime() {
static struct tm prevTimeinfo; // Зберігає попередній час для порівняння
struct tm timeinfo;
char buffer[12];
if (!getLocalTime(&timeinfo)) { // Отримання локального часу
LCD.setCursor(0, 1);
LCD.print("Connection Err "); // Виведення помилки з'єднання на LCD, якщо час не був отриманий
return;
}
// Перевірка, чи час змінився
if (timeinfo.tm_hour != prevTimeinfo.tm_hour || timeinfo.tm_min != prevTimeinfo.tm_min || timeinfo.tm_sec != prevTimeinfo.tm_sec) {
snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
LCD.setCursor(0, 0);
LCD.print(buffer);
}
// Перевірка, чи дата змінилася
if (timeinfo.tm_mday != prevTimeinfo.tm_mday || timeinfo.tm_mon != prevTimeinfo.tm_mon || timeinfo.tm_year != prevTimeinfo.tm_year) {
snprintf(buffer, sizeof(buffer), "%02d/%02d/%02d", timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year % 100);
LCD.setCursor(9, 0);
LCD.print(buffer);
}
// Збереження поточного часу для наступного порівняння
prevTimeinfo = timeinfo;
}
//оброблено
void read_button(int Butt_pin, boolean* led_pin, int* TMP_led_flag)
{
static bool flag_read_button1 = true;
static bool flag_read_button2 = true;
static unsigned long timer1 = 0;
static unsigned long timer2 = 0;
if (Butt_pin == 33) {
if (flag_read_button1) {
if (digitalRead(Butt_pin)) {
flag_read_button1 = false;
timer1 = millis();
}
} else {
if (digitalRead(Butt_pin)) {
timer1 = millis();
} else {
if ((millis() - timer1) > 10) {
*TMP_led_flag = (*TMP_led_flag) % 3;
new_leds_state_mas[*TMP_led_flag] = !new_leds_state_mas[*TMP_led_flag];
*TMP_led_flag = (*TMP_led_flag + 1) % 3;
flag_read_button1 = true;
}
}
}
}
if (Butt_pin == 13) {
if (flag_read_button2) {
if (digitalRead(Butt_pin)) {
flag_read_button2 = false;
timer2 = millis();
}
} else {
if (digitalRead(Butt_pin)) {
timer2 = millis();
} else {
if ((millis() - timer2) > 10) {
*TMP_led_flag = (*TMP_led_flag) % 3;
leds_state_mas[*TMP_led_flag] = !leds_state_mas[*TMP_led_flag];
*TMP_led_flag = (*TMP_led_flag + 1) % 3;
flag_read_button2 = true;
}
}
}
}
}
//проаналізовано, нічого не придумано
void leds_state(bool* TMP_leds_state_mas, int* TMP_previous_leds_state, const String* TMP_topics, const int* TMP_led_pins) {
// Оновлює стан світлодіодів та відправляє дані через MQTT,
// якщо стан будь-якого світлодіода змінився.
for (int i = 0; i < 3; i++) {
if (TMP_leds_state_mas[i] != TMP_previous_leds_state[i]) {
// Якщо стан змінився, оновлюємо світлодіод
digitalWrite(TMP_led_pins[i], TMP_leds_state_mas[i]);
// Перетворюємо String у const char*
const char* topic = TMP_topics[i].c_str();
// Перетворюємо bool у строку "true" або "false"
const char* payload = TMP_leds_state_mas[i] ? "1" : "0";
// Публікуємо повідомлення
client.publish(topic, payload);
TMP_previous_leds_state[i] = TMP_leds_state_mas[i]; // Оновлюємо попередній стан
}
}
}
unsigned long startTimes[3] = {0, 0, 0}; // Час початку відрахунку
bool previousPinState = LOW; // Зберігає попередній стан піну 4
//аналіз
void updateLedsAndDisplay(bool* TMP_leds_state_mas) {
static unsigned long previousMillis = 0;
const long interval = 1000; // Оновлення раз на секунду
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
bool currentPinState = digitalRead(4);
// Перевірка зміни стану піну з HIGH на LOW
if (previousPinState == HIGH && currentPinState == LOW) {
for (int i = 0; i < 3; i++) {
if (TMP_leds_state_mas[i]) {
unsigned long additionalTime = time_off_leds * 60000; // Перетворюємо хвилини в мілісекунди
startTimes[i] += additionalTime; // Додаємо час до таймера
}
}
}
previousPinState = currentPinState; // Оновлюємо попередній стан піну
for (int i = 0; i < 3; i++) {
if (TMP_leds_state_mas[i]) {
if (startTimes[i] == 0) {
startTimes[i] = currentMillis; // Запускає таймери, якщо вони не запущені
}
unsigned long elapsedTimeMillis = currentMillis - startTimes[i];
int remainingTime = time_off_leds_pz[i] * 60000 - elapsedTimeMillis;
if (remainingTime <= 0) { // Вимикає світлодіод, якщо час вимкнення минув
TMP_leds_state_mas[i] = false;
startTimes[i] = 0; // Скидає таймер
lcdPrintOff(i); // Відображає вимкнення на дисплеї
} else {
// Відображає залишковий час на дисплеї
int minutes = remainingTime / 60000;
int seconds = (remainingTime / 1000) % 60;
lcdPrintTime(i, minutes, seconds);
}
} else {
startTimes[i] = 0; // Скидає таймер, якщо світлодіод вимкнений
lcdPrintOff(i); // Відображає вимкнення на дисплеї
}
}
}
}
//оброблено
void lcdPrintTime(int ledNumber, int minutes, int seconds) {
// Функція виводу часу для конкретного світлодіода на LCD-дисплей
LCD.setCursor(ledNumber * 6 + ledNumber, 1);
LCD.print("L");
LCD.print(ledNumber + 1); // Відображення номера світлодіода
LCD.print("^");
if (minutes > 0) {
LCD.print(minutes); // Відображення хвилин
LCD.print("m ");
} else {
if (seconds < 10) {
LCD.print("0");
}
LCD.print(seconds); // Відображення секунд (додавання "0" для однозначних чисел)
LCD.print("s ");
}
}
//оброблено
void lcdPrintOff(int ledNumber) {
// Функція виводу стану "вимкнено" для конкретного світлодіода на LCD-дисплей
LCD.setCursor(ledNumber * 6 + ledNumber, 1);
LCD.print("L");
LCD.print(ledNumber + 1); // Відображення номера світлодіода
LCD.print("^off"); // Відображення стану "вимкнено"
}
void loop()
{
if (!client.connected()) {
// Перепідключення, якщо з'єднання втрачено
Serial.println("Reconnection MQTT");
client.connect("ESP_GM", mqttUser, mqttPassword);
if (client.connect("ESP_GM1")) { // Якщо клієнт знову підключився
for (int i = 0; i < 7; i++) {
client.subscribe(topicsArr[i]); // підписка на теми
}
}
}
client.loop(); // Обробка MQTT-повідомлень
read_button(BUTTON_PIN, leds_state_mas, &led_flag); // Цикл читання кнопки
read_button(NEW_BUTTON_PIN, new_leds_state_mas, &new_led_flag); // Цикл читання кнопки
blink(); // Миготіння світлодіодів
printLocalTime(); // Виведення локального часу на LCD-дисплей
updateLedsAndDisplay(leds_state_mas); // Оновлення та відображення стану світлодіодів на LCD-дисплеї
updateLedsAndDisplay(new_leds_state_mas);
leds_state(leds_state_mas, previous_leds_state, topics, LED_PINS); // Управління станом світлодіодів (відправка через MQTT та оновлення стану)
leds_state(new_leds_state_mas, new_previous_leds_state, new_topics, NEW_LED_PINS);
send_temp_hum();
servo_state();
}