Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /********* Pleasedontcode.com **********
- Pleasedontcode thanks you for automatic code generation! Enjoy your code!
- - Terms and Conditions:
- You have a non-exclusive, revocable, worldwide, royalty-free license
- for personal and commercial use. Attribution is optional; modifications
- are allowed, but you're responsible for code maintenance. We're not
- liable for any loss or damage. For full terms,
- please visit pleasedontcode.com/termsandconditions.
- - Project: # ESP32 Relay
- - Version: 001
- - Source Code NOT compiled for: ESP32-S3-Box
- - Source Code created on: 2026-03-08 01:30:22
- ********* Pleasedontcode.com **********/
- /****** SYSTEM REQUIREMENTS *****/
- /****** SYSTEM REQUIREMENT 1 *****/
- /* Initialize 7-inch touch LCD display for ESP32-S3. */
- /* Display 6 relay buttons (ON/OFF control) and 8 */
- /* digital input status indicators. Create */
- /* professional touch UI with real-time updates. */
- /****** SYSTEM REQUIREMENT 2 *****/
- /* Initialize Modbus RTU communication on UART1 */
- /* (TX=GPIO17, RX=GPIO18) with 9600 baud. Communicate */
- /* with Slave ID 0x01 to control 6 relays and read 8 */
- /* digital inputs. */
- /****** SYSTEM REQUIREMENT 3 *****/
- /* When user touches relay button on display, send */
- /* Modbus write command to toggle relay state. */
- /* Immediately read relay status from slave and */
- /* update display in real-time (decentralized */
- /* control). */
- /****** SYSTEM REQUIREMENT 4 *****/
- /* Continuously poll Modbus slave for 8 digital input */
- /* states. Display DI status on LCD with visual */
- /* indicators (green=ON, red=OFF). Update every */
- /* 500ms. */
- /****** SYSTEM REQUIREMENT 5 *****/
- /* WiFi STA mode: Connect to 'Cisco Wireless Access */
- /* Point 2' with password '12345678'. Set static IP */
- /* 192.168.1.200, Gateway 192.168.1.1, Subnet */
- /* 255.255.255.0. Maintain connection but don't */
- /* depend on it for relay control. */
- /****** SYSTEM REQUIREMENT 6 *****/
- /* Create responsive web admin panel on port 80. Show */
- /* real-time relay status, DI states, and Modbus */
- /* statistics. Allow Slave ID configuration. Work */
- /* even if web access fails (local control priority). */
- /****** END SYSTEM REQUIREMENTS *****/
- /****** DEFINITION OF LIBRARIES *****/
- #include <WiFi.h>
- #include <WebServer.h>
- #include <EEPROM.h>
- #include <HardwareSerial.h>
- #include <EasyButton.h>
- #include <lvgl.h>
- #include <Arduino_GFX_Library.h>
- /****** MACRO DEFINITIONS *****/
- #define UART1_TX_PIN 17
- #define UART1_RX_PIN 18
- #define MODBUS_BAUD 9600
- #define SLAVE_ID 0x01
- #define NUM_RELAYS 6
- #define NUM_DI 8
- #define DI_POLL_INTERVAL 500 // milliseconds
- #define TOUCHSCREEN_WIDTH 800
- #define TOUCHSCREEN_HEIGHT 480
- #define MODBUS_TIMEOUT 1000 // milliseconds
- #define EEPROM_SLAVE_ID_ADDR 0
- /****** WIFI CONFIGURATION *****/
- const char* SSID = "Cisco Wireless Access Point 2";
- const char* PASSWORD = "12345678";
- IPAddress STATIC_IP(192, 168, 1, 200);
- IPAddress GATEWAY(192, 168, 1, 1);
- IPAddress SUBNET(255, 255, 255, 0);
- /****** RELAY AND DI STATES *****/
- uint8_t relayStates[NUM_RELAYS] = {0};
- uint8_t diStates[NUM_DI] = {0};
- uint8_t slaveID = SLAVE_ID;
- /****** FUNCTION PROTOTYPES *****/
- void setup(void);
- void loop(void);
- void initializeDisplay(void);
- void initializeModbus(void);
- void initializeWiFi(void);
- void initializeWebServer(void);
- void handleDisplayTouch(void);
- void modbusWriteRelay(uint8_t relayIndex, uint8_t state);
- void modbusReadDigitalInputs(void);
- void updateDisplayRelayStatus(void);
- void updateDisplayDIStatus(void);
- void handleWebServerClients(void);
- void handleWebRoot(void);
- void handleWebAPI(void);
- void handleWebConfig(void);
- String generateWebHTML(void);
- void pollModbusSlave(void);
- void initEEPROM(void);
- void saveSlaveIDtoEEPROM(uint8_t id);
- uint8_t loadSlaveIDfromEEPROM(void);
- /****** GLOBAL VARIABLES *****/
- HardwareSerial ModbusSerial(1);
- WebServer webServer(80);
- unsigned long lastDIPollTime = 0;
- unsigned long lastWebUpdateTime = 0;
- bool modbusConnected = false;
- uint32_t modbusReadCount = 0;
- uint32_t modbusWriteCount = 0;
- uint32_t modbusErrorCount = 0;
- /****** DISPLAY SETUP *****/
- Arduino_GFX *gfx = NULL;
- uint16_t screenWidth = TOUCHSCREEN_WIDTH;
- uint16_t screenHeight = TOUCHSCREEN_HEIGHT;
- /****** LVGL DISPLAY BUFFER *****/
- static lv_disp_draw_buf_t draw_buf;
- static lv_disp_drv_t disp_drv;
- static lv_indev_drv_t indev_drv;
- static uint32_t screenWidth_lvgl = TOUCHSCREEN_WIDTH;
- static uint32_t screenHeight_lvgl = TOUCHSCREEN_HEIGHT;
- static lv_obj_t *relayButtons[NUM_RELAYS];
- static lv_obj_t *diIndicators[NUM_DI];
- static lv_obj_t *statusLabels[NUM_RELAYS];
- static lv_obj_t *diLabels[NUM_DI];
- /****** DISPLAY CALLBACK FUNCTION *****/
- void lv_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
- {
- uint32_t w = (area->x2 - area->x1 + 1);
- uint32_t h = (area->y2 - area->y1 + 1);
- gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
- lv_disp_flush_ready(disp);
- }
- /****** TOUCHSCREEN INPUT CALLBACK *****/
- void lv_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
- {
- uint16_t touchX = 0, touchY = 0;
- bool touched = false;
- // Placeholder for actual touchscreen read function
- // This would interface with ESP32-S3-Box touchscreen driver
- // For now, mark as not touched to prevent errors
- data->state = touched ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
- data->point.x = touchX;
- data->point.y = touchY;
- }
- /****** RELAY BUTTON CALLBACK *****/
- void relayButtonCallback(lv_event_t *e)
- {
- lv_obj_t *btn = lv_event_get_target(e);
- uint8_t relayIndex = 0;
- for (uint8_t i = 0; i < NUM_RELAYS; i++) {
- if (relayButtons[i] == btn) {
- relayIndex = i;
- break;
- }
- }
- uint8_t newState = (relayStates[relayIndex] == 0) ? 1 : 0;
- modbusWriteRelay(relayIndex, newState);
- }
- /****** INITIALIZE EEPROM *****/
- void initEEPROM(void)
- {
- EEPROM.begin(512);
- slaveID = loadSlaveIDfromEEPROM();
- if (slaveID == 0 || slaveID > 247) {
- slaveID = SLAVE_ID;
- saveSlaveIDtoEEPROM(slaveID);
- }
- }
- /****** SAVE SLAVE ID TO EEPROM *****/
- void saveSlaveIDtoEEPROM(uint8_t id)
- {
- EEPROM.write(EEPROM_SLAVE_ID_ADDR, id);
- EEPROM.commit();
- }
- /****** LOAD SLAVE ID FROM EEPROM *****/
- uint8_t loadSlaveIDfromEEPROM(void)
- {
- return EEPROM.read(EEPROM_SLAVE_ID_ADDR);
- }
- /****** INITIALIZE DISPLAY *****/
- void initializeDisplay(void)
- {
- // Initialize GFX library for ESP32-S3-Box display
- gfx = new Arduino_RGB_Display(
- screenWidth,
- screenHeight,
- GFX_NOT_DEFINED, // RGB mode
- true,
- NULL,
- 2, // DMA memory block size
- false,
- 16 // bit width
- );
- if (!gfx->begin()) {
- Serial.println("ERROR: Display initialization failed");
- return;
- }
- gfx->fillScreen(BLACK);
- // Initialize LVGL
- lv_init();
- // Initialize display buffer
- static lv_color_t buf1[screenWidth * 10];
- static lv_color_t buf2[screenWidth * 10];
- lv_disp_draw_buf_init(&draw_buf, buf1, buf2, screenWidth * 10);
- // Initialize display driver
- lv_disp_drv_init(&disp_drv);
- disp_drv.hor_res = screenWidth;
- disp_drv.ver_res = screenHeight;
- disp_drv.flush_cb = lv_disp_flush;
- disp_drv.draw_buf = &draw_buf;
- lv_disp_drv_register(&disp_drv);
- // Initialize touchscreen input
- lv_indev_drv_init(&indev_drv);
- indev_drv.type = LV_INDEV_TYPE_POINTER;
- indev_drv.read_cb = lv_touchpad_read;
- lv_indev_drv_register(&indev_drv);
- // Create main screen
- lv_obj_t *scr = lv_scr_act();
- lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a1a), 0);
- // Title label
- lv_obj_t *titleLabel = lv_label_create(scr);
- lv_label_set_text(titleLabel, "ESP32-S3 Relay Control");
- lv_obj_set_pos(titleLabel, 20, 10);
- lv_obj_set_style_text_color(titleLabel, lv_color_hex(0xFFFFFF), 0);
- lv_obj_set_style_text_font(titleLabel, &lv_font_montserrat_24, 0);
- // Create relay buttons
- int yPos = 50;
- for (uint8_t i = 0; i < NUM_RELAYS; i++) {
- relayButtons[i] = lv_btn_create(scr);
- lv_obj_set_pos(relayButtons[i], 20, yPos);
- lv_obj_set_size(relayButtons[i], 200, 50);
- lv_obj_set_style_bg_color(relayButtons[i], lv_color_hex(0x505050), 0);
- lv_obj_set_style_border_color(relayButtons[i], lv_color_hex(0xFFFFFF), 0);
- lv_obj_set_style_border_width(relayButtons[i], 2, 0);
- lv_obj_add_event_cb(relayButtons[i], relayButtonCallback, LV_EVENT_CLICKED, NULL);
- lv_obj_t *btnLabel = lv_label_create(relayButtons[i]);
- lv_label_set_text(btnLabel, "Relay OFF");
- lv_obj_center(btnLabel);
- lv_obj_set_style_text_color(btnLabel, lv_color_hex(0xFFFFFF), 0);
- statusLabels[i] = btnLabel;
- // Relay status text
- char relayText[20];
- snprintf(relayText, sizeof(relayText), "Relay %d", i + 1);
- lv_obj_t *relayTextLabel = lv_label_create(scr);
- lv_label_set_text(relayTextLabel, relayText);
- lv_obj_set_pos(relayTextLabel, 240, yPos + 15);
- lv_obj_set_style_text_color(relayTextLabel, lv_color_hex(0xFFFFFF), 0);
- yPos += 65;
- }
- // Create DI status indicators
- int diXPos = 500;
- int diYPos = 50;
- int diCol = 0;
- lv_obj_t *diTitleLabel = lv_label_create(scr);
- lv_label_set_text(diTitleLabel, "Digital Inputs");
- lv_obj_set_pos(diTitleLabel, diXPos, 10);
- lv_obj_set_style_text_color(diTitleLabel, lv_color_hex(0xFFFFFF), 0);
- lv_obj_set_style_text_font(diTitleLabel, &lv_font_montserrat_24, 0);
- for (uint8_t i = 0; i < NUM_DI; i++) {
- diIndicators[i] = lv_obj_create(scr);
- lv_obj_set_pos(diIndicators[i], diXPos + (diCol * 70), diYPos);
- lv_obj_set_size(diIndicators[i], 60, 60);
- lv_obj_set_style_bg_color(diIndicators[i], lv_color_hex(0xFF0000), 0);
- lv_obj_set_style_border_color(diIndicators[i], lv_color_hex(0xFFFFFF), 0);
- lv_obj_set_style_border_width(diIndicators[i], 2, 0);
- lv_obj_set_style_radius(diIndicators[i], 5, 0);
- diLabels[i] = lv_label_create(diIndicators[i]);
- char diText[5];
- snprintf(diText, sizeof(diText), "DI%d", i + 1);
- lv_label_set_text(diLabels[i], diText);
- lv_obj_center(diLabels[i]);
- lv_obj_set_style_text_color(diLabels[i], lv_color_hex(0xFFFFFF), 0);
- lv_obj_set_style_text_font(diLabels[i], &lv_font_montserrat_16, 0);
- diCol++;
- if (diCol >= 4) {
- diCol = 0;
- diYPos += 90;
- }
- }
- }
- /****** INITIALIZE MODBUS *****/
- void initializeModbus(void)
- {
- ModbusSerial.begin(MODBUS_BAUD, SERIAL_8N1, UART1_RX_PIN, UART1_TX_PIN);
- Serial.println("Modbus RTU initialized on UART1 (TX=GPIO17, RX=GPIO18) at 9600 baud");
- modbusConnected = true;
- }
- /****** MODBUS WRITE RELAY *****/
- void modbusWriteRelay(uint8_t relayIndex, uint8_t state)
- {
- if (!modbusConnected) {
- Serial.println("ERROR: Modbus not connected");
- return;
- }
- // Modbus function code 05 (Write Single Coil)
- uint8_t txBuffer[8];
- uint8_t txLen = 0;
- txBuffer[txLen++] = slaveID;
- txBuffer[txLen++] = 0x05; // Function code 05
- txBuffer[txLen++] = 0x00; // Coil address high byte
- txBuffer[txLen++] = relayIndex; // Coil address low byte
- txBuffer[txLen++] = (state == 0) ? 0x00 : 0xFF; // Coil value high byte
- txBuffer[txLen++] = 0x00; // Coil value low byte
- // Calculate CRC16
- uint16_t crc = calculateModbusCRC(txBuffer, 6);
- txBuffer[txLen++] = crc & 0xFF; // CRC low byte
- txBuffer[txLen++] = (crc >> 8) & 0xFF; // CRC high byte
- ModbusSerial.write(txBuffer, txLen);
- ModbusSerial.flush();
- modbusWriteCount++;
- // Read response
- unsigned long startTime = millis();
- uint8_t rxBuffer[8];
- int rxLen = 0;
- while (millis() - startTime < MODBUS_TIMEOUT) {
- if (ModbusSerial.available()) {
- rxBuffer[rxLen++] = ModbusSerial.read();
- if (rxLen >= 8) break;
- }
- }
- if (rxLen >= 8) {
- if (rxBuffer[0] == slaveID && rxBuffer[1] == 0x05) {
- relayStates[relayIndex] = state;
- Serial.printf("Relay %d set to %s\n", relayIndex, state ? "ON" : "OFF");
- updateDisplayRelayStatus();
- } else {
- modbusErrorCount++;
- Serial.println("ERROR: Invalid Modbus response");
- }
- } else {
- modbusErrorCount++;
- Serial.println("ERROR: Modbus timeout on write");
- }
- }
- /****** MODBUS READ DIGITAL INPUTS *****/
- void modbusReadDigitalInputs(void)
- {
- if (!modbusConnected) {
- return;
- }
- // Modbus function code 02 (Read Discrete Inputs)
- uint8_t txBuffer[8];
- uint8_t txLen = 0;
- txBuffer[txLen++] = slaveID;
- txBuffer[txLen++] = 0x02; // Function code 02
- txBuffer[txLen++] = 0x00; // Starting address high byte
- txBuffer[txLen++] = 0x00; // Starting address low byte
- txBuffer[txLen++] = 0x00; // Quantity high byte
- txBuffer[txLen++] = NUM_DI; // Quantity low byte
- // Calculate CRC16
- uint16_t crc = calculateModbusCRC(txBuffer, 6);
- txBuffer[txLen++] = crc & 0xFF; // CRC low byte
- txBuffer[txLen++] = (crc >> 8) & 0xFF; // CRC high byte
- ModbusSerial.write(txBuffer, txLen);
- ModbusSerial.flush();
- modbusReadCount++;
- // Read response
- unsigned long startTime = millis();
- uint8_t rxBuffer[256];
- int rxLen = 0;
- while (millis() - startTime < MODBUS_TIMEOUT) {
- if (ModbusSerial.available()) {
- rxBuffer[rxLen++] = ModbusSerial.read();
- if (rxLen >= 5 && rxLen >= (rxBuffer[2] + 5)) break;
- }
- }
- if (rxLen >= 5) {
- if (rxBuffer[0] == slaveID && rxBuffer[1] == 0x02) {
- uint8_t byteCount = rxBuffer[2];
- for (uint8_t i = 0; i < NUM_DI && i < (byteCount * 8); i++) {
- uint8_t byteIndex = 3 + (i / 8);
- uint8_t bitIndex = i % 8;
- diStates[i] = (rxBuffer[byteIndex] >> bitIndex) & 0x01;
- }
- updateDisplayDIStatus();
- } else {
- modbusErrorCount++;
- }
- } else {
- modbusErrorCount++;
- }
- }
- /****** CALCULATE MODBUS CRC16 *****/
- uint16_t calculateModbusCRC(uint8_t *data, uint8_t length)
- {
- uint16_t crc = 0xFFFF;
- for (uint8_t i = 0; i < length; i++) {
- crc ^= data[i];
- for (uint8_t j = 0; j < 8; j++) {
- if (crc & 0x0001) {
- crc = (crc >> 1) ^ 0xA001;
- } else {
- crc = crc >> 1;
- }
- }
- }
- return crc;
- }
- /****** UPDATE DISPLAY RELAY STATUS *****/
- void updateDisplayRelayStatus(void)
- {
- for (uint8_t i = 0; i < NUM_RELAYS; i++) {
- if (relayStates[i] == 0) {
- lv_label_set_text(statusLabels[i], "Relay OFF");
- lv_obj_set_style_bg_color(relayButtons[i], lv_color_hex(0x505050), 0);
- } else {
- lv_label_set_text(statusLabels[i], "Relay ON");
- lv_obj_set_style_bg_color(relayButtons[i], lv_color_hex(0x00AA00), 0);
- }
- }
- }
- /****** UPDATE DISPLAY DI STATUS *****/
- void updateDisplayDIStatus(void)
- {
- for (uint8_t i = 0; i < NUM_DI; i++) {
- if (diStates[i] == 0) {
- lv_obj_set_style_bg_color(diIndicators[i], lv_color_hex(0xFF0000), 0);
- } else {
- lv_obj_set_style_bg_color(diIndicators[i], lv_color_hex(0x00FF00), 0);
- }
- }
- }
- /****** INITIALIZE WIFI *****/
- void initializeWiFi(void)
- {
- WiFi.mode(WIFI_STA);
- WiFi.config(STATIC_IP, GATEWAY, SUBNET);
- WiFi.begin(SSID, PASSWORD);
- int attempts = 0;
- while (WiFi.status() != WL_CONNECTED && attempts < 20) {
- delay(500);
- Serial.print(".");
- attempts++;
- }
- if (WiFi.status() == WL_CONNECTED) {
- Serial.println("\nWiFi connected");
- Serial.printf("IP address: %s\n", WiFi.localIP().toString().c_str());
- } else {
- Serial.println("\nWiFi connection failed - local control will continue");
- }
- }
- /****** INITIALIZE WEB SERVER *****/
- void initializeWebServer(void)
- {
- webServer.on("/", HTTP_GET, handleWebRoot);
- webServer.on("/api/relays", HTTP_GET, handleWebAPI);
- webServer.on("/api/relays", HTTP_POST, handleWebAPI);
- webServer.on("/api/config", HTTP_POST, handleWebConfig);
- webServer.begin();
- Serial.println("Web server started on port 80");
- }
- /****** HANDLE WEB ROOT *****/
- void handleWebRoot(void)
- {
- webServer.send(200, "text/html", generateWebHTML());
- }
- /****** GENERATE WEB HTML *****/
- String generateWebHTML(void)
- {
- String html = R"(
- <!DOCTYPE html>
- <html>
- <head>
- <title>ESP32-S3 Relay Control Panel</title>
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <style>
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: Arial, sans-serif;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- min-height: 100vh;
- padding: 20px;
- }
- .container {
- max-width: 1000px;
- margin: 0 auto;
- background: white;
- border-radius: 10px;
- box-shadow: 0 8px 32px rgba(0,0,0,0.1);
- padding: 30px;
- }
- h1 {
- color: #333;
- text-align: center;
- margin-bottom: 30px;
- font-size: 28px;
- }
- .status-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
- gap: 20px;
- margin-bottom: 30px;
- }
- .relay-card {
- background: #f5f5f5;
- padding: 20px;
- border-radius: 8px;
- text-align: center;
- border: 2px solid #ddd;
- transition: all 0.3s ease;
- }
- .relay-card:hover {
- border-color: #667eea;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(102,126,234,0.2);
- }
- .relay-name {
- font-weight: bold;
- color: #333;
- margin-bottom: 10px;
- font-size: 16px;
- }
- .relay-status {
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 10px;
- padding: 10px;
- border-radius: 5px;
- color: white;
- }
- .relay-status.on {
- background: #4CAF50;
- }
- .relay-status.off {
- background: #f44336;
- }
- .relay-btn {
- width: 100%;
- padding: 10px;
- border: none;
- border-radius: 5px;
- background: #667eea;
- color: white;
- cursor: pointer;
- font-size: 14px;
- font-weight: bold;
- transition: background 0.3s ease;
- }
- .relay-btn:hover {
- background: #764ba2;
- }
- .relay-btn:active {
- transform: scale(0.98);
- }
- .di-grid {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
- gap: 15px;
- margin-top: 30px;
- padding-top: 30px;
- border-top: 2px solid #ddd;
- }
- .di-indicator {
- background: #f5f5f5;
- padding: 15px;
- border-radius: 8px;
- text-align: center;
- border: 2px solid #ddd;
- }
- .di-label {
- font-weight: bold;
- color: #333;
- margin-bottom: 10px;
- }
- .di-status {
- width: 80px;
- height: 80px;
- margin: 0 auto;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- font-weight: bold;
- color: white;
- font-size: 14px;
- }
- .di-status.on {
- background: #4CAF50;
- }
- .di-status.off {
- background: #f44336;
- }
- .stats {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
- gap: 15px;
- margin-top: 30px;
- padding: 20px;
- background: #f5f5f5;
- border-radius: 8px;
- }
- .stat-item {
- text-align: center;
- }
- .stat-label {
- font-weight: bold;
- color: #666;
- font-size: 12px;
- text-transform: uppercase;
- }
- .stat-value {
- font-size: 24px;
- color: #333;
- font-weight: bold;
- }
- .config-section {
- margin-top: 30px;
- padding: 20px;
- background: #f5f5f5;
- border-radius: 8px;
- }
- .config-section h2 {
- color: #333;
- margin-bottom: 15px;
- font-size: 18px;
- }
- .form-group {
- margin-bottom: 15px;
- }
- .form-group label {
- display: block;
- color: #666;
- margin-bottom: 5px;
- font-weight: bold;
- }
- .form-group input {
- width: 100%;
- padding: 10px;
- border: 1px solid #ddd;
- border-radius: 5px;
- font-size: 14px;
- }
- .form-group button {
- width: 100%;
- padding: 10px;
- background: #667eea;
- color: white;
- border: none;
- border-radius: 5px;
- cursor: pointer;
- font-weight: bold;
- transition: background 0.3s ease;
- }
- .form-group button:hover {
- background: #764ba2;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>ESP32-S3 Relay Control Panel</h1>
- <div class="status-grid" id="relayContainer">
- <!-- Relay cards will be inserted here by JavaScript -->
- </div>
- <div class="di-grid" id="diContainer">
- <!-- DI indicators will be inserted here by JavaScript -->
- </div>
- <div class="stats">
- <div class="stat-item">
- <div class="stat-label">Modbus Reads</div>
- <div class="stat-value" id="readCount">0</div>
- </div>
- <div class="stat-item">
- <div class="stat-label">Modbus Writes</div>
- <div class="stat-value" id="writeCount">0</div>
- </div>
- <div class="stat-item">
- <div class="stat-label">Modbus Errors</div>
- <div class="stat-value" id="errorCount">0</div>
- </div>
- <div class="stat-item">
- <div class="stat-label">WiFi Status</div>
- <div class="stat-value" id="wifiStatus">-</div>
- </div>
- </div>
- <div class="config-section">
- <h2>Configuration</h2>
- <div class="form-group">
- <label for="slaveId">Modbus Slave ID (1-247):</label>
- <input type="number" id="slaveId" min="1" max="247" placeholder="Enter Slave ID">
- </div>
- <div class="form-group">
- <button onclick="updateSlaveId()">Update Slave ID</button>
- </div>
- </div>
- </div>
- <script>
- const UPDATE_INTERVAL = 1000; // Update every 1 second
- function initializeUI() {
- // Create relay cards
- let relayHTML = '';
- for (let i = 0; i < 6; i++) {
- relayHTML += `
- <div class="relay-card">
- <div class="relay-name">Relay ${i + 1}</div>
- <div class="relay-status" id="relay-status-${i}">OFF</div>
- <button class="relay-btn" onclick="toggleRelay(${i})">Toggle</button>
- </div>
- `;
- }
- document.getElementById('relayContainer').innerHTML = relayHTML;
- // Create DI indicators
- let diHTML = '';
- for (let i = 0; i < 8; i++) {
- diHTML += `
- <div class="di-indicator">
- <div class="di-label">DI${i + 1}</div>
- <div class="di-status" id="di-status-${i}">OFF</div>
- </div>
- `;
- }
- document.getElementById('diContainer').innerHTML = diHTML;
- updateStatus();
- setInterval(updateStatus, UPDATE_INTERVAL);
- }
- function updateStatus() {
- fetch('/api/relays')
- .then(response => response.json())
- .then(data => {
- // Update relay status
- for (let i = 0; i < 6; i++) {
- const statusElem = document.getElementById(`relay-status-${i}`);
- statusElem.textContent = data.relays[i] ? 'ON' : 'OFF';
- statusElem.className = data.relays[i] ? 'relay-status on' : 'relay-status off';
- }
- // Update DI status
- for (let i = 0; i < 8; i++) {
- const statusElem = document.getElementById(`di-status-${i}`);
- statusElem.textContent = data.inputs[i] ? 'ON' : 'OFF';
- statusElem.className = data.inputs[i] ? 'di-status on' : 'di-status off';
- }
- // Update statistics
- document.getElementById('readCount').textContent = data.stats.reads;
- document.getElementById('writeCount').textContent = data.stats.writes;
- document.getElementById('errorCount').textContent = data.stats.errors;
- document.getElementById('wifiStatus').textContent = data.stats.wifi ? 'OK' : 'N/A';
- })
- .catch(err => console.error('Error fetching status:', err));
- }
- function toggleRelay(index) {
- fetch('/api/relays', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- relay: index,
- state: 1 // Toggle will be handled by server
- })
- })
- .then(response => response.json())
- .then(data => {
- updateStatus();
- })
- .catch(err => console.error('Error toggling relay:', err));
- }
- function updateSlaveId() {
- const slaveId = document.getElementById('slaveId').value;
- if (slaveId < 1 || slaveId > 247) {
- alert('Please enter a valid Slave ID (1-247)');
- return;
- }
- fetch('/api/config', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- slaveId: parseInt(slaveId)
- })
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- alert('Slave ID updated successfully');
- updateStatus();
- } else {
- alert('Error updating Slave ID');
- }
- })
- .catch(err => console.error('Error updating config:', err));
- }
- window.addEventListener('load', initializeUI);
- </script>
- </body>
- </html>
- )";
- return html;
- }
- /****** HANDLE WEB API *****/
- void handleWebAPI(void)
- {
- if (webServer.method() == HTTP_GET) {
- // Return current status
- String json = "{";
- json += "\"relays\":[";
- for (uint8_t i = 0; i < NUM_RELAYS; i++) {
- json += (relayStates[i] ? "1" : "0");
- if (i < NUM_RELAYS - 1) json += ",";
- }
- json += "],";
- json += "\"inputs\":[";
- for (uint8_t i = 0; i < NUM_DI; i++) {
- json += (diStates[i] ? "1" : "0");
- if (i < NUM_DI - 1) json += ",";
- }
- json += "],";
- json += "\"stats\":{";
- json += "\"reads\":" + String(modbusReadCount) + ",";
- json += "\"writes\":" + String(modbusWriteCount) + ",";
- json += "\"errors\":" + String(modbusErrorCount) + ",";
- json += "\"wifi\":" + String(WiFi.isConnected() ? 1 : 0);
- json += "}";
- json += "}";
- webServer.send(200, "application/json", json);
- } else if (webServer.method() == HTTP_POST) {
- // Toggle relay
- if (webServer.hasArg("plain")) {
- String body = webServer.arg("plain");
- // Simple JSON parsing
- int relayIndex = -1;
- sscanf(body.c_str(), "{\"relay\":%d", &relayIndex);
- if (relayIndex >= 0 && relayIndex < NUM_RELAYS) {
- uint8_t newState = (relayStates[relayIndex] == 0) ? 1 : 0;
- modbusWriteRelay(relayIndex, newState);
- String json = "{\"success\":true,\"relay\":" + String(relayIndex) + "}";
- webServer.send(200, "application/json", json);
- } else {
- webServer.send(400, "application/json", "{\"success\":false}");
- }
- } else {
- webServer.send(400, "application/json", "{\"success\":false}");
- }
- }
- }
- /****** HANDLE WEB CONFIG *****/
- void handleWebConfig(void)
- {
- if (webServer.hasArg("plain")) {
- String body = webServer.arg("plain");
- // Simple JSON parsing
- int newSlaveID = -1;
- sscanf(body.c_str(), "{\"slaveId\":%d", &newSlaveID);
- if (newSlaveID > 0 && newSlaveID < 248) {
- slaveID = newSlaveID;
- saveSlaveIDtoEEPROM(slaveID);
- String json = "{\"success\":true,\"slaveId\":" + String(slaveID) + "}";
- webServer.send(200, "application/json", json);
- } else {
- webServer.send(400, "application/json", "{\"success\":false,\"error\":\"Invalid Slave ID\"}");
- }
- } else {
- webServer.send(400, "application/json", "{\"success\":false}");
- }
- }
- /****** POLL MODBUS SLAVE *****/
- void pollModbusSlave(void)
- {
- unsigned long currentTime = millis();
- if (currentTime - lastDIPollTime >= DI_POLL_INTERVAL) {
- modbusReadDigitalInputs();
- lastDIPollTime = currentTime;
- }
- }
- /****** SETUP *****/
- void setup(void)
- {
- Serial.begin(115200);
- delay(1000);
- Serial.println("\n\n");
- Serial.println("========================================");
- Serial.println("ESP32-S3 Relay Control System Starting");
- Serial.println("========================================");
- // Initialize EEPROM
- initEEPROM();
- Serial.printf("Loaded Slave ID from EEPROM: %d\n", slaveID);
- // Initialize display (LVGL + GFX)
- Serial.println("Initializing display...");
- initializeDisplay();
- Serial.println("Display initialized");
- // Initialize Modbus RTU
- Serial.println("Initializing Modbus RTU...");
- initializeModbus();
- // Initialize WiFi
- Serial.println("Initializing WiFi...");
- initializeWiFi();
- // Initialize Web Server
- Serial.println("Initializing Web Server...");
- initializeWebServer();
- Serial.println("========================================");
- Serial.println("System Ready");
- Serial.println("========================================");
- }
- /****** MAIN LOOP *****/
- void loop(void)
- {
- // Handle web server clients
- webServer.handleClient();
- // Poll Modbus slave for digital inputs
- pollModbusSlave();
- // Update LVGL display
- lv_timer_handler();
- // Maintain WiFi connection
- if (WiFi.status() != WL_CONNECTED) {
- static unsigned long lastWiFiAttempt = 0;
- if (millis() - lastWiFiAttempt > 30000) {
- Serial.println("WiFi disconnected, attempting reconnection...");
- WiFi.reconnect();
- lastWiFiAttempt = millis();
- }
- }
- delay(10);
- }
- /* END CODE */
Advertisement
Add Comment
Please, Sign In to add comment