Friday, September 26, 2025

0000 0001 0000 0110

Interactive Analysis: ESP32 Programming Methodologies

How Gemini turned "Brute strength" into "Elegant code"

Awhile back I did a Little Bin project which worked fine until our TelCo (Optus, based in Singapore) decided to nuke it's network (and kill a few people in the process. I got off lightly, but still had some issues with compiling (library updates and some code noodling required), hardware access (the ESP32-S3 was literally in a little bin!) and finally some code snafus which Gemini helped me sort out. See below for Gemini's analysis, and see my github site (https://github.com/bovineck/LittleBin/tree/main) for the code. Video below!

Interactive Analysis

An exploration of two ESP32 programming methodologies, highlighting why the implementation approach is more critical than the library choice.

The Crucial Distinction: Library vs. Methodology

This is the most important concept to understand. While the first code file used a "non-blocking" library, its implementation was fundamentally blocking. Conversely, the second file used a simpler "blocking" library but implemented a brilliant non-blocking methodology, making its overall approach far superior.

File 1's Problem

The `ESPAsyncWebServer` library is powerful and non-blocking, but the code's use of `while` loops and `delay()` for tasks like connecting to WiFi and NTP effectively **froze the microcontroller**. This prevented it from doing anything else and negated the library's primary advantage. It's a classic case of using a powerful tool incorrectly.

File 2's Strength

The `WebServer` library is simpler, but the programmer's decision to use a **state-based, `millis()`-driven approach** allowed the program's main loop to run continuously. This non-blocking methodology ensures all tasks are managed concurrently, leading to a much more stable and responsive system.

Side-by-Side Comparison

Explore the four key differences in approach below.

Approach 1: `ESPAsyncWebServer`

This non-blocking, event-driven library is powerful. Like a modern waiter handling multiple tables at once, it doesn't wait for one task to finish before starting another. However, its benefits were undermined by blocking code elsewhere in the program.

// Event-driven handlers are set up once
server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
  request->send(200, "text/html", index_html);
});

server.on("/", HTTP_POST, [](AsyncWebServerRequest* request) {
  // Logic to handle form data
});

Approach 2: `WebServer`

This standard library is simpler but blocking. Like a traditional waiter who serves one table at a time, `server.handleClient()` must be called constantly in the main loop, otherwise the server becomes unresponsive.

// Must be called in every loop iteration
void loop() {
  server.handleClient();
  // Other non-blocking code...
}

Approach 1: Blocking with `delay()`

The code relies on `delay()` and `while` loops that halt the entire program. While waiting for WiFi to connect, the ESP32 can do nothing else—it can't serve web pages or update LEDs. This is known as "blocking" and is highly inefficient.

void initWiFi() {
  while (WiFi.status() != WL_CONNECTED) {
    delay(100); // Program is FROZEN here
    // No other tasks can run.
  }
}

Approach 2: Non-Blocking with `millis()`

This approach avoids `delay()` entirely. It uses `millis()` to check if enough time has passed to perform a task. The main `loop` runs thousands of times per second, ensuring all tasks are managed concurrently and the system remains responsive.

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - lastCheck >= interval) {
    lastCheck = currentMillis;
    // Perform a non-blocking task...
  }
}

Approach 1: Restart on Failure

The error handling is blunt: if a connection fails after a set number of tries, the device reboots with `ESP.restart()`. This is disruptive, causing several seconds of downtime and losing any temporary state.

if (numtries > wifitries) {
  numtries = 0;
  ESP.restart(); // Forces a full reboot
}

Approach 2: Graceful Retries

This method is far more graceful. If the connection is lost, it continuously tries to reconnect in the background while signaling the failure with a visual indicator (blinking LEDs). The rest of the system remains fully operational.

if (WiFi.status() != WL_CONNECTED) {
  isWifiConnected = false;
  signalFailure(); // Calls a non-blocking function
}

Approach 1: Redundant Logic

The same logic for flashing LEDs is duplicated in multiple functions (`initWiFi()`, `GetLocalTime()`). This makes the code harder to read, maintain, and debug, as a change in one place must be remembered in others.

// In GetLocalTime()...
while (!getLocalTime(&timeinfo)) {
  // ... LED flashing code ...
  delay(100);
}
// Same logic exists in initWiFi()

Approach 2: Modular Functions

The code is broken down into small, reusable, single-purpose functions like `signalFailure()` and `handleRoot()`. This modular approach makes the code clean, easy to understand, and simple to extend with new features.

// A reusable, single-purpose function
void signalFailure() {
  // Logic for blinking LEDs
}
// Called from anywhere it's needed

Visualizing Concurrency

This diagram illustrates the "Delay is Death" principle. The blocking loop gets stuck on long tasks, while the non-blocking loop handles multiple tasks concurrently, leading to a responsive system.

❌ Blocking Loop Execution

Start Loop

Begin checking tasks...

Attempting WiFi Connect...

Program is FROZEN. Waiting for 3 seconds...

Handle Web Request

Cannot run. Blocked by WiFi task.

Update LEDs

Cannot run. Blocked by WiFi task.

✔ Non-Blocking Loop Execution

Check WiFi Status

Is it time? Yes. Check and move on. (1ms)

Handle Web Request

Any requests? No. Move on. (1ms)

Update LEDs

Is it time? Yes. Update and move on. (1ms)

Check NTP Time

Is it time? No. Move on. (1ms)

Loop repeats thousands of times per second.

This interactive analysis demonstrates that a non-blocking methodology is key to creating robust and responsive embedded applications.

No comments:

Post a Comment