Skip to content

Development Guide

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

Development Guide

Guide for developers who want to extend or contribute to ESP32 WiFi Utility.

Getting Started

Prerequisites

Required Tools:

  • PlatformIO Core or IDE
  • Git
  • Text editor or IDE (VS Code recommended)

Optional:

  • ESPTool for manual flashing
  • Serial monitor (Arduino IDE, PuTTY, screen)

Development Setup

1. Clone Repository:

git clone https://github.com/yourusername/esp32-wifi-utility.git
cd esp32-wifi-utility

2. Open in PlatformIO:

# VS Code with PlatformIO extension
code .

# Or command line
pio run

3. Build:

# Build for ESP32dev
pio run -e esp32dev

# Build for Feather TFT
pio run -e feather-esp32-s3-tft

# Build all environments
pio run

4. Upload:

# Upload to connected device
pio run -e esp32dev --target upload

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

Project Structure

Directory Layout

esp32-wifi-utility/
β”œβ”€β”€ include/                 # Header files
β”‚   β”œβ”€β”€ config.h            # System configuration
β”‚   β”œβ”€β”€ wifi_manager.h      # WiFi management
β”‚   β”œβ”€β”€ web_server.h        # Web interface
β”‚   β”œβ”€β”€ ap_config.h         # AP persistence
β”‚   β”œβ”€β”€ station_config.h    # Station persistence
β”‚   β”œβ”€β”€ channel_analyzer.h  # Channel analysis
β”‚   β”œβ”€β”€ latency_analyzer.h  # Latency testing
β”‚   β”œβ”€β”€ iperf_manager.h     # iPerf integration
β”‚   β”œβ”€β”€ performance_monitor.h # Performance tracking
β”‚   β”œβ”€β”€ error_handling.h    # Error management
β”‚   └── logging.h           # Logging system
β”‚
β”œβ”€β”€ src/                     # Source files
β”‚   β”œβ”€β”€ main.cpp            # Entry point
β”‚   β”œβ”€β”€ wifi_manager.cpp    # WiFi implementation
β”‚   β”œβ”€β”€ web_server.cpp      # Web server pages
β”‚   β”œβ”€β”€ command_interface.cpp # Serial commands
β”‚   β”œβ”€β”€ ap_config.cpp       # AP config persistence
β”‚   β”œβ”€β”€ station_config.cpp  # Station config persistence
β”‚   β”œβ”€β”€ channel_analyzer.cpp # Channel scanning
β”‚   β”œβ”€β”€ latency_analyzer.cpp # Latency tests
β”‚   β”œβ”€β”€ iperf_manager.cpp   # iPerf protocol
β”‚   β”œβ”€β”€ performance_monitor.cpp # Performance stats
β”‚   β”œβ”€β”€ error_handling.cpp  # Error utilities
β”‚   β”œβ”€β”€ logging.cpp         # Logging implementation
β”‚   β”œβ”€β”€ led_controller.cpp  # LED control
β”‚   └── base64_utils.cpp    # Base64 encoding
β”‚
β”œβ”€β”€ docs/                    # Documentation
β”‚   β”œβ”€β”€ README.md           # Documentation index
β”‚   β”œβ”€β”€ technical/          # Technical docs
β”‚   β”œβ”€β”€ user-guides/        # User guides
β”‚   └── archive/            # Old documentation
β”‚
β”œβ”€β”€ scripts/                 # Build scripts
β”‚   └── version-manager.sh  # Version management
β”‚
β”œβ”€β”€ test/                    # Test files
β”‚   └── README              # Test documentation
β”‚
β”œβ”€β”€ platformio.ini          # Build configuration
β”œβ”€β”€ README.md               # Project readme
└── CHANGELOG.md            # Version history

Key Files

config.h - System-wide configuration constants

  • WiFi settings
  • Hardware pin definitions
  • Buffer sizes
  • Timeouts

wifi_manager.cpp - Core WiFi functionality

  • Mode switching
  • Network scanning
  • Connection management
  • QR code generation

web_server.cpp - Web interface implementation

  • HTTP endpoints
  • HTML page generation
  • API handlers

command_interface.cpp - Serial command processor

  • Command parsing
  • Function dispatch
  • Help system

Architecture

v4.2.0 Simplified Loop-Based Design

The system uses a straightforward loop-based architecture:

void setup() {
    // 1. Initialize subsystems
    initializeSerial();
    initializeLED();
    initializeWiFi();
    initializeWebServer();

    // 2. Load configuration
    if (hasStationConfig()) {
        startStationMode();
    } else {
        startAccessPoint();
    }

    // 3. Start services
    startWebServer();
}

void loop() {
    // 1. Handle serial commands
    processSerialCommands();

    // 2. Handle web requests
    if (webServerEnabled) {
        handleWebServerRequests();
    }

    // 3. Update status
    updateLEDStatus();
    monitorWebServerState();

    // 4. Background tasks
    // (periodic scans, performance logging, etc.)
}

