Files
chargeflow/components/evse/evse_manager.c
2025-12-21 23:28:26 +00:00

379 lines
11 KiB
C
Executable File

#include "evse_manager.h"
#include "evse_state.h"
#include "evse_error.h"
#include "evse_hardware.h"
#include "evse_config.h"
#include "evse_api.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_event.h"
#include "esp_err.h"
#include <string.h>
#include <inttypes.h>
#include "auth_events.h"
#include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "scheduler_events.h"
static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex;
// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false;
static bool lb_prev_authorized = false;
// Estado de janela do scheduler
static bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
static void lb_clear_pause_state(void)
{
portENTER_CRITICAL(&s_mgr_mux);
lb_paused = false;
lb_prev_authorized = false;
portEXIT_CRITICAL(&s_mgr_mux);
}
bool evse_sched_is_allowed(void)
{
bool v;
portENTER_CRITICAL(&s_sched_mux);
v = s_sched_allowed;
portEXIT_CRITICAL(&s_sched_mux);
return v;
}
static void evse_manager_handle_auth_on_tick(void)
{
bool sched_allowed = evse_sched_is_allowed();
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
bool local_auth_enabled;
bool local_lb_paused;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
local_lb_paused = lb_paused;
portEXIT_CRITICAL(&s_mgr_mux);
if (local_auth_enabled)
{
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
{
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
evse_state_set_authorized(false);
lb_clear_pause_state();
}
if (has_error && evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
err_bits);
evse_state_set_authorized(false);
lb_clear_pause_state();
}
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_state_set_authorized(false);
}
}
else
{
bool limit_hit = evse_is_limit_reached();
bool can_operate = evse_config_is_available() && evse_config_is_enabled();
if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
evse_state_get_authorized())
{
ESP_LOGI(TAG,
"[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
(int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
evse_state_set_authorized(false);
}
if (!local_lb_paused && sched_allowed && can_operate &&
!has_error && !limit_hit &&
!evse_state_get_authorized())
{
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
lb_clear_pause_state();
}
}
}
static void evse_manager_task(void *arg)
{
(void)arg;
while (true)
{
evse_manager_tick();
vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS));
}
}
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != AUTH_EVENTS || !data) return;
switch (id)
{
case AUTH_EVENT_TAG_PROCESSED:
{
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
evse_state_set_authorized(evt->authorized);
lb_clear_pause_state();
break;
}
case AUTH_EVENT_MODE_CHANGED:
case AUTH_EVENT_INIT:
{
const auth_mode_event_data_t *evt = (const auth_mode_event_data_t *)data;
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(evt->mode));
portENTER_CRITICAL(&s_mgr_mux);
auth_enabled = (evt->mode != AUTH_MODE_OPEN);
portEXIT_CRITICAL(&s_mgr_mux);
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
}
}
}
static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
(void)handler_arg;
(void)event_base;
if (!event_data) return;
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED",
(long long)evt->timestamp_us);
}
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{
const loadbalancer_master_limit_event_t *evt =
(const loadbalancer_master_limit_event_t *)event_data;
ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)",
evt->max_current, (long long)evt->timestamp_us);
if (evt->max_current == 0)
{
bool prev_auth = evse_state_get_authorized();
portENTER_CRITICAL(&s_mgr_mux);
lb_paused = true;
lb_prev_authorized = prev_auth;
portEXIT_CRITICAL(&s_mgr_mux);
if (prev_auth)
{
ESP_LOGI(TAG, "[LB] limit=0A → pausando sessão (authorized=false)");
evse_state_set_authorized(false);
}
}
else
{
evse_set_runtime_charging_current(evt->max_current);
bool was_paused;
bool prev_auth;
portENTER_CRITICAL(&s_mgr_mux);
was_paused = lb_paused;
prev_auth = lb_prev_authorized;
portEXIT_CRITICAL(&s_mgr_mux);
if (was_paused)
{
bool can_resume =
(evse_get_error() == 0) &&
evse_config_is_available() &&
evse_config_is_enabled() &&
evse_sched_is_allowed() &&
!evse_is_limit_reached();
if (!can_resume)
{
ESP_LOGW(TAG,
"[LB] limit=%uA → não retoma automaticamente (erro/indisp/desab/fora de horário/limite)",
evt->max_current);
lb_clear_pause_state();
return;
}
bool local_auth_enabled;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
lb_paused = false; // já vai tentar retomar
portEXIT_CRITICAL(&s_mgr_mux);
if (!local_auth_enabled)
{
ESP_LOGI(TAG, "[LB] limit=%uA → modo OPEN, reautorizando", evt->max_current);
evse_state_set_authorized(true);
}
else
{
if (prev_auth)
{
ESP_LOGI(TAG, "[LB] limit=%uA → RFID/OCPP, retomando autorização anterior", evt->max_current);
evse_state_set_authorized(true);
}
}
portENTER_CRITICAL(&s_mgr_mux);
lb_prev_authorized = false;
portEXIT_CRITICAL(&s_mgr_mux);
}
}
}
}
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != OCPP_EVENTS) return;
switch (id)
{
case OCPP_EVENT_AUTHORIZED:
ESP_LOGI(TAG, "[OCPP] Authorized");
evse_state_set_authorized(true);
lb_clear_pause_state();
break;
case OCPP_EVENT_AUTH_REJECTED:
case OCPP_EVENT_AUTH_TIMEOUT:
case OCPP_EVENT_REMOTE_STOP:
case OCPP_EVENT_STOP_TX:
ESP_LOGW(TAG, "[OCPP] Authorization/Stop");
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
case OCPP_EVENT_REMOTE_START:
ESP_LOGI(TAG, "[OCPP] RemoteStart");
evse_state_set_authorized(true);
lb_clear_pause_state();
break;
case OCPP_EVENT_START_TX:
ESP_LOGI(TAG, "[OCPP] StartTx");
lb_clear_pause_state();
break;
case OCPP_EVENT_OPERATIVE_UPDATED:
{
if (!data)
{
ESP_LOGW(TAG, "[OCPP] OperativeUpdated sem payload — ignorado");
break;
}
const ocpp_operative_event_t *ev = (const ocpp_operative_event_t *)data;
ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld",
(int)ev->operative, (long long)ev->timestamp_us);
evse_config_set_enabled(ev->operative);
break;
}
default:
ESP_LOGD(TAG, "[OCPP] Unhandled event id=%" PRId32, id);
break;
}
}
static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != SCHED_EVENTS || data == NULL) return;
const sched_event_state_t *ev = (const sched_event_state_t *)data;
portENTER_CRITICAL(&s_sched_mux);
s_sched_allowed = ev->allowed_now;
portEXIT_CRITICAL(&s_sched_mux);
ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now);
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false);
}
}
void evse_manager_init(void)
{
evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL);
esp_err_t err = evse_config_init();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to init EVSE config NVS: %s", esp_err_to_name(err));
}
evse_error_init();
evse_hardware_init();
evse_state_init();
evse_meter_init();
evse_session_init();
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, &on_sched_event, NULL));
ESP_LOGI(TAG, "EVSE Manager inicializado.");
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 8192, NULL, 4, NULL);
configASSERT(rc == pdPASS);
}
void evse_manager_tick(void)
{
xSemaphoreTake(evse_mutex, portMAX_DELAY);
evse_hardware_tick();
evse_error_tick();
evse_state_tick();
evse_temperature_check();
evse_session_tick();
evse_manager_handle_auth_on_tick();
xSemaphoreGive(evse_mutex);
}