new module

This commit is contained in:
2025-12-09 11:48:31 +00:00
parent 4820d9111e
commit e6e2622a95
98 changed files with 5349 additions and 8607 deletions

View File

@@ -18,5 +18,5 @@ idf_component_register(
SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash driver
REQUIRES peripherals auth loadbalancer
REQUIRES peripherals auth loadbalancer scheduler
)

View File

@@ -2,7 +2,7 @@
#include "evse_config.h"
#include "board_config.h"
#include "evse_limits.h"
#include "evse_api.h" // <— para evse_get_state / evse_state_is_charging
#include "evse_api.h"
#include "esp_log.h"
#include "nvs.h"
#include "esp_timer.h"
@@ -74,8 +74,8 @@ void evse_check_defaults(void)
charging_current = u16;
}
// Runtime charging current initialized from persisted default
charging_current_runtime = max_charging_current;
// Runtime charging current inicializado a partir do default persistido
charging_current_runtime = charging_current;
ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime);
// Auth required
@@ -150,7 +150,6 @@ void evse_check_defaults(void)
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted).");
}
// Save to NVS if needed
if (needs_commit)
{
err = nvs_commit(nvs);
@@ -218,7 +217,6 @@ esp_err_t evse_set_default_charging_current(uint16_t value)
// ========================
void evse_set_runtime_charging_current(uint16_t value)
{
if (value > max_charging_current)
{
value = max_charging_current;
@@ -232,7 +230,6 @@ void evse_set_runtime_charging_current(uint16_t value)
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
// --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE ---
evse_config_event_data_t evt = {
.charging = evse_state_is_charging(evse_get_state()),
.hw_max_current = (float)evse_get_max_charging_current(),
@@ -314,7 +311,6 @@ void evse_config_set_available(bool available)
{
is_available = available ? true : false;
// Persist
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available);
if (err == ESP_OK)
err = nvs_commit(nvs);
@@ -323,7 +319,6 @@ void evse_config_set_available(bool available)
ESP_LOGE(TAG, "Failed to persist 'available': %s", esp_err_to_name(err));
}
// AVAILABLE_UPDATED
evse_available_event_data_t e = {
.available = is_available,
.timestamp_us = esp_timer_get_time()};
@@ -342,7 +337,6 @@ void evse_config_set_enabled(bool enabled)
{
is_enabled = enabled ? true : false;
// Persist
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
if (err == ESP_OK)
err = nvs_commit(nvs);
@@ -351,7 +345,6 @@ void evse_config_set_enabled(bool enabled)
ESP_LOGE(TAG, "Failed to persist 'enabled': %s", esp_err_to_name(err));
}
// ENABLE_UPDATED
evse_enable_event_data_t e = {
.enabled = is_enabled,
.timestamp_us = esp_timer_get_time()};

View File

@@ -1,5 +1,3 @@
// evse_core.c - Main EVSE control logic
#include "evse_fsm.h"
#include "evse_error.h"
#include "evse_limits.h"
@@ -16,29 +14,34 @@ static const char *TAG = "evse_core";
static SemaphoreHandle_t mutex;
static evse_state_t last_state = EVSE_STATE_A;
static void evse_process(void);
static void evse_core_task(void *arg);
// ================================
// Initialization
// ================================
void evse_init(void) {
void evse_init(void)
{
ESP_LOGI(TAG, "EVSE Init");
mutex = xSemaphoreCreateMutex(); // Optional: use static version for deterministic memory
mutex = xSemaphoreCreateMutex();
if (!mutex)
{
ESP_LOGE(TAG, "Failed to create EVSE core mutex");
return;
}
evse_check_defaults();
evse_fsm_reset();
pilot_set_level(true); // Enable pilot output
pilot_set_level(true);
xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL);
}
// ================================
// Main Processing Logic
// ================================
static void evse_process(void)
{
if (!mutex)
{
return;
}
void evse_process(void) {
xSemaphoreTake(mutex, portMAX_DELAY);
pilot_voltage_t pilot_voltage;
@@ -49,34 +52,38 @@ void evse_process(void) {
evse_error_check(pilot_voltage, is_n12v);
// Só chama FSM, que decide tudo
evse_fsm_process(
pilot_voltage,
evse_state_get_authorized(),
evse_config_is_available(),
evse_config_is_enabled()
);
evse_config_is_enabled());
evse_limits_check();
evse_state_t current = evse_get_state();
if (current != last_state) {
//ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current));
last_state = current;
if (evse_is_limit_reached())
{
if (evse_state_get_authorized())
{
ESP_LOGW(TAG, "Charging limit reached → revoking authorization");
evse_state_set_authorized(false);
}
}
evse_mark_error_cleared();
evse_state_t current = evse_get_state();
if (current != last_state)
{
last_state = current;
}
xSemaphoreGive(mutex);
}
// ================================
// Background Task
// ================================
static void evse_core_task(void *arg) {
while (true) {
static void evse_core_task(void *arg)
{
(void)arg;
while (true)
{
evse_process();
vTaskDelay(pdMS_TO_TICKS(100)); // 10 Hz cycle
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}

View File

@@ -3,118 +3,227 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"
#include "esp_log.h"
#include "ntc_sensor.h"
static const char *TAG = "evse_error";
// Estado global de erros
static uint32_t error_bits = 0;
static TickType_t auto_clear_timeout = 0;
// Sticky flag: "todos erros foram limpos"
static bool error_cleared = false;
void evse_error_init(void) {
// Inicialização do sistema de erros
// Proteção contra concorrência
static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
void evse_error_init(void)
{
portENTER_CRITICAL(&error_mux);
error_bits = 0;
auto_clear_timeout = 0;
error_cleared = false;
portEXIT_CRITICAL(&error_mux);
}
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v) {
ESP_LOGD(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s",
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
{
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
pilot_voltage, is_n12v ? "true" : "false");
// Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1) {
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
// 1) Falha elétrica geral no pilot
if (pilot_voltage == PILOT_VOLTAGE_1)
{
bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_PILOT_FAULT_BIT;
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
}
}
else
{
// Pilot voltou a nível válido → limpa erro de pilot fault
evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT);
}
// Falta de -12V durante PWM (C ou D)
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) {
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
ESP_LOGW(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s", pilot_voltage, is_n12v ? "true" : "false");
// 2) Falta de -12V durante PWM (C ou D)
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v)
{
bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_DIODE_SHORT_BIT;
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
}
}
else
{
// Se já não estamos em C/D sem -12V, limpa o erro de diodo curto
evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
}
}
void evse_temperature_check(void) {
float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida)
uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável
void evse_temperature_check(void)
{
float temp_c = ntc_temp_sensor();
uint8_t threshold = evse_get_temp_threshold();
// Log informativo com os valores
ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold);
ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
temp_c, threshold);
// Se a temperatura parecer inválida, aplica erro de sensor
if (temp_c < -40.0f || temp_c > 150.0f) {
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
// Temperatura inválida -> erro de sensor
if (temp_c < -40.0f || temp_c > 150.0f)
{
bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_TEMPERATURE_FAULT_BIT;
first_time = true;
}
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
}
return;
}
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida
// Leitura válida -> limpa erro de sensor
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);
if (temp_c >= threshold) {
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold);
// Temperatura máxima
if (temp_c >= threshold)
{
bool first_time = false;
portENTER_CRITICAL(&error_mux);
if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT))
{
error_cleared = false;
error_bits |= EVSE_ERR_TEMPERATURE_HIGH_BIT;
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000);
first_time = true;
}
} else {
portEXIT_CRITICAL(&error_mux);
if (first_time)
{
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
temp_c, threshold);
}
}
else
{
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
}
}
uint32_t evse_get_error(void) {
return error_bits;
uint32_t evse_get_error(void)
{
portENTER_CRITICAL(&error_mux);
uint32_t val = error_bits;
portEXIT_CRITICAL(&error_mux);
return val;
}
bool evse_is_error_cleared(void) {
return error_cleared;
bool evse_error_cleared_flag(void)
{
portENTER_CRITICAL(&error_mux);
bool v = error_cleared;
portEXIT_CRITICAL(&error_mux);
return v;
}
void evse_mark_error_cleared(void) {
void evse_error_reset_flag(void)
{
portENTER_CRITICAL(&error_mux);
error_cleared = false;
portEXIT_CRITICAL(&error_mux);
}
// Já existentes
void evse_error_set(uint32_t bitmask) {
void evse_error_set(uint32_t bitmask)
{
portENTER_CRITICAL(&error_mux);
error_cleared = false;
error_bits |= bitmask;
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) {
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS)
{
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s
}
portEXIT_CRITICAL(&error_mux);
}
void evse_error_clear(uint32_t bitmask) {
bool had_error = error_bits != 0;
void evse_error_clear(uint32_t bitmask)
{
portENTER_CRITICAL(&error_mux);
bool had_error = (error_bits != 0);
error_bits &= ~bitmask;
if (had_error && error_bits == 0) {
if (had_error && error_bits == 0)
{
error_cleared = true;
}
portEXIT_CRITICAL(&error_mux);
}
void evse_error_tick(void) {
if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && xTaskGetTickCount() >= auto_clear_timeout) {
evse_error_clear(EVSE_ERR_AUTO_CLEAR_BITS);
void evse_error_tick(void)
{
portENTER_CRITICAL(&error_mux);
if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) &&
auto_clear_timeout != 0 &&
xTaskGetTickCount() >= auto_clear_timeout)
{
error_bits &= ~EVSE_ERR_AUTO_CLEAR_BITS;
if (error_bits == 0)
{
error_cleared = true;
}
auto_clear_timeout = 0;
}
portEXIT_CRITICAL(&error_mux);
}
bool evse_error_is_active(void) {
return error_bits != 0;
bool evse_error_is_active(void)
{
return evse_get_error() != 0;
}
uint32_t evse_error_get_bits(void) {
return error_bits;
}
void evse_error_reset_flag(void) {
error_cleared = false;
}
bool evse_error_cleared_flag(void) {
return error_cleared;
uint32_t evse_error_get_bits(void)
{
return evse_get_error();
}

View File

@@ -20,94 +20,108 @@ static const char *TAG = "evse_fsm";
static bool c1_d1_waiting = false;
static TickType_t c1_d1_relay_to = 0;
void evse_fsm_reset(void) {
void evse_fsm_reset(void)
{
evse_set_state(EVSE_STATE_A);
c1_d1_waiting = false;
c1_d1_relay_to = 0;
}
// ... includes e defines como já estão
static void update_outputs(evse_state_t state) {
static void update_outputs(evse_state_t state)
{
const uint16_t current = evse_get_runtime_charging_current();
uint8_t cable_max_current = evse_get_max_charging_current();
const bool socket_outlet = evse_get_socket_outlet();
if (socket_outlet) {
if (socket_outlet)
{
cable_max_current = proximity_get_max_current();
}
// Segurança: relé sempre off e outputs seguros em caso de erro
if (evse_get_error() != 0) {
if (ac_relay_get_state()) {
if (evse_get_error() != 0)
{
if (ac_relay_get_state())
{
ac_relay_set_state(false);
ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!");
}
ac_relay_set_state(false); // redundância tolerável
pilot_set_level(true); // sinal pilot sempre 12V (A)
if (board_config.socket_lock && socket_outlet) {
ac_relay_set_state(false);
pilot_set_level(true);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false);
}
return;
}
// Fluxo normal
switch (state) {
case EVSE_STATE_A:
case EVSE_STATE_E:
case EVSE_STATE_F:
ac_relay_set_state(false);
pilot_set_level(state == EVSE_STATE_A);
if (board_config.socket_lock && socket_outlet) {
socket_lock_set_locked(false);
}
break;
case EVSE_STATE_B1:
pilot_set_level(true);
ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet) {
socket_lock_set_locked(true);
}
if (rcm_test()) {
//ESP_LOGI(TAG, "RCM self test passed");
} else {
//ESP_LOGW(TAG, "RCM self test failed");
}
break;
case EVSE_STATE_B2:
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false);
break;
case EVSE_STATE_C1:
case EVSE_STATE_D1: {
pilot_set_amps(MIN(current, cable_max_current)); // mantém PWM
ac_relay_set_state(false); // relé ainda desligado
c1_d1_waiting = true;
c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000);
break;
switch (state)
{
case EVSE_STATE_A:
case EVSE_STATE_E:
case EVSE_STATE_F:
ac_relay_set_state(false);
pilot_set_level(state == EVSE_STATE_A);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(false);
}
case EVSE_STATE_C2:
case EVSE_STATE_D2:
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(true); // Só chega aqui se não há erro!
break;
break;
case EVSE_STATE_B1:
pilot_set_level(true);
ac_relay_set_state(false);
if (board_config.socket_lock && socket_outlet)
{
socket_lock_set_locked(true);
}
if (rcm_test())
{
// ESP_LOGI(TAG, "RCM self test passed");
}
else
{
// ESP_LOGW(TAG, "RCM self test failed");
}
break;
case EVSE_STATE_B2:
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false);
break;
case EVSE_STATE_C1:
case EVSE_STATE_D1:
{
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false);
c1_d1_waiting = true;
c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000);
break;
}
case EVSE_STATE_C2:
case EVSE_STATE_D2:
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(true);
break;
}
}
// FSM principal - centraliza a lógica de erro e de todos os estados
// FSM principal
void evse_fsm_process(
pilot_voltage_t pilot_voltage,
bool authorized,
bool available,
bool enabled
) {
bool enabled)
{
// Proteção total: erro força F sempre!
if (evse_get_error() != 0) {
if (evse_get_state() != EVSE_STATE_F) {
if (evse_get_error() != 0)
{
if (evse_get_state() != EVSE_STATE_F)
{
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)");
evse_set_state(EVSE_STATE_F);
}
@@ -119,91 +133,97 @@ void evse_fsm_process(
evse_state_t prev = evse_get_state();
evse_state_t curr = prev;
switch (curr) {
case EVSE_STATE_A:
if (!available) {
evse_set_state(EVSE_STATE_F);
} else if (pilot_voltage == PILOT_VOLTAGE_9) {
evse_set_state(EVSE_STATE_B1);
}
break;
switch (curr)
{
case EVSE_STATE_A:
if (!available)
{
evse_set_state(EVSE_STATE_F);
}
else if (pilot_voltage == PILOT_VOLTAGE_9)
{
evse_set_state(EVSE_STATE_B1);
}
break;
case EVSE_STATE_B1:
case EVSE_STATE_B2:
if (!available) {
case EVSE_STATE_B1:
case EVSE_STATE_B2:
if (!available)
{
evse_set_state(EVSE_STATE_F);
break;
}
switch (pilot_voltage)
{
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break;
default:
break;
}
break;
case EVSE_STATE_C1:
case EVSE_STATE_D1:
if (c1_d1_waiting && now >= c1_d1_relay_to)
{
ac_relay_set_state(false);
c1_d1_waiting = false;
if (!available)
{
evse_set_state(EVSE_STATE_F);
break;
}
switch (pilot_voltage) {
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break;
default:
break;
}
break;
}
__attribute__((fallthrough));
case EVSE_STATE_C1:
case EVSE_STATE_D1:
if (c1_d1_waiting && now >= c1_d1_relay_to) {
ac_relay_set_state(false);
c1_d1_waiting = false;
if (!available) {
evse_set_state(EVSE_STATE_F);
break;
}
}
__attribute__((fallthrough));
case EVSE_STATE_C2:
case EVSE_STATE_D2:
if (!enabled || !available) {
evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1)
? EVSE_STATE_D1 : EVSE_STATE_C1);
break;
}
switch (pilot_voltage) {
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break;
case PILOT_VOLTAGE_3:
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
default:
break;
}
case EVSE_STATE_C2:
case EVSE_STATE_D2:
if (!enabled || !available)
{
evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1)
? EVSE_STATE_D1
: EVSE_STATE_C1);
break;
case EVSE_STATE_E:
// Estado elétrico grave: só reset manual
}
switch (pilot_voltage)
{
case PILOT_VOLTAGE_6:
evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1);
break;
case EVSE_STATE_F:
// Fault: só sai se disponível e sem erro
if (available && evse_get_error() == 0) {
evse_set_state(EVSE_STATE_A);
}
case PILOT_VOLTAGE_3:
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
break;
case PILOT_VOLTAGE_9:
evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1);
break;
case PILOT_VOLTAGE_12:
evse_set_state(EVSE_STATE_A);
break;
default:
break;
}
break;
case EVSE_STATE_E:
// Estado elétrico grave: só reset manual
break;
case EVSE_STATE_F:
// Fault: só sai se disponível e sem erro
if (available && evse_get_error() == 0)
{
evse_set_state(EVSE_STATE_A);
}
break;
}
evse_state_t next = evse_get_state();
update_outputs(next);
if (next != prev) {
ESP_LOGI(TAG, "State changed: %s -> %s",
evse_state_to_str(prev),
evse_state_to_str(next));
}
}

View File

@@ -7,14 +7,8 @@
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// ========================
// External state references
// ========================
//extern evse_state_t current_state; // Current EVSE FSM state
//extern TickType_t session_start_tick; // Timestamp of charging session start
#include "esp_err.h"
#include "nvs.h"
// ========================
// Concurrency protection
@@ -26,24 +20,17 @@ 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
// ========================
// Default (persistent) limits
// ========================
static uint32_t default_consumption_limit = 0;
static uint32_t default_charging_time_limit = 0;
static uint16_t default_under_power_limit = 0;
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 evse_get_limit_reached(void)
{
bool val;
portENTER_CRITICAL(&evse_mux);
val = limit_reached;
@@ -51,17 +38,24 @@ bool evse_get_limit_reached(void) {
return val;
}
void evse_set_limit_reached(bool v) {
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 evse_get_consumption_limit(void)
{
uint32_t val;
portENTER_CRITICAL(&evse_mux);
val = consumption_limit;
@@ -69,13 +63,47 @@ uint32_t evse_get_consumption_limit(void) {
return val;
}
void evse_set_consumption_limit(uint32_t value) {
void evse_set_consumption_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
consumption_limit = value;
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 evse_get_charging_time_limit(void)
{
uint32_t val;
portENTER_CRITICAL(&evse_mux);
val = charging_time_limit;
@@ -83,13 +111,47 @@ uint32_t evse_get_charging_time_limit(void) {
return val;
}
void evse_set_charging_time_limit(uint32_t value) {
void evse_set_charging_time_limit(uint32_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
charging_time_limit = value;
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 evse_get_under_power_limit(void)
{
uint16_t val;
portENTER_CRITICAL(&evse_mux);
val = under_power_limit;
@@ -97,92 +159,97 @@ uint16_t evse_get_under_power_limit(void) {
return val;
}
void evse_set_under_power_limit(uint16_t value) {
void evse_set_under_power_limit(uint16_t value)
{
bool changed = false;
portENTER_CRITICAL(&evse_mux);
under_power_limit = value;
if (under_power_limit != value)
{
under_power_limit = value;
changed = true;
}
portEXIT_CRITICAL(&evse_mux);
}
// ========================
// Default (persistent) limit accessors
// These values can be stored/restored via NVS
// ========================
if (!changed)
return;
uint32_t evse_get_default_consumption_limit(void) {
return default_consumption_limit;
}
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);
void evse_set_default_consumption_limit(uint32_t value) {
default_consumption_limit = value;
}
uint32_t evse_get_default_charging_time_limit(void) {
return default_charging_time_limit;
}
void evse_set_default_charging_time_limit(uint32_t value) {
default_charging_time_limit = value;
}
uint16_t evse_get_default_under_power_limit(void) {
return default_under_power_limit;
}
void evse_set_default_under_power_limit(uint16_t value) {
default_under_power_limit = value;
}
bool evse_is_limit_reached(void) {
return evse_get_limit_reached();
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
// This function must be called periodically while charging.
// It will flag the session as "limit reached" when thresholds are violated.
// ========================
void evse_limits_check(void) {
// Only check during an active charging session
if (!evse_state_is_charging(evse_get_state())) {
void evse_limits_check(void)
{
// Só faz sentido durante carregamento
if (!evse_state_is_charging(evse_get_state()))
{
return;
}
evse_session_t sess;
// Retrieve accumulated data for the current session
if (!evse_session_get(&sess) || !sess.is_current) {
// If there's no active session, abort
if (!evse_session_get(&sess) || !sess.is_current)
{
// Sem sessão ativa → nada a fazer
return;
}
bool reached = false;
// 1) Energy consumption limit (Wh)
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) {
// 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) Charging time limit (seconds)
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) {
// 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 limit (instantaneous power)
// 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) {
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);
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
(uint32_t)inst_power,
(uint32_t)under_power_limit);
reached = true;
}
if (reached) {
if (reached)
{
evse_set_limit_reached(true);
}
}
}

View File

@@ -7,29 +7,35 @@
#include "evse_api.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_event.h"
#include <string.h>
#include <inttypes.h>
#include "auth_events.h"
#include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "esp_event.h"
#include "scheduler_events.h"
static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex;
static bool auth_enabled = false;
static volatile bool auth_enabled = false;
// Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false;
static bool lb_prev_authorized = false;
static volatile bool lb_paused = false;
static volatile bool lb_prev_authorized = false;
// Estado de janela do scheduler
static volatile bool s_sched_allowed = true;
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
@@ -41,8 +47,20 @@ static void lb_clear_pause_state(void)
lb_prev_authorized = false;
}
// Exposto para outros módulos (se quiserem saber se o scheduler permite)
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();
if (auth_enabled)
{
// Se o carro foi desconectado, revoga autorização
@@ -53,23 +71,38 @@ static void evse_manager_handle_auth_on_tick(void)
// Desconexão física invalida qualquer pausa pendente do LB
lb_clear_pause_state();
}
// Em modos RFID/OCPP, o scheduler pode também forçar paragem
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_state_set_authorized(false);
}
}
else
{
// Se autenticação está desativada, garante autorização sempre ativa
if (!evse_state_get_authorized())
// Modo OPEN: só autoriza se LB e Scheduler permitirem
if (!lb_paused && sched_allowed && !evse_state_get_authorized())
{
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization.");
// Em modo OPEN, pausa do LB não é tão relevante, mas limpamos mesmo assim
ESP_LOGI(TAG, "Authentication disabled → forced authorization (within schedule).");
lb_clear_pause_state();
}
// Fora da janela, garantir que não fica autorizado
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (OPEN mode) → revoking authorization.");
evse_state_set_authorized(false);
}
}
}
// ===== Task de ciclo principal =====
static void evse_manager_task(void *arg)
{
(void)arg;
while (true)
{
evse_manager_tick();
@@ -77,8 +110,11 @@ static void evse_manager_task(void *arg)
}
}
// ===== Tratador de eventos de AUTH =====
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;
@@ -105,7 +141,9 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(g_mode));
if (g_mode == AUTH_MODE_OPEN)
{
evse_state_set_authorized(true);
// Em OPEN, a autorização passa a ser gerida por evse_manager_handle_auth_on_tick(),
// que também respeita o scheduler.
evse_state_set_authorized(false); // vai ser forçado no próximo tick se permitido
auth_enabled = false;
}
else
@@ -121,17 +159,22 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
}
}
// ===== Tratador de eventos de loadbalancer =====
// ===== Tratador de eventos de Load Balancer =====
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);
// Ações adicionais podem ser adicionadas aqui conforme necessário
}
else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{
@@ -167,16 +210,17 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
{
lb_paused = false;
// Só retomamos se EVSE estiver operacional
// Só retomamos se EVSE estiver operacional e scheduler permitir
bool can_resume =
(evse_get_error() == 0) &&
evse_config_is_available() &&
evse_config_is_enabled();
evse_config_is_enabled() &&
evse_sched_is_allowed();
if (!can_resume)
{
ESP_LOGW(TAG,
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado)",
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado/fora de horário)",
evt->max_current);
lb_clear_pause_state();
return;
@@ -184,7 +228,7 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
if (!auth_enabled)
{
// Modo OPEN: retoma sempre
// Modo OPEN: retoma sempre (se dentro da janela do scheduler)
ESP_LOGI(TAG,
"[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)",
evt->max_current);
@@ -222,8 +266,11 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
}
}
// ===== Tratador de eventos de OCPP =====
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != OCPP_EVENTS)
return;
@@ -261,7 +308,6 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
case OCPP_EVENT_START_TX:
ESP_LOGI(TAG, "[OCPP] StartTx");
// StartTx em si não precisa mexer em auth, mas limpamos estado de pausa por segurança
lb_clear_pause_state();
break;
@@ -285,8 +331,6 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
// Mapear operative → enabled local (persiste e emite EVSE_EVENT_ENABLE_UPDATED)
evse_config_set_enabled(ev->operative);
// Opcional: poderias também limpar a pausa aqui, dependendo da política
// lb_clear_pause_state();
break;
}
@@ -296,10 +340,44 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
}
}
// ===== Tratador de eventos de Scheduler =====
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] event id=%" PRIi32 " allowed_now=%d",
id, (int)ev->allowed_now);
// Se a janela fechou, parar sessão (revogar autorização)
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_state_set_authorized(false);
}
// Se a janela abriu de novo, não auto-reautorizamos aqui.
// Deixamos que o utilizador / OCPP decida iniciar nova sessão.
// (Em modo OPEN, o tick trata disso respeitando o scheduler.)
}
// ===== Inicialização =====
void evse_manager_init(void)
{
evse_mutex = xSemaphoreCreateMutex();
configASSERT(evse_mutex != NULL);
evse_config_init();
evse_error_init();
@@ -311,9 +389,12 @@ void evse_manager_init(void)
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.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
configASSERT(rc == pdPASS);
}
// ===== Main Tick =====
@@ -331,4 +412,5 @@ void evse_manager_tick(void)
xSemaphoreGive(evse_mutex);
}
// === Fim de: components/evse/evse_manager.c ===

