#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 #include #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); }