0% found this document useful (0 votes)
45 views19 pages

ESP32-S2 Web Server Implementation Guide

Uploaded by

André Pedroza
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
45 views19 pages

ESP32-S2 Web Server Implementation Guide

Uploaded by

André Pedroza
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

Architecting a Robust Web-Based

Management Interface for an ESP32-S2


USB/IP Server using ESP-IDF

Section 1: System Architecture and RTOS Design

The development of a stable and feature-rich web interface on a resource-constrained


microcontroller like the ESP32-S2 requires a meticulously planned software architecture. The
foundation of this architecture is the Real-Time Operating System (RTOS), which in the case
of the Espressif IoT Development Framework (ESP-IDF) is FreeRTOS.2 A robust design must
account for the distinct operational phases of the device, clearly define concurrent tasks, and,
most critically, implement a concurrency-safe mechanism for inter-task communication to
prevent data corruption and ensure system stability.

1.1. The Two-Phase Application Lifecycle: Provisioning and Operation

The application's lifecycle is fundamentally divided into two distinct phases: an initial
Provisioning Phase and a continuous Operational Phase. This bifurcation dictates the
high-level state machine of the entire system.
●​ Provisioning Phase: When the device is powered on for the first time or has no stored
Wi-Fi credentials, it enters this phase. It activates its Wi-Fi radio in Soft Access Point
(SoftAP) mode, creating a temporary, local Wi-Fi network.4 The primary goal of this phase
is to present a simple web interface to the user, allowing them to scan for local Wi-Fi
networks and submit the credentials (SSID and password) for their home or office
network.4 The ESP-IDF provides a comprehensive Wi-Fi Provisioning Manager component
specifically designed to handle this entire workflow, including the creation of the
temporary network and a captive portal to simplify the user experience.6
●​ Operational Phase: Once valid Wi-Fi credentials have been received and successfully
stored, the device transitions to the operational phase. It disables the SoftAP, switches its
Wi-Fi mode to Station (STA), and connects to the user-provided network.4 Upon
successful connection and acquisition of an IP address, the device starts its primary
application logic—in this case, the USB/IP server—and launches a persistent, more
feature-rich web server. This web server serves the main configuration and monitoring
interface, accessible from any device on the local network.8 Subsequent reboots will
bypass the provisioning phase entirely, provided the stored credentials remain valid and
the network is available.

The transition between these two phases is a critical juncture. The architecture must ensure a
graceful shutdown of all provisioning-related services before initializing the operational
services to prevent resource conflicts and undefined behavior.

1.2. Defining the Core FreeRTOS Tasks

To manage the concurrent operations required by this architecture, the application logic is
segregated into distinct FreeRTOS tasks. Each task is an independent thread of execution with
its own stack and priority, managed by the FreeRTOS scheduler.3 This multi-tasking approach
prevents long-running operations, such as handling a web request or managing a USB
connection, from blocking other critical system functions.

For this system, three primary tasks are defined:


1.​ main_app_task: This is the initial task launched after boot (within app_main). Its sole
responsibility is to orchestrate the system's state transitions. It first initializes essential
services like Non-Volatile Storage (NVS) and the TCP/IP stack.4 It then checks if the
device has already been provisioned with Wi-Fi credentials. If not, it initiates the Wi-Fi
Provisioning Manager and waits for it to complete. Once the device is provisioned and
connected to a Wi-Fi network, this task launches the​
usb_ip_server_task and the web_interface_task before either suspending itself or
entering a minimal monitoring loop.
2.​ usb_ip_server_task: This task contains the core application logic for the USB/IP server. It
is responsible for initializing the USB hardware, managing connections with USB devices,
and handling the network protocol for serving USB devices over IP. This task runs
continuously during the operational phase and is responsible for updating the system's
status, such as connection state and data transfer rates.
3.​ web_interface_task: This task is dedicated to managing the esp_http_server. It is
created only after a successful Wi-Fi connection in the operational phase. Its
responsibilities include starting the web server, registering all URI handlers for the
RESTful API and web asset delivery, and processing incoming HTTP requests.10 By
isolating the web server in its own task, the system ensures that network-related delays
or intensive request processing do not impact the real-time performance of the​
usb_ip_server_task. The esp_http_server itself runs in a dedicated task created by the
httpd_start function, but this parent task manages its lifecycle and request dispatching
logic.10

1.3. The Shared State Module: A Concurrency-Safe Bridge

