// === ПІДКЛЮЧЕННЯ БІБЛІОТЕК ===
#include <OneWire.h> // Бібліотека для роботи з протоколом 1-Wire (для датчика DS18B20)
#include <DallasTemperature.h> // Бібліотека для спрощеної роботи з температурним датчиком DS18B20
#include <Wire.h> // Бібліотека для роботи з шиною I2C (для LCD дисплею)
#include <LiquidCrystal_I2C.h> // Бібліотека для керування LCD дисплеєм через I2C інтерфейс
#include <WiFi.h> // Бібліотека для підключення ESP32 до WiFi мережі
#include <HTTPClient.h> // Бібліотека для відправки HTTP запитів (для ThingSpeak)
// === НАЛАШТУВАННЯ ===
const char* SSID = "Wokwi-GUEST"; // Ім'я WiFi мережі до якої підключаємося
const char* PASSWORD = ""; // Пароль WiFi мережі
const char* API_KEY = "TLIBYJC7DINOOQHR"; // API ключ для відправки даних на ThingSpeak платформу
const char* SERVER = "http://api.thingspeak.com/update"; // URL адреса сервера ThingSpeak для оновлення даних
// === ВИЗНАЧЕННЯ ПІНІВ ESP32 ===
#define ONE_WIRE_BUS 14 // для підключення датчика температури DS18B20
#define RELAY_PIN 25 // для керування реле
#define LED_PIN 2 // вбудований LED на ESP32 для індикації стану
#define SDA_PIN 21 // лінія даних I2C (SDA) для LCD дисплею
#define SCL_PIN 22 // лінія тактування I2C (SCL) для LCD дисплею
// === НАЛАШТУВАННЯ ПАРАМЕТРІВ ===
#define TEMP_THRESHOLD 30.0 // Порогова температура для спрацювання реле
#define FILTER_SAMPLES 10 // Кількість зразків для усереднення показань
#define MEASUREMENT_INTERVAL 1000 // Інтервал між вимірюваннями температури
#define BAUD_RATE 115200 // Швидкість передачі даних через послідовний порт
const unsigned long THINGSPEAK_INTERVAL = 20000; // Інтервал відправки даних на ThingSpeak
// === ІНІЦІАЛІЗАЦІЯ ОБ'ЄКТІВ ===
OneWire oneWire(ONE_WIRE_BUS); // Створення об'єкта для роботи з шиною 1-Wire на піні 14
DallasTemperature sensors(&oneWire); // Створення об'єкта для роботи з датчиком DS18B20 через 1-Wire
LiquidCrystal_I2C lcd(0x27, 16, 2); // Створення об'єкта LCD дисплею
// === ГЛОБАЛЬНІ ЗМІННІ ===
float temperatureBuffer[FILTER_SAMPLES]; // Масив для зберігання останніх 10 вимірів температури (для фільтрації)
int bufferIndex = 0; // Поточна позиція запису в буфері
bool bufferFilled = false; // Прапорець: true якщо буфер вже заповнився хоча б раз (для коректного усереднення)
unsigned long lastMeasurementTime = 0; // Час останнього вимірювання температури
float currentTemperature = 0.0; // Поточна відфільтрована температур
float rawTemperature = 0.0; // Остання сира температура з датчика (без фільтрації)
bool relayState = false; // Поточний стан реле
unsigned long measurementCount = 0; // Загальна кількість проведених вимірювань з моменту запуску системи
unsigned long last_cloud = 0; // Час останньої відправки даних на ThingSpeak
bool wifi_ok = false; // Прапорець стану WiFi: true = підключено, false = не підключено
bool manualMode = false; // Прапорець ручного режиму керування: true = ручний, false = автоматичний
// === ПРОТОТИПИ ФУНКЦІЙ ===
void connectWiFi(); // Оголошення функції підключення до WiFi мережі
float calculateMovingAverage(); // Оголошення функції обчислення ковзного середнього (фільтрація температури)
void displayDataOnLCD(); // Оголошення функції виведення даних на LCD дисплей
void handleSerialCommand(char command); // Оголошення функції обробки команд з послідовного порту
void printStatistics(); // Оголошення функції виведення статистики роботи системи
void printHelp(); // Оголошення функції виведення довідки по доступних командах
void printWiFiInfo(); // Оголошення функції виведення інформації про WiFi підключення
void sendToCloud(); // Оголошення функції відправки даних на ThingSpeak
// === ІНІЦІАЛІЗАЦІЯ СИСТЕМИ ===
void setup() {
Serial.begin(BAUD_RATE); // Ініціалізація послідовного порту з заданою швидкістю
delay(1000); // Затримка 1 секунда для стабілізації Serial порту
Serial.println("\n\n================================="); // Виведення верхньої лінії заголовку
Serial.println("СИСТЕМА МОНІТОРИНГУ ТЕМПЕРАТУРИ"); // Виведення назви системи
Serial.println("=================================\n"); // Виведення нижньої лінії заголовку
// === НАЛАШТУВАННЯ ПІНІВ ===
pinMode(RELAY_PIN, OUTPUT); // Встановлення піну 25 як вихід для керування реле
pinMode(LED_PIN, OUTPUT); // Встановлення піну 2 як вихід для керування LED індикатором
digitalWrite(RELAY_PIN, LOW); // Встановлення LOW на піні реле = вимкнення реле при старті
digitalWrite(LED_PIN, LOW); // Встановлення LOW на піні LED = вимкнення світлодіода при старті
// === ІНІЦІАЛІЗАЦІЯ I2C ДЛЯ LCD ===
Wire.begin(SDA_PIN, SCL_PIN); // Ініціалізація шини I2C з вказаними пінами для даних і тактування
lcd.init(); // Ініціалізація LCD дисплею
lcd.backlight(); // Увімкнення підсвітки LCD дисплею
lcd.clear(); // Очищення всіх символів з екрану LCD
// === ПРИВІТАЛЬНЕ ПОВІДОМЛЕННЯ НА LCD ===
lcd.setCursor(0, 0); // Переміщення курсору на позицію 0 рядка 0
lcd.print("Thermometer"); // Виведення тексту "Thermometer" на першому рядку LCD
lcd.setCursor(0, 1); // Переміщення курсору на позицію 0 другого рядка
lcd.print("Initialization..."); // Виведення повідомлення про ініціалізацію на другому рядку LCD
// === ІНІЦІАЛІЗАЦІЯ DS18B20 ===
sensors.begin(); // Ініціалізація датчика DS18B20
delay(500); // Затримка 0.5 секунди для завершення ініціалізації датчика
// === ПЕРЕВІРКА НАЯВНОСТІ ДАТЧИКА ===
int deviceCount = sensors.getDeviceCount(); // Підрахунок кількості знайдених датчиків на шині 1-Wire
Serial.print("Знайдено датчиків DS18B20: "); // Виведення тексту перед числом знайдених датчиків
Serial.println(deviceCount); // Виведення кількості знайдених датчиків
if (deviceCount == 0) { // Перевірка: якщо не знайдено жодного датчика
Serial.println("ПОМИЛКА: Датчик не знайдено!"); // Виведення повідомлення про помилку в Serial
lcd.clear(); // Очищення екрану LCD
lcd.setCursor(0, 0); // Переміщення курсору на початок першого рядка
lcd.print("POMYLKA!"); // Виведення "ПОМИЛКА!" на першому рядку LCD
lcd.setCursor(0, 1); // Переміщення курсору на початок другого рядка
lcd.print("No DS18B20"); // Виведення "No DS18B20" на другому рядку LCD
while (1) { // Безкінечний цикл
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Інверсія стану LED
delay(200); // Затримка 200 мс між миготіннями LED
} // Цикл продовжується безкінечно
}
sensors.setResolution(12); // Встановлення роздільної здатності датчика 12 біт
Serial.println("Роздільна здатність: 12 біт"); // Виведення інформації про встановлену роздільну здатність
// === ІНІЦІАЛІЗАЦІЯ БУФЕРА ФІЛЬТРАЦІЇ ===
for (int i = 0; i < FILTER_SAMPLES; i++) { // Цикл по всіх елементах буфера
temperatureBuffer[i] = 0.0; // Заповнення кожного елемента буфера нульовим значенням
}
// === ПІДКЛЮЧЕННЯ ДО WiFi ===
connectWiFi(); // Виклик функції підключення до WiFi мережі
delay(2000); // Затримка 2 секунди для відображення привітального повідомлення
lcd.clear(); // Очищення екрану LCD перед початком основної роботи
Serial.println("Система готова до роботи!\n"); // Виведення повідомлення про готовність системи до роботи
Serial.println("Час(мс)\tСира T\tФільтр T\tРеле\tВимір#"); // Виведення заголовків колонок таблиці даних
Serial.println("-------\t------\t--------\t----\t------"); // Виведення розділювача під заголовками таблиці
}
// === ОСНОВНИЙ ЦИКЛ ПРОГРАМИ ===
void loop() { // Функція основного циклу
unsigned long now = millis(); // Отримання поточного часу з моменту запуску ESP32
// === ВІДПРАВКА ДАНИХ В ХМАРУ ===
if (now - last_cloud >= THINGSPEAK_INTERVAL && wifi_ok) { // Перевірка: чи минуло 20 секунд з останньої відправки і чи є WiFi підключення
sendToCloud(); // Виклик функції відправки даних на ThingSpeak
last_cloud = now; // Збереження поточного часу як час останньої відправки
}
// === ВИМІРЮВАННЯ ТЕМПЕРАТУРИ ===
if (now - lastMeasurementTime >= MEASUREMENT_INTERVAL) { // Перевірка: чи минула 1 секунда з останнього вимірювання
lastMeasurementTime = now; // Оновлення часу останнього вимірювання на поточний час
measurementCount++; // Збільшення лічильника загальної кількості вимірювань на 1
// === ЗЧИТУВАННЯ ТЕМПЕРАТУРИ ===
sensors.requestTemperatures(); // Відправка команди датчику для початку вимірювання температури
rawTemperature = sensors.getTempCByIndex(0); // Отримання виміряної температури в градусах Цельсія з датчика
// === ПЕРЕВІРКА ВАЛІДНОСТІ ДАНИХ ===
if (rawTemperature == DEVICE_DISCONNECTED_C || rawTemperature < -50 || rawTemperature > 100) { // Перевірка на помилкові дані
Serial.println("ПОМИЛКА: Некоректні дані від датчика!"); // Виведення повідомлення про помилку в Serial Monitor
lcd.setCursor(0, 0); // Переміщення курсору на початок першого рядка LCD
lcd.print("POMYLKA DATCHYKA"); // Виведення повідомлення про помилку датчика на LCD
return; // Вихід з функції loop()
}
// === ДОДАВАННЯ ДО БУФЕРА ФІЛЬТРАЦІЇ ===
temperatureBuffer[bufferIndex] = rawTemperature; // Збереження нового виміру в поточну позицію циклічного буфера
bufferIndex++; // Переміщення індексу на наступну позицію в буфері
if (bufferIndex >= FILTER_SAMPLES) { // Перевірка: чи досягнуто кінця буфера
bufferIndex = 0; // Скидання індексу на початок буфера
bufferFilled = true; // Встановлення прапорця що буфер заповнений хоча б раз
}
// === ОБЧИСЛЕННЯ КОВЗНОГО СЕРЕДНЬОГО ===
currentTemperature = calculateMovingAverage(); // Виклик функції обчислення середнього значення з буфера (фільтрація шумів)
// === КЕРУВАННЯ РЕЛЕ (АВТОМАТИЧНИЙ РЕЖИМ) ===
if (!manualMode) { // Перевірка: чи НЕ увімкнений ручний режим (автоматичний режим активний)
if (currentTemperature >= TEMP_THRESHOLD) { // Перевірка: чи температура досягла або перевищила поріг
if (!relayState) { // Перевірка: чи реле ще не увімкнене (щоб не увімкнути повторно)
digitalWrite(RELAY_PIN, HIGH); // Встановлення HIGH на піні = увімкнення реле
digitalWrite(LED_PIN, HIGH); // Увімкнення LED індикатора (синхронізація з реле)
relayState = true; // Оновлення змінної стану реле на "увімкнено"
Serial.println("\n>>> РЕЛЕ УВІМКНЕНО <<<"); // Виведення повідомлення про увімкнення реле в Serial Monitor
}
} else if (currentTemperature < TEMP_THRESHOLD - 1.0) { // Перевірка: чи температура нижча за поріг мінус гістерезис
if (relayState) { // Перевірка: чи реле увімкнене (щоб не вимкнути вже вимкнене)
digitalWrite(RELAY_PIN, LOW); // Встановлення LOW на піні = вимкнення реле
digitalWrite(LED_PIN, LOW); // Вимкнення LED індикатора (синхронізація з реле)
relayState = false; // Оновлення змінної стану реле на "вимкнено"
Serial.println("\n>>> РЕЛЕ ВИМКНЕНО <<<"); // Виведення повідомлення про вимкнення реле в Serial Monitor
}
}
}
// === ВИВЕДЕННЯ ДАНИХ НА LCD ===
displayDataOnLCD(); // Виклик функції оновлення інформації на LCD дисплеї
// === ВИВЕДЕННЯ ДАНИХ В SERIAL MONITOR (ТАБЛИЦЯ) ===
Serial.print(now); // Виведення поточного часу з моменту запуску
Serial.print("\t"); // Виведення символу табуляції (для форматування колонок таблиці)
Serial.print(rawTemperature, 2); // Виведення сирої температури з 2 знаками після коми
Serial.print("\t"); // Виведення символу табуляції
Serial.print(currentTemperature, 2); // Виведення відфільтрованої температури з 2 знаками після коми
Serial.print("\t\t"); // Виведення двох символів табуляції
Serial.print(relayState ? "ON " : "OFF"); // Виведення "ON " якщо реле увімкнене, інакше "OFF"
Serial.print("\t"); // Виведення символу табуляції
Serial.println(measurementCount); // Виведення номера поточного вимірювання з переходом на новий рядок
}
// === ОБРОБКА КОМАНД З SERIAL ПОРТУ ===
if (Serial.available() > 0) { // Перевірка: чи є дані в буфері послідовного порту
char command = Serial.read(); // Зчитування одного символу з послідовного порту
handleSerialCommand(command); // Виклик функції обробки отриманої команди
}
delay(10); // Затримка 10 мілісекунд для стабільності роботи циклу loop()
}
// === ФУНКЦІЯ ПІДКЛЮЧЕННЯ ДО WiFi ===
void connectWiFi() { // Функція підключення ESP32 до WiFi мережі
Serial.println("─────────────────────────────────────"); // Виведення верхньої розділювальної лінії для візуального відділення
Serial.print("Підключення до WiFi "); // Виведення тексту про початок процесу підключення
Serial.print(SSID); // Виведення імені WiFi мережі до якої підключаємося
Serial.println("..."); // Виведення трьох крапок з переходом на новий рядок
WiFi.mode(WIFI_STA); // Встановлення режиму WiFi як "станція"
WiFi.begin(SSID, PASSWORD); // Початок підключення до WiFi з вказаним ім'ям мережі і паролем
int attempts = 0; // Ініціалізація лічильника спроб підключення
while (WiFi.status() != WL_CONNECTED && attempts < 20) { // Цикл очікування підключення
delay(500); // Затримка 0.5 секунди між перевірками стану підключення
Serial.print("."); // Виведення крапки для візуалізації процесу підключення
attempts++; // Збільшення лічильника спроб на 1
}
Serial.println(); // Перехід на новий рядок після виведення крапок
if (WiFi.status() == WL_CONNECTED) { // Перевірка: чи вдалося успішно підключитися до WiFi
wifi_ok = true; // Встановлення прапорця успішного підключення
Serial.println("WiFi підключено!"); // Виведення повідомлення про успішне підключення
Serial.print("IP адреса: "); // Виведення тексту перед IP адресою
Serial.println(WiFi.localIP()); // Виведення отриманої IP адреси ESP32 в локальній мережі
} else { // Якщо підключення не вдалося після 20 спроб
wifi_ok = false; // Скидання прапорця підключення
Serial.println("WiFi не підключено"); // Виведення повідомлення про невдачу підключення
}
Serial.println("─────────────────────────────────────\n"); // Виведення нижньої розділювальної лінії
}
// === ФУНКЦІЯ ОБЧИСЛЕННЯ КОВЗНОГО СЕРЕДНЬОГО ===
float calculateMovingAverage() { // Функція обчислення середнього арифметичного з буфера температур
float sum = 0.0; // Ініціалізація змінної для накопичення суми всіх значень в буфері
int samplesToUse = bufferFilled ? FILTER_SAMPLES : bufferIndex; // Визначення кількості зразків: 10 якщо буфер заповнений повністю, інакше поточний індекс
for (int i = 0; i < samplesToUse; i++) { // Цикл по всіх доступних зразках у буфері
sum += temperatureBuffer[i]; // Додавання значення i-го елементу буфера до загальної суми
}
return sum / samplesToUse; // Повернення середнього значення
}
// === ФУНКЦІЯ ВІДОБРАЖЕННЯ ДАНИХ НА LCD ===
void displayDataOnLCD() { // Функція оновлення інформації на LCD дисплеї
// === ПЕРШИЙ РЯДОК: ТЕМПЕРАТУРА ТА ВІДХИЛЕННЯ ===
lcd.setCursor(0, 0); // Переміщення курсору на позицію 0 рядка 0
lcd.print("T:"); // Виведення префіксу "T:" перед значенням температури
lcd.print(currentTemperature, 1); // Виведення відфільтрованої температури з 1 знаком після коми
lcd.print((char)223); // Виведення спеціального символу градуса з ASCII таблиці
lcd.print("C "); // Виведення позначення шкали Цельсія з пробілом
float deviation = currentTemperature - TEMP_THRESHOLD; // Обчислення відхилення поточної температури від заданого порогу
if (deviation > 0) { // Перевірка: чи відхилення позитивне
lcd.print("+"); // Виведення знаку "+" перед позитивним числом
lcd.print(deviation, 1); // Виведення значення відхилення з 1 знаком після коми
} else { // Якщо відхилення негативне або нуль
lcd.print(deviation, 1); // Виведення відхилення
}
lcd.print(" "); // Виведення двох пробілів для очищення залишку рядка від старих символів
// === ДРУГИЙ РЯДОК: РЕЖИМ, СТАН РЕЛЕ ТА ЛІЧИЛЬНИК ===
lcd.setCursor(0, 1); // Переміщення курсору на позицію 0 рядка 1
// Індикатор режиму роботи (ручний або автоматичний)
if (manualMode) { // Перевірка: чи увімкнений ручний режим керування
lcd.print("M:"); // Виведення "M:" (Manual = ручний режим)
} else { // Якщо автоматичний режим
lcd.print("A:"); // Виведення "A:" (Auto = автоматичний режим)
}
// Стан реле
if (relayState) { // Перевірка: чи реле увімкнене
lcd.print("ON "); // Виведення "ON "
lcd.print((char)255); // Виведення символу заповненого квадрата як візуальний індикатор
} else { // Якщо реле вимкнене
lcd.print("OFF"); // Виведення "OFF"
lcd.print(" "); // Виведення двох пробілів для очищення залишку
}
// Лічильник вимірювань
lcd.setCursor(10, 1); // Переміщення курсору на позицію 10 другого рядка
lcd.print("#"); // Виведення символу "#" перед номером вимірювання
lcd.print(measurementCount % 1000); // Виведення номера вимірювання
}
// === ФУНКЦІЯ ОБРОБКИ КОМАНД ===
void handleSerialCommand(char command) { // Функція обробки команд отриманих через Serial Monitor
// === ІГНОРУВАННЯ ПРОБІЛІВ ТА СПЕЦІАЛЬНИХ СИМВОЛІВ ===
if (command == ' ' || command == '\n' || command == '\r' || command == '\t') { // Перевірка: чи це пробіл, перенос рядка, повернення каретки або табуляція
return; // Ігнорувати службові символи та вийти з функції без обробки
}
Serial.print("\nОтримано команду: "); // Виведення тексту перед символом команди
Serial.println(command); // Виведення отриманого символу команди з переходом на новий рядок
switch (command) { // Початок блоку вибору команди
// === КОМАНДА 'r' - ПЕРЕЗАВАНТАЖЕННЯ ===
case 'r': // Команда 'r' - перезапуск системи
case 'R': // Команда 'R' - перезапуск системи
Serial.println("Перезавантаження ESP32..."); // Виведення повідомлення про початок перезавантаження
delay(500); // Коротка затримка 0.5 секунди перед перезапуском
ESP.restart(); // Програмне перезавантаження мікроконтролера ESP32
break; // Вихід з блоку switch
// === КОМАНДА 's' - СТАТИСТИКА ===
case 's': // Команда 's' - вивести статистику
case 'S': // Команда 'S' - вивести статистику
printStatistics(); // Виклик функції виведення детальної статистики роботи системи
break; // Вихід з блоку switch після виконання команди
// === КОМАНДА 'm' - РУЧНИЙ РЕЖИМ ===
case 'm': // Команда 'm' - перемикання ручного режиму
case 'M': // Команда 'M' - перемикання ручного режиму
manualMode = !manualMode; // Інверсія прапорця: якщо був false стає true, якщо був true стає false
if (manualMode) { // Перевірка: чи тепер увімкнено ручний режим
Serial.println(">>> РУЧНИЙ РЕЖИМ УВІМКНЕНО <<<"); // Виведення повідомлення про увімкнення ручного режиму
Serial.println("Автоматика ВИМКНЕНА. Натисніть 'a' для повернення до авто."); // Виведення підказки як повернутися до автоматичного режиму
relayState = !relayState; // Перемикання стану реле
digitalWrite(RELAY_PIN, relayState ? HIGH : LOW); // Встановлення фізичного стану піна реле відповідно до змінної relayState
digitalWrite(LED_PIN, relayState ? HIGH : LOW); // Синхронізація стану LED індикатора зі станом реле
} else { // Якщо ручний режим було вимкнено
Serial.println(">>> АВТОМАТИЧНИЙ РЕЖИМ УВІМКНЕНО <<<"); // Виведення повідомлення про увімкнення автоматичного режиму
Serial.println("Реле керується температурою."); // Виведення підказки що реле тепер керується автоматично
}
Serial.print("Реле зараз: "); // Виведення тексту перед станом реле
Serial.println(relayState ? "УВІМКНЕНО" : "ВИМКНЕНО"); // Виведення поточного стану реле
displayDataOnLCD(); // Оновлення інформації на LCD дисплеї
break; // Вихід з блоку switch після виконання команди
// === КОМАНДА 'h' - ДОВІДКА ===
case 'h': // Команда 'h' - показати довідку
case 'H': // Команда 'H' - показати довідку
printHelp(); // Виклик функції виведення списку всіх доступних команд
break; // Вихід з блоку switch після виконання команди
// === КОМАНДА 't' - ТЕСТОВА ВІДПРАВКА ===
case 't': // Команда 't' - тестова відправка на ThingSpeak
case 'T': // Команда 'T' - тестова відправка на ThingSpeak
Serial.println("Тестова відправка на ThingSpeak..."); // Виведення повідомлення про початок тестової відправки
sendToCloud(); // Примусовий виклик функції відправки даних на ThingSpeak
break; // Вихід з блоку switch після виконання команди
// === КОМАНДА 'a' - АВТОМАТИЧНИЙ РЕЖИМ ===
case 'a': // Команда 'a' - повернення до автоматичного режиму
case 'A': // Команда 'A' - повернення до автоматичного режиму
manualMode = false; // Встановлення прапорця ручного режиму в false (автоматичний режим)
Serial.println(">>> АВТОМАТИЧНИЙ РЕЖИМ УВІМКНЕНО <<<"); // Виведення повідомлення про увімкнення автоматичного режиму
Serial.println("Реле тепер керується температурою."); // Виведення підказки що реле знову під контролем автоматики
displayDataOnLCD(); // Оновлення інформації на LCD дисплеї
break; // Вихід з блоку switch після виконання команди
// === КОМАНДА 'w' - ІНФОРМАЦІЯ ПРО WiFi ===
case 'w': // Команда 'w' - показати інформацію про WiFi
case 'W': // Команда 'W' - показати інформацію про WiFi
printWiFiInfo(); // Виклик функції виведення детальної інформації про WiFi підключення
break; // Вихід з блоку switch після виконання команди
// === НЕВІДОМА КОМАНДА ===
default: // Якщо введений символ не відповідає жодній з перелічених команд
Serial.print("Невідома команда '"); // Виведення тексту про невідому команду
Serial.print(command); // Виведення символу який було введено
Serial.print("' (код: "); // Виведення тексту перед ASCII кодом
Serial.print((int)command); // Виведення ASCII коду символу
Serial.println("). Введіть 'h' для довідки."); // Виведення підказки як отримати список команд
break; // Вихід з блоку switch
}
}
// === ФУНКЦІЯ ВИВЕДЕННЯ СТАТИСТИКИ ===
void printStatistics() { // Функція виведення детальної статистики роботи системи
Serial.println("\n========== СТАТИСТИКА =========="); // Виведення верхнього заголовку блоку статистики
Serial.print("Кількість вимірювань: "); // Виведення тексту перед числом вимірювань
Serial.println(measurementCount); // Виведення загальної кількості проведених вимірювань з моменту запуску
Serial.print("Поточна температура: "); // Виведення тексту перед відфільтрованою температурою
Serial.print(currentTemperature, 2); // Виведення відфільтрованої температури з 2 знаками після коми
Serial.println(" °C"); // Виведення одиниці виміру "°C" з переходом на новий рядок
Serial.print("Сира температура: "); // Виведення тексту перед неусередненою температурою
Serial.print(rawTemperature, 2); // Виведення останньої сирої температури з датчика з 2 знаками після коми
Serial.println(" °C"); // Виведення одиниці виміру "°C" з переходом на новий рядок
Serial.print("Різниця (сира - фільтр): "); // Виведення тексту перед різницею температур
Serial.print(rawTemperature - currentTemperature, 3); // Виведення різниці між сирою та відфільтрованою температурою з 3 знаками після коми
Serial.println(" °C"); // Виведення одиниці виміру "°C" з переходом на новий рядок
Serial.print("Поріг спрацювання: "); // Виведення тексту перед пороговою температурою
Serial.print(TEMP_THRESHOLD, 1); // Виведення порогової температури з 1 знаком після коми
Serial.println(" °C"); // Виведення одиниці виміру "°C" з переходом на новий рядок
Serial.print("Стан реле: "); // Виведення тексту перед станом реле
Serial.println(relayState ? "УВІМКНЕНО" : "ВИМКНЕНО"); // Виведення "УВІМКНЕНО" якщо relayState=true, інакше "ВИМКНЕНО"
Serial.print("Режим роботи: "); // Виведення тексту перед режимом роботи
Serial.println(manualMode ? "РУЧНИЙ" : "АВТОМАТИЧНИЙ"); // Виведення "РУЧНИЙ" якщо manualMode=true, інакше "АВТОМАТИЧНИЙ"
Serial.print("Час роботи: "); // Виведення тексту перед часом роботи
Serial.print(millis() / 1000); // Виведення часу роботи системи в секундах
Serial.println(" секунд"); // Виведення одиниці виміру "секунд" з переходом на новий рядок
Serial.print("WiFi статус: "); // Виведення тексту перед статусом WiFi
Serial.println(wifi_ok ? "Підключено" : "Не підключено"); // Виведення "Підключено" якщо wifi_ok=true, інакше "Не підключено"
Serial.println("================================\n"); // Виведення нижньої лінії блоку статистики з переходом на новий рядок
}
// === ФУНКЦІЯ ВИВЕДЕННЯ ДОВІДКИ ===
void printHelp() { // Функція виведення списку всіх доступних команд
Serial.println("\n========== ДОВІДКА =========="); // Виведення верхнього заголовку блоку довідки
Serial.println("Доступні команди:"); // Виведення заголовку списку команд
Serial.println(" r/R - Перезавантажити ESP32"); // Опис команди перезавантаження мікроконтролера
Serial.println(" s/S - Вивести статистику"); // Опис команди виведення детальної статистики системи
Serial.println(" m/M - Перемкнути РУЧНИЙ режим (вимкнути авто)"); // Опис команди перемикання ручного режиму керування
Serial.println(" a/A - Повернути АВТОМАТИЧНИЙ режим"); // Опис команди повернення до автоматичного режиму
Serial.println(" w/W - Інформація про WiFi"); // Опис команди виведення інформації про WiFi підключення
Serial.println(" t/T - Тестова відправка на ThingSpeak"); // Опис команди примусової відправки даних на ThingSpeak
Serial.println(" h/H - Показати цю довідку"); // Опис команди виведення цієї довідки
Serial.println("=============================\n"); // Виведення нижньої лінії блоку довідки з переходом на новий рядок
}
// === ФУНКЦІЯ ВИВЕДЕННЯ ІНФОРМАЦІЇ ПРО WiFi ===
void printWiFiInfo() { // Функція виведення детальної інформації про WiFi підключення
Serial.println("\n========== WiFi ІНФО =========="); // Виведення верхнього заголовку блоку інформації про WiFi
if (wifi_ok) { // Перевірка: чи WiFi успішно підключено
Serial.println("Статус: Підключено"); // Виведення статусу "Підключено"
Serial.print("SSID: "); // Виведення тексту перед назвою мережі
Serial.println(WiFi.SSID()); // Виведення назви WiFi мережі до якої підключений ESP32
Serial.print("IP адреса: "); // Виведення тексту перед IP адресою
Serial.println(WiFi.localIP()); // Виведення IP адреси ESP32 в локальній мережі
Serial.print("Потужність сигналу: "); // Виведення тексту перед рівнем сигналу
Serial.print(WiFi.RSSI()); // Виведення потужності WiFi сигналу в dBm
Serial.println(" dBm"); // Виведення одиниці виміру потужності сигналу
Serial.print("MAC адреса: "); // Виведення тексту перед MAC адресою
Serial.println(WiFi.macAddress()); // Виведення унікальної MAC адреси WiFi модуля ESP32
} else { // Якщо WiFi не підключено
Serial.println("Статус: Не підключено"); // Виведення статусу "Не підключено"
Serial.println("Спробуйте перезавантажити ESP32 (команда 'r')"); // Порада користувачу як спробувати вирішити проблему
}
Serial.println("================================\n"); // Виведення нижньої лінії блоку інформації з переходом на новий рядок
}
// === ФУНКЦІЯ ВІДПРАВКИ ДАНИХ НА THINGSPEAK ===
void sendToCloud() { // Функція відправки даних на хмарну платформу ThingSpeak
if (!wifi_ok) { // Перевірка: чи є активне підключення до WiFi
Serial.println("WiFi не підключено - пропуск відправки"); // Виведення повідомлення про неможливість відправки без WiFi
return; // Вихід з функції без відправки даних
}
HTTPClient http; // Створення об'єкта HTTP клієнта для відправки запитів
String url = String(SERVER); // Ініціалізація рядка URL базовою адресою сервера ThingSpeak
url += "?api_key=" + String(API_KEY); // Додавання до URL параметру api_key з унікальним ключем доступу
url += "&field1=" + String(rawTemperature, 2); // Додавання field1 - сира температура з датчика з 2 знаками після коми
url += "&field2=" + String(currentTemperature, 2); // Додавання field2 - відфільтрована температура з 2 знаками після коми
url += "&field3=" + String(relayState ? 1 : 0); // Додавання field3 - стан реле
url += "&field4=" + String(currentTemperature - TEMP_THRESHOLD, 2); // Додавання field4 - відхилення температури від порогу з 2 знаками після коми
url += "&field5=" + String(measurementCount); // Додавання field5 - номер поточного вимірювання
Serial.println("╔═══════════════════════════════════╗"); // Виведення верхньої декоративної лінії повідомлення
Serial.println("║ ВІДПРАВКА В THINGSPEAK ║"); // Виведення заголовку в красивій рамці
Serial.println("╚═══════════════════════════════════╝"); // Виведення нижньої декоративної лінії повідомлення
http.begin(url); // Ініціалізація HTTP запиту зі сформованим URL
http.setTimeout(5000); // Встановлення таймауту запиту 5000 мілісекунд - максимальний час очікування відповіді
int code = http.GET(); // Відправка GET запиту на сервер та отримання HTTP коду відповіді
if (code == 200) { // Перевірка: чи HTTP запит виконано успішно
String response = http.getString(); // Отримання тіла відповіді від сервера
Serial.print("Успішно! Entry ID: "); // Виведення повідомлення про успішну відправку даних
Serial.println(response); // Виведення ID створеного запису на ThingSpeak
} else { // Якщо код відповіді не 200
Serial.print("Помилка HTTP: "); // Виведення тексту про помилку відправки
Serial.println(code); // Виведення коду помилки HTTP
}
http.end(); // Закриття HTTP з'єднання та звільнення пам'яті
Serial.println(); // Виведення порожнього рядка для візуального відступу в Serial Monitor
}