#include // for PRIu32 #include "evse_state.h" #include "evse_api.h" #include "evse_limits.h" #include "evse_meter.h" #include "evse_session.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_err.h" #include "nvs.h" // ======================== // Concurrency protection // ======================== static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED; // ======================== // Runtime state (volatile) // ======================== static bool limit_reached = false; static uint32_t consumption_limit = 0; // Energy limit in Wh static uint32_t charging_time_limit = 0; // Time limit in seconds static uint16_t under_power_limit = 0; // Minimum acceptable power in W // ======================== // Limit status flag // ======================== bool evse_get_limit_reached(void) { bool val; portENTER_CRITICAL(&evse_mux); val = limit_reached; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_limit_reached(bool v) { portENTER_CRITICAL(&evse_mux); limit_reached = v; portEXIT_CRITICAL(&evse_mux); } bool evse_is_limit_reached(void) { return evse_get_limit_reached(); } // ======================== // Runtime limit accessors // ======================== uint32_t evse_get_consumption_limit(void) { uint32_t val; portENTER_CRITICAL(&evse_mux); val = consumption_limit; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_consumption_limit(uint32_t value) { bool changed = false; portENTER_CRITICAL(&evse_mux); if (consumption_limit != value) { consumption_limit = value; changed = true; } portEXIT_CRITICAL(&evse_mux); if (!changed) return; nvs_handle_t h; esp_err_t err = nvs_open("evse", NVS_READWRITE, &h); if (err == ESP_OK) { err = nvs_set_u32(h, "def_cons_lim", value); if (err == ESP_OK) err = nvs_commit(h); nvs_close(h); if (err != ESP_OK) { ESP_LOGE("EVSE_LIMITS", "Failed to persist consumption limit (%" PRIu32 " Wh): %s", value, esp_err_to_name(err)); } } else { ESP_LOGE("EVSE_LIMITS", "Failed to open NVS for consumption limit: %s", esp_err_to_name(err)); } } uint32_t evse_get_charging_time_limit(void) { uint32_t val; portENTER_CRITICAL(&evse_mux); val = charging_time_limit; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_charging_time_limit(uint32_t value) { bool changed = false; portENTER_CRITICAL(&evse_mux); if (charging_time_limit != value) { charging_time_limit = value; changed = true; } portEXIT_CRITICAL(&evse_mux); if (!changed) return; nvs_handle_t h; esp_err_t err = nvs_open("evse", NVS_READWRITE, &h); if (err == ESP_OK) { err = nvs_set_u32(h, "def_ch_time_lim", value); if (err == ESP_OK) err = nvs_commit(h); nvs_close(h); if (err != ESP_OK) { ESP_LOGE("EVSE_LIMITS", "Failed to persist charging time limit (%" PRIu32 " s): %s", value, esp_err_to_name(err)); } } else { ESP_LOGE("EVSE_LIMITS", "Failed to open NVS for charging time limit: %s", esp_err_to_name(err)); } } uint16_t evse_get_under_power_limit(void) { uint16_t val; portENTER_CRITICAL(&evse_mux); val = under_power_limit; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_under_power_limit(uint16_t value) { bool changed = false; portENTER_CRITICAL(&evse_mux); if (under_power_limit != value) { under_power_limit = value; changed = true; } portEXIT_CRITICAL(&evse_mux); if (!changed) return; nvs_handle_t h; esp_err_t err = nvs_open("evse", NVS_READWRITE, &h); if (err == ESP_OK) { err = nvs_set_u16(h, "def_un_pwr_lim", value); if (err == ESP_OK) err = nvs_commit(h); nvs_close(h); if (err != ESP_OK) { ESP_LOGE("EVSE_LIMITS", "Failed to persist under-power limit (%" PRIu32 " W): %s", (uint32_t)value, esp_err_to_name(err)); } } else { ESP_LOGE("EVSE_LIMITS", "Failed to open NVS for under-power limit: %s", esp_err_to_name(err)); } } // ======================== // Limit checking logic // ======================== void evse_limits_check(void) { // Só faz sentido durante carregamento if (!evse_state_is_charging(evse_get_state())) { return; } evse_session_t sess; if (!evse_session_get(&sess) || !sess.is_current) { // Sem sessão ativa → nada a fazer return; } bool reached = false; // 1) Limite de energia (Wh) if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) { ESP_LOGW("EVSE_LIMITS", "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh", sess.energy_wh, consumption_limit); reached = true; } // 2) Limite de tempo (s) if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) { ESP_LOGW("EVSE_LIMITS", "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s", sess.duration_s, charging_time_limit); reached = true; } // 3) Under-power (potência instantânea) uint32_t inst_power = evse_meter_get_instant_power(); if (under_power_limit > 0 && inst_power < under_power_limit) { ESP_LOGW("EVSE_LIMITS", "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W", (uint32_t)inst_power, (uint32_t)under_power_limit); reached = true; } if (reached) { evse_set_limit_reached(true); } }