# How to Implement OTA Firmware Updates and WiFi Configuration for the DIY Sim-Racing FFB Pedal

> Learn how to implement OTA firmware updates and WiFi configuration for your DIY Sim-Racing FFB Pedal. This guide covers the easy three-stage process for seamless updates.

- Repository: [chrgri/diy-sim-racing-ffb-pedal](https://github.com/chrgri/diy-sim-racing-ffb-pedal)
- Tags: how-to-guide
- Published: 2026-02-27

---

**The DIY Sim-Racing FFB Pedal implements over-the-air (OTA) firmware updates through a three-stage process: triggering via the `OTA_enable_b` flag, initializing WiFi in station or AP mode while de-initializing ESP-NOW, and using the `ESP32OTAPull` library to download and flash binaries from JSON-defined URLs.**

The `chrgri/diy-sim-racing-ffb-pedal` repository provides a complete ESP32-based force-feedback pedal system with robust remote update capabilities. The firmware supports both client-mode OTA (connecting to existing networks) and AP-mode OTA (hosting a configuration portal), controlled through compile-time macros and runtime flags defined in [`Main.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Main.cpp) and [`OTA_Pull.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/OTA_Pull.h).

## Overview of the OTA Architecture

The OTA subsystem operates as a **state machine** within the `OTATask` FreeRTOS task. When activated, the system transitions through three distinct phases: trigger detection, network initialization, and binary flashing. This architecture ensures that ESP-NOW communication (used for pedal telemetry) is safely disabled before WiFi radio resources are claimed, preventing protocol conflicts.

The implementation relies on the external `ESP32OTAPull` library (fetched via PlatformIO) to handle HTTP downloads, JSON parsing, and partition writing. The pedal firmware wraps this library with hardware-specific initialization routines and user feedback mechanisms (LEDs and buzzer).

## Stage 1: Triggering the OTA Update

### Setting the OTA Enable Flag

OTA activation begins when any external interface sets the global boolean `OTA_enable_b` to `true`. This flag is monitored continuously within the `OTATask` loop in [`Firmware_for_V3/PedalFirmware/src/Main.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/src/Main.cpp). Valid triggers include serial commands, ESP-NOW packets from a bridge device, or button combinations.

When triggered, the firmware immediately sets companion flags to manage peripheral states:

```cpp
// Serial command handler excerpt from Main.cpp
if (cmd == "ota") {
    Serial.println("Get OTA command");
    OTA_enable_b = true;                  // Enable OTA routine
    OTA_enable_start = true;              // Immediate start flag
    ESPNow_OTA_enable = false;            // Disable ESP-NOW radio
}

```

### Populating WiFi Credentials

Concurrent with flag activation, the triggering mechanism must populate the `DAP_otaWifiInfo_st` structure. This struct, defined in [`Firmware_for_V3/PedalFirmware/include/OTA_Pull.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/include/OTA_Pull.h), carries all network parameters and update preferences:

```cpp
struct DAP_otaWifiInfo_st {
    uint8_t payloadType;
    uint8_t device_ID;
    uint8_t wifi_action;      // 0 = Station mode, 1 = AP mode
    uint8_t mode_select;      // 1=Main, 2=Dev, 3=Daily build
    uint8_t SSID_Length;
    uint8_t PASS_Length;
    uint8_t WIFI_SSID[30];
    uint8_t WIFI_PASS[30];
};

```

The `mode_select` field determines which JSON endpoint the OTA checker queries, allowing users to subscribe to stable releases, development branches, or daily builds.

## Stage 2: WiFi Initialization and ESP-NOW Teardown

### De-initializing ESP-NOW

Before activating WiFi, the `OTATask` performs critical resource management. ESP-NOW, the protocol used for low-latency pedal communication, utilizes the same radio hardware as WiFi station mode. The firmware explicitly de-initializes ESP-NOW to prevent conflicts:

```cpp
// Inside OTATask loop (Main.cpp)
if (OTA_enable_b) {
    // Disable ESP-NOW to free radio resources
    ESPNow_OTA_enable = false;
    // ... LED feedback and buzzer indication
}

```

### Station Mode vs. AP Mode

The firmware supports two distinct WiFi configurations, selected by the `wifi_action` field in the OTA structure.

**Station Mode** (`wifi_action == 0`) connects to an existing infrastructure network. The `wifi_initialized()` function in [`OTA_Pull.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/OTA_Pull.h) handles the connection sequence:

```cpp
// OTA_Pull.h – Station mode initialization
void wifi_initialized(char* Wifi_SSID, char* Wifi_PASS) {
    Serial.print("SSID: "); Serial.print(Wifi_SSID);
    Serial.print(" PASS: "); Serial.println(Wifi_PASS);
    
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(100);
    WiFi.begin(Wifi_SSID, Wifi_PASS);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi connected");
    Serial.print("RSSI: "); Serial.println(WiFi.RSSI());
}

```

**AP Mode** (`wifi_action == 1`) creates a soft access point with a captive web portal. This mode uses `ota_wifi_initialize()` and the `WebServer` class defined in [`ota.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ota.h), allowing users to upload firmware binaries via a browser without internet connectivity.

## Stage 3: Downloading and Flashing Firmware

### JSON Version Checking

Once WiFi is established, the `OTATask` instantiates the `ESP32OTAPull` class and queries a remote JSON manifest. The URL is selected based on the `mode_select` value from the OTA structure:

```cpp
// URL definitions in OTA_Pull.h
#define JSON_URL_dev   "https://raw.githubusercontent.com/gilphilbert/pedal-flasher/main/json/dev/Version_ControlBoard.json"
#define JSON_URL_main  "https://raw.githubusercontent.com/gilphilbert/pedal-flasher/main/json/main/Version_ControlBoard.json"
#define JSON_URL_daily "https://raw.githubusercontent.com/ChrGri/DIY-Sim-Racing-FFB-Pedal/develop/OTA/DailyBuild/json/Version_ControlBoard.json"

```

The `CheckForOTAUpdate()` method compares the remote version string against the local `DAP_FIRMWARE_VERSION` (or forces an update if `wifi_action == 1` by passing `"0.0.0"` as the version).

### Binary Download and Partition Writing

If the JSON indicates a newer version, the library streams the binary from the provided URL directly to the ESP32's OTA partition. The pedal firmware configures the update to **not** automatically boot the new image until explicitly verified, using the `UPDATE_BUT_NO_BOOT` flag:

```cpp
// Core OTA logic in Main.cpp
ESP32OTAPull ota;
ota.SetCallback(OTAcallback);
ota.OverrideBoard(CONTROL_BOARD);

// Force update check
const char *ver = (_dap_OtaWifiInfo_st.wifi_action == 1) ? "0.0.0" : DAP_FIRMWARE_VERSION;
char *version_tag = strdup(ver);

// Execute update check and download
int ret = ota.CheckForOTAUpdate(url, version_tag, ESP32OTAPull::UPDATE_BUT_NO_BOOT);
OTA_update_status = ret;

```

### Restart and Verification

Upon successful download (`ret == ESP32OTAPull::UPDATE_OK`), the firmware immediately restarts the ESP32 to boot into the new firmware:

```cpp
if (ret == ESP32OTAPull::UPDATE_OK) {
    ESP.restart();
}

```

Throughout the process, the firmware provides **haptic and visual feedback** via the `Buzzer` class and NeoPixel LEDs, indicating connection status, download progress, and success or failure states.

## Alternative: AP Mode Web Interface

For environments without internet access, the firmware includes a **captive portal** implementation in [`ota.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ota.h). When `wifi_action` is set to AP mode, the ESP32 creates a soft access point and serves a gzipped HTML page containing a JavaScript uploader:

```cpp
// ota.h – Web server setup for AP mode
const char *host = "esp32";
const char *password = "pedaladmin";
WebServer server(80);

void handleRoot() {
    server.sendHeader("Content-Encoding", "gzip");
    server.send(200, "text/html", (const char *)jquery_min_js_v3_2_1_gz,
                jquery_min_js_v3_2_1_gz_len);
}

```

Users connect to the `esp32` network, enter the password `pedaladmin`, and upload firmware binaries directly through the browser interface.

## Compile-Time Configuration

The OTA subsystem is conditionally compiled using macros defined in [`Firmware_for_V3/PedalFirmware/include/Main.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Firmware_for_V3/PedalFirmware/include/Main.h). This allows developers to create firmware variants with or without wireless update capabilities:

- **`#define OTA_update`** – Enables client-mode OTA (connects to existing WiFi networks)
- **`#define OTA_update_ESP32`** – Enables AP-mode OTA (creates a web server soft-AP)
- **No OTA macro defined** – Falls back to the minimal [`ota.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ota.h) implementation containing only WiFi utilities

These macros guard the relevant code sections throughout [`Main.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Main.cpp) and [`OTA_Pull.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/OTA_Pull.h), ensuring that unused networking code is excluded from builds where OTA is not required.

## Summary

- **Trigger Mechanism**: OTA begins when `OTA_enable_b` is set to `true` and the `DAP_otaWifiInfo_st` structure is populated with WiFi credentials and target URLs.
- **Network Transition**: The `OTATask` in [`Main.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Main.cpp) de-initializes ESP-NOW to free radio resources, then initializes WiFi in either station mode (`wifi_initialized`) or AP mode (`ota_wifi_initialize`).
- **Update Logic**: The `ESP32OTAPull` library checks JSON manifests from `JSON_URL_main`, `JSON_URL_dev`, or `JSON_URL_daily`, downloads newer binaries, and flashes them to the OTA partition before restarting the ESP32.
- **Configuration**: Compile-time macros (`OTA_update`, `OTA_update_ESP32`) in [`Main.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Main.h) control which OTA features are included in the build.

## Frequently Asked Questions

### How do I trigger an OTA update manually via serial?

Send the command `ota` through the serial console. The firmware in [`Main.cpp`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/Main.cpp) detects this command, sets `OTA_enable_b = true`, disables ESP-NOW communication, and begins the WiFi connection sequence. Ensure you have previously configured the WiFi credentials through the `DAP_otaWifiInfo_st` structure or hardcoded them in your build.

### What is the difference between Main, Dev, and Daily build channels?

The `mode_select` field in the `DAP_otaWifiInfo_st` structure determines which JSON manifest the firmware queries. Setting `mode_select = 1` targets the stable **Main** channel (`JSON_URL_main`), `2` targets the **Dev** channel for beta testing (`JSON_URL_dev`), and `3` targets the **Daily** build channel (`JSON_URL_daily`) for bleeding-edge updates.

### Can I update the firmware without internet access?

Yes, by using **AP Mode** (`wifi_action = 1`). In this mode, the ESP32 creates a soft access point named `esp32` with password `pedaladmin` and serves a web upload interface from [`ota.h`](https://github.com/chrgri/diy-sim-racing-ffb-pedal/blob/main/ota.h). Users connect to this WiFi network with a phone or laptop and upload the firmware binary directly through the browser, bypassing the need for internet connectivity or JSON manifests.

### Why does the pedal disable ESP-NOW during OTA updates?

ESP-NOW and WiFi station mode share the same 2.4 GHz radio hardware on the ESP32. The `OTATask` explicitly sets `ESPNow_OTA_enable = false` and de-initializes the ESP-NOW stack before calling `wifi_initialized()` to prevent radio resource conflicts. This ensures stable TCP/IP connectivity required for downloading firmware binaries.