#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <IRremoteESP8266.h>
#include <IRrecv.h>
#include <IRutils.h>
#include <UrlEncode.h>
// =========== Налаштування PIN =============
#define DHTPIN 15
#define DHTTYPE DHT22
#define RED_LED 16
#define YELLOW_LED 17
#define GREEN_LED 18
#define IR_RECEIVE_PIN 19 // ІЧ-приймач
#define IR_SEND_PIN 21 // ІЧ-світлодіод (для майбутнього, зараз лише емуляція)
// =========== WiFi і Telegram ==============
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* telegramToken = "7697064187:AAGLgVHWzTrIX0vEg7QeEZRY3kdi7L3Y32Q";
const char* chatID = "516592988";
// =========== Глобальні об'єкти та змінні ==============
LiquidCrystal_I2C lcd(0x27, 16, 2);
WiFiClientSecure secured_client;
UniversalTelegramBot bot(telegramToken, secured_client);
DHT dht(DHTPIN, DHTTYPE);
IRrecv irrecv(IR_RECEIVE_PIN);
float lastTemp = NAN;
float lastHum = NAN;
unsigned long lastDhtTime = 0;
unsigned long dhtInterval = 300000; // 5 хвилин (300000 мс)
bool irReadMode = false; // Чи активний режим зчитування ІЧ коду
// =========== ОПИС КНОПОК ==============
struct ButtonIR {
const char* name;
uint32_t ir_code;
};
ButtonIR buttonIRs[] = {
{"On", 0x5DA2FF00},
{"Off", 0xFFE21D},
{"Обігрів", 0xFD02FF00},
{"Охолодження", 0xFF22DD},
{"Швидкість вентилятора1", 0xB54AFF00},
{"Швидкість вентилятора2", 0xAD52FF00},
{"Швидкість вентилятора3", 0xFF02FD},
{"16", 0xFF02FD},
{"17", 0xFFC23D},
{"18", 0xFFE01F},
{"19", 0x6798FF00},
{"20", 0xFF906F},
{"21", 0xFF9867},
{"22", 0xFFB04F},
{"23", 0xFF6897},
{"24", 0xCF30FF00},
{"25", 0xE718FF00},
{"26", 0x857AFF00},
{"27", 0xEF10FF00},
{"28", 0xC738FF00},
{"29", 0xA55AFF00},
{"30", 0xBD42FF00},
};
const int numButtons = sizeof(buttonIRs) / sizeof(buttonIRs[0]);
const ButtonIR* getButtonByName(const String& msg) {
for (int i = 0; i < numButtons; i++) {
if (msg.equalsIgnoreCase(buttonIRs[i].name)) {
return &buttonIRs[i];
}
}
return nullptr;
}
// ========== ТЕЛЕГРАМ МЕНЮ =============
String mainMenuKeyboard = "[[\"On\", \"Off\"],[\"Зчитати температуру\", \"Зчитати ІЧ-код\"]]";
String onSubMenuKeyboard = "[[\"Обігрів\", \"Охолодження\"],[\"Швидкість вентилятора\", \"Off\"],[\"Назад\"]]";
String heatTempMenuKeyboard = "[[\"16\",\"17\",\"18\",\"19\",\"20\"],[\"21\",\"22\",\"23\",\"24\",\"25\"],[\"26\",\"27\",\"28\",\"29\",\"30\"],[\"Off\",\"Назад\"]]";
String coolTempMenuKeyboard = heatTempMenuKeyboard;
String fanSpeedMenuKeyboard = "[[\"Швидкість вентилятора1\",\"Швидкість вентилятора2\",\"Швидкість вентилятора3\"],[\"Off\",\"Назад\"]]";
String tempReadMenuKeyboard = "[[\"Off\",\"Назад\"]]";
String irReadMenuKeyboard = "[[\"Закінчити\",\"Off\"]]";
// =========== ФУНКЦІЇ ДЛЯ МЕНЮ ===========
void showMainMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Оберіть дію:", "", mainMenuKeyboard, true);
}
void showOnMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Меню \"On\". Оберіть режим:", "", onSubMenuKeyboard, true);
}
void showHeatMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Обігрів: оберіть температуру:", "", heatTempMenuKeyboard, true);
}
void showCoolMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Охолодження: оберіть температуру:", "", coolTempMenuKeyboard, true);
}
void showFanMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Швидкість вентилятора:", "", fanSpeedMenuKeyboard, true);
}
void showTempReadMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Останні виміри:\nТемпература: " + String(lastTemp) + "C\nВологість: " + String(lastHum) + "%", "", tempReadMenuKeyboard, true);
}
void showIrReadMenu(const String& chat_id) {
bot.sendMessageWithReplyKeyboard(chat_id, "Зчитування ІЧ-коду. Натисніть \"Закінчити\" для виходу.", "", irReadMenuKeyboard, true);
}
// ========== ОБРОБКА ТЕЛЕГРАМ-КОМАНД =========
void processIRCommand(const ButtonIR* btn, const String& chat_id) {
Serial.print("EMULATED IR code: 0x");
Serial.print(btn->ir_code, HEX);
Serial.print(" (");
Serial.print(btn->name);
Serial.println(")");
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Button:");
lcd.setCursor(0, 1);
lcd.print(btn->name);
// Справжня відправка ІЧ-сигналу (у вас тут може бути irsend.sendNEC(btn->ir_code, 32);)
// irsend.sendNEC(btn->ir_code, 32);
bot.sendMessage(chat_id, "Емуляція кнопки: " + String(btn->name) + " (код: 0x" + String(btn->ir_code, HEX) + ")", "");
}
void handleTelegramMessages() {
int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
while (numNewMessages) {
for (int i = 0; i < numNewMessages; i++) {
String chat_id = bot.messages[i].chat_id;
String text = bot.messages[i].text;
if (chat_id != chatID) continue; // Ігноруємо чужі чати
if (irReadMode) {
if (text == "Закінчити") {
irReadMode = false;
bot.sendMessage(chat_id, "Зчитування ІЧ-коду закінчено.");
showMainMenu(chat_id);
}
else if (text == "Off") {
irReadMode = false;
bot.sendMessage(chat_id, "Вимкнено.");
showMainMenu(chat_id);
}
// В режимі зчитування - лише ці кнопки
continue;
}
if (text == "On") {
showOnMenu(chat_id);
} else if (text == "Off") {
bot.sendMessage(chat_id, "Вимкнено.");
showMainMenu(chat_id);
} else if (text == "Зчитати температуру") {
showTempReadMenu(chat_id);
} else if (text == "Зчитати ІЧ-код") {
irReadMode = true;
irrecv.enableIRIn(); // Увімкнути приймач
showIrReadMenu(chat_id);
} else if (text == "Назад") {
showMainMenu(chat_id);
} else if (text == "Обігрів") {
showHeatMenu(chat_id);
} else if (text == "Охолодження") {
showCoolMenu(chat_id);
} else if (text == "Швидкість вентилятора") {
showFanMenu(chat_id);
} else if (text.startsWith("Швидкість вентилятора")) {
const ButtonIR* btn = getButtonByName(text);
if (btn) processIRCommand(btn, chat_id);
showFanMenu(chat_id);
} else if (text == "Off") {
bot.sendMessage(chat_id, "Вимкнено.");
showMainMenu(chat_id);
} else if (text == "Закінчити") {
irReadMode = false;
irrecv.disableIRIn();
bot.sendMessage(chat_id, "Зчитування ІЧ-коду закінчено.");
showMainMenu(chat_id);
} else if (text == "Off") {
bot.sendMessage(chat_id, "Вимкнено.");
showMainMenu(chat_id);
} else if (text.toInt() >= 16 && text.toInt() <= 30) {
// Температурні кнопки для обігріву/охолодження
const ButtonIR* btn = getButtonByName(text);
if (btn) processIRCommand(btn, chat_id);
// Повертаємось у меню вибору температури
// Дізнаємось з якого підменю прийшли (просте повернення у heatMenu)
showHeatMenu(chat_id); // або showCoolMenu(chat_id);
} else {
// Спробуємо знайти кнопку за іменем серед усіх
const ButtonIR* btn = getButtonByName(text);
if (btn) {
processIRCommand(btn, chat_id);
} else {
bot.sendMessage(chat_id, "Невідома кнопка. Доступні кнопки у меню.");
showMainMenu(chat_id);
}
}
}
numNewMessages = bot.getUpdates(bot.last_message_received + 1);
}
}
// ======= DHT, LCD, Telegram =================
void showOnLcd(float temp, float hum) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temp: " + String(temp, 1) + " C");
lcd.setCursor(0, 1);
lcd.print("Hum: " + String(hum, 1) + " %");
}
void sendDhtAlert(float temp, float hum) {
if (WiFi.status() == WL_CONNECTED) {
String message = "ALERT!\nTemperature: " + String(temp) + "C\nHumidity: " + String(hum) + "%";
bot.sendMessage(chatID, message, "");
}
}
// =========== WiFi =============
void connectToWiFi() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Connecting WiFi");
WiFi.begin(ssid, password);
int timeout = 0;
while (WiFi.status() != WL_CONNECTED && timeout < 60) {
delay(500);
Serial.print(".");
timeout++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected");
lcd.clear();
lcd.print("WiFi connected");
digitalWrite(GREEN_LED, HIGH);
digitalWrite(YELLOW_LED, LOW);
digitalWrite(RED_LED, LOW);
} else {
Serial.println("\nFailed to connect WiFi");
lcd.clear();
lcd.print("WiFi FAIL");
digitalWrite(GREEN_LED, LOW);
digitalWrite(YELLOW_LED, LOW);
digitalWrite(RED_LED, HIGH);
}
delay(1000);
lcd.clear();
}
// ============= SETUP ==============
void setup() {
Serial.begin(115200);
pinMode(RED_LED, OUTPUT);
pinMode(YELLOW_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
digitalWrite(RED_LED, LOW);
digitalWrite(YELLOW_LED, LOW);
digitalWrite(GREEN_LED, LOW);
lcd.init();
lcd.backlight();
dht.begin();
irrecv.enableIRIn();
connectToWiFi();
secured_client.setInsecure();
showMainMenu(chatID); // Показати меню при запуску
}
// ============= LOOP ===============
void loop() {
static unsigned long lastBotCheck = 0;
// 1. Обробка DHT22 раз на 5 хвилин
unsigned long now = millis();
if (now - lastDhtTime > dhtInterval || isnan(lastTemp) || isnan(lastHum)) {
float hum = dht.readHumidity();
float temp = dht.readTemperature();
if (!isnan(temp) && !isnan(hum)) {
lastTemp = temp;
lastHum = hum;
showOnLcd(temp, hum);
// Індикація WiFi/статусу
if (WiFi.status() == WL_CONNECTED) {
digitalWrite(GREEN_LED, HIGH);
digitalWrite(YELLOW_LED, LOW);
digitalWrite(RED_LED, LOW);
} else {
digitalWrite(GREEN_LED, LOW);
digitalWrite(RED_LED, HIGH);
connectToWiFi();
}
// Надсилати алерт у Telegram якщо t < 20 або t > 24
if (temp < 20 || temp > 24) {
sendDhtAlert(temp, hum);
}
} else {
lcd.clear();
lcd.print("Sensor error!");
delay(2000);
}
lastDhtTime = now;
}
// 2. Обробка Telegram раз на ~2сек
if (now - lastBotCheck > 2000) {
handleTelegramMessages();
lastBotCheck = now;
}
// 3. Якщо активний режим зчитування ІЧ-коду
if (irReadMode && irrecv.decode()) {
String irMsg = "Зчитано ІЧ-код: 0x" + String(irrecv.decodedIRData.decodedRawData, HEX);
Serial.println(irMsg);
bot.sendMessage(chatID, irMsg, "");
irrecv.resume();
}
}