#include #include #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 "esp_check.h" #include "storage_service.h" #define NVS_NAMESPACE "evse_limits" static const char *TAG = "evse_limits"; static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED; static bool limit_reached = false; static uint32_t consumption_limit = 0; // Wh static uint32_t charging_time_limit = 0; // seconds static uint16_t under_power_limit = 0; // W static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); } static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(1000); } // --------------------------------- // Init + defaults // --------------------------------- esp_err_t evse_limits_init(void) { ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed"); ESP_LOGI(TAG, "EVSE limits init OK (storage-backed)"); return ESP_OK; } void evse_limits_check_defaults(void) { esp_err_t err; bool needs_flush = false; uint32_t u32 = 0; uint16_t u16 = 0; ESP_LOGD(TAG, "Checking default limits..."); // Consumption limit (Wh) default = 0 (disabled) err = storage_get_u32_sync(NVS_NAMESPACE, "def_cons_lim", &u32, BOOT_TO()); if (err == ESP_OK) { portENTER_CRITICAL(&evse_mux); consumption_limit = u32; portEXIT_CRITICAL(&evse_mux); } else { portENTER_CRITICAL(&evse_mux); consumption_limit = 0; portEXIT_CRITICAL(&evse_mux); (void)storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", 0); needs_flush = true; ESP_LOGW(TAG, "Missing def_cons_lim (%s) -> default=0 (persisted).", esp_err_to_name(err)); } // Charging time limit (s) default = 0 (disabled) err = storage_get_u32_sync(NVS_NAMESPACE, "def_ch_time_lim", &u32, BOOT_TO()); if (err == ESP_OK) { portENTER_CRITICAL(&evse_mux); charging_time_limit = u32; portEXIT_CRITICAL(&evse_mux); } else { portENTER_CRITICAL(&evse_mux); charging_time_limit = 0; portEXIT_CRITICAL(&evse_mux); (void)storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", 0); needs_flush = true; ESP_LOGW(TAG, "Missing def_ch_time_lim (%s) -> default=0 (persisted).", esp_err_to_name(err)); } // Under-power limit (W) default = 0 (disabled) err = storage_get_u16_sync(NVS_NAMESPACE, "def_un_pwr_lim", &u16, BOOT_TO()); if (err == ESP_OK) { portENTER_CRITICAL(&evse_mux); under_power_limit = u16; portEXIT_CRITICAL(&evse_mux); } else { portENTER_CRITICAL(&evse_mux); under_power_limit = 0; portEXIT_CRITICAL(&evse_mux); (void)storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", 0); needs_flush = true; ESP_LOGW(TAG, "Missing def_un_pwr_lim (%s) -> default=0 (persisted).", esp_err_to_name(err)); } if (needs_flush) { esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000)); if (fe != ESP_OK) ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe)); else ESP_LOGD(TAG, "Defaults committed (flush)."); } } // --------------------------------- // Limit reached 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(); } // --------------------------------- // Consumption limit // --------------------------------- 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; esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to persist consumption limit (%" PRIu32 " Wh): %s", value, esp_err_to_name(err)); } } // --------------------------------- // Charging time limit // --------------------------------- 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; esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to persist charging time limit (%" PRIu32 " s): %s", value, esp_err_to_name(err)); } } // --------------------------------- // Under-power limit // --------------------------------- 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; esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to persist under-power limit (%" PRIu32 " W): %s", (uint32_t)value, esp_err_to_name(err)); } } // --------------------------------- // Runtime check // --------------------------------- void evse_limits_check(void) { // Só faz sentido quando há energia ativa (C2/D2) if (!evse_state_is_charging(evse_get_state())) return; evse_session_t sess; if (!evse_session_get(&sess) || !sess.is_current) return; uint32_t cons_lim; uint32_t time_lim; uint16_t unp_lim; portENTER_CRITICAL(&evse_mux); cons_lim = consumption_limit; time_lim = charging_time_limit; unp_lim = under_power_limit; portEXIT_CRITICAL(&evse_mux); bool reached = false; if (cons_lim > 0 && sess.energy_wh >= cons_lim) { ESP_LOGW(TAG, "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh", sess.energy_wh, cons_lim); reached = true; } if (time_lim > 0 && sess.duration_s >= time_lim) { ESP_LOGW(TAG, "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s", sess.duration_s, time_lim); reached = true; } int32_t p = evse_meter_get_instant_power(); uint32_t inst_power = (p > 0) ? (uint32_t)p : 0; if (unp_lim > 0 && inst_power < (uint32_t)unp_lim) { ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W", inst_power, (uint32_t)unp_lim); reached = true; } if (reached) evse_set_limit_reached(true); }