{"id":194284,"date":"2026-04-23T14:01:47","date_gmt":"2026-04-23T14:01:47","guid":{"rendered":"https:\/\/randomnerdtutorials.com\/?p=194284"},"modified":"2026-04-23T14:01:49","modified_gmt":"2026-04-23T14:01:49","slug":"esp32-web-server-timer-schedule-arduino","status":"publish","type":"post","link":"https:\/\/randomnerdtutorials.com\/esp32-web-server-timer-schedule-arduino\/","title":{"rendered":"ESP32 Web Server: Set Timer Schedule (Arduino IDE)"},"content":{"rendered":"\n<p>In this project, we&#8217;ll build a web server with the ESP32 that allows you to control an LED by scheduling a timer with an ON\/OFF action for any period duration (seconds, minutes, or hours). The web page also has two buttons that you can use to turn the LED on or off immediately. The ESP32 will be programmed using Arduino IDE.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" fetchpriority=\"high\" decoding=\"async\" width=\"1200\" height=\"675\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?resize=1200%2C675&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Set Timer Schedule Arduino IDE\" class=\"wp-image-210740\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?w=1920&amp;quality=100&amp;strip=all&amp;ssl=1 1920w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?resize=300%2C169&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?resize=1024%2C576&amp;quality=100&amp;strip=all&amp;ssl=1 1024w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?resize=768%2C432&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?resize=1536%2C864&amp;quality=100&amp;strip=all&amp;ssl=1 1536w\" sizes=\"(max-width: 1200px) 100vw, 1200px\" \/><\/figure><\/div>\n\n\n<p><strong>You might like reading:<\/strong>&nbsp;<a href=\"https:\/\/randomnerdtutorials.com\/esp32-web-server-beginners-guide\/\">Building an ESP32 Web Server: The Complete Guide for Beginners (Arduino IDE)<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Project Overview<\/h2>\n\n\n\n<p>The following image shows the web page you\u2019ll build for this project.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"896\" height=\"1024\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core-Project-Overview.png?resize=896%2C1024&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Set Timer Schedule Arduino IDE Core Project Overview\" class=\"wp-image-194286\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core-Project-Overview.png?resize=896%2C1024&amp;quality=100&amp;strip=all&amp;ssl=1 896w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core-Project-Overview.png?resize=263%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 263w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core-Project-Overview.png?resize=768%2C877&amp;quality=100&amp;strip=all&amp;ssl=1 768w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core-Project-Overview.png?w=962&amp;quality=100&amp;strip=all&amp;ssl=1 962w\" sizes=\"(max-width: 896px) 100vw, 896px\" \/><\/figure><\/div>\n\n\n<p><strong>Here are the key features of this web server:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Current LED state text label: &#8220;GPIO is ON&#8221; \/ &#8220;GPIO is OFF&#8221;<\/li>\n\n\n\n<li>Instant LED control: TURN ON \/ TURN OFF buttons<\/li>\n\n\n\n<li>Set Timer HTML form: you can set the action (TURN ON\/OFF), time unit, and timer duration<\/li>\n\n\n\n<li>Timer status and remaining time to perform the selected action<\/li>\n\n\n\n<li>Cancel timer button<\/li>\n<\/ul>\n\n\n\n<p>For simplicity, we&#8217;ll save the HTML and CSS to build the web page in a variable on the Arduino sketch, but you could use the <a href=\"https:\/\/randomnerdtutorials.com\/esp32-web-server-littlefs\/\">LittleFS Filesystem<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>Before following this guide, you need to install the ESP32 Core add-on in your Arduino IDE. Follow the next guide to install it, if you haven\u2019t already:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/installing-esp32-arduino-ide-2-0\/\" title=\"\">Installing ESP32 Board in Arduino IDE 2<\/a><\/li>\n<\/ul>\n\n\n\n<p>You will also need an&nbsp;<a href=\"https:\/\/makeradvisor.com\/tools\/esp32-dev-board-wi-fi-bluetooth\/\" target=\"_blank\" rel=\"noreferrer noopener\">ESP32 development board<\/a>&nbsp;model of your choice.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Install Required Libraries in Arduino IDE<\/h3>\n\n\n\n<p>We&#8217;ll build the web server using the following libraries:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/ESP32Async\/ESPAsyncWebServer\" target=\"_blank\" rel=\"noopener\" title=\"\">ESPAsyncWebServer by ESP32Async&nbsp;<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/ESP32Async\/AsyncTCP\" target=\"_blank\" rel=\"noopener\" title=\"\">AsyncTCP by ESP32Async<\/a><\/li>\n<\/ul>\n\n\n\n<p>You can install these libraries in the Arduino Library Manager. Open the Library Manager by clicking the Library icon in the left sidebar.<\/p>\n\n\n\n<p>Search for <span class=\"rnthl rntliteral\">ESPAsyncWebServer<\/span> and install the <strong>ESPAsyncWebServer by ESP32Async<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"666\" height=\"586\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/Install-ESPAsyncWebServer-Library-ArduinoIDE-2-f.png?resize=666%2C586&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Installing ESPAsyncWebServer ESP32 Arduino IDE\" class=\"wp-image-167890\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/Install-ESPAsyncWebServer-Library-ArduinoIDE-2-f.png?w=666&amp;quality=100&amp;strip=all&amp;ssl=1 666w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/Install-ESPAsyncWebServer-Library-ArduinoIDE-2-f.png?resize=300%2C264&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 666px) 100vw, 666px\" \/><\/figure><\/div>\n\n\n<p>Then, install the AsyncTCP library. Search for <span class=\"rnthl rntliteral\">AsyncTCP<\/span> and install the <strong>AsyncTCP by ESP32Async<\/strong>.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"722\" height=\"586\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/Install-AsyncTCP-Library-ArduinoIDE.png?resize=722%2C586&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Installing AsyncTCP ESP32 Arduino IDE\" class=\"wp-image-167886\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/Install-AsyncTCP-Library-ArduinoIDE.png?w=722&amp;quality=100&amp;strip=all&amp;ssl=1 722w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/06\/Install-AsyncTCP-Library-ArduinoIDE.png?resize=300%2C243&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 722px) 100vw, 722px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">ESP32 Web Server: Timer Schedule &#8211; Arduino Sketch<\/h2>\n\n\n\n<p>The following code creates a web server that serves a web page that lets you schedule a timer to turn an LED ON\/OFF for a selected time duration.<\/p>\n\n\n<pre style=\"max-height: 40em; margin-bottom: 20px;\"><code class=\"language-c\">\/*********\n  Rui Santos &amp; Sara Santos - Random Nerd Tutorials\n  Complete project details at https:\/\/RandomNerdTutorials.com\/esp32-web-server-timer-schedule-arduino\/\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.\n  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n*********\/\n#include &lt;Arduino.h&gt;\n#include &lt;WiFi.h&gt;\n#include &lt;AsyncTCP.h&gt;\n#include &lt;ESPAsyncWebServer.h&gt;\n#include &lt;freertos\/timers.h&gt;\n\n\/\/ REPLACE WITH YOUR NETWORK CREDENTIALS\nconst char* ssid = &quot;REPLACE_WITH_YOUR_SSID&quot;;\nconst char* password = &quot;REPLACE_WITH_YOUR_PASSWORD&quot;;\n\n\/\/ LED connected to GPIO 5\nconst int ledPin = 5;\n\n\/\/ Global variables\nAsyncWebServer server(80);\n\nTimerHandle_t gpioTimer = NULL;\n\/\/ Timer action: 1 = ON, 0 = OFF\nint targetAction = -1;\n\nTickType_t timerStartTick = 0;\nuint64_t totalDurationTicks = 0;\n\n\/\/ HTML Web Page\nconst char index_html[] PROGMEM = R&quot;rawliteral(\n&lt;!DOCTYPE HTML&gt;\n&lt;html&gt;\n&lt;head&gt;\n  &lt;title&gt;ESP32 Web Server - Timer Schedule&lt;\/title&gt;\n  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;\n  &lt;style&gt;\n    body { font-family: Arial, sans-serif; text-align: center; margin: 0; padding: 20px; background: #f4f4f4; }\n    h1 { color: #333; }\n    .card { max-width: 440px; margin: 20px auto; padding: 25px; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }\n    button { width: 100%; padding: 14px; margin: 8px 0; font-size: 18px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; }\n    .onButton { background: #28a745; color: white; }\n    .offButton { background: #999999; color: white; }\n    button:hover { opacity: 0.95; }\n    .state { font-size: 22px; font-weight: bold; margin: 15px 0; }\n    .ledOn { color: #28a745; }\n    .ledOff { color: #999999; }\n    label { display: block; margin: 12px 0 6px; font-weight: bold; text-align: left; }\n    select, input[type=&quot;number&quot;] { width: 100%; padding: 12px; margin: 8px 0; font-size: 16px; border-radius: 6px; border: 1px solid #ccc; box-sizing: border-box;}\n    .startButton { background:#1a73e8; color:white; }\n    #cancelButton { display:none; background:#dc3545; color:white; padding:10px 20px; border:none; border-radius:6px; cursor:pointer; margin-top:10px; }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;h1&gt;ESP32 - Timer Schedule&lt;\/h1&gt;\n  &lt;div class=&quot;card&quot;&gt;\n    &lt;div class=&quot;state&quot;&gt;GPIO is &lt;span id=&quot;ledState&quot;&gt;Loading...&lt;\/span&gt;&lt;\/div&gt;\n    &lt;button class=&quot;onButton&quot; onclick=&quot;controlLED(1)&quot;&gt;TURN ON&lt;\/button&gt;\n    &lt;button class=&quot;offButton&quot; onclick=&quot;controlLED(0)&quot;&gt;TURN OFF&lt;\/button&gt;\n  &lt;\/div&gt;\n  &lt;div class=&quot;card&quot;&gt;\n    &lt;h2&gt;Set Timer&lt;\/h2&gt;\n    &lt;form id=&quot;timerForm&quot; action=&quot;\/set-timer&quot; method=&quot;POST&quot;&gt;\n      &lt;label for=&quot;action&quot;&gt;Action&lt;\/label&gt;\n      &lt;select name=&quot;action&quot; id=&quot;action&quot;&gt;\n        &lt;option value=&quot;&quot; disabled selected hidden&gt;--- Select Action ---&lt;\/option&gt;\n        &lt;option value=&quot;1&quot;&gt;TURN ON (HIGH)&lt;\/option&gt;\n        &lt;option value=&quot;0&quot;&gt;TURN OFF(LOW)&lt;\/option&gt;\n      &lt;\/select&gt;\n      &lt;label for=&quot;unit&quot;&gt;Time Unit&lt;\/label&gt;\n      &lt;select name=&quot;unit&quot; id=&quot;unit&quot;&gt;\n        &lt;option value=&quot;s&quot;&gt;Seconds&lt;\/option&gt;\n        &lt;option value=&quot;m&quot;&gt;Minutes&lt;\/option&gt;\n        &lt;option value=&quot;h&quot;&gt;Hours&lt;\/option&gt;\n      &lt;\/select&gt;\n      &lt;label for=&quot;duration&quot;&gt;Duration&lt;\/label&gt;\n      &lt;input type=&quot;number&quot; name=&quot;duration&quot; id=&quot;duration&quot; min=&quot;1&quot; value=&quot;30&quot; required&gt;\n      &lt;button type=&quot;submit&quot; class=&quot;startButton&quot;&gt;START TIMER&lt;\/button&gt;\n    &lt;\/form&gt;\n    &lt;div style=&quot;margin-top:20px; padding:15px; background:#f8f9fa; border-radius:8px;&quot;&gt;\n      &lt;strong&gt;Timer Status:&lt;\/strong&gt; &lt;span id=&quot;status&quot;&gt;No active timers&lt;\/span&gt;&lt;br&gt;&lt;br&gt;\n      &lt;span id=&quot;remaining&quot; style=&quot;font-size:18px;&quot;&gt;&lt;\/span&gt;&lt;br&gt;\n      &lt;button id=&quot;cancelButton&quot; onclick=&quot;cancelTimer()&quot;&gt;\n        CANCEL TIMER\n      &lt;\/button&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n\n  &lt;script&gt;\n    \/\/ Update LED State\n    function updateLEDState() {\n      fetch('\/led-state')\n        .then(r =&gt; r.json())\n        .then(data =&gt; {\n          const stateEl = document.getElementById('ledState');\n          if (data.state === 1) {\n            stateEl.innerHTML = '&lt;span class=&quot;ledOn&quot;&gt;ON&lt;\/span&gt;';\n          } else {\n            stateEl.innerHTML = '&lt;span class=&quot;ledOff&quot;&gt;OFF&lt;\/span&gt;';\n          }\n        })\n        .catch(() =&gt; {\n          document.getElementById('ledState').innerHTML = 'Error';\n        });\n    }\n    \/\/ Control LED\n    function controlLED(state) {\n      fetch('\/control?state=' + state)\n        .then(() =&gt; updateLEDState());\n    }\n\n    \/\/ Timer functions\n    let countdownInterval = null;\n    let remainingSeconds = 0;\n    \/\/ Format time to hours, minutes and seconds    \n    function formatTime(seconds) {\n      if (seconds &lt;= 0) return &quot;0s&quot;;\n      let h = Math.floor(seconds \/ 3600);\n      let m = Math.floor((seconds % 3600) \/ 60);\n      let s = seconds % 60;\n      if (h &gt; 0) return h + &quot;h &quot; + m + &quot;m &quot; + s + &quot;s&quot;;\n      if (m &gt; 0) return m + &quot;m &quot; + s + &quot;s&quot;;\n      return s + &quot;s&quot;;\n    }\n    \/\/ Check the remaining time for the timer to end\n    function updateTimerStatus() {\n      fetch('\/timer-status')\n        .then(r =&gt; r.json())\n        .then(data =&gt; {\n          document.getElementById('status').innerText = data.status;\n\n          const cancelButton = document.getElementById('cancelButton');\n          const remainingEl = document.getElementById('remaining');\n\n          if (data.active) {\n            remainingSeconds = data.remaining_seconds;\n            cancelButton.style.display = 'inline-block';\n\n            if (!countdownInterval) {\n              countdownInterval = setInterval(() =&gt; {\n                if (remainingSeconds &gt; 0) {\n                  remainingSeconds--;\n                  remainingEl.innerText = &quot;Remaining: &quot; + formatTime(remainingSeconds);\n                } else {\n                  clearInterval(countdownInterval);\n                  countdownInterval = null;\n                }\n              }, 1000);\n            }\n            remainingEl.innerText = &quot;Remaining: &quot; + formatTime(remainingSeconds);\n          } else {\n            cancelButton.style.display = 'none';\n            remainingEl.innerText = &quot;&quot;;\n            if (countdownInterval) {\n              clearInterval(countdownInterval);\n              countdownInterval = null;\n            }\n          }\n        });\n    }\n    \/\/ Cancel timer button\n    function cancelTimer() {\n      if (confirm(&quot;Cancel the current timer?&quot;)) {\n        fetch('\/cancel-timer', { method: 'POST' })\n          .then(() =&gt; updateTimerStatus());\n      }\n    }\n\n    \/\/ Update every 10 seconds\n    setInterval(() =&gt; {\n      updateLEDState();\n      updateTimerStatus();\n    }, 10000);\n    window.onload = () =&gt; {\n      updateLEDState();\n      updateTimerStatus();\n    };\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n)rawliteral&quot;;\n\n\/\/ Timer Callback - Turns the LED on or off depending on the action selected\nvoid IRAM_ATTR timerCallback(TimerHandle_t xTimer) {\n  if (targetAction != -1) {\n    digitalWrite(ledPin, targetAction ? HIGH : LOW);\n    Serial.printf(&quot;Timer finished: GPIO %d set to %s\\n&quot;, ledPin, targetAction ? &quot;HIGH (ON)&quot; : &quot;LOW (OFF)&quot;);\n  }\n  targetAction = -1;\n  timerStartTick = 0;\n  totalDurationTicks = 0;\n}\n\nvoid setup() {\n  Serial.begin(115200);\n  delay(1000);\n\n  \/\/ Define ledPin as an OUTPUT and initialize it off (LOW)\n  pinMode(ledPin, OUTPUT);\n  digitalWrite(ledPin, LOW);\n\n  \/\/ Create a timer and assign the timerCallback function\n  gpioTimer = xTimerCreate(&quot;GPIO_Timer&quot;, pdMS_TO_TICKS(1000), pdFALSE, 0, timerCallback);\n\n  \/\/ Start the Wi-Fi connection\n  WiFi.begin(ssid, password);\n  Serial.print(&quot;Connecting to Wi-Fi&quot;);\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    Serial.print(&quot;.&quot;);\n  }\n  Serial.println(&quot;\\nWi-Fi Connected!&quot;);\n  \/\/ Print the ESP32 IP Address\n  Serial.print(&quot;Access ESP32 IP Address: http:\/\/&quot;);\n  Serial.println(WiFi.localIP());\n\n  \/\/ Root URL handler\n  server.on(&quot;\/&quot;, HTTP_GET, [](AsyncWebServerRequest *request) {\n    request-&gt;send_P(200, &quot;text\/html&quot;, index_html);\n  });\n\n  \/\/ Instant LED control\n  server.on(&quot;\/control&quot;, HTTP_GET, [](AsyncWebServerRequest *request) {\n    if (request-&gt;hasParam(&quot;state&quot;)) {\n      int state = request-&gt;getParam(&quot;state&quot;)-&gt;value().toInt();\n      digitalWrite(ledPin, state ? HIGH : LOW);\n      Serial.printf(&quot;LED set to %s\\n&quot;, state ? &quot;ON&quot; : &quot;OFF&quot;);\n    }\n    request-&gt;send(200, &quot;text\/plain&quot;, &quot;OK&quot;);\n  });\n\n  \/\/ Get current LED state\n  server.on(&quot;\/led-state&quot;, HTTP_GET, [](AsyncWebServerRequest *request) {\n    int state = digitalRead(ledPin);\n    request-&gt;send(200, &quot;application\/json&quot;, &quot;{\\&quot;state\\&quot;:&quot; + String(state) + &quot;}&quot;);\n  });\n\n  \/\/ Set the timer duration to perform the action selected\n  server.on(&quot;\/set-timer&quot;, HTTP_POST, [](AsyncWebServerRequest *request) {\n    if (request-&gt;hasParam(&quot;action&quot;, true) &amp;&amp; request-&gt;hasParam(&quot;duration&quot;, true) &amp;&amp; request-&gt;hasParam(&quot;unit&quot;, true)) {\n\n      targetAction = request-&gt;getParam(&quot;action&quot;, true)-&gt;value().toInt();\n      int dur = request-&gt;getParam(&quot;duration&quot;, true)-&gt;value().toInt();\n      String unit = request-&gt;getParam(&quot;unit&quot;, true)-&gt;value();\n\n      uint64_t durationSeconds = 0;\n      if (unit == &quot;s&quot;)      durationSeconds = (uint64_t)dur;\n      else if (unit == &quot;m&quot;) durationSeconds = (uint64_t)dur * 60ULL;\n      else if (unit == &quot;h&quot;) durationSeconds = (uint64_t)dur * 3600ULL;\n\n      if (durationSeconds &gt; 0) {\n        if (xTimerIsTimerActive(gpioTimer)) xTimerStop(gpioTimer, 0);\n\n        uint64_t tempTicks = durationSeconds * (uint64_t)configTICK_RATE_HZ;\n        TickType_t timerTicks = (tempTicks &gt; 0xFFFFFFFFULL) ? 0xFFFFFFFFULL : (TickType_t)tempTicks;\n\n        timerStartTick = xTaskGetTickCount();\n        totalDurationTicks = tempTicks;\n\n        xTimerChangePeriod(gpioTimer, timerTicks, 0);\n        xTimerStart(gpioTimer, 0);\n\n        Serial.printf(&quot;Timer scheduled: GPIO %d will be set to %s in %llu seconds\\n&quot;, ledPin,\n                      targetAction ? &quot;ON&quot; : &quot;OFF&quot;, durationSeconds);\n      }\n    }\n    request-&gt;redirect(&quot;\/&quot;);\n  });\n\n  \/\/ Cancel Timer\n  server.on(&quot;\/cancel-timer&quot;, HTTP_POST, [](AsyncWebServerRequest *request) {\n    \/\/ Stop the active timer\n    if (xTimerIsTimerActive(gpioTimer)) {\n      xTimerStop(gpioTimer, 0);\n      Serial.println(&quot;Timer cancelled&quot;);\n    }\n    targetAction = -1;\n    timerStartTick = 0; \n    totalDurationTicks = 0;\n    request-&gt;send(200, &quot;text\/plain&quot;, &quot;OK&quot;);\n  });\n\n  \/\/ Timer Status - Returns the remaining time in seconds\n  server.on(&quot;\/timer-status&quot;, HTTP_GET, [](AsyncWebServerRequest *request) {\n    if (targetAction != -1 &amp;&amp; xTimerIsTimerActive(gpioTimer) &amp;&amp; totalDurationTicks &gt; 0) {\n      \/\/ Calculate how many seconds are left for the timer to end\n      TickType_t now = xTaskGetTickCount();\n      uint64_t elapsedTicks = 0;\n\n      if (now &gt;= timerStartTick) {\n        elapsedTicks = now - timerStartTick;\n      } else {\n        elapsedTicks = (0xFFFFFFFFULL - timerStartTick) + now + 1;\n      }\n\n      uint64_t remainingTicks = (totalDurationTicks &gt; elapsedTicks) ? totalDurationTicks - elapsedTicks : 0;\n      uint64_t remainingSeconds = remainingTicks \/ (uint64_t)configTICK_RATE_HZ;\n\n      \/\/ Return the amount of seconds left and action that will execute when the timer ends\n      String json = &quot;{\\&quot;status\\&quot;:\\&quot;Setting GPIO &quot;+ String(ledPin) +&quot; to &quot; + \n                    String(targetAction ? &quot;ON&quot; : &quot;OFF&quot;) + &quot;\\&quot;,&quot;;\n      json += &quot;\\&quot;active\\&quot;:true,&quot;;\n      json += &quot;\\&quot;remaining_seconds\\&quot;:&quot; + String((unsigned long)remainingSeconds) + &quot;}&quot;;\n      request-&gt;send(200, &quot;application\/json&quot;, json);\n    } else {\n      request-&gt;send(200, &quot;application\/json&quot;, &quot;{\\&quot;status\\&quot;:\\&quot;No active timers\\&quot;,\\&quot;active\\&quot;:false,\\&quot;remaining_seconds\\&quot;:0}&quot;);\n    }\n  });\n\n  server.begin();\n  Serial.println(&quot;ESP32 Web Server Timer Schedule is Ready!&quot;);\n}\n\nvoid loop() {\n  vTaskDelay(pdMS_TO_TICKS(100));\n}\n<\/code><\/pre>\n\t<p style=\"text-align:center\"><a class=\"rntwhite\" href=\"https:\/\/github.com\/RuiSantosdotme\/Random-Nerd-Tutorials\/raw\/master\/Projects\/ESP32\/ESP32_Timer_Web_Server.ino\" target=\"_blank\">View raw code<\/a><\/p>\n\n\n\n<p>You just need to insert your network credentials in the code and it will work straight away.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const char* ssid = \"REPLACE_WITH_YOUR_SSID\";\nconst char* password = \"REPLACE_WITH_YOUR_SSID\";<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">How the Code Works<\/h3>\n\n\n\n<p>Continue reading to learn how the code works or skip to the <a href=\"#demonstration\" title=\"\">Demonstration <\/a>section.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Including Libraries<\/h4>\n\n\n\n<p>First, include the required libraries to connect to Wi-Fi, create the web server, and use <a href=\"https:\/\/randomnerdtutorials.com\/esp32-freertos-software-timers-interrupts\/\">FreeRTOS timers<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>#include &lt;Arduino.h&gt;\n#include &lt;WiFi.h&gt;\n#include &lt;AsyncTCP.h&gt;\n#include &lt;ESPAsyncWebServer.h&gt;\n#include &lt;freertos\/timers.h&gt;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Network Credentials<\/h4>\n\n\n\n<p>Insert your network credentials in the following variables so that the ESP32 connects to your network.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>\/\/ REPLACE WITH YOUR NETWORK CREDENTIALS\nconst char* ssid = \"REPLACE_WITH_YOUR_SSID\";\nconst char* password = \"REPLACE_WITH_YOUR_SSID\";<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">LED Pin<\/h4>\n\n\n\n<p>Define the GPIO that will be used to control the LED. You can modify the <span class=\"rnthl rntliteral\">ledPin<\/span> variable to control <a href=\"https:\/\/randomnerdtutorials.com\/esp32-pinout-reference-gpios\/\">any other GPIO<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const int ledPin = 5;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Creating a Server Object<\/h4>\n\n\n\n<p>Create an <span class=\"rnthl rntliteral\">AsyncWebServer<\/span> object called <span class=\"rnthl rntliteral\">server<\/span> on port 80.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>AsyncWebServer server(80);<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Timers<\/h4>\n\n\n\n<p>Create auxiliary variables to run the timers.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>TimerHandle_t gpioTimer = NULL;\n\/\/ Timer action: 1 = ON, 0 = OFF\nint targetAction = -1;\n\nTickType_t timerStartTick = 0;\nuint64_t totalDurationTicks = 0;<\/code><\/pre>\n\n\n\n<p class=\"rntbox rntclgreen\">Learn more about FreeRTOS timers: <a href=\"https:\/\/randomnerdtutorials.com\/esp32-freertos-software-timers-interrupts\/\" title=\"\">ESP32 with FreeRTOS: Software Timers\/Timer Interrupts (Arduino IDE)<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Web Page<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">index_html<\/span> variable contains the text with the HTML, CSS, and JavaScript to build the web page.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>const char index_html&#091;] PROGMEM = R\"rawliteral(\n&lt;!DOCTYPE HTML&gt;\n&lt;html&gt;\n&lt;head&gt;\n  &lt;title&gt;ESP32 Web Server - Timer Schedule&lt;\/title&gt;\n  &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n  &lt;style&gt;\n    body { font-family: Arial, sans-serif; text-align: center; margin: 0; padding: 20px; background: #f4f4f4; }\n    h1 { color: #333; }\n    .card { max-width: 440px; margin: 20px auto; padding: 25px; background: white; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }\n    button { width: 100%; padding: 14px; margin: 8px 0; font-size: 18px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; }\n    .onButton { background: #28a745; color: white; }\n    .offButton { background: #999999; color: white; }\n    button:hover { opacity: 0.95; }\n    .state { font-size: 22px; font-weight: bold; margin: 15px 0; }\n    .ledOn { color: #28a745; }\n    .ledOff { color: #999999; }\n    label { display: block; margin: 12px 0 6px; font-weight: bold; text-align: left; }\n    select, input&#091;type=\"number\"] { width: 100%; padding: 12px; margin: 8px 0; font-size: 16px; border-radius: 6px; border: 1px solid #ccc; box-sizing: border-box;}\n    .startButton { background:#1a73e8; color:white; }\n    #cancelButton { display:none; background:#dc3545; color:white; padding:10px 20px; border:none; border-radius:6px; cursor:pointer; margin-top:10px; }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;h1&gt;ESP32 - Timer Schedule&lt;\/h1&gt;\n  &lt;div class=\"card\"&gt;\n    &lt;div class=\"state\"&gt;GPIO is &lt;span id=\"ledState\"&gt;Loading...&lt;\/span&gt;&lt;\/div&gt;\n    &lt;button class=\"onButton\" onclick=\"controlLED(1)\"&gt;TURN ON&lt;\/button&gt;\n    &lt;button class=\"offButton\" onclick=\"controlLED(0)\"&gt;TURN OFF&lt;\/button&gt;\n  &lt;\/div&gt;\n  &lt;div class=\"card\"&gt;\n    &lt;h2&gt;Set Timer&lt;\/h2&gt;\n    &lt;form id=\"timerForm\" action=\"\/set-timer\" method=\"POST\"&gt;\n      &lt;label for=\"action\"&gt;Action&lt;\/label&gt;\n      &lt;select name=\"action\" id=\"action\"&gt;\n        &lt;option value=\"\" disabled selected hidden&gt;--- Select Action ---&lt;\/option&gt;\n        &lt;option value=\"1\"&gt;TURN ON (HIGH)&lt;\/option&gt;\n        &lt;option value=\"0\"&gt;TURN OFF(LOW)&lt;\/option&gt;\n      &lt;\/select&gt;\n      &lt;label for=\"unit\"&gt;Time Unit&lt;\/label&gt;\n      &lt;select name=\"unit\" id=\"unit\"&gt;\n        &lt;option value=\"s\"&gt;Seconds&lt;\/option&gt;\n        &lt;option value=\"m\"&gt;Minutes&lt;\/option&gt;\n        &lt;option value=\"h\"&gt;Hours&lt;\/option&gt;\n      &lt;\/select&gt;\n      &lt;label for=\"duration\"&gt;Duration&lt;\/label&gt;\n      &lt;input type=\"number\" name=\"duration\" id=\"duration\" min=\"1\" value=\"30\" required&gt;\n      &lt;button type=\"submit\" class=\"startButton\"&gt;START TIMER&lt;\/button&gt;\n    &lt;\/form&gt;\n\n    &lt;div style=\"margin-top:20px; padding:15px; background:#f8f9fa; border-radius:8px;\"&gt;\n      &lt;strong&gt;Timer Status:&lt;\/strong&gt; &lt;span id=\"status\"&gt;No active timers&lt;\/span&gt;&lt;br&gt;&lt;br&gt;\n      &lt;span id=\"remaining\" style=\"font-size:18px;\"&gt;&lt;\/span&gt;&lt;br&gt;\n      &lt;button id=\"cancelButton\" onclick=\"cancelTimer()\"&gt;\n        CANCEL TIMER\n      &lt;\/button&gt;\n    &lt;\/div&gt;\n  &lt;\/div&gt;\n\n  &lt;script&gt;\n    \/\/ Update LED State\n    function updateLEDState() {\n      fetch('\/led-state')\n        .then(r =&gt; r.json())\n        .then(data =&gt; {\n          const stateEl = document.getElementById('ledState');\n          if (data.state === 1) {\n            stateEl.innerHTML = '&lt;span class=\"ledOn\"&gt;ON&lt;\/span&gt;';\n          } else {\n            stateEl.innerHTML = '&lt;span class=\"ledOff\"&gt;OFF&lt;\/span&gt;';\n          }\n        })\n        .catch(() =&gt; {\n          document.getElementById('ledState').innerHTML = 'Error';\n        });\n    }\n    \/\/ Control LED\n    function controlLED(state) {\n      fetch('\/control?state=' + state)\n        .then(() =&gt; updateLEDState());\n    }\n\n    \/\/ Timer functions\n    let countdownInterval = null;\n    let remainingSeconds = 0;\n    \/\/ Format time to hours, minutes and seconds    \n    function formatTime(seconds) {\n      if (seconds &lt;= 0) return \"0s\";\n      let h = Math.floor(seconds \/ 3600);\n      let m = Math.floor((seconds % 3600) \/ 60);\n      let s = seconds % 60;\n      if (h &gt; 0) return h + \"h \" + m + \"m \" + s + \"s\";\n      if (m &gt; 0) return m + \"m \" + s + \"s\";\n      return s + \"s\";\n    }\n    \/\/ Check the remaining time for the timer to end\n    function updateTimerStatus() {\n      fetch('\/timer-status')\n        .then(r =&gt; r.json())\n        .then(data =&gt; {\n          document.getElementById('status').innerText = data.status;\n\n          const cancelButton = document.getElementById('cancelButton');\n          const remainingEl = document.getElementById('remaining');\n\n          if (data.active) {\n            remainingSeconds = data.remaining_seconds;\n            cancelButton.style.display = 'inline-block';\n\n            if (!countdownInterval) {\n              countdownInterval = setInterval(() =&gt; {\n                if (remainingSeconds &gt; 0) {\n                  remainingSeconds--;\n                  remainingEl.innerText = \"Remaining: \" + formatTime(remainingSeconds);\n                } else {\n                  clearInterval(countdownInterval);\n                  countdownInterval = null;\n                }\n              }, 1000);\n            }\n            remainingEl.innerText = \"Remaining: \" + formatTime(remainingSeconds);\n          } else {\n            cancelButton.style.display = 'none';\n            remainingEl.innerText = \"\";\n            if (countdownInterval) {\n              clearInterval(countdownInterval);\n              countdownInterval = null;\n            }\n          }\n        });\n    }\n    \/\/ Cancel timer button\n    function cancelTimer() {\n      if (confirm(\"Cancel the current timer?\")) {\n        fetch('\/cancel-timer', { method: 'POST' })\n          .then(() =&gt; updateTimerStatus());\n      }\n    }\n\n    \/\/ Update every 10 seconds\n    setInterval(() =&gt; {\n      updateLEDState();\n      updateTimerStatus();\n    }, 10000);\n    window.onload = () =&gt; {\n      updateLEDState();\n      updateTimerStatus();\n    };\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n)rawliteral\";<\/code><\/pre>\n\n\n\n<p>In the web page, we create two buttons that make requests on the following URL paths to control the LED on\/off:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>TURN ON: <span class=\"rnthl rntliteral\">\/control?state=1<\/span><\/li>\n\n\n\n<li>TURN OFF: <span class=\"rnthl rntliteral\">\/control?state=0<\/span><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code language-html\"><code>&lt;button class=\"onButton\" onclick=\"controlLED(1)\"&gt;TURN ON&lt;\/button&gt;\n&lt;button class=\"offButton\" onclick=\"controlLED(0)\"&gt;TURN OFF&lt;\/button&gt;<\/code><\/pre>\n\n\n\n<p>The other main section of the web page is the HTML web form where you can select the timer action, unit and duration. It also shows the current state of the timer, time remaining and a button to cancel the timer.<\/p>\n\n\n\n<pre class=\"wp-block-code language-html\"><code>&lt;div class=\"card\"&gt;\n  &lt;h2&gt;Set Timer&lt;\/h2&gt;\n  &lt;form id=\"timerForm\" action=\"\/set-timer\" method=\"POST\"&gt;\n    &lt;label for=\"action\"&gt;Action&lt;\/label&gt;\n    &lt;select name=\"action\" id=\"action\"&gt;\n      &lt;option value=\"\" disabled selected hidden&gt;--- Select Action ---&lt;\/option&gt;\n      &lt;option value=\"1\"&gt;TURN ON (HIGH)&lt;\/option&gt;\n      &lt;option value=\"0\"&gt;TURN OFF(LOW)&lt;\/option&gt;\n    &lt;\/select&gt;\n    &lt;label for=\"unit\"&gt;Time Unit&lt;\/label&gt;\n    &lt;select name=\"unit\" id=\"unit\"&gt;\n      &lt;option value=\"s\"&gt;Seconds&lt;\/option&gt;\n      &lt;option value=\"m\"&gt;Minutes&lt;\/option&gt;\n      &lt;option value=\"h\"&gt;Hours&lt;\/option&gt;\n    &lt;\/select&gt;\n    &lt;label for=\"duration\"&gt;Duration&lt;\/label&gt;\n    &lt;input type=\"number\" name=\"duration\" id=\"duration\" min=\"1\" value=\"30\" required&gt;\n    &lt;button type=\"submit\" class=\"startButton\"&gt;START TIMER&lt;\/button&gt;\n  &lt;\/form&gt;\n  &lt;div style=\"margin-top:20px; padding:15px; background:#f8f9fa; border-radius:8px;\"&gt;\n    &lt;strong&gt;Timer Status:&lt;\/strong&gt; &lt;span id=\"status\"&gt;No active timers&lt;\/span&gt;&lt;br&gt;&lt;br&gt;\n    &lt;span id=\"remaining\" style=\"font-size:18px;\"&gt;&lt;\/span&gt;&lt;br&gt;\n    &lt;button id=\"cancelButton\" onclick=\"cancelTimer()\"&gt;\n      CANCEL TIMER\n    &lt;\/button&gt;\n  &lt;\/div&gt;\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Timer Callback Function<\/h4>\n\n\n\n<p>The <span class=\"rnthl rntliteral\">timerCallback()<\/span> function is the FreeRTOS one-shot timer handler that, when it expired, sets the GPIO ON\/OFF based on the previously selected action in the web server.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>void IRAM_ATTR timerCallback(TimerHandle_t xTimer) {\n  if (targetAction != -1) {\n    digitalWrite(ledPin, targetAction ? HIGH : LOW);\n    Serial.printf(\"Timer finished: GPIO %d set to %s\\n\", ledPin, targetAction ? \"HIGH (ON)\" : \"LOW (OFF)\");\n  }\n  targetAction = -1;\n  timerStartTick = 0;\n  totalDurationTicks = 0;\n}<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">setup()<\/h4>\n\n\n\n<p>In the <span class=\"rnthl rntliteral\">setup()<\/span>, we start by initializing the Serial Monitor and preparing the GPIO.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>Serial.begin(115200);\ndelay(1000);\n\npinMode(ledPin, OUTPUT);\ndigitalWrite(ledPin, LOW);<\/code><\/pre>\n\n\n\n<p>Then, create the <span class=\"rnthl rntliteral\">gpioTimer<\/span> and assign the <span class=\"rnthl rntliteral\">timerCallback<\/span> function described earlier that runs when the timer expires.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>gpioTimer = xTimerCreate(\"GPIO_Timer\", pdMS_TO_TICKS(1000), pdFALSE, 0, timerCallback);<\/code><\/pre>\n\n\n\n<p>Start the Wi-Fi connection and print the ESP32 IP address in the Serial Monitor.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>WiFi.begin(ssid, password);\nSerial.print(\"Connecting to Wi-Fi\");\nwhile (WiFi.status() != WL_CONNECTED) {\n  delay(500);\n  Serial.print(\".\");\n}\nSerial.println(\"\\nWi-Fi Connected!\");\n\/\/ Print the ESP32 IP Address\nSerial.print(\"Access ESP32 IP Address: http:\/\/\");\nSerial.println(WiFi.localIP());<\/code><\/pre>\n\n\n\n<p><strong>Root URL \/<\/strong><\/p>\n\n\n\n<p>Then, handle the web server. When you receive a request on the root (<span class=\"rnthl rntliteral\">\/<\/span>) URL (this happens when you access the ESP IP address), send the HTML text stored in <span class=\"rnthl rntliteral\">PROGMEM<\/span> to display the web page:<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/\", HTTP_GET, &#091;](AsyncWebServerRequest *request) {\n  request-&gt;send_P(200, \"text\/html\", index_html);\n});<\/code><\/pre>\n\n\n\n<p><strong>LED Control Route \/control<\/strong><\/p>\n\n\n\n<p>This GET route controls the LED ON\/OFF based on the <span class=\"rnthl rntliteral\">state<\/span> parameter (1 = HIGH and 0 = LOW).<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/control\", HTTP_GET, &#091;](AsyncWebServerRequest *request) {\n  if (request-&gt;hasParam(\"state\")) {\n    int state = request-&gt;getParam(\"state\")-&gt;value().toInt();\n    digitalWrite(ledPin, state ? HIGH : LOW);\n    Serial.printf(\"LED set to %s\\n\", state ? \"ON\" : \"OFF\");\n  }\n  request-&gt;send(200, \"text\/plain\", \"OK\");\n});<\/code><\/pre>\n\n\n\n<p><strong>GET LED State Route \/led-state<\/strong><\/p>\n\n\n\n<p>This GET route reads the current state of the LED and returns it as a simple JSON object containing the <span class=\"rnthl rntliteral\">state<\/span> value.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/led-state\", HTTP_GET, &#091;](AsyncWebServerRequest *request) {\n  int state = digitalRead(ledPin);\n  request-&gt;send(200, \"application\/json\", \"{\\\"state\\\":\" + String(state) + \"}\");\n});<\/code><\/pre>\n\n\n\n<p><strong>Set Timer Route \/set-timer<\/strong><\/p>\n\n\n\n<p>This HTTP POST route receives the data from the web page with the selected action, duration, and time unit. It converts the duration into seconds and then FreeRTOS ticks to start the one-shot timer. When the timer expires, the action runs.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/set-timer\", HTTP_POST, &#091;](AsyncWebServerRequest *request) {\n  if (request-&gt;hasParam(\"action\", true) &amp;&amp; request-&gt;hasParam(\"duration\", true) &amp;&amp; request-&gt;hasParam(\"unit\", true)) {\n\n    targetAction = request-&gt;getParam(\"action\", true)-&gt;value().toInt();\n    int dur = request-&gt;getParam(\"duration\", true)-&gt;value().toInt();\n    String unit = request-&gt;getParam(\"unit\", true)-&gt;value();\n\n    uint64_t durationSeconds = 0;\n    if (unit == \"s\")      durationSeconds = (uint64_t)dur;\n    else if (unit == \"m\") durationSeconds = (uint64_t)dur * 60ULL;\n    else if (unit == \"h\") durationSeconds = (uint64_t)dur * 3600ULL;\n\n    if (durationSeconds &gt; 0) {\n      if (xTimerIsTimerActive(gpioTimer)) xTimerStop(gpioTimer, 0);\n\n      uint64_t tempTicks = durationSeconds * (uint64_t)configTICK_RATE_HZ;\n      TickType_t timerTicks = (tempTicks &gt; 0xFFFFFFFFULL) ? 0xFFFFFFFFULL : (TickType_t)tempTicks;\n\n      timerStartTick = xTaskGetTickCount();\n      totalDurationTicks = tempTicks;\n\n      xTimerChangePeriod(gpioTimer, timerTicks, 0);\n      xTimerStart(gpioTimer, 0);\n\n      Serial.printf(\"Timer scheduled: GPIO %d will be set to %s in %llu seconds\\n\", ledPin,\n                    targetAction ? \"ON\" : \"OFF\", durationSeconds);\n    }\n  }\n  request-&gt;redirect(\"\/\");\n});<\/code><\/pre>\n\n\n\n<p><strong>Cancel Timer Route \/cancel-timer<\/strong><\/p>\n\n\n\n<p>This route stops any active FreeRTOS timer and resets the timer global variables (<span class=\"rnthl rntliteral\">timerStartTick<\/span>, <span class=\"rnthl rntliteral\">totalDurationTicks<\/span>) to their idle state.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/cancel-timer\", HTTP_POST, &#091;](AsyncWebServerRequest *request) {\n  \/\/ Stop the active timer\n  if (xTimerIsTimerActive(gpioTimer)) {\n    xTimerStop(gpioTimer, 0);\n    Serial.println(\"Timer cancelled\");\n  }\n  targetAction = -1;\n  timerStartTick = 0; \n  totalDurationTicks = 0;\n  request-&gt;send(200, \"text\/plain\", \"OK\");\n});<\/code><\/pre>\n\n\n\n<p><strong>Timer Status Route \/timer-status<\/strong><\/p>\n\n\n\n<p>This route checks if a timer is running, calculates the exact remaining time in seconds, and returns a JSON object with the current status message.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.on(\"\/timer-status\", HTTP_GET, &#091;](AsyncWebServerRequest *request) {\n  if (targetAction != -1 &amp;&amp; xTimerIsTimerActive(gpioTimer) &amp;&amp; totalDurationTicks &gt; 0) {\n    \/\/ Calculate how many seconds are left for the timer to end\n    TickType_t now = xTaskGetTickCount();\n    uint64_t elapsedTicks = 0;\n\n    if (now &gt;= timerStartTick) {\n      elapsedTicks = now - timerStartTick;\n    } else {\n      elapsedTicks = (0xFFFFFFFFULL - timerStartTick) + now + 1;\n    }\n\n    uint64_t remainingTicks = (totalDurationTicks &gt; elapsedTicks) ? totalDurationTicks - elapsedTicks : 0;\n    uint64_t remainingSeconds = remainingTicks \/ (uint64_t)configTICK_RATE_HZ;\n\n    \/\/ Return the amount of seconds left and action that will execute when the timer ends\n    String json = \"{\\\"status\\\":\\\"Setting GPIO \"+ String(ledPin) +\" to \" + \n                  String(targetAction ? \"ON\" : \"OFF\") + \"\\\",\";\n    json += \"\\\"active\\\":true,\";\n    json += \"\\\"remaining_seconds\\\":\" + String((unsigned long)remainingSeconds) + \"}\";\n    request-&gt;send(200, \"application\/json\", json);\n  } else {\n    request-&gt;send(200, \"application\/json\", \"{\\\"status\\\":\\\"No active timers\\\",\\\"active\\\":false,\\\"remaining_seconds\\\":0}\");\n  }\n});<\/code><\/pre>\n\n\n\n<p><strong>Initialize the Server<\/strong><\/p>\n\n\n\n<p>Finally, call the <span class=\"rnthl rntliteral\">begin()<\/span> method on the <span class=\"rnthl rntliteral\">server<\/span> object to initialize the server.<\/p>\n\n\n\n<pre class=\"wp-block-code language-c\"><code>server.begin();<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"demonstration\">Demonstration<\/h2>\n\n\n\n<p>After inserting your network credentials, upload the code to the ESP32 board. After uploading, open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST button.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"41\" height=\"39\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2024\/02\/serial-monitor-logo-arduino-ide-2.png?resize=41%2C39&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"Open Serial Monitor Arduino 2\" class=\"wp-image-148549\" style=\"width:41px;height:auto\"\/><\/figure><\/div>\n\n\n<p>The ESP32 IP address will be printed in the Serial Monitor.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"814\" height=\"248\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-IP-Address.png?resize=814%2C248&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Set Timer Schedule Arduino IDE Serial Monitor IP Address\" class=\"wp-image-194291\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-IP-Address.png?w=814&amp;quality=100&amp;strip=all&amp;ssl=1 814w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-IP-Address.png?resize=300%2C91&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-IP-Address.png?resize=768%2C234&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 814px) 100vw, 814px\" \/><\/figure><\/div>\n\n\n<p>With an ESP32 that has an LED connected to GPIO 5, open any browser on your local network and type the ESP32 IP address. The following web page will load.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"772\" height=\"859\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Web-Page.png?resize=772%2C859&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Set Timer Schedule Web Page\" class=\"wp-image-194292\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Web-Page.png?w=772&amp;quality=100&amp;strip=all&amp;ssl=1 772w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Web-Page.png?resize=270%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 270w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Web-Page.png?resize=768%2C855&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 772px) 100vw, 772px\" \/><\/figure><\/div>\n\n\n<p>Click on the TURN ON or TURN OFF buttons to control the LED. You can also check the current LED state on the text label.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"551\" height=\"322\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Turn-LED-On-Off-Buttons.png?resize=551%2C322&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Timer Schedule Arduino IDE Turn LED On Off Buttons\" class=\"wp-image-194288\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Turn-LED-On-Off-Buttons.png?w=551&amp;quality=100&amp;strip=all&amp;ssl=1 551w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Turn-LED-On-Off-Buttons.png?resize=300%2C175&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 551px) 100vw, 551px\" \/><\/figure><\/div>\n\n\n<p>Next, you have an HTML form where you can select the action, time unit and timer duration. After selecting your desired timer settings, press the &#8220;<strong>START TIMER<\/strong>&#8221; button.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"515\" height=\"588\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Set-Timer-HTML-Web-Form.png?resize=515%2C588&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Timer Schedule Arduino IDE Set Timer HTML Web Form\" class=\"wp-image-194289\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Set-Timer-HTML-Web-Form.png?w=515&amp;quality=100&amp;strip=all&amp;ssl=1 515w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Set-Timer-HTML-Web-Form.png?resize=263%2C300&amp;quality=100&amp;strip=all&amp;ssl=1 263w\" sizes=\"(max-width: 515px) 100vw, 515px\" \/><\/figure><\/div>\n\n\n<p>In the bottom of the web page, the time status will show the current pending action and remaining time. There&#8217;s also a button to cancel the timer.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"463\" height=\"162\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Time-Remaining-Cancel-Timer-Button.png?resize=463%2C162&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Timer Schedule Arduino IDE Time Remaining Cancel Timer Button\" class=\"wp-image-194290\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Time-Remaining-Cancel-Timer-Button.png?w=463&amp;quality=100&amp;strip=all&amp;ssl=1 463w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Timer-Schedule-Arduino-IDE-Time-Remaining-Cancel-Timer-Button.png?resize=300%2C105&amp;quality=100&amp;strip=all&amp;ssl=1 300w\" sizes=\"(max-width: 463px) 100vw, 463px\" \/><\/figure><\/div>\n\n\n<p>In the Arduino IDE Serial Monitor, you can check all the actions that were performed in the web server.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"835\" height=\"410\" src=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-Demonstration.png?resize=835%2C410&#038;quality=100&#038;strip=all&#038;ssl=1\" alt=\"ESP32 Web Server Set Timer Schedule Arduino IDE Serial Monitor Demonstration\" class=\"wp-image-194287\" srcset=\"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-Demonstration.png?w=835&amp;quality=100&amp;strip=all&amp;ssl=1 835w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-Demonstration.png?resize=300%2C147&amp;quality=100&amp;strip=all&amp;ssl=1 300w, https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Serial-Monitor-Demonstration.png?resize=768%2C377&amp;quality=100&amp;strip=all&amp;ssl=1 768w\" sizes=\"(max-width: 835px) 100vw, 835px\" \/><\/figure><\/div>\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>In this tutorial, you learned how to create a simple web server to create a timer to control your ESP32 GPIOs. You can now extend this project to control more outputs. You can take a look at other tutorials:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-web-server-gauges\/\">ESP32 Web Server: Display Sensor Readings in Gauges<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/stepper-motor-esp32-websocket\/\">ESP32 Web Server: Control Stepper Motor (WebSocket)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-web-server-websocket-sliders\/\">ESP32 Web Server (WebSocket) with Multiple Sliders: Control LEDs Brightness (PWM)<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/randomnerdtutorials.com\/esp32-web-server-sent-events-sse\/\">ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically)<\/a><\/li>\n<\/ul>\n\n\n\n<p>If you would like to learn more about building web servers with the ESP32 from scratch, we recommend taking a look at our eBook dedicated to this subject:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/randomnerdtutorials.com\/build-web-servers-esp32-esp8266-ebook\/\" title=\"\">Build Web Servers with ESP32 and ESP8266 eBook<\/a><\/li>\n<\/ul>\n\n\n\n<p>Thanks for reading.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this project, we&#8217;ll build a web server with the ESP32 that allows you to control an LED by scheduling a timer with an ON\/OFF action for any period duration &#8230; <\/p>\n<p class=\"read-more-container\"><a title=\"ESP32 Web Server: Set Timer Schedule (Arduino IDE)\" class=\"read-more button\" href=\"https:\/\/randomnerdtutorials.com\/esp32-web-server-timer-schedule-arduino\/#more-194284\" aria-label=\"Read more about ESP32 Web Server: Set Timer Schedule (Arduino IDE)\">CONTINUE READING \u00bb<\/a><\/p>\n","protected":false},"author":1,"featured_media":210740,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[276,281,277,299,264],"tags":[],"class_list":["post-194284","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-esp32","category-esp32-project","category-esp32-arduino-ide","category-0-esp32","category-project"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/randomnerdtutorials.com\/wp-content\/uploads\/2026\/04\/ESP32-Web-Server-Set-Timer-Schedule-Arduino-IDE-Core.jpg?fit=1920%2C1080&quality=100&strip=all&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/194284","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/comments?post=194284"}],"version-history":[{"count":19,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/194284\/revisions"}],"predecessor-version":[{"id":210785,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/posts\/194284\/revisions\/210785"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media\/210740"}],"wp:attachment":[{"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/media?parent=194284"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/categories?post=194284"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/randomnerdtutorials.com\/wp-json\/wp\/v2\/tags?post=194284"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}