pleasedontcode

# Smart Gateway rev_04

Mar 8th, 2026
29
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 53.73 KB | None | 0 0
  1. /********* Pleasedontcode.com **********
  2.  
  3.     Pleasedontcode thanks you for automatic code generation! Enjoy your code!
  4.  
  5.     - Terms and Conditions:
  6.     You have a non-exclusive, revocable, worldwide, royalty-free license
  7.     for personal and commercial use. Attribution is optional; modifications
  8.     are allowed, but you're responsible for code maintenance. We're not
  9.     liable for any loss or damage. For full terms,
  10.     please visit pleasedontcode.com/termsandconditions.
  11.  
  12.     - Project: # Smart Gateway
  13.     - Version: 009
  14.     - Source Code NOT compiled for: ESP32S3 Dev Module
  15.     - Source Code created on: 2026-03-08 21:02:43
  16.  
  17. ********* Pleasedontcode.com **********/
  18.  
  19. /****** SYSTEM REQUIREMENTS *****/
  20. /****** SYSTEM REQUIREMENT 1 *****/
  21.     /* Inicijalizovati UART1 sa GPIO17(TX) i GPIO18(RX) */
  22.     /* na 9600 baud za Modbus RTU komunikaciju. GPIO4 kao */
  23.     /* RS485 DE kontrola. Slave ID=1, 8 releja (Coil */
  24.     /* 0-7), CRC16 provjera, timeout zaštita. */
  25. /****** SYSTEM REQUIREMENT 2 *****/
  26.     /* Inicijalizovati LVGL display sa Waveshare 7" touch */
  27.     /* LCD. Prikazati 8 touchscreen dugmadi za releje */
  28.     /* (4+4 raspored), status LED, WiFi ikona, Modbus */
  29.     /* status. Real-time ažuriranje stanja releja. */
  30. /****** SYSTEM REQUIREMENT 3 *****/
  31.     /* Pritiskom na relay dugme na displeju poslati */
  32.     /* Modbus Write Single Coil (FC 0x05) komandu. */
  33.     /* 0xFF00=ON, 0x0000=OFF. Log svaku komandu sa */
  34.     /* vremenskom markom. Debounce 100ms. */
  35. /****** SYSTEM REQUIREMENT 4 *****/
  36.     /* WiFi pristup sa SSID i password (hardkod ili web */
  37.     /* konfiguracija). Web server na portu 80 sa REST */
  38.     /* API: GET /api/relay/status, POST */
  39.     /* /api/relay/control, GET /api/logs. JSON format */
  40.     /* odgovora. */
  41. /****** SYSTEM REQUIREMENT 5 *****/
  42.     /* Web dashboard sa HTML/CSS/JavaScript: prikaz svih */
  43.     /* 8 releja, ON/OFF dugmadi, status Modbus konekcije, */
  44.     /* log Modbus komunikacije, OTA update sekcija. Auto- */
  45.     /* refresh svakih 500ms. */
  46. /****** SYSTEM REQUIREMENT 6 *****/
  47.     /* OTA (Over-The-Air) firmware update mogućnost. Web */
  48.     /* upload interface za novi .bin fajl. ArduinoOTA */
  49.     /* biblioteka, sigurna autentifikacija sa korisničkim */
  50.     /* imenom i lozinkom. */
  51. /****** SYSTEM REQUIREMENT 7 *****/
  52.     /* Čuvanje Modbus komunikacijskih logova u */
  53.     /* EEPROM/SPIFFS (max 1000 posljednjih komandi). */
  54.     /* Prikaz logova na web interfejsu sa filtriranjem po */
  55.     /* tipu, vremenu, statusu. CSV export mogućnost. */
  56. /****** SYSTEM REQUIREMENT 8 *****/
  57.     /* Web interfejs autentifikacija sa admin korisničkim */
  58.     /* imenom i lozinkom. JWT token ili session-based */
  59.     /* autentifikacija. Logovanje pokušaja pristupa. */
  60.     /* Default user: admin/admin123 (promjena obavezna */
  61.     /* pri prvom loginu). */
  62. /****** END SYSTEM REQUIREMENTS *****/
  63.  
  64.  
  65. /* START CODE */
  66.  
  67. #include <Arduino.h>
  68. #include <HardwareSerial.h>
  69. #include <WiFi.h>
  70. #include <WebServer.h>
  71. #include <ArduinoJson.h>
  72. #include <Update.h>
  73. #include <Preferences.h>
  74. #include <time.h>
  75. #include <vector>
  76. #include <lvgl.h>
  77. #include "waveshare_lcd_port.h"
  78.  
  79. // =====================================================================
  80. // SYSTEM REQUIREMENTS - UART1 Configuration for Modbus RTU
  81. // =====================================================================
  82. // UART1 sa GPIO17(TX) i GPIO18(RX) na 9600 baud
  83. #define UART1_TX_PIN 17
  84. #define UART1_RX_PIN 18
  85. #define MODBUS_BAUD 9600
  86. #define UART_CHANNEL 1
  87.  
  88. // =====================================================================
  89. // RS485 Control and Modbus Configuration
  90. // =====================================================================
  91. #define RS485_DE_PIN 4              // RS485 Driver Enable pin
  92. #define MODBUS_SLAVE_ADDR 1         // Slave address for Modbus commands
  93. #define NUM_RELAYS 8                // Number of relays (0-7)
  94. #define MODBUS_TIMEOUT 1000         // milliseconds
  95. #define MODBUS_MAX_RETRIES 3
  96. #define MODBUS_CRC_POLY 0xA001
  97.  
  98. // =====================================================================
  99. // Web Server Configuration
  100. // =====================================================================
  101. #define WEB_SERVER_PORT 80
  102. #define WEB_DASHBOARD_REFRESH_MS 500
  103.  
  104. // =====================================================================
  105. // EEPROM/SPIFFS Configuration
  106. // =====================================================================
  107. #define MAX_LOG_ENTRIES 1000
  108. #define LOG_ENTRY_SIZE 128
  109.  
  110. // =====================================================================
  111. // WiFi Configuration
  112. // =====================================================================
  113. #define WIFI_SSID "YourSSID"
  114. #define WIFI_PASSWORD "YourPassword"
  115. #define WIFI_TIMEOUT 20000
  116.  
  117. // =====================================================================
  118. // JWT/Session Configuration
  119. // =====================================================================
  120. #define JWT_SECRET "your-secret-key-change-this"
  121. #define DEFAULT_USERNAME "admin"
  122. #define DEFAULT_PASSWORD "admin123"
  123.  
  124. // =====================================================================
  125. // Display Configuration
  126. // =====================================================================
  127. #define DISPLAY_WIDTH 800
  128. #define DISPLAY_HEIGHT 480
  129. #define BUTTON_ROWS 2
  130. #define BUTTONS_PER_ROW 4
  131. #define TOTAL_BUTTONS 8
  132.  
  133. // =====================================================================
  134. // LVGL Configuration
  135. // =====================================================================
  136. #define LVGL_TICK_PERIOD 5
  137. #define LVGL_BUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT / 10)
  138.  
  139. // =====================================================================
  140. // Global Variables
  141. // =====================================================================
  142. HardwareSerial ModbusSerial(UART_CHANNEL);
  143. WebServer webServer(WEB_SERVER_PORT);
  144. Preferences prefs;
  145.  
  146. // Relay states: 0 = OFF, 1 = ON
  147. uint8_t relayStates[NUM_RELAYS] = {0};
  148.  
  149. // UI state
  150. bool wifiConnected = false;
  151. bool modbusConnected = false;
  152. uint32_t lastUIRefresh = 0;
  153. uint32_t lastHealthCheck = 0;
  154.  
  155. // Authentication
  156. String currentSessionToken = "";
  157. uint32_t sessionExpire = 0;
  158. const uint32_t SESSION_TIMEOUT = 3600000; // 1 hour in milliseconds
  159.  
  160. // LVGL objects
  161. static lv_obj_t *screen_main = NULL;
  162. static lv_obj_t *relay_buttons[NUM_RELAYS];
  163. static lv_obj_t *wifi_label = NULL;
  164. static lv_obj_t *modbus_label = NULL;
  165. static lv_obj_t *status_container = NULL;
  166.  
  167. // =====================================================================
  168. // Log Entry Structure
  169. // =====================================================================
  170. struct LogEntry {
  171.     uint32_t timestamp;
  172.     uint8_t type;           // 0=Modbus Send, 1=Modbus Response, 2=Error, 3=Access
  173.     uint8_t relayIndex;
  174.     bool value;
  175.     bool success;
  176.     char details[64];
  177. };
  178.  
  179. // =====================================================================
  180. // Global Log Buffer
  181. // =====================================================================
  182. std::vector<LogEntry> modbusLog;
  183.  
  184. // =====================================================================
  185. // LVGL Display Buffer
  186. // =====================================================================
  187. static lv_disp_buf_t disp_buf;
  188. static lv_color_t buf[LVGL_BUFFER_SIZE];
  189.  
  190. // =====================================================================
  191. // FUNCTION: LVGL Flush Callback
  192. // =====================================================================
  193. void lv_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
  194. {
  195.     esp_panel::drivers::LCD *lcd = waveshare_lcd_get();
  196.     if (!lcd) return;
  197.  
  198.     uint32_t w = (area->x2 - area->x1 + 1);
  199.     uint32_t h = (area->y2 - area->y1 + 1);
  200.    
  201.     lcd->drawBitmap(area->x1, area->y1, w, h, (uint16_t *)color_p);
  202.    
  203.     lv_disp_flush_ready(disp_drv);
  204. }
  205.  
  206. // =====================================================================
  207. // FUNCTION: LVGL Timer Callback
  208. // =====================================================================
  209. static void lv_tick_task(void *arg)
  210. {
  211.     lv_tick_inc(LVGL_TICK_PERIOD);
  212. }
  213.  
  214. // =====================================================================
  215. // FUNCTION: Initialize LVGL Display
  216. // =====================================================================
  217. void initializeLVGL()
  218. {
  219.     Serial.println("Initializing LVGL display...");
  220.    
  221.     // Initialize LVGL
  222.     lv_init();
  223.    
  224.     // Initialize display buffer
  225.     lv_disp_buf_init(&disp_buf, buf, NULL, LVGL_BUFFER_SIZE);
  226.    
  227.     // Initialize display driver
  228.     lv_disp_drv_t disp_drv;
  229.     lv_disp_drv_init(&disp_drv);
  230.    
  231.     disp_drv.buffer = &disp_buf;
  232.     disp_drv.hor_res = DISPLAY_WIDTH;
  233.     disp_drv.ver_res = DISPLAY_HEIGHT;
  234.     disp_drv.flush_cb = lv_flush_cb;
  235.    
  236.     lv_disp_drv_register(&disp_drv);
  237.    
  238.     // Create main screen
  239.     screen_main = lv_scr_act();
  240.     lv_obj_set_style_local_bg_color(screen_main, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
  241.    
  242.     Serial.println("LVGL initialized successfully");
  243. }
  244.  
  245. // =====================================================================
  246. // FUNCTION: Create GUI Relay Control Panel
  247. // =====================================================================
  248. void createRelayPanel()
  249. {
  250.     Serial.println("Creating relay control panel...");
  251.    
  252.     // Create container for relay buttons
  253.     lv_obj_t *relay_container = lv_cont_create(screen_main, NULL);
  254.     lv_obj_set_auto_realign(relay_container, true);
  255.     lv_obj_align(relay_container, NULL, LV_ALIGN_IN_TOP_MID, 0, 10);
  256.     lv_cont_set_fit2(relay_container, LV_FIT_MAX, LV_FIT_MAX);
  257.     lv_cont_set_layout(relay_container, LV_LAYOUT_GRID);
  258.    
  259.     // Set grid spacing
  260.     lv_obj_set_style_local_pad_all(relay_container, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 10);
  261.     lv_obj_set_style_local_pad_inner(relay_container, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 10);
  262.    
  263.     // Create relay buttons in 4x2 grid
  264.     for (uint8_t i = 0; i < NUM_RELAYS; i++)
  265.     {
  266.         relay_buttons[i] = lv_btn_create(relay_container, NULL);
  267.         lv_obj_set_width(relay_buttons[i], 150);
  268.         lv_obj_set_height(relay_buttons[i], 100);
  269.        
  270.         // Update button appearance based on state
  271.         if (relayStates[i])
  272.         {
  273.             lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
  274.         }
  275.         else
  276.         {
  277.             lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
  278.         }
  279.        
  280.         // Add label to button
  281.         lv_obj_t *label = lv_label_create(relay_buttons[i], NULL);
  282.         char btn_text[16];
  283.         snprintf(btn_text, sizeof(btn_text), "Relay %d\n%s", i, relayStates[i] ? "ON" : "OFF");
  284.         lv_label_set_text(label, btn_text);
  285.         lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
  286.     }
  287.    
  288.     Serial.println("Relay panel created successfully");
  289. }
  290.  
  291. // =====================================================================
  292. // FUNCTION: Create Status Display
  293. // =====================================================================
  294. void createStatusDisplay()
  295. {
  296.     Serial.println("Creating status display...");
  297.    
  298.     // Create status container at bottom
  299.     status_container = lv_cont_create(screen_main, NULL);
  300.     lv_obj_set_width(status_container, DISPLAY_WIDTH);
  301.     lv_obj_set_height(status_container, 80);
  302.     lv_obj_align(status_container, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
  303.     lv_obj_set_style_local_bg_color(status_container, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
  304.    
  305.     // WiFi status label
  306.     wifi_label = lv_label_create(status_container, NULL);
  307.     lv_label_set_text(wifi_label, "WiFi: Connecting...");
  308.     lv_obj_align(wifi_label, NULL, LV_ALIGN_IN_TOP_LEFT, 10, 10);
  309.    
  310.     // Modbus status label
  311.     modbus_label = lv_label_create(status_container, NULL);
  312.     lv_label_set_text(modbus_label, "Modbus: Ready");
  313.     lv_obj_align(modbus_label, wifi_label, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 5);
  314.    
  315.     Serial.println("Status display created successfully");
  316. }
  317.  
  318. // =====================================================================
  319. // FUNCTION: Update GUI Display
  320. // =====================================================================
  321. void updateGUIDisplay()
  322. {
  323.     // Update WiFi status
  324.     if (wifi_label)
  325.     {
  326.         if (wifiConnected)
  327.         {
  328.             lv_label_set_text(wifi_label, "WiFi: Connected");
  329.             lv_obj_set_style_local_text_color(wifi_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
  330.         }
  331.         else
  332.         {
  333.             lv_label_set_text(wifi_label, "WiFi: Disconnected");
  334.             lv_obj_set_style_local_text_color(wifi_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
  335.         }
  336.     }
  337.    
  338.     // Update Modbus status
  339.     if (modbus_label)
  340.     {
  341.         if (modbusConnected)
  342.         {
  343.             lv_label_set_text(modbus_label, "Modbus: Connected");
  344.             lv_obj_set_style_local_text_color(modbus_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
  345.         }
  346.         else
  347.         {
  348.             lv_label_set_text(modbus_label, "Modbus: Offline");
  349.             lv_obj_set_style_local_text_color(modbus_label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
  350.         }
  351.     }
  352.    
  353.     // Update relay button states
  354.     for (uint8_t i = 0; i < NUM_RELAYS; i++)
  355.     {
  356.         if (relay_buttons[i])
  357.         {
  358.             if (relayStates[i])
  359.             {
  360.                 lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
  361.             }
  362.             else
  363.             {
  364.                 lv_obj_set_style_local_bg_color(relay_buttons[i], LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
  365.             }
  366.         }
  367.     }
  368.    
  369.     // Refresh LVGL
  370.     lv_task_handler();
  371. }
  372.  
  373. // =====================================================================
  374. // FUNCTION: Initialize Preferences/NVS Storage
  375. // =====================================================================
  376. void initializeStorage()
  377. {
  378.     if (!prefs.begin("modbus_esp32", false))
  379.     {
  380.         Serial.println("Failed to initialize Preferences");
  381.         return;
  382.     }
  383.    
  384.     Serial.println("Storage initialized");
  385. }
  386.  
  387. // =====================================================================
  388. // FUNCTION: Add Log Entry
  389. // =====================================================================
  390. void addLogEntry(uint8_t type, uint8_t relayIndex, bool value, bool success, const char* details)
  391. {
  392.     LogEntry entry;
  393.     entry.timestamp = millis() / 1000;
  394.     entry.type = type;
  395.     entry.relayIndex = relayIndex;
  396.     entry.value = value;
  397.     entry.success = success;
  398.     strncpy(entry.details, details, sizeof(entry.details) - 1);
  399.     entry.details[sizeof(entry.details) - 1] = '\0';
  400.    
  401.     modbusLog.push_back(entry);
  402.    
  403.     // Maintain max log size
  404.     if (modbusLog.size() > MAX_LOG_ENTRIES)
  405.     {
  406.         modbusLog.erase(modbusLog.begin());
  407.     }
  408. }
  409.  
  410. // =====================================================================
  411. // FUNCTION: Calculate Modbus CRC16
  412. // =====================================================================
  413. uint16_t calculateModbusCRC(uint8_t *data, uint8_t length)
  414. {
  415.     uint16_t crc = 0xFFFF;
  416.    
  417.     for(uint8_t i = 0; i < length; i++)
  418.     {
  419.         crc ^= data[i];
  420.        
  421.         for(uint8_t j = 0; j < 8; j++)
  422.         {
  423.             if(crc & 1)
  424.                 crc = (crc >> 1) ^ MODBUS_CRC_POLY;
  425.             else
  426.                 crc >>= 1;
  427.         }
  428.     }
  429.    
  430.     return crc;
  431. }
  432.  
  433. // =====================================================================
  434. // FUNCTION: Set RS485 to TX mode
  435. // =====================================================================
  436. inline void rs485_tx()
  437. {
  438.     digitalWrite(RS485_DE_PIN, HIGH);
  439. }
  440.  
  441. // =====================================================================
  442. // FUNCTION: Set RS485 to RX mode
  443. // =====================================================================
  444. inline void rs485_rx()
  445. {
  446.     digitalWrite(RS485_DE_PIN, LOW);
  447. }
  448.  
  449. // =====================================================================
  450. // SYSTEM REQUIREMENT 3: Modbus Write Single Coil
  451. // =====================================================================
  452. // FC 0x05: Write Single Coil
  453. bool modbusWriteCoil(uint8_t relayIndex, bool on)
  454. {
  455.     if (relayIndex >= NUM_RELAYS)
  456.     {
  457.         addLogEntry(2, relayIndex, on, false, "Invalid relay index");
  458.         return false;
  459.     }
  460.    
  461.     uint8_t txBuffer[8];
  462.    
  463.     // Build Modbus frame
  464.     txBuffer[0] = MODBUS_SLAVE_ADDR;    // Slave address
  465.     txBuffer[1] = 0x05;                 // Function code: Write Single Coil
  466.     txBuffer[2] = 0x00;                 // Coil address high byte
  467.     txBuffer[3] = relayIndex;           // Coil address low byte (Coil 0-7)
  468.     txBuffer[4] = on ? 0xFF : 0x00;     // Value high byte (0xFF00 for ON, 0x0000 for OFF)
  469.     txBuffer[5] = 0x00;                 // Value low byte
  470.    
  471.     // Calculate and append CRC16
  472.     uint16_t crc = calculateModbusCRC(txBuffer, 6);
  473.     txBuffer[6] = crc & 0xFF;
  474.     txBuffer[7] = crc >> 8;
  475.    
  476.     // Clear any pending data in receive buffer
  477.     while(ModbusSerial.available())
  478.         ModbusSerial.read();
  479.    
  480.     // Switch RS485 to TX mode
  481.     rs485_tx();
  482.     delayMicroseconds(100);
  483.    
  484.     // Send Modbus frame
  485.     ModbusSerial.write(txBuffer, 8);
  486.     ModbusSerial.flush();
  487.    
  488.     // Wait for transmission to complete
  489.     delayMicroseconds(200);
  490.    
  491.     // Switch RS485 to RX mode
  492.     rs485_rx();
  493.    
  494.     // Read response with timeout
  495.     uint8_t rxBuffer[8];
  496.     uint8_t bytesRead = 0;
  497.     uint32_t startTime = millis();
  498.    
  499.     while(ModbusSerial.available() && bytesRead < 8)
  500.     {
  501.         if(millis() - startTime > MODBUS_TIMEOUT)
  502.         {
  503.             addLogEntry(2, relayIndex, on, false, "Modbus timeout");
  504.             modbusConnected = false;
  505.             return false;
  506.         }
  507.        
  508.         rxBuffer[bytesRead] = ModbusSerial.read();
  509.         bytesRead++;
  510.         delayMicroseconds(100);
  511.     }
  512.    
  513.     // Verify response
  514.     bool success = false;
  515.     if(bytesRead >= 8)
  516.     {
  517.         // Extract CRC from response
  518.         uint16_t receivedCRC = (rxBuffer[7] << 8) | rxBuffer[6];
  519.        
  520.         // Calculate CRC of received data (excluding CRC bytes)
  521.         uint16_t calculatedCRC = calculateModbusCRC(rxBuffer, 6);
  522.        
  523.         if(receivedCRC == calculatedCRC)
  524.         {
  525.             success = true;
  526.             relayStates[relayIndex] = on ? 1 : 0;
  527.             addLogEntry(1, relayIndex, on, true, "CRC verified");
  528.             modbusConnected = true;
  529.            
  530.             Serial.print("Modbus Write Success: Relay ");
  531.             Serial.print(relayIndex);
  532.             Serial.print(" -> ");
  533.             Serial.println(on ? "ON" : "OFF");
  534.         }
  535.         else
  536.         {
  537.             addLogEntry(2, relayIndex, on, false, "CRC mismatch");
  538.             Serial.println("Modbus Response: CRC mismatch!");
  539.             modbusConnected = false;
  540.         }
  541.     }
  542.     else
  543.     {
  544.         addLogEntry(2, relayIndex, on, false, "No response");
  545.         Serial.println("Modbus Write: No response from slave");
  546.         modbusConnected = false;
  547.     }
  548.    
  549.     return success;
  550. }
  551.  
  552. // =====================================================================
  553. // FUNCTION: Read Modbus Coil Status (Function Code 0x01)
  554. // =====================================================================
  555. bool modbusReadCoils(uint8_t startCoil, uint8_t count, uint8_t* values)
  556. {
  557.     if (startCoil + count > NUM_RELAYS)
  558.     {
  559.         addLogEntry(2, startCoil, false, false, "Invalid coil range");
  560.         return false;
  561.     }
  562.    
  563.     uint8_t txBuffer[12];
  564.    
  565.     // Build read coils frame
  566.     txBuffer[0] = MODBUS_SLAVE_ADDR;
  567.     txBuffer[1] = 0x01;                 // Function code: Read Coils
  568.     txBuffer[2] = 0x00;
  569.     txBuffer[3] = startCoil;
  570.     txBuffer[4] = 0x00;
  571.     txBuffer[5] = count;
  572.    
  573.     uint16_t crc = calculateModbusCRC(txBuffer, 6);
  574.     txBuffer[6] = crc & 0xFF;
  575.     txBuffer[7] = crc >> 8;
  576.    
  577.     // Clear receive buffer
  578.     while(ModbusSerial.available())
  579.         ModbusSerial.read();
  580.    
  581.     // Send request
  582.     rs485_tx();
  583.     delayMicroseconds(100);
  584.     ModbusSerial.write(txBuffer, 8);
  585.     ModbusSerial.flush();
  586.     delayMicroseconds(200);
  587.     rs485_rx();
  588.    
  589.     // Read response
  590.     uint8_t rxBuffer[32];
  591.     uint8_t bytesRead = 0;
  592.     uint32_t startTime = millis();
  593.    
  594.     while(ModbusSerial.available() && bytesRead < 32)
  595.     {
  596.         if(millis() - startTime > MODBUS_TIMEOUT)
  597.         {
  598.             addLogEntry(2, startCoil, false, false, "Read timeout");
  599.             return false;
  600.         }
  601.        
  602.         rxBuffer[bytesRead] = ModbusSerial.read();
  603.         bytesRead++;
  604.         delayMicroseconds(100);
  605.     }
  606.    
  607.     // Verify response
  608.     if(bytesRead >= 5)
  609.     {
  610.         uint16_t receivedCRC = (rxBuffer[bytesRead - 1] << 8) | rxBuffer[bytesRead - 2];
  611.         uint16_t calculatedCRC = calculateModbusCRC(rxBuffer, bytesRead - 2);
  612.        
  613.         if(receivedCRC == calculatedCRC)
  614.         {
  615.             // Parse coil values
  616.             uint8_t byteCount = rxBuffer[2];
  617.             for(uint8_t i = 0; i < count && i < byteCount; i++)
  618.             {
  619.                 values[i] = (rxBuffer[3 + (i / 8)] >> (i % 8)) & 1;
  620.             }
  621.             addLogEntry(1, startCoil, false, true, "Read coils OK");
  622.             modbusConnected = true;
  623.             return true;
  624.         }
  625.         else
  626.         {
  627.             addLogEntry(2, startCoil, false, false, "Read CRC error");
  628.         }
  629.     }
  630.     else
  631.     {
  632.         addLogEntry(2, startCoil, false, false, "Read no response");
  633.     }
  634.    
  635.     modbusConnected = false;
  636.     return false;
  637. }
  638.  
  639. // =====================================================================
  640. // FUNCTION: Initialize UART1 for Modbus RTU
  641. // =====================================================================
  642. void initializeModbusUART()
  643. {
  644.     // Configure RS485 control pin
  645.     pinMode(RS485_DE_PIN, OUTPUT);
  646.     digitalWrite(RS485_DE_PIN, LOW);  // Start in RX mode
  647.    
  648.     // Initialize UART1 with specified pins and baud rate
  649.     ModbusSerial.begin(
  650.         MODBUS_BAUD,           // 9600 baud
  651.         SERIAL_8N1,            // 8 data bits, no parity, 1 stop bit
  652.         UART1_RX_PIN,          // RX on GPIO18
  653.         UART1_TX_PIN           // TX on GPIO17
  654.     );
  655.    
  656.     Serial.println("Modbus UART1 initialized");
  657.     Serial.println("TX: GPIO17, RX: GPIO18, Baud: 9600");
  658. }
  659.  
  660. // =====================================================================
  661. // FUNCTION: Initialize WiFi Connection
  662. // =====================================================================
  663. void initializeWiFi()
  664. {
  665.     Serial.println("\nStarting WiFi connection...");
  666.     WiFi.mode(WIFI_STA);
  667.     WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  668.    
  669.     uint32_t startTime = millis();
  670.     while(WiFi.status() != WL_CONNECTED && millis() - startTime < WIFI_TIMEOUT)
  671.     {
  672.         delay(500);
  673.         Serial.print(".");
  674.     }
  675.    
  676.     if(WiFi.status() == WL_CONNECTED)
  677.     {
  678.         wifiConnected = true;
  679.         Serial.println("\nWiFi connected!");
  680.         Serial.print("IP address: ");
  681.         Serial.println(WiFi.localIP());
  682.        
  683.         // Configure time for NTP
  684.         configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  685.         Serial.println("NTP time configured");
  686.     }
  687.     else
  688.     {
  689.         wifiConnected = false;
  690.         Serial.println("\nWiFi connection failed!");
  691.     }
  692. }
  693.  
  694. // =====================================================================
  695. // FUNCTION: Generate JWT Token
  696. // =====================================================================
  697. String generateJWT(const String& username)
  698. {
  699.     String payload = username + String(millis());
  700.    
  701.     // Simple hash for signature
  702.     uint32_t hash = 5381;
  703.     for(unsigned int i = 0; i < payload.length(); i++)
  704.     {
  705.         hash = ((hash << 5) + hash) + payload.charAt(i);
  706.     }
  707.    
  708.     String token = "ESP32." + payload + "." + String(hash);
  709.     return token;
  710. }
  711.  
  712. // =====================================================================
  713. // FUNCTION: Verify Session Token
  714. // =====================================================================
  715. bool verifySession()
  716. {
  717.     // Check if session is expired
  718.     if(millis() > sessionExpire && sessionExpire > 0)
  719.     {
  720.         currentSessionToken = "";
  721.         return false;
  722.     }
  723.    
  724.     return currentSessionToken.length() > 0;
  725. }
  726.  
  727. // =====================================================================
  728. // WEB API HANDLERS
  729. // =====================================================================
  730.  
  731. // =====================================================================
  732. // HANDLER: Login Endpoint
  733. // =====================================================================
  734. void handleLogin()
  735. {
  736.     if(webServer.method() != HTTP_POST)
  737.     {
  738.         webServer.send(405, "application/json", "{\"error\":\"Method not allowed\"}");
  739.         return;
  740.     }
  741.    
  742.     String body = webServer.arg("plain");
  743.     DynamicJsonDocument doc(256);
  744.    
  745.     if(deserializeJson(doc, body) != DeserializationError::Ok)
  746.     {
  747.         webServer.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
  748.         return;
  749.     }
  750.    
  751.     String username = doc["username"] | "";
  752.     String password = doc["password"] | "";
  753.    
  754.     // Verify credentials
  755.     if(username == DEFAULT_USERNAME && password == DEFAULT_PASSWORD)
  756.     {
  757.         // Generate session token
  758.         currentSessionToken = generateJWT(username);
  759.         sessionExpire = millis() + SESSION_TIMEOUT;
  760.        
  761.         DynamicJsonDocument response(256);
  762.         response["success"] = true;
  763.         response["token"] = currentSessionToken;
  764.         response["expires_in"] = SESSION_TIMEOUT / 1000;
  765.        
  766.         String jsonResponse;
  767.         serializeJson(response, jsonResponse);
  768.         webServer.send(200, "application/json", jsonResponse);
  769.        
  770.         addLogEntry(3, 0, true, true, "Login successful");
  771.     }
  772.     else
  773.     {
  774.         webServer.send(401, "application/json", "{\"error\":\"Invalid credentials\"}");
  775.         addLogEntry(3, 0, false, false, "Login failed");
  776.     }
  777. }
  778.  
  779. // =====================================================================
  780. // HANDLER: Get Relay Status
  781. // =====================================================================
  782. void handleGetRelayStatus()
  783. {
  784.     if(!verifySession())
  785.     {
  786.         webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
  787.         return;
  788.     }
  789.    
  790.     DynamicJsonDocument response(512);
  791.     response["timestamp"] = millis() / 1000;
  792.     response["modbus_connected"] = modbusConnected;
  793.     response["wifi_connected"] = wifiConnected;
  794.    
  795.     JsonArray relays = response.createNestedArray("relays");
  796.     for(uint8_t i = 0; i < NUM_RELAYS; i++)
  797.     {
  798.         JsonObject relay = relays.createNestedObject();
  799.         relay["index"] = i;
  800.         relay["state"] = relayStates[i] ? "ON" : "OFF";
  801.     }
  802.    
  803.     String jsonResponse;
  804.     serializeJson(response, jsonResponse);
  805.     webServer.send(200, "application/json", jsonResponse);
  806. }
  807.  
  808. // =====================================================================
  809. // HANDLER: Control Relay
  810. // =====================================================================
  811. void handleControlRelay()
  812. {
  813.     if(!verifySession())
  814.     {
  815.         webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
  816.         return;
  817.     }
  818.    
  819.     if(webServer.method() != HTTP_POST)
  820.     {
  821.         webServer.send(405, "application/json", "{\"error\":\"Method not allowed\"}");
  822.         return;
  823.     }
  824.    
  825.     String body = webServer.arg("plain");
  826.     DynamicJsonDocument doc(256);
  827.    
  828.     if(deserializeJson(doc, body) != DeserializationError::Ok)
  829.     {
  830.         webServer.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
  831.         return;
  832.     }
  833.    
  834.     uint8_t relayIndex = doc["relay"] | 255;
  835.     String action = doc["action"] | "";
  836.    
  837.     if(relayIndex >= NUM_RELAYS)
  838.     {
  839.         webServer.send(400, "application/json", "{\"error\":\"Invalid relay index\"}");
  840.         return;
  841.     }
  842.    
  843.     bool turnOn = (action == "ON");
  844.     bool success = modbusWriteCoil(relayIndex, turnOn);
  845.    
  846.     DynamicJsonDocument response(256);
  847.     response["success"] = success;
  848.     response["relay"] = relayIndex;
  849.     response["state"] = turnOn ? "ON" : "OFF";
  850.    
  851.     String jsonResponse;
  852.     serializeJson(response, jsonResponse);
  853.    
  854.     webServer.send(success ? 200 : 500, "application/json", jsonResponse);
  855. }
  856.  
  857. // =====================================================================
  858. // HANDLER: Get Modbus Logs
  859. // =====================================================================
  860. void handleGetLogs()
  861. {
  862.     if(!verifySession())
  863.     {
  864.         webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
  865.         return;
  866.     }
  867.    
  868.     // Parse query parameters for filtering
  869.     String typeFilter = webServer.arg("type");
  870.     String limit = webServer.arg("limit");
  871.     int maxEntries = limit.length() > 0 ? limit.toInt() : 100;
  872.    
  873.     if(maxEntries > 1000) maxEntries = 1000;
  874.     if(maxEntries < 1) maxEntries = 10;
  875.    
  876.     DynamicJsonDocument response(8192);
  877.     response["total_entries"] = modbusLog.size();
  878.     response["returned"] = 0;
  879.    
  880.     JsonArray logs = response.createNestedArray("logs");
  881.    
  882.     int count = 0;
  883.     int startIdx = modbusLog.size() > maxEntries ? modbusLog.size() - maxEntries : 0;
  884.    
  885.     for(int i = startIdx; i < (int)modbusLog.size() && count < maxEntries; i++)
  886.     {
  887.         LogEntry& entry = modbusLog[i];
  888.        
  889.         // Apply type filter if specified
  890.         if(typeFilter.length() > 0)
  891.         {
  892.             uint8_t filterType = typeFilter.toInt();
  893.             if(entry.type != filterType) continue;
  894.         }
  895.        
  896.         JsonObject logEntry = logs.createNestedObject();
  897.         logEntry["timestamp"] = entry.timestamp;
  898.         logEntry["type"] = entry.type;
  899.         logEntry["relay"] = entry.relayIndex;
  900.         logEntry["value"] = entry.value ? "ON" : "OFF";
  901.         logEntry["success"] = entry.success;
  902.         logEntry["details"] = entry.details;
  903.        
  904.         count++;
  905.     }
  906.    
  907.     response["returned"] = count;
  908.    
  909.     String jsonResponse;
  910.     serializeJson(response, jsonResponse);
  911.     webServer.send(200, "application/json", jsonResponse);
  912. }
  913.  
  914. // =====================================================================
  915. // HANDLER: Export Logs as CSV
  916. // =====================================================================
  917. void handleExportLogs()
  918. {
  919.     if(!verifySession())
  920.     {
  921.         webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
  922.         return;
  923.     }
  924.    
  925.     String csv = "Timestamp,Type,Relay,Value,Success,Details\n";
  926.    
  927.     for(const auto& entry : modbusLog)
  928.     {
  929.         csv += String(entry.timestamp) + ",";
  930.         csv += String(entry.type) + ",";
  931.         csv += String(entry.relayIndex) + ",";
  932.         csv += (entry.value ? "ON" : "OFF") + String(",");
  933.         csv += (entry.success ? "true" : "false") + String(",");
  934.         csv += String(entry.details) + "\n";
  935.     }
  936.    
  937.     webServer.send(200, "text/csv", csv);
  938. }
  939.  
  940. // =====================================================================
  941. // HANDLER: OTA Firmware Update
  942. // =====================================================================
  943. void handleOTAUpdate()
  944. {
  945.     if(!verifySession())
  946.     {
  947.         webServer.send(401, "application/json", "{\"error\":\"Unauthorized\"}");
  948.         return;
  949.     }
  950.    
  951.     if(webServer.method() != HTTP_POST)
  952.     {
  953.         webServer.send(405, "application/json", "{\"error\":\"Method not allowed\"}");
  954.         return;
  955.     }
  956.    
  957.     if(!Update.hasError())
  958.     {
  959.         DynamicJsonDocument response(256);
  960.         response["success"] = true;
  961.         response["message"] = "Firmware update completed. Device rebooting...";
  962.        
  963.         String jsonResponse;
  964.         serializeJson(response, jsonResponse);
  965.         webServer.send(200, "application/json", jsonResponse);
  966.        
  967.         delay(1000);
  968.         ESP.restart();
  969.     }
  970.     else
  971.     {
  972.         DynamicJsonDocument response(256);
  973.         response["success"] = false;
  974.         response["error"] = Update.errorString();
  975.        
  976.         String jsonResponse;
  977.         serializeJson(response, jsonResponse);
  978.         webServer.send(500, "application/json", jsonResponse);
  979.     }
  980. }
  981.  
  982. // =====================================================================
  983. // HANDLER: Web Dashboard HTML
  984. // =====================================================================
  985. void handleDashboard()
  986. {
  987.     String html = R"rawliteral(
  988. <!DOCTYPE html>
  989. <html lang="en">
  990. <head>
  991.    <meta charset="UTF-8">
  992.    <meta name="viewport" content="width=device-width, initial-scale=1.0">
  993.    <title>ESP32-S3 Modbus Master Dashboard</title>
  994.    <style>
  995.        * { margin: 0; padding: 0; box-sizing: border-box; }
  996.        body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; }
  997.        .container { max-width: 1200px; margin: 0 auto; }
  998.        .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; }
  999.        .header h1 { color: #333; font-size: 28px; }
  1000.        .header .status { display: flex; gap: 20px; align-items: center; }
  1001.        .status-item { display: flex; align-items: center; gap: 10px; padding: 10px 15px; background: #f0f0f0; border-radius: 5px; }
  1002.        .status-indicator { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
  1003.        .status-indicator.online { background: #4CAF50; }
  1004.        .status-indicator.offline { background: #f44336; }
  1005.        .main-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 30px; margin-bottom: 30px; }
  1006.        .relay-panel { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
  1007.        .relay-panel h2 { color: #333; margin-bottom: 20px; font-size: 20px; }
  1008.        .relay-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; }
  1009.        .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; }
  1010.        .relay-button.on { background: #4CAF50; color: white; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4); }
  1011.        .relay-button.off { background: #f44336; color: white; box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4); }
  1012.        .relay-button:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0,0,0,0.2); }
  1013.        .relay-label { font-size: 12px; opacity: 0.8; }
  1014.        .sidebar { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); height: fit-content; }
  1015.        .sidebar h3 { color: #333; margin-bottom: 15px; }
  1016.        .sidebar-item { padding: 10px; margin-bottom: 10px; background: #f0f0f0; border-radius: 5px; font-size: 14px; }
  1017.        .sidebar-item label { display: block; color: #666; font-size: 12px; margin-bottom: 5px; }
  1018.        .sidebar-item span { display: block; color: #333; font-weight: bold; }
  1019.        .logs-panel { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); margin-bottom: 30px; }
  1020.        .logs-panel h2 { color: #333; margin-bottom: 20px; }
  1021.        .logs-table { width: 100%; border-collapse: collapse; font-size: 13px; }
  1022.        .logs-table th { background: #667eea; color: white; padding: 12px; text-align: left; font-weight: 600; }
  1023.        .logs-table td { padding: 12px; border-bottom: 1px solid #e0e0e0; }
  1024.        .logs-table tr:hover { background: #f5f5f5; }
  1025.        .log-success { color: #4CAF50; font-weight: bold; }
  1026.        .log-error { color: #f44336; font-weight: bold; }
  1027.        .button-group { display: flex; gap: 10px; margin-bottom: 20px; }
  1028.        .btn { padding: 12px 24px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.3s ease; }
  1029.        .btn-primary { background: #667eea; color: white; }
  1030.        .btn-primary:hover { background: #5568d3; transform: translateY(-2px); }
  1031.        .btn-secondary { background: #e0e0e0; color: #333; }
  1032.        .btn-secondary:hover { background: #d0d0d0; }
  1033.        .login-page { display: flex; justify-content: center; align-items: center; min-height: 100vh; }
  1034.        .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; }
  1035.        .login-box h1 { color: #333; margin-bottom: 30px; text-align: center; }
  1036.        .form-group { margin-bottom: 20px; }
  1037.        .form-group label { display: block; color: #333; font-weight: 600; margin-bottom: 8px; }
  1038.        .form-group input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; transition: border-color 0.3s ease; }
  1039.        .form-group input:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); }
  1040.        .alert { padding: 12px; border-radius: 5px; margin-bottom: 20px; display: none; }
  1041.        .alert.error { background: #ffebee; color: #c62828; display: block; }
  1042.        .hidden { display: none !important; }
  1043.    </style>
  1044. </head>
  1045. <body>
  1046.    <div class="login-page" id="loginPage">
  1047.        <div class="login-box">
  1048.            <h1>ESP32-S3 Modbus Master</h1>
  1049.            <div class="alert error" id="loginError"></div>
  1050.            <form id="loginForm">
  1051.                <div class="form-group">
  1052.                    <label for="username">Username</label>
  1053.                    <input type="text" id="username" name="username" placeholder="admin" required>
  1054.                </div>
  1055.                <div class="form-group">
  1056.                    <label for="password">Password</label>
  1057.                    <input type="password" id="password" name="password" placeholder="••••••" required>
  1058.                </div>
  1059.                <button type="submit" class="btn btn-primary" style="width: 100%;">Login</button>
  1060.            </form>
  1061.        </div>
  1062.    </div>
  1063.    
  1064.    <div id="dashboard" class="hidden">
  1065.        <div class="container">
  1066.            <div class="header">
  1067.                <h1>ESP32-S3 Modbus Master Dashboard</h1>
  1068.                <div class="status">
  1069.                    <div class="status-item">
  1070.                        <span class="status-indicator online" id="wifiStatus"></span>
  1071.                        <span>WiFi: <span id="wifiStatusText">Connecting...</span></span>
  1072.                    </div>
  1073.                    <div class="status-item">
  1074.                        <span class="status-indicator online" id="modbusStatus"></span>
  1075.                        <span>Modbus: <span id="modbusStatusText">Ready</span></span>
  1076.                    </div>
  1077.                    <button class="btn btn-secondary" onclick="logout()">Logout</button>
  1078.                </div>
  1079.            </div>
  1080.            
  1081.            <div class="main-grid">
  1082.                <div class="relay-panel">
  1083.                    <h2>Relay Control (8 Channel)</h2>
  1084.                    <div class="relay-grid" id="relayGrid"></div>
  1085.                </div>
  1086.                
  1087.                <div class="sidebar">
  1088.                    <h3>System Status</h3>
  1089.                    <div class="sidebar-item">
  1090.                        <label>Uptime</label>
  1091.                        <span id="uptime">0s</span>
  1092.                    </div>
  1093.                    <div class="sidebar-item">
  1094.                        <label>Modbus Commands</label>
  1095.                        <span id="commandCount">0</span>
  1096.                    </div>
  1097.                    <div class="sidebar-item">
  1098.                        <label>Connected Relays</label>
  1099.                        <span id="relayCount">0/8</span>
  1100.                    </div>
  1101.                    <div class="sidebar-item">
  1102.                        <label>Last Update</label>
  1103.                        <span id="lastUpdate">--:--:--</span>
  1104.                    </div>
  1105.                </div>
  1106.            </div>
  1107.            
  1108.            <div class="logs-panel">
  1109.                <h2>Modbus Log</h2>
  1110.                <div class="button-group">
  1111.                    <button class="btn btn-primary" onclick="refreshLogs()">Refresh Logs</button>
  1112.                    <button class="btn btn-secondary" onclick="exportLogs()">Export CSV</button>
  1113.                    <button class="btn btn-secondary" onclick="clearLogs()">Clear Logs</button>
  1114.                </div>
  1115.                <table class="logs-table">
  1116.                    <thead>
  1117.                        <tr>
  1118.                            <th>Timestamp</th>
  1119.                            <th>Type</th>
  1120.                            <th>Relay</th>
  1121.                            <th>Value</th>
  1122.                            <th>Status</th>
  1123.                            <th>Details</th>
  1124.                        </tr>
  1125.                    </thead>
  1126.                    <tbody id="logsBody">
  1127.                        <tr><td colspan="6" style="text-align: center; color: #999;">Loading logs...</td></tr>
  1128.                     </tbody>
  1129.                 </table>
  1130.             </div>
  1131.         </div>
  1132.     </div>
  1133.    
  1134.     <script>
  1135.         const API_URL = location.origin;
  1136.         let authToken = localStorage.getItem('authToken');
  1137.         let refreshInterval = null;
  1138.         let startTime = Date.now();
  1139.        
  1140.         if (authToken) {
  1141.             showDashboard();
  1142.             startAutoRefresh();
  1143.         } else {
  1144.             showLoginPage();
  1145.         }
  1146.        
  1147.         document.getElementById('loginForm').addEventListener('submit', async (e) => {
  1148.             e.preventDefault();
  1149.             const username = document.getElementById('username').value;
  1150.             const password = document.getElementById('password').value;
  1151.             try {
  1152.                 const response = await fetch(API_URL + '/api/login', {
  1153.                     method: 'POST',
  1154.                     headers: { 'Content-Type': 'application/json' },
  1155.                     body: JSON.stringify({ username, password })
  1156.                 });
  1157.                 if (response.status === 200) {
  1158.                     const data = await response.json();
  1159.                     authToken = data.token;
  1160.                     localStorage.setItem('authToken', authToken);
  1161.                     showDashboard();
  1162.                     startAutoRefresh();
  1163.                 } else {
  1164.                     showLoginError('Invalid username or password');
  1165.                 }
  1166.             } catch (error) {
  1167.                 showLoginError('Connection error: ' + error.message);
  1168.             }
  1169.         });
  1170.        
  1171.         function showLoginPage() {
  1172.             document.getElementById('loginPage').classList.remove('hidden');
  1173.             document.getElementById('dashboard').classList.add('hidden');
  1174.         }
  1175.        
  1176.         function showDashboard() {
  1177.             document.getElementById('loginPage').classList.add('hidden');
  1178.             document.getElementById('dashboard').classList.remove('hidden');
  1179.             buildRelayButtons();
  1180.             updateDashboard();
  1181.         }
  1182.        
  1183.         function showLoginError(message) {
  1184.             const errorDiv = document.getElementById('loginError');
  1185.             errorDiv.textContent = message;
  1186.             errorDiv.style.display = 'block';
  1187.         }
  1188.        
  1189.         function logout() {
  1190.             authToken = null;
  1191.             localStorage.removeItem('authToken');
  1192.             if (refreshInterval) clearInterval(refreshInterval);
  1193.             showLoginPage();
  1194.         }
  1195.        
  1196.         function buildRelayButtons() {
  1197.             const grid = document.getElementById('relayGrid');
  1198.             grid.innerHTML = '';
  1199.             for (let i = 0; i < 8; i++) {
  1200.                 const button = document.createElement('button');
  1201.                 button.className = 'relay-button';
  1202.                 button.id = 'relay-' + i;
  1203.                 button.innerHTML = '<span>' + i + '</span><span class="relay-label">Relay</span>';
  1204.                 button.onclick = () => toggleRelay(i);
  1205.                 grid.appendChild(button);
  1206.             }
  1207.         }
  1208.        
  1209.         async function toggleRelay(index) {
  1210.             const button = document.getElementById('relay-' + index);
  1211.             const isOn = button.classList.contains('on');
  1212.             try {
  1213.                 const response = await fetch(API_URL + '/api/relay/control', {
  1214.                     method: 'POST',
  1215.                     headers: {
  1216.                         'Content-Type': 'application/json',
  1217.                         'Authorization': 'Bearer ' + authToken
  1218.                     },
  1219.                     body: JSON.stringify({
  1220.                         relay: index,
  1221.                         action: isOn ? 'OFF' : 'ON'
  1222.                     })
  1223.                 });
  1224.                 if (response.status === 200) {
  1225.                     const data = await response.json();
  1226.                     if (data.success) {
  1227.                         updateRelayButton(index, data.state === 'ON');
  1228.                     }
  1229.                 } else if (response.status === 401) {
  1230.                     logout();
  1231.                 }
  1232.             } catch (error) {
  1233.                 console.error('Error toggling relay:', error);
  1234.             }
  1235.         }
  1236.        
  1237.         async function updateDashboard() {
  1238.             try {
  1239.                 const response = await fetch(API_URL + '/api/relay/status', {
  1240.                     headers: { 'Authorization': 'Bearer ' + authToken }
  1241.                 });
  1242.                 if (response.status === 200) {
  1243.                     const data = await response.json();
  1244.                     for (let relay of data.relays) {
  1245.                         updateRelayButton(relay.index, relay.state === 'ON');
  1246.                     }
  1247.                     updateSystemStatus();
  1248.                     refreshLogs();
  1249.                 } else if (response.status === 401) {
  1250.                     logout();
  1251.                 }
  1252.             } catch (error) {
  1253.                 console.error('Error updating dashboard:', error);
  1254.             }
  1255.         }
  1256.        
  1257.         function updateRelayButton(index, isOn) {
  1258.             const button = document.getElementById('relay-' + index);
  1259.             if (isOn) {
  1260.                 button.classList.remove('off');
  1261.                 button.classList.add('on');
  1262.                 button.innerHTML = '<span>' + index + '</span><span class="relay-label">ON</span>';
  1263.             } else {
  1264.                 button.classList.remove('on');
  1265.                 button.classList.add('off');
  1266.                 button.innerHTML = '<span>' + index + '</span><span class="relay-label">OFF</span>';
  1267.             }
  1268.         }
  1269.        
  1270.         function updateSystemStatus() {
  1271.             const uptime = Math.floor((Date.now() - startTime) / 1000);
  1272.             document.getElementById('uptime').textContent = formatUptime(uptime);
  1273.             document.getElementById('lastUpdate').textContent = new Date().toLocaleTimeString();
  1274.         }
  1275.        
  1276.         function formatUptime(seconds) {
  1277.             const days = Math.floor(seconds / 86400);
  1278.             const hours = Math.floor((seconds % 86400) / 3600);
  1279.             const minutes = Math.floor((seconds % 3600) / 60);
  1280.             const secs = seconds % 60;
  1281.             if (days > 0) return days + 'd ' + hours + 'h ' + minutes + 'm';
  1282.             if (hours > 0) return hours + 'h ' + minutes + 'm ' + secs + 's';
  1283.             return minutes + 'm ' + secs + 's';
  1284.         }
  1285.        
  1286.         async function refreshLogs() {
  1287.             try {
  1288.                 const response = await fetch(API_URL + '/api/logs?limit=50', {
  1289.                     headers: { 'Authorization': 'Bearer ' + authToken }
  1290.                 });
  1291.                 if (response.status === 200) {
  1292.                     const data = await response.json();
  1293.                     const tbody = document.getElementById('logsBody');
  1294.                     tbody.innerHTML = '';
  1295.                     for (let log of data.logs) {
  1296.                         const row = document.createElement('tr');
  1297.                         const statusClass = log.success ? 'log-success' : 'log-error';
  1298.                         const statusText = log.success ? 'OK' : 'Error';
  1299.                         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>';
  1300.                         tbody.appendChild(row);
  1301.                     }
  1302.                     document.getElementById('commandCount').textContent = data.total_entries;
  1303.                 } else if (response.status === 401) {
  1304.                     logout();
  1305.                 }
  1306.             } catch (error) {
  1307.                 console.error('Error refreshing logs:', error);
  1308.             }
  1309.         }
  1310.        
  1311.         async function exportLogs() {
  1312.             try {
  1313.                 const response = await fetch(API_URL + '/api/logs/export', {
  1314.                     headers: { 'Authorization': 'Bearer ' + authToken }
  1315.                 });
  1316.                 if (response.status === 200) {
  1317.                     const csv = await response.text();
  1318.                     const blob = new Blob([csv], { type: 'text/csv' });
  1319.                     const url = window.URL.createObjectURL(blob);
  1320.                     const a = document.createElement('a');
  1321.                     a.href = url;
  1322.                     a.download = 'modbus_logs.csv';
  1323.                     a.click();
  1324.                 } else if (response.status === 401) {
  1325.                     logout();
  1326.                 }
  1327.             } catch (error) {
  1328.                 console.error('Error exporting logs:', error);
  1329.             }
  1330.         }
  1331.        
  1332.         function clearLogs() {
  1333.             if (confirm('Are you sure you want to clear all logs?')) {
  1334.                 console.log('Logs cleared');
  1335.             }
  1336.         }
  1337.        
  1338.         function startAutoRefresh() {
  1339.             updateDashboard();
  1340.             refreshInterval = setInterval(updateDashboard, 500);
  1341.         }
  1342.     </script>
  1343. </body>
  1344. </html>
  1345. )rawliteral";
  1346.    
  1347.    webServer.send(200, "text/html", html);
  1348. }
  1349.  
  1350. // =====================================================================
  1351. // HANDLER: Not Found
  1352. // =====================================================================
  1353. void handleNotFound()
  1354. {
  1355.    webServer.send(404, "application/json", "{\"error\":\"Not found\"}");
  1356. }
  1357.  
  1358. // =====================================================================
  1359. // FUNCTION: Setup Web Server Routes
  1360. // =====================================================================
  1361. void setupWebServer()
  1362. {
  1363.     webServer.on("/", HTTP_GET, handleDashboard);
  1364.     webServer.on("/api/login", HTTP_POST, handleLogin);
  1365.     webServer.on("/api/relay/status", HTTP_GET, handleGetRelayStatus);
  1366.     webServer.on("/api/relay/control", HTTP_POST, handleControlRelay);
  1367.     webServer.on("/api/logs", HTTP_GET, handleGetLogs);
  1368.     webServer.on("/api/logs/export", HTTP_GET, handleExportLogs);
  1369.     webServer.on("/api/ota/update", HTTP_POST, handleOTAUpdate);
  1370.    
  1371.     webServer.onNotFound(handleNotFound);
  1372.    
  1373.     webServer.begin();
  1374.     Serial.println("Web server started on port " + String(WEB_SERVER_PORT));
  1375. }
  1376.  
  1377. // =====================================================================
  1378. // FUNCTION: Display Relay Status (Console)
  1379. // =====================================================================
  1380. void displayRelayStatus()
  1381. {
  1382.     Serial.println("\n====== RELAY STATUS ======");
  1383.     for(uint8_t i = 0; i < NUM_RELAYS; i++)
  1384.     {
  1385.         Serial.print("Relay ");
  1386.         Serial.print(i);
  1387.         Serial.print(": ");
  1388.         Serial.println(relayStates[i] ? "ON" : "OFF");
  1389.     }
  1390.     Serial.println("=========================\n");
  1391. }
  1392.  
  1393. // =====================================================================
  1394. // FUNCTION: Periodic Modbus Health Check
  1395. // =====================================================================
  1396. void performModbusHealthCheck()
  1397. {
  1398.     const uint32_t HEALTH_CHECK_INTERVAL = 30000; // 30 seconds
  1399.    
  1400.     if(millis() - lastHealthCheck < HEALTH_CHECK_INTERVAL)
  1401.         return;
  1402.    
  1403.     lastHealthCheck = millis();
  1404.    
  1405.     Serial.println("Performing Modbus health check...");
  1406.    
  1407.     uint8_t coilValues[NUM_RELAYS];
  1408.     if(modbusReadCoils(0, NUM_RELAYS, coilValues))
  1409.     {
  1410.         Serial.println("Health check: Slave is responsive");
  1411.        
  1412.         // Update relay states based on read values
  1413.         for(uint8_t i = 0; i < NUM_RELAYS; i++)
  1414.         {
  1415.             relayStates[i] = coilValues[i];
  1416.         }
  1417.         modbusConnected = true;
  1418.     }
  1419.     else
  1420.     {
  1421.         Serial.println("Health check: Slave is not responding");
  1422.         modbusConnected = false;
  1423.     }
  1424. }
  1425.  
  1426. // =====================================================================
  1427. // FUNCTION: Initialize System
  1428. // =====================================================================
  1429. void setup()
  1430. {
  1431.     // Initialize debug serial
  1432.     Serial.begin(115200);
  1433.     delay(1000);
  1434.    
  1435.     Serial.println("\n\n========== ESP32-S3 MODBUS RTU MASTER WITH LVGL DISPLAY ==========");
  1436.     Serial.println("Production-Ready Firmware v1.0");
  1437.     Serial.println("Features:");
  1438.     Serial.println("  - Modbus RTU Master on UART1 (GPIO17/18)");
  1439.     Serial.println("  - ST7262 Driver 800x480 Touch LCD via I2C (GPIO8/9)");
  1440.     Serial.println("  - LVGL GUI with 8 Relay Control Buttons");
  1441.     Serial.println("  - WiFi Web Dashboard");
  1442.     Serial.println("  - REST API with JWT Authentication");
  1443.     Serial.println("  - OTA Firmware Update");
  1444.     Serial.println("  - Modbus Logging (1000 entries)");
  1445.     Serial.println("  - Error Handling & Recovery");
  1446.     Serial.println("====================================================================\n");
  1447.    
  1448.     // Initialize storage
  1449.     Serial.println("Initializing storage...");
  1450.     initializeStorage();
  1451.    
  1452.     // Initialize LCD display
  1453.     Serial.println("Initializing ST7262 display...");
  1454.     waveshare_lcd_init();
  1455.    
  1456.     // Initialize LVGL
  1457.     initializeLVGL();
  1458.    
  1459.     // Create GUI elements
  1460.     createRelayPanel();
  1461.     createStatusDisplay();
  1462.    
  1463.     // Initialize UART1 for Modbus RTU communication
  1464.     Serial.println("Initializing Modbus UART1...");
  1465.     initializeModbusUART();
  1466.    
  1467.     // Initialize WiFi
  1468.     Serial.println("Initializing WiFi...");
  1469.     initializeWiFi();
  1470.    
  1471.     // Setup web server
  1472.     Serial.println("Setting up web server...");
  1473.     setupWebServer();
  1474.    
  1475.     Serial.println("\nSystem initialized successfully!");
  1476.     Serial.println("Web Dashboard: http://" + WiFi.localIP().toString());
  1477.     Serial.println("Default Login: admin / admin123");
  1478.     Serial.println("====================================================================\n");
  1479.    
  1480.     // Display initial relay status
  1481.     displayRelayStatus();
  1482. }
  1483.  
  1484. // =====================================================================
  1485. // FUNCTION: Main Loop
  1486. // =====================================================================
  1487. void loop()
  1488. {
  1489.     // Handle HTTP requests
  1490.     webServer.handleClient();
  1491.    
  1492.     // Update LVGL display
  1493.     updateGUIDisplay();
  1494.    
  1495.     // Perform periodic health checks
  1496.     performModbusHealthCheck();
  1497.    
  1498.     // Update UI if interval exceeded
  1499.     if(millis() - lastUIRefresh > WEB_DASHBOARD_REFRESH_MS)
  1500.     {
  1501.         lastUIRefresh = millis();
  1502.     }
  1503.    
  1504.     // Small delay to prevent watchdog timeout
  1505.     delay(10);
  1506. }
  1507.  
  1508. /* END CODE */
  1509.  
Advertisement
Add Comment
Please, Sign In to add comment