pleasedontcode

# ESP32 Relay rev_01

Mar 7th, 2026
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 31.83 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: # ESP32 Relay
  13.     - Version: 001
  14.     - Source Code NOT compiled for: ESP32-S3-Box
  15.     - Source Code created on: 2026-03-08 01:30:22
  16.  
  17. ********* Pleasedontcode.com **********/
  18.  
  19. /****** SYSTEM REQUIREMENTS *****/
  20. /****** SYSTEM REQUIREMENT 1 *****/
  21.     /* Initialize 7-inch touch LCD display for ESP32-S3. */
  22.     /* Display 6 relay buttons (ON/OFF control) and 8 */
  23.     /* digital input status indicators. Create */
  24.     /* professional touch UI with real-time updates. */
  25. /****** SYSTEM REQUIREMENT 2 *****/
  26.     /* Initialize Modbus RTU communication on UART1 */
  27.     /* (TX=GPIO17, RX=GPIO18) with 9600 baud. Communicate */
  28.     /* with Slave ID 0x01 to control 6 relays and read 8 */
  29.     /* digital inputs. */
  30. /****** SYSTEM REQUIREMENT 3 *****/
  31.     /* When user touches relay button on display, send */
  32.     /* Modbus write command to toggle relay state. */
  33.     /* Immediately read relay status from slave and */
  34.     /* update display in real-time (decentralized */
  35.     /* control). */
  36. /****** SYSTEM REQUIREMENT 4 *****/
  37.     /* Continuously poll Modbus slave for 8 digital input */
  38.     /* states. Display DI status on LCD with visual */
  39.     /* indicators (green=ON, red=OFF). Update every */
  40.     /* 500ms. */
  41. /****** SYSTEM REQUIREMENT 5 *****/
  42.     /* WiFi STA mode: Connect to 'Cisco Wireless Access */
  43.     /* Point 2' with password '12345678'. Set static IP */
  44.     /* 192.168.1.200, Gateway 192.168.1.1, Subnet */
  45.     /* 255.255.255.0. Maintain connection but don't */
  46.     /* depend on it for relay control. */
  47. /****** SYSTEM REQUIREMENT 6 *****/
  48.     /* Create responsive web admin panel on port 80. Show */
  49.     /* real-time relay status, DI states, and Modbus */
  50.     /* statistics. Allow Slave ID configuration. Work */
  51.     /* even if web access fails (local control priority). */
  52. /****** END SYSTEM REQUIREMENTS *****/
  53.  
  54.  
  55. /****** DEFINITION OF LIBRARIES *****/
  56. #include <WiFi.h>
  57. #include <WebServer.h>
  58. #include <EEPROM.h>
  59. #include <HardwareSerial.h>
  60. #include <EasyButton.h>
  61. #include <lvgl.h>
  62. #include <Arduino_GFX_Library.h>
  63.  
  64. /****** MACRO DEFINITIONS *****/
  65. #define UART1_TX_PIN 17
  66. #define UART1_RX_PIN 18
  67. #define MODBUS_BAUD 9600
  68. #define SLAVE_ID 0x01
  69. #define NUM_RELAYS 6
  70. #define NUM_DI 8
  71. #define DI_POLL_INTERVAL 500  // milliseconds
  72. #define TOUCHSCREEN_WIDTH 800
  73. #define TOUCHSCREEN_HEIGHT 480
  74. #define MODBUS_TIMEOUT 1000  // milliseconds
  75. #define EEPROM_SLAVE_ID_ADDR 0
  76.  
  77. /****** WIFI CONFIGURATION *****/
  78. const char* SSID = "Cisco Wireless Access Point 2";
  79. const char* PASSWORD = "12345678";
  80. IPAddress STATIC_IP(192, 168, 1, 200);
  81. IPAddress GATEWAY(192, 168, 1, 1);
  82. IPAddress SUBNET(255, 255, 255, 0);
  83.  
  84. /****** RELAY AND DI STATES *****/
  85. uint8_t relayStates[NUM_RELAYS] = {0};
  86. uint8_t diStates[NUM_DI] = {0};
  87. uint8_t slaveID = SLAVE_ID;
  88.  
  89. /****** FUNCTION PROTOTYPES *****/
  90. void setup(void);
  91. void loop(void);
  92. void initializeDisplay(void);
  93. void initializeModbus(void);
  94. void initializeWiFi(void);
  95. void initializeWebServer(void);
  96. void handleDisplayTouch(void);
  97. void modbusWriteRelay(uint8_t relayIndex, uint8_t state);
  98. void modbusReadDigitalInputs(void);
  99. void updateDisplayRelayStatus(void);
  100. void updateDisplayDIStatus(void);
  101. void handleWebServerClients(void);
  102. void handleWebRoot(void);
  103. void handleWebAPI(void);
  104. void handleWebConfig(void);
  105. String generateWebHTML(void);
  106. void pollModbusSlave(void);
  107. void initEEPROM(void);
  108. void saveSlaveIDtoEEPROM(uint8_t id);
  109. uint8_t loadSlaveIDfromEEPROM(void);
  110.  
  111. /****** GLOBAL VARIABLES *****/
  112. HardwareSerial ModbusSerial(1);
  113. WebServer webServer(80);
  114. unsigned long lastDIPollTime = 0;
  115. unsigned long lastWebUpdateTime = 0;
  116. bool modbusConnected = false;
  117. uint32_t modbusReadCount = 0;
  118. uint32_t modbusWriteCount = 0;
  119. uint32_t modbusErrorCount = 0;
  120.  
  121. /****** DISPLAY SETUP *****/
  122. Arduino_GFX *gfx = NULL;
  123. uint16_t screenWidth = TOUCHSCREEN_WIDTH;
  124. uint16_t screenHeight = TOUCHSCREEN_HEIGHT;
  125.  
  126. /****** LVGL DISPLAY BUFFER *****/
  127. static lv_disp_draw_buf_t draw_buf;
  128. static lv_disp_drv_t disp_drv;
  129. static lv_indev_drv_t indev_drv;
  130. static uint32_t screenWidth_lvgl = TOUCHSCREEN_WIDTH;
  131. static uint32_t screenHeight_lvgl = TOUCHSCREEN_HEIGHT;
  132. static lv_obj_t *relayButtons[NUM_RELAYS];
  133. static lv_obj_t *diIndicators[NUM_DI];
  134. static lv_obj_t *statusLabels[NUM_RELAYS];
  135. static lv_obj_t *diLabels[NUM_DI];
  136.  
  137. /****** DISPLAY CALLBACK FUNCTION *****/
  138. void lv_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
  139. {
  140.     uint32_t w = (area->x2 - area->x1 + 1);
  141.     uint32_t h = (area->y2 - area->y1 + 1);
  142.     gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
  143.     lv_disp_flush_ready(disp);
  144. }
  145.  
  146. /****** TOUCHSCREEN INPUT CALLBACK *****/
  147. void lv_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
  148. {
  149.     uint16_t touchX = 0, touchY = 0;
  150.     bool touched = false;
  151.    
  152.     // Placeholder for actual touchscreen read function
  153.     // This would interface with ESP32-S3-Box touchscreen driver
  154.     // For now, mark as not touched to prevent errors
  155.     data->state = touched ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
  156.     data->point.x = touchX;
  157.     data->point.y = touchY;
  158. }
  159.  
  160. /****** RELAY BUTTON CALLBACK *****/
  161. void relayButtonCallback(lv_event_t *e)
  162. {
  163.     lv_obj_t *btn = lv_event_get_target(e);
  164.     uint8_t relayIndex = 0;
  165.    
  166.     for (uint8_t i = 0; i < NUM_RELAYS; i++) {
  167.         if (relayButtons[i] == btn) {
  168.             relayIndex = i;
  169.             break;
  170.         }
  171.     }
  172.    
  173.     uint8_t newState = (relayStates[relayIndex] == 0) ? 1 : 0;
  174.     modbusWriteRelay(relayIndex, newState);
  175. }
  176.  
  177. /****** INITIALIZE EEPROM *****/
  178. void initEEPROM(void)
  179. {
  180.     EEPROM.begin(512);
  181.     slaveID = loadSlaveIDfromEEPROM();
  182.     if (slaveID == 0 || slaveID > 247) {
  183.         slaveID = SLAVE_ID;
  184.         saveSlaveIDtoEEPROM(slaveID);
  185.     }
  186. }
  187.  
  188. /****** SAVE SLAVE ID TO EEPROM *****/
  189. void saveSlaveIDtoEEPROM(uint8_t id)
  190. {
  191.     EEPROM.write(EEPROM_SLAVE_ID_ADDR, id);
  192.     EEPROM.commit();
  193. }
  194.  
  195. /****** LOAD SLAVE ID FROM EEPROM *****/
  196. uint8_t loadSlaveIDfromEEPROM(void)
  197. {
  198.     return EEPROM.read(EEPROM_SLAVE_ID_ADDR);
  199. }
  200.  
  201. /****** INITIALIZE DISPLAY *****/
  202. void initializeDisplay(void)
  203. {
  204.     // Initialize GFX library for ESP32-S3-Box display
  205.     gfx = new Arduino_RGB_Display(
  206.         screenWidth,
  207.         screenHeight,
  208.         GFX_NOT_DEFINED,  // RGB mode
  209.         true,
  210.         NULL,
  211.         2,                 // DMA memory block size
  212.         false,
  213.         16                 // bit width
  214.     );
  215.    
  216.     if (!gfx->begin()) {
  217.         Serial.println("ERROR: Display initialization failed");
  218.         return;
  219.     }
  220.    
  221.     gfx->fillScreen(BLACK);
  222.    
  223.     // Initialize LVGL
  224.     lv_init();
  225.    
  226.     // Initialize display buffer
  227.     static lv_color_t buf1[screenWidth * 10];
  228.     static lv_color_t buf2[screenWidth * 10];
  229.     lv_disp_draw_buf_init(&draw_buf, buf1, buf2, screenWidth * 10);
  230.    
  231.     // Initialize display driver
  232.     lv_disp_drv_init(&disp_drv);
  233.     disp_drv.hor_res = screenWidth;
  234.     disp_drv.ver_res = screenHeight;
  235.     disp_drv.flush_cb = lv_disp_flush;
  236.     disp_drv.draw_buf = &draw_buf;
  237.     lv_disp_drv_register(&disp_drv);
  238.    
  239.     // Initialize touchscreen input
  240.     lv_indev_drv_init(&indev_drv);
  241.     indev_drv.type = LV_INDEV_TYPE_POINTER;
  242.     indev_drv.read_cb = lv_touchpad_read;
  243.     lv_indev_drv_register(&indev_drv);
  244.    
  245.     // Create main screen
  246.     lv_obj_t *scr = lv_scr_act();
  247.     lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a1a), 0);
  248.    
  249.     // Title label
  250.     lv_obj_t *titleLabel = lv_label_create(scr);
  251.     lv_label_set_text(titleLabel, "ESP32-S3 Relay Control");
  252.     lv_obj_set_pos(titleLabel, 20, 10);
  253.     lv_obj_set_style_text_color(titleLabel, lv_color_hex(0xFFFFFF), 0);
  254.     lv_obj_set_style_text_font(titleLabel, &lv_font_montserrat_24, 0);
  255.    
  256.     // Create relay buttons
  257.     int yPos = 50;
  258.     for (uint8_t i = 0; i < NUM_RELAYS; i++) {
  259.         relayButtons[i] = lv_btn_create(scr);
  260.         lv_obj_set_pos(relayButtons[i], 20, yPos);
  261.         lv_obj_set_size(relayButtons[i], 200, 50);
  262.         lv_obj_set_style_bg_color(relayButtons[i], lv_color_hex(0x505050), 0);
  263.         lv_obj_set_style_border_color(relayButtons[i], lv_color_hex(0xFFFFFF), 0);
  264.         lv_obj_set_style_border_width(relayButtons[i], 2, 0);
  265.         lv_obj_add_event_cb(relayButtons[i], relayButtonCallback, LV_EVENT_CLICKED, NULL);
  266.        
  267.         lv_obj_t *btnLabel = lv_label_create(relayButtons[i]);
  268.         lv_label_set_text(btnLabel, "Relay OFF");
  269.         lv_obj_center(btnLabel);
  270.         lv_obj_set_style_text_color(btnLabel, lv_color_hex(0xFFFFFF), 0);
  271.         statusLabels[i] = btnLabel;
  272.        
  273.         // Relay status text
  274.         char relayText[20];
  275.         snprintf(relayText, sizeof(relayText), "Relay %d", i + 1);
  276.         lv_obj_t *relayTextLabel = lv_label_create(scr);
  277.         lv_label_set_text(relayTextLabel, relayText);
  278.         lv_obj_set_pos(relayTextLabel, 240, yPos + 15);
  279.         lv_obj_set_style_text_color(relayTextLabel, lv_color_hex(0xFFFFFF), 0);
  280.        
  281.         yPos += 65;
  282.     }
  283.    
  284.     // Create DI status indicators
  285.     int diXPos = 500;
  286.     int diYPos = 50;
  287.     int diCol = 0;
  288.    
  289.     lv_obj_t *diTitleLabel = lv_label_create(scr);
  290.     lv_label_set_text(diTitleLabel, "Digital Inputs");
  291.     lv_obj_set_pos(diTitleLabel, diXPos, 10);
  292.     lv_obj_set_style_text_color(diTitleLabel, lv_color_hex(0xFFFFFF), 0);
  293.     lv_obj_set_style_text_font(diTitleLabel, &lv_font_montserrat_24, 0);
  294.    
  295.     for (uint8_t i = 0; i < NUM_DI; i++) {
  296.         diIndicators[i] = lv_obj_create(scr);
  297.         lv_obj_set_pos(diIndicators[i], diXPos + (diCol * 70), diYPos);
  298.         lv_obj_set_size(diIndicators[i], 60, 60);
  299.         lv_obj_set_style_bg_color(diIndicators[i], lv_color_hex(0xFF0000), 0);
  300.         lv_obj_set_style_border_color(diIndicators[i], lv_color_hex(0xFFFFFF), 0);
  301.         lv_obj_set_style_border_width(diIndicators[i], 2, 0);
  302.         lv_obj_set_style_radius(diIndicators[i], 5, 0);
  303.        
  304.         diLabels[i] = lv_label_create(diIndicators[i]);
  305.         char diText[5];
  306.         snprintf(diText, sizeof(diText), "DI%d", i + 1);
  307.         lv_label_set_text(diLabels[i], diText);
  308.         lv_obj_center(diLabels[i]);
  309.         lv_obj_set_style_text_color(diLabels[i], lv_color_hex(0xFFFFFF), 0);
  310.         lv_obj_set_style_text_font(diLabels[i], &lv_font_montserrat_16, 0);
  311.        
  312.         diCol++;
  313.         if (diCol >= 4) {
  314.             diCol = 0;
  315.             diYPos += 90;
  316.         }
  317.     }
  318. }
  319.  
  320. /****** INITIALIZE MODBUS *****/
  321. void initializeModbus(void)
  322. {
  323.     ModbusSerial.begin(MODBUS_BAUD, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);
  324.     Serial.println("Modbus RTU initialized on UART1 (TX=GPIO17, RX=GPIO18) at 9600 baud");
  325.     modbusConnected = true;
  326. }
  327.  
  328. /****** MODBUS WRITE RELAY *****/
  329. void modbusWriteRelay(uint8_t relayIndex, uint8_t state)
  330. {
  331.     if (!modbusConnected) {
  332.         Serial.println("ERROR: Modbus not connected");
  333.         return;
  334.     }
  335.    
  336.     // Modbus function code 05 (Write Single Coil)
  337.     uint8_t txBuffer[8];
  338.     uint8_t txLen = 0;
  339.    
  340.     txBuffer[txLen++] = slaveID;
  341.     txBuffer[txLen++] = 0x05;  // Function code 05
  342.     txBuffer[txLen++] = 0x00;  // Coil address high byte
  343.     txBuffer[txLen++] = relayIndex;  // Coil address low byte
  344.     txBuffer[txLen++] = (state == 0) ? 0x00 : 0xFF;  // Coil value high byte
  345.     txBuffer[txLen++] = 0x00;  // Coil value low byte
  346.    
  347.     // Calculate CRC16
  348.     uint16_t crc = calculateModbusCRC(txBuffer, 6);
  349.     txBuffer[txLen++] = crc & 0xFF;  // CRC low byte
  350.     txBuffer[txLen++] = (crc >> 8) & 0xFF;  // CRC high byte
  351.    
  352.     ModbusSerial.write(txBuffer, txLen);
  353.     ModbusSerial.flush();
  354.    
  355.     modbusWriteCount++;
  356.    
  357.     // Read response
  358.     unsigned long startTime = millis();
  359.     uint8_t rxBuffer[8];
  360.     int rxLen = 0;
  361.    
  362.     while (millis() - startTime < MODBUS_TIMEOUT) {
  363.         if (ModbusSerial.available()) {
  364.             rxBuffer[rxLen++] = ModbusSerial.read();
  365.             if (rxLen >= 8) break;
  366.         }
  367.     }
  368.    
  369.     if (rxLen >= 8) {
  370.         if (rxBuffer[0] == slaveID && rxBuffer[1] == 0x05) {
  371.             relayStates[relayIndex] = state;
  372.             Serial.printf("Relay %d set to %s\n", relayIndex, state ? "ON" : "OFF");
  373.             updateDisplayRelayStatus();
  374.         } else {
  375.             modbusErrorCount++;
  376.             Serial.println("ERROR: Invalid Modbus response");
  377.         }
  378.     } else {
  379.         modbusErrorCount++;
  380.         Serial.println("ERROR: Modbus timeout on write");
  381.     }
  382. }
  383.  
  384. /****** MODBUS READ DIGITAL INPUTS *****/
  385. void modbusReadDigitalInputs(void)
  386. {
  387.     if (!modbusConnected) {
  388.         return;
  389.     }
  390.    
  391.     // Modbus function code 02 (Read Discrete Inputs)
  392.     uint8_t txBuffer[8];
  393.     uint8_t txLen = 0;
  394.    
  395.     txBuffer[txLen++] = slaveID;
  396.     txBuffer[txLen++] = 0x02;  // Function code 02
  397.     txBuffer[txLen++] = 0x00;  // Starting address high byte
  398.     txBuffer[txLen++] = 0x00;  // Starting address low byte
  399.     txBuffer[txLen++] = 0x00;  // Quantity high byte
  400.     txBuffer[txLen++] = NUM_DI;  // Quantity low byte
  401.    
  402.     // Calculate CRC16
  403.     uint16_t crc = calculateModbusCRC(txBuffer, 6);
  404.     txBuffer[txLen++] = crc & 0xFF;  // CRC low byte
  405.     txBuffer[txLen++] = (crc >> 8) & 0xFF;  // CRC high byte
  406.    
  407.     ModbusSerial.write(txBuffer, txLen);
  408.     ModbusSerial.flush();
  409.    
  410.     modbusReadCount++;
  411.    
  412.     // Read response
  413.     unsigned long startTime = millis();
  414.     uint8_t rxBuffer[256];
  415.     int rxLen = 0;
  416.    
  417.     while (millis() - startTime < MODBUS_TIMEOUT) {
  418.         if (ModbusSerial.available()) {
  419.             rxBuffer[rxLen++] = ModbusSerial.read();
  420.             if (rxLen >= 5 && rxLen >= (rxBuffer[2] + 5)) break;
  421.         }
  422.     }
  423.    
  424.     if (rxLen >= 5) {
  425.         if (rxBuffer[0] == slaveID && rxBuffer[1] == 0x02) {
  426.             uint8_t byteCount = rxBuffer[2];
  427.             for (uint8_t i = 0; i < NUM_DI && i < (byteCount * 8); i++) {
  428.                 uint8_t byteIndex = 3 + (i / 8);
  429.                 uint8_t bitIndex = i % 8;
  430.                 diStates[i] = (rxBuffer[byteIndex] >> bitIndex) & 0x01;
  431.             }
  432.             updateDisplayDIStatus();
  433.         } else {
  434.             modbusErrorCount++;
  435.         }
  436.     } else {
  437.         modbusErrorCount++;
  438.     }
  439. }
  440.  
  441. /****** CALCULATE MODBUS CRC16 *****/
  442. uint16_t calculateModbusCRC(uint8_t *data, uint8_t length)
  443. {
  444.     uint16_t crc = 0xFFFF;
  445.     for (uint8_t i = 0; i < length; i++) {
  446.         crc ^= data[i];
  447.         for (uint8_t j = 0; j < 8; j++) {
  448.             if (crc & 0x0001) {
  449.                 crc = (crc >> 1) ^ 0xA001;
  450.             } else {
  451.                 crc = crc >> 1;
  452.             }
  453.         }
  454.     }
  455.     return crc;
  456. }
  457.  
  458. /****** UPDATE DISPLAY RELAY STATUS *****/
  459. void updateDisplayRelayStatus(void)
  460. {
  461.     for (uint8_t i = 0; i < NUM_RELAYS; i++) {
  462.         if (relayStates[i] == 0) {
  463.             lv_label_set_text(statusLabels[i], "Relay OFF");
  464.             lv_obj_set_style_bg_color(relayButtons[i], lv_color_hex(0x505050), 0);
  465.         } else {
  466.             lv_label_set_text(statusLabels[i], "Relay ON");
  467.             lv_obj_set_style_bg_color(relayButtons[i], lv_color_hex(0x00AA00), 0);
  468.         }
  469.     }
  470. }
  471.  
  472. /****** UPDATE DISPLAY DI STATUS *****/
  473. void updateDisplayDIStatus(void)
  474. {
  475.     for (uint8_t i = 0; i < NUM_DI; i++) {
  476.         if (diStates[i] == 0) {
  477.             lv_obj_set_style_bg_color(diIndicators[i], lv_color_hex(0xFF0000), 0);
  478.         } else {
  479.             lv_obj_set_style_bg_color(diIndicators[i], lv_color_hex(0x00FF00), 0);
  480.         }
  481.     }
  482. }
  483.  
  484. /****** INITIALIZE WIFI *****/
  485. void initializeWiFi(void)
  486. {
  487.     WiFi.mode(WIFI_STA);
  488.     WiFi.config(STATIC_IP, GATEWAY, SUBNET);
  489.     WiFi.begin(SSID, PASSWORD);
  490.    
  491.     int attempts = 0;
  492.     while (WiFi.status() != WL_CONNECTED && attempts < 20) {
  493.         delay(500);
  494.         Serial.print(".");
  495.         attempts++;
  496.     }
  497.    
  498.     if (WiFi.status() == WL_CONNECTED) {
  499.         Serial.println("\nWiFi connected");
  500.         Serial.printf("IP address: %s\n", WiFi.localIP().toString().c_str());
  501.     } else {
  502.         Serial.println("\nWiFi connection failed - local control will continue");
  503.     }
  504. }
  505.  
  506. /****** INITIALIZE WEB SERVER *****/
  507. void initializeWebServer(void)
  508. {
  509.     webServer.on("/", HTTP_GET, handleWebRoot);
  510.     webServer.on("/api/relays", HTTP_GET, handleWebAPI);
  511.     webServer.on("/api/relays", HTTP_POST, handleWebAPI);
  512.     webServer.on("/api/config", HTTP_POST, handleWebConfig);
  513.     webServer.begin();
  514.     Serial.println("Web server started on port 80");
  515. }
  516.  
  517. /****** HANDLE WEB ROOT *****/
  518. void handleWebRoot(void)
  519. {
  520.     webServer.send(200, "text/html", generateWebHTML());
  521. }
  522.  
  523. /****** GENERATE WEB HTML *****/
  524. String generateWebHTML(void)
  525. {
  526.     String html = R"(
  527. <!DOCTYPE html>
  528. <html>
  529. <head>
  530.    <title>ESP32-S3 Relay Control Panel</title>
  531.    <meta name="viewport" content="width=device-width, initial-scale=1">
  532.    <style>
  533.        * {
  534.            margin: 0;
  535.            padding: 0;
  536.            box-sizing: border-box;
  537.        }
  538.        body {
  539.            font-family: Arial, sans-serif;
  540.            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  541.            min-height: 100vh;
  542.            padding: 20px;
  543.        }
  544.        .container {
  545.            max-width: 1000px;
  546.            margin: 0 auto;
  547.            background: white;
  548.            border-radius: 10px;
  549.            box-shadow: 0 8px 32px rgba(0,0,0,0.1);
  550.            padding: 30px;
  551.        }
  552.        h1 {
  553.            color: #333;
  554.            text-align: center;
  555.            margin-bottom: 30px;
  556.            font-size: 28px;
  557.        }
  558.        .status-grid {
  559.            display: grid;
  560.            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  561.            gap: 20px;
  562.            margin-bottom: 30px;
  563.        }
  564.        .relay-card {
  565.            background: #f5f5f5;
  566.            padding: 20px;
  567.            border-radius: 8px;
  568.            text-align: center;
  569.            border: 2px solid #ddd;
  570.            transition: all 0.3s ease;
  571.        }
  572.        .relay-card:hover {
  573.            border-color: #667eea;
  574.            transform: translateY(-2px);
  575.            box-shadow: 0 4px 12px rgba(102,126,234,0.2);
  576.        }
  577.        .relay-name {
  578.            font-weight: bold;
  579.            color: #333;
  580.            margin-bottom: 10px;
  581.            font-size: 16px;
  582.        }
  583.        .relay-status {
  584.            font-size: 18px;
  585.            font-weight: bold;
  586.            margin-bottom: 10px;
  587.            padding: 10px;
  588.            border-radius: 5px;
  589.            color: white;
  590.        }
  591.        .relay-status.on {
  592.            background: #4CAF50;
  593.        }
  594.        .relay-status.off {
  595.            background: #f44336;
  596.        }
  597.        .relay-btn {
  598.            width: 100%;
  599.            padding: 10px;
  600.            border: none;
  601.            border-radius: 5px;
  602.            background: #667eea;
  603.            color: white;
  604.            cursor: pointer;
  605.            font-size: 14px;
  606.            font-weight: bold;
  607.            transition: background 0.3s ease;
  608.        }
  609.        .relay-btn:hover {
  610.            background: #764ba2;
  611.        }
  612.        .relay-btn:active {
  613.            transform: scale(0.98);
  614.        }
  615.        .di-grid {
  616.            display: grid;
  617.            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  618.            gap: 15px;
  619.            margin-top: 30px;
  620.            padding-top: 30px;
  621.            border-top: 2px solid #ddd;
  622.        }
  623.        .di-indicator {
  624.            background: #f5f5f5;
  625.            padding: 15px;
  626.            border-radius: 8px;
  627.            text-align: center;
  628.            border: 2px solid #ddd;
  629.        }
  630.        .di-label {
  631.            font-weight: bold;
  632.            color: #333;
  633.            margin-bottom: 10px;
  634.        }
  635.        .di-status {
  636.            width: 80px;
  637.            height: 80px;
  638.            margin: 0 auto;
  639.            border-radius: 50%;
  640.            display: flex;
  641.            align-items: center;
  642.            justify-content: center;
  643.            font-weight: bold;
  644.            color: white;
  645.            font-size: 14px;
  646.        }
  647.        .di-status.on {
  648.            background: #4CAF50;
  649.        }
  650.        .di-status.off {
  651.            background: #f44336;
  652.        }
  653.        .stats {
  654.            display: grid;
  655.            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  656.            gap: 15px;
  657.            margin-top: 30px;
  658.            padding: 20px;
  659.            background: #f5f5f5;
  660.            border-radius: 8px;
  661.        }
  662.        .stat-item {
  663.            text-align: center;
  664.        }
  665.        .stat-label {
  666.            font-weight: bold;
  667.            color: #666;
  668.            font-size: 12px;
  669.            text-transform: uppercase;
  670.        }
  671.        .stat-value {
  672.            font-size: 24px;
  673.            color: #333;
  674.            font-weight: bold;
  675.        }
  676.        .config-section {
  677.            margin-top: 30px;
  678.            padding: 20px;
  679.            background: #f5f5f5;
  680.            border-radius: 8px;
  681.        }
  682.        .config-section h2 {
  683.            color: #333;
  684.            margin-bottom: 15px;
  685.            font-size: 18px;
  686.        }
  687.        .form-group {
  688.            margin-bottom: 15px;
  689.        }
  690.        .form-group label {
  691.            display: block;
  692.            color: #666;
  693.            margin-bottom: 5px;
  694.            font-weight: bold;
  695.        }
  696.        .form-group input {
  697.            width: 100%;
  698.            padding: 10px;
  699.            border: 1px solid #ddd;
  700.            border-radius: 5px;
  701.            font-size: 14px;
  702.        }
  703.        .form-group button {
  704.            width: 100%;
  705.            padding: 10px;
  706.            background: #667eea;
  707.            color: white;
  708.            border: none;
  709.            border-radius: 5px;
  710.            cursor: pointer;
  711.            font-weight: bold;
  712.            transition: background 0.3s ease;
  713.        }
  714.        .form-group button:hover {
  715.            background: #764ba2;
  716.        }
  717.    </style>
  718. </head>
  719. <body>
  720.    <div class="container">
  721.        <h1>ESP32-S3 Relay Control Panel</h1>
  722.        
  723.        <div class="status-grid" id="relayContainer">
  724.            <!-- Relay cards will be inserted here by JavaScript -->
  725.        </div>
  726.        
  727.        <div class="di-grid" id="diContainer">
  728.            <!-- DI indicators will be inserted here by JavaScript -->
  729.        </div>
  730.        
  731.        <div class="stats">
  732.            <div class="stat-item">
  733.                <div class="stat-label">Modbus Reads</div>
  734.                <div class="stat-value" id="readCount">0</div>
  735.            </div>
  736.            <div class="stat-item">
  737.                <div class="stat-label">Modbus Writes</div>
  738.                <div class="stat-value" id="writeCount">0</div>
  739.            </div>
  740.            <div class="stat-item">
  741.                <div class="stat-label">Modbus Errors</div>
  742.                <div class="stat-value" id="errorCount">0</div>
  743.            </div>
  744.            <div class="stat-item">
  745.                <div class="stat-label">WiFi Status</div>
  746.                <div class="stat-value" id="wifiStatus">-</div>
  747.            </div>
  748.        </div>
  749.        
  750.        <div class="config-section">
  751.            <h2>Configuration</h2>
  752.            <div class="form-group">
  753.                <label for="slaveId">Modbus Slave ID (1-247):</label>
  754.                <input type="number" id="slaveId" min="1" max="247" placeholder="Enter Slave ID">
  755.            </div>
  756.            <div class="form-group">
  757.                <button onclick="updateSlaveId()">Update Slave ID</button>
  758.            </div>
  759.        </div>
  760.    </div>
  761.  
  762.    <script>
  763.        const UPDATE_INTERVAL = 1000;  // Update every 1 second
  764.        
  765.        function initializeUI() {
  766.            // Create relay cards
  767.            let relayHTML = '';
  768.            for (let i = 0; i < 6; i++) {
  769.                relayHTML += `
  770.                    <div class="relay-card">
  771.                        <div class="relay-name">Relay ${i + 1}</div>
  772.                        <div class="relay-status" id="relay-status-${i}">OFF</div>
  773.                        <button class="relay-btn" onclick="toggleRelay(${i})">Toggle</button>
  774.                    </div>
  775.                `;
  776.            }
  777.            document.getElementById('relayContainer').innerHTML = relayHTML;
  778.            
  779.            // Create DI indicators
  780.            let diHTML = '';
  781.            for (let i = 0; i < 8; i++) {
  782.                diHTML += `
  783.                    <div class="di-indicator">
  784.                        <div class="di-label">DI${i + 1}</div>
  785.                        <div class="di-status" id="di-status-${i}">OFF</div>
  786.                    </div>
  787.                `;
  788.            }
  789.            document.getElementById('diContainer').innerHTML = diHTML;
  790.            
  791.            updateStatus();
  792.            setInterval(updateStatus, UPDATE_INTERVAL);
  793.        }
  794.        
  795.        function updateStatus() {
  796.            fetch('/api/relays')
  797.                .then(response => response.json())
  798.                .then(data => {
  799.                    // Update relay status
  800.                    for (let i = 0; i < 6; i++) {
  801.                        const statusElem = document.getElementById(`relay-status-${i}`);
  802.                        statusElem.textContent = data.relays[i] ? 'ON' : 'OFF';
  803.                        statusElem.className = data.relays[i] ? 'relay-status on' : 'relay-status off';
  804.                    }
  805.                    
  806.                    // Update DI status
  807.                    for (let i = 0; i < 8; i++) {
  808.                        const statusElem = document.getElementById(`di-status-${i}`);
  809.                        statusElem.textContent = data.inputs[i] ? 'ON' : 'OFF';
  810.                        statusElem.className = data.inputs[i] ? 'di-status on' : 'di-status off';
  811.                    }
  812.                    
  813.                    // Update statistics
  814.                    document.getElementById('readCount').textContent = data.stats.reads;
  815.                    document.getElementById('writeCount').textContent = data.stats.writes;
  816.                    document.getElementById('errorCount').textContent = data.stats.errors;
  817.                    document.getElementById('wifiStatus').textContent = data.stats.wifi ? 'OK' : 'N/A';
  818.                })
  819.                .catch(err => console.error('Error fetching status:', err));
  820.        }
  821.        
  822.        function toggleRelay(index) {
  823.            fetch('/api/relays', {
  824.                method: 'POST',
  825.                headers: {
  826.                    'Content-Type': 'application/json'
  827.                },
  828.                body: JSON.stringify({
  829.                    relay: index,
  830.                    state: 1  // Toggle will be handled by server
  831.                })
  832.            })
  833.            .then(response => response.json())
  834.            .then(data => {
  835.                updateStatus();
  836.            })
  837.            .catch(err => console.error('Error toggling relay:', err));
  838.        }
  839.        
  840.        function updateSlaveId() {
  841.            const slaveId = document.getElementById('slaveId').value;
  842.            if (slaveId < 1 || slaveId > 247) {
  843.                alert('Please enter a valid Slave ID (1-247)');
  844.                return;
  845.            }
  846.            
  847.            fetch('/api/config', {
  848.                method: 'POST',
  849.                headers: {
  850.                    'Content-Type': 'application/json'
  851.                },
  852.                body: JSON.stringify({
  853.                    slaveId: parseInt(slaveId)
  854.                })
  855.            })
  856.            .then(response => response.json())
  857.            .then(data => {
  858.                if (data.success) {
  859.                    alert('Slave ID updated successfully');
  860.                    updateStatus();
  861.                } else {
  862.                    alert('Error updating Slave ID');
  863.                }
  864.            })
  865.            .catch(err => console.error('Error updating config:', err));
  866.        }
  867.        
  868.        window.addEventListener('load', initializeUI);
  869.    </script>
  870. </body>
  871. </html>
  872.    )";
  873.    
  874.     return html;
  875. }
  876.  
  877. /****** HANDLE WEB API *****/
  878. void handleWebAPI(void)
  879. {
  880.     if (webServer.method() == HTTP_GET) {
  881.         // Return current status
  882.         String json = "{";
  883.         json += "\"relays\":[";
  884.         for (uint8_t i = 0; i < NUM_RELAYS; i++) {
  885.             json += (relayStates[i] ? "1" : "0");
  886.             if (i < NUM_RELAYS - 1) json += ",";
  887.         }
  888.         json += "],";
  889.         json += "\"inputs\":[";
  890.         for (uint8_t i = 0; i < NUM_DI; i++) {
  891.             json += (diStates[i] ? "1" : "0");
  892.             if (i < NUM_DI - 1) json += ",";
  893.         }
  894.         json += "],";
  895.         json += "\"stats\":{";
  896.         json += "\"reads\":" + String(modbusReadCount) + ",";
  897.         json += "\"writes\":" + String(modbusWriteCount) + ",";
  898.         json += "\"errors\":" + String(modbusErrorCount) + ",";
  899.         json += "\"wifi\":" + String(WiFi.isConnected() ? 1 : 0);
  900.         json += "}";
  901.         json += "}";
  902.        
  903.         webServer.send(200, "application/json", json);
  904.     } else if (webServer.method() == HTTP_POST) {
  905.         // Toggle relay
  906.         if (webServer.hasArg("plain")) {
  907.             String body = webServer.arg("plain");
  908.            
  909.             // Simple JSON parsing
  910.             int relayIndex = -1;
  911.             sscanf(body.c_str(), "{\"relay\":%d", &relayIndex);
  912.            
  913.             if (relayIndex >= 0 && relayIndex < NUM_RELAYS) {
  914.                 uint8_t newState = (relayStates[relayIndex] == 0) ? 1 : 0;
  915.                 modbusWriteRelay(relayIndex, newState);
  916.                
  917.                 String json = "{\"success\":true,\"relay\":" + String(relayIndex) + "}";
  918.                 webServer.send(200, "application/json", json);
  919.             } else {
  920.                 webServer.send(400, "application/json", "{\"success\":false}");
  921.             }
  922.         } else {
  923.             webServer.send(400, "application/json", "{\"success\":false}");
  924.         }
  925.     }
  926. }
  927.  
  928. /****** HANDLE WEB CONFIG *****/
  929. void handleWebConfig(void)
  930. {
  931.     if (webServer.hasArg("plain")) {
  932.         String body = webServer.arg("plain");
  933.        
  934.         // Simple JSON parsing
  935.         int newSlaveID = -1;
  936.         sscanf(body.c_str(), "{\"slaveId\":%d", &newSlaveID);
  937.        
  938.         if (newSlaveID > 0 && newSlaveID < 248) {
  939.             slaveID = newSlaveID;
  940.             saveSlaveIDtoEEPROM(slaveID);
  941.            
  942.             String json = "{\"success\":true,\"slaveId\":" + String(slaveID) + "}";
  943.             webServer.send(200, "application/json", json);
  944.         } else {
  945.             webServer.send(400, "application/json", "{\"success\":false,\"error\":\"Invalid Slave ID\"}");
  946.         }
  947.     } else {
  948.         webServer.send(400, "application/json", "{\"success\":false}");
  949.     }
  950. }
  951.  
  952. /****** POLL MODBUS SLAVE *****/
  953. void pollModbusSlave(void)
  954. {
  955.     unsigned long currentTime = millis();
  956.     if (currentTime - lastDIPollTime >= DI_POLL_INTERVAL) {
  957.         modbusReadDigitalInputs();
  958.         lastDIPollTime = currentTime;
  959.     }
  960. }
  961.  
  962. /****** SETUP *****/
  963. void setup(void)
  964. {
  965.     Serial.begin(115200);
  966.     delay(1000);
  967.    
  968.     Serial.println("\n\n");
  969.     Serial.println("========================================");
  970.     Serial.println("ESP32-S3 Relay Control System Starting");
  971.     Serial.println("========================================");
  972.    
  973.     // Initialize EEPROM
  974.     initEEPROM();
  975.     Serial.printf("Loaded Slave ID from EEPROM: %d\n", slaveID);
  976.    
  977.     // Initialize display (LVGL + GFX)
  978.     Serial.println("Initializing display...");
  979.     initializeDisplay();
  980.     Serial.println("Display initialized");
  981.    
  982.     // Initialize Modbus RTU
  983.     Serial.println("Initializing Modbus RTU...");
  984.     initializeModbus();
  985.    
  986.     // Initialize WiFi
  987.     Serial.println("Initializing WiFi...");
  988.     initializeWiFi();
  989.    
  990.     // Initialize Web Server
  991.     Serial.println("Initializing Web Server...");
  992.     initializeWebServer();
  993.    
  994.     Serial.println("========================================");
  995.     Serial.println("System Ready");
  996.     Serial.println("========================================");
  997. }
  998.  
  999. /****** MAIN LOOP *****/
  1000. void loop(void)
  1001. {
  1002.     // Handle web server clients
  1003.     webServer.handleClient();
  1004.    
  1005.     // Poll Modbus slave for digital inputs
  1006.     pollModbusSlave();
  1007.    
  1008.     // Update LVGL display
  1009.     lv_timer_handler();
  1010.    
  1011.     // Maintain WiFi connection
  1012.     if (WiFi.status() != WL_CONNECTED) {
  1013.         static unsigned long lastWiFiAttempt = 0;
  1014.         if (millis() - lastWiFiAttempt > 30000) {
  1015.             Serial.println("WiFi disconnected, attempting reconnection...");
  1016.             WiFi.reconnect();
  1017.             lastWiFiAttempt = millis();
  1018.         }
  1019.     }
  1020.    
  1021.     delay(10);
  1022. }
  1023.  
  1024. /* END CODE */
  1025.  
Advertisement
Add Comment
Please, Sign In to add comment