With multiple tasks operating concurrently, a robust mechanism for sharing data between
them is not an optional refinement but a foundational requirement for system stability. The
web_interface_task needs to read status information generated by the usb_ip_server_task,
and the usb_ip_server_task may need to act on configuration changes initiated from the web
interface. Direct access to shared global variables in a preemptive, multi-core RTOS
environment is a recipe for disaster, leading to race conditions and data corruption.3

To address this, the architecture employs a centralized Shared State Module. This is a C
module (shared_state.c) that encapsulates all data to be shared between tasks within a single
struct. This struct might contain fields such as usb_device_connected (a boolean),
client_ip_address (a string), and data_throughput_kbps (an integer).

Critically, all access to this shared struct is arbitrated by a FreeRTOS mutex


(xSemaphoreCreateMutex).13 A mutex acts as a lock, ensuring that only one task can access
the shared data at any given time. Before reading or writing to the shared struct, a task must
first "take" the mutex using

xSemaphoreTake. After the access is complete, it must "give" the mutex back using
xSemaphoreGive. This guarantees atomic operations on the shared data, preventing one task
from reading the data while another is in the middle of updating it.

While other FreeRTOS primitives like queues and task notifications are excellent for signaling
events or passing discrete messages, they are less suitable for managing a persistent,
complex state that needs to be accessed randomly by multiple tasks.14 The mutex-protected
shared structure provides a clear, efficient, and thread-safe repository for the system's
current state, acting as a reliable bridge between the core application logic and the web
interface. This formal Inter-Task Communication (ITC) strategy is paramount to building a
production-grade embedded system.

Section 2: Implementing First-Time Setup with the


Wi-Fi Provisioning Manager

A seamless out-of-the-box user experience is crucial for any connected device. The initial
process of connecting the device to a user's local Wi-Fi network, known as provisioning, can
be complex to implement correctly. Fortunately, ESP-IDF provides a high-level, integrated
component, the Wi-Fi Provisioning Manager, which abstracts away the low-level details of
network creation, device discovery, and secure credential transfer.6

2.1. Leveraging the wifi_prov_scheme_softap

The Wi-Fi Provisioning Manager supports multiple transport mechanisms, including Bluetooth
LE and SoftAP. For this application, the wifi_prov_scheme_softap is the ideal choice.6 When
this scheme is used, the manager automatically performs the following actions:
1.​ Initializes Wi-Fi in AP+STA mode: The device can simultaneously act as an Access Point
while being ready to connect as a Station.6
2.​ Starts a SoftAP: It creates a Wi-Fi network with a configurable Service Set Identifier
(SSID), for example, "PROV_XXXXXX".16
3.​ Starts an HTTP Server: An internal, lightweight web server is launched to handle
provisioning requests.6
4.​ Starts an mDNS Service: This allows for user-friendly service discovery. Instead of
needing to know the device's default IP address (e.g., 192.168.4.1), a user can often
navigate to a hostname like http://espressif.local in their browser.6

The entire process is initiated with a few function calls. First, the manager is configured with a
wifi_prov_mgr_config_t structure, specifying wifi_prov_scheme_softap as the chosen scheme.
Then, wifi_prov_mgr_init() initializes the manager. Finally, wifi_prov_mgr_start_provisioning()
starts the service, taking parameters for security level, proof-of-possession, and the service
name (SoftAP SSID).6 Using the official manager provides a standardized and secure (Security
1 offers AES-CTR encryption) solution that is also compatible with official Espressif
provisioning mobile applications for iOS and Android, offering an alternative to the web-based
setup.16

2.2. The Provisioning Event Loop


The provisioning manager operates asynchronously and communicates its status to the main
application via the ESP-IDF event loop system. By registering an event handler for provisioning
events, the application can monitor and react to the process in real-time.6 Key events include:
●​ WIFI_PROV_START: Signals that the provisioning service has started successfully and the
SoftAP is active.
●​ WIFI_PROV_CRED_RECV: Indicates that the manager has received Wi-Fi credentials (SSID
and password) from the client. The event data contains the received credentials, allowing
the application to inspect them if needed.
●​ WIFI_PROV_END: This is the most critical event. It signals that the provisioning process is
complete. The device has attempted to connect to the target network with the received
credentials.

The event handler for WIFI_PROV_END is the trigger for the application to transition from the
provisioning phase to the operational phase. Upon receiving this event, the handler should
de-initialize the provisioning manager using wifi_prov_mgr_deinit() to free up resources, and
then signal the main_app_task to proceed with launching the primary application tasks.6

2.3. Storing Credentials Persistently

A key feature of the Wi-Fi Provisioning Manager is its tight integration with the Non-Volatile
Storage (NVS) library. When credentials are successfully received, the manager automatically
saves them to the default NVS partition.5 This ensures that the Wi-Fi configuration persists
across device reboots and power cycles.

