new module
This commit is contained in:
@@ -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
|
||||
)
|
||||
@@ -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()};
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
@@ -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}, "
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 ===
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
Reference in New Issue
Block a user