fix evse_link
This commit is contained in:
165
components/loadbalancer/src/pv_optimizer.c
Executable file
165
components/loadbalancer/src/pv_optimizer.c
Executable file
@@ -0,0 +1,165 @@
|
||||
#include "pv_optimizer.h"
|
||||
#include "esp_log.h"
|
||||
#include <math.h>
|
||||
|
||||
static const char *TAG = "pv_optimizer";
|
||||
|
||||
// internos (fixos, como pediste)
|
||||
#define PV_MIN_EXPORT_W (50) // deadband export (anti-oscilações)
|
||||
#define PV_TOTAL_RAMP_STEP_A (2.0f) // step total por ciclo (como tens loop 5s)
|
||||
#define DEFAULT_VOLTAGE_V (230.0f)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool enabled;
|
||||
int32_t max_import_w; // >=0
|
||||
} pv_cfg_t;
|
||||
|
||||
static pv_cfg_t s_cfg = {
|
||||
.enabled = false,
|
||||
.max_import_w = 0};
|
||||
|
||||
static float clamp_pf(float pf)
|
||||
{
|
||||
if (pf < 0.05f || pf > 1.2f)
|
||||
return 1.0f;
|
||||
return pf;
|
||||
}
|
||||
|
||||
static void estimate_v_and_phases(const meter_event_data_t *m, float *v_avg, int *nph)
|
||||
{
|
||||
float sum = 0.0f;
|
||||
int cnt = 0;
|
||||
|
||||
if (!m)
|
||||
{
|
||||
*v_avg = DEFAULT_VOLTAGE_V;
|
||||
*nph = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (m->vrms[i] > 80.0f)
|
||||
{
|
||||
sum += m->vrms[i];
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cnt == 0)
|
||||
{
|
||||
*v_avg = DEFAULT_VOLTAGE_V;
|
||||
*nph = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
*v_avg = sum / (float)cnt;
|
||||
*nph = cnt;
|
||||
}
|
||||
|
||||
void pv_optimizer_init(void)
|
||||
{
|
||||
// nada a fazer
|
||||
}
|
||||
|
||||
void pv_optimizer_set_enabled(bool en) { s_cfg.enabled = en; }
|
||||
bool pv_optimizer_is_enabled(void) { return s_cfg.enabled; }
|
||||
|
||||
esp_err_t pv_optimizer_set_max_import_w(int32_t w)
|
||||
{
|
||||
if (w < 0)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
s_cfg.max_import_w = w;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int32_t pv_optimizer_get_max_import_w(void) { return s_cfg.max_import_w; }
|
||||
|
||||
static float ramp_total(float last_a, float target_a)
|
||||
{
|
||||
if (target_a > last_a + PV_TOTAL_RAMP_STEP_A)
|
||||
return last_a + PV_TOTAL_RAMP_STEP_A;
|
||||
if (target_a < last_a - PV_TOTAL_RAMP_STEP_A)
|
||||
return last_a - PV_TOTAL_RAMP_STEP_A;
|
||||
return target_a;
|
||||
}
|
||||
|
||||
float pv_optimizer_compute_budget_a(const meter_event_data_t *grid_evt,
|
||||
float last_total_cmd_a,
|
||||
float total_hw_max_a)
|
||||
{
|
||||
if (!s_cfg.enabled)
|
||||
return total_hw_max_a;
|
||||
if (!grid_evt)
|
||||
return 0.0f;
|
||||
|
||||
// se meter não fornece potência (fica 0) não dá para PV -> conservador: não importa
|
||||
// (podes mudar para "mantém last" se preferires)
|
||||
if (grid_evt->watt_total == 0)
|
||||
{
|
||||
return ramp_total(last_total_cmd_a, 0.0f);
|
||||
}
|
||||
|
||||
float v_avg;
|
||||
int nph;
|
||||
estimate_v_and_phases(grid_evt, &v_avg, &nph);
|
||||
|
||||
const float pf = clamp_pf(grid_evt->power_factor);
|
||||
const float w_per_a = v_avg * (float)nph * pf;
|
||||
if (w_per_a < 10.0f)
|
||||
{
|
||||
return ramp_total(last_total_cmd_a, 0.0f);
|
||||
}
|
||||
|
||||
const int32_t p_grid_w = grid_evt->watt_total; // +import / -export
|
||||
const int32_t target_import_w = s_cfg.max_import_w; // >=0
|
||||
|
||||
// deadband só para o "Só PV"
|
||||
if (target_import_w == 0)
|
||||
{
|
||||
if (p_grid_w < 0)
|
||||
{
|
||||
int32_t export_w = -p_grid_w;
|
||||
if (export_w < PV_MIN_EXPORT_W)
|
||||
{
|
||||
return ramp_total(last_total_cmd_a, 0.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// está a importar
|
||||
if (p_grid_w < PV_MIN_EXPORT_W)
|
||||
{
|
||||
return ramp_total(last_total_cmd_a, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// estima base-load com o comando anterior
|
||||
const float p_evse_last_w = last_total_cmd_a * w_per_a;
|
||||
const float p_base_w = (float)p_grid_w - p_evse_last_w;
|
||||
|
||||
// queremos p_grid -> target_import_w
|
||||
float p_evse_target_w = (float)target_import_w - p_base_w;
|
||||
|
||||
// clamp [0..max]
|
||||
if (p_evse_target_w < 0.0f)
|
||||
p_evse_target_w = 0.0f;
|
||||
const float p_evse_max_w = total_hw_max_a * w_per_a;
|
||||
if (p_evse_target_w > p_evse_max_w)
|
||||
p_evse_target_w = p_evse_max_w;
|
||||
|
||||
float target_total_a = p_evse_target_w / w_per_a;
|
||||
if (target_total_a < 0.0f)
|
||||
target_total_a = 0.0f;
|
||||
if (target_total_a > total_hw_max_a)
|
||||
target_total_a = total_hw_max_a;
|
||||
|
||||
float ramped = ramp_total(last_total_cmd_a, target_total_a);
|
||||
|
||||
ESP_LOGD(TAG, "pv: p_grid=%ldW target_imp=%ldW base=%.1fW last=%.1fA -> target=%.1fA (v=%.1f nph=%d pf=%.2f)",
|
||||
(long)p_grid_w, (long)target_import_w, p_base_w, last_total_cmd_a, ramped, v_avg, nph, pf);
|
||||
|
||||
return ramped;
|
||||
}
|
||||
Reference in New Issue
Block a user