← Riferimento API | Indice | Avanti: Comandi Terminale →
6. File dell'Applicazione Principale (main.cpp)
6.1 Panoramica
Il file main/main.cpp è il punto di ingresso dell'applicazione AC Power Router. Implementa la funzione app_main() (punto di ingresso standard ESP-IDF) e svolge le seguenti operazioni:
- Inizializzazione di Arduino Core — per la compatibilità con le librerie Arduino
- Inizializzazione sequenziale di tutti i componenti di sistema in ordine rigoroso
- Configurazione del meccanismo di callback per il ciclo principale di elaborazione della potenza
- Ciclo principale dell'applicazione — gestione dei comandi, WiFi, web server
Architettura di Funzionamento
Il sistema opera con un'architettura basata su callback:
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)
Importante: La logica principale di controllo della potenza viene eseguita all'interno del callback RMS, che viene chiamato ogni 200 ms indipendentemente dal ciclo principale. Il ciclo principale gestisce solo l'interfaccia utente (Serial, WiFi, Web).
6.2 Ordine di Inizializzazione dei Componenti
I componenti vengono inizializzati in un ordine rigorosamente definito tenendo conto delle dipendenze:
Diagramma delle Dipendenze
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)
Tabella di Criticità dei Componenti
| Componente | Criticità | Azione in Caso di Errore | Dipendenze |
|---|---|---|---|
| Arduino Core | CRITICO | System won't start | - |
| ConfigManager | Media | Vengono usati i valori predefiniti | Arduino |
| WiFiManager | Bassa | Funzionamento solo in modalità AP | Config |
| WebServerManager | Bassa | Nessuna interfaccia web | WiFi |
| DimmerHAL | CRITICO | Sistema arrestato (while(1)) | Arduino |
| RouterController | CRITICO | Sistema arrestato (while(1)) | DimmerHAL, Config |
| PowerMeterADC | CRITICO | Sistema arrestato (while(1)) | RouterController |
| SerialCommand | Bassa | Nessuna interfaccia Serial | Config, Router |
| NTPManager | Bassa | Nessuna sincronizzazione orario | WiFi STA |
6.3 Versione Base di main.cpp
Di seguito una versione base semplificata di main.cpp con commenti:
/**
* @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 Funzione app_main() — Analisi Dettagliata
6.4.1 Inizializzazione di Arduino Core
extern "C" void app_main()
{
initArduino();
Scopo: Inizializza il livello di compatibilità Arduino sopra 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 Configurazione del Debug Seriale
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
Output all'avvio:
========================================
AC Power Router Controller
ESP-IDF Version: v5.5.1
========================================
6.4.3 Inizializzazione di ConfigManager
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à: Bassa — il sistema prosegue con i valori predefiniti.
Valori predefiniti (da ConfigManager.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 Inizializzazione di WiFiManager
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
Due opzioni di configurazione:
Opzione 1: Credenziali codificate nel sorgente (per test)
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!");
}
Opzione 2: Da NVS tramite comando Serial (consigliata)
# Via Serial command:
wifi-connect MyNetwork MyPassword123
Le credenziali vengono salvate in NVS e caricate automaticamente a ogni avvio.
Criticità: Bassa — il sistema funziona senza rete in modalità solo AP.
6.4.5 Inizializzazione di WebServerManager
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à: Bassa — è possibile controllare tramite Serial.
6.4.6 Configurazione dei Canali ADC
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
};
Struttura ADCChannelConfig:
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)
Pin GPIO (da PinDefinitions.h):
#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 Inizializzazione di DimmerHAL (CRITICO!)
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à: MASSIMA — senza DimmerHAL il controllo della potenza è impossibile. In caso di errore, il sistema si arresta (while(1)).
Possible errors:
- No zero-crossing signal (mains not connected)
- Zero-crossing detector malfunction (H11AA1)
- GPIO conflict
6.4.8 Inizializzazione di RouterController (CRITICO!)
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 Inizializzazione di PowerMeterADC (CRITICO!)
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à: MASSIMA — senza PowerMeterADC non ci sono misurazioni di potenza.
6.4.10 Registrazione del Callback RMS — Driver Principale del Sistema
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);
Frequenza di chiamata: Ogni 200 ms (5 volte al secondo)
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)
- RouterController analizza la modalità:
- AUTO: P_grid → 0 (controllore proporzionale)
- ECO: P_grid ≤ 0 (anti-esportazione)
- OFFGRID: P_load ≤ 0.8 × P_solar
- MANUAL: Livello fisso
- BOOST: Potenza al 100%
-
OFF: Potenza allo 0%
-
DimmerHAL imposta il livello di potenza sul TRIAC
IMPORTANTE: Questo è l'unico punto nel codice dove avviene il controllo della potenza! Il ciclo principale gestisce solo l'interfaccia utente.
6.4.11 Avvio di PowerMeterADC
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
Da questo punto, il sistema è completamente basato su callback — il controllo della potenza funziona indipendentemente dal ciclo principale.
6.4.12 Inizializzazione di SerialCommand
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
Esempi di comandi:
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
L'elenco completo dei comandi sarà nella prossima sezione della documentazione (07_COMMANDS.md).
6.5 Ciclo Principale
Dopo l'inizializzazione di tutti i componenti, si avvia il ciclo principale infinito:
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));
}
Operazioni del Ciclo Principale
| Operazione | Frequenza | Scopo |
|---|---|---|
serialCmd.process() |
Ogni 100 ms | Elaborazione comandi da Serial UART |
wifi.handle() |
Ogni 100 ms | Riconnessione, keepalive AP, eventi |
| Inizializzazione NTP | Una volta alla connessione STA | Avvio sincronizzazione orario |
webserver.handle() |
Ogni 100 ms | Elaborazione richieste HTTP/WebSocket |
ntp.handle() |
Ogni 100 ms | Sincronizzazione periodica (oraria) |
| Statistiche | Ogni 10 secondi | Logging seriale |
Importante: Il ciclo principale NON gestisce il controllo della potenza! Questo viene fatto dal callback RMS ogni 200 ms in modo indipendente.
6.5.1 NTP Manager — Inizializzazione Differita
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)
Modifica per altri fusi orari:
// 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 Statistiche Ogni 10 Secondi
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;
}
Output seriale:
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 Versione Minimale (senza WiFi e WebServer)
For systems that don't need network interface:
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 Applicazione in Formato Arduino
Per chi preferisce il formato Arduino standard con le funzioni setup() e loop(), di seguito è riportato come organizzare il codice in questo stile.
Importante: In ESP-IDF con Arduino Core, le funzioni setup() e loop() vengono chiamate automaticamente da app_main(). Se si crea un file main.cpp con app_main(), non è possibile usare setup() e loop() — sono in conflitto. Ma se si lavora in Arduino IDE con il framework Arduino, utilizzare il formato seguente.
6.7.1 Variabili Globali
/**
* @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 Funzione setup()
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 Funzione loop()
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 Confronto: app_main() vs setup()/loop()
| Aspetto | app_main() | setup() + loop() |
|---|---|---|
| Punto di ingresso | extern "C" void app_main() |
void setup() + void loop() |
| Variabili globali | Non necessarie (locali in app_main) | Necessarie (per l'accesso da loop) |
| Ciclo infinito | while(1) esplicito |
Chiamata automatica di loop() |
| Ritardo | vTaskDelay(pdMS_TO_TICKS(100)) |
delay(100) |
| FreeRTOS | Accesso diretto | Tramite wrapper Arduino |
| Compatibilità | Solo ESP-IDF | Multipiattaforma (ESP32, AVR, ecc.) |
6.7.5 Versione Arduino Minimale
Versione semplificata senza WiFi/WebServer per Arduino IDE:
#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 Differenze Importanti del Formato Arduino
1. Puntatori globali:
// 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. Ritardi:
// app_main():
vTaskDelay(pdMS_TO_TICKS(100));
// loop():
delay(100);
3. Ciclo infinito:
// app_main() - explicit loop:
while(1) {
// ...
}
// loop() - called automatically:
void loop() {
// ... code executes infinitely
}
4. Variabili statiche in setup():
// For use in callback, static is needed:
static ADCChannelConfig adc_channels[4] = { ... };
// Otherwise the array will be deleted after exiting setup()!
6.7.7 Quando Usare Ciascun Formato?
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 Task FreeRTOS
Although main.cpp doesn't explicitly create FreeRTOS tasks, they are created inside the components:
Tabella dei Task di Sistema
| Operazione | Componente | Priorità | Stack | Scopo |
|---|---|---|---|---|
arduino_loop |
Arduino Core | 1 | 8192 | Emulazione loop() di Arduino |
dimmer_task |
DimmerHAL | 10 | 4096 | Sincronizzazione passaggio per lo zero |
adc_dma_task |
PowerMeterADC | 8 | 4096 | Elaborazione buffer DMA |
wifi_task |
WiFiManager | 5 | 4096 | Eventi WiFi, riconnessione |
httpd_task |
WebServerManager | 5 | 8192 | Server HTTP/WebSocket |
ntp_task |
NTPManager | 3 | 2048 | Sincronizzazione 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 Gestione degli Errori
Componenti Critici (arresto del sistema)
In caso di errore nell'inizializzazione dei componenti critici, il sistema si arresta:
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
Componenti Non Critici (proseguimento del funzionamento)
In caso di errore dei componenti non critici, il sistema prosegue:
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 Esempi di Personalizzazione
Esempio 1: Registrazione delle Misurazioni su Serial
Aggiungere l'output dei dati al callback RMS:
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);
Esempio 2: Invio Dati via WebSocket
Aggiungere la trasmissione dati in tempo reale:
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);
Esempio 3: Scrittura su Scheda SD
Registrazione dati su scheda SD per l'analisi:
#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);
Esempio 4: Comando Serial Personalizzato
Aggiungere un proprio comando a SerialCommand:
// 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 Checklist di Debug
In caso di problemi all'avvio, verificare:
6.12 Raccomandazioni per le Modifiche
✅ Modifiche sicure:
- Aggiunta di logging al callback RMS
- Modifica della frequenza delle statistiche (da 10 secondi a un altro valore)
- Aggiunta di comandi Serial tramite
serialCmd.registerCommand() - Modifica delle credenziali WiFi nel codice o in NVS
- Modifica del server NTP e del fuso orario
⚠️ Con cautela:
- Modifica della frequenza di campionamento ADC — può alterare i calcoli RMS
- Modifica dell'ordine di inizializzazione — considerare le dipendenze
- Aggiunta di operazioni pesanti al callback RMS — il callback deve essere rapido (< 50 ms)
- Modifica delle priorità dei task FreeRTOS — può alterare la sincronizzazione del passaggio per lo zero
❌ Pericoloso (può compromettere il sistema):
- Rimozione di componenti critici (DimmerHAL, RouterController, PowerMeterADC)
- Modifica dell'algoritmo di passaggio per lo zero in DimmerHAL
- Utilizzo di ADC1 per altri scopi (conflitto con PowerMeterADC)
- Operazioni bloccanti nel callback RMS (delay, cicli lunghi)
6.13 Riepilogo
Il file principale main.cpp implementa:
- Inizializzazione sequenziale di tutti i componenti tenendo conto delle dipendenze
- Architettura basata su callback per il controllo della potenza (ogni 200 ms)
- Ciclo principale non bloccante per la gestione dell'interfaccia (Serial, WiFi, Web)
- Degradazione graduale — il sistema funziona anche con errori dei componenti non critici
- Inizializzazione differita di NTP al momento della connessione alla rete
Caratteristica principale: Il controllo della potenza avviene all'interno del callback RMS, indipendentemente dal ciclo principale. Il ciclo principale gestisce solo l'interfaccia utente.