Se rendre au contenu

← Référence API | Sommaire | Suivant : Commandes terminal →

6. Fichier principal de l'application (main.cpp)



6.1 Vue d'ensemble

Le fichier main/main.cpp est le point d'entrée de l'application AC Power Router. Il implémente la fonction app_main() (point d'entrée standard ESP-IDF) et effectue les tâches suivantes :

  • Initialisation du noyau Arduino — pour la compatibilité avec les bibliothèques Arduino
  • Initialisation séquentielle de tous les composants système dans un ordre strict
  • Configuration du mécanisme de callback pour la boucle principale de traitement de puissance
  • Boucle principale de l'application — traitement des commandes, WiFi, serveur web


Architecture de fonctionnement

Le système fonctionne sur une architecture pilotée par callbacks :

python
PowerMeterADC (DMA ADC)
       |
       | Every 200 ms (10 AC cycles)
       v
RMS Callback ────────> RouterController.update()
       |                       |
       |                       v
       |               Mode processing (AUTO/ECO/OFFGRID)
       |                       |
       |                       v
       |               DimmerHAL (dimmer control)
       v
Main Loop (100 ms)
  ├─> SerialCommand.process()
  ├─> WiFiManager.handle()
  ├─> WebServerManager.handle()
  ├─> NTPManager.handle()
  └─> Statistics (every 10 seconds)

Important : La logique principale de commande de puissance est exécutée à l'intérieur du callback RMS, qui est appelé toutes les 200 ms indépendamment de la boucle principale. La boucle principale ne gère que l'interface utilisateur (Serial, WiFi, Web).




6.2 Ordre d'initialisation des composants

Les composants sont initialisés dans un ordre strictement défini en tenant compte des dépendances :


Diagramme de dépendances

python
1. Arduino Core (initArduino)
   └─> 2. Serial (Serial.begin)
       └─> 3. ConfigManager (NVS)
           ├─> 4. WiFiManager (network)
           │   └─> 5. WebServerManager (REST API)
           │       └─> 6. NTPManager (time, after STA connection)
           │
           └─> 7. DimmerHAL (hardware)
               └─> 8. RouterController (control)
                   └─> 9. PowerMeterADC (measurements + callback)
                       └─> 10. SerialCommand (user interface)


Tableau de criticité des composants

Composant Criticité Action en cas d'erreur Dépendances
Arduino Core CRITIQUE System won't start -
ConfigManager Moyenne Valeurs par défaut utilisées Arduino
WiFiManager Basse Fonctionnement en mode AP uniquement Config
WebServerManager Basse Pas d'interface web WiFi
DimmerHAL CRITIQUE Système arrêté (while(1)) Arduino
RouterController CRITIQUE Système arrêté (while(1)) DimmerHAL, Config
PowerMeterADC CRITIQUE Système arrêté (while(1)) RouterController
SerialCommand Basse Pas d'interface Serial Config, Router
NTPManager Basse Pas de synchronisation horaire WiFi STA



6.3 Version de base de main.cpp

Voici une version de base simplifiée de main.cpp avec commentaires :

cpp
/**
 * @file main.cpp
 * @brief AC Power Router Controller - Main Entry Point
 */

#include "Arduino.h"
#include "esp_log.h"
#include "PowerMeterADC.h"
#include "DimmerHAL.h"
#include "RouterController.h"
#include "ConfigManager.h"
#include "SerialCommand.h"
#include "WiFiManager.h"
#include "WebServerManager.h"
#include "NTPManager.h"
#include "PinDefinitions.h"
#include "SensorTypes.h"

static const char* TAG = "MAIN";

// Mode names for logging
const char* ROUTER_MODE_NAMES[] = {"OFF", "AUTO", "MANUAL", "BOOST"};
const char* ROUTER_STATE_NAMES[] = {"IDLE", "INCREASING", "DECREASING",
                                     "AT_MAX", "AT_MIN", "ERROR"};

// Buzzer pin (disable at startup)
#define PIN_BUZZER 4

extern "C" void app_main()
{
    // ================================================================
    // STEP 1: Initialize Arduino Core
    // ================================================================
    initArduino();

    // Disable buzzer (hardware-specific)
    pinMode(PIN_BUZZER, OUTPUT);
    digitalWrite(PIN_BUZZER, HIGH);

    // ================================================================
    // STEP 2: Setup Serial for debugging
    // ================================================================
    Serial.begin(115200);
    delay(100);

    ESP_LOGI(TAG, "========================================");
    ESP_LOGI(TAG, "AC Power Router Controller");
    ESP_LOGI(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
    ESP_LOGI(TAG, "========================================");

    // ================================================================
    // STEP 3: Initialize ConfigManager (NVS)
    // ================================================================
    ESP_LOGI(TAG, "Initializing ConfigManager...");
    ConfigManager& config = ConfigManager::getInstance();

    if (!config.begin()) {
        ESP_LOGE(TAG, "Failed to initialize ConfigManager!");
        ESP_LOGW(TAG, "Using default values");
        // NOT critical - continue with defaults
    }

    // ================================================================
    // STEP 4: Initialize WiFiManager
    // ================================================================
    ESP_LOGI(TAG, "Initializing WiFiManager...");
    WiFiManager& wifi = WiFiManager::getInstance();
    wifi.setHostname("ACRouter");

    // Load credentials from NVS (if available) or start AP-only
    if (!wifi.begin()) {
        ESP_LOGE(TAG, "Failed to initialize WiFiManager!");
        // NOT critical - can operate without network
    } else {
        const WiFiStatus& ws = wifi.getStatus();
        if (ws.ap_active) {
            ESP_LOGI(TAG, "WiFi AP started: %s, IP: %s",
                     ws.ap_ssid.c_str(),
                     wifi.getAPIP().toString().c_str());
        }
        if (ws.sta_connected) {
            ESP_LOGI(TAG, "WiFi STA connected: %s, IP: %s",
                     ws.sta_ssid.c_str(),
                     wifi.getSTAIP().toString().c_str());
        }
    }

    // ================================================================
    // STEP 5: Initialize WebServerManager
    // ================================================================
    ESP_LOGI(TAG, "Initializing WebServerManager...");
    WebServerManager& webserver = WebServerManager::getInstance();

    if (!webserver.begin(80, 81)) {
        ESP_LOGE(TAG, "Failed to initialize WebServerManager!");
        // NOT critical - can operate without web interface
    } else {
        ESP_LOGI(TAG, "WebServer started - HTTP:%d, WS:%d",
                 webserver.getHttpPort(), webserver.getWsPort());
    }

    // ================================================================
    // STEP 6: Configure ADC channels for measurements
    // ================================================================
    ADCChannelConfig adc_channels[4] = {
        // Channel 0: Voltage sensor on GPIO35 (ADC1_CH7)
        ADCChannelConfig(
            PIN_VOLTAGE_SENSOR,              // GPIO35
            SensorType::VOLTAGE_AC,          // ZMPT107
            SensorCalibration::ZMPT107_MULTIPLIER,  // ~0.185
            SensorCalibration::ZMPT107_OFFSET,      // ~0.5
            true                             // Enabled
        ),
        // Channel 1: Load current sensor on GPIO39 (ADC1_CH3)
        ADCChannelConfig(
            PIN_CURRENT_SENSOR_1,
            SensorType::CURRENT_LOAD,
            SensorCalibration::SCT013_030_MULTIPLIER,
            SensorCalibration::SCT013_030_OFFSET,
            true
        ),
        // Channel 2: Grid current sensor on GPIO36 (ADC1_CH0)
        ADCChannelConfig(
            PIN_CURRENT_SENSOR_2,
            SensorType::CURRENT_GRID,
            SensorCalibration::SCT013_030_MULTIPLIER,
            SensorCalibration::SCT013_030_OFFSET,
            true
        ),
        // Channel 3: Solar panel current sensor on GPIO34 (ADC1_CH6)
        ADCChannelConfig(
            PIN_CURRENT_SENSOR_3,
            SensorType::CURRENT_SOLAR,
            SensorCalibration::SCT013_030_MULTIPLIER,
            SensorCalibration::SCT013_030_OFFSET,
            true
        )
    };

    // ================================================================
    // STEP 7: Initialize DimmerHAL (CRITICAL!)
    // ================================================================
    ESP_LOGI(TAG, "Initializing DimmerHAL...");
    DimmerHAL& dimmer = DimmerHAL::getInstance();

    if (!dimmer.begin(DimmerCurve::RMS)) {
        ESP_LOGE(TAG, "Failed to initialize DimmerHAL!");
        ESP_LOGE(TAG, "System halted.");
        while(1) {
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
    ESP_LOGI(TAG, "DimmerHAL initialized, frequency=%d Hz",
             dimmer.getMainsFrequency());

    // ================================================================
    // STEP 8: Initialize RouterController (CRITICAL!)
    // ================================================================
    ESP_LOGI(TAG, "Initializing RouterController...");
    RouterController& router = RouterController::getInstance();

    if (!router.begin(&dimmer, DimmerChannel::CHANNEL_1)) {
        ESP_LOGE(TAG, "Failed to initialize RouterController!");
        ESP_LOGE(TAG, "System halted.");
        while(1) {
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }

    // Apply configuration from NVS (or defaults)
    const SystemConfig& cfg = config.getConfig();
    router.setControlGain(cfg.control_gain);
    router.setBalanceThreshold(cfg.balance_threshold);
    router.setMode(static_cast(cfg.router_mode));
    if (cfg.router_mode == 2) {  // MANUAL mode
        router.setManualLevel(cfg.manual_level);
    }
    ESP_LOGI(TAG, "RouterController initialized: mode=%s, gain=%.1f, threshold=%.1f W",
             ROUTER_MODE_NAMES[cfg.router_mode],
             cfg.control_gain,
             cfg.balance_threshold);

    // ================================================================
    // STEP 9: Initialize PowerMeterADC (CRITICAL!)
    // ================================================================
    ESP_LOGI(TAG, "Initializing PowerMeterADC...");
    PowerMeterADC& powerMeter = PowerMeterADC::getInstance();

    if (!powerMeter.begin(adc_channels, 4)) {
        ESP_LOGE(TAG, "Failed to initialize PowerMeterADC!");
        ESP_LOGE(TAG, "System halted.");
        while(1) {
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }

    // ================================================================
    // STEP 10: Register RMS Callback - MAIN SYSTEM DRIVER
    // ================================================================
    powerMeter.setResultsCallback([](const PowerMeterADC::Measurements& m,
                                      void* user_data) {
        // This callback is called every 200 ms with new RMS data
        // THIS IS THE MAIN DRIVER for all system processing!

        // === UPDATE ROUTERCONTROLLER ===
        // Pass measurements for AUTO/ECO/OFFGRID mode processing
        RouterController& router = RouterController::getInstance();
        router.update(m);

        // Additional logic can be added here:
        // - Data logging
        // - WebSocket transmission
        // - SD card writing
        // - etc.

    }, nullptr);

    // Start DMA ADC
    if (!powerMeter.start()) {
        ESP_LOGE(TAG, "Failed to start PowerMeterADC!");
        ESP_LOGE(TAG, "System halted.");
        while(1) {
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }

    ESP_LOGI(TAG, "PowerMeterADC started successfully");

    // ================================================================
    // STEP 11: Initialize SerialCommand processor
    // ================================================================
    SerialCommand& serialCmd = SerialCommand::getInstance();
    serialCmd.begin(&config, &router);

    ESP_LOGI(TAG, "System initialization complete");
    ESP_LOGI(TAG, "Power measurement running (callback-driven)");

    // NTP Manager - initialized when WiFi STA connects
    NTPManager& ntp = NTPManager::getInstance();
    bool ntp_initialized = false;

    // ================================================================
    // MAIN LOOP - system is now callback-driven!
    // ================================================================
    while(1) {
        // Process serial commands
        serialCmd.process();

        // Process WiFi events
        wifi.handle();

        // Initialize NTP when STA connects and gets IP
        const WiFiStatus& ws = wifi.getStatus();
        if (!ntp_initialized && ws.sta_connected &&
            ws.sta_ip != IPAddress(0, 0, 0, 0)) {
            ESP_LOGI(TAG, "WiFi STA connected, initializing NTPManager...");
            // UTC+3 for Moscow, change for your timezone
            if (ntp.begin("pool.ntp.org",
                         "EET-2EEST,M3.5.0/3,M10.5.0/4",
                         3 * 3600, 3600)) {
                ESP_LOGI(TAG, "NTP started - Server: pool.ntp.org");
                ntp_initialized = true;
            } else {
                ESP_LOGE(TAG, "Failed to initialize NTPManager!");
            }
        }

        // Process WebServer requests
        webserver.handle();

        // Process NTP synchronization (if initialized)
        if (ntp_initialized) {
            ntp.handle();
        }

        // Statistics every 10 seconds
        static uint32_t last_stats = 0;
        uint32_t now = millis();
        if (now - last_stats >= 10000) {
            ESP_LOGI(TAG, "Statistics: Frames=%lu, Dropped=%lu, RMS=%lu, Freq=%dHz",
                     powerMeter.getFramesProcessed(),
                     powerMeter.getFramesDropped(),
                     powerMeter.getRMSUpdateCount(),
                     dimmer.getMainsFrequency());

            // WiFi status
            const WiFiStatus& ws = wifi.getStatus();
            if (ws.sta_connected) {
                ESP_LOGI(TAG, "WiFi STA: %s, IP=%s, RSSI=%d",
                         ws.sta_ssid.c_str(),
                         ws.sta_ip.toString().c_str(),
                         ws.rssi);
            }
            if (ws.ap_active) {
                ESP_LOGI(TAG, "WiFi AP: %s, IP=%s, clients=%d",
                         ws.ap_ssid.c_str(),
                         ws.ap_ip.toString().c_str(),
                         ws.sta_clients);
            }

            last_stats = now;
        }

        // 100 ms delay for responsive serial input
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}



6.4 Fonction app_main() — Analyse détaillée


6.4.1 Initialisation du noyau Arduino

cpp
extern "C" void app_main()
{
    initArduino();

Objectif : Initialise la couche compatible Arduino au-dessus d'ESP-IDF.

What it does:
- Creates Arduino loop task
- Initializes GPIO, I2C, SPI HAL
- Starts FreeRTOS scheduler for Arduino tasks
- Configures Serial UART0

Important: Without this call, Serial, pinMode(), digitalWrite(), millis() and other Arduino functions won't work.



6.4.2 Configuration du débogage Serial

cpp
Serial.begin(115200);
delay(100);

ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, "AC Power Router Controller");
ESP_LOGI(TAG, "ESP-IDF Version: %s", esp_get_idf_version());
ESP_LOGI(TAG, "========================================");

Parameters:
- Speed: 115200 baud (standard for ESP32)
- 100 ms delay for UART stabilization

Sortie au démarrage :

python
========================================
AC Power Router Controller
ESP-IDF Version: v5.5.1
========================================


6.4.3 Initialisation du ConfigManager

cpp
ConfigManager& config = ConfigManager::getInstance();

if (!config.begin()) {
    ESP_LOGE(TAG, "Failed to initialize ConfigManager!");
    ESP_LOGW(TAG, "Using default values");
}

What it does:
- Opens NVS namespace "acrouter"
- Loads saved parameters: router_mode, control_gain, balance_threshold, manual_level
- If NVS is empty or error - uses defaults

Criticité : Basse — le système continue avec les valeurs par défaut.

Valeurs par défaut (depuis ConfigManager.cpp) :

cpp
router_mode = 0;           // OFF
control_gain = 800.0f;     // Proportional controller coefficient
balance_threshold = 50.0f; // Balance threshold ±50 W
manual_level = 0;          // Dimmer 0% in MANUAL mode


6.4.4 Initialisation du WiFiManager

cpp
WiFiManager& wifi = WiFiManager::getInstance();
wifi.setHostname("ACRouter");

if (!wifi.begin()) {
    ESP_LOGE(TAG, "Failed to initialize WiFiManager!");
}

What it does:
- Loads WiFi credentials from NVS namespace "wifi" (if available)
- If credentials exist - connects to STA + starts AP
- If no credentials - starts only AP: ACRouter-XXXXXX

Deux options de configuration :

Option 1 : Identifiants codés en dur (pour les tests)

cpp
WiFiConfig wifiConfig;
strncpy(wifiConfig.sta_ssid, "MyNetwork", sizeof(wifiConfig.sta_ssid) - 1);
strncpy(wifiConfig.sta_password, "MyPassword123", sizeof(wifiConfig.sta_password) - 1);
if (!wifi.begin(wifiConfig)) {
    ESP_LOGE(TAG, "Failed to initialize WiFiManager!");
}
bash
# Via Serial command:
wifi-connect MyNetwork MyPassword123

Les identifiants sont sauvegardés dans NVS et chargés automatiquement à chaque démarrage.

Criticité : Basse — le système fonctionne sans réseau en mode AP uniquement.



6.4.5 Initialisation du WebServerManager

cpp
WebServerManager& webserver = WebServerManager::getInstance();

if (!webserver.begin(80, 81)) {
    ESP_LOGE(TAG, "Failed to initialize WebServerManager!");
} else {
    ESP_LOGI(TAG, "WebServer started - HTTP:%d, WS:%d",
             webserver.getHttpPort(), webserver.getWsPort());
}

Parameters:
- HTTP port: 80 (REST API)
- WebSocket port: 81 (real-time data)

Dependencies:
- Requires initialized WiFiManager
- Works on both AP IP (192.168.4.1) and STA IP

Criticité : Basse — commande possible via Serial.



6.4.6 Configuration des canaux ADC

cpp
ADCChannelConfig adc_channels[4] = {
    ADCChannelConfig(
        PIN_VOLTAGE_SENSOR,              // GPIO35
        SensorType::VOLTAGE_AC,          // ZMPT107
        SensorCalibration::ZMPT107_MULTIPLIER,  // ~0.185
        SensorCalibration::ZMPT107_OFFSET,      // ~0.5
        true                             // enabled
    ),
    // ... remaining 3 channels
};

Structure ADCChannelConfig :

cpp
struct ADCChannelConfig {
    gpio_num_t gpio;          // GPIO pin (ADC1: 32-39)
    SensorType type;          // Sensor type
    float multiplier;         // Calibration multiplier
    float offset;             // ADC offset (usually 0.5)
    bool enabled;             // Channel enabled/disabled
};

Sensor types:
- VOLTAGE_AC: ZMPT107 (220V AC voltage)
- CURRENT_LOAD: ACS-712 or SCT-013 (dimmer current)
- CURRENT_GRID: SCT-013 (grid current)
- CURRENT_SOLAR: SCT-013 (solar panel current)

Broches GPIO (depuis PinDefinitions.h) :

cpp
#define PIN_VOLTAGE_SENSOR    35  // ADC1_CH7
#define PIN_CURRENT_SENSOR_1  39  // ADC1_CH3 (Load)
#define PIN_CURRENT_SENSOR_2  36  // ADC1_CH0 (Grid)
#define PIN_CURRENT_SENSOR_3  34  // ADC1_CH6 (Solar)


6.4.7 Initialisation de DimmerHAL (CRITIQUE !)

cpp
DimmerHAL& dimmer = DimmerHAL::getInstance();

if (!dimmer.begin(DimmerCurve::RMS)) {
    ESP_LOGE(TAG, "Failed to initialize DimmerHAL!");
    ESP_LOGE(TAG, "System halted.");
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

What it does:
- Initializes zero-crossing detector on GPIO26
- Configures TRIAC control pins (GPIO22, GPIO23)
- Starts FreeRTOS task for mains synchronization
- Detects mains frequency (50 or 60 Hz)

DimmerCurve parameter:
- DimmerCurve::RMS: Linear curve for heating elements (resistors)
- DimmerCurve::LINEAR: For incandescent lamps (not used)

Criticité : MAXIMALE — sans DimmerHAL, la commande de puissance est impossible. En cas d'erreur, le système s'arrête (while(1)).

Possible errors:
- No zero-crossing signal (mains not connected)
- Zero-crossing detector malfunction (H11AA1)
- GPIO conflict



6.4.8 Initialisation du RouterController (CRITIQUE !)

cpp
RouterController& router = RouterController::getInstance();

if (!router.begin(&dimmer, DimmerChannel::CHANNEL_1)) {
    ESP_LOGE(TAG, "Failed to initialize RouterController!");
    ESP_LOGE(TAG, "System halted.");
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// Apply configuration from NVS
const SystemConfig& cfg = config.getConfig();
router.setControlGain(cfg.control_gain);
router.setBalanceThreshold(cfg.balance_threshold);
router.setMode(static_cast(cfg.router_mode));
if (cfg.router_mode == 2) {  // MANUAL mode
    router.setManualLevel(cfg.manual_level);
}

Parameters:
- dimmer: Pointer to DimmerHAL
- channel: DimmerChannel::CHANNEL_1 (first of two dimmer channels)

Settings from NVS:
- control_gain: P-controller coefficient (default 800.0)
- balance_threshold: Balance threshold (default 50.0 W)
- router_mode: Operating mode (default OFF)
- manual_level: Dimmer level for MANUAL mode (default 0%)

Criticality: MAXIMUM - without RouterController, there's no control logic.



6.4.9 Initialisation de PowerMeterADC (CRITIQUE !)

cpp
PowerMeterADC& powerMeter = PowerMeterADC::getInstance();

if (!powerMeter.begin(adc_channels, 4)) {
    ESP_LOGE(TAG, "Failed to initialize PowerMeterADC!");
    ESP_LOGE(TAG, "System halted.");
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

What it does:
- Configures ADC1 in continuous (DMA) mode
- Sampling frequency: 10 kHz per channel (80 kHz total for 8 channels)
- Creates DMA buffers
- Starts callback processing every 10 ms (DMA frame)
- Calculates RMS every 200 ms (20 frames)

Criticité : MAXIMALE — sans PowerMeterADC, il n'y a pas de mesures de puissance.



6.4.10 Enregistrement du callback RMS — Pilote principal du système

cpp
powerMeter.setResultsCallback([](const PowerMeterADC::Measurements& m,
                                  void* user_data) {
    // Called every 200 ms with new RMS data

    RouterController& router = RouterController::getInstance();
    router.update(m);  // MAIN CONTROL LOGIC!

}, nullptr);

Fréquence d'appel : Toutes les 200 ms (5 fois par seconde)

What happens inside the callback:
1. RouterController::update(m) receives measurements:
- m.voltage_rms: Grid voltage (V)
- m.current_rms[]: Currents on 4 channels (A)
- m.power_active[]: Active power (W)
- m.direction[]: Current direction (consumption/generation)

  1. RouterController analyse le mode :
  2. AUTO : P_grid → 0 (régulateur proportionnel)
  3. ECO : P_grid ≤ 0 (anti-export)
  4. OFFGRID : P_load ≤ 0.8 × P_solar
  5. MANUAL : Niveau fixe
  6. BOOST : 100% de puissance
  7. OFF : 0% de puissance

  8. DimmerHAL définit le niveau de puissance sur le TRIAC

IMPORTANT : C'est le seul endroit dans le code où la commande de puissance a lieu ! La boucle principale ne gère que l'interface utilisateur.



6.4.11 Démarrage de PowerMeterADC

cpp
if (!powerMeter.start()) {
    ESP_LOGE(TAG, "Failed to start PowerMeterADC!");
    ESP_LOGE(TAG, "System halted.");
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

ESP_LOGI(TAG, "PowerMeterADC started successfully");

What it does:
- Starts DMA ADC pipeline
- Begins calling DMA callbacks every 10 ms
- Starts RMS callback every 200 ms

À partir de ce moment, le système est entièrement piloté par callbacks — la commande de puissance fonctionne indépendamment de la boucle principale.



6.4.12 Initialisation de SerialCommand

cpp
SerialCommand& serialCmd = SerialCommand::getInstance();
serialCmd.begin(&config, &router);

ESP_LOGI(TAG, "System initialization complete");
ESP_LOGI(TAG, "Power measurement running (callback-driven)");

What it does:
- Registers Serial interface commands
- Links commands to ConfigManager and RouterController
- Enables system control via UART

Exemples de commandes :

bash
status                    # Show system status
set-mode auto            # Switch to AUTO mode
set-gain 800             # Set gain coefficient
calibrate-adc 0 1.0 0.5  # Calibrate ADC channel 0

La liste complète des commandes sera dans la section suivante de la documentation (07_COMMANDS.md).




6.5 Boucle principale

Après l'initialisation de tous les composants, la boucle principale infinie démarre :

cpp
while(1) {
    // 1. Process serial commands
    serialCmd.process();

    // 2. Process WiFi events
    wifi.handle();

    // 3. Initialize NTP when STA connects
    const WiFiStatus& ws = wifi.getStatus();
    if (!ntp_initialized && ws.sta_connected &&
        ws.sta_ip != IPAddress(0, 0, 0, 0)) {
        // NTPManager initialization...
    }

    // 4. Process WebServer requests
    webserver.handle();

    // 5. Process NTP synchronization
    if (ntp_initialized) {
        ntp.handle();
    }

    // 6. Statistics every 10 seconds
    static uint32_t last_stats = 0;
    uint32_t now = millis();
    if (now - last_stats >= 10000) {
        // Output statistics...
        last_stats = now;
    }

    // 7. 100 ms delay
    vTaskDelay(pdMS_TO_TICKS(100));
}


Tâches de la boucle principale

Tâche Fréquence Objectif
serialCmd.process() Toutes les 100 ms Traiter les commandes depuis Serial UART
wifi.handle() Toutes les 100 ms Reconnexion, keepalive AP, événements
Initialisation NTP Une fois à la connexion STA Démarrer la synchronisation horaire
webserver.handle() Toutes les 100 ms Traiter les requêtes HTTP/WebSocket
ntp.handle() Toutes les 100 ms Synchronisation périodique (horaire)
Statistiques Toutes les 10 secondes Journalisation Serial

Important : La boucle principale NE gère PAS la commande de puissance ! Cela est fait par le callback RMS toutes les 200 ms indépendamment.



6.5.1 NTP Manager — Initialisation différée

cpp
NTPManager& ntp = NTPManager::getInstance();
bool ntp_initialized = false;

// Inside main loop:
const WiFiStatus& ws = wifi.getStatus();
if (!ntp_initialized && ws.sta_connected &&
    ws.sta_ip != IPAddress(0, 0, 0, 0)) {
    ESP_LOGI(TAG, "WiFi STA connected, initializing NTPManager...");
    if (ntp.begin("pool.ntp.org",
                 "EET-2EEST,M3.5.0/3,M10.5.0/4",  // Timezone
                 3 * 3600,                         // GMT offset (UTC+3)
                 3600)) {                          // DST offset (1 hour)
        ESP_LOGI(TAG, "NTP started - Server: pool.ntp.org");
        ntp_initialized = true;
    } else {
        ESP_LOGE(TAG, "Failed to initialize NTPManager!");
    }
}

Why deferred initialization?
- NTP requires internet connection (STA mode)
- In AP-only mode, NTP is not needed
- When STA connects - starts automatically

NTP parameters:
- Server: pool.ntp.org (public NTP server pool)
- Timezone: EET-2EEST,M3.5.0/3,M10.5.0/4 (UTC+3 with daylight saving time transition)
- GMT offset: 3 * 3600 = 10800 seconds (UTC+3)
- DST offset: 3600 seconds (1 hour)

Modifier pour d'autres fuseaux horaires :

cpp
// UTC+0 (London)
ntp.begin("pool.ntp.org", "GMT0BST,M3.5.0/1,M10.5.0", 0, 3600);

// UTC-5 (New York)
ntp.begin("pool.ntp.org", "EST5EDT,M3.2.0,M11.1.0", -5 * 3600, 3600);

// UTC+8 (Beijing)
ntp.begin("pool.ntp.org", "CST-8", 8 * 3600, 0);


6.5.2 Statistiques toutes les 10 secondes

cpp
static uint32_t last_stats = 0;
uint32_t now = millis();
if (now - last_stats >= 10000) {
    ESP_LOGI(TAG, "Statistics: Frames=%lu, Dropped=%lu, RMS=%lu, Freq=%dHz",
             powerMeter.getFramesProcessed(),
             powerMeter.getFramesDropped(),
             powerMeter.getRMSUpdateCount(),
             dimmer.getMainsFrequency());

    // WiFi status
    const WiFiStatus& ws = wifi.getStatus();
    if (ws.sta_connected) {
        ESP_LOGI(TAG, "WiFi STA: %s, IP=%s, RSSI=%d",
                 ws.sta_ssid.c_str(),
                 ws.sta_ip.toString().c_str(),
                 ws.rssi);
    }
    if (ws.ap_active) {
        ESP_LOGI(TAG, "WiFi AP: %s, IP=%s, clients=%d",
                 ws.ap_ssid.c_str(),
                 ws.ap_ip.toString().c_str(),
                 ws.sta_clients);
    }

    last_stats = now;
}

Sortie Serial :

python
I (10000) MAIN: Statistics: Frames=1000, Dropped=0, RMS=50, Freq=50Hz
I (10000) MAIN: WiFi STA: MyNetwork, IP=192.168.1.100, RSSI=-45
I (10000) MAIN: WiFi AP: ACRouter-ABCD, IP=192.168.4.1, clients=1

Statistics parameters:
- Frames: Number of DMA frames (every 10 ms)
- Dropped: Dropped frames (should be 0!)
- RMS: Number of RMS calculations (every 200 ms)
- Freq: Detected mains frequency (50 or 60 Hz)




6.6 Version minimale (sans WiFi et WebServer)

For systems that don't need network interface:

cpp
extern "C" void app_main()
{
    initArduino();
    Serial.begin(115200);
    delay(100);

    ESP_LOGI(TAG, "AC Power Router - Minimal Version");

    // ConfigManager
    ConfigManager& config = ConfigManager::getInstance();
    config.begin();

    // ADC channels configuration
    ADCChannelConfig adc_channels[4] = {
        // ... (same as above)
    };

    // DimmerHAL
    DimmerHAL& dimmer = DimmerHAL::getInstance();
    if (!dimmer.begin(DimmerCurve::RMS)) {
        ESP_LOGE(TAG, "DimmerHAL init failed!");
        while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); }
    }

    // RouterController
    RouterController& router = RouterController::getInstance();
    if (!router.begin(&dimmer, DimmerChannel::CHANNEL_1)) {
        ESP_LOGE(TAG, "RouterController init failed!");
        while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); }
    }

    const SystemConfig& cfg = config.getConfig();
    router.setControlGain(cfg.control_gain);
    router.setBalanceThreshold(cfg.balance_threshold);
    router.setMode(static_cast(cfg.router_mode));

    // PowerMeterADC
    PowerMeterADC& powerMeter = PowerMeterADC::getInstance();
    if (!powerMeter.begin(adc_channels, 4)) {
        ESP_LOGE(TAG, "PowerMeterADC init failed!");
        while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); }
    }

    // RMS Callback
    powerMeter.setResultsCallback([](const PowerMeterADC::Measurements& m,
                                      void* user_data) {
        RouterController& router = RouterController::getInstance();
        router.update(m);
    }, nullptr);

    if (!powerMeter.start()) {
        ESP_LOGE(TAG, "PowerMeterADC start failed!");
        while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); }
    }

    // SerialCommand
    SerialCommand& serialCmd = SerialCommand::getInstance();
    serialCmd.begin(&config, &router);

    ESP_LOGI(TAG, "System ready (minimal mode)");

    // Main loop
    while(1) {
        serialCmd.process();
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

Minimal version advantages:
- Less memory usage (no WiFi/WebServer)
- Faster startup
- Fewer dependencies
- Control only via Serial

Disadvantages:
- No remote access
- No web interface
- No time synchronization




6.7 Application au format Arduino

Pour ceux qui préfèrent le format Arduino standard avec les fonctions setup() et loop(), voici comment organiser le code dans ce style.

Important : Dans ESP-IDF avec Arduino Core, les fonctions setup() et loop() sont appelées automatiquement depuis app_main(). Si vous créez un fichier main.cpp avec app_main(), vous ne pouvez pas utiliser setup() et loop() — ils entrent en conflit. Mais si vous travaillez dans l'IDE Arduino avec le framework Arduino, utilisez le format ci-dessous.


6.7.1 Variables globales

cpp
/**
 * @file ACRouter.ino
 * @brief AC Power Router Controller - Arduino Format
 */

#include "Arduino.h"
#include "esp_log.h"
#include "PowerMeterADC.h"
#include "DimmerHAL.h"
#include "RouterController.h"
#include "ConfigManager.h"
#include "SerialCommand.h"
#include "WiFiManager.h"
#include "WebServerManager.h"
#include "NTPManager.h"
#include "PinDefinitions.h"
#include "SensorTypes.h"

static const char* TAG = "ACROUTER";

// Global component references (for use in loop)
ConfigManager* g_config = nullptr;
WiFiManager* g_wifi = nullptr;
WebServerManager* g_webserver = nullptr;
NTPManager* g_ntp = nullptr;
SerialCommand* g_serialCmd = nullptr;
PowerMeterADC* g_powerMeter = nullptr;
DimmerHAL* g_dimmer = nullptr;
RouterController* g_router = nullptr;

// State flags
bool g_ntp_initialized = false;
uint32_t g_last_stats = 0;

// Buzzer pin
#define PIN_BUZZER 4


6.7.2 Fonction setup()

cpp
void setup()
{
    // ================================================================
    // STEP 1: Disable buzzer
    // ================================================================
    pinMode(PIN_BUZZER, OUTPUT);
    digitalWrite(PIN_BUZZER, HIGH);

    // ================================================================
    // STEP 2: Setup Serial for debugging
    // ================================================================
    Serial.begin(115200);
    delay(100);

    Serial.println("========================================");
    Serial.println("AC Power Router Controller");
    Serial.print("ESP-IDF Version: ");
    Serial.println(esp_get_idf_version());
    Serial.println("========================================");

    // ================================================================
    // STEP 3: Initialize ConfigManager (NVS)
    // ================================================================
    Serial.println("Initializing ConfigManager...");
    g_config = &ConfigManager::getInstance();

    if (!g_config->begin()) {
        Serial.println("ERROR: Failed to initialize ConfigManager!");
        Serial.println("WARNING: Using default values");
    }

    // ================================================================
    // STEP 4: Initialize WiFiManager
    // ================================================================
    Serial.println("Initializing WiFiManager...");
    g_wifi = &WiFiManager::getInstance();
    g_wifi->setHostname("ACRouter");

    if (!g_wifi->begin()) {
        Serial.println("ERROR: Failed to initialize WiFiManager!");
    } else {
        const WiFiStatus& ws = g_wifi->getStatus();
        if (ws.ap_active) {
            Serial.print("WiFi AP started: ");
            Serial.print(ws.ap_ssid);
            Serial.print(", IP: ");
            Serial.println(g_wifi->getAPIP().toString());
        }
        if (ws.sta_connected) {
            Serial.print("WiFi STA connected: ");
            Serial.print(ws.sta_ssid);
            Serial.print(", IP: ");
            Serial.println(g_wifi->getSTAIP().toString());
        }
    }

    // ================================================================
    // STEP 5: Initialize WebServerManager
    // ================================================================
    Serial.println("Initializing WebServerManager...");
    g_webserver = &WebServerManager::getInstance();

    if (!g_webserver->begin(80, 81)) {
        Serial.println("ERROR: Failed to initialize WebServerManager!");
    } else {
        Serial.print("WebServer started - HTTP:");
        Serial.print(g_webserver->getHttpPort());
        Serial.print(", WS:");
        Serial.println(g_webserver->getWsPort());
    }

    // ================================================================
    // STEP 6: Configure ADC channels for measurements
    // ================================================================
    static ADCChannelConfig adc_channels[4] = {
        // Channel 0: Voltage sensor on GPIO35 (ADC1_CH7)
        ADCChannelConfig(
            PIN_VOLTAGE_SENSOR,
            SensorType::VOLTAGE_AC,
            SensorCalibration::ZMPT107_MULTIPLIER,
            SensorCalibration::ZMPT107_OFFSET,
            true
        ),
        // Channel 1: Load current sensor on GPIO39 (ADC1_CH3)
        ADCChannelConfig(
            PIN_CURRENT_SENSOR_1,
            SensorType::CURRENT_LOAD,
            SensorCalibration::SCT013_030_MULTIPLIER,
            SensorCalibration::SCT013_030_OFFSET,
            true
        ),
        // Channel 2: Grid current sensor on GPIO36 (ADC1_CH0)
        ADCChannelConfig(
            PIN_CURRENT_SENSOR_2,
            SensorType::CURRENT_GRID,
            SensorCalibration::SCT013_030_MULTIPLIER,
            SensorCalibration::SCT013_030_OFFSET,
            true
        ),
        // Channel 3: Solar panel current sensor on GPIO34 (ADC1_CH6)
        ADCChannelConfig(
            PIN_CURRENT_SENSOR_3,
            SensorType::CURRENT_SOLAR,
            SensorCalibration::SCT013_030_MULTIPLIER,
            SensorCalibration::SCT013_030_OFFSET,
            true
        )
    };

    // ================================================================
    // STEP 7: Initialize DimmerHAL (CRITICAL!)
    // ================================================================
    Serial.println("Initializing DimmerHAL...");
    g_dimmer = &DimmerHAL::getInstance();

    if (!g_dimmer->begin(DimmerCurve::RMS)) {
        Serial.println("ERROR: Failed to initialize DimmerHAL!");
        Serial.println("System halted.");
        while(1) {
            delay(1000);
        }
    }
    Serial.print("DimmerHAL initialized, frequency=");
    Serial.print(g_dimmer->getMainsFrequency());
    Serial.println(" Hz");

    // ================================================================
    // STEP 8: Initialize RouterController (CRITICAL!)
    // ================================================================
    Serial.println("Initializing RouterController...");
    g_router = &RouterController::getInstance();

    if (!g_router->begin(g_dimmer, DimmerChannel::CHANNEL_1)) {
        Serial.println("ERROR: Failed to initialize RouterController!");
        Serial.println("System halted.");
        while(1) {
            delay(1000);
        }
    }

    // Apply configuration from NVS (or defaults)
    const SystemConfig& cfg = g_config->getConfig();
    g_router->setControlGain(cfg.control_gain);
    g_router->setBalanceThreshold(cfg.balance_threshold);
    g_router->setMode(static_cast(cfg.router_mode));
    if (cfg.router_mode == 2) {  // MANUAL mode
        g_router->setManualLevel(cfg.manual_level);
    }
    Serial.print("RouterController initialized: mode=");
    Serial.print(cfg.router_mode);
    Serial.print(", gain=");
    Serial.print(cfg.control_gain);
    Serial.print(", threshold=");
    Serial.print(cfg.balance_threshold);
    Serial.println(" W");

    // ================================================================
    // STEP 9: Initialize PowerMeterADC (CRITICAL!)
    // ================================================================
    Serial.println("Initializing PowerMeterADC...");
    g_powerMeter = &PowerMeterADC::getInstance();

    if (!g_powerMeter->begin(adc_channels, 4)) {
        Serial.println("ERROR: Failed to initialize PowerMeterADC!");
        Serial.println("System halted.");
        while(1) {
            delay(1000);
        }
    }

    // ================================================================
    // STEP 10: Register RMS Callback - MAIN SYSTEM DRIVER
    // ================================================================
    g_powerMeter->setResultsCallback([](const PowerMeterADC::Measurements& m,
                                         void* user_data) {
        // Called every 200 ms with new RMS data

        // Update RouterController
        g_router->update(m);

        // Additional logic can be added here:
        // - WebSocket transmission
        // - SD card writing
        // - etc.

    }, nullptr);

    // Start DMA ADC
    if (!g_powerMeter->start()) {
        Serial.println("ERROR: Failed to start PowerMeterADC!");
        Serial.println("System halted.");
        while(1) {
            delay(1000);
        }
    }

    Serial.println("PowerMeterADC started successfully");

    // ================================================================
    // STEP 11: Initialize SerialCommand processor
    // ================================================================
    g_serialCmd = &SerialCommand::getInstance();
    g_serialCmd->begin(g_config, g_router);

    // ================================================================
    // STEP 12: Initialize NTPManager (will be later in loop)
    // ================================================================
    g_ntp = &NTPManager::getInstance();

    Serial.println("System initialization complete");
    Serial.println("Power measurement running (callback-driven)");
}


6.7.3 Fonction loop()

cpp
void loop()
{
    // ================================================================
    // 1. Process serial commands
    // ================================================================
    g_serialCmd->process();

    // ================================================================
    // 2. Process WiFi events
    // ================================================================
    g_wifi->handle();

    // ================================================================
    // 3. Initialize NTP when STA connects and gets IP
    // ================================================================
    const WiFiStatus& ws = g_wifi->getStatus();
    if (!g_ntp_initialized && ws.sta_connected &&
        ws.sta_ip != IPAddress(0, 0, 0, 0)) {
        Serial.println("WiFi STA connected, initializing NTPManager...");
        // UTC+3 for Moscow, change for your timezone
        if (g_ntp->begin("pool.ntp.org",
                         "EET-2EEST,M3.5.0/3,M10.5.0/4",
                         3 * 3600, 3600)) {
            Serial.println("NTP started - Server: pool.ntp.org");
            g_ntp_initialized = true;
        } else {
            Serial.println("ERROR: Failed to initialize NTPManager!");
        }
    }

    // ================================================================
    // 4. Process WebServer requests
    // ================================================================
    g_webserver->handle();

    // ================================================================
    // 5. Process NTP synchronization (if initialized)
    // ================================================================
    if (g_ntp_initialized) {
        g_ntp->handle();
    }

    // ================================================================
    // 6. Statistics every 10 seconds
    // ================================================================
    uint32_t now = millis();
    if (now - g_last_stats >= 10000) {
        Serial.print("Statistics: Frames=");
        Serial.print(g_powerMeter->getFramesProcessed());
        Serial.print(", Dropped=");
        Serial.print(g_powerMeter->getFramesDropped());
        Serial.print(", RMS=");
        Serial.print(g_powerMeter->getRMSUpdateCount());
        Serial.print(", Freq=");
        Serial.print(g_dimmer->getMainsFrequency());
        Serial.println("Hz");

        // WiFi status
        if (ws.sta_connected) {
            Serial.print("WiFi STA: ");
            Serial.print(ws.sta_ssid);
            Serial.print(", IP=");
            Serial.print(ws.sta_ip.toString());
            Serial.print(", RSSI=");
            Serial.println(ws.rssi);
        }
        if (ws.ap_active) {
            Serial.print("WiFi AP: ");
            Serial.print(ws.ap_ssid);
            Serial.print(", IP=");
            Serial.print(ws.ap_ip.toString());
            Serial.print(", clients=");
            Serial.println(ws.sta_clients);
        }

        g_last_stats = now;
    }

    // ================================================================
    // 7. 100 ms delay for responsive serial input
    // ================================================================
    delay(100);
}


6.7.4 Comparaison : app_main() vs setup()/loop()

Aspect app_main() setup() + loop()
Point d'entrée extern "C" void app_main() void setup() + void loop()
Variables globales Non nécessaires (locales dans app_main) Nécessaires (pour accès depuis loop)
Boucle infinie while(1) explicite Appel automatique de loop()
Délai vTaskDelay(pdMS_TO_TICKS(100)) delay(100)
FreeRTOS Accès direct Via le wrapper Arduino
Compatibilité ESP-IDF uniquement Multiplateforme (ESP32, AVR, etc.)


6.7.5 Version Arduino minimale

Version simplifiée sans WiFi/WebServer pour l'IDE Arduino :

cpp
#include "Arduino.h"
#include "esp_log.h"
#include "PowerMeterADC.h"
#include "DimmerHAL.h"
#include "RouterController.h"
#include "ConfigManager.h"
#include "SerialCommand.h"
#include "PinDefinitions.h"
#include "SensorTypes.h"

static const char* TAG = "ACROUTER";

ConfigManager* g_config = nullptr;
SerialCommand* g_serialCmd = nullptr;
PowerMeterADC* g_powerMeter = nullptr;
DimmerHAL* g_dimmer = nullptr;
RouterController* g_router = nullptr;

void setup()
{
    Serial.begin(115200);
    delay(100);
    Serial.println("AC Power Router - Minimal Arduino Version");

    // ConfigManager
    g_config = &ConfigManager::getInstance();
    g_config->begin();

    // ADC channels
    static ADCChannelConfig adc_channels[4] = {
        ADCChannelConfig(PIN_VOLTAGE_SENSOR, SensorType::VOLTAGE_AC,
                         SensorCalibration::ZMPT107_MULTIPLIER,
                         SensorCalibration::ZMPT107_OFFSET, true),
        ADCChannelConfig(PIN_CURRENT_SENSOR_1, SensorType::CURRENT_LOAD,
                         SensorCalibration::SCT013_030_MULTIPLIER,
                         SensorCalibration::SCT013_030_OFFSET, true),
        ADCChannelConfig(PIN_CURRENT_SENSOR_2, SensorType::CURRENT_GRID,
                         SensorCalibration::SCT013_030_MULTIPLIER,
                         SensorCalibration::SCT013_030_OFFSET, true),
        ADCChannelConfig(PIN_CURRENT_SENSOR_3, SensorType::CURRENT_SOLAR,
                         SensorCalibration::SCT013_030_MULTIPLIER,
                         SensorCalibration::SCT013_030_OFFSET, true)
    };

    // DimmerHAL
    g_dimmer = &DimmerHAL::getInstance();
    if (!g_dimmer->begin(DimmerCurve::RMS)) {
        Serial.println("ERROR: DimmerHAL init failed!");
        while(1) { delay(1000); }
    }

    // RouterController
    g_router = &RouterController::getInstance();
    if (!g_router->begin(g_dimmer, DimmerChannel::CHANNEL_1)) {
        Serial.println("ERROR: RouterController init failed!");
        while(1) { delay(1000); }
    }

    const SystemConfig& cfg = g_config->getConfig();
    g_router->setControlGain(cfg.control_gain);
    g_router->setBalanceThreshold(cfg.balance_threshold);
    g_router->setMode(static_cast(cfg.router_mode));

    // PowerMeterADC
    g_powerMeter = &PowerMeterADC::getInstance();
    if (!g_powerMeter->begin(adc_channels, 4)) {
        Serial.println("ERROR: PowerMeterADC init failed!");
        while(1) { delay(1000); }
    }

    // RMS Callback
    g_powerMeter->setResultsCallback([](const PowerMeterADC::Measurements& m,
                                         void* user_data) {
        g_router->update(m);
    }, nullptr);

    if (!g_powerMeter->start()) {
        Serial.println("ERROR: PowerMeterADC start failed!");
        while(1) { delay(1000); }
    }

    // SerialCommand
    g_serialCmd = &SerialCommand::getInstance();
    g_serialCmd->begin(g_config, g_router);

    Serial.println("System ready");
}

void loop()
{
    g_serialCmd->process();
    delay(100);
}


6.7.6 Différences importantes du format Arduino

1. Pointeurs globaux :

cpp
// In app_main() we use local references:
ConfigManager& config = ConfigManager::getInstance();

// In setup()/loop() we need global pointers:
ConfigManager* g_config = nullptr;
g_config = &ConfigManager::getInstance();

2. Délais :

cpp
// app_main():
vTaskDelay(pdMS_TO_TICKS(100));

// loop():
delay(100);

3. Boucle infinie :

cpp
// app_main() - explicit loop:
while(1) {
    // ...
}

// loop() - called automatically:
void loop() {
    // ... code executes infinitely
}

4. Variables statiques dans setup() :

cpp
// For use in callback, static is needed:
static ADCChannelConfig adc_channels[4] = { ... };

// Otherwise the array will be deleted after exiting setup()!


6.7.7 Quand utiliser quel format ?

Use app_main() if:
- ✅ Working in ESP-IDF framework
- ✅ Need full control over FreeRTOS
- ✅ Project is ESP32 only
- ✅ Using advanced ESP-IDF features

Use setup()/loop() if:
- ✅ Working in Arduino IDE
- ✅ Using PlatformIO: check compatible of PlatformIO with ESP32 Arduino core 3.x
- ✅ Need compatibility with Arduino libraries
- ✅ More comfortable with Arduino code style
- ✅ Planning to port to other platforms




6.8 Tâches FreeRTOS

Although main.cpp doesn't explicitly create FreeRTOS tasks, they are created inside the components:


Tableau des tâches système

Tâche Composant Priorité Pile Objectif
arduino_loop Arduino Core 1 8192 Émulation de loop() Arduino
dimmer_task DimmerHAL 10 4096 Synchronisation passage par zéro
adc_dma_task PowerMeterADC 8 4096 Traitement des tampons DMA
wifi_task WiFiManager 5 4096 Événements WiFi, reconnexion
httpd_task WebServerManager 5 8192 Serveur HTTP/WebSocket
ntp_task NTPManager 3 2048 Synchronisation NTP

Priorities (0 - lowest, 24 - highest):
- Dimmer task (10): Highest - zero-crossing timing is critical
- ADC DMA task (8): High - cannot miss DMA events
- WiFi/HTTP (5): Medium - delays are not critical
- NTP (3): Low - synchronization once per hour
- Arduino loop (1): Minimum - not used in this project




6.9 Gestion des erreurs


Composants critiques (arrêt du système)

En cas d'erreur d'initialisation des composants critiques, le système s'arrête :

cpp
if (!dimmer.begin(DimmerCurve::RMS)) {
    ESP_LOGE(TAG, "Failed to initialize DimmerHAL!");
    ESP_LOGE(TAG, "System halted.");
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Critical components:
- DimmerHAL
- RouterController
- PowerMeterADC

Halt reasons:
- No zero-crossing signal (220V mains not connected)
- ADC conflict (another component uses ADC1)
- GPIO conflict


Composants non critiques (poursuite du fonctionnement)

En cas d'erreur des composants non critiques, le système continue :

cpp
if (!config.begin()) {
    ESP_LOGE(TAG, "Failed to initialize ConfigManager!");
    ESP_LOGW(TAG, "Using default values");
    // Continue with defaults
}

Non-critical components:
- ConfigManager (defaults)
- WiFiManager (AP-only mode)
- WebServerManager (Serial control)
- NTPManager (no time)




6.10 Exemples de personnalisation


Exemple 1 : Journalisation des mesures sur Serial

Ajouter la sortie des données au callback RMS :

cpp
powerMeter.setResultsCallback([](const PowerMeterADC::Measurements& m,
                                  void* user_data) {
    static uint32_t callback_count = 0;
    callback_count++;

    // Update RouterController
    RouterController& router = RouterController::getInstance();
    router.update(m);

    // Log every 5 callbacks (1 second)
    if (callback_count % 5 == 0) {
        ESP_LOGI(TAG, "Voltage: %.1f V", m.voltage_rms);
        ESP_LOGI(TAG, "Power Grid: %.0f W",
                 m.power_active[PowerMeterADC::CURRENT_GRID]);
        ESP_LOGI(TAG, "Power Solar: %.0f W",
                 m.power_active[PowerMeterADC::CURRENT_SOLAR]);

        const RouterStatus& status = router.getStatus();
        ESP_LOGI(TAG, "Dimmer: %d%%", status.dimmer_percent);
    }
}, nullptr);


Exemple 2 : Envoi de données via WebSocket

Ajouter la transmission de données en temps réel :

cpp
powerMeter.setResultsCallback([](const PowerMeterADC::Measurements& m,
                                  void* user_data) {
    RouterController& router = RouterController::getInstance();
    router.update(m);

    // Every callback (200 ms) send to WebSocket
    WebServerManager& ws = WebServerManager::getInstance();

    StaticJsonDocument<256> doc;
    doc["voltage"] = m.voltage_rms;
    doc["power_grid"] = m.power_active[PowerMeterADC::CURRENT_GRID];
    doc["power_solar"] = m.power_active[PowerMeterADC::CURRENT_SOLAR];
    doc["dimmer"] = router.getStatus().dimmer_percent;

    String json;
    serializeJson(doc, json);
    ws.broadcastWebSocket(json);

}, nullptr);


Exemple 3 : Écriture sur carte SD

Journalisation des données sur carte SD pour analyse :

cpp
#include "SD.h"
#include "SPI.h"

// In setup (after initialization):
if (!SD.begin(5)) {  // CS pin = GPIO5
    ESP_LOGE(TAG, "SD card mount failed!");
} else {
    ESP_LOGI(TAG, "SD card mounted");
}

// In RMS callback:
powerMeter.setResultsCallback([](const PowerMeterADC::Measurements& m,
                                  void* user_data) {
    static uint32_t count = 0;
    count++;

    RouterController& router = RouterController::getInstance();
    router.update(m);

    // Write every 5 seconds (25 callbacks)
    if (count % 25 == 0) {
        File dataFile = SD.open("/datalog.csv", FILE_APPEND);
        if (dataFile) {
            char buf[128];
            snprintf(buf, sizeof(buf), "%lu,%.1f,%.0f,%.0f,%d\n",
                     millis(),
                     m.voltage_rms,
                     m.power_active[PowerMeterADC::CURRENT_GRID],
                     m.power_active[PowerMeterADC::CURRENT_SOLAR],
                     router.getStatus().dimmer_percent);
            dataFile.print(buf);
            dataFile.close();
        }
    }
}, nullptr);


Exemple 4 : Commande Serial personnalisée

Ajouter votre propre commande à SerialCommand :

cpp
// After serialCmd.begin():
serialCmd.registerCommand("test", [](const char* args) {
    ESP_LOGI(TAG, "Test command executed with args: %s", args);
    Serial.println("OK");
});

// Now you can call:
// test hello world



6.11 Liste de vérification pour le débogage

En cas de problèmes au démarrage, vérifiez :

  • ☐ **Sortie Serial** : USB-UART est-il connecté, vitesse correcte (115200) ?
  • ☐ **Alimentation 220V** : L'alimentation est-elle connectée pour le détecteur de passage par zéro ?
  • ☐ **Conflits GPIO** : Les broches sont-elles utilisées par d'autres composants ?
  • ☐ **Partition NVS** : Y a-t-il une partition NVS dans la table de partitions ?
  • ☐ **Taille Flash** : 4 Mo de Flash sont-ils suffisants pour l'application ?
  • ☐ **Canaux ADC** : Les capteurs sont-ils correctement connectés aux GPIO 32-39 ?
  • ☐ **Identifiants WiFi** : Ont-ils été sauvegardés via commande Serial ?
  • ☐ **Fréquence secteur** : 50/60 Hz est-il détecté correctement ?
  • ☐ **Trames perdues** : `Dropped=0` dans les statistiques ?
  • ☐ **Mémoire** : Y a-t-il assez de mémoire heap libre ?



  • 6.12 Recommandations pour les modifications


    ✅ Modifications sûres :

    1. Ajouter de la journalisation au callback RMS
    2. Modifier la fréquence des statistiques (de 10 secondes à une autre valeur)
    3. Ajouter des commandes Serial via serialCmd.registerCommand()
    4. Modifier les identifiants WiFi dans le code ou NVS
    5. Modifier le serveur NTP et le fuseau horaire


    ⚠️ Avec précaution :

    1. Modifier la fréquence d'échantillonnage ADC — peut perturber les calculs RMS
    2. Modifier l'ordre d'initialisation — tenir compte des dépendances
    3. Ajouter des opérations lourdes au callback RMS — le callback doit être rapide (< 50 ms)
    4. Modifier les priorités des tâches FreeRTOS — peut perturber la synchronisation du passage par zéro


    ❌ Dangereux (peut casser le système) :

    1. Supprimer des composants critiques (DimmerHAL, RouterController, PowerMeterADC)
    2. Modifier l'algorithme de passage par zéro dans DimmerHAL
    3. Utiliser ADC1 à d'autres fins (conflits avec PowerMeterADC)
    4. Opérations bloquantes dans le callback RMS (delay, longues boucles)



    6.13 Résumé

    Le fichier principal main.cpp implémente :

    1. Initialisation séquentielle de tous les composants en tenant compte des dépendances
    2. Architecture pilotée par callbacks pour la commande de puissance (toutes les 200 ms)
    3. Boucle principale non bloquante pour la gestion de l'interface (Serial, WiFi, Web)
    4. Dégradation gracieuse — le système fonctionne même avec des erreurs de composants non critiques
    5. Initialisation différée de NTP lors de la connexion au réseau

    Caractéristique clé : La commande de puissance a lieu à l'intérieur du callback RMS, indépendamment de la boucle principale. La boucle principale ne gère que l'interface utilisateur.


    ← Référence API | Sommaire | Suivant : Commandes terminal →