chore: snapshot before shared RS485 bus integration
This commit is contained in:
0
components/loadbalancer/src/grid_limiter.c
Executable file → Normal file
0
components/loadbalancer/src/grid_limiter.c
Executable file → Normal file
0
components/loadbalancer/src/input_filter.c
Executable file → Normal file
0
components/loadbalancer/src/input_filter.c
Executable file → Normal file
620
components/loadbalancer/src/loadbalancer.c
Executable file → Normal file
620
components/loadbalancer/src/loadbalancer.c
Executable file → Normal file
@@ -18,6 +18,7 @@
|
||||
#include "input_filter.h"
|
||||
#include "meter_events.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_config.h"
|
||||
|
||||
#include "storage_service.h"
|
||||
#include "evse_link_events.h"
|
||||
@@ -42,7 +43,7 @@ static const char *TAG = "loadbalancer";
|
||||
#define LB_SUSPEND_THRESHOLD (MIN_CHARGING_CURRENT_LIMIT - 1.0f)
|
||||
#define LB_RESUME_THRESHOLD (MIN_CHARGING_CURRENT_LIMIT + 1.0f)
|
||||
|
||||
#define GRID_METER_TIMEOUT_US (10LL * 1000000LL)
|
||||
#define GRID_METER_TIMEOUT_US (30LL * 1000000LL)
|
||||
|
||||
static bool loadbalancer_enabled = false;
|
||||
|
||||
@@ -53,6 +54,7 @@ static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT; // mantém nome (já u
|
||||
// PV
|
||||
static bool pv_enabled = false;
|
||||
static int32_t pv_max_import_w = 0; // 0=só PV
|
||||
static loadbalancer_limit_mode_t limit_mode = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
|
||||
// métricas do meter
|
||||
static float grid_current = 0.0f; // fallback magnitude (max irms filtrado)
|
||||
@@ -100,6 +102,7 @@ static const int64_t METRICS_TIMEOUT_US = 60LL * 1000000LL;
|
||||
#define LB_KEY_GRID_MAXA "grid_max_a"
|
||||
#define LB_KEY_PV_ON "pv_on"
|
||||
#define LB_KEY_PV_MAXW "pv_max_w" // precisa storage u32/i32
|
||||
#define LB_KEY_LIMIT_MODE "limit_mode"
|
||||
|
||||
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
|
||||
|
||||
@@ -286,9 +289,25 @@ static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t
|
||||
|
||||
have_grid_evt = true;
|
||||
|
||||
ESP_LOGI(TAG, "GRID: I=%.3fA P=%ldW (maxA=%u pv_on=%d pvMaxW=%ld)",
|
||||
grid_current, (long)grid_watt_total, (unsigned)max_grid_current,
|
||||
pv_enabled, (long)pv_max_import_w);
|
||||
if (loadbalancer_enabled)
|
||||
{
|
||||
ESP_LOGI(TAG,
|
||||
"GRID: V=[%.1f %.1f %.1f]V I=[%.3f %.3f %.3f]A maxI=%.3fA P=%ldW (maxA=%u pv_on=%d pvMaxW=%ld)",
|
||||
evt->vrms[0], evt->vrms[1], evt->vrms[2],
|
||||
evt->irms[0], evt->irms[1], evt->irms[2],
|
||||
grid_current,
|
||||
(long)grid_watt_total,
|
||||
(unsigned)max_grid_current,
|
||||
pv_enabled,
|
||||
(long)pv_max_import_w);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGD(TAG,
|
||||
"GRID ignored because loadbalancer disabled: maxI=%.3fA P=%ldW",
|
||||
grid_current,
|
||||
(long)grid_watt_total);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -354,6 +373,179 @@ static void loadbalancer_evse_event_handler(void *handler_arg, esp_event_base_t
|
||||
}
|
||||
|
||||
// --------- Config load/save ---------
|
||||
|
||||
const char *loadbalancer_limit_mode_to_str(loadbalancer_limit_mode_t mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case LOADBALANCER_LIMIT_MODE_OFF:
|
||||
return "off";
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_MANUAL:
|
||||
return "manual";
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_GRID:
|
||||
return "grid";
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_SOLAR:
|
||||
return "solar";
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_GRID_SOLAR:
|
||||
return "grid_solar";
|
||||
|
||||
default:
|
||||
return "off";
|
||||
}
|
||||
}
|
||||
|
||||
bool loadbalancer_limit_mode_from_str(const char *str, loadbalancer_limit_mode_t *out)
|
||||
{
|
||||
if (!str || !out)
|
||||
return false;
|
||||
|
||||
if (strcmp(str, "off") == 0)
|
||||
{
|
||||
*out = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(str, "manual") == 0)
|
||||
{
|
||||
*out = LOADBALANCER_LIMIT_MODE_MANUAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(str, "grid") == 0 || strcmp(str, "loadbalancer") == 0)
|
||||
{
|
||||
*out = LOADBALANCER_LIMIT_MODE_GRID;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(str, "solar") == 0 || strcmp(str, "pv") == 0)
|
||||
{
|
||||
*out = LOADBALANCER_LIMIT_MODE_SOLAR;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(str, "grid_solar") == 0 ||
|
||||
strcmp(str, "solar_grid") == 0 ||
|
||||
strcmp(str, "grid_pv") == 0 ||
|
||||
strcmp(str, "pv_grid") == 0)
|
||||
{
|
||||
*out = LOADBALANCER_LIMIT_MODE_GRID_SOLAR;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static esp_err_t persist_limit_mode(loadbalancer_limit_mode_t mode)
|
||||
{
|
||||
esp_err_t err = storage_set_u8_sync(LB_NS, LB_KEY_LIMIT_MODE, (uint8_t)mode, TO_TICKS_MS(1000));
|
||||
if (err == ESP_OK)
|
||||
err = storage_flush_sync(TO_TICKS_MS(2000));
|
||||
|
||||
if (err != ESP_OK)
|
||||
ESP_LOGW(TAG, "Persist %s=%u failed: %s", LB_KEY_LIMIT_MODE, (unsigned)mode, esp_err_to_name(err));
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void loadbalancer_apply_limit_mode_no_persist(loadbalancer_limit_mode_t mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case LOADBALANCER_LIMIT_MODE_OFF:
|
||||
loadbalancer_enabled = false;
|
||||
grid_limit_enabled = false;
|
||||
pv_enabled = false;
|
||||
pv_optimizer_set_enabled(false);
|
||||
grid_limiter_set_enabled(false);
|
||||
break;
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_MANUAL:
|
||||
loadbalancer_enabled = false;
|
||||
pv_optimizer_set_enabled(false);
|
||||
grid_limiter_set_enabled(false);
|
||||
break;
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_GRID:
|
||||
loadbalancer_enabled = true;
|
||||
grid_limit_enabled = true;
|
||||
pv_enabled = false;
|
||||
|
||||
grid_limiter_set_enabled(true);
|
||||
pv_optimizer_set_enabled(false);
|
||||
break;
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_SOLAR:
|
||||
loadbalancer_enabled = true;
|
||||
grid_limit_enabled = false;
|
||||
pv_enabled = true;
|
||||
|
||||
grid_limiter_set_enabled(false);
|
||||
pv_optimizer_set_enabled(true);
|
||||
break;
|
||||
|
||||
case LOADBALANCER_LIMIT_MODE_GRID_SOLAR:
|
||||
loadbalancer_enabled = true;
|
||||
grid_limit_enabled = true;
|
||||
pv_enabled = true;
|
||||
|
||||
grid_limiter_set_enabled(true);
|
||||
pv_optimizer_set_enabled(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
loadbalancer_enabled = false;
|
||||
pv_optimizer_set_enabled(false);
|
||||
grid_limiter_set_enabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
limit_mode = mode;
|
||||
}
|
||||
|
||||
esp_err_t loadbalancer_set_limit_mode(loadbalancer_limit_mode_t mode)
|
||||
{
|
||||
if (mode > LOADBALANCER_LIMIT_MODE_GRID_SOLAR)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
loadbalancer_apply_limit_mode_no_persist(mode);
|
||||
|
||||
(void)storage_set_u8_sync(LB_NS, LB_KEY_ENABLED,
|
||||
loadbalancer_enabled ? 1 : 0,
|
||||
TO_TICKS_MS(1000));
|
||||
|
||||
(void)storage_set_u8_sync(LB_NS, LB_KEY_GRID_ON,
|
||||
grid_limit_enabled ? 1 : 0,
|
||||
TO_TICKS_MS(1000));
|
||||
|
||||
(void)storage_set_u8_sync(LB_NS, LB_KEY_PV_ON,
|
||||
pv_enabled ? 1 : 0,
|
||||
TO_TICKS_MS(1000));
|
||||
|
||||
esp_err_t err = persist_limit_mode(mode);
|
||||
|
||||
loadbalancer_state_event_t evt = {
|
||||
.enabled = loadbalancer_enabled,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
(void)esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
loadbalancer_limit_mode_t loadbalancer_get_limit_mode(void)
|
||||
{
|
||||
return limit_mode;
|
||||
}
|
||||
|
||||
static esp_err_t loadbalancer_load_config(void)
|
||||
{
|
||||
esp_err_t se = storage_service_init();
|
||||
@@ -364,72 +556,318 @@ static esp_err_t loadbalancer_load_config(void)
|
||||
bool needs_flush = false;
|
||||
uint8_t temp_u8 = 0;
|
||||
|
||||
// enabled
|
||||
/*
|
||||
* Defaults seguros em RAM.
|
||||
*
|
||||
* Estes valores só são gravados na storage quando a chave não existe
|
||||
* ou quando existe mas tem valor inválido.
|
||||
*
|
||||
* Em erro temporário de leitura, usa-se o default em RAM, mas NÃO se
|
||||
* sobrescreve a storage.
|
||||
*/
|
||||
loadbalancer_enabled = false;
|
||||
grid_limit_enabled = true;
|
||||
max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
pv_enabled = false;
|
||||
pv_max_import_w = 0;
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
|
||||
/*
|
||||
* enabled - legacy.
|
||||
*/
|
||||
err = storage_get_u8_sync(LB_NS, LB_KEY_ENABLED, &temp_u8, TO_TICKS_MS(800));
|
||||
if (err == ESP_OK && temp_u8 <= 1)
|
||||
{
|
||||
loadbalancer_enabled = (temp_u8 != 0);
|
||||
else
|
||||
}
|
||||
else if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
loadbalancer_enabled = false;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_ENABLED, 0);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG, "LB config: %s missing -> default=0", LB_KEY_ENABLED);
|
||||
}
|
||||
else if (err == ESP_OK)
|
||||
{
|
||||
loadbalancer_enabled = false;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_ENABLED, 0);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s invalid value=%u -> default=0",
|
||||
LB_KEY_ENABLED,
|
||||
(unsigned)temp_u8);
|
||||
}
|
||||
else
|
||||
{
|
||||
loadbalancer_enabled = false;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: failed to read %s/%s: %s. Runtime default=0, storage not overwritten.",
|
||||
LB_NS,
|
||||
LB_KEY_ENABLED,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// grid on
|
||||
/*
|
||||
* grid_on - legacy/config auxiliar.
|
||||
*/
|
||||
err = storage_get_u8_sync(LB_NS, LB_KEY_GRID_ON, &temp_u8, TO_TICKS_MS(800));
|
||||
if (err == ESP_OK && temp_u8 <= 1)
|
||||
{
|
||||
grid_limit_enabled = (temp_u8 != 0);
|
||||
else
|
||||
}
|
||||
else if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
grid_limit_enabled = true;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_GRID_ON, 1);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG, "LB config: %s missing -> default=1", LB_KEY_GRID_ON);
|
||||
}
|
||||
else if (err == ESP_OK)
|
||||
{
|
||||
grid_limit_enabled = true;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_GRID_ON, 1);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s invalid value=%u -> default=1",
|
||||
LB_KEY_GRID_ON,
|
||||
(unsigned)temp_u8);
|
||||
}
|
||||
else
|
||||
{
|
||||
grid_limit_enabled = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: failed to read %s/%s: %s. Runtime default=1, storage not overwritten.",
|
||||
LB_NS,
|
||||
LB_KEY_GRID_ON,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// grid maxA
|
||||
/*
|
||||
* grid_max_a.
|
||||
*/
|
||||
err = storage_get_u8_sync(LB_NS, LB_KEY_GRID_MAXA, &temp_u8, TO_TICKS_MS(800));
|
||||
if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
|
||||
if (err == ESP_OK &&
|
||||
temp_u8 >= MIN_GRID_CURRENT_LIMIT &&
|
||||
temp_u8 <= MAX_GRID_CURRENT_LIMIT)
|
||||
{
|
||||
max_grid_current = temp_u8;
|
||||
else
|
||||
}
|
||||
else if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_GRID_MAXA, max_grid_current);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s missing -> default=%u",
|
||||
LB_KEY_GRID_MAXA,
|
||||
(unsigned)max_grid_current);
|
||||
}
|
||||
else if (err == ESP_OK)
|
||||
{
|
||||
max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_GRID_MAXA, max_grid_current);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s invalid value=%u -> default=%u",
|
||||
LB_KEY_GRID_MAXA,
|
||||
(unsigned)temp_u8,
|
||||
(unsigned)max_grid_current);
|
||||
}
|
||||
else
|
||||
{
|
||||
max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: failed to read %s/%s: %s. Runtime default=%u, storage not overwritten.",
|
||||
LB_NS,
|
||||
LB_KEY_GRID_MAXA,
|
||||
esp_err_to_name(err),
|
||||
(unsigned)max_grid_current);
|
||||
}
|
||||
|
||||
// pv on
|
||||
/*
|
||||
* pv_on - legacy/config auxiliar.
|
||||
*/
|
||||
err = storage_get_u8_sync(LB_NS, LB_KEY_PV_ON, &temp_u8, TO_TICKS_MS(800));
|
||||
if (err == ESP_OK && temp_u8 <= 1)
|
||||
{
|
||||
pv_enabled = (temp_u8 != 0);
|
||||
else
|
||||
}
|
||||
else if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
pv_enabled = false;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_PV_ON, 0);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG, "LB config: %s missing -> default=0", LB_KEY_PV_ON);
|
||||
}
|
||||
else if (err == ESP_OK)
|
||||
{
|
||||
pv_enabled = false;
|
||||
(void)storage_set_u8_async(LB_NS, LB_KEY_PV_ON, 0);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s invalid value=%u -> default=0",
|
||||
LB_KEY_PV_ON,
|
||||
(unsigned)temp_u8);
|
||||
}
|
||||
else
|
||||
{
|
||||
pv_enabled = false;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: failed to read %s/%s: %s. Runtime default=0, storage not overwritten.",
|
||||
LB_NS,
|
||||
LB_KEY_PV_ON,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// pv maxW (se não tiveres storage_get_u32_sync, adapta para i32/u32 do teu storage_service)
|
||||
/*
|
||||
* pv_max_w.
|
||||
*/
|
||||
uint32_t temp_u32 = 0;
|
||||
err = storage_get_u32_sync(LB_NS, LB_KEY_PV_MAXW, &temp_u32, TO_TICKS_MS(800));
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
pv_max_import_w = (int32_t)temp_u32;
|
||||
else
|
||||
}
|
||||
else if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
pv_max_import_w = 0;
|
||||
(void)storage_set_u32_async(LB_NS, LB_KEY_PV_MAXW, 0);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG, "LB config: %s missing -> default=0", LB_KEY_PV_MAXW);
|
||||
}
|
||||
else
|
||||
{
|
||||
pv_max_import_w = 0;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: failed to read %s/%s: %s. Runtime default=0, storage not overwritten.",
|
||||
LB_NS,
|
||||
LB_KEY_PV_MAXW,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
/*
|
||||
* limit_mode - fonte principal do estado operacional.
|
||||
*
|
||||
* IMPORTANTE:
|
||||
* - Se a chave existe e é válida, ela ganha sempre.
|
||||
* - Só migrar dos booleans antigos quando LB_KEY_LIMIT_MODE não existe.
|
||||
* - Se a leitura falhar temporariamente, NÃO gravar OFF por cima.
|
||||
*/
|
||||
err = storage_get_u8_sync(LB_NS, LB_KEY_LIMIT_MODE, &temp_u8, TO_TICKS_MS(800));
|
||||
if (err == ESP_OK && temp_u8 <= LOADBALANCER_LIMIT_MODE_GRID_SOLAR)
|
||||
{
|
||||
limit_mode = (loadbalancer_limit_mode_t)temp_u8;
|
||||
}
|
||||
else if (err == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
/*
|
||||
* Migração de versões antigas sem limit_mode.
|
||||
*/
|
||||
if (loadbalancer_enabled && pv_enabled && grid_limit_enabled)
|
||||
{
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_GRID_SOLAR;
|
||||
}
|
||||
else if (loadbalancer_enabled && pv_enabled)
|
||||
{
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_SOLAR;
|
||||
}
|
||||
else if (loadbalancer_enabled && grid_limit_enabled)
|
||||
{
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_GRID;
|
||||
}
|
||||
else
|
||||
{
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
}
|
||||
|
||||
(void)storage_set_u8_async(LB_NS,
|
||||
LB_KEY_LIMIT_MODE,
|
||||
(uint8_t)limit_mode);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s missing -> migrated mode=%s",
|
||||
LB_KEY_LIMIT_MODE,
|
||||
loadbalancer_limit_mode_to_str(limit_mode));
|
||||
}
|
||||
else if (err == ESP_OK)
|
||||
{
|
||||
/*
|
||||
* Valor existente mas inválido.
|
||||
* Aqui é aceitável reparar para OFF.
|
||||
*/
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
|
||||
(void)storage_set_u8_async(LB_NS,
|
||||
LB_KEY_LIMIT_MODE,
|
||||
(uint8_t)limit_mode);
|
||||
needs_flush = true;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: %s invalid value=%u -> mode=off",
|
||||
LB_KEY_LIMIT_MODE,
|
||||
(unsigned)temp_u8);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Erro temporário de leitura.
|
||||
* Não sobrescrever storage.
|
||||
*/
|
||||
limit_mode = LOADBALANCER_LIMIT_MODE_OFF;
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: failed to read %s/%s: %s. Runtime mode=off, storage not overwritten.",
|
||||
LB_NS,
|
||||
LB_KEY_LIMIT_MODE,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
if (needs_flush)
|
||||
(void)storage_flush_sync(TO_TICKS_MS(2000));
|
||||
{
|
||||
esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000));
|
||||
if (fe != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"LB config: flush defaults/migration failed: %s",
|
||||
esp_err_to_name(fe));
|
||||
}
|
||||
}
|
||||
|
||||
// propagar para sub-módulos
|
||||
pv_optimizer_set_enabled(pv_enabled);
|
||||
/*
|
||||
* Propagar parâmetros para sub-módulos antes de aplicar o modo.
|
||||
*/
|
||||
(void)pv_optimizer_set_max_import_w(pv_max_import_w);
|
||||
|
||||
grid_limiter_set_enabled(grid_limit_enabled);
|
||||
(void)grid_limiter_set_max_import_a(max_grid_current);
|
||||
|
||||
/*
|
||||
* Aplicar modo operacional sem persistir novamente.
|
||||
*/
|
||||
loadbalancer_apply_limit_mode_no_persist(limit_mode);
|
||||
|
||||
ESP_LOGW(TAG,
|
||||
"LB config loaded: mode=%s enabled=%d grid=%d maxA=%u pv=%d pvMaxW=%ld",
|
||||
loadbalancer_limit_mode_to_str(limit_mode),
|
||||
loadbalancer_enabled,
|
||||
grid_limit_enabled,
|
||||
(unsigned)max_grid_current,
|
||||
pv_enabled,
|
||||
(long)pv_max_import_w);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -438,17 +876,19 @@ static void persist_u32(const char *key, uint32_t v) { (void)storage_set_u32_asy
|
||||
|
||||
void loadbalancer_set_enabled(bool enabled)
|
||||
{
|
||||
if (enabled == loadbalancer_enabled)
|
||||
return;
|
||||
|
||||
loadbalancer_enabled = enabled;
|
||||
persist_u8(LB_KEY_ENABLED, enabled ? 1 : 0);
|
||||
|
||||
loadbalancer_state_event_t evt = {.enabled = enabled, .timestamp_us = esp_timer_get_time()};
|
||||
(void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
&evt, sizeof(evt), portMAX_DELAY);
|
||||
if (enabled)
|
||||
{
|
||||
/*
|
||||
* Compatibilidade legacy:
|
||||
* enabled=true passa a significar modo GRID.
|
||||
*/
|
||||
(void)loadbalancer_set_limit_mode(LOADBALANCER_LIMIT_MODE_GRID);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)loadbalancer_set_limit_mode(LOADBALANCER_LIMIT_MODE_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
bool loadbalancer_is_enabled(void) { return loadbalancer_enabled; }
|
||||
|
||||
// grid setters/getters
|
||||
@@ -463,8 +903,13 @@ bool loadbalancer_grid_is_enabled(void) { return grid_limit_enabled; }
|
||||
|
||||
esp_err_t loadbalancer_grid_set_max_import_a(uint8_t a)
|
||||
{
|
||||
ESP_LOGW(TAG, "SET grid max import: %uA -> %uA",
|
||||
(unsigned)max_grid_current,
|
||||
(unsigned)a);
|
||||
|
||||
if (a < MIN_GRID_CURRENT_LIMIT || a > MAX_GRID_CURRENT_LIMIT)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
max_grid_current = a;
|
||||
persist_u8(LB_KEY_GRID_MAXA, a);
|
||||
return grid_limiter_set_max_import_a(a);
|
||||
@@ -741,83 +1186,60 @@ static void apply_meter_timeout_policy_locked(int *idxs, int cnt)
|
||||
|
||||
static void apply_normal_policy_locked(int *idxs, int cnt, float grid_snapshot)
|
||||
{
|
||||
// o teu algoritmo atual (grid-limit em A, por corrente medida)
|
||||
float available = (float)max_grid_current - grid_snapshot;
|
||||
|
||||
if (available < -AVAILABLE_EPS)
|
||||
{
|
||||
float factor = 0.0f;
|
||||
if (grid_snapshot > AVAILABLE_EPS)
|
||||
factor = ((float)max_grid_current) / grid_snapshot;
|
||||
|
||||
if (factor < 0.0f)
|
||||
factor = 0.0f;
|
||||
if (factor > 1.0f)
|
||||
factor = 1.0f;
|
||||
|
||||
for (int k = 0; k < cnt; k++)
|
||||
{
|
||||
int i = idxs[k];
|
||||
// baseline = runtime (como tinhas)
|
||||
float cur = connectors[i].runtime_current;
|
||||
float hw = connectors[i].hw_max_current;
|
||||
if (cur < 0.0f)
|
||||
cur = 0.0f;
|
||||
if (hw < 0.0f)
|
||||
hw = 0.0f;
|
||||
connectors[i].assigned = MIN(cur, hw) * factor;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// baseline = runtime
|
||||
for (int k = 0; k < cnt; k++)
|
||||
{
|
||||
int i = idxs[k];
|
||||
float cur = connectors[i].runtime_current;
|
||||
float hw = connectors[i].hw_max_current;
|
||||
if (cur < 0.0f)
|
||||
cur = 0.0f;
|
||||
if (hw < 0.0f)
|
||||
hw = 0.0f;
|
||||
connectors[i].assigned = MIN(cur, hw);
|
||||
}
|
||||
|
||||
if (fabsf(available) <= AVAILABLE_EPS)
|
||||
/*
|
||||
* Grid limit em modo normal.
|
||||
*
|
||||
* max_grid_current é o limite TOTAL de importação da instalação.
|
||||
*
|
||||
* Não podemos usar runtime_current como baseline inicial, porque isso
|
||||
* permite comandos acima do limite configurado. Exemplo observado:
|
||||
* maxA=8 e SET_CURRENT=22A/27A.
|
||||
*
|
||||
* Fórmula:
|
||||
*
|
||||
* budget = ultimo_total_comandado + (max_grid_current - grid_medido)
|
||||
*
|
||||
* Exemplos:
|
||||
* last=0A, grid=0A, max=8A => budget=8A
|
||||
* last=20A, grid=15A, max=8A => budget=13A
|
||||
* last=0A, grid=5A, max=8A => budget=3A => abaixo de 6A, fica pausado
|
||||
*/
|
||||
if (cnt <= 0)
|
||||
return;
|
||||
|
||||
float remaining = available;
|
||||
if (grid_snapshot < 0.0f)
|
||||
grid_snapshot = 0.0f;
|
||||
|
||||
// fase 1: subir até MIN para os mais antigos, se possível
|
||||
for (int k = 0; k < cnt && remaining > 0.0f; k++)
|
||||
{
|
||||
int i = idxs[k];
|
||||
float hw = connectors[i].hw_max_current;
|
||||
float target = MIN((float)MIN_CHARGING_CURRENT_LIMIT, hw);
|
||||
const float total_hw_max_a = sum_hw_max_locked(idxs, cnt);
|
||||
const float last_total_cmd_a = sum_last_limit_locked(idxs, cnt);
|
||||
|
||||
if (connectors[i].assigned >= target)
|
||||
continue;
|
||||
float budget_total_a =
|
||||
last_total_cmd_a + ((float)max_grid_current - grid_snapshot);
|
||||
|
||||
float delta = target - connectors[i].assigned;
|
||||
float inc = MIN(delta, remaining);
|
||||
connectors[i].assigned += inc;
|
||||
remaining -= inc;
|
||||
}
|
||||
if (budget_total_a < 0.0f)
|
||||
budget_total_a = 0.0f;
|
||||
|
||||
// fase 2: suspender os mais recentes que ficaram abaixo do mínimo
|
||||
for (int k = cnt - 1; k >= 0; k--)
|
||||
{
|
||||
int i = idxs[k];
|
||||
if (connectors[i].assigned >= (float)MIN_CHARGING_CURRENT_LIMIT)
|
||||
continue;
|
||||
/*
|
||||
* Safety clamp:
|
||||
* Em modo grid-limit, o total comandado nunca deve ultrapassar
|
||||
* max_grid_current. A fórmula incremental pode subir acima do limite
|
||||
* quando o meter ainda mede pouca corrente após pausa/retoma.
|
||||
*/
|
||||
if (budget_total_a > (float)max_grid_current)
|
||||
budget_total_a = (float)max_grid_current;
|
||||
|
||||
connectors[i].assigned = 0.0f;
|
||||
connectors[i].suspended_by_lb = true;
|
||||
}
|
||||
if (budget_total_a > total_hw_max_a)
|
||||
budget_total_a = total_hw_max_a;
|
||||
|
||||
// fase 3: distribuir extra pelos que estão ativos
|
||||
if (remaining > AVAILABLE_EPS)
|
||||
distribute_extra_evenly(idxs, cnt, remaining);
|
||||
ESP_LOGI(TAG,
|
||||
"GRID policy: maxA=%u grid=%.2fA lastCmd=%.2fA budget=%.2fA active=%d",
|
||||
(unsigned)max_grid_current,
|
||||
grid_snapshot,
|
||||
last_total_cmd_a,
|
||||
budget_total_a,
|
||||
cnt);
|
||||
|
||||
apply_budget_policy_locked(idxs, cnt, budget_total_a);
|
||||
}
|
||||
|
||||
static int prepare_pending_limits_locked(const int *idxs,
|
||||
|
||||
0
components/loadbalancer/src/pv_optimizer.c
Executable file → Normal file
0
components/loadbalancer/src/pv_optimizer.c
Executable file → Normal file
Reference in New Issue
Block a user