#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
// ===== НАСТРОЙКИ ПОДКЛЮЧЕНИЯ =====
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// MQTT брокер (HiveMQ)
const char* mqtt_server = "broker.hivemq.com";
const int mqtt_port = 1883;
// Подписываемся на данные всех зон
const char* TOPIC_ZONE1_DATA = "farm/zone1/data";
const char* TOPIC_ZONE2_DATA = "farm/zone2/data";
const char* TOPIC_ZONE1_COMMANDS_LIGHTS = "farm/zone1/commands/lights";
const char* TOPIC_ZONE2_COMMANDS_LIGHTS = "farm/zone2/commands/lights";
const char* TOPIC_ZONE1_COMMANDS_SERVO = "farm/zone1/commands/servo";
const char* TOPIC_ZONE2_COMMANDS_SERVO = "farm/zone2/commands/servo";
const char* TOPIC_ZONE1_COMMANDS_NEOPIXEL = "farm/zone1/commands/neopixel";
const char* TOPIC_ZONE2_COMMANDS_NEOPIXEL = "farm/zone2/commands/neopixel";
// Светодиоды (согласно вашей схеме)
const int BLUE_LED_PIN = 12; // Подключение к брокеру
const int YELLOW_LED_PIN = 13; // Получение данных
#define BTN_LEFT 26
#define BTN_RIGHT 27
const unsigned long BUTTON_DEBOUNCE = 50;
const unsigned long LONG_PRESS_TIME = 800;
// LCD дисплей (20x4, адрес 0x27)
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ===== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ =====
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// Данные с датчиков
// float temperature = 0.0;
// float humidity = 0.0;
// int packet_count = 0;
// unsigned long last_received_time = 0;
// bool data_received = false;
struct ZoneData {
float temperature = 0.0;
float humidity = 0.0;
unsigned long lastUpdate = 0;
bool connected = false;
};
ZoneData zones[2]; // zones[0] = Zone 1, zones[1] = Zone 2
int currentZone = 0; // 0: Zone 1, 1: Zone 2
int packet_count = 0;
// Переменные для мигания светодиода
unsigned long led_blink_start = 0;
int blink_count = 0;
bool blink_active = false;
unsigned long lastLeftBtnPress = 0;
unsigned long lastRightBtnPress = 0;
bool leftBtnPressed = false;
bool rightBtnPressed = false;
// ===== ФУНКЦИИ ДЛЯ РАБОТЫ С ЖК-ДИСПЛЕЕМ =====
void setupLCD() {
lcd.init();
lcd.backlight();
lcd.clear();
// Отображаем начальный экран
lcd.setCursor(0, 0);
lcd.print("FARM RECEIVER v2.0");
lcd.setCursor(0, 1);
lcd.print("Connecting WiFi...");
}
void updateDisplay() {
lcd.clear();
// Строка 1: ZONE и навигация
lcd.setCursor(0, 0);
lcd.print("ZONE:");
lcd.print(currentZone + 1);
lcd.print(" ");
// Показываем стрелки навигации
lcd.setCursor(10, 0);
lcd.print("<");
lcd.setCursor(12, 0);
lcd.print(">");
// Если зона онлайн, показываем индикатор
if (zones[currentZone].connected) {
lcd.setCursor(18, 0);
lcd.print("*");
}
// Строка 2: Температура и влажность
lcd.setCursor(0, 1);
lcd.print("T:");
if (zones[currentZone].temperature > -100) {
lcd.print(zones[currentZone].temperature, 1);
lcd.print("C");
} else {
lcd.print("--.-C");
}
lcd.setCursor(11, 1);
lcd.print("H:");
if (zones[currentZone].humidity >= 0) {
lcd.print(zones[currentZone].humidity, 0);
lcd.print("%");
} else {
lcd.print("--%");
}
// Строка 3: Время последнего обновления
lcd.setCursor(0, 2);
if (zones[currentZone].connected) {
unsigned long secondsAgo = (millis() - zones[currentZone].lastUpdate) / 1000;
lcd.print("Updated ");
lcd.print(secondsAgo);
lcd.print("s ago");
} else {
lcd.print("No data received");
}
// Строка 4: Статус соединения MQTT
lcd.setCursor(0, 3);
lcd.print("MQTT:");
lcd.print(mqttClient.connected() ? "OK" : "NO");
lcd.print(" Pkts:");
lcd.print(packet_count);
}
// ===== ФУНКЦИИ ДЛЯ СВЕТОДИОДОВ =====
void blinkYellowLED() {
// Запускаем процесс мигания: 3 быстрых мигания
blink_active = true;
blink_count = 0;
led_blink_start = millis();
}
void handleButtons() {
unsigned long currentTime = millis();
// Левая кнопка - предыдущая зона
if (digitalRead(BTN_LEFT) == LOW) {
if (!leftBtnPressed && (currentTime - lastLeftBtnPress > BUTTON_DEBOUNCE)) {
leftBtnPressed = true;
lastLeftBtnPress = currentTime;
currentZone--;
if (currentZone < 0) currentZone = 1; // Всего 2 зоны
Serial.print("Switched to Zone ");
Serial.println(currentZone + 1);
updateDisplay();
}
} else {
leftBtnPressed = false;
}
// Правая кнопка - следующая зона
if (digitalRead(BTN_RIGHT) == LOW) {
if (!rightBtnPressed && (currentTime - lastRightBtnPress > BUTTON_DEBOUNCE)) {
rightBtnPressed = true;
lastRightBtnPress = currentTime;
currentZone++;
if (currentZone > 1) currentZone = 0; // Всего 2 зоны
Serial.print("Switched to Zone ");
Serial.println(currentZone + 1);
updateDisplay();
}
} else {
rightBtnPressed = false;
}
}
void updateLEDs() {
// Синий светодиод: горит когда подключен к MQTT
digitalWrite(BLUE_LED_PIN, mqttClient.connected() ? HIGH : LOW);
// Управление желтым светодиодом (мигание)
if (blink_active) {
unsigned long current_time = millis();
unsigned long elapsed = current_time - led_blink_start;
// Быстрое мигание: 100ms ON, 100ms OFF
int cycle_duration = 200; // 200ms на полный цикл (ON+OFF)
int cycle_position = elapsed % cycle_duration;
if (cycle_position < 100) {
digitalWrite(YELLOW_LED_PIN, HIGH);
} else {
digitalWrite(YELLOW_LED_PIN, LOW);
}
// Проверяем, завершили ли мы 3 мигания (6 полупериодов)
if (elapsed >= 1200) { // 200ms * 6 = 1200ms
blink_active = false;
digitalWrite(YELLOW_LED_PIN, LOW);
}
} else {
digitalWrite(YELLOW_LED_PIN, LOW);
}
}
// ===== ФУНКЦИИ ДЛЯ WIFI =====
void connectToWiFi() {
Serial.print("Connecting to WiFi");
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi connection failed!");
}
}
// ===== ФУНКЦИИ ДЛЯ MQTT =====
void mqttCallback(char* topic, byte* payload, unsigned int length) {
// Преобразуем payload в строку
char message[length + 1];
memcpy(message, payload, length);
message[length] = '\0';
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("]: ");
Serial.println(message);
// Определяем, для какой зоны пришли данные
String topicStr = String(topic);
int zoneIndex = -1;
if (topicStr == TOPIC_ZONE1_DATA) {
zoneIndex = 0; // Zone 1
} else if (topicStr == TOPIC_ZONE2_DATA) {
zoneIndex = 1; // Zone 2
}
if (zoneIndex != -1) {
// Парсим JSON данные
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, message);
if (!error) {
// Извлекаем значения из JSON
float temp = doc["temperature"];
float hum = doc["humidity"];
// int zone = doc["zone"]; // Можно проверить соответствие
// Сохраняем в структуру зоны
zones[zoneIndex].temperature = temp;
zones[zoneIndex].humidity = hum;
zones[zoneIndex].lastUpdate = millis();
zones[zoneIndex].connected = true;
packet_count++;
// Обновляем дисплей, если отображается эта зона
if (zoneIndex == currentZone) {
updateDisplay();
}
// Мигаем желтым светодиодом
blinkYellowLED();
Serial.print("Zone ");
Serial.print(zoneIndex + 1);
Serial.print(" - Temp: ");
Serial.print(temp);
Serial.print("C, Hum: ");
Serial.print(hum);
Serial.println("%");
} else {
Serial.print("JSON parsing failed: ");
Serial.println(error.c_str());
}
} else {
Serial.println("Unknown topic received");
}
}
void reconnectMQTT() {
// Пытаемся переподключиться
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
// Создаем уникальный Client ID
String clientId = "Farm_Receiver_";
clientId += String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
Serial.println("connected!");
// ===== ПОДПИСКА НА ТОПИКИ (ЯВНО УКАЗАНЫ) =====
// Подписываемся на данные обеих зон
bool sub1 = mqttClient.subscribe(TOPIC_ZONE1_DATA);
bool sub2 = mqttClient.subscribe(TOPIC_ZONE2_DATA);
Serial.println("Subscribed to topics:");
if (sub1) Serial.println(" - " + String(TOPIC_ZONE1_DATA));
if (sub2) Serial.println(" - " + String(TOPIC_ZONE2_DATA));
// Обновляем дисплей
updateDisplay();
} else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
// Функция для отправки команд в зоны (если потребуется)
void sendCommandToZone(int zoneNumber, String device, String command) {
String topic;
if (zoneNumber == 1) {
if (device == "lights") topic = TOPIC_ZONE1_COMMANDS_LIGHTS;
else if (device == "servo") topic = TOPIC_ZONE1_COMMANDS_SERVO;
else if (device == "neopixel") topic = TOPIC_ZONE1_COMMANDS_NEOPIXEL;
} else if (zoneNumber == 2) {
if (device == "lights") topic = TOPIC_ZONE2_COMMANDS_LIGHTS;
else if (device == "servo") topic = TOPIC_ZONE2_COMMANDS_SERVO;
else if (device == "neopixel") topic = TOPIC_ZONE2_COMMANDS_NEOPIXEL;
}
if (topic.length() > 0) {
mqttClient.publish(topic.c_str(), command.c_str());
Serial.print("Command sent to Zone ");
Serial.print(zoneNumber);
Serial.print(" - ");
Serial.print(device);
Serial.print(": ");
Serial.println(command);
}
}
// ===== ОСНОВНЫЕ ФУНКЦИИ =====
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("\n=== FARM MONITOR RECEIVER ===");
// Настройка пинов светодиодов
pinMode(BLUE_LED_PIN, OUTPUT);
pinMode(YELLOW_LED_PIN, OUTPUT);
digitalWrite(BLUE_LED_PIN, LOW);
digitalWrite(YELLOW_LED_PIN, LOW);
// Инициализация дисплея
Wire.begin();
setupLCD();
// Подключение к WiFi
connectToWiFi();
// Настройка MQTT
mqttClient.setServer(mqtt_server, mqtt_port);
mqttClient.setCallback(mqttCallback);
mqttClient.setKeepAlive(60);
// Первоначальное отображение
updateDisplay();
Serial.println("Receiver initialized!");
Serial.println("Use LEFT/RIGHT buttons to switch zones");
Serial.println("\nSubscribed topics:");
Serial.println(" - " + String(TOPIC_ZONE1_DATA));
Serial.println(" - " + String(TOPIC_ZONE2_DATA));
}
void loop() {
// Поддерживаем WiFi соединение
if (WiFi.status() != WL_CONNECTED) {
connectToWiFi();
}
// Поддерживаем MQTT соединение
if (!mqttClient.connected()) {
reconnectMQTT();
}
// Обрабатываем входящие MQTT сообщения
mqttClient.loop();
handleButtons();
updateLEDs();
// Если давно не было данных, показываем сообщение
static unsigned long last_status_check = 0;
if (millis() - last_status_check > 10000) { // Каждые 10 секунд
if (packet_count == 0 || millis() - last_received_time > 30000) {
lcd.setCursor(0, 2);
lcd.print("Waiting for data...");
lcd.setCursor(0, 3);
lcd.print(" ");
}
last_status_check = millis();
}
delay(10);
}