new upgrade
This commit is contained in:
@@ -17,6 +17,6 @@ set(srcs
|
||||
idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES nvs_flash driver
|
||||
REQUIRES peripherals auth loadbalancer scheduler
|
||||
PRIV_REQUIRES driver
|
||||
REQUIRES peripherals auth loadbalancer scheduler storage_service
|
||||
)
|
||||
@@ -1,215 +1,289 @@
|
||||
#include <inttypes.h> // For PRI macros
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#include "evse_config.h"
|
||||
#include "board_config.h"
|
||||
#include "evse_limits.h"
|
||||
#include "evse_api.h"
|
||||
#include "evse_state.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
#include "storage_service.h"
|
||||
|
||||
static const char *TAG = "evse_config";
|
||||
|
||||
static nvs_handle_t nvs;
|
||||
#define NVS_NAMESPACE "evse_config"
|
||||
|
||||
// ========================
|
||||
// Configurable parameters
|
||||
// 3 variáveis (semântica simples)
|
||||
// ========================
|
||||
static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
|
||||
static uint16_t charging_current; // Persisted (NVS)
|
||||
static uint16_t charging_current_runtime = 0; // Runtime only
|
||||
static bool socket_outlet;
|
||||
static bool rcm;
|
||||
|
||||
// 1) Hardware (FIXO)
|
||||
static const uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
|
||||
|
||||
// 2) Configurável (persistido)
|
||||
static uint16_t charging_current = MAX_CHARGING_CURRENT_LIMIT;
|
||||
|
||||
// 3) Runtime (RAM)
|
||||
static uint16_t charging_current_runtime = 0;
|
||||
|
||||
// Outros parâmetros (persistidos)
|
||||
static bool socket_outlet = false;
|
||||
static bool rcm = false;
|
||||
static uint8_t temp_threshold = 60;
|
||||
static bool require_auth;
|
||||
|
||||
// Availability / Enable flags
|
||||
// Availability / Enable flags (persistidos)
|
||||
static bool is_available = true;
|
||||
static bool is_enabled = true;
|
||||
|
||||
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
|
||||
|
||||
// Ajusta conforme o teu boot:
|
||||
// 1000ms pode ser curto com Wi-Fi/FS/tasks; 2000ms é mais robusto em produto.
|
||||
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(2000); }
|
||||
|
||||
// ========================
|
||||
// Initialization
|
||||
// ========================
|
||||
esp_err_t evse_config_init(void)
|
||||
{
|
||||
ESP_LOGD(TAG, "Initializing NVS configuration...");
|
||||
return nvs_open("evse", NVS_READWRITE, &nvs);
|
||||
// garante storage iniciado
|
||||
ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
|
||||
ESP_LOGI(TAG, "EVSE config init OK (storage-backed)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void evse_check_defaults(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
uint8_t u8;
|
||||
uint16_t u16;
|
||||
uint32_t u32;
|
||||
bool needs_commit = false;
|
||||
uint8_t u8_bool;
|
||||
uint8_t u8 = 0;
|
||||
uint16_t u16 = 0;
|
||||
|
||||
ESP_LOGD(TAG, "Checking default parameters...");
|
||||
// Timeouts: leitura e escrita no boot
|
||||
const TickType_t rd_to = BOOT_TO();
|
||||
const TickType_t wr_to = TO_TICKS_MS(2000);
|
||||
|
||||
// Max charging current
|
||||
err = nvs_get_u8(nvs, "max_chrg_curr", &u8);
|
||||
if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT)
|
||||
{
|
||||
max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
|
||||
nvs_set_u8(nvs, "max_chrg_curr", max_charging_current);
|
||||
needs_commit = true;
|
||||
ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current);
|
||||
}
|
||||
else
|
||||
{
|
||||
max_charging_current = u8;
|
||||
}
|
||||
ESP_LOGD(TAG, "Checking default parameters (sync persistence)...");
|
||||
|
||||
// -----------------------------------------
|
||||
// Charging current (default, persisted)
|
||||
err = nvs_get_u16(nvs, "def_chrg_curr", &u16);
|
||||
if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT) || u16 > (max_charging_current))
|
||||
// -----------------------------------------
|
||||
err = storage_get_u16_sync(NVS_NAMESPACE, "def_chrg_curr", &u16, rd_to);
|
||||
if (err != ESP_OK || u16 < MIN_CHARGING_CURRENT_LIMIT || u16 > max_charging_current)
|
||||
{
|
||||
charging_current = max_charging_current;
|
||||
nvs_set_u16(nvs, "def_chrg_curr", charging_current);
|
||||
needs_commit = true;
|
||||
ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current);
|
||||
|
||||
esp_err_t se = storage_set_u16_sync(NVS_NAMESPACE, "def_chrg_curr", charging_current, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist def_chrg_curr=%u: %s",
|
||||
(unsigned)charging_current, esp_err_to_name(se));
|
||||
// seguimos com RAM correta; persist pode falhar por flash/partição
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Invalid/missing def_chrg_curr (%s) -> reset to %u (sync persisted)",
|
||||
esp_err_to_name(err), (unsigned)charging_current);
|
||||
}
|
||||
else
|
||||
{
|
||||
charging_current = u16;
|
||||
}
|
||||
|
||||
// Runtime charging current inicializado a partir do default persistido
|
||||
// runtime inicializa a partir do default
|
||||
charging_current_runtime = charging_current;
|
||||
ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime);
|
||||
ESP_LOGD(TAG, "Runtime charging current initialized from default: %u",
|
||||
(unsigned)charging_current_runtime);
|
||||
|
||||
// Auth required
|
||||
err = nvs_get_u8(nvs, "require_auth", &u8);
|
||||
require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false;
|
||||
if (err != ESP_OK)
|
||||
// -----------------------------------------
|
||||
// Socket outlet (persisted) + capability gate
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "socket_outlet", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
nvs_set_u8(nvs, "require_auth", require_auth);
|
||||
needs_commit = true;
|
||||
}
|
||||
bool wanted = (u8 != 0);
|
||||
|
||||
// Socket outlet
|
||||
err = nvs_get_u8(nvs, "socket_outlet", &u8);
|
||||
socket_outlet = (err == ESP_OK && u8) && board_config.proximity;
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
nvs_set_u8(nvs, "socket_outlet", socket_outlet);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
// RCM
|
||||
err = nvs_get_u8(nvs, "rcm", &u8);
|
||||
rcm = (err == ESP_OK && u8) && board_config.rcm;
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
nvs_set_u8(nvs, "rcm", rcm);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
// Temp threshold
|
||||
err = nvs_get_u8(nvs, "temp_threshold", &u8);
|
||||
temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60;
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
nvs_set_u8(nvs, "temp_threshold", temp_threshold);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
// Optional limits
|
||||
if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK)
|
||||
evse_set_consumption_limit(u32);
|
||||
|
||||
if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK)
|
||||
evse_set_charging_time_limit(u32);
|
||||
|
||||
if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK)
|
||||
evse_set_under_power_limit(u16);
|
||||
|
||||
// Availability (persist)
|
||||
if (nvs_get_u8(nvs, "available", &u8_bool) == ESP_OK && u8_bool <= 1)
|
||||
{
|
||||
is_available = (u8_bool != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_available = true; // default
|
||||
nvs_set_u8(nvs, "available", (uint8_t)is_available);
|
||||
needs_commit = true;
|
||||
ESP_LOGW(TAG, "Missing 'available' -> default=true (persisted).");
|
||||
}
|
||||
|
||||
// Enabled (persist)
|
||||
if (nvs_get_u8(nvs, "enabled", &u8_bool) == ESP_OK && u8_bool <= 1)
|
||||
{
|
||||
is_enabled = (u8_bool != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_enabled = true; // default
|
||||
nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
|
||||
needs_commit = true;
|
||||
ESP_LOGW(TAG, "Missing 'enabled' -> default=true (persisted).");
|
||||
}
|
||||
|
||||
if (needs_commit)
|
||||
{
|
||||
err = nvs_commit(nvs);
|
||||
if (err == ESP_OK)
|
||||
if (wanted && !board_config.proximity)
|
||||
{
|
||||
ESP_LOGD(TAG, "Configuration committed to NVS.");
|
||||
// NVS dizia 1, mas HW não suporta -> runtime false e persistir 0
|
||||
socket_outlet = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist socket_outlet=0 (capability mismatch): %s",
|
||||
esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "socket_outlet requested but HW has no proximity -> forcing false (sync persisted)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err));
|
||||
socket_outlet = wanted;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_outlet = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "socket_outlet", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist socket_outlet default=0: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid socket_outlet (%s) -> default=false (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// RCM (persisted) + capability gate
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "rcm", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
bool wanted = (u8 != 0);
|
||||
|
||||
if (wanted && !board_config.rcm)
|
||||
{
|
||||
rcm = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist rcm=0 (capability mismatch): %s",
|
||||
esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "rcm requested but HW has no RCM -> forcing false (sync persisted)");
|
||||
}
|
||||
else
|
||||
{
|
||||
rcm = wanted;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rcm = false;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "rcm", 0, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist rcm default=0: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid rcm (%s) -> default=false (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Temp threshold (persisted)
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "temp_threshold", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 >= 40 && u8 <= 80)
|
||||
{
|
||||
temp_threshold = u8;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp_threshold = 60;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "temp_threshold", temp_threshold, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist temp_threshold=%u: %s",
|
||||
(unsigned)temp_threshold, esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Invalid/missing temp_threshold (%s) -> default=60 (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Availability (persisted) [0/1]
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "available", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
is_available = (u8 != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_available = true;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "available", 1, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist available=1: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid 'available' (%s) -> default=true (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Enabled (persisted) [0/1]
|
||||
// -----------------------------------------
|
||||
err = storage_get_u8_sync(NVS_NAMESPACE, "enabled", &u8, rd_to);
|
||||
if (err == ESP_OK && u8 <= 1)
|
||||
{
|
||||
is_enabled = (u8 != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_enabled = true;
|
||||
|
||||
esp_err_t se = storage_set_u8_sync(NVS_NAMESPACE, "enabled", 1, wr_to);
|
||||
if (se != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist enabled=1: %s", esp_err_to_name(se));
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "Missing/invalid 'enabled' (%s) -> default=true (sync persisted).",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Flush explícito no boot:
|
||||
// - ajuda a garantir commit determinístico antes do resto do sistema avançar
|
||||
// - mantém-se útil mesmo com setters sync se o teu storage ainda estiver com debounce interno
|
||||
esp_err_t fe = storage_flush_sync(wr_to);
|
||||
if (fe != ESP_OK)
|
||||
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Charging current getters/setters
|
||||
// ========================
|
||||
uint8_t evse_get_max_charging_current(void)
|
||||
{
|
||||
return max_charging_current;
|
||||
}
|
||||
uint8_t evse_get_max_charging_current(void) { return max_charging_current; }
|
||||
|
||||
esp_err_t evse_set_max_charging_current(uint8_t value)
|
||||
{
|
||||
if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
max_charging_current = value;
|
||||
evse_set_runtime_charging_current(value);
|
||||
nvs_set_u8(nvs, "max_chrg_curr", value);
|
||||
return nvs_commit(nvs);
|
||||
}
|
||||
|
||||
uint16_t evse_get_charging_current(void)
|
||||
{
|
||||
return charging_current;
|
||||
}
|
||||
uint16_t evse_get_charging_current(void) { return charging_current; }
|
||||
|
||||
esp_err_t evse_set_charging_current(uint16_t value)
|
||||
{
|
||||
if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current))
|
||||
if (value < MIN_CHARGING_CURRENT_LIMIT || value > max_charging_current)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
|
||||
if (value == charging_current)
|
||||
{
|
||||
evse_set_runtime_charging_current(value);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
charging_current = value;
|
||||
nvs_set_u16(nvs, "def_chrg_curr", value);
|
||||
return nvs_commit(nvs);
|
||||
}
|
||||
|
||||
uint16_t evse_get_default_charging_current(void)
|
||||
{
|
||||
uint16_t value;
|
||||
if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK)
|
||||
return value;
|
||||
return charging_current;
|
||||
}
|
||||
esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_chrg_curr", value);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
// Em runtime, isto pode falhar por fila cheia. RAM fica correta; persistência é best-effort.
|
||||
ESP_LOGE(TAG, "Failed to persist def_chrg_curr async=%u: %s", (unsigned)value, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t evse_set_default_charging_current(uint16_t value)
|
||||
{
|
||||
if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current))
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
nvs_set_u16(nvs, "def_chrg_curr", value);
|
||||
return nvs_commit(nvs);
|
||||
evse_set_runtime_charging_current(value);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ========================
|
||||
@@ -218,135 +292,119 @@ 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;
|
||||
}
|
||||
else if (value < MIN_CHARGING_CURRENT_LIMIT)
|
||||
{
|
||||
value = MIN_CHARGING_CURRENT_LIMIT;
|
||||
}
|
||||
|
||||
charging_current_runtime = value;
|
||||
|
||||
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
|
||||
|
||||
evse_config_event_data_t evt = {
|
||||
.charging = evse_state_is_charging(evse_get_state()),
|
||||
.hw_max_current = (float)evse_get_max_charging_current(),
|
||||
.runtime_current = (float)evse_get_runtime_charging_current(),
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
esp_event_post(EVSE_EVENTS,
|
||||
EVSE_EVENT_CONFIG_UPDATED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
}
|
||||
|
||||
uint16_t evse_get_runtime_charging_current(void)
|
||||
{
|
||||
return charging_current_runtime;
|
||||
}
|
||||
uint16_t evse_get_runtime_charging_current(void) { return charging_current_runtime; }
|
||||
|
||||
// ========================
|
||||
// Socket outlet
|
||||
// ========================
|
||||
bool evse_get_socket_outlet(void)
|
||||
{
|
||||
return socket_outlet;
|
||||
}
|
||||
bool evse_get_socket_outlet(void) { return socket_outlet; }
|
||||
|
||||
esp_err_t evse_set_socket_outlet(bool value)
|
||||
{
|
||||
if (value && !board_config.proximity)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
if (value == socket_outlet)
|
||||
return ESP_OK;
|
||||
|
||||
socket_outlet = value;
|
||||
nvs_set_u8(nvs, "socket_outlet", value);
|
||||
return nvs_commit(nvs);
|
||||
|
||||
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "socket_outlet", (uint8_t)value);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist socket_outlet async=%u: %s", (unsigned)value, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// RCM
|
||||
// ========================
|
||||
bool evse_is_rcm(void)
|
||||
{
|
||||
return rcm;
|
||||
}
|
||||
bool evse_is_rcm(void) { return rcm; }
|
||||
|
||||
esp_err_t evse_set_rcm(bool value)
|
||||
{
|
||||
if (value && !board_config.rcm)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
if (value == rcm)
|
||||
return ESP_OK;
|
||||
|
||||
rcm = value;
|
||||
nvs_set_u8(nvs, "rcm", value);
|
||||
return nvs_commit(nvs);
|
||||
|
||||
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "rcm", (uint8_t)value);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist rcm async=%u: %s", (unsigned)value, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Temperature
|
||||
// ========================
|
||||
uint8_t evse_get_temp_threshold(void)
|
||||
{
|
||||
return temp_threshold;
|
||||
}
|
||||
uint8_t evse_get_temp_threshold(void) { return temp_threshold; }
|
||||
|
||||
esp_err_t evse_set_temp_threshold(uint8_t value)
|
||||
{
|
||||
if (value < 40 || value > 80)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
if (value == temp_threshold)
|
||||
return ESP_OK;
|
||||
|
||||
temp_threshold = value;
|
||||
nvs_set_u8(nvs, "temp_threshold", value);
|
||||
return nvs_commit(nvs);
|
||||
|
||||
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "temp_threshold", value);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist temp_threshold async=%u: %s", (unsigned)value, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Availability
|
||||
// ========================
|
||||
bool evse_config_is_available(void)
|
||||
{
|
||||
return is_available;
|
||||
}
|
||||
bool evse_config_is_available(void) { return is_available; }
|
||||
|
||||
void evse_config_set_available(bool available)
|
||||
{
|
||||
is_available = available ? true : false;
|
||||
bool newv = available;
|
||||
if (newv == is_available)
|
||||
return;
|
||||
|
||||
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available);
|
||||
if (err == ESP_OK)
|
||||
err = nvs_commit(nvs);
|
||||
is_available = newv;
|
||||
|
||||
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "available", (uint8_t)is_available);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist 'available': %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
evse_available_event_data_t e = {
|
||||
.available = is_available,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
esp_event_post(EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, &e, sizeof(e), portMAX_DELAY);
|
||||
ESP_LOGE(TAG, "Failed to persist 'available' async=%u: %s", (unsigned)is_available, esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Enable/Disable
|
||||
// ========================
|
||||
bool evse_config_is_enabled(void)
|
||||
{
|
||||
return is_enabled;
|
||||
}
|
||||
bool evse_config_is_enabled(void) { return is_enabled; }
|
||||
|
||||
void evse_config_set_enabled(bool enabled)
|
||||
{
|
||||
is_enabled = enabled ? true : false;
|
||||
bool newv = enabled;
|
||||
if (newv == is_enabled)
|
||||
return;
|
||||
|
||||
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
|
||||
if (err == ESP_OK)
|
||||
err = nvs_commit(nvs);
|
||||
is_enabled = newv;
|
||||
|
||||
esp_err_t err = storage_set_u8_async(NVS_NAMESPACE, "enabled", (uint8_t)is_enabled);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to persist 'enabled': %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
evse_enable_event_data_t e = {
|
||||
.enabled = is_enabled,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
esp_event_post(EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, &e, sizeof(e), portMAX_DELAY);
|
||||
ESP_LOGE(TAG, "Failed to persist 'enabled' async=%u: %s", (unsigned)is_enabled, esp_err_to_name(err));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// components/evse/evse_core.c
|
||||
#include "evse_fsm.h"
|
||||
#include "evse_error.h"
|
||||
#include "evse_limits.h"
|
||||
@@ -14,6 +15,36 @@ static const char *TAG = "evse_core";
|
||||
static SemaphoreHandle_t mutex;
|
||||
static evse_state_t last_state = EVSE_STATE_A;
|
||||
|
||||
// Filtro simples de histerese no pilot
|
||||
#define PILOT_STABLE_SAMPLES 2
|
||||
|
||||
static pilot_voltage_t s_last_raw = PILOT_VOLTAGE_12;
|
||||
static pilot_voltage_t s_filtered = PILOT_VOLTAGE_12;
|
||||
static int s_stable_count = 0;
|
||||
|
||||
static pilot_voltage_t filter_pilot_voltage(pilot_voltage_t raw)
|
||||
{
|
||||
if (raw == s_last_raw)
|
||||
{
|
||||
if (s_stable_count < PILOT_STABLE_SAMPLES)
|
||||
{
|
||||
s_stable_count++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_last_raw = raw;
|
||||
s_stable_count = 1;
|
||||
}
|
||||
|
||||
if (s_stable_count >= PILOT_STABLE_SAMPLES && raw != s_filtered)
|
||||
{
|
||||
s_filtered = raw;
|
||||
}
|
||||
|
||||
return s_filtered;
|
||||
}
|
||||
|
||||
static void evse_process(void);
|
||||
static void evse_core_task(void *arg);
|
||||
|
||||
@@ -32,7 +63,8 @@ void evse_init(void)
|
||||
evse_fsm_reset();
|
||||
pilot_set_level(true);
|
||||
|
||||
xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL);
|
||||
BaseType_t rc = xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 6, NULL);
|
||||
configASSERT(rc == pdPASS);
|
||||
}
|
||||
|
||||
static void evse_process(void)
|
||||
@@ -44,19 +76,27 @@ static void evse_process(void)
|
||||
|
||||
xSemaphoreTake(mutex, portMAX_DELAY);
|
||||
|
||||
pilot_voltage_t pilot_voltage;
|
||||
pilot_voltage_t pilot_raw;
|
||||
bool is_n12v = false;
|
||||
|
||||
pilot_measure(&pilot_voltage, &is_n12v);
|
||||
ESP_LOGD(TAG, "Pilot: %d, -12V: %s", pilot_voltage, is_n12v ? "yes" : "no");
|
||||
pilot_measure(&pilot_raw, &is_n12v);
|
||||
pilot_voltage_t pilot_voltage = filter_pilot_voltage(pilot_raw);
|
||||
|
||||
ESP_LOGD(TAG, "Pilot(raw=%d, filt=%d), -12V: %s",
|
||||
pilot_raw, pilot_voltage, is_n12v ? "yes" : "no");
|
||||
|
||||
// raw set/clear; erro visível mantém holdoff interno (60s após sumir)
|
||||
evse_error_check(pilot_voltage, is_n12v);
|
||||
|
||||
// ✅ Sem cooldown externo: disponibilidade depende só do erro "visível"
|
||||
bool available = evse_config_is_available() && (evse_get_error() == 0);
|
||||
bool enabled = evse_config_is_enabled();
|
||||
|
||||
evse_fsm_process(
|
||||
pilot_voltage,
|
||||
evse_state_get_authorized(),
|
||||
evse_config_is_available(),
|
||||
evse_config_is_enabled());
|
||||
available,
|
||||
enabled);
|
||||
|
||||
evse_limits_check();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "evse_error.h"
|
||||
#include "evse_config.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -7,151 +6,147 @@
|
||||
#include "esp_log.h"
|
||||
#include "ntc_sensor.h"
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_timer.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_config.h"
|
||||
|
||||
static const char *TAG = "evse_error";
|
||||
|
||||
// Estado global de erros
|
||||
static uint32_t error_bits = 0;
|
||||
static TickType_t auto_clear_timeout = 0;
|
||||
// ----------------------------------------------------
|
||||
// Estado interno
|
||||
// ----------------------------------------------------
|
||||
// raw_bits = erros “instantâneos” conforme checks (set/clear)
|
||||
// visible_bits = erros expostos ao resto do sistema (com holdoff)
|
||||
// clear_deadline = quando pode finalmente limpar visible_bits para 0
|
||||
static uint32_t raw_bits = 0;
|
||||
static uint32_t visible_bits = 0;
|
||||
static TickType_t clear_deadline = 0;
|
||||
|
||||
// Sticky flag: "todos erros foram limpos"
|
||||
// Sticky flag: "todos erros visíveis foram limpos"
|
||||
static bool error_cleared = false;
|
||||
|
||||
// Proteção contra concorrência
|
||||
static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void evse_error_init(void)
|
||||
// ----------------------------------------------------
|
||||
// Helper: publicar evento de alteração de erro (visible_bits)
|
||||
// ----------------------------------------------------
|
||||
static void evse_error_post_event(uint32_t new_bits, uint32_t changed_mask)
|
||||
{
|
||||
evse_error_event_data_t ev = {
|
||||
.error_bits = new_bits,
|
||||
.changed_mask = changed_mask,
|
||||
.timestamp_us = esp_timer_get_time(),
|
||||
};
|
||||
|
||||
esp_err_t err = esp_event_post(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_ERROR_CHANGED,
|
||||
&ev,
|
||||
sizeof(ev),
|
||||
portMAX_DELAY);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s",
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Helpers internos
|
||||
// ----------------------------------------------------
|
||||
static bool raw_has_bit(uint32_t bit)
|
||||
{
|
||||
bool v;
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
error_bits = 0;
|
||||
auto_clear_timeout = 0;
|
||||
error_cleared = false;
|
||||
v = ((raw_bits & bit) != 0);
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
return v;
|
||||
}
|
||||
|
||||
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
|
||||
static void reconcile_visible_locked(TickType_t now)
|
||||
{
|
||||
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
|
||||
pilot_voltage, is_n12v ? "true" : "false");
|
||||
|
||||
// 1) Falha elétrica geral no pilot
|
||||
if (pilot_voltage == PILOT_VOLTAGE_1)
|
||||
// Se existem erros reais, o visível segue imediatamente
|
||||
if (raw_bits != 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
uint8_t threshold = evse_get_temp_threshold();
|
||||
|
||||
ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
|
||||
temp_c, threshold);
|
||||
|
||||
// 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");
|
||||
}
|
||||
visible_bits = raw_bits;
|
||||
clear_deadline = 0;
|
||||
error_cleared = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Leitura válida -> limpa erro de sensor
|
||||
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);
|
||||
|
||||
// Temperatura máxima
|
||||
if (temp_c >= threshold)
|
||||
// raw_bits == 0
|
||||
if (visible_bits == 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (first_time)
|
||||
{
|
||||
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
|
||||
temp_c, threshold);
|
||||
}
|
||||
clear_deadline = 0;
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
// Ainda há erro visível (holdoff). Arma deadline 1x.
|
||||
if (clear_deadline == 0)
|
||||
{
|
||||
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
|
||||
clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Expirou -> limpar finalmente
|
||||
if ((int32_t)(now - clear_deadline) >= 0)
|
||||
{
|
||||
visible_bits = 0;
|
||||
clear_deadline = 0;
|
||||
error_cleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------
|
||||
// API pública
|
||||
// ----------------------------------------------------
|
||||
void evse_error_init(void)
|
||||
{
|
||||
uint32_t old_vis, new_vis, changed;
|
||||
bool post = false;
|
||||
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
|
||||
old_vis = visible_bits;
|
||||
|
||||
raw_bits = 0;
|
||||
visible_bits = 0;
|
||||
clear_deadline = 0;
|
||||
error_cleared = false;
|
||||
|
||||
new_vis = visible_bits;
|
||||
changed = old_vis ^ new_vis;
|
||||
post = (changed != 0);
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (post)
|
||||
{
|
||||
evse_error_post_event(new_vis, changed);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t evse_get_error(void)
|
||||
{
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
uint32_t val = error_bits;
|
||||
uint32_t val = visible_bits;
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
return val;
|
||||
}
|
||||
|
||||
bool evse_error_is_active(void)
|
||||
{
|
||||
return evse_get_error() != 0;
|
||||
}
|
||||
|
||||
uint32_t evse_error_get_bits(void)
|
||||
{
|
||||
return evse_get_error();
|
||||
}
|
||||
|
||||
bool evse_error_cleared_flag(void)
|
||||
{
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
@@ -169,61 +164,147 @@ void evse_error_reset_flag(void)
|
||||
|
||||
void evse_error_set(uint32_t bitmask)
|
||||
{
|
||||
uint32_t old_vis, new_vis, changed;
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
|
||||
error_cleared = false;
|
||||
error_bits |= bitmask;
|
||||
old_vis = visible_bits;
|
||||
|
||||
if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS)
|
||||
{
|
||||
auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s
|
||||
}
|
||||
raw_bits |= bitmask;
|
||||
// se aparece qualquer erro, o "cleared" deixa de ser verdade
|
||||
error_cleared = false;
|
||||
|
||||
reconcile_visible_locked(now);
|
||||
|
||||
new_vis = visible_bits;
|
||||
changed = old_vis ^ new_vis;
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (changed != 0)
|
||||
{
|
||||
evse_error_post_event(new_vis, changed);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_error_clear(uint32_t bitmask)
|
||||
{
|
||||
uint32_t old_vis, new_vis, changed;
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
|
||||
portENTER_CRITICAL(&error_mux);
|
||||
|
||||
bool had_error = (error_bits != 0);
|
||||
error_bits &= ~bitmask;
|
||||
old_vis = visible_bits;
|
||||
|
||||
if (had_error && error_bits == 0)
|
||||
{
|
||||
error_cleared = true;
|
||||
}
|
||||
raw_bits &= ~bitmask;
|
||||
|
||||
// ✅ Aqui é onde o “60s depois do erro desaparecer” é armado:
|
||||
// quando raw_bits chega a 0, reconcile arma clear_deadline (uma vez)
|
||||
reconcile_visible_locked(now);
|
||||
|
||||
new_vis = visible_bits;
|
||||
changed = old_vis ^ new_vis;
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (changed != 0)
|
||||
{
|
||||
evse_error_post_event(new_vis, changed);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_error_tick(void)
|
||||
{
|
||||
uint32_t old_vis, new_vis, changed;
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
|
||||
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;
|
||||
}
|
||||
old_vis = visible_bits;
|
||||
reconcile_visible_locked(now);
|
||||
new_vis = visible_bits;
|
||||
changed = old_vis ^ new_vis;
|
||||
|
||||
portEXIT_CRITICAL(&error_mux);
|
||||
|
||||
if (changed != 0)
|
||||
{
|
||||
evse_error_post_event(new_vis, changed);
|
||||
}
|
||||
}
|
||||
|
||||
bool evse_error_is_active(void)
|
||||
// ----------------------------------------------------
|
||||
// Checks (raw -> set/clear)
|
||||
// ----------------------------------------------------
|
||||
void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v)
|
||||
{
|
||||
return evse_get_error() != 0;
|
||||
ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s",
|
||||
pilot_voltage, is_n12v ? "true" : "false");
|
||||
|
||||
// 1) Falha elétrica geral no pilot
|
||||
if (pilot_voltage == PILOT_VOLTAGE_1)
|
||||
{
|
||||
if (!raw_has_bit(EVSE_ERR_PILOT_FAULT_BIT))
|
||||
{
|
||||
ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)");
|
||||
}
|
||||
evse_error_set(EVSE_ERR_PILOT_FAULT_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT);
|
||||
}
|
||||
|
||||
// 2) Falta de -12V durante PWM (C ou D)
|
||||
if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v)
|
||||
{
|
||||
if (!raw_has_bit(EVSE_ERR_DIODE_SHORT_BIT))
|
||||
{
|
||||
ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)");
|
||||
}
|
||||
evse_error_set(EVSE_ERR_DIODE_SHORT_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t evse_error_get_bits(void)
|
||||
void evse_temperature_check(void)
|
||||
{
|
||||
return evse_get_error();
|
||||
float temp_c = ntc_temp_sensor();
|
||||
uint8_t threshold = evse_get_temp_threshold();
|
||||
|
||||
ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C",
|
||||
temp_c, threshold);
|
||||
|
||||
// Temperatura inválida -> erro de sensor
|
||||
if (temp_c < -40.0f || temp_c > 150.0f)
|
||||
{
|
||||
if (!raw_has_bit(EVSE_ERR_TEMPERATURE_FAULT_BIT))
|
||||
{
|
||||
ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado");
|
||||
}
|
||||
evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT);
|
||||
return;
|
||||
}
|
||||
|
||||
// Leitura válida -> limpa erro de sensor
|
||||
evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT);
|
||||
|
||||
// Temperatura máxima
|
||||
if (temp_c >= threshold)
|
||||
{
|
||||
if (!raw_has_bit(EVSE_ERR_TEMPERATURE_HIGH_BIT))
|
||||
{
|
||||
ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C",
|
||||
temp_c, threshold);
|
||||
}
|
||||
evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT);
|
||||
}
|
||||
else
|
||||
{
|
||||
evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// components/evse/evse_fsm.c
|
||||
#include "evse_fsm.h"
|
||||
#include "evse_api.h"
|
||||
#include "evse_pilot.h"
|
||||
@@ -17,16 +18,14 @@ static const char *TAG = "evse_fsm";
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static bool c1_d1_waiting = false;
|
||||
static TickType_t c1_d1_relay_to = 0;
|
||||
|
||||
void evse_fsm_reset(void)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
c1_d1_waiting = false;
|
||||
c1_d1_relay_to = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Atualiza saídas de hardware (pilot, relé, trava) em função do estado lógico.
|
||||
*/
|
||||
static void update_outputs(evse_state_t state)
|
||||
{
|
||||
const uint16_t current = evse_get_runtime_charging_current();
|
||||
@@ -38,7 +37,7 @@ static void update_outputs(evse_state_t state)
|
||||
cable_max_current = proximity_get_max_current();
|
||||
}
|
||||
|
||||
// Segurança: relé sempre off e outputs seguros em caso de erro
|
||||
// Segurança total: qualquer erro ativo força saída segura
|
||||
if (evse_get_error() != 0)
|
||||
{
|
||||
if (ac_relay_get_state())
|
||||
@@ -46,8 +45,14 @@ static void update_outputs(evse_state_t state)
|
||||
ac_relay_set_state(false);
|
||||
ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!");
|
||||
}
|
||||
ac_relay_set_state(false);
|
||||
else
|
||||
{
|
||||
ac_relay_set_state(false);
|
||||
}
|
||||
|
||||
// Em erro, garantir pilot OFF (não PWM / não +12V)
|
||||
pilot_set_level(true);
|
||||
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(false);
|
||||
@@ -55,14 +60,16 @@ static void update_outputs(evse_state_t state)
|
||||
return;
|
||||
}
|
||||
|
||||
// Fluxo normal
|
||||
switch (state)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
ac_relay_set_state(false);
|
||||
|
||||
// A → pilot alto (+12V), E/F → pilot OFF
|
||||
pilot_set_level(state == EVSE_STATE_A);
|
||||
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(false);
|
||||
@@ -72,66 +79,77 @@ static void update_outputs(evse_state_t state)
|
||||
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");
|
||||
}
|
||||
(void)rcm_test();
|
||||
break;
|
||||
|
||||
case EVSE_STATE_B2:
|
||||
pilot_set_amps(MIN(current, cable_max_current));
|
||||
ac_relay_set_state(false);
|
||||
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(true);
|
||||
}
|
||||
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);
|
||||
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case EVSE_STATE_C2:
|
||||
case EVSE_STATE_D2:
|
||||
pilot_set_amps(MIN(current, cable_max_current));
|
||||
ac_relay_set_state(true);
|
||||
|
||||
if (board_config.socket_lock && socket_outlet)
|
||||
{
|
||||
socket_lock_set_locked(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// FSM principal
|
||||
/**
|
||||
* @brief Máquina de estados principal do EVSE (IEC 61851).
|
||||
*/
|
||||
void evse_fsm_process(
|
||||
pilot_voltage_t pilot_voltage,
|
||||
bool authorized,
|
||||
bool available,
|
||||
bool enabled)
|
||||
{
|
||||
// Proteção total: erro força F sempre!
|
||||
if (evse_get_error() != 0)
|
||||
// 1) Erros globais: dominam qualquer outra lógica
|
||||
uint32_t err_bits = evse_get_error();
|
||||
if (err_bits != 0)
|
||||
{
|
||||
if (evse_get_state() != EVSE_STATE_F)
|
||||
evse_state_t forced_state =
|
||||
(err_bits & EVSE_ERR_PILOT_FAULT_BIT) ? EVSE_STATE_E : EVSE_STATE_F;
|
||||
|
||||
if (evse_get_state() != forced_state)
|
||||
{
|
||||
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)");
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
ESP_LOGW(TAG, "Erro ativo detectado: forçando estado %s",
|
||||
evse_state_to_str(forced_state));
|
||||
evse_set_state(forced_state);
|
||||
}
|
||||
update_outputs(EVSE_STATE_F);
|
||||
|
||||
update_outputs(forced_state);
|
||||
return;
|
||||
}
|
||||
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
evse_state_t prev = evse_get_state();
|
||||
evse_state_t curr = prev;
|
||||
evse_state_t curr = evse_get_state();
|
||||
|
||||
switch (curr)
|
||||
{
|
||||
@@ -153,17 +171,25 @@ void evse_fsm_process(
|
||||
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;
|
||||
|
||||
case PILOT_VOLTAGE_3:
|
||||
evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -171,52 +197,59 @@ void evse_fsm_process(
|
||||
|
||||
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)
|
||||
if (!available)
|
||||
{
|
||||
evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1)
|
||||
? EVSE_STATE_D1
|
||||
: EVSE_STATE_C1);
|
||||
evse_set_state(EVSE_STATE_F);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
if (curr == EVSE_STATE_C2)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_C1);
|
||||
}
|
||||
else if (curr == EVSE_STATE_D2)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_D1);
|
||||
}
|
||||
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;
|
||||
}
|
||||
break;
|
||||
|
||||
case EVSE_STATE_E:
|
||||
// Estado elétrico grave: só reset manual
|
||||
// ✅ Agora recupera como F: se disponível e sem erro -> volta a A
|
||||
if (available && evse_get_error() == 0)
|
||||
{
|
||||
evse_set_state(EVSE_STATE_A);
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -1,34 +1,123 @@
|
||||
#include <inttypes.h> // for PRIu32
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "evse_state.h"
|
||||
#include "evse_api.h"
|
||||
#include "evse_limits.h"
|
||||
#include "evse_meter.h"
|
||||
#include "evse_session.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_check.h"
|
||||
|
||||
// ========================
|
||||
// Concurrency protection
|
||||
// ========================
|
||||
#include "storage_service.h"
|
||||
|
||||
#define NVS_NAMESPACE "evse_limits"
|
||||
static const char *TAG = "evse_limits";
|
||||
|
||||
static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
// ========================
|
||||
// Runtime state (volatile)
|
||||
// ========================
|
||||
static bool limit_reached = false;
|
||||
static uint32_t consumption_limit = 0; // Wh
|
||||
static uint32_t charging_time_limit = 0; // seconds
|
||||
static uint16_t under_power_limit = 0; // W
|
||||
|
||||
static 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
|
||||
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
|
||||
static inline TickType_t BOOT_TO(void) { return TO_TICKS_MS(1000); }
|
||||
|
||||
// ========================
|
||||
// Limit status flag
|
||||
// ========================
|
||||
// ---------------------------------
|
||||
// Init + defaults
|
||||
// ---------------------------------
|
||||
esp_err_t evse_limits_init(void)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(storage_service_init(), TAG, "storage init failed");
|
||||
ESP_LOGI(TAG, "EVSE limits init OK (storage-backed)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void evse_limits_check_defaults(void)
|
||||
{
|
||||
esp_err_t err;
|
||||
bool needs_flush = false;
|
||||
|
||||
uint32_t u32 = 0;
|
||||
uint16_t u16 = 0;
|
||||
|
||||
ESP_LOGD(TAG, "Checking default limits...");
|
||||
|
||||
// Consumption limit (Wh) default = 0 (disabled)
|
||||
err = storage_get_u32_sync(NVS_NAMESPACE, "def_cons_lim", &u32, BOOT_TO());
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
consumption_limit = u32;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
}
|
||||
else
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
consumption_limit = 0;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
(void)storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", 0);
|
||||
needs_flush = true;
|
||||
ESP_LOGW(TAG, "Missing def_cons_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Charging time limit (s) default = 0 (disabled)
|
||||
err = storage_get_u32_sync(NVS_NAMESPACE, "def_ch_time_lim", &u32, BOOT_TO());
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
charging_time_limit = u32;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
}
|
||||
else
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
charging_time_limit = 0;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
(void)storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", 0);
|
||||
needs_flush = true;
|
||||
ESP_LOGW(TAG, "Missing def_ch_time_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
// Under-power limit (W) default = 0 (disabled)
|
||||
err = storage_get_u16_sync(NVS_NAMESPACE, "def_un_pwr_lim", &u16, BOOT_TO());
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
under_power_limit = u16;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
}
|
||||
else
|
||||
{
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
under_power_limit = 0;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
(void)storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", 0);
|
||||
needs_flush = true;
|
||||
ESP_LOGW(TAG, "Missing def_un_pwr_lim (%s) -> default=0 (persisted).", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
if (needs_flush)
|
||||
{
|
||||
esp_err_t fe = storage_flush_sync(TO_TICKS_MS(2000));
|
||||
if (fe != ESP_OK)
|
||||
ESP_LOGE(TAG, "storage_flush_sync failed: %s", esp_err_to_name(fe));
|
||||
else
|
||||
ESP_LOGD(TAG, "Defaults committed (flush).");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Limit reached flag
|
||||
// ---------------------------------
|
||||
bool evse_get_limit_reached(void)
|
||||
{
|
||||
bool val;
|
||||
@@ -50,10 +139,9 @@ bool evse_is_limit_reached(void)
|
||||
return evse_get_limit_reached();
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Runtime limit accessors
|
||||
// ========================
|
||||
|
||||
// ---------------------------------
|
||||
// Consumption limit
|
||||
// ---------------------------------
|
||||
uint32_t evse_get_consumption_limit(void)
|
||||
{
|
||||
uint32_t val;
|
||||
@@ -78,30 +166,18 @@ void evse_set_consumption_limit(uint32_t value)
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h);
|
||||
if (err == ESP_OK)
|
||||
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_cons_lim", value);
|
||||
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));
|
||||
ESP_LOGE(TAG,
|
||||
"Failed to persist consumption limit (%" PRIu32 " Wh): %s",
|
||||
value, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Charging time limit
|
||||
// ---------------------------------
|
||||
uint32_t evse_get_charging_time_limit(void)
|
||||
{
|
||||
uint32_t val;
|
||||
@@ -126,30 +202,18 @@ void evse_set_charging_time_limit(uint32_t value)
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h);
|
||||
if (err == ESP_OK)
|
||||
esp_err_t err = storage_set_u32_async(NVS_NAMESPACE, "def_ch_time_lim", value);
|
||||
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));
|
||||
ESP_LOGE(TAG,
|
||||
"Failed to persist charging time limit (%" PRIu32 " s): %s",
|
||||
value, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
// Under-power limit
|
||||
// ---------------------------------
|
||||
uint16_t evse_get_under_power_limit(void)
|
||||
{
|
||||
uint16_t val;
|
||||
@@ -174,82 +238,64 @@ void evse_set_under_power_limit(uint16_t value)
|
||||
if (!changed)
|
||||
return;
|
||||
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open("evse", NVS_READWRITE, &h);
|
||||
if (err == ESP_OK)
|
||||
esp_err_t err = storage_set_u16_async(NVS_NAMESPACE, "def_un_pwr_lim", value);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
err = nvs_set_u16(h, "def_un_pwr_lim", value);
|
||||
if (err == ESP_OK)
|
||||
err = nvs_commit(h);
|
||||
nvs_close(h);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE("EVSE_LIMITS",
|
||||
"Failed to persist under-power limit (%" PRIu32 " W): %s",
|
||||
(uint32_t)value, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE("EVSE_LIMITS",
|
||||
"Failed to open NVS for under-power limit: %s",
|
||||
esp_err_to_name(err));
|
||||
ESP_LOGE(TAG,
|
||||
"Failed to persist under-power limit (%" PRIu32 " W): %s",
|
||||
(uint32_t)value, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
// ========================
|
||||
// Limit checking logic
|
||||
// ========================
|
||||
|
||||
// ---------------------------------
|
||||
// Runtime check
|
||||
// ---------------------------------
|
||||
void evse_limits_check(void)
|
||||
{
|
||||
// Só faz sentido durante carregamento
|
||||
// Só faz sentido quando há energia ativa (C2/D2)
|
||||
if (!evse_state_is_charging(evse_get_state()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
evse_session_t sess;
|
||||
if (!evse_session_get(&sess) || !sess.is_current)
|
||||
{
|
||||
// Sem sessão ativa → nada a fazer
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t cons_lim;
|
||||
uint32_t time_lim;
|
||||
uint16_t unp_lim;
|
||||
|
||||
portENTER_CRITICAL(&evse_mux);
|
||||
cons_lim = consumption_limit;
|
||||
time_lim = charging_time_limit;
|
||||
unp_lim = under_power_limit;
|
||||
portEXIT_CRITICAL(&evse_mux);
|
||||
|
||||
bool reached = false;
|
||||
|
||||
// 1) Limite de energia (Wh)
|
||||
if (consumption_limit > 0 && sess.energy_wh >= consumption_limit)
|
||||
if (cons_lim > 0 && sess.energy_wh >= cons_lim)
|
||||
{
|
||||
ESP_LOGW("EVSE_LIMITS",
|
||||
"Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
|
||||
sess.energy_wh, consumption_limit);
|
||||
ESP_LOGW(TAG, "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh",
|
||||
sess.energy_wh, cons_lim);
|
||||
reached = true;
|
||||
}
|
||||
|
||||
// 2) Limite de tempo (s)
|
||||
if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit)
|
||||
if (time_lim > 0 && sess.duration_s >= time_lim)
|
||||
{
|
||||
ESP_LOGW("EVSE_LIMITS",
|
||||
"Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
|
||||
sess.duration_s, charging_time_limit);
|
||||
ESP_LOGW(TAG, "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s",
|
||||
sess.duration_s, time_lim);
|
||||
reached = true;
|
||||
}
|
||||
|
||||
// 3) Under-power (potência instantânea)
|
||||
uint32_t inst_power = evse_meter_get_instant_power();
|
||||
if (under_power_limit > 0 && inst_power < under_power_limit)
|
||||
int32_t p = evse_meter_get_instant_power();
|
||||
uint32_t inst_power = (p > 0) ? (uint32_t)p : 0;
|
||||
|
||||
if (unp_lim > 0 && inst_power < (uint32_t)unp_lim)
|
||||
{
|
||||
ESP_LOGW("EVSE_LIMITS",
|
||||
"Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
|
||||
(uint32_t)inst_power,
|
||||
(uint32_t)under_power_limit);
|
||||
ESP_LOGW(TAG, "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W",
|
||||
inst_power, (uint32_t)unp_lim);
|
||||
reached = true;
|
||||
}
|
||||
|
||||
if (reached)
|
||||
{
|
||||
evse_set_limit_reached(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// === Início de: components/evse/evse_manager.c ===
|
||||
#include "evse_manager.h"
|
||||
#include "evse_state.h"
|
||||
#include "evse_error.h"
|
||||
@@ -11,10 +10,10 @@
|
||||
#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 "esp_err.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
@@ -27,27 +26,29 @@
|
||||
static const char *TAG = "EVSE_Manager";
|
||||
|
||||
static SemaphoreHandle_t evse_mutex;
|
||||
static volatile bool auth_enabled = false;
|
||||
|
||||
// ✅ Proteção para flags partilhadas (event handlers vs task)
|
||||
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
static bool auth_enabled = false;
|
||||
|
||||
// Estado de pausa controlado pelo Load Balancer
|
||||
static volatile bool lb_paused = false;
|
||||
static volatile bool lb_prev_authorized = false;
|
||||
static bool lb_paused = false;
|
||||
static bool lb_prev_authorized = false;
|
||||
|
||||
// Estado de janela do scheduler
|
||||
static volatile bool s_sched_allowed = true;
|
||||
static bool s_sched_allowed = true;
|
||||
static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
|
||||
|
||||
// ================= Helpers internos =================
|
||||
|
||||
static void lb_clear_pause_state(void)
|
||||
{
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
lb_paused = false;
|
||||
lb_prev_authorized = false;
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
}
|
||||
|
||||
// Exposto para outros módulos (se quiserem saber se o scheduler permite)
|
||||
bool evse_sched_is_allowed(void)
|
||||
{
|
||||
bool v;
|
||||
@@ -60,19 +61,35 @@ bool evse_sched_is_allowed(void)
|
||||
static void evse_manager_handle_auth_on_tick(void)
|
||||
{
|
||||
bool sched_allowed = evse_sched_is_allowed();
|
||||
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
|
||||
bool has_error = (err_bits != 0);
|
||||
|
||||
if (auth_enabled)
|
||||
bool local_auth_enabled;
|
||||
bool local_lb_paused;
|
||||
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
local_auth_enabled = auth_enabled;
|
||||
local_lb_paused = lb_paused;
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
|
||||
if (local_auth_enabled)
|
||||
{
|
||||
// Se o carro foi desconectado, revoga autorização
|
||||
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
|
||||
{
|
||||
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
|
||||
evse_state_set_authorized(false);
|
||||
// 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 (has_error && evse_state_get_authorized())
|
||||
{
|
||||
ESP_LOGI(TAG,
|
||||
"[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
|
||||
err_bits);
|
||||
evse_state_set_authorized(false);
|
||||
lb_clear_pause_state();
|
||||
}
|
||||
|
||||
if (!sched_allowed && evse_state_get_authorized())
|
||||
{
|
||||
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
|
||||
@@ -81,28 +98,32 @@ static void evse_manager_handle_auth_on_tick(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Modo OPEN: só autoriza se LB e Scheduler permitirem
|
||||
if (!lb_paused && sched_allowed && !evse_state_get_authorized())
|
||||
bool limit_hit = evse_is_limit_reached();
|
||||
bool can_operate = evse_config_is_available() && evse_config_is_enabled();
|
||||
|
||||
if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
|
||||
evse_state_get_authorized())
|
||||
{
|
||||
evse_state_set_authorized(true);
|
||||
ESP_LOGI(TAG, "Authentication disabled → forced authorization (within schedule).");
|
||||
lb_clear_pause_state();
|
||||
ESP_LOGI(TAG,
|
||||
"[OPEN] blocking (err=%d limit=%d sched=%d operate=%d lb_paused=%d) → revoking authorization.",
|
||||
(int)has_error, (int)limit_hit, (int)sched_allowed, (int)can_operate, (int)local_lb_paused);
|
||||
evse_state_set_authorized(false);
|
||||
}
|
||||
|
||||
// Fora da janela, garantir que não fica autorizado
|
||||
if (!sched_allowed && evse_state_get_authorized())
|
||||
if (!local_lb_paused && sched_allowed && can_operate &&
|
||||
!has_error && !limit_hit &&
|
||||
!evse_state_get_authorized())
|
||||
{
|
||||
ESP_LOGI(TAG, "[SCHED] window closed (OPEN mode) → revoking authorization.");
|
||||
evse_state_set_authorized(false);
|
||||
evse_state_set_authorized(true);
|
||||
ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
|
||||
lb_clear_pause_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Task de ciclo principal =====
|
||||
static void evse_manager_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
while (true)
|
||||
{
|
||||
evse_manager_tick();
|
||||
@@ -110,15 +131,10 @@ 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;
|
||||
|
||||
auth_mode_t g_mode = AUTH_MODE_OPEN;
|
||||
if (base != AUTH_EVENTS || !data) return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
@@ -127,8 +143,6 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
|
||||
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
|
||||
ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
|
||||
evse_state_set_authorized(evt->authorized);
|
||||
|
||||
// Qualquer alteração explícita de auth invalida pausa do LB
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
}
|
||||
@@ -137,37 +151,26 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
|
||||
case AUTH_EVENT_INIT:
|
||||
{
|
||||
const auth_mode_event_data_t *evt = (const auth_mode_event_data_t *)data;
|
||||
g_mode = evt->mode;
|
||||
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(g_mode));
|
||||
if (g_mode == AUTH_MODE_OPEN)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
evse_state_set_authorized(false);
|
||||
auth_enabled = true;
|
||||
}
|
||||
ESP_LOGI(TAG, "Auth mode = %s", auth_mode_to_str(evt->mode));
|
||||
|
||||
// Modo mudou -> qualquer pausa antiga deixa de fazer sentido
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
auth_enabled = (evt->mode != AUTH_MODE_OPEN);
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
|
||||
evse_state_set_authorized(false);
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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_data) return;
|
||||
|
||||
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
|
||||
{
|
||||
@@ -181,98 +184,86 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
|
||||
const loadbalancer_master_limit_event_t *evt =
|
||||
(const loadbalancer_master_limit_event_t *)event_data;
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Novo limite de corrente (master): %u A (ts: %lld)",
|
||||
ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)",
|
||||
evt->max_current, (long long)evt->timestamp_us);
|
||||
|
||||
if (evt->max_current == 0)
|
||||
{
|
||||
// Suspensão por LB (não interessa se é OPEN ou RFID/OCPP)
|
||||
lb_paused = true;
|
||||
lb_prev_authorized = evse_state_get_authorized();
|
||||
bool prev_auth = evse_state_get_authorized();
|
||||
|
||||
if (lb_prev_authorized)
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
lb_paused = true;
|
||||
lb_prev_authorized = prev_auth;
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
|
||||
if (prev_auth)
|
||||
{
|
||||
ESP_LOGI(TAG, "[LB] limit=0A → pausando sessão (authorized=false)");
|
||||
evse_state_set_authorized(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGD(TAG, "[LB] limit=0A → já não estava autorizado");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ajusta corrente em runtime
|
||||
evse_set_runtime_charging_current(evt->max_current);
|
||||
|
||||
if (lb_paused)
|
||||
{
|
||||
lb_paused = false;
|
||||
bool was_paused;
|
||||
bool prev_auth;
|
||||
|
||||
// Só retomamos se EVSE estiver operacional e scheduler permitir
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
was_paused = lb_paused;
|
||||
prev_auth = lb_prev_authorized;
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
|
||||
if (was_paused)
|
||||
{
|
||||
bool can_resume =
|
||||
(evse_get_error() == 0) &&
|
||||
evse_config_is_available() &&
|
||||
evse_config_is_enabled() &&
|
||||
evse_sched_is_allowed();
|
||||
evse_sched_is_allowed() &&
|
||||
!evse_is_limit_reached();
|
||||
|
||||
if (!can_resume)
|
||||
{
|
||||
ESP_LOGW(TAG,
|
||||
"[LB] limit=%uA → não retoma automaticamente (erro/indisponível/desabilitado/fora de horário)",
|
||||
"[LB] limit=%uA → não retoma automaticamente (erro/indisp/desab/fora de horário/limite)",
|
||||
evt->max_current);
|
||||
lb_clear_pause_state();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!auth_enabled)
|
||||
bool local_auth_enabled;
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
local_auth_enabled = auth_enabled;
|
||||
lb_paused = false; // já vai tentar retomar
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
|
||||
if (!local_auth_enabled)
|
||||
{
|
||||
// Modo OPEN: retoma sempre (se dentro da janela do scheduler)
|
||||
ESP_LOGI(TAG,
|
||||
"[LB] limit=%uA → modo OPEN, reautorizando (authorized=true)",
|
||||
evt->max_current);
|
||||
ESP_LOGI(TAG, "[LB] limit=%uA → modo OPEN, reautorizando", evt->max_current);
|
||||
evse_state_set_authorized(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// RFID/OCPP: só retoma se havia autorização antes da pausa
|
||||
if (lb_prev_authorized)
|
||||
if (prev_auth)
|
||||
{
|
||||
ESP_LOGI(TAG,
|
||||
"[LB] limit=%uA → RFID/OCPP, retomando autorização anterior (auto-resume)",
|
||||
evt->max_current);
|
||||
ESP_LOGI(TAG, "[LB] limit=%uA → RFID/OCPP, retomando autorização anterior", evt->max_current);
|
||||
evse_state_set_authorized(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGI(TAG,
|
||||
"[LB] limit=%uA → RFID/OCPP, sem autorização prévia, mantendo estado atual",
|
||||
evt->max_current);
|
||||
}
|
||||
}
|
||||
|
||||
// Limpa estado prévio (não reaplicar em pausas futuras)
|
||||
portENTER_CRITICAL(&s_mgr_mux);
|
||||
lb_prev_authorized = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Caso normal: apenas ajuste de corrente, sem mexer em auth
|
||||
ESP_LOGD(TAG,
|
||||
"[LB] limit=%uA → ajustando corrente runtime (sem mudança de autorização)",
|
||||
evt->max_current);
|
||||
portEXIT_CRITICAL(&s_mgr_mux);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 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;
|
||||
if (base != OCPP_EVENTS) return;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
@@ -283,13 +274,10 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
|
||||
break;
|
||||
|
||||
case OCPP_EVENT_AUTH_REJECTED:
|
||||
ESP_LOGW(TAG, "[OCPP] Authorization rejected");
|
||||
evse_state_set_authorized(false);
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
|
||||
case OCPP_EVENT_AUTH_TIMEOUT:
|
||||
ESP_LOGW(TAG, "[OCPP] Authorization timeout");
|
||||
case OCPP_EVENT_REMOTE_STOP:
|
||||
case OCPP_EVENT_STOP_TX:
|
||||
ESP_LOGW(TAG, "[OCPP] Authorization/Stop");
|
||||
evse_state_set_authorized(false);
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
@@ -300,24 +288,11 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
|
||||
case OCPP_EVENT_REMOTE_STOP:
|
||||
ESP_LOGI(TAG, "[OCPP] RemoteStop");
|
||||
evse_state_set_authorized(false);
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
|
||||
case OCPP_EVENT_START_TX:
|
||||
ESP_LOGI(TAG, "[OCPP] StartTx");
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
|
||||
case OCPP_EVENT_STOP_TX:
|
||||
ESP_LOGI(TAG, "[OCPP] StopTx");
|
||||
evse_state_set_authorized(false);
|
||||
lb_clear_pause_state();
|
||||
break;
|
||||
|
||||
// ChangeAvailability remoto (operative/inoperative)
|
||||
case OCPP_EVENT_OPERATIVE_UPDATED:
|
||||
{
|
||||
if (!data)
|
||||
@@ -329,7 +304,6 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
|
||||
ESP_LOGI(TAG, "[OCPP] OperativeUpdated: operative=%d ts=%lld",
|
||||
(int)ev->operative, (long long)ev->timestamp_us);
|
||||
|
||||
// Mapear operative → enabled local (persiste e emite EVSE_EVENT_ENABLE_UPDATED)
|
||||
evse_config_set_enabled(ev->operative);
|
||||
break;
|
||||
}
|
||||
@@ -340,16 +314,10 @@ 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)
|
||||
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;
|
||||
if (base != SCHED_EVENTS || data == NULL) return;
|
||||
|
||||
const sched_event_state_t *ev = (const sched_event_state_t *)data;
|
||||
|
||||
@@ -357,29 +325,26 @@ static void on_sched_event(void *arg,
|
||||
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);
|
||||
ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (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();
|
||||
esp_err_t err = evse_config_init();
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to init EVSE config NVS: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
evse_error_init();
|
||||
evse_hardware_init();
|
||||
evse_state_init();
|
||||
@@ -393,11 +358,10 @@ void evse_manager_init(void)
|
||||
|
||||
ESP_LOGI(TAG, "EVSE Manager inicializado.");
|
||||
|
||||
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
|
||||
BaseType_t rc = xTaskCreate(evse_manager_task, "evse_manager_task", 8192, NULL, 4, NULL);
|
||||
configASSERT(rc == pdPASS);
|
||||
}
|
||||
|
||||
// ===== Main Tick =====
|
||||
void evse_manager_tick(void)
|
||||
{
|
||||
xSemaphoreTake(evse_mutex, portMAX_DELAY);
|
||||
@@ -412,5 +376,3 @@ void evse_manager_tick(void)
|
||||
|
||||
xSemaphoreGive(evse_mutex);
|
||||
}
|
||||
|
||||
// === Fim de: components/evse/evse_manager.c ===
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// components/evse/evse_pilot.c
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "driver/ledc.h"
|
||||
#include "esp_err.h"
|
||||
@@ -13,153 +12,232 @@
|
||||
#include "adc121s021_dma.h"
|
||||
#include "board_config.h"
|
||||
|
||||
#define PILOT_PWM_TIMER LEDC_TIMER_0
|
||||
#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0
|
||||
#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT
|
||||
#define PILOT_PWM_MAX_DUTY 1023
|
||||
#define PILOT_PWM_TIMER LEDC_TIMER_0
|
||||
#define PILOT_PWM_CHANNEL LEDC_CHANNEL_0
|
||||
#define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE
|
||||
#define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT
|
||||
#define PILOT_PWM_MAX_DUTY 1023
|
||||
|
||||
#define NUM_PILOT_SAMPLES 100
|
||||
#define MAX_SAMPLE_ATTEMPTS 1000
|
||||
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
|
||||
// --- Configuração de amostragem do Pilot ---
|
||||
#define NUM_PILOT_SAMPLES 100
|
||||
#define MAX_SAMPLE_ATTEMPTS 1000
|
||||
|
||||
#define ADC121_VREF_MV 3300
|
||||
#define ADC121_MAX 4095
|
||||
#define PILOT_SAMPLE_DELAY_US 10
|
||||
|
||||
// Percentagem para descartar extremos superior/inferior (ruído)
|
||||
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior
|
||||
|
||||
// ADC referência
|
||||
#define ADC121_VREF_MV 3300
|
||||
#define ADC121_MAX 4095
|
||||
|
||||
static const char *TAG = "evse_pilot";
|
||||
|
||||
static int last_pilot_level = -1;
|
||||
static uint32_t last_pwm_duty = 0;
|
||||
typedef enum {
|
||||
PILOT_MODE_DC_HIGH = 0, // +12V (nível alto)
|
||||
PILOT_MODE_DC_LOW, // nível baixo / pilot desligado (dependente do hardware)
|
||||
PILOT_MODE_PWM // PWM ativo
|
||||
} pilot_mode_t;
|
||||
|
||||
static int adc_raw_to_mv(uint16_t raw) {
|
||||
return (raw * ADC121_VREF_MV) / ADC121_MAX;
|
||||
static pilot_mode_t s_mode = PILOT_MODE_DC_LOW;
|
||||
static uint32_t last_pwm_duty = 0;
|
||||
|
||||
// ---------------------
|
||||
// Helpers internos
|
||||
// ---------------------
|
||||
static int adc_raw_to_mv(uint16_t raw)
|
||||
{
|
||||
return (int)((raw * ADC121_VREF_MV) / ADC121_MAX);
|
||||
}
|
||||
|
||||
static int compare_uint16(const void *a, const void *b)
|
||||
{
|
||||
uint16_t va = *(const uint16_t *)a;
|
||||
uint16_t vb = *(const uint16_t *)b;
|
||||
if (va < vb) return -1;
|
||||
if (va > vb) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// Inicialização PWM + ADC
|
||||
// ---------------------
|
||||
void pilot_init(void)
|
||||
{
|
||||
// Configura timer do PWM do Pilot (1 kHz)
|
||||
ledc_timer_config_t ledc_timer = {
|
||||
.speed_mode = PILOT_PWM_SPEED_MODE,
|
||||
.timer_num = PILOT_PWM_TIMER,
|
||||
.duty_resolution = PILOT_PWM_DUTY_RES,
|
||||
.freq_hz = 1000,
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
.speed_mode = PILOT_PWM_SPEED_MODE,
|
||||
.timer_num = PILOT_PWM_TIMER,
|
||||
.duty_resolution = PILOT_PWM_DUTY_RES,
|
||||
.freq_hz = 1000, // 1 kHz (IEC 61851)
|
||||
.clk_cfg = LEDC_AUTO_CLK
|
||||
};
|
||||
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
|
||||
|
||||
// Canal do PWM no pino configurado em board_config
|
||||
ledc_channel_config_t ledc_channel = {
|
||||
.speed_mode = PILOT_PWM_SPEED_MODE,
|
||||
.channel = PILOT_PWM_CHANNEL,
|
||||
.timer_sel = PILOT_PWM_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = board_config.pilot_pwm_gpio,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
.channel = PILOT_PWM_CHANNEL,
|
||||
.timer_sel = PILOT_PWM_TIMER,
|
||||
.intr_type = LEDC_INTR_DISABLE,
|
||||
.gpio_num = board_config.pilot_pwm_gpio,
|
||||
.duty = 0,
|
||||
.hpoint = 0
|
||||
};
|
||||
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
|
||||
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
|
||||
|
||||
// Garante que começa parado e em idle baixo (pilot off)
|
||||
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
|
||||
s_mode = PILOT_MODE_DC_LOW;
|
||||
last_pwm_duty = 0;
|
||||
|
||||
// Inicializa driver do ADC121S021
|
||||
adc121s021_dma_init();
|
||||
}
|
||||
|
||||
void pilot_set_level(bool level)
|
||||
// ---------------------
|
||||
// Controlo do modo do Pilot
|
||||
// ---------------------
|
||||
void pilot_set_level(bool high)
|
||||
{
|
||||
if (last_pilot_level == level) return;
|
||||
last_pilot_level = level;
|
||||
pilot_mode_t target = high ? PILOT_MODE_DC_HIGH : PILOT_MODE_DC_LOW;
|
||||
|
||||
ESP_LOGI(TAG, "Set level %d", level);
|
||||
ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0);
|
||||
// Se já estiver no modo DC desejado e sem PWM ativo, ignora
|
||||
if (s_mode == target && last_pwm_duty == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Pilot set DC level: %s", high ? "HIGH(+12V)" : "LOW/OFF");
|
||||
|
||||
// Para PWM e fixa o nível idle do GPIO
|
||||
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, high ? 1 : 0));
|
||||
|
||||
s_mode = target;
|
||||
last_pwm_duty = 0;
|
||||
}
|
||||
|
||||
void pilot_set_amps(uint16_t amps)
|
||||
{
|
||||
if (amps < 6 || amps > 80) {
|
||||
if (amps < 6 || amps > 80)
|
||||
{
|
||||
ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 6–80 A)", amps);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t duty_percent;
|
||||
if (amps <= 51) {
|
||||
if (amps <= 51)
|
||||
{
|
||||
duty_percent = (amps * 10) / 6;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
duty_percent = (amps * 10) / 25 + 64;
|
||||
}
|
||||
if (duty_percent > 100) duty_percent = 100;
|
||||
|
||||
uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100;
|
||||
|
||||
if (last_pilot_level == 0 && last_pwm_duty == duty) return;
|
||||
last_pilot_level = 0;
|
||||
// Se já estiver em PWM com o mesmo duty, ignora
|
||||
if (s_mode == PILOT_MODE_PWM && last_pwm_duty == duty) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_mode = PILOT_MODE_PWM;
|
||||
last_pwm_duty = duty;
|
||||
|
||||
ESP_LOGI(TAG, "Pilot set: %d A → %d/%d (≈ %d%% duty)",
|
||||
ESP_LOGI(TAG, "Pilot set PWM: %d A → %d/%d (≈ %d%% duty)",
|
||||
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);
|
||||
ESP_ERROR_CHECK(ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty));
|
||||
ESP_ERROR_CHECK(ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL));
|
||||
}
|
||||
|
||||
bool pilot_get_state(void) {
|
||||
return (last_pilot_level == 1) && (last_pwm_duty == 0);
|
||||
}
|
||||
|
||||
static int compare_int(const void *a, const void *b) {
|
||||
return (*(const int *)a - *(const int *)b);
|
||||
bool pilot_get_state(void)
|
||||
{
|
||||
// "Alto" significa DC +12V (estado A). PWM não conta como DC high.
|
||||
return (s_mode == PILOT_MODE_DC_HIGH);
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// Medição do sinal de Pilot (PWM 1 kHz J1772)
|
||||
// ---------------------
|
||||
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
|
||||
{
|
||||
ESP_LOGD(TAG, "pilot_measure");
|
||||
|
||||
int samples[NUM_PILOT_SAMPLES];
|
||||
int collected = 0, attempts = 0;
|
||||
uint16_t adc_sample = 0;
|
||||
uint16_t samples[NUM_PILOT_SAMPLES];
|
||||
int collected = 0;
|
||||
int attempts = 0;
|
||||
|
||||
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) {
|
||||
adc_sample = 0;
|
||||
if (adc121s021_dma_get_sample(&adc_sample)) {
|
||||
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS)
|
||||
{
|
||||
uint16_t adc_sample;
|
||||
|
||||
if (adc121s021_dma_get_sample(&adc_sample))
|
||||
{
|
||||
samples[collected++] = adc_sample;
|
||||
esp_rom_delay_us(10);
|
||||
} else {
|
||||
esp_rom_delay_us(PILOT_SAMPLE_DELAY_US);
|
||||
}
|
||||
else
|
||||
{
|
||||
esp_rom_delay_us(100);
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
|
||||
if (collected < NUM_PILOT_SAMPLES) {
|
||||
if (collected < NUM_PILOT_SAMPLES)
|
||||
{
|
||||
ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES);
|
||||
*up_voltage = PILOT_VOLTAGE_1;
|
||||
*down_voltage_n12 = false;
|
||||
return;
|
||||
}
|
||||
|
||||
qsort(samples, collected, sizeof(int), compare_int);
|
||||
// Ordena as amostras para eliminar extremos (ruído/espúrios)
|
||||
qsort(samples, collected, sizeof(uint16_t), compare_uint16);
|
||||
|
||||
int k = (collected * PILOT_EXTREME_PERCENT) / 100;
|
||||
if (k == 0) k = 1;
|
||||
if (k < 2) k = 2; // garante margem mínima
|
||||
|
||||
// descarta k/2 em cada lado (aprox. 10% total, mantendo simetria)
|
||||
int low_index = k / 2;
|
||||
int high_index = collected - k + (k / 2);
|
||||
if (high_index >= collected) high_index = collected - 1;
|
||||
int high_index = collected - 1 - (k / 2);
|
||||
|
||||
int low_raw = samples[low_index];
|
||||
int high_raw = samples[high_index];
|
||||
if (low_index < 0) low_index = 0;
|
||||
if (high_index >= collected) high_index = collected - 1;
|
||||
if (high_index <= low_index) high_index = low_index;
|
||||
|
||||
uint16_t low_raw = samples[low_index];
|
||||
uint16_t high_raw = samples[high_index];
|
||||
|
||||
int high_mv = adc_raw_to_mv(high_raw);
|
||||
int low_mv = adc_raw_to_mv(low_raw);
|
||||
|
||||
// Determina o nível positivo (+12, +9, +6, +3 ou <3 V)
|
||||
if (high_mv >= board_config.pilot_down_threshold_12)
|
||||
{
|
||||
*up_voltage = PILOT_VOLTAGE_12;
|
||||
}
|
||||
else if (high_mv >= board_config.pilot_down_threshold_9)
|
||||
{
|
||||
*up_voltage = PILOT_VOLTAGE_9;
|
||||
}
|
||||
else if (high_mv >= board_config.pilot_down_threshold_6)
|
||||
{
|
||||
*up_voltage = PILOT_VOLTAGE_6;
|
||||
}
|
||||
else if (high_mv >= board_config.pilot_down_threshold_3)
|
||||
{
|
||||
*up_voltage = PILOT_VOLTAGE_3;
|
||||
}
|
||||
else
|
||||
{
|
||||
*up_voltage = PILOT_VOLTAGE_1;
|
||||
}
|
||||
|
||||
// Verifica se o nível negativo atinge -12 V (diodo presente, C/D válidos)
|
||||
*down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12);
|
||||
|
||||
ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d", *up_voltage, *down_voltage_n12);
|
||||
ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d (high=%d mV, low=%d mV)",
|
||||
*up_voltage, *down_voltage_n12, high_mv, low_mv);
|
||||
}
|
||||
|
||||
@@ -7,22 +7,49 @@
|
||||
#include "evse_events.h"
|
||||
#include "esp_event.h"
|
||||
#include "evse_limits.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#define EVSE_EVENT_POST_TIMEOUT_MS 50
|
||||
|
||||
static const char *TAG = "evse_session";
|
||||
|
||||
static TickType_t session_start_tick = 0;
|
||||
static uint32_t watt_seconds = 0;
|
||||
|
||||
// Tempo real (microsegundos)
|
||||
static int64_t session_start_us = 0;
|
||||
static int64_t last_tick_us = 0;
|
||||
|
||||
// Energia integrada com tempo real: soma de (W * us)
|
||||
static uint64_t watt_microseconds = 0;
|
||||
|
||||
static evse_session_t last_session;
|
||||
static bool last_session_valid = false;
|
||||
static uint32_t session_counter = 0;
|
||||
|
||||
static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
static void post_session_event(const evse_session_event_data_t *evt)
|
||||
{
|
||||
esp_err_t err = esp_event_post(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_SESSION,
|
||||
evt,
|
||||
sizeof(*evt),
|
||||
portMAX_DELAY);
|
||||
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "esp_event_post(EVSE_EVENT_SESSION) failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
void evse_session_init(void)
|
||||
{
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
session_start_tick = 0;
|
||||
watt_seconds = 0;
|
||||
session_start_us = 0;
|
||||
last_tick_us = 0;
|
||||
watt_microseconds = 0;
|
||||
last_session_valid = false;
|
||||
session_counter = 0;
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
@@ -31,55 +58,65 @@ void evse_session_init(void)
|
||||
void evse_session_start(void)
|
||||
{
|
||||
TickType_t tick = xTaskGetTickCount();
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
session_start_tick = tick;
|
||||
watt_seconds = 0;
|
||||
session_start_us = now_us;
|
||||
last_tick_us = now_us;
|
||||
watt_microseconds = 0;
|
||||
session_counter++;
|
||||
uint32_t id = session_counter;
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
|
||||
evse_set_limit_reached(false);
|
||||
|
||||
ESP_LOGI(TAG, "Session started at tick %u", (unsigned)tick);
|
||||
ESP_LOGI(TAG, "Session started (id=%" PRIu32 ") tick=%u us=%" PRId64,
|
||||
id, (unsigned)tick, now_us);
|
||||
|
||||
evse_session_event_data_t evt = {
|
||||
.type = EVSE_SESSION_EVENT_STARTED,
|
||||
.session_id = session_counter,
|
||||
.session_id = id,
|
||||
.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);
|
||||
post_session_event(&evt);
|
||||
}
|
||||
|
||||
void evse_session_end(void)
|
||||
{
|
||||
TickType_t start_tick;
|
||||
uint32_t ws;
|
||||
int64_t start_us;
|
||||
uint64_t w_us;
|
||||
uint32_t id;
|
||||
|
||||
int64_t end_us = esp_timer_get_time();
|
||||
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
if (session_start_tick == 0) {
|
||||
if (session_start_tick == 0)
|
||||
{
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
ESP_LOGW(TAG, "evse_session_end called without active session");
|
||||
return;
|
||||
}
|
||||
|
||||
start_tick = session_start_tick;
|
||||
ws = watt_seconds;
|
||||
start_us = session_start_us;
|
||||
w_us = watt_microseconds;
|
||||
id = session_counter;
|
||||
|
||||
session_start_tick = 0;
|
||||
session_start_us = 0;
|
||||
last_tick_us = 0;
|
||||
watt_microseconds = 0;
|
||||
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;
|
||||
uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0;
|
||||
uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
|
||||
uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
|
||||
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
|
||||
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
last_session.start_tick = start_tick;
|
||||
@@ -91,9 +128,9 @@ void evse_session_end(void)
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Session ended: duration=%" PRIu32 " s, energy=%" PRIu32
|
||||
"Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32
|
||||
" Wh, avg_power=%" PRIu32 " W",
|
||||
duration_s, energy_wh, avg_power);
|
||||
id, duration_s, energy_wh, avg_power);
|
||||
|
||||
evse_session_event_data_t evt = {
|
||||
.type = EVSE_SESSION_EVENT_FINISHED,
|
||||
@@ -103,21 +140,36 @@ void evse_session_end(void)
|
||||
.avg_power_w = avg_power,
|
||||
.is_current = false,
|
||||
};
|
||||
|
||||
esp_event_post(EVSE_EVENTS,
|
||||
EVSE_EVENT_SESSION,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
post_session_event(&evt);
|
||||
}
|
||||
|
||||
void evse_session_tick(void)
|
||||
{
|
||||
uint32_t power_w = evse_meter_get_instant_power();
|
||||
// Potência instantânea pode ser negativa (ruído/overflow de sensor) -> clamp
|
||||
int p = evse_meter_get_instant_power();
|
||||
uint32_t power_w = (p > 0) ? (uint32_t)p : 0;
|
||||
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
if (session_start_tick != 0) {
|
||||
watt_seconds += power_w;
|
||||
if (session_start_tick != 0)
|
||||
{
|
||||
if (last_tick_us == 0)
|
||||
{
|
||||
last_tick_us = now_us;
|
||||
}
|
||||
int64_t dt_us = now_us - last_tick_us;
|
||||
if (dt_us > 0)
|
||||
{
|
||||
// Energia incremental: W * us (64-bit)
|
||||
watt_microseconds += ((uint64_t)power_w * (uint64_t)dt_us);
|
||||
last_tick_us = now_us;
|
||||
}
|
||||
else
|
||||
{
|
||||
// relógio não devia andar para trás; ignora
|
||||
last_tick_us = now_us;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&session_mux);
|
||||
}
|
||||
@@ -127,15 +179,19 @@ bool evse_session_get(evse_session_t *out)
|
||||
if (out == NULL)
|
||||
return false;
|
||||
|
||||
TickType_t start;
|
||||
uint32_t ws;
|
||||
TickType_t start_tick;
|
||||
int64_t start_us;
|
||||
uint64_t w_us;
|
||||
bool has_current;
|
||||
evse_session_t last_copy;
|
||||
bool last_valid;
|
||||
|
||||
int64_t now_us = esp_timer_get_time();
|
||||
|
||||
portENTER_CRITICAL(&session_mux);
|
||||
start = session_start_tick;
|
||||
ws = watt_seconds;
|
||||
start_tick = session_start_tick;
|
||||
start_us = session_start_us;
|
||||
w_us = watt_microseconds;
|
||||
has_current = (session_start_tick != 0);
|
||||
last_copy = last_session;
|
||||
last_valid = last_session_valid;
|
||||
@@ -143,12 +199,12 @@ bool evse_session_get(evse_session_t *out)
|
||||
|
||||
if (has_current)
|
||||
{
|
||||
TickType_t now = xTaskGetTickCount();
|
||||
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;
|
||||
uint32_t duration_s = (now_us > start_us) ? (uint32_t)((now_us - start_us) / 1000000LL) : 0;
|
||||
uint32_t energy_wh = (uint32_t)(w_us / (3600ULL * 1000000ULL));
|
||||
uint64_t watt_seconds = (uint64_t)(w_us / 1000000ULL);
|
||||
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
|
||||
|
||||
out->start_tick = start;
|
||||
out->start_tick = start_tick;
|
||||
out->duration_s = duration_s;
|
||||
out->energy_wh = energy_wh;
|
||||
out->avg_power_w = avg_power;
|
||||
|
||||
@@ -5,150 +5,203 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
|
||||
// =========================
|
||||
// Internal State Variables
|
||||
// =========================
|
||||
#define EVSE_EVENT_POST_TIMEOUT_MS 50
|
||||
|
||||
static evse_state_t current_state = EVSE_STATE_A;
|
||||
static bool is_authorized = false;
|
||||
|
||||
static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
static const char *TAG = "evse_state";
|
||||
|
||||
// =========================
|
||||
// Internal Mapping
|
||||
// =========================
|
||||
static evse_state_event_t map_state_to_event(evse_state_t s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
return EVSE_STATE_EVENT_IDLE;
|
||||
|
||||
static evse_state_event_t map_state_to_event(evse_state_t s) {
|
||||
switch (s) {
|
||||
case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE;
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2: return EVSE_STATE_EVENT_WAITING;
|
||||
case EVSE_STATE_C1:
|
||||
case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING;
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT;
|
||||
default: return EVSE_STATE_EVENT_IDLE;
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2:
|
||||
return EVSE_STATE_EVENT_WAITING;
|
||||
|
||||
case EVSE_STATE_C1:
|
||||
case EVSE_STATE_C2:
|
||||
case EVSE_STATE_D1:
|
||||
case EVSE_STATE_D2:
|
||||
return EVSE_STATE_EVENT_CHARGING;
|
||||
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
return EVSE_STATE_EVENT_FAULT;
|
||||
|
||||
default:
|
||||
return EVSE_STATE_EVENT_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// Public API
|
||||
// =========================
|
||||
static void post_evse_event(evse_event_id_t id, const void *data, size_t len)
|
||||
{
|
||||
esp_err_t err = esp_event_post(
|
||||
EVSE_EVENTS,
|
||||
id,
|
||||
data,
|
||||
len,
|
||||
portMAX_DELAY);
|
||||
|
||||
void evse_set_state(evse_state_t new_state) {
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "esp_event_post(id=%d) failed: %s", (int)id, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
bool evse_state_is_charging(evse_state_t state)
|
||||
{
|
||||
// “charging” == energia efetiva (relé ON)
|
||||
return (state == EVSE_STATE_C2 || state == EVSE_STATE_D2);
|
||||
}
|
||||
|
||||
bool evse_state_is_power_flowing(evse_state_t state)
|
||||
{
|
||||
return evse_state_is_charging(state);
|
||||
}
|
||||
|
||||
bool evse_state_is_requesting(evse_state_t state)
|
||||
{
|
||||
// EV pediu carga mas o relé ainda está OFF
|
||||
return (state == EVSE_STATE_C1 || state == EVSE_STATE_D1);
|
||||
}
|
||||
|
||||
bool evse_state_is_plugged(evse_state_t state)
|
||||
{
|
||||
return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 ||
|
||||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
|
||||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2;
|
||||
}
|
||||
|
||||
bool evse_state_is_session(evse_state_t state)
|
||||
{
|
||||
// Sessão lógica: “autorizado/pronto” ou “a pedir/a fornecer energia”
|
||||
return (state == EVSE_STATE_B2 ||
|
||||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
|
||||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2);
|
||||
}
|
||||
|
||||
void evse_set_state(evse_state_t new_state)
|
||||
{
|
||||
bool changed = false;
|
||||
evse_state_t prev_state;
|
||||
bool start_session = false;
|
||||
bool end_session = false;
|
||||
bool end_session = false;
|
||||
|
||||
// 1) Detecta transição de estado dentro da região crítica
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
prev_state = current_state;
|
||||
if (new_state != current_state) {
|
||||
// se entrou em charging pela primeira vez
|
||||
if (evse_state_is_charging(new_state) && !evse_state_is_charging(prev_state)) {
|
||||
start_session = true;
|
||||
}
|
||||
// se saiu de charging para qualquer outro
|
||||
else if (!evse_state_is_charging(new_state) && evse_state_is_charging(prev_state)) {
|
||||
end_session = true;
|
||||
}
|
||||
current_state = new_state;
|
||||
changed = true;
|
||||
}
|
||||
prev_state = current_state;
|
||||
|
||||
if (new_state != current_state)
|
||||
{
|
||||
|
||||
// Sessão começa quando entra em energia (relé ON)
|
||||
if (evse_state_is_power_flowing(new_state) && !evse_state_is_power_flowing(prev_state))
|
||||
{
|
||||
start_session = true;
|
||||
}
|
||||
// Sessão termina quando sai de energia
|
||||
else if (!evse_state_is_power_flowing(new_state) && evse_state_is_power_flowing(prev_state))
|
||||
{
|
||||
end_session = true;
|
||||
}
|
||||
|
||||
current_state = new_state;
|
||||
changed = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
// 2) Executa start/end de sessão FORA da região crítica, evitando logs/alloc dentro dela
|
||||
if (start_session) {
|
||||
// Fora da região crítica
|
||||
if (start_session)
|
||||
{
|
||||
evse_session_start();
|
||||
}
|
||||
if (end_session) {
|
||||
if (end_session)
|
||||
{
|
||||
evse_session_end();
|
||||
}
|
||||
|
||||
// 3) Se mudou o estado, faz log e dispara evento
|
||||
if (changed) {
|
||||
const char *prev_str = evse_state_to_str(prev_state);
|
||||
const char *curr_str = evse_state_to_str(new_state);
|
||||
ESP_LOGI(TAG, "State changed: %s → %s", prev_str, curr_str);
|
||||
if (changed)
|
||||
{
|
||||
ESP_LOGI(TAG, "State changed: %s → %s",
|
||||
evse_state_to_str(prev_state),
|
||||
evse_state_to_str(new_state));
|
||||
|
||||
evse_state_event_data_t evt = {
|
||||
.state = map_state_to_event(new_state)
|
||||
};
|
||||
esp_event_post(EVSE_EVENTS,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
.state = map_state_to_event(new_state)};
|
||||
post_evse_event(EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
evse_state_t evse_get_state(void) {
|
||||
evse_state_t evse_get_state(void)
|
||||
{
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
evse_state_t s = current_state;
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
return s;
|
||||
}
|
||||
|
||||
const char* evse_state_to_str(evse_state_t state) {
|
||||
switch (state) {
|
||||
case EVSE_STATE_A: return "A - EV Not Connected (12V)";
|
||||
case EVSE_STATE_B1: return "B1 - EV Connected (9V, Not Authorized)";
|
||||
case EVSE_STATE_B2: return "B2 - EV Connected (9V, Authorized and Ready)";
|
||||
case EVSE_STATE_C1: return "C1 - Charging Requested (6V, Relay Off)";
|
||||
case EVSE_STATE_C2: return "C2 - Charging Active (6V, Relay On)";
|
||||
case EVSE_STATE_D1: return "D1 - Ventilation Required (3V, Relay Off)";
|
||||
case EVSE_STATE_D2: return "D2 - Ventilation Active (3V, Relay On)";
|
||||
case EVSE_STATE_E: return "E - Error: Control Pilot Shorted to Ground (0V)";
|
||||
case EVSE_STATE_F: return "F - Fault: EVSE Unavailable or No Pilot Signal";
|
||||
default: return "Unknown State";
|
||||
const char *evse_state_to_str(evse_state_t state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case EVSE_STATE_A:
|
||||
return "A - EV Not Connected (12V)";
|
||||
case EVSE_STATE_B1:
|
||||
return "B1 - EV Connected (9V, Not Authorized)";
|
||||
case EVSE_STATE_B2:
|
||||
return "B2 - EV Connected (9V, Authorized and Ready)";
|
||||
case EVSE_STATE_C1:
|
||||
return "C1 - Charging Requested (6V, Relay Off)";
|
||||
case EVSE_STATE_C2:
|
||||
return "C2 - Charging Active (6V, Relay On)";
|
||||
case EVSE_STATE_D1:
|
||||
return "D1 - Ventilation Required (3V, Relay Off)";
|
||||
case EVSE_STATE_D2:
|
||||
return "D2 - Ventilation Active (3V, Relay On)";
|
||||
case EVSE_STATE_E:
|
||||
return "E - Error: Control Pilot Shorted to Ground (0V)";
|
||||
case EVSE_STATE_F:
|
||||
return "F - Fault: EVSE Unavailable or No Pilot Signal";
|
||||
default:
|
||||
return "Unknown State";
|
||||
}
|
||||
}
|
||||
|
||||
void evse_state_init(void) {
|
||||
void evse_state_init(void)
|
||||
{
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
current_state = EVSE_STATE_A;
|
||||
is_authorized = true;
|
||||
is_authorized = false;
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
ESP_LOGI("EVSE_STATE", "Initialized in state: %s", evse_state_to_str(current_state));
|
||||
ESP_LOGI(TAG, "Initialized in state: %s", evse_state_to_str(current_state));
|
||||
|
||||
evse_state_event_data_t evt = {
|
||||
.state = map_state_to_event(current_state)
|
||||
};
|
||||
esp_event_post(EVSE_EVENTS, EVSE_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
|
||||
.state = map_state_to_event(current_state)};
|
||||
post_evse_event(EVSE_EVENT_INIT, &evt, sizeof(evt));
|
||||
}
|
||||
|
||||
void evse_state_tick(void) {
|
||||
// Placeholder for future state logic
|
||||
void evse_state_tick(void)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
bool evse_state_is_charging(evse_state_t state) {
|
||||
return state == EVSE_STATE_C1 || state == EVSE_STATE_C2;
|
||||
}
|
||||
|
||||
bool evse_state_is_plugged(evse_state_t state) {
|
||||
return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 ||
|
||||
state == EVSE_STATE_C1 || state == EVSE_STATE_C2 ||
|
||||
state == EVSE_STATE_D1 || state == EVSE_STATE_D2;
|
||||
}
|
||||
|
||||
bool evse_state_is_session(evse_state_t state) {
|
||||
return state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2;
|
||||
}
|
||||
|
||||
void evse_state_set_authorized(bool authorized) {
|
||||
void evse_state_set_authorized(bool authorized)
|
||||
{
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
is_authorized = authorized;
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
}
|
||||
|
||||
bool evse_state_get_authorized(void) {
|
||||
bool evse_state_get_authorized(void)
|
||||
{
|
||||
portENTER_CRITICAL(&state_mux);
|
||||
bool result = is_authorized;
|
||||
portEXIT_CRITICAL(&state_mux);
|
||||
|
||||
@@ -15,11 +15,9 @@ extern "C" {
|
||||
// Limites Globais (Defines)
|
||||
// ========================
|
||||
|
||||
// Corrente máxima de carregamento (configurável pelo usuário)
|
||||
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
|
||||
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
|
||||
|
||||
// Corrente via cabo (proximity) — se configurável
|
||||
#define MIN_CABLE_CURRENT_LIMIT 6 // A
|
||||
#define MAX_CABLE_CURRENT_LIMIT 63 // A
|
||||
|
||||
@@ -31,23 +29,20 @@ extern "C" {
|
||||
esp_err_t evse_config_init(void);
|
||||
void evse_check_defaults(void);
|
||||
|
||||
// Corrente de carregamento
|
||||
// Corrente máxima de hardware (fixa)
|
||||
uint8_t evse_get_max_charging_current(void);
|
||||
esp_err_t evse_set_max_charging_current(uint8_t value);
|
||||
|
||||
// Corrente configurável (persistida) <= max hardware
|
||||
uint16_t evse_get_charging_current(void);
|
||||
esp_err_t evse_set_charging_current(uint16_t value);
|
||||
|
||||
uint16_t evse_get_default_charging_current(void);
|
||||
esp_err_t evse_set_default_charging_current(uint16_t value);
|
||||
|
||||
// Configuração de socket outlet
|
||||
bool evse_get_socket_outlet(void);
|
||||
esp_err_t evse_set_socket_outlet(bool socket_outlet);
|
||||
|
||||
// Corrente runtime (RAM) <= max hardware (load balancer pode alterar)
|
||||
void evse_set_runtime_charging_current(uint16_t value);
|
||||
uint16_t evse_get_runtime_charging_current(void);
|
||||
|
||||
// Socket outlet
|
||||
bool evse_get_socket_outlet(void);
|
||||
esp_err_t evse_set_socket_outlet(bool socket_outlet);
|
||||
|
||||
// RCM
|
||||
bool evse_is_rcm(void);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// === Início de: components/evse/include/evse_error.h ===
|
||||
#ifndef EVSE_ERROR_H
|
||||
#define EVSE_ERROR_H
|
||||
|
||||
@@ -6,21 +5,23 @@
|
||||
#include <stdbool.h>
|
||||
#include "evse_pilot.h"
|
||||
|
||||
// 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)
|
||||
// ----------------------------------------------------
|
||||
// Holdoff interno pós-erro (sem expor "cooldown" ao resto)
|
||||
// ----------------------------------------------------
|
||||
// Após TODOS os erros reais desaparecerem (raw_bits == 0),
|
||||
// o módulo mantém o erro "visível" durante este tempo.
|
||||
// Durante este período, evse_get_error() continua != 0.
|
||||
#define EVSE_ERROR_COOLDOWN_MS 60000
|
||||
|
||||
// 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);
|
||||
@@ -30,7 +31,7 @@ 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
|
||||
// Leitura e controle de erros (estado "visível" com holdoff)
|
||||
uint32_t evse_get_error(void);
|
||||
void evse_error_set(uint32_t bitmask);
|
||||
void evse_error_clear(uint32_t bitmask);
|
||||
@@ -40,12 +41,9 @@ uint32_t evse_error_get_bits(void);
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Semântica sticky: flag "todos erros limpos"
|
||||
// (fica true quando o erro visível chega a 0; pode ser útil para UI/logs)
|
||||
// ----------------------------------------------------
|
||||
// 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 ===
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef enum {
|
||||
EVSE_EVENT_ENABLE_UPDATED,
|
||||
EVSE_EVENT_AVAILABLE_UPDATED,
|
||||
EVSE_EVENT_SESSION,
|
||||
EVSE_EVENT_ERROR_CHANGED,
|
||||
} evse_event_id_t;
|
||||
|
||||
// -----------------
|
||||
@@ -43,35 +44,41 @@ typedef enum {
|
||||
typedef struct {
|
||||
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)
|
||||
uint32_t session_id;
|
||||
uint32_t duration_s;
|
||||
uint32_t energy_wh;
|
||||
uint32_t avg_power_w;
|
||||
|
||||
bool is_current; ///< true se ainda estiver em curso
|
||||
bool is_current;
|
||||
} 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
|
||||
bool charging;
|
||||
float hw_max_current;
|
||||
float runtime_current;
|
||||
int64_t timestamp_us;
|
||||
} evse_config_event_data_t;
|
||||
|
||||
// Eventos simples e específicos
|
||||
typedef struct {
|
||||
bool enabled; // novo estado de enabled
|
||||
int64_t timestamp_us; // epoch micros
|
||||
bool enabled;
|
||||
int64_t timestamp_us;
|
||||
} evse_enable_event_data_t;
|
||||
|
||||
typedef struct {
|
||||
bool available; // novo estado de available
|
||||
int64_t timestamp_us; // epoch micros
|
||||
bool available;
|
||||
int64_t timestamp_us;
|
||||
} evse_available_event_data_t;
|
||||
|
||||
// -----------------
|
||||
// Eventos de ERRO
|
||||
// -----------------
|
||||
typedef struct {
|
||||
uint32_t error_bits; ///< estado atual (todos os bits de erro)
|
||||
uint32_t changed_mask; ///< bits que mudaram nesta notificação
|
||||
int64_t timestamp_us; ///< esp_timer_get_time()
|
||||
} evse_error_event_data_t;
|
||||
|
||||
#endif // EVSE_EVENTS_H
|
||||
|
||||
@@ -2,65 +2,43 @@
|
||||
#define PILOT_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Níveis categóricos de tensão no sinal CP (Control Pilot)
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
PILOT_VOLTAGE_12, ///< Estado A: +12V
|
||||
PILOT_VOLTAGE_9, ///< Estado B: +9V
|
||||
PILOT_VOLTAGE_6, ///< Estado C: +6V
|
||||
PILOT_VOLTAGE_3, ///< Estado D: +3V
|
||||
PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V
|
||||
} pilot_voltage_t;
|
||||
typedef enum
|
||||
{
|
||||
PILOT_VOLTAGE_12,
|
||||
PILOT_VOLTAGE_9,
|
||||
PILOT_VOLTAGE_6,
|
||||
PILOT_VOLTAGE_3,
|
||||
PILOT_VOLTAGE_1
|
||||
} pilot_voltage_t;
|
||||
|
||||
/**
|
||||
* @brief Inicializa o driver do sinal Pilot
|
||||
*/
|
||||
void pilot_init(void);
|
||||
void pilot_init(void);
|
||||
|
||||
/**
|
||||
* @brief Define o nível do Pilot: +12V ou -12V
|
||||
*
|
||||
* @param level true = +12V, false = -12V
|
||||
*/
|
||||
void pilot_set_level(bool level);
|
||||
/**
|
||||
* @brief Define o pilot em modo DC.
|
||||
*
|
||||
* @param high true = nível alto (+12V)
|
||||
* false = nível baixo (-12V)
|
||||
*/
|
||||
void pilot_set_level(bool high);
|
||||
|
||||
/**
|
||||
* @brief Ativa o PWM do Pilot com corrente limitada
|
||||
*
|
||||
* @param amps Corrente em ampères (ex: 16 = 16A)
|
||||
*/
|
||||
void pilot_set_amps(uint16_t amps);
|
||||
void pilot_set_amps(uint16_t amps);
|
||||
|
||||
/**
|
||||
* @brief Mede o nível de tensão do Pilot e detecta -12V
|
||||
*
|
||||
* @param up_voltage Valor categórico da tensão positiva
|
||||
* @param down_voltage_n12 true se o nível negativo atingir -12V
|
||||
*/
|
||||
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12);
|
||||
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12);
|
||||
|
||||
/**
|
||||
* @brief Retorna o estado lógico atual do Pilot (nível alto = +12V)
|
||||
*
|
||||
* @return true se nível atual for +12V, false se for -12V
|
||||
*/
|
||||
bool pilot_get_state(void);
|
||||
bool pilot_get_state(void);
|
||||
|
||||
/**
|
||||
* @brief Cache interno opcional dos níveis de tensão reais do Pilot
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t high_mv; ///< Pico positivo medido (mV)
|
||||
uint16_t low_mv; ///< Pico negativo medido (mV)
|
||||
} pilot_voltage_cache_t;
|
||||
typedef struct
|
||||
{
|
||||
uint16_t high_mv;
|
||||
uint16_t low_mv;
|
||||
} pilot_voltage_cache_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* evse_session.h
|
||||
* Module to track and retrieve charging session data (current or last completed),
|
||||
* accumulating energy via periodic tick of instantaneous power.
|
||||
*/
|
||||
|
||||
#ifndef EVSE_SESSION_H
|
||||
#define EVSE_SESSION_H
|
||||
|
||||
@@ -15,39 +9,23 @@
|
||||
* @brief Charging session statistics
|
||||
*/
|
||||
typedef struct {
|
||||
TickType_t start_tick; ///< tick when session began
|
||||
uint32_t duration_s; ///< total duration in seconds
|
||||
uint32_t energy_wh; ///< total energy consumed in Wh
|
||||
TickType_t start_tick; ///< tick when session began (debug/trace)
|
||||
uint32_t duration_s; ///< total duration in seconds (tempo real)
|
||||
uint32_t energy_wh; ///< total energy consumed in Wh (tempo real)
|
||||
uint32_t avg_power_w; ///< average power in W
|
||||
bool is_current; ///< true if session still in progress
|
||||
} evse_session_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize the session module
|
||||
*/
|
||||
void evse_session_init(void);
|
||||
|
||||
/**
|
||||
* @brief Mark the beginning of a charging session
|
||||
*/
|
||||
void evse_session_start(void);
|
||||
|
||||
/**
|
||||
* @brief Mark the end of the charging session and store it as "last session"
|
||||
*/
|
||||
void evse_session_end(void);
|
||||
|
||||
/**
|
||||
* @brief Periodic tick: must be called (e.g., each 1s) to accumulate energy from instant power
|
||||
* @brief Periodic tick: called (e.g., each 1s) to accumulate energy from instant power.
|
||||
* Implementação usa esp_timer (não assume 1s exato).
|
||||
*/
|
||||
void evse_session_tick(void);
|
||||
|
||||
/**
|
||||
* @brief Retrieve statistics of either the current ongoing session (if any) or
|
||||
* the last completed session.
|
||||
* @param out pointer to evse_session_t to be filled
|
||||
* @return true if there is a current or last session available, false otherwise
|
||||
*/
|
||||
bool evse_session_get(evse_session_t *out);
|
||||
|
||||
#endif // EVSE_SESSION_H
|
||||
|
||||
@@ -6,90 +6,68 @@
|
||||
#include "evse_events.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// ============================
|
||||
// EVSE Pilot Signal States
|
||||
// ============================
|
||||
typedef enum
|
||||
{
|
||||
EVSE_STATE_A, // EV Not Connected (12V)
|
||||
EVSE_STATE_B1, // EV Connected (9V, Not Authorized)
|
||||
EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready)
|
||||
EVSE_STATE_C1, // Charging Requested (6V, Relay Off)
|
||||
EVSE_STATE_C2, // Charging Active (6V, Relay On)
|
||||
EVSE_STATE_D1, // Ventilation Required (3V, Relay Off)
|
||||
EVSE_STATE_D2, // Ventilation Active (3V, Relay On)
|
||||
EVSE_STATE_E, // Error: Pilot Short to Ground (0V)
|
||||
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
|
||||
} evse_state_t;
|
||||
|
||||
typedef enum {
|
||||
EVSE_STATE_A, // EV Not Connected (12V)
|
||||
EVSE_STATE_B1, // EV Connected (9V, Not Authorized)
|
||||
EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready)
|
||||
EVSE_STATE_C1, // Charging Requested (6V, Relay Off)
|
||||
EVSE_STATE_C2, // Charging Active (6V, Relay On)
|
||||
EVSE_STATE_D1, // Ventilation Required (3V, Relay Off)
|
||||
EVSE_STATE_D2, // Ventilation Active (3V, Relay On)
|
||||
EVSE_STATE_E, // Error: Pilot Short to Ground (0V)
|
||||
EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable
|
||||
} evse_state_t;
|
||||
// Initialization
|
||||
void evse_state_init(void);
|
||||
void evse_state_tick(void);
|
||||
|
||||
// ============================
|
||||
// Initialization
|
||||
// ============================
|
||||
// State Access & Control
|
||||
evse_state_t evse_get_state(void);
|
||||
void evse_set_state(evse_state_t state);
|
||||
const char *evse_state_to_str(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Initializes the EVSE state machine and default state.
|
||||
*/
|
||||
void evse_state_init(void);
|
||||
// ---------------------------
|
||||
// State Evaluation Helpers
|
||||
// ---------------------------
|
||||
|
||||
/**
|
||||
* @brief Periodic tick for state handling (optional hook).
|
||||
*/
|
||||
void evse_state_tick(void);
|
||||
/**
|
||||
* @brief True se existe uma sessão "lógica" ativa (carro ligado e autorizado/pronto ou a carregar).
|
||||
* Inclui B2, C1/C2, D1/D2.
|
||||
*/
|
||||
bool evse_state_is_session(evse_state_t state);
|
||||
|
||||
// ============================
|
||||
// State Access & Control
|
||||
// ============================
|
||||
/**
|
||||
* @brief True se o EVSE está a fornecer energia (relé ON).
|
||||
* Estados com energia: C2 e D2.
|
||||
*
|
||||
* Nota: isto substitui a antiga interpretação “C1/C2”.
|
||||
*/
|
||||
bool evse_state_is_charging(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Returns the current EVSE state.
|
||||
*/
|
||||
evse_state_t evse_get_state(void);
|
||||
/**
|
||||
* @brief True se o EV pediu carga mas o relé ainda está OFF (C1/D1).
|
||||
*/
|
||||
bool evse_state_is_requesting(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Sets the current EVSE state and emits a change event if needed.
|
||||
*/
|
||||
void evse_set_state(evse_state_t state);
|
||||
/**
|
||||
* @brief True se há fluxo de energia (alias explícito para charging).
|
||||
*/
|
||||
bool evse_state_is_power_flowing(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief Converts the state enum into a human-readable string.
|
||||
*/
|
||||
const char* evse_state_to_str(evse_state_t state);
|
||||
/**
|
||||
* @brief True se o EV está fisicamente ligado (B1 e além).
|
||||
*/
|
||||
bool evse_state_is_plugged(evse_state_t state);
|
||||
|
||||
// ============================
|
||||
// State Evaluation Helpers
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* @brief True if EV is in an active session (B2, C1, C2).
|
||||
*/
|
||||
bool evse_state_is_session(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief True if EV is actively charging (C1, C2).
|
||||
*/
|
||||
bool evse_state_is_charging(evse_state_t state);
|
||||
|
||||
/**
|
||||
* @brief True if EV is physically plugged in (B1 and beyond).
|
||||
*/
|
||||
bool evse_state_is_plugged(evse_state_t state);
|
||||
|
||||
// ============================
|
||||
// Authorization Control
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* @brief Sets whether the EV is authorized to charge.
|
||||
*/
|
||||
void evse_state_set_authorized(bool authorized);
|
||||
|
||||
/**
|
||||
* @brief Gets whether the EV is currently authorized.
|
||||
*/
|
||||
bool evse_state_get_authorized(void);
|
||||
// Authorization Control
|
||||
void evse_state_set_authorized(bool authorized);
|
||||
bool evse_state_get_authorized(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user