View File

@@ -48,7 +48,7 @@ void evse_meter_on_meter_event(void *arg, void *event_data)
meter_data.energy_wh = (uint32_t)(evt->total_energy);
xSemaphoreGive(meter_mutex);
ESP_LOGI(TAG,
ESP_LOGD(TAG,
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, "

View File

@@ -23,24 +23,20 @@
#define MAX_SAMPLE_ATTEMPTS 1000
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
// ADC121S021 setup
#define ADC121_VREF_MV 3300 // AJUSTE conforme Vref do seu hardware!
#define ADC121_MAX 4095 // 12 bits
#define ADC121_VREF_MV 3300
#define ADC121_MAX 4095
static const char *TAG = "evse_pilot";
// Memoização de estado para evitar comandos/logs desnecessários
static int last_pilot_level = -1;
static uint32_t last_pwm_duty = 0;
// Função para converter leitura bruta do ADC para mV
static int adc_raw_to_mv(uint16_t raw) {
return (raw * ADC121_VREF_MV) / ADC121_MAX;
}
void pilot_init(void)
{
// PWM (LEDC) configuração
ledc_timer_config_t ledc_timer = {
.speed_mode = PILOT_PWM_SPEED_MODE,
.timer_num = PILOT_PWM_TIMER,
@@ -61,20 +57,18 @@ void pilot_init(void)
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
//ESP_ERROR_CHECK(ledc_fade_func_install(0));
// Inicializa ADC121S021 externo
adc121s021_dma_init();
}
void pilot_set_level(bool level)
{
if (last_pilot_level == level) return; // só muda se necessário
if (last_pilot_level == level) return;
last_pilot_level = level;
ESP_LOGI(TAG, "Set level %d", level);
ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0);
last_pwm_duty = 0; // PWM parado
last_pwm_duty = 0;
}
void pilot_set_amps(uint16_t amps)
@@ -85,13 +79,11 @@ void pilot_set_amps(uint16_t amps)
}
uint32_t duty_percent;
if (amps <= 51) {
duty_percent = (amps * 10) / 6; // Duty (%) = Amps / 0.6
duty_percent = (amps * 10) / 6;
} else {
duty_percent = (amps * 10) / 25 + 64; // Duty (%) = (Amps / 2.5) + 64
duty_percent = (amps * 10) / 25 + 64;
}
if (duty_percent > 100) duty_percent = 100;
uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100;
@@ -101,40 +93,18 @@ void pilot_set_amps(uint16_t amps)
last_pwm_duty = duty;
ESP_LOGI(TAG, "Pilot set: %d A → %d/%d (≈ %d%% duty)",
amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);
amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);
ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty);
ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL);
}
bool pilot_get_state(void) {
// true se estamos em 12V fixo; false se PWM ou -12V
// Se quiser diferenciar PWM, guarde um flag quando chamar set_amps.
return (last_pilot_level == 1) && (last_pwm_duty == 0);
}
static int compare_int(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}
static int select_low_median_qsort(int *src, int n, int percent) {
int k = (n * percent) / 100;
if (k == 0) k = 1;
int *copy = alloca(n * sizeof(int));
memcpy(copy, src, n * sizeof(int));
qsort(copy, n, sizeof(int), compare_int);
return copy[k / 2];
}
static int select_high_median_qsort(int *src, int n, int percent) {
int k = (n * percent) / 100;
if (k == 0) k = 1;
int *copy = alloca(n * sizeof(int));
memcpy(copy, src, n * sizeof(int));
qsort(copy, n, sizeof(int), compare_int);
return copy[n - k + (k / 2)];
return (*(const int *)a - *(const int *)b);
}
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
@@ -145,7 +115,6 @@ void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
int collected = 0, attempts = 0;
uint16_t adc_sample = 0;
// Lê samples usando ADC121S021 externo
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) {
adc_sample = 0;
if (adc121s021_dma_get_sample(&adc_sample)) {
@@ -164,13 +133,21 @@ void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
return;
}
int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);
int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);
qsort(samples, collected, sizeof(int), compare_int);
int k = (collected * PILOT_EXTREME_PERCENT) / 100;
if (k == 0) k = 1;
int low_index = k / 2;
int high_index = collected - k + (k / 2);
if (high_index >= collected) high_index = collected - 1;
int low_raw = samples[low_index];
int high_raw = samples[high_index];
int high_mv = adc_raw_to_mv(high_raw);
int low_mv = adc_raw_to_mv(low_raw);
// Aplica thresholds definidos em board_config (em mV)
if (high_mv >= board_config.pilot_down_threshold_12)
*up_voltage = PILOT_VOLTAGE_12;
else if (high_mv >= board_config.pilot_down_threshold_9)

