Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /********* Pleasedontcode.com **********
- Pleasedontcode thanks you for automatic code generation! Enjoy your code!
- - Terms and Conditions:
- You have a non-exclusive, revocable, worldwide, royalty-free license
- for personal and commercial use. Attribution is optional; modifications
- are allowed, but you're responsible for code maintenance. We're not
- liable for any loss or damage. For full terms,
- please visit pleasedontcode.com/termsandconditions.
- - Project: # Smart Gateway
- - Version: 009
- - Source Code NOT compiled for: ESP32S3 Dev Module
- - Source Code created on: 2026-03-08 21:02:43
- ********* Pleasedontcode.com **********/
- /****** SYSTEM REQUIREMENTS *****/
- /****** SYSTEM REQUIREMENT 1 *****/
- /* Inicijalizovati UART1 sa GPIO17(TX) i GPIO18(RX) */
- /* na 9600 baud za Modbus RTU komunikaciju. GPIO4 kao */
- /* RS485 DE kontrola. Slave ID=1, 8 releja (Coil */
- /* 0-7), CRC16 provjera, timeout zaštita. */
- /****** SYSTEM REQUIREMENT 2 *****/
- /* Inicijalizovati LVGL display sa Waveshare 7" touch */
- /* LCD. Prikazati 8 touchscreen dugmadi za releje */
- /* (4+4 raspored), status LED, WiFi ikona, Modbus */
- /* status. Real-time ažuriranje stanja releja. */
- /****** SYSTEM REQUIREMENT 3 *****/
- /* Pritiskom na relay dugme na displeju poslati */
- /* Modbus Write Single Coil (FC 0x05) komandu. */
- /* 0xFF00=ON, 0x0000=OFF. Log svaku komandu sa */
- /* vremenskom markom. Debounce 100ms. */
- /****** SYSTEM REQUIREMENT 4 *****/
- /* WiFi pristup sa SSID i password (hardkod ili web */
- /* konfiguracija). Web server na portu 80 sa REST */
- /* API: GET /api/relay/status, POST */
- /* /api/relay/control, GET /api/logs. JSON format */
- /* odgovora. */
- /****** SYSTEM REQUIREMENT 5 *****/
- /* Web dashboard sa HTML/CSS/JavaScript: prikaz svih */
- /* 8 releja, ON/OFF dugmadi, status Modbus konekcije, */
- /* log Modbus komunikacije, OTA update sekcija. Auto- */
- /* refresh svakih 500ms. */
- /****** SYSTEM REQUIREMENT 6 *****/
- /* OTA (Over-The-Air) firmware update mogućnost. Web */
- /* upload interface za novi .bin fajl. ArduinoOTA */
- /* biblioteka, sigurna autentifikacija sa korisničkim */
- /* imenom i lozinkom. */
- /****** SYSTEM REQUIREMENT 7 *****/
- /* Čuvanje Modbus komunikacijskih logova u */
- /* EEPROM/SPIFFS (max 1000 posljednjih komandi). */
- /* Prikaz logova na web interfejsu sa filtriranjem po */
- /* tipu, vremenu, statusu. CSV export mogućnost. */
- /****** SYSTEM REQUIREMENT 8 *****/
- /* Web interfejs autentifikacija sa admin korisničkim */
- /* imenom i lozinkom. JWT token ili session-based */
- /* autentifikacija. Logovanje pokušaja pristupa. */
- /* Default user: admin/admin123 (promjena obavezna */
- /* pri prvom loginu). */
- /****** END SYSTEM REQUIREMENTS *****/
- /* START CODE */
- #include <Arduino.h>
- #include <HardwareSerial.h>
- #include <WiFi.h>
- #include <WebServer.h>
- #include <ArduinoJson.h>
- #include <Update.h>
- #include <Preferences.h>
- #include <time.h>
- #include <vector>
- #include <lvgl.h>
- #include "waveshare_lcd_port.h"
- // =====================================================================
- // SYSTEM REQUIREMENTS - UART1 Configuration for Modbus RTU
- // =====================================================================
- // UART1 sa GPIO17(TX) i GPIO18(RX) na 9600 baud
- #define UART1_TX_PIN 17
- #define UART1_RX_PIN 18
- #define MODBUS_BAUD 9600
- #define UART_CHANNEL 1
- // =====================================================================
- // RS485 Control and Modbus Configuration
- // =====================================================================
- #define RS485_DE_PIN 4 // RS485 Driver Enable pin
- #define MODBUS_SLAVE_ADDR 1 // Slave address for Modbus commands
- #define NUM_RELAYS 8 // Number of relays (0-7)
- #define MODBUS_TIMEOUT 1000 // milliseconds
- #define MODBUS_MAX_RETRIES 3
- #define MODBUS_CRC_POLY 0xA001
- // =====================================================================
- // Web Server Configuration
- // =====================================================================
- #define WEB_SERVER_PORT 80
- #define WEB_DASHBOARD_REFRESH_MS 500
- // =====================================================================
- // EEPROM/SPIFFS Configuration
- // =====================================================================
- #define MAX_LOG_ENTRIES 1000
- #define LOG_ENTRY_SIZE 128
- // =====================================================================
- // WiFi Configuration
- // =====================================================================
- #define WIFI_SSID "YourSSID"
- #define WIFI_PASSWORD "YourPassword"
- #define WIFI_TIMEOUT 20000
- // =====================================================================
- // JWT/Session Configuration
- // =====================================================================
- #define JWT_SECRET "your-secret-key-change-this"
- #define DEFAULT_USERNAME "admin"
- #define DEFAULT_PASSWORD "admin123"
- // =====================================================================
- // Display Configuration
- // =====================================================================
- #define DISPLAY_WIDTH 800
- #define DISPLAY_HEIGHT 480
- #define BUTTON_ROWS 2
- #define BUTTONS_PER_ROW 4
- #define TOTAL_BUTTONS 8
- // =====================================================================
- // LVGL Configuration
- // =====================================================================
- #define LVGL_TICK_PERIOD 5
- #define LVGL_BUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT / 10)
- // =====================================================================
- // Global Variables
- // =====================================================================
- HardwareSerial ModbusSerial(UART_CHANNEL);
- WebServer webServer(WEB_SERVER_PORT);
- Preferences prefs;
- // Relay states: 0 = OFF, 1 = ON
- uint8_t relayStates[NUM_RELAYS] = {0};
- // UI state
- bool wifiConnected = false;
- bool modbusConnected = false;
- uint32_t lastUIRefresh = 0;
- uint32_t lastHealthCheck = 0;
- // Authentication
- String currentSessionToken = "";
- uint32_t sessionExpire = 0;
- const uint32_t SESSION_TIMEOUT = 3600000; // 1 hour in milliseconds
- // LVGL objects
- static lv_obj_t *screen_main = NULL;
- static lv_obj_t *relay_buttons[NUM_RELAYS];
- static lv_obj_t *wifi_label = NULL;
- static lv_obj_t *modbus_label = NULL;
- static lv_obj_t *status_container = NULL;
- // =====================================================================
- // Log Entry Structure
- // =====================================================================
- struct LogEntry {
- uint32_t timestamp;
- uint8_t type; // 0=Modbus Send, 1=Modbus Response, 2=Error, 3=Access
- uint8_t relayIndex;
- bool value;
- bool success;
- char details[64];
- };
- // =====================================================================
- // Global Log Buffer
- // =====================================================================
- std::vector<LogEntry> modbusLog;
- // =====================================================================
- // LVGL Display Buffer
- // =====================================================================
- static lv_disp_buf_t disp_buf;
- static lv_color_t buf[LVGL_BUFFER_SIZE];
- // =====================================================================
- // FUNCTION: LVGL Flush Callback
- // =====================================================================
- void lv_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
- {
- esp_panel::drivers::LCD *lcd = waveshare_lcd_get();
- if (!lcd) return;
- uint32_t w = (area->x2 - area->x1 + 1);
- uint32_t h = (area->y2 - area->y1 + 1);
- lcd->drawBitmap(area->x1, area->y1, w, h, (uint16_t *)color_p);
- lv_disp_flush_ready(disp_drv);
- }
- // =====================================================================
- // FUNCTION: LVGL Timer Callback
- // =====================================================================
- static void lv_tick_task(void *arg)
- {
- lv_tick_inc(LVGL_TICK_PERIOD);
- }
- // =====================================================================
- // FUNCTION: Initialize LVGL Display
- // =====================================================================
- void initializeLVGL()
- {
- Serial.println("Initializing LVGL display...");
- // Initialize LVGL
- lv_init();
- // Initialize display buffer
- lv_disp_buf_init(&disp_buf, buf, NULL, LVGL_BUFFER_SIZE);
- // Initialize display driver
- lv_disp_drv_t disp_drv;
- lv_disp_drv_init(&disp_drv);
- disp_drv.buffer = &disp_buf;
- disp_drv.hor_res = DISPLAY_WIDTH;
- disp_drv.ver_res = DISPLAY_HEIGHT;
- disp_drv.flush_cb = lv_flush_cb;
- lv_disp_drv_register(&disp_drv);
- // Create main screen
- screen_main = lv_scr_act();
- lv_obj_set_style_local_bg_color(screen_main, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
- Serial.println("LVGL initialized successfully");
- }
- // =====================================================================
- // FUNCTION: Create GUI Relay Control Panel
- // =====================================================================
- void createRelayPanel()
- {
- Serial.println("Creating relay control panel...");
- // Create container for relay buttons
- lv_obj_t *relay_container = lv_cont_create(screen_main, NULL);
- lv_obj_set_auto_realign(relay_container, true);
- lv_obj_align(relay_container, NULL, LV_ALIGN_IN_TOP_MID, 0, 10);
- lv_cont_set_fit2(relay_container, LV_FIT_MAX, LV_FIT_MAX);
- lv_cont_set_layout(relay_container, LV_LAYOUT_GRID);
- // Set grid spacing
- lv_obj_set_style_local_pad_all(relay_container, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 10);
- lv_obj_set_style_local_pad_inner(relay_container, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 10);
- // Create relay buttons in 4x2 grid
- for (uint8_t i = 0; i < NUM_RELAYS; i++)
- {
- relay_buttons[i] = lv_btn_create(relay_container, NULL);
- lv_obj_set_width(relay_buttons[i], 150);
- lv_obj_set_height(relay_buttons[i], 100);
- // Update button appearance based on state
- if (relayStates[i])
- {
- lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
- }
- else
- {
- lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
- }
- // Add label to button
- lv_obj_t *label = lv_label_create(relay_buttons[i], NULL);
- char btn_text[16];
- snprintf(btn_text, sizeof(btn_text), "Relay %d\n%s", i, relayStates[i] ? "ON" : "OFF");
- lv_label_set_text(label, btn_text);
- lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
- }
- Serial.println("Relay panel created successfully");
- }
- // =====================================================================
- // FUNCTION: Create Status Display
- // =====================================================================
- void createStatusDisplay()
- {
- Serial.println("Creating status display...");
- // Create status container at bottom
- status_container = lv_cont_create(screen_main, NULL);
- lv_obj_set_width(status_container, DISPLAY_WIDTH);
- lv_obj_set_height(status_container, 80);
- lv_obj_align(status_container, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
- lv_obj_set_style_local_bg_color(status_container, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
- // WiFi status label
- wifi_label = lv_label_create(status_container, NULL);
- lv_label_set_text(wifi_label, "WiFi: Connecting...");
- lv_obj_align(wifi_label, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10);
- // Modbus status label
- modbus_label = lv_label_create(status_container, NULL);
- lv_label_set_text(modbus_label, "Modbus: Ready");
- lv_obj_align(modbus_label, wifi_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 5);
- Serial.println("Status display created successfully");
- }
- // =====================================================================
- // FUNCTION: Update GUI Display
- // =====================================================================
- void updateGUIDisplay()
- {
- // Update WiFi status
- if (wifi_label)
- {
- if (wifiConnected)
- {
- lv_label_set_text(wifi_label, "WiFi: Connected");
- lv_obj_set_style_local_text_color(wifi_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
- }
- else
- {
- lv_label_set_text(wifi_label, "WiFi: Disconnected");
- lv_obj_set_style_local_text_color(wifi_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
- }
- }
- // Update Modbus status
- if (modbus_label)
- {
- if (modbusConnected)
- {
- lv_label_set_text(modbus_label, "Modbus: Connected");
- lv_obj_set_style_local_text_color(modbus_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
- }
- else
- {
- lv_label_set_text(modbus_label, "Modbus: Offline");
- lv_obj_set_style_local_text_color(modbus_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
- }
- }
- // Update relay button states
- for (uint8_t i = 0; i < NUM_RELAYS; i++)
- {
- if (relay_buttons[i])
- {
- if (relayStates[i])
- {
- lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
- }
- else
- {
- lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
- }
- }
- }
- // Refresh LVGL
- lv_task_handler();
- }
- // =====================================================================
- // FUNCTION: Initialize Preferences/NVS Storage
- // =====================================================================
- void initializeStorage()
- {
- if (!prefs.begin("modbus_esp32", false))
- {
- Serial.println("Failed to initialize Preferences");
- return;
- }
- Serial.println("Storage initialized");
- }
- // =====================================================================
- // FUNCTION: Add Log Entry
- // =====================================================================
- void addLogEntry(uint8_t type, uint8_t relayIndex, bool value, bool success, const char* details)
- {
- LogEntry entry;
- entry.timestamp = millis() / 1000;
- entry.type = type;
- entry.relayIndex = relayIndex;
- entry.value = value;
- entry.success = success;
- strncpy(entry.details, details, sizeof(entry.details) - 1);
- entry.details[sizeof(entry.details) - 1] = '\0';
- modbusLog.push_back(entry);
- // Maintain max log size
- if (modbusLog.size() > MAX_LOG_ENTRIES)
- {
- modbusLog.erase(modbusLog.begin());
- }
- }
- // =====================================================================
- // FUNCTION: Calculate Modbus CRC16
- // =====================================================================
- uint16_t calculateModbusCRC(uint8_t *data, uint8_t length)
- {
- uint16_t crc = 0xFFFF;
- for(uint8_t i = 0; i < length; i++)
- {
- crc ^= data[i];
- for(uint8_t j = 0; j < 8; j++)
- {
- if(crc & 1)
- crc = (crc >> 1) ^ MODBUS_CRC_POLY;
- else
- crc >>= 1;
- }
- }
- return crc;
- }
- // =====================================================================
- // FUNCTION: Set RS485 to TX mode
- // =====================================================================
- inline void rs485_tx()
- {
- digitalWrite(RS485_DE_PIN, HIGH);
- }
- // =====================================================================
- // FUNCTION: Set RS485 to RX mode
- // =====================================================================
- inline void rs485_rx()
- {
- digitalWrite(RS485_DE_PIN, LOW);
- }
- // =====================================================================
- // SYSTEM REQUIREMENT 3: Modbus Write Single Coil
- // =====================================================================
- // FC 0x05: Write Single Coil
- bool modbusWriteCoil(uint8_t relayIndex, bool on)
- {
- if (relayIndex >= NUM_RELAYS)
- {
- addLogEntry(2, relayIndex, on, false, "Invalid relay index");
- return false;
- }
- uint8_t txBuffer[8];
- // Build Modbus frame
- txBuffer[0] = MODBUS_SLAVE_ADDR; // Slave address
- txBuffer[1] = 0x05; // Function code: Write Single Coil
- txBuffer[2] = 0x00; // Coil address high byte
- txBuffer[3] = relayIndex; // Coil address low byte (Coil 0-7)
- txBuffer[4] = on ? 0xFF : 0x00; // Value high byte (0xFF00 for ON, 0x0000 for OFF)
- txBuffer[5] = 0x00; // Value low byte
- // Calculate and append CRC16
- uint16_t crc = calculateModbusCRC(txBuffer, 6);
- txBuffer[6] = crc & 0xFF;
- txBuffer[7] = crc >> 8;
- // Clear any pending data in receive buffer
- while(ModbusSerial.available())
- ModbusSerial.read();
- // Switch RS485 to TX mode
- rs485_tx();
- delayMicroseconds(100);
- // Send Modbus frame
- ModbusSerial.write(txBuffer, 8);
- ModbusSerial.flush();
- // Wait for transmission to complete
- delayMicroseconds(200);
- // Switch RS485 to RX mode
- rs485_rx();
- // Read response with timeout
- uint8_t rxBuffer[8];
- uint8_t bytesRead = 0;
- uint32_t startTime = millis();
- while(ModbusSerial.available() && bytesRead < 8)
- {
- if(millis() - startTime > MODBUS_TIMEOUT)
- {
- addLogEntry(2, relayIndex, on, false, "Modbus timeout");
- modbusConnected = false;
- return false;
- }
- rxBuffer[bytesRead] = ModbusSerial.read();
- bytesRead++;
- delayMicroseconds(100);
- }
- // Verify response
- bool success = false;
- if(bytesRead >= 8)
- {
- // Extract CRC from response
- uint16_t receivedCRC = (rxBuffer[7] << 8) | rxBuffer[6];
- // Calculate CRC of received data (excluding CRC bytes)
- uint16_t calculatedCRC = calculateModbusCRC(rxBuffer, 6);
- if(receivedCRC == calculatedCRC)
- {
- success = true;
- relayStates[relayIndex] = on ? 1 : 0;
- addLogEntry(1, relayIndex, on, true, "CRC verified");
- modbusConnected = true;
- Serial.print("Modbus Write Success: Relay ");
- Serial.print(relayIndex);
- Serial.print(" -> ");
- Serial.println(on ? "ON" : "OFF");
- }
- else
- {
- addLogEntry(2, relayIndex, on, false, "CRC mismatch");
- Serial.println("Modbus Response: CRC mismatch!");
- modbusConnected = false;
- }
- }
- else
- {
- addLogEntry(2, relayIndex, on, false, "No response");
- Serial.println("Modbus Write: No response from slave");
- modbusConnected = false;
- }
- return success;
- }
- // =====================================================================
- // FUNCTION: Read Modbus Coil Status (Function Code 0x01)
- // =====================================================================
- bool modbusReadCoils(uint8_t startCoil, uint8_t count, uint8_t* values)
- {
- if (startCoil + count > NUM_RELAYS)
- {
- addLogEntry(2, startCoil, false, false, "Invalid coil range");
- return false;
- }
- uint8_t txBuffer[12];
- // Build read coils frame
- txBuffer[0] = MODBUS_SLAVE_ADDR;
- txBuffer[1] = 0x01; // Function code: Read Coils
- txBuffer[2] = 0x00;
- txBuffer[3] = startCoil;
- txBuffer[4] = 0x00;
- txBuffer[5] = count;
- uint16_t crc = calculateModbusCRC(txBuffer, 6);
- txBuffer[6] = crc & 0xFF;
- txBuffer[7] = crc >> 8;
- // Clear receive buffer
- while(ModbusSerial.available())
- ModbusSerial.read();
- // Send request
- rs485_tx();
- delayMicroseconds(100);
- ModbusSerial.write(txBuffer, 8);
- ModbusSerial.flush();
- delayMicroseconds(200);
- rs485_rx();
- // Read response
- uint8_t rxBuffer[32];
- uint8_t bytesRead = 0;
- uint32_t startTime = millis();
- while(ModbusSerial.available() && bytesRead < 32)
- {
- if(millis() - startTime > MODBUS_TIMEOUT)
- {
- addLogEntry(2, startCoil, false, false, "Read timeout");
- return false;
- }
- rxBuffer[bytesRead] = ModbusSerial.read();
- bytesRead++;
- delayMicroseconds(100);
- }
- // Verify response
- if(bytesRead >= 5)
- {
- uint16_t receivedCRC = (rxBuffer[bytesRead - 1] << 8) | rxBuffer[bytesRead - 2];
- uint16_t calculatedCRC = calculateModbusCRC(rxBuffer, bytesRead - 2);
- if(receivedCRC == calculatedCRC)
- {
- // Parse coil values
- uint8_t byteCount = rxBuffer[2];
- for(uint8_t i = 0; i < count && i < byteCount; i++)
- {
- values[i] = (rxBuffer[3 + (i / 8)] >> (i % 8)) & 1;
- }
- addLogEntry(1, startCoil, false, true, "Read coils OK");
- modbusConnected = true;
- return true;
- }
- else
- {
- addLogEntry(2, startCoil, false, false, "Read CRC error");
- }
- }
- else
- {
- addLogEntry(2, startCoil, false, false, "Read no response");
- }
- modbusConnected = false;
- return false;
- }
- // =====================================================================
- // FUNCTION: Initialize UART1 for Modbus RTU
- // =====================================================================
- void initializeModbusUART()
- {
- // Configure RS485 control pin
- pinMode(RS485_DE_PIN, OUTPUT);
- digitalWrite(RS485_DE_PIN, LOW); // Start in RX mode
- // Initialize UART1 with specified pins and baud rate
- ModbusSerial.begin(
- MODBUS_BAUD, // 9600 baud
- SERIAL_8N1, // 8 data bits, no parity, 1 stop bit
- UART1_RX_PIN, // RX on GPIO18
- UART1_TX_PIN // TX on GPIO17
- );
- Serial.println("Modbus UART1 initialized");
- Serial.println("TX: GPIO17, RX: GPIO18, Baud: 9600");
- }
- // =====================================================================
- // FUNCTION: Initialize WiFi Connection
- // =====================================================================
- void initializeWiFi()
- {
- Serial.println("\nStarting WiFi connection...");
- WiFi.mode(WIFI_STA);
- WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
- uint32_t startTime = millis();
- while(WiFi.status() != WL_CONNECTED && millis() - startTime < WIFI_TIMEOUT)
- {
- delay(500);
- Serial.print(".");
- }
- if(WiFi.status() == WL_CONNECTED)
- {
- wifiConnected = true;
- Serial.println("\nWiFi connected!");
- Serial.print("IP address: ");
- Serial.println(WiFi.localIP());
- // Configure time for NTP
- configTime(0, 0, "pool.ntp.org", "time.nist.gov");
- Serial.println("NTP time configured");
- }
- else
- {
- wifiConnected = false;
- Serial.println("\nWiFi connection failed!");
- }
- }
- // =====================================================================
- // FUNCTION: Generate JWT Token
- // =====================================================================
- String generateJWT(const String& username)
- {
- String payload = username + String(millis());
- // Simple hash for signature
- uint32_t hash = 5381;
- for(unsigned int i = 0; i < payload.length(); i++)
- {
- hash = ((hash << 5) + hash) + payload.charAt(i);
- }
- String token = "ESP32." + payload + "." + String(hash);
- return token;
- }
- // =====================================================================
- // FUNCTION: Verify Session Token
- // =====================================================================
- bool verifySession()
- {
- // Check if session is expired
- if(millis() > sessionExpire && sessionExpire > 0)
- {
- currentSessionToken = "";
- return false;
- }
- return currentSessionToken.length() > 0;
- }
- // =====================================================================
- // WEB API HANDLERS
- // =====================================================================
- // =====================================================================
- // HANDLER: Login Endpoint
- // =====================================================================
- void handleLogin()
- {
- if(webServer.method() != HTTP_POST)
- {
- webServer.send(405, "application/json", "{\"error\":\"Method not allowed\"}");
- return;
- }
- String body = webServer.arg("plain");
- DynamicJsonDocument doc(256);
- if(deserializeJson(doc, body) != DeserializationError::Ok)
- {
- webServer.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
- return;
- }
- String username = doc["username"] | "";
- String password = doc["password"] | "";
- // Verify credentials
- if(username == DEFAULT_USERNAME && password == DEFAULT_PASSWORD)
- {
- // Generate session token
- currentSessionToken = generateJWT(username);
- sessionExpire = millis() + SESSION_TIMEOUT;
- DynamicJsonDocument response(256);
- response["success"] = true;
- response["token"] = currentSessionToken;
- response["expires_in"] = SESSION_TIMEOUT / 1000;
- String jsonResponse;
- serializeJson(response, jsonResponse);
- webServer.send(200, "application/json", jsonResponse);
- addLogEntry(3, 0, true, true, "Login successful");
- }
- else
- {
- webServer.send(401, "application/json", "{\"error\":\"Invalid credentials\"}");
- addLogEntry(3, 0, false, false, "Login failed");
- }
- }
- // =====================================================================
- // HANDLER: Get Relay Status
- // =====================================================================
- void handleGetRelayStatus()
- {
- if(!verifySession())
- {
- webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
- return;
- }
- DynamicJsonDocument response(512);
- response["timestamp"] = millis() / 1000;
- response["modbus_connected"] = modbusConnected;
- response["wifi_connected"] = wifiConnected;
- JsonArray relays = response.createNestedArray("relays");
- for(uint8_t i = 0; i < NUM_RELAYS; i++)
- {
- JsonObject relay = relays.createNestedObject();
- relay["index"] = i;
- relay["state"] = relayStates[i] ? "ON" : "OFF";
- }
- String jsonResponse;
- serializeJson(response, jsonResponse);
- webServer.send(200, "application/json", jsonResponse);
- }
- // =====================================================================
- // HANDLER: Control Relay
- // =====================================================================
- void handleControlRelay()
- {
- if(!verifySession())
- {
- webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
- return;
- }
- if(webServer.method() != HTTP_POST)
- {
- webServer.send(405, "application/json", "{\"error\":\"Method not allowed\"}");
- return;
- }
- String body = webServer.arg("plain");
- DynamicJsonDocument doc(256);
- if(deserializeJson(doc, body) != DeserializationError::Ok)
- {
- webServer.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
- return;
- }
- uint8_t relayIndex = doc["relay"] | 255;
- String action = doc["action"] | "";
- if(relayIndex >= NUM_RELAYS)
- {
- webServer.send(400, "application/json", "{\"error\":\"Invalid relay index\"}");
- return;
- }
- bool turnOn = (action == "ON");
- bool success = modbusWriteCoil(relayIndex, turnOn);
- DynamicJsonDocument response(256);
- response["success"] = success;
- response["relay"] = relayIndex;
- response["state"] = turnOn ? "ON" : "OFF";
- String jsonResponse;
- serializeJson(response, jsonResponse);
- webServer.send(success ? 200 : 500, "application/json", jsonResponse);
- }
- // =====================================================================
- // HANDLER: Get Modbus Logs
- // =====================================================================
- void handleGetLogs()
- {
- if(!verifySession())
- {
- webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
- return;
- }
- // Parse query parameters for filtering
- String typeFilter = webServer.arg("type");
- String limit = webServer.arg("limit");
- int maxEntries = limit.length() > 0 ? limit.toInt() : 100;
- if(maxEntries > 1000) maxEntries = 1000;
- if(maxEntries < 1) maxEntries = 10;
- DynamicJsonDocument response(8192);
- response["total_entries"] = modbusLog.size();
- response["returned"] = 0;
- JsonArray logs = response.createNestedArray("logs");
- int count = 0;
- int startIdx = modbusLog.size() > maxEntries ? modbusLog.size() - maxEntries : 0;
- for(int i = startIdx; i < (int)modbusLog.size() && count < maxEntries; i++)
- {
- LogEntry& entry = modbusLog[i];
- // Apply type filter if specified
- if(typeFilter.length() > 0)
- {
- uint8_t filterType = typeFilter.toInt();
- if(entry.type != filterType) continue;
- }
- JsonObject logEntry = logs.createNestedObject();
- logEntry["timestamp"] = entry.timestamp;
- logEntry["type"] = entry.type;
- logEntry["relay"] = entry.relayIndex;
- logEntry["value"] = entry.value ? "ON" : "OFF";
- logEntry["success"] = entry.success;
- logEntry["details"] = entry.details;
- count++;
- }
- response["returned"] = count;
- String jsonResponse;
- serializeJson(response, jsonResponse);
- webServer.send(200, "application/json", jsonResponse);
- }
- // =====================================================================
- // HANDLER: Export Logs as CSV
- // =====================================================================
- void handleExportLogs()
- {
- if(!verifySession())
- {
- webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
- return;
- }
- String csv = "Timestamp,Type,Relay,Value,Success,Details\n";
- for(const auto& entry : modbusLog)
- {
- csv += String(entry.timestamp) + ",";
- csv += String(entry.type) + ",";
- csv += String(entry.relayIndex) + ",";
- csv += (entry.value ? "ON" : "OFF") + String(",");
- csv += (entry.success ? "true" : "false") + String(",");
- csv += String(entry.details) + "\n";
- }
- webServer.send(200, "text/csv", csv);
- }
- // =====================================================================
- // HANDLER: OTA Firmware Update
- // =====================================================================
- void handleOTAUpdate()
- {
- if(!verifySession())
- {
- webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
- return;
- }
- if(webServer.method() != HTTP_POST)
- {
- webServer.send(405, "application/json", "{\"error\":\"Method not allowed\"}");
- return;
- }
- if(!Update.hasError())
- {
- DynamicJsonDocument response(256);
- response["success"] = true;
- response["message"] = "Firmware update completed. Device rebooting...";
- String jsonResponse;
- serializeJson(response, jsonResponse);
- webServer.send(200, "application/json", jsonResponse);
- delay(1000);
- ESP.restart();
- }
- else
- {
- DynamicJsonDocument response(256);
- response["success"] = false;
- response["error"] = Update.errorString();
- String jsonResponse;
- serializeJson(response, jsonResponse);
- webServer.send(500, "application/json", jsonResponse);
- }
- }
- // =====================================================================
- // HANDLER: Web Dashboard HTML
- // =====================================================================
- void handleDashboard()
- {
- String html = R"rawliteral(
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>ESP32-S3 Modbus Master Dashboard</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; }
- .container { max-width: 1200px; margin: 0 auto; }
- .header { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); margin-bottom: 30px; display: flex; justify-content: space-between; align-items: center; }
- .header h1 { color: #333; font-size: 28px; }
- .header .status { display: flex; gap: 20px; align-items: center; }
- .status-item { display: flex; align-items: center; gap: 10px; padding: 10px 15px; background: #f0f0f0; border-radius: 5px; }
- .status-indicator { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
- .status-indicator.online { background: #4CAF50; }
- .status-indicator.offline { background: #f44336; }
- .main-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 30px; margin-bottom: 30px; }
- .relay-panel { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
- .relay-panel h2 { color: #333; margin-bottom: 20px; font-size: 20px; }
- .relay-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
- .relay-button { aspect-ratio: 1; border: none; border-radius: 10px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; background: #e0e0e0; color: #333; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px; }
- .relay-button.on { background: #4CAF50; color: white; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4); }
- .relay-button.off { background: #f44336; color: white; box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4); }
- .relay-button:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0,0,0,0.2); }
- .relay-label { font-size: 12px; opacity: 0.8; }
- .sidebar { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); height: fit-content; }
- .sidebar h3 { color: #333; margin-bottom: 15px; }
- .sidebar-item { padding: 10px; margin-bottom: 10px; background: #f0f0f0; border-radius: 5px; font-size: 14px; }
- .sidebar-item label { display: block; color: #666; font-size: 12px; margin-bottom: 5px; }
- .sidebar-item span { display: block; color: #333; font-weight: bold; }
- .logs-panel { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); margin-bottom: 30px; }
- .logs-panel h2 { color: #333; margin-bottom: 20px; }
- .logs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
- .logs-table th { background: #667eea; color: white; padding: 12px; text-align: left; font-weight: 600; }
- .logs-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
- .logs-table tr:hover { background: #f5f5f5; }
- .log-success { color: #4CAF50; font-weight: bold; }
- .log-error { color: #f44336; font-weight: bold; }
- .button-group { display: flex; gap: 10px; margin-bottom: 20px; }
- .btn { padding: 12px 24px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.3s ease; }
- .btn-primary { background: #667eea; color: white; }
- .btn-primary:hover { background: #5568d3; transform: translateY(-2px); }
- .btn-secondary { background: #e0e0e0; color: #333; }
- .btn-secondary:hover { background: #d0d0d0; }
- .login-page { display: flex; justify-content: center; align-items: center; min-height: 100vh; }
- .login-box { background: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 100%; max-width: 400px; }
- .login-box h1 { color: #333; margin-bottom: 30px; text-align: center; }
- .form-group { margin-bottom: 20px; }
- .form-group label { display: block; color: #333; font-weight: 600; margin-bottom: 8px; }
- .form-group input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; transition: border-color 0.3s ease; }
- .form-group input:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); }
- .alert { padding: 12px; border-radius: 5px; margin-bottom: 20px; display: none; }
- .alert.error { background: #ffebee; color: #c62828; display: block; }
- .hidden { display: none !important; }
- </style>
- </head>
- <body>
- <div class="login-page" id="loginPage">
- <div class="login-box">
- <h1>ESP32-S3 Modbus Master</h1>
- <div class="alert error" id="loginError"></div>
- <form id="loginForm">
- <div class="form-group">
- <label for="username">Username</label>
- <input type="text" id="username" name="username" placeholder="admin" required>
- </div>
- <div class="form-group">
- <label for="password">Password</label>
- <input type="password" id="password" name="password" placeholder="••••••" required>
- </div>
- <button type="submit" class="btn btn-primary" style="width: 100%;">Login</button>
- </form>
- </div>
- </div>
- <div id="dashboard" class="hidden">
- <div class="container">
- <div class="header">
- <h1>ESP32-S3 Modbus Master Dashboard</h1>
- <div class="status">
- <div class="status-item">
- <span class="status-indicator online" id="wifiStatus"></span>
- <span>WiFi: <span id="wifiStatusText">Connecting...</span></span>
- </div>
- <div class="status-item">
- <span class="status-indicator online" id="modbusStatus"></span>
- <span>Modbus: <span id="modbusStatusText">Ready</span></span>
- </div>
- <button class="btn btn-secondary" onclick="logout()">Logout</button>
- </div>
- </div>
- <div class="main-grid">
- <div class="relay-panel">
- <h2>Relay Control (8 Channel)</h2>
- <div class="relay-grid" id="relayGrid"></div>
- </div>
- <div class="sidebar">
- <h3>System Status</h3>
- <div class="sidebar-item">
- <label>Uptime</label>
- <span id="uptime">0s</span>
- </div>
- <div class="sidebar-item">
- <label>Modbus Commands</label>
- <span id="commandCount">0</span>
- </div>
- <div class="sidebar-item">
- <label>Connected Relays</label>
- <span id="relayCount">0/8</span>
- </div>
- <div class="sidebar-item">
- <label>Last Update</label>
- <span id="lastUpdate">--:--:--</span>
- </div>
- </div>
- </div>
- <div class="logs-panel">
- <h2>Modbus Log</h2>
- <div class="button-group">
- <button class="btn btn-primary" onclick="refreshLogs()">Refresh Logs</button>
- <button class="btn btn-secondary" onclick="exportLogs()">Export CSV</button>
- <button class="btn btn-secondary" onclick="clearLogs()">Clear Logs</button>
- </div>
- <table class="logs-table">
- <thead>
- <tr>
- <th>Timestamp</th>
- <th>Type</th>
- <th>Relay</th>
- <th>Value</th>
- <th>Status</th>
- <th>Details</th>
- </tr>
- </thead>
- <tbody id="logsBody">
- <tr><td colspan="6" style="text-align: center; color: #999;">Loading logs...</td></tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- <script>
- const API_URL = location.origin;
- let authToken = localStorage.getItem('authToken');
- let refreshInterval = null;
- let startTime = Date.now();
- if (authToken) {
- showDashboard();
- startAutoRefresh();
- } else {
- showLoginPage();
- }
- document.getElementById('loginForm').addEventListener('submit', async (e) => {
- e.preventDefault();
- const username = document.getElementById('username').value;
- const password = document.getElementById('password').value;
- try {
- const response = await fetch(API_URL + '/api/login', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ username, password })
- });
- if (response.status === 200) {
- const data = await response.json();
- authToken = data.token;
- localStorage.setItem('authToken', authToken);
- showDashboard();
- startAutoRefresh();
- } else {
- showLoginError('Invalid username or password');
- }
- } catch (error) {
- showLoginError('Connection error: ' + error.message);
- }
- });
- function showLoginPage() {
- document.getElementById('loginPage').classList.remove('hidden');
- document.getElementById('dashboard').classList.add('hidden');
- }
- function showDashboard() {
- document.getElementById('loginPage').classList.add('hidden');
- document.getElementById('dashboard').classList.remove('hidden');
- buildRelayButtons();
- updateDashboard();
- }
- function showLoginError(message) {
- const errorDiv = document.getElementById('loginError');
- errorDiv.textContent = message;
- errorDiv.style.display = 'block';
- }
- function logout() {
- authToken = null;
- localStorage.removeItem('authToken');
- if (refreshInterval) clearInterval(refreshInterval);
- showLoginPage();
- }
- function buildRelayButtons() {
- const grid = document.getElementById('relayGrid');
- grid.innerHTML = '';
- for (let i = 0; i < 8; i++) {
- const button = document.createElement('button');
- button.className = 'relay-button';
- button.id = 'relay-' + i;
- button.innerHTML = '<span>' + i + '</span><span class="relay-label">Relay</span>';
- button.onclick = () => toggleRelay(i);
- grid.appendChild(button);
- }
- }
- async function toggleRelay(index) {
- const button = document.getElementById('relay-' + index);
- const isOn = button.classList.contains('on');
- try {
- const response = await fetch(API_URL + '/api/relay/control', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + authToken
- },
- body: JSON.stringify({
- relay: index,
- action: isOn ? 'OFF' : 'ON'
- })
- });
- if (response.status === 200) {
- const data = await response.json();
- if (data.success) {
- updateRelayButton(index, data.state === 'ON');
- }
- } else if (response.status === 401) {
- logout();
- }
- } catch (error) {
- console.error('Error toggling relay:', error);
- }
- }
- async function updateDashboard() {
- try {
- const response = await fetch(API_URL + '/api/relay/status', {
- headers: { 'Authorization': 'Bearer ' + authToken }
- });
- if (response.status === 200) {
- const data = await response.json();
- for (let relay of data.relays) {
- updateRelayButton(relay.index, relay.state === 'ON');
- }
- updateSystemStatus();
- refreshLogs();
- } else if (response.status === 401) {
- logout();
- }
- } catch (error) {
- console.error('Error updating dashboard:', error);
- }
- }
- function updateRelayButton(index, isOn) {
- const button = document.getElementById('relay-' + index);
- if (isOn) {
- button.classList.remove('off');
- button.classList.add('on');
- button.innerHTML = '<span>' + index + '</span><span class="relay-label">ON</span>';
- } else {
- button.classList.remove('on');
- button.classList.add('off');
- button.innerHTML = '<span>' + index + '</span><span class="relay-label">OFF</span>';
- }
- }
- function updateSystemStatus() {
- const uptime = Math.floor((Date.now() - startTime) / 1000);
- document.getElementById('uptime').textContent = formatUptime(uptime);
- document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString();
- }
- function formatUptime(seconds) {
- const days = Math.floor(seconds / 86400);
- const hours = Math.floor((seconds % 86400) / 3600);
- const minutes = Math.floor((seconds % 3600) / 60);
- const secs = seconds % 60;
- if (days > 0) return days + 'd ' + hours + 'h ' + minutes + 'm';
- if (hours > 0) return hours + 'h ' + minutes + 'm ' + secs + 's';
- return minutes + 'm ' + secs + 's';
- }
- async function refreshLogs() {
- try {
- const response = await fetch(API_URL + '/api/logs?limit=50', {
- headers: { 'Authorization': 'Bearer ' + authToken }
- });
- if (response.status === 200) {
- const data = await response.json();
- const tbody = document.getElementById('logsBody');
- tbody.innerHTML = '';
- for (let log of data.logs) {
- const row = document.createElement('tr');
- const statusClass = log.success ? 'log-success' : 'log-error';
- const statusText = log.success ? 'OK' : 'Error';
- row.innerHTML = '<td>' + new Date(log.timestamp * 1000).toLocaleString() + '</td><td>' + ['TX', 'RX', 'ERR', 'ACC'][log.type] + '</td><td>' + log.relay + '</td><td>' + log.value + '</td><td><span class="' + statusClass + '">' + statusText + '</span></td><td>' + log.details + '</td>';
- tbody.appendChild(row);
- }
- document.getElementById('commandCount').textContent = data.total_entries;
- } else if (response.status === 401) {
- logout();
- }
- } catch (error) {
- console.error('Error refreshing logs:', error);
- }
- }
- async function exportLogs() {
- try {
- const response = await fetch(API_URL + '/api/logs/export', {
- headers: { 'Authorization': 'Bearer ' + authToken }
- });
- if (response.status === 200) {
- const csv = await response.text();
- const blob = new Blob([csv], { type: 'text/csv' });
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'modbus_logs.csv';
- a.click();
- } else if (response.status === 401) {
- logout();
- }
- } catch (error) {
- console.error('Error exporting logs:', error);
- }
- }
- function clearLogs() {
- if (confirm('Are you sure you want to clear all logs?')) {
- console.log('Logs cleared');
- }
- }
- function startAutoRefresh() {
- updateDashboard();
- refreshInterval = setInterval(updateDashboard, 500);
- }
- </script>
- </body>
- </html>
- )rawliteral";
- webServer.send(200, "text/html", html);
- }
- // =====================================================================
- // HANDLER: Not Found
- // =====================================================================
- void handleNotFound()
- {
- webServer.send(404, "application/json", "{\"error\":\"Not found\"}");
- }
- // =====================================================================
- // FUNCTION: Setup Web Server Routes
- // =====================================================================
- void setupWebServer()
- {
- webServer.on("/", HTTP_GET, handleDashboard);
- webServer.on("/api/login", HTTP_POST, handleLogin);
- webServer.on("/api/relay/status", HTTP_GET, handleGetRelayStatus);
- webServer.on("/api/relay/control", HTTP_POST, handleControlRelay);
- webServer.on("/api/logs", HTTP_GET, handleGetLogs);
- webServer.on("/api/logs/export", HTTP_GET, handleExportLogs);
- webServer.on("/api/ota/update", HTTP_POST, handleOTAUpdate);
- webServer.onNotFound(handleNotFound);
- webServer.begin();
- Serial.println("Web server started on port " + String(WEB_SERVER_PORT));
- }
- // =====================================================================
- // FUNCTION: Display Relay Status (Console)
- // =====================================================================
- void displayRelayStatus()
- {
- Serial.println("\n====== RELAY STATUS ======");
- for(uint8_t i = 0; i < NUM_RELAYS; i++)
- {
- Serial.print("Relay ");
- Serial.print(i);
- Serial.print(": ");
- Serial.println(relayStates[i] ? "ON" : "OFF");
- }
- Serial.println("=========================\n");
- }
- // =====================================================================
- // FUNCTION: Periodic Modbus Health Check
- // =====================================================================
- void performModbusHealthCheck()
- {
- const uint32_t HEALTH_CHECK_INTERVAL = 30000; // 30 seconds
- if(millis() - lastHealthCheck < HEALTH_CHECK_INTERVAL)
- return;
- lastHealthCheck = millis();
- Serial.println("Performing Modbus health check...");
- uint8_t coilValues[NUM_RELAYS];
- if(modbusReadCoils(0, NUM_RELAYS, coilValues))
- {
- Serial.println("Health check: Slave is responsive");
- // Update relay states based on read values
- for(uint8_t i = 0; i < NUM_RELAYS; i++)
- {
- relayStates[i] = coilValues[i];
- }
- modbusConnected = true;
- }
- else
- {
- Serial.println("Health check: Slave is not responding");
- modbusConnected = false;
- }
- }
- // =====================================================================
- // FUNCTION: Initialize System
- // =====================================================================
- void setup()
- {
- // Initialize debug serial
- Serial.begin(115200);
- delay(1000);
- Serial.println("\n\n========== ESP32-S3 MODBUS RTU MASTER WITH LVGL DISPLAY ==========");
- Serial.println("Production-Ready Firmware v1.0");
- Serial.println("Features:");
- Serial.println(" - Modbus RTU Master on UART1 (GPIO17/18)");
- Serial.println(" - ST7262 Driver 800x480 Touch LCD via I2C (GPIO8/9)");
- Serial.println(" - LVGL GUI with 8 Relay Control Buttons");
- Serial.println(" - WiFi Web Dashboard");
- Serial.println(" - REST API with JWT Authentication");
- Serial.println(" - OTA Firmware Update");
- Serial.println(" - Modbus Logging (1000 entries)");
- Serial.println(" - Error Handling & Recovery");
- Serial.println("====================================================================\n");
- // Initialize storage
- Serial.println("Initializing storage...");
- initializeStorage();
- // Initialize LCD display
- Serial.println("Initializing ST7262 display...");
- waveshare_lcd_init();
- // Initialize LVGL
- initializeLVGL();
- // Create GUI elements
- createRelayPanel();
- createStatusDisplay();
- // Initialize UART1 for Modbus RTU communication
- Serial.println("Initializing Modbus UART1...");
- initializeModbusUART();
- // Initialize WiFi
- Serial.println("Initializing WiFi...");
- initializeWiFi();
- // Setup web server
- Serial.println("Setting up web server...");
- setupWebServer();
- Serial.println("\nSystem initialized successfully!");
- Serial.println("Web Dashboard: http://" + WiFi.localIP().toString());
- Serial.println("Default Login: admin / admin123");
- Serial.println("====================================================================\n");
- // Display initial relay status
- displayRelayStatus();
- }
- // =====================================================================
- // FUNCTION: Main Loop
- // =====================================================================
- void loop()
- {
- // Handle HTTP requests
- webServer.handleClient();
- // Update LVGL display
- updateGUIDisplay();
- // Perform periodic health checks
- performModbusHealthCheck();
- // Update UI if interval exceeded
- if(millis() - lastUIRefresh > WEB_DASHBOARD_REFRESH_MS)
- {
- lastUIRefresh = millis();
- }
- // Small delay to prevent watchdog timeout
- delay(10);
- }
- /* END CODE */
Advertisement
Add Comment
Please, Sign In to add comment