Key Characteristics:

  • No FreeRTOS tasks
  • No queues or semaphores
  • Simple sequential execution
  • Direct function calls

Removed in v4.1.0:

  • Task priorities
  • Mutex locks
  • Queue management
  • Task communication
  • Watchdog complications

Adding Features

Step 1: Define Interface

Create header in include/:

// include/my_feature.h
#pragma once
#include <Arduino.h>
#include "config.h"
#include "logging.h"
#include "error_handling.h"

#define TAG_MYFEATURE "MyFeature"

// Initialize feature
void initializeMyFeature();

// Main functionality
Result<void> doSomething(int param);

// Configuration
void configureMyFeature(bool enabled);

// Status
void printMyFeatureStatus();

Step 2: Implement Functionality

Create source in src/:

// src/my_feature.cpp
#include "my_feature.h"

// State variables
static bool featureEnabled = false;
static int featureState = 0;

void initializeMyFeature() {
    LOG_INFO(TAG_MYFEATURE, "Initializing...");
    featureEnabled = true;
    featureState = 0;
}

Result<void> doSomething(int param) {
    if (!featureEnabled) {
        return {ErrorCode::FEATURE_DISABLED, "Feature not initialized"};
    }

    if (param < 0) {
        return {ErrorCode::INVALID_PARAMETER, "Parameter must be positive"};
    }

    LOG_DEBUG(TAG_MYFEATURE, "Doing something with param: %d", param);
    featureState = param;

    return {};  // Success
}

void configureMyFeature(bool enabled) {
    featureEnabled = enabled;
    LOG_INFO(TAG_MYFEATURE, "Feature %s", enabled ? "enabled" : "disabled");
}

void printMyFeatureStatus() {
    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    Serial.println("My Feature Status");
    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    Serial.printf("Enabled: %s\n", featureEnabled ? "Yes" : "No");
    Serial.printf("State: %d\n", featureState);
    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
}

Step 3: Add Commands

Update command_interface.cpp:

// In executeCommand()
else if (command == "myfeature") {
    if (argument == "enable") {
        configureMyFeature(true);
    } else if (argument == "disable") {
        configureMyFeature(false);
    } else if (argument == "do") {
        int param = secondArgument.toInt();
        auto result = doSomething(param);
        if (!result) {
            LOG_ERROR(TAG_MYFEATURE, "%s", result.getMessage());
        }
    } else if (argument == "status") {
        printMyFeatureStatus();
    } else {
        Serial.println("Usage: myfeature <enable|disable|do <n>|status>");
    }
}

// In printHelp()
Serial.println("  myfeature enable          - Enable feature");
Serial.println("  myfeature disable         - Disable feature");
Serial.println("  myfeature do <number>     - Do something");
Serial.println("  myfeature status          - Show status");

Step 4: Add Web Interface (Optional)

Update web_server.cpp:

// Register handler in startWebServer()
webServer->on("/myfeature", HTTP_GET, handleMyFeature);

// Implement page handler
void handleMyFeature() {
    String html = htmlHeader("My Feature");

    html += "<div class='card'>";
    html += "<h2>My Feature Control</h2>";
    html += "<button onclick='enableFeature()'>Enable</button>";
    html += "<button onclick='disableFeature()'>Disable</button>";
    html += "<div id='status'></div>";
    html += "</div>";

    html += "<script>";
    html += "function enableFeature() {";
    html += "  fetch('/myfeature/enable').then(r => updateStatus());";
    html += "}";
    html += "function disableFeature() {";
    html += "  fetch('/myfeature/disable').then(r => updateStatus());";
    html += "}";
    html += "function updateStatus() {";
    html += "  fetch('/myfeature/status')";
    html += "    .then(r => r.json())";
    html += "    .then(d => document.getElementById('status').innerHTML = d.status);";
    html += "}";
    html += "updateStatus();";
    html += "</script>";

    html += htmlFooter();
    webServer->send(200, "text/html", html);
}

Step 5: Add Configuration Persistence (Optional)

If your feature needs to save settings:

// include/my_feature_config.h
#pragma once
#include <Arduino.h>

void initializeMyFeatureConfig();
bool loadMyFeatureConfig();
bool saveMyFeatureConfig(int setting1, bool setting2);
void clearMyFeatureConfig();
void printMyFeatureConfig();
// src/my_feature_config.cpp
#include "my_feature_config.h"
#include <Preferences.h>

static Preferences prefs;

void initializeMyFeatureConfig() {
    loadMyFeatureConfig();
}

bool loadMyFeatureConfig() {
    prefs.begin("myfeature", true);  // Read-only
    int setting1 = prefs.getInt("setting1", 0);
    bool setting2 = prefs.getBool("setting2", false);
    prefs.end();

    // Apply settings to feature
    configureMyFeature(setting2);

    return true;
}

