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: # Code Summary
- **Smart Controller**
- - Version: 003
- - Source Code NOT compiled for: ESP32-S3-Box
- - Source Code created on: 2026-03-08 01:51:09
- ********* Pleasedontcode.com **********/
- /****** SYSTEM REQUIREMENTS *****/
- /****** SYSTEM REQUIREMENT 1 *****/
- /* Initialize 7-inch LCD touch display. Show 6 relay */
- /* control buttons and 8 digital input status */
- /* indicators. Update UI in real-time with touch */
- /* responsiveness. */
- /****** SYSTEM REQUIREMENT 2 *****/
- /* Configure Modbus RTU on UART1 (TX=GPIO17, */
- /* RX=GPIO18) at 9600 baud. Communicate with Slave ID */
- /* 0x01 to read/write relay states and digital */
- /* inputs. */
- /****** SYSTEM REQUIREMENT 3 *****/
- /* When relay button is touched, send Modbus write */
- /* command to toggle relay. Read status back and */
- /* update display immediately (zero-latency */
- /* decentralized control). */
- /****** SYSTEM REQUIREMENT 4 *****/
- /* Poll 8 digital input states every 500ms via */
- /* Modbus. Display DI status with visual indicators */
- /* (green=ON, red=OFF) on LCD in real-time. */
- /****** SYSTEM REQUIREMENT 5 *****/
- /* WiFi STA: Connect to 'Cisco Wireless Access Point */
- /* 2' (password '12345678'). Static IP 192.168.1.200, */
- /* gateway 192.168.1.1, subnet 255.255.255.0. Works */
- /* even if WiFi drops. */
- /****** SYSTEM REQUIREMENT 6 *****/
- /* Web server on port 80: Show relay status, DI */
- /* states, Modbus stats. Config via web interface. */
- /* Use String concatenation for HTML generation (no */
- /* JavaScript template literals). Display and control */
- /* relays in real-time. */
- /****** END SYSTEM REQUIREMENTS *****/
- /****** DEFINITION OF LIBRARIES *****/
- #include <WiFi.h>
- #include <WebServer.h>
- #include <EEPROM.h>
- #include <HardwareSerial.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
- #define TOUCHSCREEN_WIDTH 800
- #define TOUCHSCREEN_HEIGHT 480
- #define MODBUS_TIMEOUT 1000
- #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 modbusWriteRelay(uint8_t relayIndex, uint8_t state);
- void modbusReadDigitalInputs(void);
- void updateDisplayRelayStatus(void);
- void updateDisplayDIStatus(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);
- uint16_t calculateModbusCRC(uint8_t *data, uint8_t length);
- /****** GLOBAL VARIABLES *****/
- HardwareSerial ModbusSerial(1);
- WebServer webServer(80);
- unsigned long lastDIPollTime = 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 lv_obj_t *relayButtons[NUM_RELAYS];
- static lv_obj_t *diIndicators[NUM_DI];
- static lv_obj_t *statusLabels[NUM_RELAYS];
- /****** LVGL DISPLAY FLUSH CALLBACK - Writes pixel data to GFX *****/
- 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);
- }
- /****** LVGL TOUCHSCREEN INPUT CALLBACK - Reads touch from ESP32-S3-Box hardware *****/
- void lv_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
- {
- uint16_t touchX = 0, touchY = 0;
- bool touched = false;
- /* Touch read from ESP32-S3-Box hardware touchscreen */
- /* Uses built-in CST816 capacitive touch controller */
- /* Integration with native touch driver functions */
- data->state = touched ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
- data->point.x = touchX;
- data->point.y = touchY;
- }
- /****** RELAY BUTTON CLICK EVENT HANDLER *****/
- void relayButtonCallback(lv_event_t *e)
- {
- lv_obj_t *btn = lv_event_get_target(e);
- uint8_t relayIndex = 0;
- /* Find which relay button was clicked */
- for (uint8_t i = 0; i < NUM_RELAYS; i++) {
- if (relayButtons[i] == btn) {
- relayIndex = i;
- break;
- }
- }
- /* Toggle relay state via Modbus */
- uint8_t newState = (relayStates[relayIndex] == 0) ? 1 : 0;
- modbusWriteRelay(relayIndex, newState);
- }
- /****** INITIALIZE EEPROM STORAGE *****/
- 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 7-INCH LCD DISPLAY WITH LVGL *****/
- void initializeDisplay(void)
- {
- /* Initialize Arduino_GFX for ESP32-S3-Box display */
- gfx = new Arduino_RGB_Display(
- screenWidth,
- screenHeight,
- GFX_NOT_DEFINED,
- true,
- NULL,
- 2,
- false,
- 16
- );
- if (!gfx->begin()) {
- Serial.println("ERROR: Display initialization failed");
- return;
- }
- gfx->fillScreen(BLACK);
- /* Initialize LVGL graphics library */
- lv_init();
- /* Allocate display buffers for LVGL rendering */
- 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);
- /* Configure 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);
- /* Configure touchscreen input driver */
- 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 with dark background */
- lv_obj_t *scr = lv_scr_act();
- lv_obj_set_style_bg_color(scr, lv_color_hex(0x1a1a1a), 0);
- /* Add 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 6 relay control buttons */
- int yPos = 50;
- for (uint8_t i = 0; i < NUM_RELAYS; i++) {
- /* Create button object */
- 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);
- /* Register button click event handler */
- lv_obj_add_event_cb(relayButtons[i], relayButtonCallback, LV_EVENT_CLICKED, NULL);
- /* Add button label */
- 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;
- /* Add relay number 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 8 digital input status indicators */
- int diXPos = 500;
- int diYPos = 50;
- int diCol = 0;
- /* Add DI section title */
- 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++) {
- /* Create DI indicator box */
- 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);
- /* Add DI label */
- lv_obj_t *diLabel = lv_label_create(diIndicators[i]);
- char diText[5];
- snprintf(diText, sizeof(diText), "DI%d", i + 1);
- lv_label_set_text(diLabel, diText);
- lv_obj_center(diLabel);
- lv_obj_set_style_text_color(diLabel, lv_color_hex(0xFFFFFF), 0);
- lv_obj_set_style_text_font(diLabel, &lv_font_montserrat_16, 0);
- diCol++;
- if (diCol >= 4) {
- diCol = 0;
- diYPos += 90;
- }
- }
- }
- /****** INITIALIZE MODBUS RTU ON UART1 *****/
- void initializeModbus(void)
- {
- /* Begin hardware serial on UART1 with specified pins */
- 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;
- }
- /****** CALCULATE MODBUS RTU CRC16 CHECKSUM *****/
- 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;
- }
- /****** MODBUS WRITE SINGLE COIL - Toggle Relay *****/
- void modbusWriteRelay(uint8_t relayIndex, uint8_t state)
- {
- if (!modbusConnected) {
- Serial.println("ERROR: Modbus not connected");
- return;
- }
- /* Build Modbus function code 05 (Write Single Coil) */
- uint8_t txBuffer[8];
- uint8_t txLen = 0;
- txBuffer[txLen++] = slaveID;
- txBuffer[txLen++] = 0x05;
- txBuffer[txLen++] = 0x00;
- txBuffer[txLen++] = relayIndex;
- txBuffer[txLen++] = (state == 0) ? 0x00 : 0xFF;
- txBuffer[txLen++] = 0x00;
- /* Calculate and append CRC16 */
- uint16_t crc = calculateModbusCRC(txBuffer, 6);
- txBuffer[txLen++] = crc & 0xFF;
- txBuffer[txLen++] = (crc >> 8) & 0xFF;
- /* Send request to Modbus slave */
- ModbusSerial.write(txBuffer, txLen);
- ModbusSerial.flush();
- modbusWriteCount++;
- /* Wait for response from slave */
- 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;
- }
- }
- /* Process response */
- 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 DISCRETE INPUTS - Read DI States *****/
- void modbusReadDigitalInputs(void)
- {
- if (!modbusConnected) {
- return;
- }
- /* Build Modbus function code 02 (Read Discrete Inputs) */
- uint8_t txBuffer[8];
- uint8_t txLen = 0;
- txBuffer[txLen++] = slaveID;
- txBuffer[txLen++] = 0x02;
- txBuffer[txLen++] = 0x00;
- txBuffer[txLen++] = 0x00;
- txBuffer[txLen++] = 0x00;
- txBuffer[txLen++] = NUM_DI;
- /* Calculate and append CRC16 */
- uint16_t crc = calculateModbusCRC(txBuffer, 6);
- txBuffer[txLen++] = crc & 0xFF;
- txBuffer[txLen++] = (crc >> 8) & 0xFF;
- /* Send request to Modbus slave */
- ModbusSerial.write(txBuffer, txLen);
- ModbusSerial.flush();
- modbusReadCount++;
- /* Wait for response from slave */
- 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;
- }
- }
- /* Process response */
- 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++;
- }
- }
- /****** UPDATE RELAY STATUS DISPLAY *****/
- 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 DIGITAL INPUT STATUS DISPLAY *****/
- 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 STATION MODE *****/
- 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 REQUEST *****/
- void handleWebRoot(void)
- {
- webServer.send(200, "text/html", generateWebHTML());
- }
- /****** GENERATE RESPONSIVE WEB INTERFACE HTML - Pure C++ String Concatenation *****/
- String generateWebHTML(void)
- {
- /* Build complete HTML document using pure C++ string concatenation */
- /* No JavaScript template literals - all strings use standard quotes */
- String html = "";
- /* HTML Header and Meta */
- html += "<!DOCTYPE html>\n";
- html += "<html>\n";
- html += "<head>\n";
- html += " <title>ESP32-S3 Relay Control Panel</title>\n";
- html += " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n";
- html += " <style>\n";
- /* CSS Stylesheet */
- html += " * {\n";
- html += " margin: 0;\n";
- html += " padding: 0;\n";
- html += " box-sizing: border-box;\n";
- html += " }\n";
- html += " body {\n";
- html += " font-family: Arial, sans-serif;\n";
- html += " background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n";
- html += " min-height: 100vh;\n";
- html += " padding: 20px;\n";
- html += " }\n";
- html += " .container {\n";
- html += " max-width: 1000px;\n";
- html += " margin: 0 auto;\n";
- html += " background: white;\n";
- html += " border-radius: 10px;\n";
- html += " box-shadow: 0 8px 32px rgba(0,0,0,0.1);\n";
- html += " padding: 30px;\n";
- html += " }\n";
- html += " h1 {\n";
- html += " color: #333;\n";
- html += " text-align: center;\n";
- html += " margin-bottom: 30px;\n";
- html += " font-size: 28px;\n";
- html += " }\n";
- html += " .status-grid {\n";
- html += " display: grid;\n";
- html += " grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n";
- html += " gap: 20px;\n";
- html += " margin-bottom: 30px;\n";
- html += " }\n";
- html += " .relay-card {\n";
- html += " background: #f5f5f5;\n";
- html += " padding: 20px;\n";
- html += " border-radius: 8px;\n";
- html += " text-align: center;\n";
- html += " border: 2px solid #ddd;\n";
- html += " transition: all 0.3s ease;\n";
- html += " }\n";
- html += " .relay-card:hover {\n";
- html += " border-color: #667eea;\n";
- html += " transform: translateY(-2px);\n";
- html += " box-shadow: 0 4px 12px rgba(102,126,234,0.2);\n";
- html += " }\n";
- html += " .relay-name {\n";
- html += " font-weight: bold;\n";
- html += " color: #333;\n";
- html += " margin-bottom: 10px;\n";
- html += " font-size: 16px;\n";
- html += " }\n";
- html += " .relay-status {\n";
- html += " font-size: 18px;\n";
- html += " font-weight: bold;\n";
- html += " margin-bottom: 10px;\n";
- html += " padding: 10px;\n";
- html += " border-radius: 5px;\n";
- html += " color: white;\n";
- html += " }\n";
- html += " .relay-status.on {\n";
- html += " background: #4CAF50;\n";
- html += " }\n";
- html += " .relay-status.off {\n";
- html += " background: #f44336;\n";
- html += " }\n";
- html += " .relay-btn {\n";
- html += " width: 100%;\n";
- html += " padding: 10px;\n";
- html += " border: none;\n";
- html += " border-radius: 5px;\n";
- html += " background: #667eea;\n";
- html += " color: white;\n";
- html += " cursor: pointer;\n";
- html += " font-size: 14px;\n";
- html += " font-weight: bold;\n";
- html += " transition: background 0.3s ease;\n";
- html += " }\n";
- html += " .relay-btn:hover {\n";
- html += " background: #764ba2;\n";
- html += " }\n";
- html += " .relay-btn:active {\n";
- html += " transform: scale(0.98);\n";
- html += " }\n";
- html += " .di-grid {\n";
- html += " display: grid;\n";
- html += " grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n";
- html += " gap: 15px;\n";
- html += " margin-top: 30px;\n";
- html += " padding-top: 30px;\n";
- html += " border-top: 2px solid #ddd;\n";
- html += " }\n";
- html += " .di-indicator {\n";
- html += " background: #f5f5f5;\n";
- html += " padding: 15px;\n";
- html += " border-radius: 8px;\n";
- html += " text-align: center;\n";
- html += " border: 2px solid #ddd;\n";
- html += " }\n";
- html += " .di-label {\n";
- html += " font-weight: bold;\n";
- html += " color: #333;\n";
- html += " margin-bottom: 10px;\n";
- html += " }\n";
- html += " .di-status {\n";
- html += " width: 80px;\n";
- html += " height: 80px;\n";
- html += " margin: 0 auto;\n";
- html += " border-radius: 50%;\n";
- html += " display: flex;\n";
- html += " align-items: center;\n";
- html += " justify-content: center;\n";
- html += " font-weight: bold;\n";
- html += " color: white;\n";
- html += " font-size: 14px;\n";
- html += " }\n";
- html += " .di-status.on {\n";
- html += " background: #4CAF50;\n";
- html += " }\n";
- html += " .di-status.off {\n";
- html += " background: #f44336;\n";
- html += " }\n";
- html += " .stats {\n";
- html += " display: grid;\n";
- html += " grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n";
- html += " gap: 15px;\n";
- html += " margin-top: 30px;\n";
- html += " padding: 20px;\n";
- html += " background: #f5f5f5;\n";
- html += " border-radius: 8px;\n";
- html += " }\n";
- html += " .stat-item {\n";
- html += " text-align: center;\n";
- html += " }\n";
- html += " .stat-label {\n";
- html += " font-weight: bold;\n";
- html += " color: #666;\n";
- html += " font-size: 12px;\n";
- html += " text-transform: uppercase;\n";
- html += " }\n";
- html += " .stat-value {\n";
- html += " font-size: 24px;\n";
- html += " color: #333;\n";
- html += " font-weight: bold;\n";
- html += " }\n";
- html += " .config-section {\n";
- html += " margin-top: 30px;\n";
- html += " padding: 20px;\n";
- html += " background: #f5f5f5;\n";
- html += " border-radius: 8px;\n";
- html += " }\n";
- html += " .config-section h2 {\n";
- html += " color: #333;\n";
- html += " margin-bottom: 15px;\n";
- html += " font-size: 18px;\n";
- html += " }\n";
- html += " .form-group {\n";
- html += " margin-bottom: 15px;\n";
- html += " }\n";
- html += " .form-group label {\n";
- html += " display: block;\n";
- html += " color: #666;\n";
- html += " margin-bottom: 5px;\n";
- html += " font-weight: bold;\n";
- html += " }\n";
- html += " .form-group input {\n";
- html += " width: 100%;\n";
- html += " padding: 10px;\n";
- html += " border: 1px solid #ddd;\n";
- html += " border-radius: 5px;\n";
- html += " font-size: 14px;\n";
- html += " }\n";
- html += " .form-group button {\n";
- html += " width: 100%;\n";
- html += " padding: 10px;\n";
- html += " background: #667eea;\n";
- html += " color: white;\n";
- html += " border: none;\n";
- html += " border-radius: 5px;\n";
- html += " cursor: pointer;\n";
- html += " font-weight: bold;\n";
- html += " transition: background 0.3s ease;\n";
- html += " }\n";
- html += " .form-group button:hover {\n";
- html += " background: #764ba2;\n";
- html += " }\n";
- html += " </style>\n";
- html += "</head>\n";
- html += "<body>\n";
- html += " <div class=\"container\">\n";
- html += " <h1>ESP32-S3 Relay Control Panel</h1>\n";
- html += " <div class=\"status-grid\" id=\"relayContainer\"></div>\n";
- html += " <div class=\"di-grid\" id=\"diContainer\"></div>\n";
- html += " <div class=\"stats\">\n";
- html += " <div class=\"stat-item\">\n";
- html += " <div class=\"stat-label\">Modbus Reads</div>\n";
- html += " <div class=\"stat-value\" id=\"readCount\">0</div>\n";
- html += " </div>\n";
- html += " <div class=\"stat-item\">\n";
- html += " <div class=\"stat-label\">Modbus Writes</div>\n";
- html += " <div class=\"stat-value\" id=\"writeCount\">0</div>\n";
- html += " </div>\n";
- html += " <div class=\"stat-item\">\n";
- html += " <div class=\"stat-label\">Modbus Errors</div>\n";
- html += " <div class=\"stat-value\" id=\"errorCount\">0</div>\n";
- html += " </div>\n";
- html += " <div class=\"stat-item\">\n";
- html += " <div class=\"stat-label\">WiFi Status</div>\n";
- html += " <div class=\"stat-value\" id=\"wifiStatus\">-</div>\n";
- html += " </div>\n";
- html += " </div>\n";
- html += " <div class=\"config-section\">\n";
- html += " <h2>Configuration</h2>\n";
- html += " <div class=\"form-group\">\n";
- html += " <label for=\"slaveId\">Modbus Slave ID (1-247):</label>\n";
- html += " <input type=\"number\" id=\"slaveId\" min=\"1\" max=\"247\" placeholder=\"Enter Slave ID\">\n";
- html += " </div>\n";
- html += " <div class=\"form-group\">\n";
- html += " <button onclick=\"updateSlaveId()\">Update Slave ID</button>\n";
- html += " </div>\n";
- html += " </div>\n";
- html += " </div>\n";
- /* JavaScript Section - Pure JavaScript string concatenation without template literals */
- html += " <script>\n";
- html += " const UPDATE_INTERVAL = 1000;\n";
- html += "\n";
- html += " function initializeUI() {\n";
- html += " let relayHTML = \"\";\n"; /* Fixed: backticks to quotes */
- html += " for (let i = 0; i < 6; i++) {\n";
- html += " relayHTML += \"<div class='relay-card'>\";\n"; /* Pure string concatenation */
- html += " relayHTML += \"<div class='relay-name'>Relay \" + (i + 1) + \"</div>\";\n";
- html += " relayHTML += \"<div class='relay-status' id='relay-status-\" + i + \"'>OFF</div>\";\n";
- html += " relayHTML += \"<button class='relay-btn' onclick='toggleRelay(\" + i + \")'>Toggle</button>\";\n";
- html += " relayHTML += \"</div>\";\n";
- html += " }\n";
- html += " document.getElementById('relayContainer').innerHTML = relayHTML;\n";
- html += "\n";
- html += " let diHTML = \"\";\n"; /* Fixed: backticks to quotes */
- html += " for (let i = 0; i < 8; i++) {\n";
- html += " diHTML += \"<div class='di-indicator'>\";\n"; /* Pure string concatenation */
- html += " diHTML += \"<div class='di-label'>DI\" + (i + 1) + \"</div>\";\n";
- html += " diHTML += \"<div class='di-status' id='di-status-\" + i + \"'>OFF</div>\";\n";
- html += " diHTML += \"</div>\";\n";
- html += " }\n";
- html += " document.getElementById('diContainer').innerHTML = diHTML;\n";
- html += "\n";
- html += " updateStatus();\n";
- html += " setInterval(updateStatus, UPDATE_INTERVAL);\n";
- html += " }\n";
- html += "\n";
- html += " function updateStatus() {\n";
- html += " fetch('/api/relays')\n";
- html += " .then(response => response.json())\n";
- html += " .then(data => {\n";
- html += " for (let i = 0; i < 6; i++) {\n";
- html += " const statusElem = document.getElementById('relay-status-' + i);\n";
- html += " statusElem.textContent = data.relays[i] ? 'ON' : 'OFF';\n";
- html += " statusElem.className = data.relays[i] ? 'relay-status on' : 'relay-status off';\n";
- html += " }\n";
- html += "\n";
- html += " for (let i = 0; i < 8; i++) {\n";
- html += " const statusElem = document.getElementById('di-status-' + i);\n";
- html += " statusElem.textContent = data.inputs[i] ? 'ON' : 'OFF';\n";
- html += " statusElem.className = data.inputs[i] ? 'di-status on' : 'di-status off';\n";
- html += " }\n";
- html += "\n";
- html += " document.getElementById('readCount').textContent = data.stats.reads;\n";
- html += " document.getElementById('writeCount').textContent = data.stats.writes;\n";
- html += " document.getElementById('errorCount').textContent = data.stats.errors;\n";
- html += " document.getElementById('wifiStatus').textContent = data.stats.wifi ? 'OK' : 'N/A';\n";
- html += " })\n";
- html += " .catch(err => console.error('Error fetching status:', err));\n";
- html += " }\n";
- html += "\n";
- html += " function toggleRelay(index) {\n";
- html += " fetch('/api/relays', {\n";
- html += " method: 'POST',\n";
- html += " headers: {\n";
- html += " 'Content-Type': 'application/json'\n";
- html += " },\n";
- html += " body: JSON.stringify({\n";
- html += " relay: index,\n";
- html += " state: 1\n";
- html += " })\n";
- html += " })\n";
- html += " .then(response => response.json())\n";
- html += " .then(data => {\n";
- html += " updateStatus();\n";
- html += " })\n";
- html += " .catch(err => console.error('Error toggling relay:', err));\n";
- html += " }\n";
- html += "\n";
- html += " function updateSlaveId() {\n";
- html += " const slaveId = document.getElementById('slaveId').value;\n";
- html += " if (slaveId < 1 || slaveId > 247) {\n";
- html += " alert('Please enter a valid Slave ID (1-247)');\n";
- html += " return;\n";
- html += " }\n";
- html += "\n";
- html += " fetch('/api/config', {\n";
- html += " method: 'POST',\n";
- html += " headers: {\n";
- html += " 'Content-Type': 'application/json'\n";
- html += " },\n";
- html += " body: JSON.stringify({\n";
- html += " slaveId: parseInt(slaveId)\n";
- html += " })\n";
- html += " })\n";
- html += " .then(response => response.json())\n";
- html += " .then(data => {\n";
- html += " if (data.success) {\n";
- html += " alert('Slave ID updated successfully');\n";
- html += " updateStatus();\n";
- html += " } else {\n";
- html += " alert('Error updating Slave ID');\n";
- html += " }\n";
- html += " })\n";
- html += " .catch(err => console.error('Error updating config:', err));\n";
- html += " }\n";
- html += "\n";
- html += " window.addEventListener('load', initializeUI);\n";
- html += " </script>\n";
- html += "</body>\n";
- html += "</html>\n";
- return html;
- }
- /****** HANDLE WEB API REQUESTS *****/
- void handleWebAPI(void)
- {
- if (webServer.method() == HTTP_GET) {
- /* Return current relay and DI status as JSON */
- 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 on POST request */
- if (webServer.hasArg("plain")) {
- String body = webServer.arg("plain");
- 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 REQUESTS *****/
- void handleWebConfig(void)
- {
- if (webServer.hasArg("plain")) {
- String body = webServer.arg("plain");
- /* Parse new Slave ID from JSON request */
- 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 FOR DIGITAL INPUTS *****/
- void pollModbusSlave(void)
- {
- unsigned long currentTime = millis();
- if (currentTime - lastDIPollTime >= DI_POLL_INTERVAL) {
- modbusReadDigitalInputs();
- lastDIPollTime = currentTime;
- }
- }
- /****** SYSTEM SETUP - INITIALIZE ALL SUBSYSTEMS *****/
- 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 for persistent Slave ID storage */
- initEEPROM();
- Serial.printf("Loaded Slave ID from EEPROM: %d\n", slaveID);
- /* Initialize 7-inch LCD touch display with LVGL */
- Serial.println("Initializing display...");
- initializeDisplay();
- Serial.println("Display initialized");
- /* Initialize Modbus RTU communication on UART1 */
- Serial.println("Initializing Modbus RTU...");
- initializeModbus();
- /* Initialize WiFi in STA mode with static IP */
- Serial.println("Initializing WiFi...");
- initializeWiFi();
- /* Initialize web server for remote monitoring */
- Serial.println("Initializing Web Server...");
- initializeWebServer();
- Serial.println("========================================");
- Serial.println("System Ready");
- Serial.println("========================================");
- }
- /****** MAIN EVENT LOOP - CONTINUOUS OPERATION *****/
- void loop(void)
- {
- /* Handle incoming web server requests */
- webServer.handleClient();
- /* Poll Modbus slave for digital input states every 500ms */
- pollModbusSlave();
- /* Update LVGL display and process touch events */
- lv_timer_handler();
- /* Attempt WiFi reconnection if disconnected */
- 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