Skip to content

Migration Guide

Arunkumar Mourougappane edited this page Oct 25, 2025 · 1 revision

Migration Guide

Upgrading from v4.1.0 to v4.2.0.

Overview

Version 4.2.0 is 100% backward compatible. Existing code will continue to work without any changes.

This guide helps you adopt the new v4.2.0 features to improve your code quality, reliability, and maintainability.

What's New in v4.2.0

Configuration Persistence (NEW)

Access Point Configuration:

  • Save AP settings to NVS flash
  • Automatic loading on boot
  • Commands: config ap save/load/clear/show

Station Configuration:

  • Save network credentials to NVS
  • Base64 password encoding for security
  • Commands: config station save/load/clear/show

Benefits:

  • Settings persist across reboots
  • No need to reconfigure every time
  • Secure password storage

Enhanced Error Handling

Result Type:

  • Type-safe error returns
  • Descriptive error messages
  • Easier error propagation

Example:

// Old way
bool connectWiFi(const char* ssid) {
    if (!ssid) return false;
    return WiFi.begin(ssid);
}

// New way
Result<void> connectWiFi(const char* ssid) {
    if (!ssid) {
        return {ErrorCode::WIFI_INVALID_SSID, "SSID cannot be null"};
    }
    if (!WiFi.begin(ssid)) {
        return {ErrorCode::WIFI_CONNECT_FAILED, "Connection failed"};
    }
    return {};  // Success
}

Structured Logging

Log Levels:

  • TRACE, DEBUG, INFO, WARN, ERROR, FATAL
  • Configurable at runtime
  • Optional timestamps

Example:

// Old way
Serial.println("[WiFi] Connecting...");
Serial.print("[WiFi] ERROR: ");
Serial.println(errorMsg);

// New way
LOG_INFO(TAG_WIFI, "Connecting to %s", ssid);
LOG_ERROR(TAG_WIFI, "Connection failed: %s", errorMsg);

Performance Monitoring

Automatic Tracking:

  • Operation timing
  • Min/avg/max statistics
  • Call counts

Example:

void scanNetworks() {
    ScopedTimer timer(g_wifiScanMonitor);
    int count = WiFi.scanNetworks();
    // Timing automatically recorded
}

// Later, view stats
printAllPerformanceStats();

Migration Steps

Step 1: Add Configuration Persistence

For Access Point Mode:

// Before (v4.1.0)
void setup() {
    startAccessPoint("ESP32-AP", "password123");
}

// After (v4.2.0)
void setup() {
    initializeAPConfig();  // Load saved config

    // First time setup
    if (!loadAPConfig()) {
        saveAPConfig("ESP32-AP", "password123", 6);
    }

    startAccessPoint();  // Uses saved config
}

For Station Mode:

// Before (v4.1.0)
void setup() {
    connectToNetwork("HomeWiFi", "mypassword");
}

// After (v4.2.0)
void setup() {
    initializeStationConfig();  // Load saved config

    // Check if credentials saved
    if (hasStationConfig()) {
        // Auto-connect to saved network
        startStationMode();
    } else {
        // First time - save credentials
        saveStationConfig("HomeWiFi", "mypassword");
        connectToNetwork("HomeWiFi", "mypassword");
    }
}

Configuration Commands:

Users can now configure via serial:

# AP configuration
> config ap save "MyESP32" "securepass" 6
> config ap show

# Station configuration
> config station save "HomeNetwork" "wifipassword"
> config station show

# Clear configuration
> config ap clear
> config station clear

Step 2: Replace Error Returns

Simple Boolean Returns:

// Old pattern
bool myFunction() {
    if (error) {
        Serial.println("ERROR: Something failed");
        return false;
    }
    return true;
}

// New pattern
Result<void> myFunction() {
    if (error) {
        return {ErrorCode::OPERATION_FAILED, "Something failed"};
    }
    return {};  // Success
}

// Usage
auto result = myFunction();
if (!result) {
    LOG_ERROR(TAG_MODULE, "%s", result.getMessage());
}

Functions with Return Values:

// Old pattern
int getValue(bool& success) {
    if (notReady) {
        success = false;
        return 0;
    }
    success = true;
    return 42;
}

// New pattern
Result<int> getValue() {
    if (notReady) {
        return {ErrorCode::NOT_READY, "System not initialized"};
    }
    return 42;  // Success with value
}