To create a seamless user experience, the main_app_task should, upon startup, first check if
the device has already been provisioned. This is accomplished by calling
wifi_prov_mgr_is_provisioned(). If this function returns true, it means valid credentials exist in
NVS. The application can then skip the entire provisioning process and proceed directly to
connecting to the stored Wi-Fi network.6 This logic ensures that the user only has to perform
the setup procedure once. If the device later fails to connect to the stored network,
application logic can be implemented to re-initiate the provisioning process, for example, after
a button press or a timeout period.

Section 3: Building the Core Web Server and RESTful


API

After the device has been provisioned and is connected to the local network, the persistent
operational web server is launched. This server is the backbone of the user interface,
responsible for serving the frontend web application and providing a dynamic data interface
through a RESTful API. The esp_http_server component in ESP-IDF provides a flexible and
efficient foundation for this purpose.11

3.1. Initializing the esp_http_server

The esp_http_server is a capable, lightweight web server designed for embedded


applications. Its setup is straightforward and typically encapsulated within a dedicated
function, such as start_webserver(), which is called by the web_interface_task.

The initialization process involves two main steps:


1.​ Configuration: A httpd_config_t structure is populated with the server's desired
parameters. While many options are available (e.g., task priority, stack size, core affinity),
the HTTPD_DEFAULT_CONFIG() macro provides a sensible starting point for most
applications.2 This configuration can be customized as needed, for instance, to increase
the maximum number of open sockets or registered URI handlers.
2.​ Starting the Server: The httpd_start() function is called, passing it a pointer to the
configuration structure and the address of a httpd_handle_t variable. If successful, this
function allocates the necessary resources, creates the server's underlying FreeRTOS
task, and returns a unique handle to the server instance.10 This handle is then used in all
subsequent API calls to manage the server, such as registering URI handlers.

3.2. Designing the RESTful API Endpoints

To create a dynamic, responsive user interface that can display real-time status and accept
configuration changes without full page reloads, a clear and well-defined Application
Programming Interface (API) is essential. A RESTful (Representational State Transfer) API using
JSON (JavaScript Object Notation) as the data exchange format is the modern standard for
such web applications.19
All API endpoints are grouped under a common prefix, such as /api/, to distinguish them from
requests for static assets like HTML or CSS files. The core endpoints for this application are:
●​ GET /api/status: This endpoint is used by the frontend to poll the device for its latest
status. When requested, the server will respond with a JSON object containing real-time
information from the usb_ip_server_task. An example response might be:
{"usb_connected": true, "client_ip": "192.168.1.123", "uptime_sec": 3600}.
●​ GET /api/config: This endpoint retrieves the current, non-sensitive configuration settings
stored on the device. This allows the web interface to populate configuration forms with
the existing values when the page loads.
●​ POST /api/config: This endpoint is used to submit updated configuration settings to the
device. The web browser will send a JSON object in the body of the POST request
containing the new key-value pairs. The server will then validate these settings, update its
internal state, and persist the changes to NVS.

This API-centric design decouples the backend (firmware) from the frontend (web page). As
long as the API contract is maintained, either side can be updated independently, leading to a
more modular and maintainable system.

3.3. Implementing API Handlers

For each URI endpoint defined, a corresponding handler function must be implemented and
registered with the web server using httpd_register_uri_handler().2 This function takes a

httpd_uri_t structure that links a URI path and an HTTP method (e.g., HTTP_GET, HTTP_POST)
to a specific C function.18
●​ status_get_handler (for GET /api/status): This function's primary role is to read the
current status from the mutex-protected shared state module. It will take the mutex, copy
the relevant status variables into local memory, and then release the mutex as quickly as
possible to minimize blocking time. It then formats these variables into a JSON string. For
simple structures, this can be done with sprintf, but for more complex objects, a
dedicated library like cJSON is recommended. Finally, it sets the HTTP Content-Type
header to application/json 21 and sends the JSON string as the response body using​
httpd_resp_send().2
●​ config_post_handler (for POST /api/config): This handler is more complex as it involves
processing incoming data. It first needs to determine the length of the request body from
the content_len field of the httpd_req_t structure and allocate a buffer to receive it. The
httpd_req_recv() function is used to read the POST data into this buffer.10 Once received,
the JSON data is parsed and validated. If the new settings are valid, the handler takes the
shared state mutex, updates the relevant configuration fields in the shared​
struct, and then calls a function to save these new settings to NVS. After releasing the
mutex, it sends a success response to the client, typically a 200 OK status with a simple
JSON body like {"success": true}.

