evse_link feature
This commit is contained in:
@@ -11,67 +11,32 @@ extern "C" {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initializes the load balancer.
|
||||
*
|
||||
* This function configures the load balancer and its resources, including
|
||||
* any necessary persistence configurations, such as storage in NVS (Non-Volatile Storage).
|
||||
* This function prepares the system to perform load balancing efficiently.
|
||||
* @brief Inicializa o módulo de load balancer
|
||||
*/
|
||||
void loadbalancer_init(void);
|
||||
|
||||
/**
|
||||
* @brief Continuous task for the load balancer.
|
||||
*
|
||||
* This function executes the load balancing logic continuously, typically in a FreeRTOS task.
|
||||
* It performs balance calculations, checks the grid current and energy conditions, and adjusts
|
||||
* the outputs as necessary to ensure efficient energy consumption.
|
||||
*
|
||||
* @param param Input parameter, usually used to pass additional information or relevant context
|
||||
* for the task execution.
|
||||
* @brief Task contínua do algoritmo de balanceamento
|
||||
*/
|
||||
void loadbalancer_task(void *param);
|
||||
|
||||
/**
|
||||
* @brief Enables or disables the load balancing system.
|
||||
*
|
||||
* This function allows enabling or disabling the load balancing system. When enabled, the load
|
||||
* balancer starts managing the grid current based on the configured limits. If disabled, the system
|
||||
* operates without balancing.
|
||||
*
|
||||
* The configuration is persisted in NVS, ensuring that the choice is maintained across system restarts.
|
||||
*
|
||||
* @param value If true, enables load balancing. If false, disables it.
|
||||
* @brief Ativa ou desativa o load balancing
|
||||
*/
|
||||
void loadbalancer_set_enabled(bool value);
|
||||
|
||||
/**
|
||||
* @brief Checks if load balancing is enabled.
|
||||
*
|
||||
* This function returns the current status of the load balancing system.
|
||||
*
|
||||
* @return Returns true if load balancing is enabled, otherwise returns false.
|
||||
* @brief Verifica se o load balancing está ativo
|
||||
*/
|
||||
bool loadbalancer_is_enabled(void);
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum grid current.
|
||||
*
|
||||
* This function configures the maximum grid current that can be supplied to the load balancing system.
|
||||
* The value set ensures that the system does not overload the electrical infrastructure and respects
|
||||
* the safety limits.
|
||||
*
|
||||
* @param max_grid_current The maximum allowed current (in amperes) for the load balancing system.
|
||||
* This value should be appropriate for the grid capacity and the installation.
|
||||
* @brief Define a corrente máxima do grid
|
||||
*/
|
||||
esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets the maximum grid current.
|
||||
*
|
||||
* This function retrieves the current maximum grid current limit.
|
||||
*
|
||||
* @return The maximum grid current (in amperes).
|
||||
* @brief Obtém a corrente máxima do grid
|
||||
*/
|
||||
uint8_t load_balancing_get_max_grid_current(void);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "esp_event.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_timer.h"
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS);
|
||||
@@ -9,16 +9,39 @@ ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS);
|
||||
typedef enum {
|
||||
LOADBALANCER_EVENT_INIT,
|
||||
LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED
|
||||
LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT,
|
||||
LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
|
||||
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
|
||||
LOADBALANCER_EVENT_SLAVE_STATUS
|
||||
} loadbalancer_event_id_t;
|
||||
|
||||
typedef struct {
|
||||
float limit;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_charging_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
int64_t timestamp_us;
|
||||
bool enabled;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_state_event_t;
|
||||
|
||||
// (opcional)
|
||||
typedef struct {
|
||||
float limit;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_global_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t slave_id;
|
||||
uint16_t max_current;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_master_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t slave_id;
|
||||
uint16_t max_current;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_slave_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t slave_id; // ID do slave que reportou
|
||||
bool charging; // Status de carregamento
|
||||
float hw_max_current; // Limite máximo de corrente do hardware informado
|
||||
float runtime_current; // Corrente atual de carregamento (A)
|
||||
int64_t timestamp_us; // Momento em que o status foi coletado
|
||||
} loadbalancer_slave_status_event_t;
|
||||
@@ -10,17 +10,22 @@
|
||||
#include <string.h>
|
||||
#include "meter_events.h"
|
||||
#include "evse_events.h"
|
||||
#include <math.h>
|
||||
#include "math.h"
|
||||
#include <inttypes.h> // Necessário para PRIu64
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static const char *TAG = "loadbalancer";
|
||||
|
||||
// Configurable limits
|
||||
#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
|
||||
// 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
|
||||
|
||||
// Parameters
|
||||
// Parâmetros
|
||||
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
static bool loadbalancer_enabled = false;
|
||||
|
||||
@@ -29,16 +34,109 @@ static float evse_current = 0.0f;
|
||||
static input_filter_t grid_filter;
|
||||
static input_filter_t evse_filter;
|
||||
|
||||
#define NVS_NAMESPACE "loadbalancing"
|
||||
#define NVS_MAX_GRID_CURRENT "max_grid_curr"
|
||||
#define NVS_LOADBALANCER_ENABLED "enabled"
|
||||
#define MAX_SLAVES 255
|
||||
#define CONNECTOR_COUNT (MAX_SLAVES + 1)
|
||||
|
||||
// Reset filter helper
|
||||
// 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;
|
||||
} evse_connector_t;
|
||||
|
||||
static evse_connector_t connectors[CONNECTOR_COUNT];
|
||||
|
||||
const int64_t METRICS_TIMEOUT_US = 60 * 1000000; // 60 segundos
|
||||
|
||||
// Helper: inicializa array de conectores
|
||||
static void init_connectors(void)
|
||||
{
|
||||
// master em índice 0
|
||||
connectors[0] = (evse_connector_t){
|
||||
.id = 0xFF,
|
||||
.is_master = true,
|
||||
.charging = false,
|
||||
.hw_max_current = MAX_CHARGING_CURRENT_LIMIT,
|
||||
.runtime_current = 0,
|
||||
.timestamp = 0,
|
||||
.online = false,
|
||||
.assigned = 0.0f
|
||||
};
|
||||
// slaves em 1..CONNECTOR_COUNT-1
|
||||
for (int i = 1; i < CONNECTOR_COUNT; i++) {
|
||||
connectors[i] = (evse_connector_t){
|
||||
.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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
static void input_filter_reset(input_filter_t *filter)
|
||||
{
|
||||
filter->value = 0.0f;
|
||||
}
|
||||
|
||||
// Callback de status de slave
|
||||
static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data;
|
||||
|
||||
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].runtime_current = status->runtime_current;
|
||||
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",
|
||||
status->slave_id, status->charging,
|
||||
status->hw_max_current, status->runtime_current);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void on_evse_config_event(void* handler_arg,
|
||||
esp_event_base_t base,
|
||||
int32_t id,
|
||||
void* 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].runtime_current = evt->runtime_current;
|
||||
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,
|
||||
int32_t id,
|
||||
@@ -46,39 +144,18 @@ static void loadbalancer_meter_event_handler(void *handler_arg,
|
||||
{
|
||||
if (id != METER_EVENT_DATA_READY || event_data == NULL)
|
||||
return;
|
||||
|
||||
const meter_event_data_t *evt = (const meter_event_data_t *)event_data;
|
||||
|
||||
ESP_LOGI(TAG, "Received meter event from source: %s", evt->source);
|
||||
ESP_LOGI(TAG, "IRMS: [%.2f, %.2f, %.2f] A", evt->irms[0], evt->irms[1], evt->irms[2]);
|
||||
ESP_LOGI(TAG, "VRMS: [%.1f, %.1f, %.1f] V", evt->vrms[0], evt->vrms[1], evt->vrms[2]);
|
||||
ESP_LOGI(TAG, "Power: [W1=%d, W2=%d, W3=%d]", evt->watt[0], evt->watt[1], evt->watt[2]);
|
||||
ESP_LOGI(TAG, "Freq: %.2f Hz | PF: %.2f | Energy: %.3f kWh",
|
||||
evt->frequency, evt->power_factor, evt->total_energy);
|
||||
|
||||
float max_irms = evt->irms[0];
|
||||
for (int i = 1; i < 3; ++i)
|
||||
{
|
||||
if (evt->irms[i] > max_irms)
|
||||
{
|
||||
max_irms = evt->irms[i];
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Max IRMS detected: %.2f A", max_irms);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -90,28 +167,32 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
|
||||
{
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data;
|
||||
|
||||
ESP_LOGI(TAG, "EVSE state changed: %d", evt->state);
|
||||
|
||||
switch (evt->state)
|
||||
{
|
||||
case EVSE_STATE_EVENT_IDLE:
|
||||
ESP_LOGI(TAG, "EVSE is IDLE - vehicle disconnected");
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_WAITING:
|
||||
ESP_LOGI(TAG, "EVSE is WAITING - connected but not charging");
|
||||
ESP_LOGI(TAG, "Local EVSE is %s - vehicle %sconnected / not charging",
|
||||
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
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_CHARGING:
|
||||
ESP_LOGI(TAG, "EVSE is CHARGING - resetting filters");
|
||||
ESP_LOGI(TAG, "Local EVSE is CHARGING - resetting filters");
|
||||
grid_current = 0.0f;
|
||||
evse_current = 0.0f;
|
||||
input_filter_reset(&grid_filter);
|
||||
input_filter_reset(&evse_filter);
|
||||
connectors[0].charging = true;
|
||||
connectors[0].online = true;
|
||||
connectors[0].timestamp = esp_timer_get_time();
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_FAULT:
|
||||
ESP_LOGW(TAG, "EVSE is in FAULT state - consider disabling load balancing");
|
||||
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
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -120,120 +201,66 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Config persistência ---
|
||||
static esp_err_t loadbalancer_load_config()
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
esp_err_t err = nvs_open("loadbalancing", NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
bool needs_commit = false;
|
||||
uint8_t temp_u8;
|
||||
|
||||
err = nvs_get_u8(handle, NVS_MAX_GRID_CURRENT, &temp_u8);
|
||||
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, NVS_MAX_GRID_CURRENT, max_grid_current);
|
||||
ESP_LOGW(TAG, "max_grid_current invalid or missing, set to default: %d", max_grid_current);
|
||||
nvs_set_u8(handle, "max_grid_curr", max_grid_current);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
err = nvs_get_u8(handle, NVS_LOADBALANCER_ENABLED, &temp_u8);
|
||||
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, NVS_LOADBALANCER_ENABLED, 0);
|
||||
ESP_LOGW(TAG, "loadbalancer_enabled invalid or missing, set to false");
|
||||
nvs_set_u8(handle, "enabled", 0);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
if (needs_commit)
|
||||
{
|
||||
nvs_commit(handle);
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// --- API ---
|
||||
void loadbalancer_set_enabled(bool enabled)
|
||||
{
|
||||
ESP_LOGI(TAG, "Setting load balancing to %d", enabled);
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, enabled ? 1 : 0);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK) {
|
||||
nvs_set_u8(handle, "enabled", enabled ? 1 : 0);
|
||||
nvs_commit(handle);
|
||||
loadbalancer_enabled = enabled;
|
||||
ESP_LOGI(TAG, "Load balancing state saved");
|
||||
|
||||
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);
|
||||
nvs_close(handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to save loadbalancer_enabled");
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
loadbalancer_enabled = enabled;
|
||||
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);
|
||||
}
|
||||
|
||||
esp_err_t load_balancing_set_max_grid_current(uint8_t value)
|
||||
{
|
||||
if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT)
|
||||
{
|
||||
ESP_LOGE(TAG, "Invalid grid current limit: %d", value);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, value);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
nvs_commit(handle);
|
||||
max_grid_current = value;
|
||||
ESP_LOGI(TAG, "max_grid_current set to: %d", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to save max_grid_current to NVS");
|
||||
}
|
||||
|
||||
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) != ESP_OK)
|
||||
return ESP_FAIL;
|
||||
nvs_set_u8(handle, "max_grid_curr", value);
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
return err;
|
||||
max_grid_current = value;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t load_balancing_get_max_grid_current(void)
|
||||
@@ -246,79 +273,168 @@ bool loadbalancer_is_enabled(void)
|
||||
return loadbalancer_enabled;
|
||||
}
|
||||
|
||||
// --- Task principal ---
|
||||
void loadbalancer_task(void *param)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!loadbalancer_enabled)
|
||||
{
|
||||
while (true) {
|
||||
if (!loadbalancer_is_enabled()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
continue;
|
||||
}
|
||||
|
||||
float available = max_grid_current - grid_current;
|
||||
int idxs[CONNECTOR_COUNT];
|
||||
int active_cnt = 0;
|
||||
int64_t now = esp_timer_get_time();
|
||||
|
||||
if (available < 0.0f)
|
||||
{
|
||||
available = 0.0f;
|
||||
// --- Atualiza estado online e conta ativos ---
|
||||
for (int i = 0; i < CONNECTOR_COUNT; i++) {
|
||||
|
||||
// --- Master nunca pode ficar offline ---
|
||||
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) {
|
||||
idxs[active_cnt++] = i;
|
||||
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Ignora conectores já marcados como offline ---
|
||||
if (!connectors[i].online) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Timeout de heartbeat para escravos ---
|
||||
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));
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Connector[%d] ONLINE (charging=%d, hw_max_current=%.1f, timestamp_diff=%lld us)",
|
||||
i, connectors[i].charging, connectors[i].hw_max_current,
|
||||
(long long)(now - connectors[i].timestamp));
|
||||
|
||||
if (connectors[i].charging) {
|
||||
idxs[active_cnt++] = i;
|
||||
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
|
||||
}
|
||||
}
|
||||
else if (available < MIN_CHARGING_CURRENT_LIMIT)
|
||||
{
|
||||
|
||||
ESP_LOGI(TAG, "Active connectors: %d", active_cnt);
|
||||
|
||||
// --- Calcula corrente disponível ---
|
||||
float available = max_grid_current - grid_current;
|
||||
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);
|
||||
|
||||
ESP_LOGD(TAG, "Calculated available EVSE current: %.1f A", available);
|
||||
// --- 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) {
|
||||
int tmp = idxs[b];
|
||||
idxs[b] = idxs[b + 1];
|
||||
idxs[b + 1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadbalancer_charging_limit_event_t evt = {
|
||||
.limit = available,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
// --- Distribui corrente (water-filling) ---
|
||||
float remaining = available;
|
||||
int remaining_cnt = active_cnt;
|
||||
for (int k = 0; k < active_cnt; k++) {
|
||||
int i = idxs[k];
|
||||
float share = remaining / remaining_cnt;
|
||||
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++) {
|
||||
connectors[idxs[m]].assigned = share;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
// --- Aplica piso mínimo ---
|
||||
for (int k = 0; k < active_cnt; k++) {
|
||||
int i = idxs[k];
|
||||
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++) {
|
||||
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) {
|
||||
continue; // sem alteração
|
||||
}
|
||||
|
||||
if (connectors[i].is_master) {
|
||||
loadbalancer_master_limit_event_t master_evt = {
|
||||
.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 {
|
||||
loadbalancer_slave_limit_event_t slave_evt = {
|
||||
.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)",
|
||||
connectors[i].id, (float)max_cur, connectors[i].runtime_current);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Init ---
|
||||
void loadbalancer_init(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing load balancer");
|
||||
|
||||
if (loadbalancer_load_config() != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Failed to load/init config. Using in-memory defaults.");
|
||||
}
|
||||
ESP_LOGW(TAG, "Failed to load/init config. Using defaults.");
|
||||
|
||||
init_connectors();
|
||||
input_filter_init(&grid_filter, 0.3f);
|
||||
input_filter_init(&evse_filter, 0.3f);
|
||||
|
||||
if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to create loadbalancer task");
|
||||
}
|
||||
|
||||
loadbalancer_state_event_t evt = {
|
||||
.enabled = loadbalancer_enabled,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_INIT,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
loadbalancer_state_event_t evt = {.enabled = loadbalancer_enabled, .timestamp_us = esp_timer_get_time()};
|
||||
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY,
|
||||
&loadbalancer_meter_event_handler, NULL));
|
||||
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_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(LOADBALANCER_EVENTS,LOADBALANCER_EVENT_SLAVE_STATUS,
|
||||
&on_slave_status, NULL));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user