// Usage
auto result = getValue();
if (result) {
    int value = result.getValue();
    // Use value
} else {
    LOG_ERROR(TAG_MODULE, "%s", result.getMessage());
}

Error Propagation:

// Old pattern
bool doMultipleThings() {
    if (!step1()) return false;
    if (!step2()) return false;
    if (!step3()) return false;
    return true;
}

// New pattern
Result<void> doMultipleThings() {
    RETURN_IF_ERROR(step1());
    RETURN_IF_ERROR(step2());
    RETURN_IF_ERROR(step3());
    return {};
}

Step 3: Update to Structured Logging

Basic Logging:

// Old way
Serial.println("[WiFi] Starting scan...");

// New way
LOG_INFO(TAG_WIFI, "Starting scan");

Formatted Logging:

// Old way
Serial.print("[WiFi] Connected to ");
Serial.print(ssid);
Serial.print(" with IP ");
Serial.println(WiFi.localIP());

// New way
LOG_INFO(TAG_WIFI, "Connected to %s with IP %s",
         ssid, WiFi.localIP().toString().c_str());

Error Logging:

// Old way
Serial.print("[ERROR] Failed to connect: ");
Serial.println(errorCode);

// New way
LOG_ERROR(TAG_WIFI, "Failed to connect: %d", errorCode);

Configure Logging:

void setup() {
    Serial.begin(115200);

    // Set log level
    setLogLevel(LogLevel::INFO);  // INFO, WARN, ERROR, FATAL

    // Enable timestamps
    setLogTimestamps(true);
}

Log Levels:

  • TRACE - Very detailed debugging
  • DEBUG - Detailed debugging
  • INFO - Informational messages
  • WARN - Warnings (non-critical issues)
  • ERROR - Errors (critical issues)
  • FATAL - Fatal errors (system-breaking)

Step 4: Add Performance Monitoring

Wrap Critical Operations:

// Before
void performScan() {
    unsigned long start = millis();
    int count = WiFi.scanNetworks();
    Serial.printf("Scan took %lu ms\n", millis() - start);
}

// After
#include "performance_monitor.h"

void performScan() {
    ScopedTimer timer(g_wifiScanMonitor);
    int count = WiFi.scanNetworks();
    // Timing automatically recorded with statistics
}

View Statistics:

void loop() {
    // Your main loop code

    // Periodically print performance stats
    static unsigned long lastStats = 0;
    if (millis() - lastStats > 60000) {  // Every minute
        printAllPerformanceStats();
        lastStats = millis();
    }
}

Monitor Memory:

void loop() {
    static unsigned long lastMemCheck = 0;
    if (millis() - lastMemCheck > 60000) {
        logMemoryStats(TAG_SYSTEM);
        lastMemCheck = millis();
    }
}

Step 5: Use Configuration Constants

Replace Magic Numbers:

// Old way
delay(300);
if (strlen(ssid) > 32) { ... }
WiFi.begin(ssid, password, 0, NULL, true);

// New way
#include "config.h"

delay(SystemConstants::LED_STARTUP_ANIMATION_DELAY_MS);
if (strlen(ssid) > SystemConstants::MAX_SSID_LENGTH) { ... }
WiFi.begin(ssid, password, 0, NULL,
           SystemConstants::WIFI_CONNECT_TIMEOUT_MS);

Available Constants:

From config.h:

  • MAX_SSID_LENGTH = 32
  • MAX_PASSWORD_LENGTH = 63
  • MAX_NETWORKS = 20
  • WIFI_CONNECT_TIMEOUT_MS = 10000
  • SERIAL_BAUD_RATE = 115200
  • And many more...

Web Interface Updates

New Configuration Page

Access: http://[device-ip]/config

Features:

  • Save/load AP configuration
  • Save/load Station configuration
  • Clear stored settings
  • View current configuration
  • All changes persist across reboots

Example Usage:

  1. Open web interface
  2. Navigate to Configuration page
  3. Fill in AP or Station settings
  4. Click "Save"
  5. Settings persist after reboot

Configuration API Endpoints

AP Configuration:

// Save AP config
POST /config/ap
{
  "ssid": "MyESP32",
  "password": "securepass",
  "channel": 6
}

// Load AP config
GET /config/ap

// Clear AP config
DELETE /config/ap

Station Configuration:

// Save Station config
POST /config/station
{
  "ssid": "HomeWiFi",
  "password": "mypassword"
}

// Load Station config
GET /config/station

// Clear Station config
DELETE /config/station

Testing Your Migration

1. Compile Test

# Build for all environments
pio run

# Build specific board
pio run -e esp32dev

Expected: Clean build with no errors

2. Configuration Test

# Upload and monitor
pio run -e esp32dev --target upload --target monitor

# Test AP config
> config ap save "TestAP" "testpass" 11
> config ap show
> reboot

# After reboot, verify
> config ap show
# Should show saved settings

3. Logging Test

void setup() {
    Serial.begin(115200);

    setLogLevel(LogLevel::DEBUG);
    setLogTimestamps(true);

    LOG_INFO(TAG_MAIN, "System starting v4.2.0");
    LOG_DEBUG(TAG_MAIN, "Debug logging enabled");
}

Expected Output:

[2024-01-15 10:30:45] [INFO] [MAIN] System starting v4.2.0
[2024-01-15 10:30:45] [DEBUG] [MAIN] Debug logging enabled

4. Performance Monitoring Test

void loop() {
    static unsigned long lastPrint = 0;
    if (millis() - lastPrint > 60000) {
        printAllPerformanceStats();
        logMemoryStats(TAG_SYSTEM);
        lastPrint = millis();
    }
}

Expected Output:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Performance Statistics
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
WiFi Scan: 1235ms avg (min: 1104ms, max: 1458ms, count: 15)
Channel Analysis: 3421ms avg (min: 3102ms, max: 3895ms, count: 3)
...

Gradual Migration Strategy

You don't need to migrate everything at once. Here's a recommended phased approach:

Phase 1: Low-Risk Improvements

Week 1: Add Logging

  • Replace Serial.print with LOG_XXX macros
  • No functional changes
  • Immediate benefits: Better debugging

Week 2: Add Performance Monitoring

  • Wrap critical functions with ScopedTimer
  • No functional changes
  • Benefits: Identify bottlenecks

Testing: Everything should work exactly as before

Phase 2: Medium-Risk Improvements

Week 3: Add Configuration Persistence

  • Initialize config system
  • Test save/load functionality
  • Verify persistence across reboots

Testing: Test configuration commands thoroughly

Phase 3: Code Quality Improvements

Week 4+: Migrate Error Handling

  • Start with leaf functions
  • Work up the call stack
  • Thorough testing after each module

Testing: Verify error cases handled correctly


Common Patterns

Pattern 1: Initialize Configuration

void setup() {
    Serial.begin(115200);

    // Initialize configuration
    initializeAPConfig();
    initializeStationConfig();

    // Check for saved Station config
    if (hasStationConfig()) {
        LOG_INFO(TAG_MAIN, "Using saved Station config");
        startStationMode();
    } else {
        LOG_INFO(TAG_MAIN, "No Station config, starting AP");
        startAccessPoint();
    }
}

Pattern 2: Save User Configuration

// From serial command
if (command == "connect") {
    String ssid = argument;
    String password = secondArgument;

    // Save for future use
    saveStationConfig(ssid, password);

    // Connect now
    connectToNetwork(ssid, password);
}

Pattern 3: Configuration Reset

// Factory reset
void factoryReset() {
    clearAPConfig();
    clearStationConfig();
    LOG_INFO(TAG_SYSTEM, "Configuration cleared");
    ESP.restart();
}

Breaking Changes

None! Version 4.2.0 is fully backward compatible.

All v4.1.0 code continues to work without modification. New features are purely additive.


Need Help?

Documentation

Code Examples

  • See src/ap_config.cpp for configuration patterns
  • Check include/error_handling.h for Result examples
  • Look at src/logging.cpp for logging implementation

Support

  • GitHub Issues: Report bugs or ask questions
  • Documentation: Check docs/ folder for detailed guides

Version: 4.2.0
Compatibility: βœ… 100% Backward Compatible
Migration Difficulty: 🟒 Easy (Optional)
Recommended: βœ… Yes - Significant Quality Improvements


← Back to Home | Next: FAQ β†’

πŸ“‘ ESP32 WiFi Utility

🏠 Main

πŸ“¦ Setup

βš™οΈ Configuration

πŸ“Š Features

πŸ’» Development

πŸ†˜ Support

πŸ”— Links


Version: 4.2.0
License: MIT

Clone this wiki locally