bool saveMyFeatureConfig(int setting1, bool setting2) {
    prefs.begin("myfeature", false);  // Read-write
    prefs.putInt("setting1", setting1);
    prefs.putBool("setting2", setting2);
    prefs.end();

    return true;
}

Code Style

Naming Conventions

Functions:

// camelCase for public functions
void initializeWiFi();
void startAccessPoint();

// static for internal/private functions
static void updateInternalState();
static bool validateInput();

Variables:

// camelCase for local/member variables
int networkCount;
String currentSSID;

// UPPERCASE for constants
#define MAX_NETWORKS 20
const int TIMEOUT_MS = 5000;

Types:

// PascalCase for types
enum class WiFiMode { IDLE, STATION, AP };
class NetworkAnalyzer { };
struct ScanResult { };

Comments

Header Comments:

/**
 * @brief Connects to WiFi network
 * @param ssid Network SSID
 * @param password Network password
 * @return Result indicating success or error
 */
Result<void> connectToNetwork(const String& ssid, const String& password);

Inline Comments:

// Single-line comment for brief explanations
int timeout = 5000;  // 5 second timeout

/* Multi-line comment
   for longer explanations
   or disabled code blocks */

Error Handling

Use Result:

Result<int> getValue() {
    if (notInitialized) {
        return {ErrorCode::NOT_INITIALIZED, "System not ready"};
    }
    return 42;  // Success with value
}

Check Results:

auto result = getValue();
if (!result) {
    LOG_ERROR(TAG, "%s", result.getMessage());
    return;
}
int value = result.getValue();

Logging

Use Logging Macros:

LOG_INFO(TAG_WIFI, "Connecting to %s", ssid);
LOG_ERROR(TAG_CONFIG, "Save failed: %d", errorCode);
LOG_DEBUG(TAG_PERF, "Operation took %lu ms", duration);

Don't use Serial.print directly:

// BAD
Serial.print("Connecting...");

// GOOD
LOG_INFO(TAG_WIFI, "Connecting to %s", ssid);

Testing

Build Testing

Test All Environments:

# Build all configurations
pio run

# Build specific environment
pio run -e esp32dev
pio run -e feather-esp32-s3-tft
pio run -e feather-esp32-s3-reverse-tft

Functional Testing

1. Serial Commands:

# Connect to device
pio device monitor

# Test commands
> help
> mode station
> scan
> connect "TestNetwork" "password"
> status

2. Web Interface:

  • Access http://[device-ip]
  • Test all pages
  • Verify forms work
  • Check mobile responsiveness

3. Configuration Persistence:

> config ap save "MyAP" "password" 6
> config ap show
> reboot
# Verify config persists after reboot

Performance Testing

Monitor Memory:

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

Check for Leaks:

  • Monitor free heap over time
  • Should be stable
  • Watch for gradual decrease

Debugging

Serial Debugging

Enable Debug Logs:

void setup() {
    setLogLevel(LogLevel::DEBUG);
    setLogTimestamps(true);
}

Add Debug Points:

LOG_DEBUG(TAG_MYFEATURE, "Variable x = %d", x);

Memory Debugging

Check Heap:

Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
Serial.printf("Largest block: %d bytes\n", ESP.getMaxAllocHeap());

Find Leaks:

// Before operation
size_t heapBefore = ESP.getFreeHeap();

// Do operation
performOperation();

// After operation
size_t heapAfter = ESP.getFreeHeap();
Serial.printf("Heap change: %d bytes\n", (int)(heapAfter - heapBefore));

Contributing

Pull Request Process

1. Fork & Clone:

git clone https://github.com/yourusername/esp32-wifi-utility.git
cd esp32-wifi-utility
git checkout -b feature/my-new-feature

2. Make Changes:

  • Follow code style
  • Add documentation
  • Test thoroughly

3. Commit:

git add .
git commit -m "Add my new feature"
git push origin feature/my-new-feature

4. Create PR:

  • Clear description
  • List changes
  • Include testing notes

Commit Messages

Format:

[Type] Brief description

Detailed explanation of changes...

Types:

  • [Feature] - New functionality
  • [Fix] - Bug fix
  • [Docs] - Documentation
  • [Refactor] - Code improvement
  • [Test] - Test updates

Examples:

[Feature] Add network latency testing

Implements TCP-based latency measurement with jitter
calculation and quality assessment. Includes web
interface and serial commands.

[Fix] Correct AP channel validation

Channel selection was allowing invalid values. Now
properly validates against 1-13 range.

Additional Resources

Documentation

External References


← Back to Home | Next: Migration Guide β†’

πŸ“‘ ESP32 WiFi Utility

🏠 Main

πŸ“¦ Setup

βš™οΈ Configuration

πŸ“Š Features

πŸ’» Development

πŸ†˜ Support

πŸ”— Links


Version: 4.2.0
License: MIT

Clone this wiki locally