The following table provides a clear API contract, serving as a single source of truth for both
firmware and frontend development.

URI HTTP Method Request Response Description


Payload (JSON)
(JSON)

/ GET N/A text/html Serves the


main HTML
application
page.

/main.js GET N/A application/jav Serves the


ascript main
JavaScript file.

/style.css GET N/A text/css Serves the


main CSS file.

/api/status GET N/A {"usb_connect Fetches


ed": bool,...} real-time
status of the
USB/IP server.

/api/config GET N/A {"setting1": Fetches


"value",...} current
configuration
from the
device.

/api/config POST {"setting1": {"success": Updates one


"new_value"} true} or more
configuration
settings.
Section 4: Developing a Dynamic Frontend for
Real-Time Interaction

The frontend is the user-facing component of the system, comprising the HTML, CSS, and
JavaScript files that are served by the ESP32-S2 and rendered in the user's web browser. A
modern, dynamic frontend is essential for providing real-time monitoring and a seamless
configuration experience, leveraging asynchronous JavaScript to communicate with the
device's REST API.

4.1. Structuring the User Interface (HTML & CSS)

The foundation of the user interface is a well-structured HTML document. This document
defines the layout of the page, including sections for status display and configuration forms.
To enable dynamic updates via JavaScript, key elements that will display changing data are
given unique id attributes.

For example, a section for displaying the USB connection status might look like this:
<h2>USB/IP Server Status</h2>
<p>USB Device: <span id="usb-status">Initializing...</span></p>
<p>Connected Client: <span id="client-ip">None</span></p>
A separate CSS file is used to style the HTML elements, ensuring a clean, professional, and
responsive layout that works well on both desktop and mobile browsers.22 The HTML file links
to this stylesheet using a

<link> tag in the <head> section.22

4.2. Implementing Dynamic Updates with JavaScript Fetch API

The key to a real-time monitoring interface is the ability to update data on the page without
requiring the user to manually refresh it. This is achieved using asynchronous JavaScript. The
modern standard for this is the Fetch API, which provides a clean, promise-based interface for
making network requests.23

The core logic in the main JavaScript file will be as follows:


1.​ Periodic Polling: The setInterval() function is used to execute a status update function,
for example, updateStatus(), at a regular interval (e.g., every 2 seconds).
2.​ Fetching Data: The updateStatus() function uses fetch('/api/status') to send an
asynchronous GET request to the ESP32's status endpoint.
3.​ Processing the Response: The fetch call returns a Promise. The response is first parsed
as JSON using .json(). The resulting JavaScript object contains the latest status data
from the device.
4.​ Updating the DOM: The script then uses the received data to update the content of the
placeholder elements in the HTML. For instance,
document.getElementById('usb-status').innerText = data.usb_connected? 'Connected' :
'Disconnected'; would update the status display based on the value from the JSON
response.

This polling mechanism creates the appearance of a live dashboard, providing the user with
up-to-date information on the USB/IP server's operation.25

4.3. Handling Configuration Changes

Submitting configuration changes from the web interface also leverages JavaScript to provide
a better user experience than a traditional HTML form submission, which would cause a full
page reload.24

An event listener is attached to the "submit" event of the configuration form. This listener's
callback function will:
1.​ Prevent Default Action: Call event.preventDefault() to stop the browser from
performing a standard form submission.
2.​ Gather Form Data: Collect the values from the various input fields in the form.
3.​ Construct JSON Payload: Assemble the collected data into a JavaScript object that
matches the expected format for the /api/config POST endpoint.
4.​ Send POST Request: Use the Fetch API to send a POST request to /api/config. The
request options will specify the method: 'POST', the necessary headers ('Content-Type':
'application/json'), and the JSON payload converted to a string in the body.
5.​ Provide User Feedback: Based on the success or failure of the fetch request, the script
can provide feedback to the user, such as displaying a "Settings saved successfully"
message or an error alert.

4.4. Web Asset Management Strategy


A critical architectural decision is how to store and serve the frontend assets (HTML, CSS,
JavaScript files). ESP-IDF supports two primary methods, each with distinct trade-offs
affecting development workflow and production deployment.
1.​ Embedded Files: The ESP-IDF build system allows files to be directly embedded into the
final firmware binary. By adding the EMBED_FILES directive to the component's
CMakeLists.txt file, the contents of the specified asset files are converted into byte arrays
accessible from the C code.27 These assets can then be served efficiently from
memory-mapped flash. This method is excellent for production as it creates a single,
monolithic firmware file that is simple to deploy and eliminates any possibility of a
mismatch between the firmware and the web assets.28
2.​ SPIFFS Filesystem: Alternatively, the assets can be stored on a dedicated SPI Flash File
System (SPIFFS) partition on the flash chip.22 This requires creating a separate SPIFFS
image containing the web files and flashing it to the device alongside the main firmware.
This approach is highly advantageous during development, as it allows for rapid iteration
on the UI. A developer can update the HTML, CSS, or JavaScript files and re-flash only
the small SPIFFS partition without needing to recompile the entire C/C++ firmware,
drastically speeding up the development cycle.

The optimal approach is a hybrid workflow that leverages the strengths of both methods.
During active UI development, use SPIFFS for its fast iteration capabilities. For creating the
final production release, switch the build configuration to use the EMBED_FILES method. This
provides the best of both worlds: a highly efficient development process and a simple, robust,
single-file deployment for the end-user.

The following table summarizes the trade-offs between these two strategies.

Criterion Embedded Files SPIFFS Filesystem


(EMBED_FILES)

Development Workflow Slow iteration; requires full Fast iteration; UI assets can
firmware recompile for any be updated independently.
UI change.

Deployment Simplicity High; single binary file to Lower; requires flashing


flash. Eliminates user both firmware and
error.29 filesystem images.

Memory Footprint Potentially smaller overall Requires space for the


firmware due to no SPIFFS driver in firmware,
filesystem driver overhead. plus the partition.

Runtime Performance Faster access; assets are in Slower access; involves


memory-mapped flash.30 filesystem lookups and file
reads.

Dependency Overhead Minimal; only Adds dependency on


esp_http_server. SPIFFS and VFS
components.

Section 5: Managing Persistent Configuration with


Non-Volatile Storage (NVS)

For an embedded device to be practical, its configuration must persist across reboots and
power cycles. The ESP-IDF provides the Non-Volatile Storage (NVS) library, a specialized
key-value storage system designed for use with the onboard flash memory.31 It is the standard
and recommended method for storing small amounts of data like Wi-Fi credentials, device
settings, and calibration data.

5.1. NVS Fundamentals

NVS operates on a dedicated partition in the flash memory, typically labeled "nvs" in the
partition table.34 The library is designed for robustness, incorporating wear-leveling to
maximize the lifespan of the flash memory and mechanisms to recover from sudden power
loss during a write operation.33

Key concepts for using NVS include:


●​ Initialization: Before any NVS operations can be performed, the library must be
initialized by calling nvs_flash_init(). This function checks the integrity of the NVS
partition. It's common practice to handle potential errors like
ESP_ERR_NVS_NO_FREE_PAGES or ESP_ERR_NVS_NEW_VERSION_FOUND by erasing the
NVS partition with nvs_flash_erase() and re-initializing it.32 This ensures the device can
recover from a corrupted NVS state, albeit at the cost of losing stored settings.
●​ Namespaces: To prevent key name collisions between different software components
that might use NVS (e.g., the Wi-Fi driver and the custom application), NVS uses
namespaces. Each key-value pair belongs to a specific namespace. It is a best practice
to define a unique namespace for the application's configuration data.34
●​ Data Types: NVS supports storing a variety of primitive data types, including integers of
various sizes (uint8_t, int32_t, etc.), null-terminated strings, and variable-length binary
data (blobs).33

5.2. Reading and Writing Configuration

The NVS API is handle-based. To perform read or write operations, an application must first
open a namespace using nvs_open(), which provides a handle for subsequent operations.

To promote modularity and separation of concerns, it is highly recommended to encapsulate


all NVS interactions within a dedicated "configuration manager" module (config_manager.c).
This module would expose a clean API to the rest of the application, hiding the low-level
details of NVS. This module would include:
●​ config_load(): This function is called once at system startup. It opens the application's
NVS namespace, reads each configuration key using the appropriate nvs_get_*() function
(e.g., nvs_get_str()), and populates the in-memory shared state structure with the loaded
values. If a key is not found, it can populate the structure with a default value.
●​ config_save(): This function is called whenever the configuration needs to be persisted,
typically from the web server's config_post_handler after a user submits new settings. It
opens the NVS namespace in read-write mode, writes the new values from the shared
state structure to NVS using nvs_set_*() functions (e.g., nvs_set_str()), and, crucially, calls
nvs_commit() to ensure the changes are written from the cache to the flash memory.36
Finally, it closes the handle with​
nvs_close().

This modular approach ensures that the web server task and other tasks do not need to know
the specifics of the NVS API. They simply interact with the in-memory shared state and trigger
a save operation when necessary, leading to cleaner and more maintainable code.

Section 6: Ensuring Data Integrity in a Concurrent


Environment
The architectural principles of multi-tasking and shared state, introduced in Section 1, are the
cornerstones of this system's design. This final section provides a concrete implementation
strategy for these principles, demonstrating how the FreeRTOS mutex is used to tie the
system together and guarantee data integrity, preventing the subtle and difficult-to-debug
issues that arise from improper handling of concurrency.

6.1. Implementing the Mutex-Guarded Shared State

The shared_state module is the central nervous system of the application. Its implementation
is critical for robust operation. The module consists of three main parts:
1.​ The State Structure: A struct that contains all data to be shared.​
C​
typedef struct {​
bool usb_device_connected;​
char client_ip_address[1];​
uint32_t data_throughput_kbps;​
//... other configuration and status fields​
} app_state_t;​

2.​ The Mutex Handle: A static SemaphoreHandle_t variable within the module to hold the
mutex.​
C​
static SemaphoreHandle_t g_state_mutex;​

3.​ The Accessor Functions: A set of public functions that provide controlled access to the
state. These functions are the only way other modules should interact with the shared
data.

The shared_state_init() function, called once at startup, creates the mutex:


g_state_mutex = xSemaphoreCreateMutex();.13
Accessor functions then use this mutex to protect access. For example, a function to update
the USB connection status would be implemented as follows:

C
void shared_state_set_usb_status(bool is_connected) {​
if (xSemaphoreTake(g_state_mutex, portMAX_DELAY) == pdTRUE) {​
// Safely access the shared resource​
g_app_state.usb_device_connected = is_connected;​
xSemaphoreGive(g_state_mutex);​
}​
}​

Similarly, a getter function would wrap its read access within the same
xSemaphoreTake/xSemaphoreGive block. The portMAX_DELAY parameter ensures the task
will wait indefinitely until the mutex is available, preventing it from proceeding with a
potentially unsafe access. It is vital that critical sections—the code between taking and giving
the mutex—are kept as short as possible to avoid unnecessarily blocking other tasks.3

6.2. Task Interaction Walkthrough

With the thread-safe shared state module in place, we can trace the flow of data through the
system in two common scenarios, illustrating how the architecture works in practice.

Scenario 1: Real-Time Status Update


1.​ Event Occurs: The usb_ip_server_task detects that a new USB device has been
connected.
2.​ State Update: It immediately calls shared_state_set_usb_status(true). Internally, this
function acquires the mutex, updates the usb_device_connected flag in the global state
structure, and releases the mutex. The entire operation is atomic.
3.​ Client Polls: A few moments later, the user's web browser, driven by the JavaScript
setInterval function, sends a GET /api/status request to the ESP32-S2.
4.​ Request Handling: The web_interface_task receives this request and executes the
status_get_handler.
5.​ State Read: The handler calls a corresponding getter function, e.g.,
shared_state_get_usb_status(). This function acquires the same mutex, reads the now
true value of the flag, and releases the mutex.
6.​ Response: The handler formats the status into a JSON response ({"usb_connected":
true,...}) and sends it back to the browser, which then updates the UI to show
"Connected".

Throughout this process, the mutex ensures that the web_interface_task never reads the state
structure while the usb_ip_server_task is in the middle of modifying it, preventing inconsistent
data from ever being sent to the user.
Scenario 2: User-Initiated Configuration Change
1.​ User Action: The user changes a setting in the web interface—for instance, a device
hostname—and clicks "Save".
2.​ POST Request: The browser's JavaScript sends a POST /api/config request with a JSON
body: {"hostname": "new-device-name"}.
3.​ Request Handling: The web_interface_task executes the config_post_handler. It
receives and parses the JSON body.
4.​ In-Memory Update: The handler calls a setter function,
shared_state_set_hostname("new-device-name"). This function takes the mutex,
updates the hostname field in the shared state structure, and gives the mutex. The
configuration is now updated in RAM.
5.​ Persistent Storage: The handler then calls config_save(). This function takes the mutex
again, reads the complete, consistent state from the shared structure, writes it to NVS,
and releases the mutex.
6.​ Application Logic Reacts: At some later point, the usb_ip_server_task, perhaps in its
main loop, calls shared_state_get_hostname() to retrieve the current hostname. Because
it uses the thread-safe getter, it will receive the new value set by the user, and can act on
it accordingly (e.g., by restarting its mDNS service with the new name).

This walkthrough demonstrates how the carefully designed architecture, centered around a
mutex-protected shared state, facilitates complex, asynchronous interactions between
multiple RTOS tasks in a safe, reliable, and predictable manner.

Section 7: Conclusion

The architecture detailed in this report provides a comprehensive and robust framework for
implementing a web-based configuration and monitoring interface on an ESP32-S2 based
device. By leveraging the core components of the ESP-IDF, including the Wi-Fi Provisioning
Manager, the esp_http_server, and the Non-Volatile Storage library, it is possible to create a
professional-grade product with a seamless user experience.

The key architectural decisions that ensure the system's stability and maintainability are:
●​ A Two-Phase Lifecycle: Clearly separating the initial device provisioning from its main
operational state simplifies logic and resource management.
●​ RTOS Task Segregation: Isolating the core application logic, the web server, and the
main state machine into distinct FreeRTOS tasks promotes modularity and prevents
blocking operations from impacting system responsiveness.
●​ A Centralized, Mutex-Guarded State: The implementation of a thread-safe shared
state module is the most critical element for ensuring data integrity in a concurrent,
multi-tasking environment. This prevents race conditions and provides a reliable
communication bridge between all system components.
●​ A RESTful API Design: Using a well-defined RESTful API with JSON decouples the
firmware backend from the web frontend, allowing for independent development and
greater flexibility.
●​ A Hybrid Web Asset Workflow: Employing SPIFFS for rapid UI development and
embedded files for production releases offers an optimal balance between developer
efficiency and deployment simplicity.

By adhering to these design principles, developers can move beyond simple examples and
build complex, connected IoT devices that are not only feature-rich but also stable, secure,
and easy for end-users to manage. This architectural blueprint serves as a solid foundation
for any ESP32-based project requiring a sophisticated web interface.

Works cited

1.​ ESP32 Web Server (ESP-IDF) - Embedded Explorer, accessed September 25,
2025, https://embeddedexplorer.com/esp32-web-server/
2.​ FreeRTOS (IDF) - ESP32 - — ESP-IDF Programming Guide v5.5.1 documentation -
Espressif Systems, accessed September 25, 2025,
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/
freertos_idf.html
3.​ ESP-IDF Tutorials: Soft-AP · Developer Portal, accessed September 25, 2025,
https://developer.espressif.com/blog/2025/04/soft-ap-tutorial/
4.​ ESP32: WiFi Provisioning with Soft AP and Captive Portal - Reddit, accessed
September 25, 2025,
https://www.reddit.com/r/esp32/comments/1n4c6nl/esp32_wifi_provisioning_with
_soft_ap_and_captive/
5.​ Wi-Fi Provisioning - ESP32 - — ESP-IDF Programming Guide v5.5.1 ..., accessed
September 25, 2025,
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/provisio
ning/wifi_provisioning.html
6.​ Making a commercial product from esp and wifi creds : r/esp32 - Reddit,
accessed September 25, 2025,
https://www.reddit.com/r/esp32/comments/18z3gwr/making_a_commercial_prod
uct_from_esp_and_wifi/
7.​ A simple HTTP server - Embedded Rust on Espressif - ESP-RS, accessed
September 25, 2025, https://docs.esp-rs.org/std-training/03_4_http_server.html
8.​ ESP32 Web Server with ESP-IDF, accessed September 25, 2025,
https://esp32tutorials.com/esp32-web-server-esp-idf/
9.​ HTTP Server - ESP32 - — ESP-IDF Programming Guide v4.2.2 documentation,
accessed September 25, 2025,
https://docs.espressif.com/projects/esp-idf/en/v4.2.2/esp32/api-reference/protoco
ls/esp_http_server.html
10.​HTTP Server - ESP32 - — ESP-IDF Programming Guide v5.5.1 documentation,
accessed September 25, 2025,
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/protoc
ols/esp_http_server.html
11.​ ESP32, ESP-IDF, FreeRTOS - Queues or global state (plus mutex?) to share when a
key on a keyboard is pressed / released? - Reddit, accessed September 25, 2025,
https://www.reddit.com/r/esp32/comments/1jck2bz/esp32_espidf_freertos_queue
s_or_global_state_plus/
12.​Semaphores and Mutexes - - — ESP-Techpedia latest documentation, accessed
September 25, 2025,
https://docs.espressif.com/projects/esp-techpedia/en/latest/esp-friends/get-start
ed/basic-concepts/common-freertos-api/semaphore-and-mutexes.html
13.​Efficient Inter-Task Communication with FreeRTOS Task ..., accessed September
25, 2025,
https://embeddedexplorer.com/efficient-inter-task-communication-with-freertos
-task-notifications-on-esp-wrover-kit/
14.​Inter task Communication Architecture - Kernel - FreeRTOS Community Forums,
accessed September 25, 2025,
https://forums.freertos.org/t/inter-task-communication-architecture/11815
15.​espressif/esp-idf-provisioning-android - GitHub, accessed September 25, 2025,
https://github.com/espressif/esp-idf-provisioning-android
16.​ESP SoftAP Provisioning 4+ - App Store - Apple, accessed September 25, 2025,
https://apps.apple.com/us/app/esp-softap-provisioning/id1474040630
17.​ESP-IDF Tutorials: Basic HTTP server · Developer Portal, accessed September 25,
2025, https://developer.espressif.com/blog/2025/06/basic_http_server/
18.​ESP32 Rest API Web Server GET and POST Examples with ..., accessed September
25, 2025,
https://microcontrollerslab.com/esp32-rest-api-web-server-get-post-postman/
19.​Example of JSON REST API on ESP32 | by Phoax | Medium, accessed September
25, 2025,
https://phoax.medium.com/example-of-json-rest-api-for-esp32-4a5f64774a05
20.​esp_http_client: how to post using json? - ESP32 Forum, accessed September 25,
2025, https://esp32.com/viewtopic.php?t=6584
21.​ESP32 Web Server using SPIFFS (SPI Flash File System) - Random Nerd Tutorials,
accessed September 25, 2025,
https://randomnerdtutorials.com/esp32-web-server-spiffs-spi-flash-file-system/
22.​Resolving JavaScript File Download Issues from ESP32 Webserver - Medium,
accessed September 25, 2025,
https://medium.com/@python-javascript-php-html-css/resolving-javascript-file-
download-issues-from-esp32-webserver-d6cbf6e66915
23.​Input Data on HTML Form ESP32/ESP8266 Web Server Arduino ..., accessed
September 25, 2025,
https://randomnerdtutorials.com/esp32-esp8266-input-data-html-form/
24.​Web server on ESP32: How to update and display sensor values from the server
automatically? - Stack Overflow, accessed September 25, 2025,
https://stackoverflow.com/questions/44809589/web-server-on-esp32-how-to-u
pdate-and-display-sensor-values-from-the-server-aut
25.​Web server on ESP32: How to update and display sensor values? -
Circuits4you.com, accessed September 25, 2025,
https://circuits4you.com/2018/11/20/web-server-on-esp32-how-to-update-and-
display-sensor-values/
26.​ESP-IDF using HTML in my code with variable inside : r/esp32 - Reddit, accessed
September 25, 2025,
https://www.reddit.com/r/esp32/comments/1ndp9jc/espidf_using_html_in_my_cod
e_with_variable_inside/
27.​mongoose - Espressif ESP32 web server HTML example - Stack ..., accessed
September 25, 2025,
https://stackoverflow.com/questions/41910072/espressif-esp32-web-server-html
-example
28.​Embed Your Website in Your ESP8266 Firmware Image - Tinkerman, accessed
September 25, 2025,
https://tinkerman.cat/post/embed-your-website-in-your-esp8266-firmware-imag
e/
29.​Store and read static pages in flash - ESP32 Forum, accessed September 25,
2025, https://esp32.com/viewtopic.php?t=1059
30.​ESP IDF - Storage Help - What is NVS and Why? : r/esp32 - Reddit, accessed
September 25, 2025,
https://www.reddit.com/r/esp32/comments/1ajmhog/esp_idf_storage_help_what_i
s_nvs_and_why/
31.​ESP32 Non-Volatile Storage (NVS) - ncona.com, accessed September 25, 2025,
https://ncona.com/2024/09/esp32-non-volatile-storage-nvs/
32.​Non-Volatile Storage Library - ESP32 - — ESP-IDF Programming ..., accessed
September 25, 2025,
https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage
/nvs_flash.html
33.​Non-volatile storage library - - — ESP-IDF Programming Guide release-v3.3
documentation, accessed September 25, 2025,
https://docs.espressif.com/projects/esp-idf/en/release-v3.3/api-reference/storage
/nvs_flash.html
34.​NVS Data Storage and Reading in ESP32: A Comprehensive Guide - Medium,
accessed September 25, 2025,
https://medium.com/engineering-iot/nvs-data-storage-and-reading-in-esp32-a-
comprehensive-guide-12bdbc6325ac
35.​ESP32 ESP-IDF Storing a text string into the NVS and updating it, so ..., accessed
September 25, 2025,
http://www.macpczone.co.uk/content/esp32-esp-idf-storing-text-string-nvs-and
-updating-it-so-it-can-survive-power-cycles

You might also like