Over-The-Air (OTA) firmware update handler for ESP32-WROOM-32 with WiFi connectivity and serial-only status feedback.
This library provides Over-The-Air (OTA) firmware update capability for ESP32-WROOM-32 microcontrollers. It temporarily connects to WiFi, enables ArduinoOTA to receive firmware uploads, waits for a configurable duration, and then automatically disconnects to free up WiFi resources for other system components (such as CAN bus operations).
Key Features:
- Temporary WiFi connection for OTA updates
- ArduinoOTA integration for firmware reception
- Configurable OTA window timeout
- Automatic WiFi disconnection after OTA window
- Serial status output (no visual feedback hardware required)
- MAC address-based device identification
- mDNS support for easy device discovery
- ESP32-WROOM-32 microcontroller
- WiFi connectivity (integrated into ESP32)
- Dual OTA partition scheme on flash (recommended for safe updates)
Add this library to your PlatformIO platformio.ini:
lib_deps =
git@github.com:trailcurrentoss/OtaUpdateLibraryWROOM32.gitCredentials can be provided from compile-time constants or read from NVM (Non-Volatile Memory) at runtime.
Option 1: Compile-time Credentials (Secrets.h)
Store credentials in a Secrets.h file (not tracked in Git):
// src/Secrets.h (add to .gitignore)
const char* ssid = "YOUR_SSID_HERE";
const char* password = "YOUR_PASSWORD_HERE";Create a template file for documentation:
// src/Secrets.h.template
const char* ssid = "YOUR_SSID_HERE";
const char* password = "YOUR_PASSWORD_HERE";Option 2: Runtime Credentials from NVM (ESP32 Preferences)
Load credentials from ESP32 Preferences and pass them to the OTA handler:
#include <Preferences.h>
#include <OtaUpdate.h>
Preferences prefs;
void setup() {
Serial.begin(115200);
// Read WiFi credentials from NVM
prefs.begin("wifi", true); // true = read-only
String ssid = prefs.getString("ssid", "");
String password = prefs.getString("password", "");
prefs.end();
// Safe to pass temporary String objects - library copies credentials internally
OtaUpdate otaUpdate(180000, ssid.c_str(), password.c_str());
}Memory Safety: The library internally copies credentials, so it's safe to pass temporary strings from NVM reads. The strings do not need to remain valid after construction.
Saving to Preferences:
void saveWiFiCredentials(const char* ssid, const char* password) {
Preferences prefs;
prefs.begin("wifi", false); // false = read-write
prefs.putString("ssid", ssid);
prefs.putString("password", password);
prefs.end();
}
## Usage Example
### Basic Usage
```cpp
#include <Arduino.h>
#include <OtaUpdate.h>
#include "Secrets.h"
// Create OTA update handler (3-minute window, 180000 ms)
OtaUpdate otaUpdate(180000, ssid, password);
void setup() {
Serial.begin(115200);
delay(100);
Serial.println("Device initialized");
Serial.printf("Hostname: %s\n", otaUpdate.getHostName().c_str());
}
void loop() {
// Normal operation happens here
delay(1000);
}
// Call from external trigger (e.g., CAN message):
void triggerOTA() {
Serial.println("OTA triggered!");
otaUpdate.waitForOta(); // Blocks until upload or timeout
Serial.println("Resuming normal operation");
}#include <TwaiTaskBased.h>
#include <OtaUpdate.h>
OtaUpdate otaUpdate(180000, ssid, password);
void onCanRx(const twai_message_t &msg) {
// OTA trigger: CAN message ID 0x0 with MAC address
if (msg.identifier == 0x0) {
char targetHostname[14];
String currentHostname = otaUpdate.getHostName();
// Extract target MAC from CAN data
sprintf(targetHostname, "esp32-%02X%02X%02X",
msg.data[0], msg.data[1], msg.data[2]);
// Check if this message is for us
if (currentHostname.equals(targetHostname)) {
Serial.println("OTA trigger received - entering OTA mode");
otaUpdate.waitForOta();
Serial.println("OTA complete - resuming normal operation");
}
}
}
void setup() {
Serial.begin(115200);
// Set up CAN with OTA callback
TwaiTaskBased::onReceive(onCanRx);
TwaiTaskBased::begin(GPIO_NUM_15, GPIO_NUM_13, 500000);
Serial.println("CAN bus initialized - OTA ready");
}
void loop() {
// CAN communication handled by FreeRTOS tasks
delay(1000);
}Initialize the OTA update handler.
Parameters:
waitForUploadTimeMs- Duration in milliseconds to wait for firmware upload (default: 30000 = 30 seconds)ssid- WiFi network name. Internally copied for safe storage (default: empty string)password- WiFi network password. Internally copied for safe storage (default: empty string)
Memory Safety: The library makes internal copies of the SSID and password strings, so it is safe to pass temporary strings or strings read from NVM. The source strings do not need to remain valid after construction.
Example (Compile-time):
// 3-minute OTA window
OtaUpdate otaUpdate(180000, "SSID", "Password");Example (NVM):
String ssid = readFromNVM();
String password = readFromNVM();
// Safe to pass temporary Strings - library copies them internally
OtaUpdate otaUpdate(180000, ssid.c_str(), password.c_str());
// ssid and password can now go out of scope safelyEnter OTA update mode. This method blocks until either:
- A firmware upload is completed and the device reboots, OR
- The OTA window timeout expires
Operations during this call:
- Connects to configured WiFi network
- Initializes ArduinoOTA listening for firmware uploads
- Waits for upload or timeout
- Disconnects from WiFi
- Returns to caller
Serial Output Example:
[OTA] === OTA Update Mode ===
[OTA] Connecting to WiFi network: SSID
.....
[OTA] WiFi connected!
[OTA] IP address: 192.168.1.100
[OTA] Hostname: esp32-8A3B4C
[OTA] ArduinoOTA initialized and listening for uploads
[OTA] Ready to receive firmware on: esp32-8A3B4C
[OTA] Waiting for firmware upload...
[OTA] OTA update window: 180 seconds
OTA Progress: 45%
OTA Progress: 100%
OTA End
[OTA] OTA update window closed
[OTA] WiFi disconnected
[OTA] Resuming normal operation
[OTA] ========================
Example:
Serial.println("Entering OTA mode...");
otaUpdate.waitForOta();
Serial.println("OTA window closed, resuming normal operation");Get the WiFi hostname of this device.
Format: esp32-XXXXXX where XXXXXX is the last 3 bytes of the MAC address in hexadecimal.
Returns:
- String containing the device hostname (e.g., "esp32-8A3B4C")
Example:
String hostname = otaUpdate.getHostName();
Serial.println("My hostname: " + hostname);
// Output: My hostname: esp32-8A3B4C-
Trigger OTA mode on the device (via CAN, button, etc.)
-
Update
platformio.iniwith the device hostname:
[env:esp32dev]
upload_protocol = espota
upload_port = esp32-XXXXXX ; Replace XXXXXX with your device's MAC- Build and upload during the OTA window:
pio run --target uploadFor safe updates with automatic rollback capability, configure dual OTA partitions:
partitions.csv:
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,0x5000,
otadata,data,ota,0xE000,0x2000,
app0,app,ota_0,0x10000,0x1A0000,
app1,app,ota_1,0x1B0000,0x1A0000,
spiffs,data,spiffs,0x350000,0x89000,
coredump,data,coredump,0x3D9000,0x10000,Add to platformio.ini:
board_build.partitions = partitions.csvCAN Message: Unicast OTA trigger via CAN ID 0x0
| Field | Value | Description |
|---|---|---|
| CAN ID | 0x0 | OTA trigger identifier |
| Data[0] | MAC byte 0 | Target device MAC (byte 3 of full MAC) |
| Data[1] | MAC byte 1 | Target device MAC (byte 4 of full MAC) |
| Data[2] | MAC byte 2 | Target device MAC (byte 5 of full MAC) |
| Data[3-7] | Reserved | Reserved for future use |
Example:
Device with MAC address AA:BB:CC:8A:3B:4C should be targeted with:
- CAN ID:
0x0 - Data:
[0x8A, 0x3B, 0x4C, ...]
All status messages are prefixed with [OTA] for easy filtering/logging:
[OTA] === OTA Update Mode ===
[OTA] Connecting to WiFi network: SSID
[OTA] WiFi connected!
[OTA] IP address: 192.168.1.100
[OTA] Hostname: esp32-8A3B4C
[OTA] ArduinoOTA initialized and listening for uploads
[OTA] Ready to receive firmware on: esp32-8A3B4C
[OTA] Waiting for firmware upload...
[OTA] OTA update window: 180 seconds
[OTA] OTA update window closed
[OTA] WiFi disconnected
[OTA] Resuming normal operation
Symptoms: Device times out connecting to WiFi
Solutions:
- Verify SSID and password in
Secrets.h - Check WiFi network is available and 2.4GHz (5GHz not supported by ESP32)
- Verify device is in WiFi range
- Check for WiFi MAC address filtering on router
Symptoms: Upload fails during PlatformIO OTA
Solutions:
- Verify device hostname matches
upload_portinplatformio.ini - Ensure you're uploading during the OTA window (check serial monitor)
- Check firewall isn't blocking UDP port 5555 and TCP port 3232
- Try again during a longer OTA window
Symptoms: Sending CAN OTA trigger doesn't activate OTA mode
Solutions:
- Verify CAN message ID is 0x0
- Check target MAC address bytes match device hostname
- Verify device is receiving CAN messages (check RX callback logs)
- Check serial output for OTA trigger messages
- WiFi connection typically takes 3-5 seconds
- OTA upload speed depends on firmware size (typically 50-100 KB/sec)
- WiFi is automatically disabled after OTA window to avoid interference with CAN
- CAN bus operations resume immediately after OTA disconnection
- WiFi Credentials:
- Compile-time: Store in
src/Secrets.hfile, add to.gitignoreto prevent accidental credential commits - Runtime (NVM): Store encrypted in ESP32 Preferences if possible. Use ESP32's secure boot and flash encryption features for additional protection
- Compile-time: Store in
- OTA Authentication: ArduinoOTA supports optional password protection (not configured in this version)
- MAC-based Targeting: Requires physical access to device to obtain MAC address for OTA targeting
- WiFi in NVM: Never commit credentials to version control, even in encrypted form. Use NVM for runtime configuration only
- 0.1.0 - NVM-compatible credential handling: library now safely copies WiFi credentials internally, enabling runtime configuration from ESP32 Preferences or other NVM sources
- 0.0.1 - Initial release for ESP32-WROOM-32
MIT License