View File

@@ -1,82 +1,164 @@
/*
* evse_session.c
* Implementation of evse_session module using instantaneous power accumulation
*/
#include <inttypes.h> // for PRIu32
#include <inttypes.h>
#include "evse_session.h"
#include "evse_meter.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "evse_events.h"
#include "esp_event.h"
#include "evse_limits.h"
static const char *TAG = "evse_session";
// Internal state
static TickType_t session_start_tick = 0;
static uint32_t watt_seconds = 0; // accumulator of W·s
static uint32_t watt_seconds = 0;
static evse_session_t last_session;
static bool last_session_valid = false;
static bool last_session_valid = false;
static uint32_t session_counter = 0;
void evse_session_init(void) {
session_start_tick = 0;
watt_seconds = 0;
last_session_valid = false;
static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
void evse_session_init(void)
{
portENTER_CRITICAL(&session_mux);
session_start_tick = 0;
watt_seconds = 0;
last_session_valid = false;
session_counter = 0;
portEXIT_CRITICAL(&session_mux);
}
void evse_session_start(void) {
session_start_tick = xTaskGetTickCount();
watt_seconds = 0;
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)session_start_tick);
void evse_session_start(void)
{
TickType_t tick = xTaskGetTickCount();
portENTER_CRITICAL(&session_mux);
session_start_tick = tick;
watt_seconds = 0;
session_counter++;
portEXIT_CRITICAL(&session_mux);
evse_set_limit_reached(false);
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)tick);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_STARTED,
.session_id = session_counter,
.duration_s = 0,
.energy_wh = 0,
.avg_power_w = 0,
.is_current = true,
};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_SESSION,
&evt,
sizeof(evt),
portMAX_DELAY);
}
void evse_session_end(void) {
void evse_session_end(void)
{
TickType_t start_tick;
uint32_t ws;
uint32_t id;
portENTER_CRITICAL(&session_mux);
if (session_start_tick == 0) {
portEXIT_CRITICAL(&session_mux);
ESP_LOGW(TAG, "evse_session_end called without active session");
return;
}
TickType_t now = xTaskGetTickCount();
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ;
uint32_t energy_wh = watt_seconds / 3600U;
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0;
last_session.start_tick = session_start_tick;
last_session.duration_s = duration_s;
last_session.energy_wh = energy_wh;
last_session.avg_power_w = avg_power;
last_session.is_current = false;
last_session_valid = true;
start_tick = session_start_tick;
ws = watt_seconds;
id = session_counter;
session_start_tick = 0;
ESP_LOGI(TAG, "Session ended: duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg_power=%" PRIu32 " W",
(uint32_t)duration_s, (uint32_t)energy_wh, (uint32_t)avg_power);
portEXIT_CRITICAL(&session_mux);
TickType_t now = xTaskGetTickCount();
uint32_t duration_s = (now - start_tick) / configTICK_RATE_HZ;
uint32_t energy_wh = ws / 3600U;
uint32_t avg_power = duration_s > 0 ? ws / duration_s : 0;
portENTER_CRITICAL(&session_mux);
last_session.start_tick = start_tick;
last_session.duration_s = duration_s;
last_session.energy_wh = energy_wh;
last_session.avg_power_w = avg_power;
last_session.is_current = false;
last_session_valid = true;
portEXIT_CRITICAL(&session_mux);
ESP_LOGI(TAG,
"Session ended: duration=%" PRIu32 " s, energy=%" PRIu32
" Wh, avg_power=%" PRIu32 " W",
duration_s, energy_wh, avg_power);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_FINISHED,
.session_id = id,
.duration_s = duration_s,
.energy_wh = energy_wh,
.avg_power_w = avg_power,
.is_current = false,
};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_SESSION,
&evt,
sizeof(evt),
portMAX_DELAY);
}
void evse_session_tick(void) {
if (session_start_tick == 0) return;
// Should be called every second (or known interval)
void evse_session_tick(void)
{
uint32_t power_w = evse_meter_get_instant_power();
watt_seconds += power_w;
portENTER_CRITICAL(&session_mux);
if (session_start_tick != 0) {
watt_seconds += power_w;
}
portEXIT_CRITICAL(&session_mux);
}
bool evse_session_get(evse_session_t *out) {
if (out == NULL) return false;
bool evse_session_get(evse_session_t *out)
{
if (out == NULL)
return false;
if (session_start_tick != 0) {
TickType_t start;
uint32_t ws;
bool has_current;
evse_session_t last_copy;
bool last_valid;
portENTER_CRITICAL(&session_mux);
start = session_start_tick;
ws = watt_seconds;
has_current = (session_start_tick != 0);
last_copy = last_session;
last_valid = last_session_valid;
portEXIT_CRITICAL(&session_mux);
if (has_current)
{
TickType_t now = xTaskGetTickCount();
uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ;
uint32_t energy_wh = watt_seconds / 3600U;
uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0;
uint32_t duration_s = (now - start) / configTICK_RATE_HZ;
uint32_t energy_wh = ws / 3600U;
uint32_t avg_power = duration_s > 0 ? ws / duration_s : 0;
out->start_tick = session_start_tick;
out->duration_s = duration_s;
out->energy_wh = energy_wh;
out->avg_power_w = avg_power;
out->is_current = true;
out->start_tick = start;
out->duration_s = duration_s;
out->energy_wh = energy_wh;
out->avg_power_w = avg_power;
out->is_current = true;
return true;
}
if (last_session_valid) {
*out = last_session;
if (last_valid)
{
*out = last_copy;
return true;
}

View File

@@ -71,6 +71,24 @@ void evse_state_set_authorized(bool authorized);
*/
bool evse_state_get_authorized(void);
// ===============================
// Configuration / Availability
// ===============================
/**
* @brief Enable or disable the EVSE (software flag, persisted in NVS).
*/
void evse_set_enabled(bool value);
/**
* @brief Returns true if the EVSE is currently available for use.
*/
bool evse_is_available(void);
/**
* @brief Set EVSE availability flag (may be persisted in NVS).
*/
void evse_set_available(bool value);
// ===============================
// Limit Status
@@ -85,4 +103,4 @@ bool evse_is_limit_reached(void);
}
#endif
#endif // EVSE_API_H
#endif // EVSE_API_H

View File

@@ -1,3 +1,4 @@
// === Início de: components/evse/include/evse_error.h ===
#ifndef EVSE_ERROR_H
#define EVSE_ERROR_H
@@ -5,41 +6,46 @@
#include <stdbool.h>
#include "evse_pilot.h"
#define EVSE_ERR_AUTO_CLEAR_BITS ( \
EVSE_ERR_DIODE_SHORT_BIT | \
// Bits que auto-limpam passado um timeout
#define EVSE_ERR_AUTO_CLEAR_BITS ( \
EVSE_ERR_DIODE_SHORT_BIT | \
EVSE_ERR_TEMPERATURE_HIGH_BIT | \
EVSE_ERR_RCM_TRIGGERED_BIT )
EVSE_ERR_RCM_TRIGGERED_BIT)
// Error bits
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
#define EVSE_ERR_DIODE_SHORT_BIT (1 << 0)
#define EVSE_ERR_LOCK_FAULT_BIT (1 << 1)
#define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2)
#define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3)
#define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4)
#define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5)
#define EVSE_ERR_PILOT_FAULT_BIT (1 << 6)
#define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7)
// Inicialização do módulo de erros
void evse_error_init(void);
// Verificações e monitoramento
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v);
void evse_temperature_check(void);
void evse_error_tick(void);
// Leitura e controle de erros
uint32_t evse_get_error(void);
bool evse_is_error_cleared(void);
void evse_mark_error_cleared(void);
void evse_error_set(uint32_t bitmask);
void evse_error_clear(uint32_t bitmask);
bool evse_error_is_active(void);
uint32_t evse_error_get_bits(void);
void evse_error_reset_flag(void);
// ----------------------------------------------------
// Semântica sticky: flag "todos erros limpos"
// ----------------------------------------------------
// Fica true quando TODOS os erros são limpos.
// Volta a false assim que qualquer erro novo aparece.
// Permanece true até o consumidor limpar explicitamente.
bool evse_error_cleared_flag(void);
void evse_error_reset_flag(void);
#endif // EVSE_ERROR_H
// === Fim de: components/evse/include/evse_error.h ===

View File

@@ -2,6 +2,9 @@
#define EVSE_EVENTS_H
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "esp_event.h"
ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
@@ -12,8 +15,12 @@ typedef enum {
EVSE_EVENT_CONFIG_UPDATED,
EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED,
EVSE_EVENT_SESSION,
} evse_event_id_t;
// -----------------
// Eventos de STATE
// -----------------
typedef enum {
EVSE_STATE_EVENT_IDLE,
EVSE_STATE_EVENT_WAITING,
@@ -25,11 +32,35 @@ typedef struct {
evse_state_event_t state;
} evse_state_event_data_t;
// -----------------
// Eventos de SESSÃO
// -----------------
typedef enum {
EVSE_SESSION_EVENT_STARTED = 0,
EVSE_SESSION_EVENT_FINISHED,
} evse_session_event_type_t;
typedef struct {
bool charging; // Estado de carregamento
float hw_max_current; // Corrente máxima suportada pelo hardware
float runtime_current; // Corrente de carregamento em uso
int64_t timestamp_us; // Momento da atualização
evse_session_event_type_t type; ///< STARTED / FINISHED
// campos básicos da sessão, em tipos simples:
uint32_t session_id; ///< opcional, se tiveres um ID
uint32_t duration_s; ///< duração em segundos (0 no STARTED)
uint32_t energy_wh; ///< energia em Wh (0 no STARTED)
uint32_t avg_power_w; ///< potência média em W (0 no STARTED)
bool is_current; ///< true se ainda estiver em curso
} evse_session_event_data_t;
// -----------------
// Eventos de CONFIG
// -----------------
typedef struct {
bool charging; // Estado de carregamento
float hw_max_current; // Corrente máxima suportada pelo hardware
float runtime_current; // Corrente de carregamento em uso
int64_t timestamp_us; // Momento da atualização
} evse_config_event_data_t;
// Eventos simples e específicos

View File

@@ -1,3 +1,4 @@
// === Início de: components/evse/include/evse_limits.h ===
#ifndef EVSE_LIMITS_H
#define EVSE_LIMITS_H
@@ -15,7 +16,6 @@ extern "C" {
/**
* @brief Sets the internal 'limit reached' flag.
* Called internally when a limit condition is triggered.
*/
void evse_set_limit_reached(bool value);
@@ -24,6 +24,11 @@ void evse_set_limit_reached(bool value);
*/
bool evse_get_limit_reached(void);
/**
* @brief Convenience alias for evse_get_limit_reached().
*/
bool evse_is_limit_reached(void);
/**
* @brief Checks if any session limit has been exceeded (energy, time or power).
* Should be called periodically during charging.
@@ -48,30 +53,13 @@ void evse_set_charging_time_limit(uint32_t value);
/**
* @brief Get/set minimum acceptable power level (in Watts).
* If the power remains below this for a long time, the session may be interrupted.
*/
uint16_t evse_get_under_power_limit(void);
void evse_set_under_power_limit(uint16_t value);
// ============================
// Default (Persistent) Limits
// ============================
/**
* @brief Default values used after system boot or reset.
* These can be restored from NVS or fallback values.
*/
uint32_t evse_get_default_consumption_limit(void);
void evse_set_default_consumption_limit(uint32_t value);
uint32_t evse_get_default_charging_time_limit(void);
void evse_set_default_charging_time_limit(uint32_t value);
uint16_t evse_get_default_under_power_limit(void);
void evse_set_default_under_power_limit(uint16_t value);
#ifdef __cplusplus
}
#endif
#endif // EVSE_LIMITS_H
// === Fim de: components/evse/include/evse_limits.h ===