This commit is contained in:
2025-08-24 11:17:48 +01:00
parent 0d0dc5b129
commit 96b2ab1f57
31 changed files with 2883 additions and 4054 deletions

View File

@@ -11,7 +11,7 @@
#include "meter_events.h"
#include "evse_events.h"
#include "math.h"
#include <inttypes.h> // Necessário para PRIu64
#include <inttypes.h> // Necessário para PRIu64
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -20,10 +20,10 @@
static const char *TAG = "loadbalancer";
// Limites configuráveis
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
#define MIN_GRID_CURRENT_LIMIT 6 // A
#define MAX_GRID_CURRENT_LIMIT 100 // A
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
#define MIN_GRID_CURRENT_LIMIT 6 // A
#define MAX_GRID_CURRENT_LIMIT 100 // A
// Parâmetros
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
@@ -38,15 +38,16 @@ static input_filter_t evse_filter;
#define CONNECTOR_COUNT (MAX_SLAVES + 1)
// Estrutura unificada para master e slaves
typedef struct {
uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave
bool is_master;
bool charging;
float hw_max_current;
float runtime_current;
int64_t timestamp; // microssegundos
bool online;
float assigned;
typedef struct
{
uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave
bool is_master;
bool charging;
float hw_max_current;
float runtime_current;
int64_t timestamp; // microssegundos
bool online;
float assigned;
} evse_connector_t;
static evse_connector_t connectors[CONNECTOR_COUNT];
@@ -58,27 +59,26 @@ static void init_connectors(void)
{
// master em índice 0
connectors[0] = (evse_connector_t){
.id = 0xFF,
.is_master = true,
.charging = false,
.id = 0xFF,
.is_master = true,
.charging = false,
.hw_max_current = MAX_CHARGING_CURRENT_LIMIT,
.runtime_current = 0,
.timestamp = 0,
.online = false,
.assigned = 0.0f
};
.timestamp = 0,
.online = false,
.assigned = 0.0f};
// slaves em 1..CONNECTOR_COUNT-1
for (int i = 1; i < CONNECTOR_COUNT; i++) {
for (int i = 1; i < CONNECTOR_COUNT; i++)
{
connectors[i] = (evse_connector_t){
.id = (uint8_t)(i - 1),
.is_master = false,
.charging = false,
.id = (uint8_t)(i - 1),
.is_master = false,
.charging = false,
.hw_max_current = 0.0f,
.runtime_current = 0.0f,
.timestamp = 0,
.online = false,
.assigned = 0.0f
};
.timestamp = 0,
.online = false,
.assigned = 0.0f};
}
}
@@ -93,17 +93,18 @@ static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id
{
const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data;
if (status->slave_id >= MAX_SLAVES) {
if (status->slave_id >= MAX_SLAVES)
{
ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id);
return;
}
int idx = status->slave_id + 1; // slaves começam no índice 1
connectors[idx].charging = status->charging;
connectors[idx].hw_max_current = status->hw_max_current;
connectors[idx].charging = status->charging;
connectors[idx].hw_max_current = status->hw_max_current;
connectors[idx].runtime_current = status->runtime_current;
connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true;
connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true;
ESP_LOGI(TAG,
"Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA",
@@ -111,31 +112,24 @@ static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id
status->hw_max_current, status->runtime_current);
}
static void on_evse_config_event(void* handler_arg,
static void on_evse_config_event(void *handler_arg,
esp_event_base_t base,
int32_t id,
void* event_data)
void *event_data)
{
const evse_config_event_data_t *evt = (const evse_config_event_data_t*) event_data;
const evse_config_event_data_t *evt = (const evse_config_event_data_t *)event_data;
int idx = 0; // MASTER INDICE 0
connectors[idx].charging = evt->charging;
connectors[idx].hw_max_current = evt->hw_max_current;
connectors[idx].charging = evt->charging;
connectors[idx].hw_max_current = evt->hw_max_current;
connectors[idx].runtime_current = evt->runtime_current;
connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true;
connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true;
ESP_LOGI(TAG, "EVSE config updated: charging=%d hw_max_current=%.1f runtime_current=%.1f",
evt->charging, evt->hw_max_current, evt->runtime_current);
}
// --- Handlers de eventos externos ---
static void loadbalancer_meter_event_handler(void *handler_arg,
esp_event_base_t base,
@@ -149,13 +143,18 @@ static void loadbalancer_meter_event_handler(void *handler_arg,
for (int i = 1; i < 3; ++i)
if (evt->irms[i] > max_irms)
max_irms = evt->irms[i];
if (evt->source && strcmp(evt->source, "GRID") == 0) {
if (evt->source && strcmp(evt->source, "GRID") == 0)
{
grid_current = input_filter_update(&grid_filter, max_irms);
ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current);
} else if (evt->source && strcmp(evt->source, "EVSE") == 0) {
}
else if (evt->source && strcmp(evt->source, "EVSE") == 0)
{
evse_current = input_filter_update(&evse_filter, max_irms);
ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
} else {
}
else
{
ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source);
}
}
@@ -175,7 +174,7 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
evt->state == EVSE_STATE_EVENT_IDLE ? "IDLE" : "WAITING",
evt->state == EVSE_STATE_EVENT_IDLE ? "dis" : "");
connectors[0].charging = false;
connectors[0].online = true; // master está sempre online
connectors[0].online = true; // master está sempre online
break;
case EVSE_STATE_EVENT_CHARGING:
@@ -184,15 +183,15 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
evse_current = 0.0f;
input_filter_reset(&grid_filter);
input_filter_reset(&evse_filter);
connectors[0].charging = true;
connectors[0].online = true;
connectors[0].charging = true;
connectors[0].online = true;
connectors[0].timestamp = esp_timer_get_time();
break;
case EVSE_STATE_EVENT_FAULT:
ESP_LOGW(TAG, "Local EVSE is in FAULT state - disabling load balancing temporarily");
connectors[0].charging = false;
connectors[0].online = true; // EVSE está online mas com falha
connectors[0].online = true; // EVSE está online mas com falha
break;
default:
@@ -201,8 +200,6 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
}
}
// --- Config persistência ---
static esp_err_t loadbalancer_load_config()
{
@@ -215,7 +212,8 @@ static esp_err_t loadbalancer_load_config()
err = nvs_get_u8(handle, "max_grid_curr", &temp_u8);
if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
max_grid_current = temp_u8;
else {
else
{
max_grid_current = MAX_GRID_CURRENT_LIMIT;
nvs_set_u8(handle, "max_grid_curr", max_grid_current);
needs_commit = true;
@@ -223,7 +221,8 @@ static esp_err_t loadbalancer_load_config()
err = nvs_get_u8(handle, "enabled", &temp_u8);
if (err == ESP_OK && temp_u8 <= 1)
loadbalancer_enabled = (temp_u8 != 0);
else {
else
{
loadbalancer_enabled = false;
nvs_set_u8(handle, "enabled", 0);
needs_commit = true;
@@ -238,13 +237,14 @@ static esp_err_t loadbalancer_load_config()
void loadbalancer_set_enabled(bool enabled)
{
nvs_handle_t handle;
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK) {
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK)
{
nvs_set_u8(handle, "enabled", enabled ? 1 : 0);
nvs_commit(handle);
nvs_close(handle);
}
loadbalancer_enabled = enabled;
loadbalancer_state_event_t evt = { .enabled = enabled, .timestamp_us = esp_timer_get_time() };
loadbalancer_state_event_t evt = {.enabled = enabled, .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
&evt, sizeof(evt), portMAX_DELAY);
}
@@ -276,8 +276,10 @@ bool loadbalancer_is_enabled(void)
// --- Task principal ---
void loadbalancer_task(void *param)
{
while (true) {
if (!loadbalancer_is_enabled()) {
while (true)
{
if (!loadbalancer_is_enabled())
{
vTaskDelay(pdMS_TO_TICKS(5000));
continue;
}
@@ -287,16 +289,19 @@ void loadbalancer_task(void *param)
int64_t now = esp_timer_get_time();
// --- Atualiza estado online e conta ativos ---
for (int i = 0; i < CONNECTOR_COUNT; i++) {
for (int i = 0; i < CONNECTOR_COUNT; i++)
{
// --- Master nunca pode ficar offline ---
if (connectors[i].is_master) {
if (connectors[i].is_master)
{
connectors[i].online = true;
ESP_LOGI(TAG, "Connector[%d] ONLINE (MASTER, charging=%d, hw_max_current=%.1f)",
i, connectors[i].charging, connectors[i].hw_max_current);
if (connectors[i].charging) {
if (connectors[i].charging)
{
idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
}
@@ -304,12 +309,14 @@ void loadbalancer_task(void *param)
}
// --- Ignora conectores já marcados como offline ---
if (!connectors[i].online) {
if (!connectors[i].online)
{
continue;
}
// --- Timeout de heartbeat para escravos ---
if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US) {
if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US)
{
connectors[i].online = false;
ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)",
i, connectors[i].charging, (long long)(now - connectors[i].timestamp));
@@ -320,7 +327,8 @@ void loadbalancer_task(void *param)
i, connectors[i].charging, connectors[i].hw_max_current,
(long long)(now - connectors[i].timestamp));
if (connectors[i].charging) {
if (connectors[i].charging)
{
idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
}
@@ -330,17 +338,23 @@ void loadbalancer_task(void *param)
// --- Calcula corrente disponível ---
float available = max_grid_current - grid_current;
if (available < MIN_CHARGING_CURRENT_LIMIT) {
if (available < MIN_CHARGING_CURRENT_LIMIT)
{
available = MIN_CHARGING_CURRENT_LIMIT;
} else if (available > max_grid_current) {
}
else if (available > max_grid_current)
{
available = max_grid_current;
}
ESP_LOGI(TAG, "LB Calc: available=%.1fA, active_connectors=%d", available, active_cnt);
// --- Ordena conectores por hw_max_current (bubble sort simples) ---
for (int a = 0; a < active_cnt - 1; a++) {
for (int b = 0; b < active_cnt - 1 - a; b++) {
if (connectors[idxs[b]].hw_max_current > connectors[idxs[b + 1]].hw_max_current) {
for (int a = 0; a < active_cnt - 1; a++)
{
for (int b = 0; b < active_cnt - 1 - a; b++)
{
if (connectors[idxs[b]].hw_max_current > connectors[idxs[b + 1]].hw_max_current)
{
int tmp = idxs[b];
idxs[b] = idxs[b + 1];
idxs[b + 1] = tmp;
@@ -351,15 +365,20 @@ void loadbalancer_task(void *param)
// --- Distribui corrente (water-filling) ---
float remaining = available;
int remaining_cnt = active_cnt;
for (int k = 0; k < active_cnt; k++) {
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
float share = remaining / remaining_cnt;
if (share >= connectors[i].hw_max_current) {
if (share >= connectors[i].hw_max_current)
{
connectors[i].assigned = connectors[i].hw_max_current;
remaining -= connectors[i].assigned;
remaining_cnt--;
} else {
for (int m = k; m < active_cnt; m++) {
}
else
{
for (int m = k; m < active_cnt; m++)
{
connectors[idxs[m]].assigned = share;
}
break;
@@ -367,39 +386,44 @@ void loadbalancer_task(void *param)
}
// --- Aplica piso mínimo ---
for (int k = 0; k < active_cnt; k++) {
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
if (connectors[i].assigned < MIN_CHARGING_CURRENT_LIMIT) {
if (connectors[i].assigned < MIN_CHARGING_CURRENT_LIMIT)
{
connectors[i].assigned = MIN_CHARGING_CURRENT_LIMIT;
}
}
// --- Publica limites de corrente ---
for (int k = 0; k < active_cnt; k++) {
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
uint16_t max_cur = (uint16_t)MIN(connectors[i].assigned, MAX_CHARGING_CURRENT_LIMIT);
uint16_t current_rounded = (uint16_t)roundf(connectors[i].runtime_current);
if (current_rounded == max_cur) {
if (current_rounded == max_cur)
{
continue; // sem alteração
}
if (connectors[i].is_master) {
if (connectors[i].is_master)
{
loadbalancer_master_limit_event_t master_evt = {
.slave_id = connectors[i].id,
.max_current = max_cur,
.timestamp_us = now
};
.slave_id = connectors[i].id,
.max_current = max_cur,
.timestamp_us = now};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
&master_evt, sizeof(master_evt), portMAX_DELAY);
ESP_LOGI(TAG, "Master limit changed -> %.1f A (runtime=%.1f A)",
(float)max_cur, connectors[i].runtime_current);
} else {
}
else
{
loadbalancer_slave_limit_event_t slave_evt = {
.slave_id = connectors[i].id,
.max_current = max_cur,
.timestamp_us = now
};
.slave_id = connectors[i].id,
.max_current = max_cur,
.timestamp_us = now};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
&slave_evt, sizeof(slave_evt), portMAX_DELAY);
ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (runtime=%.1f A)",
@@ -432,9 +456,9 @@ void loadbalancer_init(void)
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
&loadbalancer_evse_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS,EVSE_EVENT_CONFIG_UPDATED,
&on_evse_config_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_CONFIG_UPDATED,
&on_evse_config_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS,LOADBALANCER_EVENT_SLAVE_STATUS,
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_STATUS,
&on_slave_status, NULL));
}