/********* ESP32C3 web server example by gllbhh for Fab Academy and related courses. based on: Rui Santos & Sara Santos - Random Nerd Tutorials Complete project details at https://RandomNerdTutorials.com/esp32-web-server-slider-pwm/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. This sketch runs a web server on the XIAO ESP32C3. It serves a web page with: - a slider that controls LED brightness on GPIO 21 via PWM - a button indicator that reflects the real-time state of the push button on GPIO 20 The browser polls the ESP32 every 100 ms to update the button indicator. *********/ // WiFi — connects the ESP32 to your wireless network #include // AsyncTCP — low-level async TCP layer required by ESPAsyncWebServer #include // ESPAsyncWebServer — serves web pages and handles HTTP requests without blocking the main loop #include // ----- Wi-Fi credentials ----- // Change these to match your network const char* ssid = "congty3bros"; const char* password = "jaguar_2934"; // ----- Pin definitions ----- const int output = 21; // LED (active HIGH) — board label D6 const int button = 20; // Push button (active LOW, wired to GND) — board label D7 // Remembers the last button reading so we only print to Serial when the state changes bool lastButtonState = HIGH; // HIGH = not pressed (pull-up holds the pin HIGH by default) // ----- Slider / PWM state ----- // The slider on the web page sends a value 0–255. // We store it as a String because the web server receives and sends text. String sliderValue = "0"; // PWM settings for the ESP32 LEDC peripheral const int freq = 5000; // PWM frequency in Hz — 5 kHz is above audible range const int resolution = 8; // Bit depth: 8 bits gives a duty cycle range of 0–255 // The URL query parameter name the slider uses, e.g. GET /slider?value=128 const char* PARAM_INPUT = "value"; // Create the web server — port 80 is the default HTTP port AsyncWebServer server(80); // ----- HTML page ----- // The page is stored in flash (PROGMEM) as a raw string literal. // %SLIDERVALUE% is a placeholder; the processor() function replaces it with the // current slider value before the page is sent to the browser. const char index_html[] PROGMEM = R"rawliteral( ESP Web Server

ESP Web Server

%SLIDERVALUE%

Button: OFF

)rawliteral"; // ----- Template processor ----- // ESPAsyncWebServer calls this function for every %PLACEHOLDER% found in the HTML. // We return the current slider value so the page loads with the correct position. String processor(const String& var){ if (var == "SLIDERVALUE"){ return sliderValue; } return String(); } void setup(){ Serial.begin(115200); // Button: INPUT_PULLUP keeps the pin HIGH when the button is open. // When pressed, the button connects the pin to GND, pulling it LOW. pinMode(button, INPUT_PULLUP); // Attach the LED pin to the LEDC PWM peripheral and start with the LED off (duty = 0). // ledcAttach(pin, frequency, resolution) — ESP32 Arduino core 3.x API. // Resolution 8 bits means duty values range from 0 (off) to 255 (full brightness). ledcAttach(output, freq, resolution); ledcWrite(output, sliderValue.toInt()); // Connect to Wi-Fi and wait until the connection is established WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi.."); } // Print the IP address — open this in a browser to see the web page Serial.println(WiFi.localIP()); // ----- HTTP routes ----- // GET / — serve the main HTML page server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ // send_P serves a PROGMEM string; processor() fills in the %SLIDERVALUE% placeholder request->send_P(200, "text/html", index_html, processor); }); // GET /slider?value=<0-255> — update PWM duty cycle from the web slider server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest *request) { String inputMessage; if (request->hasParam(PARAM_INPUT)) { inputMessage = request->getParam(PARAM_INPUT)->value(); sliderValue = inputMessage; // ledcWrite sets the duty cycle: 0 = LED off, 255 = LED full brightness ledcWrite(output, sliderValue.toInt()); } else { inputMessage = "No message sent"; } Serial.println(inputMessage); request->send(200, "text/plain", "OK"); }); // GET /buttonstate — return "1" if the button is currently pressed, "0" if not. // The browser calls this every 100 ms to update the indicator colour. server.on("/buttonstate", HTTP_GET, [](AsyncWebServerRequest *request){ bool pressed = (digitalRead(button) == LOW); // LOW means button is pressed request->send(200, "text/plain", pressed ? "1" : "0"); }); server.begin(); } void loop() { // Read the physical button and print to Serial when the state changes. // This runs independently of the web server — the ESP32 handles both at once. bool currentButtonState = digitalRead(button); if (currentButtonState != lastButtonState) { lastButtonState = currentButtonState; if (currentButtonState == LOW) { Serial.println("Button pressed"); } else { Serial.println("Button released"); } } }