new upgrade

This commit is contained in:
2025-12-21 23:28:26 +00:00
parent 82fa194bd8
commit 023644a887
99 changed files with 7457 additions and 7079 deletions

View File

@@ -3,5 +3,5 @@ set(srcs "src/auth_types.c" "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c"
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash driver esp_timer
REQUIRES esp_event evse ocpp evse_link)
PRIV_REQUIRES driver esp_timer
REQUIRES esp_event evse ocpp evse_link storage_service)

View File

@@ -4,19 +4,22 @@
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <esp_err.h>
#include <string.h>
#include <strings.h> // strcasecmp
#include <strings.h>
#include <stdio.h>
#include "wiegand_reader.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "storage_service.h"
#include "evse_link.h"
#include "evse_link_events.h"
#define MAX_TAGS 50
static const char *TAG = "Auth";
/* ===== Estado ===== */
@@ -25,162 +28,16 @@ static bool waiting_for_registration = false;
static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0;
static uint32_t s_next_req_id = 1;
static bool s_wiegand_started = false; // controla se o Wiegand já foi iniciado
static bool s_wiegand_started = false;
/* ===== NVS keys ===== */
/* ===== Storage keys ===== */
#define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count"
#define NVS_MODE_KEY "mode" // uint8_t
/* =========================
* NVS Persistence (tags)
* ========================= */
static void load_tags_from_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No stored tags in NVS (nvs_open: %s)", esp_err_to_name(err));
return;
}
uint8_t count = 0;
err = nvs_get_u8(handle, NVS_TAG_COUNT_KEY, &count);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No tag count key in NVS (nvs_get_u8: %s)", esp_err_to_name(err));
nvs_close(handle);
return;
}
tag_count = 0;
for (int i = 0; i < count && i < MAX_TAGS; i++)
{
char key[16];
char tag_buf[AUTH_TAG_MAX_LEN];
size_t len = sizeof(tag_buf);
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = nvs_get_str(handle, key, tag_buf, &len);
if (err == ESP_OK)
{
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
}
else
{
ESP_LOGW(TAG, "Failed to load tag %d from NVS (%s)", i, esp_err_to_name(err));
}
}
nvs_close(handle);
ESP_LOGI(TAG, "Loaded %d tags from NVS", tag_count);
}
static void save_tags_to_nvs(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS to save tags: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(handle, NVS_TAG_COUNT_KEY, tag_count);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(count) failed: %s", esp_err_to_name(err));
nvs_close(handle);
return;
}
for (int i = 0; i < tag_count; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = nvs_set_str(handle, key, valid_tags[i]);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_str(%s) failed: %s", key, esp_err_to_name(err));
nvs_close(handle);
return;
}
}
err = nvs_commit(handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed when saving tags: %s", esp_err_to_name(err));
nvs_close(handle);
return;
}
nvs_close(handle);
ESP_LOGI(TAG, "Tags saved to NVS (%d tags)", tag_count);
}
/* =========================
* NVS Persistence (mode)
* ========================= */
static void load_mode_from_nvs(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_OK)
{
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
err = nvs_get_u8(h, NVS_MODE_KEY, &u);
if (err == ESP_OK)
{
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
s_mode = (auth_mode_t)u;
}
else
{
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_get_u8: %s). Default OPEN", esp_err_to_name(err));
}
nvs_close(h);
}
else
{
ESP_LOGW(TAG, "No stored auth mode in NVS (nvs_open: %s). Default OPEN", esp_err_to_name(err));
}
ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode));
}
static void save_mode_to_nvs(auth_mode_t mode)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS to save auth mode: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
nvs_close(h);
return;
}
err = nvs_commit(h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed when saving mode: %s", esp_err_to_name(err));
nvs_close(h);
return;
}
nvs_close(h);
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
}
// timeout para operações sync do storage
#define STORAGE_TO pdMS_TO_TICKS(2000)
/* =========================
* Helpers
@@ -190,37 +47,155 @@ static bool is_tag_valid(const char *tag)
for (int i = 0; i < tag_count; i++)
{
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0)
{
return true;
}
}
return false;
}
/* =========================
* Storage Persistence (tags)
* ========================= */
static void load_tags_from_storage(void)
{
uint8_t count = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &count, STORAGE_TO);
if (err == ESP_ERR_NOT_FOUND)
{
ESP_LOGD(TAG, "No stored tags (count not found)");
tag_count = 0;
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Failed to read tag count (%s)", esp_err_to_name(err));
tag_count = 0;
return;
}
tag_count = 0;
for (int i = 0; i < (int)count && i < MAX_TAGS; i++)
{
char key[16];
char tag_buf[AUTH_TAG_MAX_LEN] = {0};
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
err = storage_get_str_sync(NVS_NAMESPACE, key, tag_buf, sizeof(tag_buf), STORAGE_TO);
if (err == ESP_OK)
{
if (tag_buf[0] != '\0')
{
strncpy(valid_tags[tag_count], tag_buf, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
}
}
else if (err == ESP_ERR_NOT_FOUND)
{
// pode acontecer se count estiver desfasado; ignora
continue;
}
else
{
ESP_LOGW(TAG, "Failed to load tag %d (%s)", i, esp_err_to_name(err));
}
}
ESP_LOGI(TAG, "Loaded %d tags from storage", tag_count);
}
static void save_tags_to_storage(void)
{
// ler count antigo (para apagar keys antigas se removemos tags)
uint8_t old_count = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, &old_count, STORAGE_TO);
if (err == ESP_ERR_NOT_FOUND)
old_count = 0;
// grava count + tags
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_TAG_COUNT_KEY, (uint8_t)tag_count);
for (int i = 0; i < tag_count; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
(void)storage_set_str_async(NVS_NAMESPACE, key, valid_tags[i]);
}
// se removemos tags: apagar chaves antigas
if (old_count > (uint8_t)tag_count)
{
for (int i = tag_count; i < (int)old_count && i < MAX_TAGS; i++)
{
char key[16];
snprintf(key, sizeof(key), "%s%d", NVS_TAG_PREFIX, i);
(void)storage_erase_key_async(NVS_NAMESPACE, key);
}
}
// opcional: forçar commit “já”
(void)storage_flush_async();
ESP_LOGD(TAG, "Tags saved to storage (%d tags)", tag_count);
}
/* =========================
* Storage Persistence (mode)
* ========================= */
static void load_mode_from_storage(void)
{
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_MODE_KEY, &u, STORAGE_TO);
if (err == ESP_OK)
{
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID)
s_mode = (auth_mode_t)u;
else
s_mode = AUTH_MODE_OPEN;
}
else if (err == ESP_ERR_NOT_FOUND)
{
s_mode = AUTH_MODE_OPEN;
ESP_LOGD(TAG, "No stored mode -> default OPEN");
}
else
{
s_mode = AUTH_MODE_OPEN;
ESP_LOGW(TAG, "Failed to read mode (%s) -> default OPEN", esp_err_to_name(err));
}
ESP_LOGI(TAG, "Loaded mode = %d (%s)", (int)s_mode, auth_mode_to_str(s_mode));
}
static void save_mode_to_storage(auth_mode_t mode)
{
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_MODE_KEY, (uint8_t)mode);
(void)storage_flush_async(); // opcional: commit mais rápido
ESP_LOGD(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
}
/* =========================
* Bridge: EVSE-Link -> AUTH (remote AUTH_GRANTED no slave)
* ========================= */
static void on_remote_auth_grant(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != EVSE_LINK_EVENTS || id != LINK_EVENT_REMOTE_AUTH_GRANTED || data == NULL)
{
return;
}
const evse_link_auth_grant_event_t *src = (const evse_link_auth_grant_event_t *)data;
// Só faz sentido em SLAVE; em MASTER este evento não deve aparecer
if (evse_link_get_mode() != EVSE_LINK_MODE_SLAVE)
{
return;
}
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, src->tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true;
ESP_LOGI(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);
ESP_LOGD(TAG, "Remote auth grant on SLAVE for tag=%s", ev.tag);
esp_err_t err = esp_event_post(
AUTH_EVENTS,
@@ -241,8 +216,11 @@ static void on_remote_auth_grant(void *arg, esp_event_base_t base, int32_t id, v
* ========================= */
void auth_init(void)
{
load_mode_from_nvs();
load_tags_from_nvs();
// garantir que o storage service está pronto
ESP_ERROR_CHECK(storage_service_init());
load_mode_from_storage();
load_tags_from_storage();
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand)
@@ -256,7 +234,7 @@ void auth_init(void)
ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
}
// Registar bridge para autorizações remotas vindas do EVSE-Link
// bridge EVSE-Link -> AUTH
{
esp_err_t err = esp_event_handler_register(
EVSE_LINK_EVENTS,
@@ -285,44 +263,34 @@ void auth_set_mode(auth_mode_t mode)
if (mode == s_mode)
{
ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
ESP_LOGD(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
return;
}
auth_mode_t old = s_mode;
s_mode = mode;
save_mode_to_nvs(mode);
save_mode_to_storage(mode);
bool need_wiegand = (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID);
if (need_wiegand && !s_wiegand_started)
{
ESP_LOGI(TAG, "Mode changed %s -> %s, starting Wiegand",
ESP_LOGD(TAG, "Mode changed %s -> %s, starting Wiegand",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
initWiegand();
s_wiegand_started = true;
}
else if (!need_wiegand && s_wiegand_started)
{
// Aqui poderias implementar um wiegand_deinit() se o driver o expuser.
ESP_LOGI(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
ESP_LOGD(TAG, "Mode changed %s -> %s, Wiegand remains started (no deinit implemented)",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
}
else
{
ESP_LOGI(TAG, "Mode changed %s -> %s, no change in Wiegand state",
ESP_LOGD(TAG, "Mode changed %s -> %s, no change in Wiegand state",
auth_mode_to_str(old), auth_mode_to_str(s_mode));
}
if (s_mode == AUTH_MODE_OPEN)
{
ESP_LOGI(TAG, "Mode set to OPEN");
}
else
{
ESP_LOGI(TAG, "Mode set to %s", auth_mode_to_str(s_mode));
}
auth_mode_event_data_t evt = {.mode = s_mode};
esp_event_post(AUTH_EVENTS, AUTH_EVENT_MODE_CHANGED, &evt, sizeof(evt), portMAX_DELAY);
}
@@ -338,15 +306,16 @@ bool auth_add_tag(const char *tag)
return false;
if (tag_count >= MAX_TAGS)
return false;
if (is_tag_valid(tag))
return true; // já existe
return true;
strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
tag_count++;
save_tags_to_nvs();
ESP_LOGI(TAG, "Tag added: %s", tag);
save_tags_to_storage();
ESP_LOGD(TAG, "Tag added: %s", tag);
return true;
}
@@ -366,8 +335,8 @@ bool auth_remove_tag(const char *tag)
}
tag_count--;
save_tags_to_nvs();
ESP_LOGI(TAG, "Tag removed: %s", tag);
save_tags_to_storage();
ESP_LOGD(TAG, "Tag removed: %s", tag);
return true;
}
}
@@ -383,11 +352,9 @@ bool auth_tag_exists(const char *tag)
void auth_list_tags(void)
{
ESP_LOGI(TAG, "Registered Tags (%d):", tag_count);
ESP_LOGD(TAG, "Registered Tags (%d):", tag_count);
for (int i = 0; i < tag_count; i++)
{
ESP_LOGI(TAG, "- %s", valid_tags[i]);
}
ESP_LOGD(TAG, "- %s", valid_tags[i]);
}
void auth_wait_for_tag_registration(void)
@@ -398,7 +365,7 @@ void auth_wait_for_tag_registration(void)
return;
}
waiting_for_registration = true;
ESP_LOGI(TAG, "Tag registration mode enabled.");
ESP_LOGD(TAG, "Tag registration mode enabled.");
}
void auth_process_tag(const char *tag)
@@ -412,11 +379,8 @@ void auth_process_tag(const char *tag)
switch (s_mode)
{
case AUTH_MODE_OPEN:
{
// Sem verificação; normalmente nem é necessário evento.
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
ESP_LOGD(TAG, "Mode OPEN: tag=%s (no verification)", tag);
break;
}
case AUTH_MODE_LOCAL_RFID:
{
@@ -430,9 +394,10 @@ void auth_process_tag(const char *tag)
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = true;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED,
&ev, sizeof(ev), portMAX_DELAY);
ESP_LOGI(TAG, "Tag registered: %s", tag);
ESP_LOGD(TAG, "Tag registered: %s", tag);
}
else
{
@@ -446,8 +411,9 @@ void auth_process_tag(const char *tag)
ev.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
ev.authorized = is_tag_valid(tag);
ESP_LOGI(TAG, "LOCAL tag %s: %s", tag,
ESP_LOGD(TAG, "LOCAL tag %s: %s", tag,
ev.authorized ? "AUTHORIZED" : "DENIED");
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED,
&ev, sizeof(ev), portMAX_DELAY);
break;
@@ -455,13 +421,14 @@ void auth_process_tag(const char *tag)
case AUTH_MODE_OCPP_RFID:
{
// Não decide localmente. Pede validação ao OCPP.
auth_tag_verify_event_t rq = {0};
strncpy(rq.tag, tag, AUTH_TAG_MAX_LEN - 1);
rq.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
rq.req_id = s_next_req_id++;
ESP_LOGI(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
ESP_LOGD(TAG, "OCPP VERIFY requested for tag=%s (req_id=%u)",
rq.tag, (unsigned)rq.req_id);
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY,
&rq, sizeof(rq), portMAX_DELAY);
break;

View File

@@ -301,7 +301,7 @@ void initWiegand(void)
xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL);
// Para testes, podes ativar o simulador:
//ESP_LOGI(TAG, "Inicializando Wiegand simulado");
//xTaskCreate(wiegand_sim_task, "WiegandSim",
// ESP_LOGI(TAG, "Inicializando Wiegand simulado");
// xTaskCreate(wiegand_sim_task, "WiegandSim",
// configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
}

View File

@@ -8,5 +8,5 @@ idf_component_register(
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
REQUIRES esp_event
PRIV_REQUIRES driver nvs_flash esp_timer evse
PRIV_REQUIRES driver esp_timer evse network
)

View File

@@ -398,7 +398,7 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int
if (base != NETWORK_EVENTS)
return;
ESP_LOGI(TAG, "Network event id=%d", (int)id);
ESP_LOGD(TAG, "Network event id=%d", (int)id);
buzzer_event_data_t evt = {0};

View File

@@ -2,5 +2,5 @@ set(srcs
"board_config.c")
idf_component_register(SRCS "${srcs}"
PRIV_REQUIRES nvs_flash
PRIV_REQUIRES
INCLUDE_DIRS "include")

View File

@@ -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
)

View File

@@ -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));
}

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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 ===

View File

@@ -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: 680 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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 ===

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -9,8 +9,8 @@ set(srcs
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES driver esp_timer nvs_flash
REQUIRES config evse loadbalancer)
PRIV_REQUIRES driver esp_timer
REQUIRES config evse loadbalancer storage_service)

View File

@@ -2,19 +2,23 @@
#include "evse_link.h"
#include "evse_link_framing.h"
#include "driver/uart.h"
#include "nvs.h"
#include "esp_log.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <stdbool.h>
#include <stdint.h>
#include "storage_service.h"
static const char *TAG = "evse_link";
// NVS keys
// Storage keys
#define _NVS_NAMESPACE "evse_link"
#define _NVS_MODE_KEY "mode"
#define _NVS_ID_KEY "self_id"
#define _NVS_ENABLED_KEY "enabled"
#define _KEY_MODE "mode"
#define _KEY_SELF_ID "self_id"
#define _KEY_ENABLED "enabled"
// UART parameters
#define UART_PORT UART_NUM_2
@@ -37,9 +41,7 @@ static void framing_rx_cb(uint8_t src, uint8_t dest,
{
ESP_LOGD(TAG, "framing_rx_cb: src=0x%02X dest=0x%02X len=%u", src, dest, len);
if (_rx_cb)
{
_rx_cb(src, dest, payload, len);
}
}
// Register protocol-level Rx callback
@@ -48,87 +50,117 @@ void evse_link_register_rx_cb(evse_link_rx_cb_t cb)
_rx_cb = cb;
}
// Load config from NVS
enum
{
EV_OK = ESP_OK
};
// Load config from storage_service (NVS-backed)
static void load_link_config(void)
{
nvs_handle_t handle;
if (nvs_open(_NVS_NAMESPACE, NVS_READONLY, &handle) != EV_OK)
{
ESP_LOGW(TAG, "NVS open failed, using defaults");
return;
}
uint8_t mode, id, en;
if (nvs_get_u8(handle, _NVS_MODE_KEY, &mode) == EV_OK &&
(mode == EVSE_LINK_MODE_MASTER || mode == EVSE_LINK_MODE_SLAVE))
{
_mode = (evse_link_mode_t)mode;
}
if (nvs_get_u8(handle, _NVS_ID_KEY, &id) == EV_OK)
{
_self_id = id;
}
if (nvs_get_u8(handle, _NVS_ENABLED_KEY, &en) == EV_OK)
{
_enabled = (en != 0);
}
nvs_close(handle);
}
uint8_t u8 = 0;
// Save config to NVS
static void save_link_config(void)
{
nvs_handle_t handle;
if (nvs_open(_NVS_NAMESPACE, NVS_READWRITE, &handle) == EV_OK)
// mode
esp_err_t err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_MODE, &u8, pdMS_TO_TICKS(500));
if (err == ESP_OK && (u8 == (uint8_t)EVSE_LINK_MODE_MASTER || u8 == (uint8_t)EVSE_LINK_MODE_SLAVE))
{
nvs_set_u8(handle, _NVS_MODE_KEY, (uint8_t)_mode);
nvs_set_u8(handle, _NVS_ID_KEY, _self_id);
nvs_set_u8(handle, _NVS_ENABLED_KEY, _enabled ? 1 : 0);
nvs_commit(handle);
nvs_close(handle);
_mode = (evse_link_mode_t)u8;
}
else
{
ESP_LOGE(TAG, "Failed to save NVS");
// default + persist
_mode = EVSE_LINK_MODE_MASTER;
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_MODE, (uint8_t)_mode);
ESP_LOGW(TAG, "Missing/invalid mode (%s) -> default MASTER (persisted async)",
esp_err_to_name(err));
}
// self_id
err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_SELF_ID, &u8, pdMS_TO_TICKS(500));
if (err == ESP_OK)
{
_self_id = u8;
}
else
{
_self_id = 0x01;
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_SELF_ID, _self_id);
ESP_LOGW(TAG, "Missing self_id (%s) -> default 0x%02X (persisted async)",
esp_err_to_name(err), _self_id);
}
// enabled
err = storage_get_u8_sync(_NVS_NAMESPACE, _KEY_ENABLED, &u8, pdMS_TO_TICKS(500));
if (err == ESP_OK && u8 <= 1)
{
_enabled = (u8 != 0);
}
else
{
_enabled = false;
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_ENABLED, 0);
ESP_LOGW(TAG, "Missing/invalid enabled (%s) -> default false (persisted async)",
esp_err_to_name(err));
}
}
// Save config to storage_service (debounced)
static void save_link_config(void)
{
// Debounced writes: não bloqueia e reduz desgaste
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_MODE, (uint8_t)_mode);
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_SELF_ID, _self_id);
(void)storage_set_u8_async(_NVS_NAMESPACE, _KEY_ENABLED, _enabled ? 1 : 0);
// opcional: se quiseres persistência imediata em configurações “críticas”
// (void)storage_flush_async();
}
// Getters/setters
void evse_link_set_mode(evse_link_mode_t m)
{
if (m != EVSE_LINK_MODE_MASTER && m != EVSE_LINK_MODE_SLAVE)
{
ESP_LOGW(TAG, "Invalid link mode: %d", (int)m);
return;
}
if (_mode == m)
return;
_mode = m;
save_link_config();
}
evse_link_mode_t evse_link_get_mode(void) { return _mode; }
void evse_link_set_self_id(uint8_t id)
{
if (_self_id == id)
return;
_self_id = id;
save_link_config();
}
uint8_t evse_link_get_self_id(void) { return _self_id; }
void evse_link_set_enabled(bool en)
{
if (_enabled == en)
return;
_enabled = en;
save_link_config();
}
bool evse_link_is_enabled(void) { return _enabled; }
// RX task: reads bytes from UART and feeds framing
static void evse_link_rx_task(void *arg)
{
(void)arg;
uint8_t buf[UART_RX_BUF_SIZE];
while (true)
{
int len = uart_read_bytes(UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(1000));
if (len > 0)
{
for (int i = 0; i < len; ++i)
{
evse_link_recv_byte(buf[i]);
}
}
}
}
@@ -136,11 +168,22 @@ static void evse_link_rx_task(void *arg)
// Initialize EVSE-Link component
void evse_link_init(void)
{
load_link_config();
// garante storage disponível
esp_err_t se = storage_service_init();
if (se != ESP_OK)
{
ESP_LOGE(TAG, "storage_service_init failed: %s (using defaults in RAM)", esp_err_to_name(se));
// segue com defaults em RAM
}
else
{
load_link_config();
}
ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d",
_mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S',
_self_id, _enabled);
if (!_enabled)
return;
@@ -149,17 +192,13 @@ void evse_link_init(void)
evse_link_framing_register_cb(framing_rx_cb);
// 2) start RX task
xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL);
xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 3, NULL);
// 3) delegate to master or slave
if (_mode == EVSE_LINK_MODE_MASTER)
{
evse_link_master_init();
}
else
{
evse_link_slave_init();
}
}
// Send a frame (delegates to framing module)
@@ -167,6 +206,7 @@ bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len)
{
if (!evse_link_is_enabled())
return false;
uint8_t src = evse_link_get_self_id();
return evse_link_framing_send(dest, src, payload, len);
}

View File

@@ -13,7 +13,7 @@
#include "evse_state.h"
#include "ledc_driver.h"
#define BLOCK_TIME pdMS_TO_TICKS(10)
#define BLOCK_TIME portMAX_DELAY
static const char *TAG = "led";
@@ -350,7 +350,7 @@ static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id,
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state);
ESP_LOGD(TAG, "EVSE State Changed: state=%d", evt->state);
// Atualiza o estado base
current_state_mode = evt->state;
@@ -378,7 +378,7 @@ static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int
const evse_session_event_data_t *evt =
(const evse_session_event_data_t *)data;
ESP_LOGI(TAG,
ESP_LOGD(TAG,
"EVSE Session Event: type=%d, id=%" PRIu32
", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d",
(int)evt->type,

View File

@@ -4,5 +4,4 @@ set(srcs
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash
REQUIRES esp_event esp_timer meter_manager evse)

View File

@@ -1,17 +1,26 @@
// components/loadbalancer/include/loadbalancer_events.h
#pragma once
#include "esp_event.h"
#include <stdint.h>
#include <stdbool.h>
#include "esp_timer.h"
#ifdef __cplusplus
extern "C" {
#endif
ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS);
typedef enum {
LOADBALANCER_EVENT_INIT,
LOADBALANCER_EVENT_INIT = 0,
LOADBALANCER_EVENT_STATE_CHANGED,
LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT,
// IMPORTANT: eventos separados e payloads diferentes
LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
LOADBALANCER_EVENT_SLAVE_STATUS
} loadbalancer_event_id_t;
@@ -20,18 +29,18 @@ typedef struct {
int64_t timestamp_us;
} loadbalancer_state_event_t;
// (opcional)
typedef struct {
float limit;
int64_t timestamp_us;
} loadbalancer_global_limit_event_t;
// MASTER: NÃO tem slave_id
typedef struct {
uint8_t slave_id;
uint16_t max_current;
int64_t timestamp_us;
} loadbalancer_master_limit_event_t;
// SLAVE: tem slave_id
typedef struct {
uint8_t slave_id;
uint16_t max_current;
@@ -39,9 +48,13 @@ typedef struct {
} loadbalancer_slave_limit_event_t;
typedef struct {
uint8_t slave_id; // ID do slave que reportou
bool charging; // Status de carregamento
float hw_max_current; // Limite máximo de corrente do hardware informado
float runtime_current; // Corrente atual de carregamento (A)
int64_t timestamp_us; // Momento em que o status foi coletado
} loadbalancer_slave_status_event_t;
uint8_t slave_id;
bool charging;
float hw_max_current;
float runtime_current;
int64_t timestamp_us;
} loadbalancer_slave_status_event_t;
#ifdef __cplusplus
}
#endif

View File

@@ -1,18 +1,25 @@
// components/loadbalancer/src/loadbalancer.c
#include "loadbalancer.h"
#include "loadbalancer_events.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "input_filter.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <string.h>
#include "meter_events.h"
#include "evse_events.h"
#include "math.h"
#include <inttypes.h> // Necessário para PRIu64
#include "storage_service.h"
#include <string.h>
#include <math.h>
#include <inttypes.h>
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -20,23 +27,18 @@
static const char *TAG = "loadbalancer";
// Limites configuráveis
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
#define MIN_GRID_CURRENT_LIMIT 6 // A
#define MAX_GRID_CURRENT_LIMIT 100 // A
#define MIN_CHARGING_CURRENT_LIMIT 6
#define MAX_CHARGING_CURRENT_LIMIT 32
#define MIN_GRID_CURRENT_LIMIT 6
#define MAX_GRID_CURRENT_LIMIT 100
// Pequena tolerância para considerar "sem margem"
#define AVAILABLE_EPS 1.0f
#define LB_SUSPEND_THRESHOLD (MIN_CHARGING_CURRENT_LIMIT - 1.0f)
#define LB_RESUME_THRESHOLD (MIN_CHARGING_CURRENT_LIMIT + 1.0f)
// Histerese de suspensão / retoma (em torno dos 6A)
#define LB_SUSPEND_THRESHOLD 5.0f // abaixo disto -> suspende
#define LB_RESUME_THRESHOLD 7.0f // acima disto -> pode retomar
#define GRID_METER_TIMEOUT_US (120LL * 1000000LL)
// Timeout para perda de medição de GRID (fail-safe)
#define GRID_METER_TIMEOUT_US (10LL * 1000000LL) // 30 segundos
// Parâmetros
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
static bool loadbalancer_enabled = false;
@@ -45,56 +47,66 @@ static float evse_current = 0.0f;
static input_filter_t grid_filter;
static input_filter_t evse_filter;
static int64_t last_grid_timestamp_us = 0; // última atualização de medição GRID
static int64_t last_grid_timestamp_us = 0;
#define MAX_SLAVES 255
#define CONNECTOR_COUNT (MAX_SLAVES + 1)
// Proteção simples de concorrência
static SemaphoreHandle_t lb_mutex = NULL;
// Estrutura unificada para master e slaves
typedef struct
{
uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave
uint8_t id; // para slaves: 0..254 ; para master não usado externamente
bool is_master;
bool charging;
float hw_max_current;
float runtime_current;
int64_t timestamp; // microssegundos (última métrica EVSE/slave)
int64_t timestamp;
bool online;
float assigned; // limite calculado pelo LB
int64_t started_us; // início da sessão de carregamento (para prioridade)
uint16_t last_limit; // último max_current enviado
bool suspended_by_lb; // flag de suspensão por LB (para histerese)
float assigned;
int64_t started_us;
uint16_t last_limit;
bool suspended_by_lb;
} evse_connector_t;
static evse_connector_t connectors[CONNECTOR_COUNT];
const int64_t METRICS_TIMEOUT_US = 60 * 1000000; // 60 segundos
const int64_t METRICS_TIMEOUT_US = 60LL * 1000000LL;
// Storage namespace/keys (mantém os mesmos nomes para compatibilidade)
#define LB_NS "loadbalancing"
#define LB_KEY_MAX_GRID "max_grid_curr" // u8
#define LB_KEY_ENABLED "enabled" // u8 (0/1)
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
static void input_filter_reset(input_filter_t *filter)
{
if (!filter)
return;
filter->value = 0.0f;
filter->initialized = 0;
}
// Helper: inicializa array de conectores
static void init_connectors(void)
{
// master em índice 0
connectors[0] = (evse_connector_t){
.id = 0xFF,
.id = 0,
.is_master = true,
.charging = false,
.hw_max_current = MAX_CHARGING_CURRENT_LIMIT,
.runtime_current = 0,
.timestamp = 0,
.online = false,
.online = true,
.assigned = 0.0f,
.started_us = 0,
.last_limit = 0,
.suspended_by_lb = false};
// slaves em 1..CONNECTOR_COUNT-1
for (int i = 1; i < CONNECTOR_COUNT; i++)
{
connectors[i] = (evse_connector_t){
.id = (uint8_t)(i - 1),
.id = (uint8_t)(i - 1), // slaves 0..254
.is_master = false,
.charging = false,
.hw_max_current = 0.0f,
@@ -108,26 +120,23 @@ static void init_connectors(void)
}
}
// --- Helpers ---
static void input_filter_reset(input_filter_t *filter)
{
filter->value = 0.0f;
}
// Callback de status de slave
static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id, void *data)
{
(void)handler_arg;
if (base != LOADBALANCER_EVENTS || id != LOADBALANCER_EVENT_SLAVE_STATUS || !data)
return;
const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data;
if (status->slave_id >= MAX_SLAVES)
{
ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id);
ESP_LOGW(TAG, "Invalid slave_id %u", (unsigned)status->slave_id);
return;
}
int idx = status->slave_id + 1; // slaves começam no índice 1
int idx = (int)status->slave_id + 1;
bool was_charging;
bool was_charging = false;
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
@@ -140,44 +149,40 @@ static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id
connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true;
// Se começou agora a carregar, marca início da sessão
if (status->charging && !was_charging)
{
connectors[idx].started_us = connectors[idx].timestamp;
connectors[idx].suspended_by_lb = false; // reset
connectors[idx].suspended_by_lb = false;
}
if (lb_mutex)
xSemaphoreGive(lb_mutex);
ESP_LOGI(TAG,
"Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA",
status->slave_id, status->charging,
"Slave %u status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA",
(unsigned)status->slave_id, status->charging,
status->hw_max_current, status->runtime_current);
}
static void on_evse_config_event(void *handler_arg,
esp_event_base_t base,
int32_t id,
void *event_data)
static void on_evse_config_event(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data)
{
const evse_config_event_data_t *evt = (const evse_config_event_data_t *)event_data;
(void)handler_arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_CONFIG_UPDATED || !event_data)
return;
int idx = 0; // MASTER INDICE 0
const evse_config_event_data_t *evt = (const evse_config_event_data_t *)event_data;
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
connectors[idx].charging = evt->charging;
connectors[idx].hw_max_current = evt->hw_max_current;
connectors[idx].runtime_current = evt->runtime_current;
connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true;
connectors[0].charging = evt->charging;
connectors[0].hw_max_current = evt->hw_max_current;
connectors[0].runtime_current = evt->runtime_current;
connectors[0].timestamp = esp_timer_get_time();
connectors[0].online = true;
if (!evt->charging)
{
connectors[idx].suspended_by_lb = false;
}
connectors[0].suspended_by_lb = false;
if (lb_mutex)
xSemaphoreGive(lb_mutex);
@@ -186,30 +191,25 @@ static void on_evse_config_event(void *handler_arg,
evt->charging, evt->hw_max_current, evt->runtime_current);
}
// --- Handlers de eventos externos ---
static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data)
{
if (id != METER_EVENT_DATA_READY || event_data == NULL)
(void)handler_arg;
(void)base;
if (id != METER_EVENT_DATA_READY || !event_data)
return;
const meter_event_data_t *evt = (const meter_event_data_t *)event_data;
float max_irms = evt->irms[0];
for (int i = 1; i < 3; ++i)
{
if (evt->irms[i] > max_irms)
{
max_irms = evt->irms[i];
}
}
float max_vrms = evt->vrms[0];
for (int i = 1; i < 3; ++i)
{
if (evt->vrms[i] > max_vrms)
{
max_vrms = evt->vrms[i];
}
}
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
@@ -218,28 +218,28 @@ static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t
{
grid_current = input_filter_update(&grid_filter, max_irms);
last_grid_timestamp_us = esp_timer_get_time();
ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current);
ESP_LOGI(TAG, "GRID VRMS: %.2f V", max_vrms);
ESP_LOGD(TAG, "GRID IRMS (filtered): %.2f A VRMS: %.2f V", grid_current, max_vrms);
}
else if (evt->source && strcmp(evt->source, "EVSE") == 0)
{
evse_current = input_filter_update(&evse_filter, max_irms);
ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
ESP_LOGD(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
}
else
{
ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source);
ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source ? evt->source : "(null)");
}
if (lb_mutex)
xSemaphoreGive(lb_mutex);
}
static void loadbalancer_evse_event_handler(void *handler_arg,
esp_event_base_t base,
int32_t id,
void *event_data)
static void loadbalancer_evse_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data)
{
(void)handler_arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || !event_data)
return;
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data;
if (lb_mutex)
@@ -249,17 +249,13 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
{
case EVSE_STATE_EVENT_IDLE:
case EVSE_STATE_EVENT_WAITING:
ESP_LOGI(TAG, "Local EVSE is %s - vehicle %sconnected / not charging",
evt->state == EVSE_STATE_EVENT_IDLE ? "IDLE" : "WAITING",
evt->state == EVSE_STATE_EVENT_IDLE ? "dis" : "");
connectors[0].charging = false;
connectors[0].online = true; // master está sempre online
connectors[0].online = true;
connectors[0].suspended_by_lb = false;
break;
case EVSE_STATE_EVENT_CHARGING:
{
ESP_LOGI(TAG, "Local EVSE is CHARGING - resetting filters");
grid_current = 0.0f;
evse_current = 0.0f;
input_filter_reset(&grid_filter);
@@ -280,14 +276,12 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
}
case EVSE_STATE_EVENT_FAULT:
ESP_LOGW(TAG, "Local EVSE is in FAULT state - disabling load balancing temporarily");
connectors[0].charging = false;
connectors[0].online = true; // EVSE está online mas com falha
connectors[0].online = true;
connectors[0].suspended_by_lb = false;
break;
default:
ESP_LOGW(TAG, "Unknown EVSE state: %d", evt->state);
break;
}
@@ -295,86 +289,110 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
xSemaphoreGive(lb_mutex);
}
// --- Config persistência ---
static esp_err_t loadbalancer_load_config()
// --------- Config load/save via storage_service ---------
static esp_err_t loadbalancer_load_config(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("loadbalancing", NVS_READWRITE, &handle);
if (err != ESP_OK)
return err;
// garante storage iniciado
esp_err_t se = storage_service_init();
if (se != ESP_OK)
return se;
bool needs_commit = false;
uint8_t temp_u8;
esp_err_t err;
bool needs_flush = false;
err = nvs_get_u8(handle, "max_grid_curr", &temp_u8);
uint8_t temp_u8 = 0;
// max_grid_curr
err = storage_get_u8_sync(LB_NS, LB_KEY_MAX_GRID, &temp_u8, TO_TICKS_MS(800));
if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
{
max_grid_current = temp_u8;
}
else
{
max_grid_current = MAX_GRID_CURRENT_LIMIT;
nvs_set_u8(handle, "max_grid_curr", max_grid_current);
needs_commit = true;
(void)storage_set_u8_async(LB_NS, LB_KEY_MAX_GRID, max_grid_current);
needs_flush = true;
ESP_LOGW(TAG, "Invalid/missing max_grid_curr (%s) -> default=%u (persisted)",
esp_err_to_name(err), (unsigned)max_grid_current);
}
err = nvs_get_u8(handle, "enabled", &temp_u8);
// enabled
err = storage_get_u8_sync(LB_NS, LB_KEY_ENABLED, &temp_u8, TO_TICKS_MS(800));
if (err == ESP_OK && temp_u8 <= 1)
{
loadbalancer_enabled = (temp_u8 != 0);
}
else
{
loadbalancer_enabled = false;
nvs_set_u8(handle, "enabled", 0);
needs_commit = true;
(void)storage_set_u8_async(LB_NS, LB_KEY_ENABLED, 0);
needs_flush = true;
ESP_LOGW(TAG, "Invalid/missing enabled (%s) -> default=false (persisted)",
esp_err_to_name(err));
}
if (needs_flush)
{
// determinístico no boot
(void)storage_flush_sync(TO_TICKS_MS(2000));
}
if (needs_commit)
nvs_commit(handle);
nvs_close(handle);
return ESP_OK;
}
// --- API ---
static void persist_lb_enabled(bool enabled)
{
(void)storage_set_u8_async(LB_NS, LB_KEY_ENABLED, enabled ? 1 : 0);
// debounced por defeito
}
static void persist_max_grid_current(uint8_t value)
{
(void)storage_set_u8_async(LB_NS, LB_KEY_MAX_GRID, value);
// debounced por defeito
}
void loadbalancer_set_enabled(bool enabled)
{
nvs_handle_t handle;
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK)
// Só persiste/propaga se mudou
if (enabled == loadbalancer_enabled)
{
nvs_set_u8(handle, "enabled", enabled ? 1 : 0);
nvs_commit(handle);
nvs_close(handle);
return;
}
loadbalancer_enabled = enabled;
persist_lb_enabled(enabled);
loadbalancer_state_event_t evt = {.enabled = enabled, .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
&evt, sizeof(evt), portMAX_DELAY);
(void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
&evt, sizeof(evt), portMAX_DELAY);
}
esp_err_t load_balancing_set_max_grid_current(uint8_t value)
{
if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT)
return ESP_ERR_INVALID_ARG;
nvs_handle_t handle;
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) != ESP_OK)
return ESP_FAIL;
nvs_set_u8(handle, "max_grid_curr", value);
nvs_commit(handle);
nvs_close(handle);
// Só persiste se mudou
if (value == max_grid_current)
{
return ESP_OK;
}
max_grid_current = value;
persist_max_grid_current(value);
return ESP_OK;
}
uint8_t load_balancing_get_max_grid_current(void)
{
return max_grid_current;
}
uint8_t load_balancing_get_max_grid_current(void) { return max_grid_current; }
bool loadbalancer_is_enabled(void) { return loadbalancer_enabled; }
bool loadbalancer_is_enabled(void)
{
return loadbalancer_enabled;
}
// --- Task principal ---
void loadbalancer_task(void *param)
{
(void)param;
while (true)
{
if (!loadbalancer_is_enabled())
@@ -387,110 +405,66 @@ void loadbalancer_task(void *param)
int active_cnt = 0;
int64_t now = esp_timer_get_time();
// --- Atualiza estado online e conta ativos ---
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
for (int i = 0; i < CONNECTOR_COUNT; i++)
{
// --- Master nunca pode ficar offline ---
if (connectors[i].is_master)
{
connectors[i].online = true;
ESP_LOGI(TAG, "Connector[%d] ONLINE (MASTER, charging=%d, hw_max_current=%.1f)",
i, connectors[i].charging, connectors[i].hw_max_current);
if (connectors[i].charging)
{
idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
}
continue;
}
// --- Ignora conectores já marcados como offline ---
if (!connectors[i].online)
{
continue;
}
// --- Timeout de heartbeat para escravos ---
if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US)
{
connectors[i].online = false;
ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)",
i, connectors[i].charging, (long long)(now - connectors[i].timestamp));
continue;
}
ESP_LOGI(TAG, "Connector[%d] ONLINE (charging=%d, hw_max_current=%.1f, timestamp_diff=%lld us)",
i, connectors[i].charging, connectors[i].hw_max_current,
(long long)(now - connectors[i].timestamp));
if (connectors[i].charging)
{
idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
}
}
// snapshot de grid_current e last_grid_timestamp sob mutex
float grid_snapshot = grid_current;
int64_t last_grid_ts_snapshot = last_grid_timestamp_us;
if (lb_mutex)
xSemaphoreGive(lb_mutex);
ESP_LOGI(TAG, "Active connectors: %d", active_cnt);
if (active_cnt == 0)
{
vTaskDelay(pdMS_TO_TICKS(5000));
continue;
}
// --- Verifica timeout de medição de GRID (fail-safe) ---
bool meter_timeout = (last_grid_ts_snapshot == 0 ||
(now - last_grid_ts_snapshot) > GRID_METER_TIMEOUT_US);
if (meter_timeout)
{
ESP_LOGW(TAG,
"GRID meter timeout (last update=%lld us ago). Applying fail-safe limits (<=%dA).",
(long long)(now - last_grid_ts_snapshot), MIN_CHARGING_CURRENT_LIMIT);
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
// Fail-safe: limitar cada EV ao mínimo permitido (6A) ou menos, nunca aumentar
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
float cur = connectors[i].runtime_current;
if (cur > MIN_CHARGING_CURRENT_LIMIT)
connectors[i].assigned = (float)MIN_CHARGING_CURRENT_LIMIT;
else
connectors[i].assigned = cur;
connectors[i].assigned = (cur > MIN_CHARGING_CURRENT_LIMIT) ? (float)MIN_CHARGING_CURRENT_LIMIT : cur;
}
if (lb_mutex)
xSemaphoreGive(lb_mutex);
goto publish_limits;
}
// --- Calcula corrente disponível (headroom global) ---
float available = (float)max_grid_current - grid_snapshot;
ESP_LOGI(TAG, "LB raw headroom: max_grid=%uA, grid_current=%.1fA, available=%.2fA",
max_grid_current, grid_snapshot, available);
// ==========================
// ZONA A: overload significativo -> reduzir correntes (throttling)
// ==========================
if (available < -AVAILABLE_EPS)
{
ESP_LOGW(TAG, "Overload: grid=%.1fA, max=%.1fA (available=%.2fA) -> throttling",
grid_snapshot, (float)max_grid_current, available);
float factor = ((float)max_grid_current) / grid_snapshot;
if (factor < 0.0f)
factor = 0.0f;
@@ -499,89 +473,53 @@ void loadbalancer_task(void *param)
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
connectors[i].assigned = connectors[i].runtime_current * factor;
ESP_LOGI(TAG,
"Connector[%d] overload throttling: runtime=%.1fA -> assigned=%.1fA",
i, connectors[i].runtime_current, connectors[i].assigned);
}
if (lb_mutex)
xSemaphoreGive(lb_mutex);
}
// ==========================
// ZONA B: sem margem prática -> manter correntes atuais como limites
// ==========================
else if (fabsf(available) <= AVAILABLE_EPS)
{
ESP_LOGI(TAG,
"No effective headroom: grid=%.1fA, max=%.1fA (available=%.2fA). Keeping current as limit.",
grid_snapshot, (float)max_grid_current, available);
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
connectors[i].assigned = connectors[i].runtime_current;
ESP_LOGI(TAG,
"Connector[%d] keep runtime as limit: assigned=%.1fA",
i, connectors[i].assigned);
}
if (lb_mutex)
xSemaphoreGive(lb_mutex);
}
// ==========================
// ZONA C: há margem positiva -> garantir mínimo e depois water-filling SOBRE assigned
// ==========================
else // available > AVAILABLE_EPS
else
{
if (available > max_grid_current)
{
available = (float)max_grid_current;
}
ESP_LOGI(TAG, "LB Calc (zone C): available=%.1fA, active_connectors=%d",
available, active_cnt);
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
// 1) Ordenar conectores ativos por started_us (mais antigos primeiro)
// ordenar por started_us
for (int a = 0; a < active_cnt - 1; a++)
{
for (int b = 0; b < active_cnt - 1 - a; b++)
{
int i1 = idxs[b];
int i2 = idxs[b + 1];
if (connectors[i1].started_us > connectors[i2].started_us)
if (connectors[idxs[b]].started_us > connectors[idxs[b + 1]].started_us)
{
int tmp = idxs[b];
idxs[b] = idxs[b + 1];
idxs[b + 1] = tmp;
}
}
}
// Inicialmente: assigned = runtime_current
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
connectors[i].assigned = connectors[i].runtime_current;
}
connectors[idxs[k]].assigned = connectors[idxs[k]].runtime_current;
float remaining = available; // margem extra total
float remaining = available;
// 2) FASE 1: tentar garantir pelo menos 6A (ou hw_max, se menor) aos mais antigos
// fase 1 min 6A
for (int k = 0; k < active_cnt && remaining > 0.0f; k++)
{
int i = idxs[k];
float current = connectors[i].runtime_current;
float hw_max = connectors[i].hw_max_current;
@@ -590,10 +528,7 @@ void loadbalancer_task(void *param)
target_min = hw_max;
if (current >= target_min)
{
connectors[i].assigned = current;
continue;
}
float delta = target_min - current;
if (delta <= remaining)
@@ -601,37 +536,25 @@ void loadbalancer_task(void *param)
connectors[i].assigned = current + delta;
remaining -= delta;
}
else
{
connectors[i].assigned = current;
}
}
// 3) FASE 2: "last in, first cut" -> cortar quem ficou abaixo do mínimo começando pelos mais recentes
// fase 2 suspender recentes abaixo min
for (int k = active_cnt - 1; k >= 0; k--)
{
int i = idxs[k];
if (connectors[i].assigned >= MIN_CHARGING_CURRENT_LIMIT)
{
continue;
}
ESP_LOGI(TAG, "Connector[%d] below min after phase1 (assigned=%.1fA) -> suspending (0A)",
i, connectors[i].assigned);
connectors[i].assigned = 0.0f;
connectors[i].suspended_by_lb = true;
}
// 4) FASE 3: se ainda sobrar margem, distribuir extra por cima dos que ficaram ON (assigned > 0)
// fase 3 distribuir extra
if (remaining > AVAILABLE_EPS)
{
int on_cnt = 0;
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
if (connectors[i].assigned > 0.0f)
if (connectors[idxs[k]].assigned > 0.0f)
on_cnt++;
}
float extra_remaining = remaining;
int extra_cnt = on_cnt;
@@ -639,10 +562,8 @@ void loadbalancer_task(void *param)
for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k];
if (connectors[i].assigned <= 0.0f)
continue;
if (extra_cnt <= 0 || extra_remaining <= 0.0f)
break;
@@ -666,7 +587,6 @@ void loadbalancer_task(void *param)
for (int m = k; m < active_cnt; m++)
{
int j = idxs[m];
if (connectors[j].assigned <= 0.0f)
continue;
@@ -686,7 +606,7 @@ void loadbalancer_task(void *param)
xSemaphoreGive(lb_mutex);
}
// --- Publicação de limites / suspensão com histerese ---
publish_limits:
if (lb_mutex)
xSemaphoreTake(lb_mutex, portMAX_DELAY);
@@ -696,10 +616,8 @@ void loadbalancer_task(void *param)
float assigned = connectors[i].assigned;
float effective = assigned;
// Histerese de suspensão / retoma
if (connectors[i].suspended_by_lb)
{
// Está suspenso: só retoma se limite calculado for >= limiar de retoma
if (assigned >= LB_RESUME_THRESHOLD)
{
effective = assigned;
@@ -712,7 +630,6 @@ void loadbalancer_task(void *param)
}
else
{
// Ainda não está suspenso: só suspende se ficar claramente abaixo do limiar
if (assigned > 0.0f && assigned < LB_SUSPEND_THRESHOLD)
{
effective = 0.0f;
@@ -720,48 +637,40 @@ void loadbalancer_task(void *param)
}
}
uint16_t max_cur;
if (effective <= 0.0f)
{
max_cur = 0;
}
else
{
max_cur = (uint16_t)MIN(effective, (float)MAX_CHARGING_CURRENT_LIMIT);
}
uint16_t max_cur = (effective <= 0.0f) ? 0 : (uint16_t)MIN(effective, (float)MAX_CHARGING_CURRENT_LIMIT);
// Evita flapping de comandos: só envia se o limite mudou
if (connectors[i].last_limit == max_cur)
{
continue; // sem alteração
}
continue;
connectors[i].last_limit = max_cur;
// sair do mutex durante o post
if (lb_mutex)
xSemaphoreGive(lb_mutex);
if (connectors[i].is_master)
{
loadbalancer_master_limit_event_t master_evt = {
.slave_id = connectors[i].id,
loadbalancer_master_limit_event_t m = {
.max_current = max_cur,
.timestamp_us = now};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
&master_evt, sizeof(master_evt), portMAX_DELAY);
ESP_LOGI(TAG, "Master limit changed -> %.1f A (assigned=%.2f A)",
(float)max_cur, assigned);
(void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
&m, sizeof(m), portMAX_DELAY);
}
else
{
loadbalancer_slave_limit_event_t slave_evt = {
.slave_id = connectors[i].id,
.max_current = max_cur,
.timestamp_us = now};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
&slave_evt, sizeof(slave_evt), portMAX_DELAY);
ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (assigned=%.2f A)",
connectors[i].id, (float)max_cur, assigned);
uint8_t sid = connectors[i].id;
if (sid < MAX_SLAVES)
{
loadbalancer_slave_limit_event_t s = {
.slave_id = sid,
.max_current = max_cur,
.timestamp_us = now};
(void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
&s, sizeof(s), portMAX_DELAY);
}
else
{
ESP_LOGW(TAG, "Skipping publish: invalid slave id=%u", (unsigned)sid);
}
}
if (lb_mutex)
@@ -775,36 +684,31 @@ void loadbalancer_task(void *param)
}
}
// --- Init ---
void loadbalancer_init(void)
{
if (loadbalancer_load_config() != ESP_OK)
ESP_LOGW(TAG, "Failed to load/init config. Using defaults.");
lb_mutex = xSemaphoreCreateMutex();
if (lb_mutex == NULL)
{
if (!lb_mutex)
ESP_LOGE(TAG, "Failed to create loadbalancer mutex");
}
init_connectors();
input_filter_init(&grid_filter, 0.3f);
input_filter_init(&evse_filter, 0.3f);
if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS)
if (xTaskCreate(loadbalancer_task, "loadbalancer", 8192, NULL, 4, NULL) != pdPASS)
ESP_LOGE(TAG, "Failed to create loadbalancer task");
loadbalancer_state_event_t evt = {.enabled = loadbalancer_enabled, .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
(void)esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY,
&loadbalancer_meter_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
&loadbalancer_evse_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_CONFIG_UPDATED,
&on_evse_config_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_STATUS,
&on_slave_status, NULL));
}
}

View File

@@ -3,56 +3,45 @@
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#define LOGGER_SERIAL_BIT BIT0
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Logger event group LOGGER_SERIAL_BIT
*
*/
extern EventGroupHandle_t logger_event_group;
#define LOGGER_SERIAL_BIT BIT0
/**
* @brief Initialize logger
*
*/
void logger_init(void);
extern EventGroupHandle_t logger_event_group;
/**
* @brief Print
*
* @param str
*/
void logger_print(const char* str);
void logger_init(void);
/**
* @brief Print va
*
* @param str
* @param l
* @return int
*/
int logger_vprintf(const char* str, va_list l);
void logger_print(const char *str);
/**
* @brief Get entries count
*
* @return uint16_t
*/
uint16_t logger_count(void);
int logger_vprintf(const char *fmt, va_list args);
/**
* @brief Read line from index, set index for reading next entry
*
* @param index
* @param str
* @param v
* @return true When has next entry
* @return false When no entry left
*/
bool logger_read(uint16_t *index, char **str, uint16_t* len);
uint16_t logger_count(void);
// opcional: quantas mensagens foram dropadas por contenção de mutex
uint32_t logger_dropped_count(void);
#endif /* LOGGER_H_ */
/**
* ⚠️ API antiga (não recomendada): devolve ponteiro interno.
* Pode ficar inválido se houver novas escritas/rotação.
*/
bool logger_read(uint16_t *index, char **str, uint16_t *len);
/**
* ✅ API recomendada: copia a entrada para buffer do caller (safe).
* out é sempre terminado com '\0' (se out_sz > 0).
*/
bool logger_read_copy(uint16_t *index, char *out, uint16_t out_sz, uint16_t *out_len);
#ifdef __cplusplus
}
#endif
#endif /* LOGGER_H_ */

View File

@@ -4,21 +4,31 @@
#include <stdint.h>
#include <stdbool.h>
typedef struct {
uint16_t size;
uint16_t count;
uint8_t* data;
uint8_t* append;
} output_buffer_t;
#ifdef __cplusplus
extern "C"
{
#endif
output_buffer_t* output_buffer_create(uint16_t size);
typedef struct
{
uint16_t size;
uint16_t count;
uint8_t *data;
uint8_t *append;
} output_buffer_t;
void output_buffer_delete(output_buffer_t* buffer);
output_buffer_t *output_buffer_create(uint16_t size);
void output_buffer_append_buf(output_buffer_t* buffer, const char* buf, uint16_t len);
void output_buffer_delete(output_buffer_t *buffer);
void output_buffer_append_str(output_buffer_t* buffer, const char* str);
void output_buffer_append_buf(output_buffer_t *buffer, const char *buf, uint16_t len);
bool output_buffer_read(output_buffer_t* buffer, uint16_t *index, char **str, uint16_t* len);
void output_buffer_append_str(output_buffer_t *buffer, const char *str);
#endif /* OUTPUT_BUFFER_H_ */
bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len);
#ifdef __cplusplus
}
#endif
#endif /* OUTPUT_BUFFER_H_ */

View File

@@ -1,66 +1,155 @@
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "logger.h"
#include "output_buffer.h"
#define LOG_BUFFER_SIZE 6096 //4096
#define MAX_LOG_SIZE 512
#define LOG_BUFFER_SIZE 6096 // tamanho total do buffer circular
#define MAX_LOG_SIZE 256 // ✅ reduzir stack/CPU; era 512
static SemaphoreHandle_t mutex;
static output_buffer_t * buffer = NULL;
static SemaphoreHandle_t mutex = NULL;
static output_buffer_t *buffer = NULL;
EventGroupHandle_t logger_event_group = NULL;
// opcional: contador de mensagens dropadas quando mutex está ocupado
static volatile uint32_t s_dropped = 0;
void logger_init(void)
{
// Permitir múltiplas chamadas seguras
if (mutex != NULL)
{
return;
}
mutex = xSemaphoreCreateMutex();
configASSERT(mutex != NULL);
logger_event_group = xEventGroupCreate();
configASSERT(logger_event_group != NULL);
buffer = output_buffer_create(LOG_BUFFER_SIZE);
configASSERT(buffer != NULL);
}
uint16_t logger_count(void)
{
return buffer->count;
if (!mutex || !buffer)
{
return 0;
}
// ✅ não bloquear para sempre (mas aqui pode bloquear sem stress)
xSemaphoreTake(mutex, portMAX_DELAY);
uint16_t c = buffer->count;
xSemaphoreGive(mutex);
return c;
}
void logger_print(const char* str)
uint32_t logger_dropped_count(void)
{
xSemaphoreTake(mutex, portMAX_DELAY);
return s_dropped;
}
void logger_print(const char *str)
{
if (!str || !mutex || !buffer)
{
return;
}
// Limitar comprimento para evitar entradas enormes
size_t len = strlen(str);
if (len > (MAX_LOG_SIZE - 1))
{
len = MAX_LOG_SIZE - 1;
}
// ✅ NÃO bloquear aqui: se o mutex estiver ocupado, dropa
if (xSemaphoreTake(mutex, 0) != pdTRUE)
{
s_dropped++;
return;
}
output_buffer_append_buf(buffer, str, (uint16_t)len);
xEventGroupSetBits(logger_event_group, LOGGER_SERIAL_BIT);
output_buffer_append_str(buffer, str);
xEventGroupSetBits(logger_event_group, 0xFF);
xSemaphoreGive(mutex);
}
int logger_vprintf(const char* str, va_list l)
int logger_vprintf(const char *fmt, va_list args)
{
char log_buf[MAX_LOG_SIZE];
#ifdef CONFIG_ESP_CONSOLE_UART
vprintf(str, l);
// Duplicar va_list para ecoar na UART sem consumir o original
va_list args_copy;
va_copy(args_copy, args);
vprintf(fmt, args_copy);
va_end(args_copy);
#endif
xSemaphoreTake(mutex, portMAX_DELAY);
// Se ainda não inicializado, apenas formatar para devolver comprimento
if (!mutex || !buffer)
{
int len = vsnprintf(log_buf, MAX_LOG_SIZE, fmt, args);
if (len < 0)
{
len = 0;
}
else if (len >= MAX_LOG_SIZE)
{
len = MAX_LOG_SIZE - 1;
}
return len;
}
static char log[MAX_LOG_SIZE];
int len = vsnprintf(log, MAX_LOG_SIZE, str, l);
int len = vsnprintf(log_buf, MAX_LOG_SIZE, fmt, args);
if (len < 0)
{
len = 0;
}
else if (len >= MAX_LOG_SIZE)
{
len = MAX_LOG_SIZE - 1;
}
output_buffer_append_buf(buffer, log, len);
xEventGroupSetBits(logger_event_group, 0xFF);
// ✅ NÃO bloquear o sistema (sys_evt/httpd/wifi/etc) por causa de log
if (xSemaphoreTake(mutex, 0) != pdTRUE)
{
s_dropped++;
return len;
}
output_buffer_append_buf(buffer, log_buf, (uint16_t)len);
xEventGroupSetBits(logger_event_group, LOGGER_SERIAL_BIT);
xSemaphoreGive(mutex);
return len;
}
bool logger_read(uint16_t* index, char** str, uint16_t* len)
/**
* ⚠️ API antiga: devolve ponteiro interno do buffer.
* Só é segura se o caller COPIAR imediatamente e garantir que não há novas escritas.
* Recomendo usar logger_read_copy().
*/
bool logger_read(uint16_t *index, char **str, uint16_t *len)
{
if (!mutex || !buffer || !index || !str || !len)
{
return false;
}
xSemaphoreTake(mutex, portMAX_DELAY);
bool has_next = output_buffer_read(buffer, index, str, len);
@@ -68,4 +157,36 @@ bool logger_read(uint16_t* index, char** str, uint16_t* len)
xSemaphoreGive(mutex);
return has_next;
}
}
// ✅ API segura: copia a linha para buffer do caller (evita ponteiro ficar inválido após rotação)
bool logger_read_copy(uint16_t *index, char *out, uint16_t out_sz, uint16_t *out_len)
{
if (!mutex || !buffer || !index || !out || out_sz == 0)
{
return false;
}
xSemaphoreTake(mutex, portMAX_DELAY);
char *ptr = NULL;
uint16_t len = 0;
bool ok = output_buffer_read(buffer, index, &ptr, &len);
if (!ok)
{
xSemaphoreGive(mutex);
return false;
}
uint16_t n = (len < (out_sz - 1)) ? len : (out_sz - 1);
memcpy(out, ptr, n);
out[n] = '\0';
if (out_len)
{
*out_len = n;
}
xSemaphoreGive(mutex);
return true;
}

View File

@@ -1,86 +1,217 @@
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#include "output_buffer.h"
output_buffer_t* output_buffer_create(uint16_t size)
output_buffer_t *output_buffer_create(uint16_t size)
{
output_buffer_t* buffer = (output_buffer_t*)malloc(sizeof(output_buffer_t));
if (size == 0)
{
return NULL;
}
output_buffer_t *buffer = (output_buffer_t *)malloc(sizeof(output_buffer_t));
if (!buffer)
{
return NULL;
}
buffer->data = (uint8_t *)malloc((size_t)size);
if (!buffer->data)
{
free(buffer);
return NULL;
}
buffer->size = size;
buffer->count = 0;
buffer->data = (uint8_t*)malloc(sizeof(uint8_t) * size);
buffer->append = buffer->data;
return buffer;
}
void output_buffer_delete(output_buffer_t* buffer)
void output_buffer_delete(output_buffer_t *buffer)
{
free((void*)buffer->data);
free((void*)buffer);
if (!buffer)
{
return;
}
if (buffer->data)
{
free((void *)buffer->data);
buffer->data = NULL;
}
free((void *)buffer);
}
void output_buffer_append_buf(output_buffer_t* buffer, const char* str, uint16_t len)
void output_buffer_append_buf(output_buffer_t *buffer, const char *str, uint16_t len)
{
if (((buffer->append - buffer->data) + sizeof(uint16_t) + len) >= buffer->size) {
//rotate buffer
uint8_t* pos = buffer->data;
if (!buffer || !buffer->data || !str || len == 0)
{
return;
}
// Garantir que nunca escrevemos entradas absurdamente grandes
if (len > buffer->size / 2)
{
// Tamanho de entrada demasiado grande para a lógica de rotação;
// corta-a para caber de forma segura.
len = buffer->size / 2;
}
size_t used = (size_t)(buffer->append - buffer->data);
if (used > buffer->size)
{
// Estado incoerente: reset defensivo
buffer->append = buffer->data;
buffer->count = 0;
used = 0;
}
// Se não couber mais esta entrada, rodar o buffer
if (used + sizeof(uint16_t) + len > buffer->size)
{
uint8_t *pos = buffer->data;
uint16_t rotate_count = 0;
while ((pos - buffer->data) < buffer->size / 2) {
//seek first half
uint8_t *end = buffer->data + buffer->size;
// Avança entradas até aproximadamente metade do buffer
while ((pos + sizeof(uint16_t)) < end &&
(size_t)(pos - buffer->data) < buffer->size / 2)
{
uint16_t entry_len;
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t));
pos += entry_len + sizeof(uint16_t);
memcpy(&entry_len, pos, sizeof(uint16_t));
// Sanitizar entry_len para evitar corrupções
if (entry_len == 0 || entry_len > buffer->size)
{
// Corrompido → reset defensivo
pos = buffer->data;
rotate_count = 0;
buffer->count = 0;
break;
}
if (pos + sizeof(uint16_t) + entry_len > end)
{
// Entrada incompleta na cauda → para por aqui
break;
}
pos += sizeof(uint16_t) + entry_len;
rotate_count++;
}
memmove((void*)buffer->data, (void*)pos, buffer->size - (pos - buffer->data));
buffer->count -= rotate_count;
buffer->append -= (pos - buffer->data);
// Compacta o que sobrou para o início
size_t remaining = (size_t)(end - pos);
memmove(buffer->data, pos, remaining);
buffer->count = (buffer->count >= rotate_count) ? (buffer->count - rotate_count) : 0;
buffer->append = buffer->data + remaining;
used = (size_t)(buffer->append - buffer->data);
}
memcpy((void*)buffer->append, (void*)&len, sizeof(uint16_t));
// Escreve [len][dados]
memcpy(buffer->append, &len, sizeof(uint16_t));
buffer->append += sizeof(uint16_t);
memcpy((void*)buffer->append, (void*)str, len);
memcpy(buffer->append, str, len);
buffer->append += len;
buffer->count++;
}
void output_buffer_append_str(output_buffer_t* buffer, const char* str)
void output_buffer_append_str(output_buffer_t *buffer, const char *str)
{
output_buffer_append_buf(buffer, str, strlen(str));
if (!buffer || !str)
{
return;
}
size_t len = strlen(str);
if (len == 0)
{
return;
}
// A API pública em logger.c já limita o tamanho, mas aqui fazemos
// um clamp defensivo para o caso de uso direto.
if (len > UINT16_MAX)
{
len = UINT16_MAX;
}
output_buffer_append_buf(buffer, str, (uint16_t)len);
}
bool output_buffer_read(output_buffer_t* buffer, uint16_t* index, char** str, uint16_t* len)
bool output_buffer_read(output_buffer_t *buffer, uint16_t *index, char **str, uint16_t *len)
{
if (*index > buffer->count) {
if (!buffer || !buffer->data || !index || !str || !len)
{
return false;
}
if (*index > buffer->count)
{
*index = buffer->count;
}
bool has_next = false;
if (*index < buffer->count) {
uint8_t* pos = buffer->data;
uint16_t current = 0;
while (current != *index) {
uint16_t entry_len;
memcpy((void*)&entry_len, (void*)pos, sizeof(uint16_t));
pos += entry_len + sizeof(uint16_t);
current++;
}
memcpy((void*)len, (void*)pos, sizeof(uint16_t));
pos += sizeof(uint16_t);
*str = (char*)pos;
(*index)++;
has_next = true;
if (*index >= buffer->count)
{
return false;
}
return has_next;
}
uint8_t *pos = buffer->data;
uint8_t *end = buffer->data + buffer->size;
uint16_t current = 0;
// Avança até à entrada [*index]
while (current < *index)
{
if (pos + sizeof(uint16_t) > end)
{
// Dados corrompidos ou índice fora → fail-safe
return false;
}
uint16_t entry_len;
memcpy(&entry_len, pos, sizeof(uint16_t));
if (entry_len == 0 || entry_len > buffer->size)
{
// Corrompido → aborta
return false;
}
if (pos + sizeof(uint16_t) + entry_len > end)
{
return false;
}
pos += sizeof(uint16_t) + entry_len;
current++;
}
// Agora pos aponta para o len da entrada desejada
if (pos + sizeof(uint16_t) > end)
{
return false;
}
memcpy(len, pos, sizeof(uint16_t));
pos += sizeof(uint16_t);
if (pos + *len > end)
{
return false;
}
*str = (char *)pos;
(*index)++;
return true;
}

View File

@@ -1,29 +1,30 @@
# List the source files to be compiled
# components/meter_manager/CMakeLists.txt
set(srcs
"driver/meter_ade7758/meter_ade7758.c"
"driver/meter_ade7758/ade7758.c"
"driver/meter_orno/meter_orno513.c"
"driver/meter_orno/meter_orno526.c"
"driver/meter_orno/meter_orno516.c"
"driver/meter_orno/meter_dts6619.c"
"driver/meter_orno/meter_dds661.c"
"driver/meter_orno/meter_ea777.c"
"driver/meter_orno/modbus_params.c"
"driver/meter_zigbee/meter_zigbee.c"
"src/meter_manager.c"
"src/meter_events.c"
driver/meter_ade7758/meter_ade7758.c
driver/meter_ade7758/ade7758.c
driver/meter_orno/meter_orno513.c
driver/meter_orno/meter_orno526.c
driver/meter_orno/meter_orno516.c
driver/meter_orno/meter_dts6619.c
driver/meter_orno/meter_dds661.c
driver/meter_orno/meter_ea777.c
driver/meter_orno/modbus_params.c
driver/meter_zigbee/meter_zigbee.c
src/meter_manager.c
src/meter_events.c
)
# List the include directories
set(includes
"include"
"driver/meter_ade7758"
"driver/meter_orno"
"driver/meter_zigbee"
include
driver/meter_ade7758
driver/meter_orno
driver/meter_zigbee
)
# Register the component with the ESP-IDF build system
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "${includes}"
PRIV_REQUIRES nvs_flash
REQUIRES esp_event esp-modbus spi_bus_manager network)
idf_component_register(
SRCS ${srcs}
INCLUDE_DIRS ${includes}
REQUIRES esp_event
PRIV_REQUIRES esp-modbus spi_bus_manager storage_service network
)

View File

@@ -53,7 +53,7 @@ static void meter_ade7758_post_event(const meter_ade7758_internal_data_t *data)
memcpy(evt.irms, data->irms, sizeof(evt.irms));
memcpy(evt.watt, data->watt, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
}

View File

@@ -197,7 +197,7 @@ static void serial_mdb_task(void *param)
memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, watt, sizeof(evt.watt));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
vTaskDelay(UPDATE_INTERVAL);
}
}

View File

@@ -138,7 +138,7 @@ static void meter_dts6619_post_event(float *voltage, float *current, int *power_
memcpy(evt.watt, power_w, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), pdMS_TO_TICKS(10));
&evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

@@ -1,35 +1,32 @@
#ifndef METER_EA777_H_
#define METER_EA777_H_
#ifndef METER_DTS6619_H_
#define METER_DTS6619_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Inicializa o driver do medidor EA777 (UART RS485, Modbus, registradores).
* @brief Inicializa o driver do medidor dts6619 (SPI, mutex, registradores).
*
* @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro.
*/
esp_err_t meter_ea777_init(void);
esp_err_t meter_dts6619_init(void);
/**
* @brief Inicia a tarefa de leitura de dados do medidor EA777.
* @brief Inicia a tarefa de leitura de dados do medidor DTS6619.
*
* @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro.
*/
esp_err_t meter_ea777_start(void);
esp_err_t meter_dts6619_start(void);
/**
* @brief Para a tarefa de leitura e limpa os dados internos do medidor EA777.
* @brief Para a tarefa de leitura e limpa os dados internos do medidor DTS6619.
*/
void meter_ea777_stop(void);
void meter_dts6619_stop(void);
#ifdef __cplusplus
}
#endif
#endif /* METER_EA777_H_ */
#endif /* METER_DTS6619_H_ */

View File

@@ -7,6 +7,7 @@
#include "driver/uart.h"
#include <stddef.h>
#include <string.h>
#include "meter_ea777.h"
#define TAG "serial_mdb_ea777"
@@ -148,7 +149,7 @@ static void meter_ea777_post_event(float *voltage, float *current, int *power_w,
memcpy(evt.watt, power_w, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), pdMS_TO_TICKS(10));
&evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

@@ -1,32 +1,35 @@
#ifndef METER_DTS6619_H_
#define METER_DTS6619_H_
#ifndef METER_EA777_H_
#define METER_EA777_H_
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Inicializa o driver do medidor dts6619 (SPI, mutex, registradores).
* @brief Inicializa o driver do medidor EA777 (UART RS485, Modbus, registradores).
*
* @return esp_err_t Retorna ESP_OK se a inicialização for bem-sucedida, caso contrário retorna um erro.
*/
esp_err_t meter_dts6619_init(void);
esp_err_t meter_ea777_init(void);
/**
* @brief Inicia a tarefa de leitura de dados do medidor DTS6619.
* @brief Inicia a tarefa de leitura de dados do medidor EA777.
*
* @return esp_err_t Retorna ESP_OK se a tarefa for iniciada com sucesso, caso contrário retorna um erro.
*/
esp_err_t meter_dts6619_start(void);
esp_err_t meter_ea777_start(void);
/**
* @brief Para a tarefa de leitura e limpa os dados internos do medidor DTS6619.
* @brief Para a tarefa de leitura e limpa os dados internos do medidor EA777.
*/
void meter_dts6619_stop(void);
void meter_ea777_stop(void);
#ifdef __cplusplus
}
#endif
#endif /* METER_DTS6619_H_ */
#endif /* METER_EA777_H_ */

View File

@@ -8,11 +8,12 @@
#define TAG "serial_mdb_orno513"
#define MB_PORT_NUM 2
#define MB_DEV_SPEED 9600
#define MB_UART_TXD 17
#define MB_UART_RXD 16
#define MB_UART_RTS 5
#define MB_UART_RTS 2
#define UPDATE_INTERVAL (3000 / portTICK_PERIOD_MS)
#define POLL_INTERVAL (100 / portTICK_PERIOD_MS)
@@ -129,7 +130,7 @@ static void serial_mdb_task(void *param) {
memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, watt, sizeof(evt.watt));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
vTaskDelay(UPDATE_INTERVAL);

View File

@@ -91,7 +91,7 @@ static void meter_orno516_post_event(float *voltage, float *current, int *power)
memcpy(evt.watt, power, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), pdMS_TO_TICKS(10));
&evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));

View File

@@ -207,7 +207,7 @@ static void serial_mdb_task(void *param)
memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, watt, sizeof(evt.watt));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), pdMS_TO_TICKS(10));
esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY);
vTaskDelay(UPDATE_INTERVAL);
}
}

View File

@@ -12,7 +12,7 @@
#define TAG "meter_zigbee"
// UART config
#define UART_PORT UART_NUM_1
#define UART_PORT UART_NUM_2
#define TXD_PIN GPIO_NUM_17
#define RXD_PIN GPIO_NUM_16
#define UART_BUF_SIZE 128
@@ -85,7 +85,7 @@ static void meter_zigbee_post_event(void) {
METER_EVENT_DATA_READY,
&evt,
sizeof(evt),
pdMS_TO_TICKS(10));
portMAX_DELAY);
if (err != ESP_OK) {
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
@@ -94,8 +94,8 @@ static void meter_zigbee_post_event(void) {
static void handle_zigbee_frame(const uint8_t *buf, size_t len) {
ESP_LOGI(TAG, "Received UART frame (%d bytes):", len);
ESP_LOG_BUFFER_HEX(TAG, buf, len);
ESP_LOGD(TAG, "Received UART frame (%d bytes):", len);
//ESP_LOG_BUFFER_HEX(TAG, buf, len);
if (len < RX_FRAME_SIZE) {
ESP_LOGW(TAG, "Invalid frame: too short (len = %d)", len);
@@ -118,7 +118,7 @@ static void handle_zigbee_frame(const uint8_t *buf, size_t len) {
float current = current_raw / 1000.0f;
float power = power_raw;
ESP_LOGI(TAG, "Parsed Attr 0x%04X: V=%.1fV I=%.2fA P=%.1fW", attr, volt, current, power);
ESP_LOGD(TAG, "Parsed Attr 0x%04X: V=%.1fV I=%.2fA P=%.1fW", attr, volt, current, power);
if (xSemaphoreTake(meter_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
switch (attr) {
@@ -224,13 +224,8 @@ esp_err_t meter_zigbee_start(void) {
void meter_zigbee_stop(void) {
//send_stop_command();
//vTaskDelay(pdMS_TO_TICKS(100)); // Aguarda o outro lado processar
if (meter_zigbee_task) {
vTaskDelete(meter_zigbee_task);
meter_zigbee_task = NULL;

View File

@@ -3,6 +3,7 @@
#include "esp_event.h"
#include "meter_manager.h" // Para meter_type_t
#include <stdint.h> // Para int64_t
#ifdef __cplusplus
extern "C" {
@@ -16,20 +17,27 @@ typedef enum {
METER_EVENT_DATA_READY = 0,
METER_EVENT_ERROR,
METER_EVENT_STARTED,
METER_EVENT_STOPPED
METER_EVENT_STOPPED,
METER_EVENT_CONFIG_UPDATED // Novo: configuração (grid/evse) atualizada
} meter_event_id_t;
// Estrutura de dados enviados com METER_EVENT_DATA_READY
typedef struct {
const char *source; // "GRID" ou "EVSE"
float vrms[3]; // Tensão por fase
float irms[3]; // Corrente por fase
int watt[3]; // Potência ativa por fase
float frequency; // Frequência da rede (Hz)
float power_factor; // Fator de potência
float total_energy; // Energia acumulada (kWh)
float vrms[3]; // Tensão por fase
float irms[3]; // Corrente por fase
int watt[3]; // Potência ativa por fase
float frequency; // Frequência da rede (Hz)
float power_factor; // Fator de potência
float total_energy; // Energia acumulada (kWh)
} meter_event_data_t;
// Estrutura de dados enviados com METER_EVENT_CONFIG_UPDATED
typedef struct {
meter_type_t grid_type; // Tipo de contador configurado para o GRID
meter_type_t evse_type; // Tipo de contador configurado para a EVSE
int64_t timestamp_us; // Momento da atualização (esp_timer_get_time)
} meter_config_event_t;
#ifdef __cplusplus
}

View File

@@ -10,10 +10,15 @@
#include "meter_zigbee.h"
#include "meter_ea777.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <string.h>
#include "network_events.h"
#include "meter_events.h"
#include "esp_event.h"
#include "esp_timer.h"
// NEW:
#include "storage_service.h"
static const char *TAG = "meter_manager";
@@ -21,179 +26,200 @@ static const char *TAG = "meter_manager";
static meter_type_t meter_evse_type = METER_TYPE_NONE;
static meter_type_t meter_grid_type = METER_TYPE_NONE;
#define NVS_NAMESPACE "meterconfig"
#define NVS_EVSE_MODEL "evse_model"
#define NVS_GRID_MODEL "grid_model"
#define STORE_NAMESPACE "meterconfig"
#define STORE_EVSE_MODEL "evse_model"
#define STORE_GRID_MODEL "grid_model"
// timeouts storage
#define STORAGE_TO pdMS_TO_TICKS(800)
// ------------------------------------------------------------------
// Helpers storage (robustos a queue cheia)
// ------------------------------------------------------------------
static esp_err_t storage_try_flush(void)
{
return storage_flush_sync(pdMS_TO_TICKS(2000));
}
static esp_err_t storage_try_set_u8(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t err = storage_set_u8_async(ns, key, v);
if (err == ESP_OK)
return ESP_OK;
if (err == ESP_ERR_TIMEOUT)
{
(void)storage_try_flush();
continue;
}
return err;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t storage_get_u8_default(const char *ns, const char *key, uint8_t *out, uint8_t def)
{
if (!out)
return ESP_ERR_INVALID_ARG;
uint8_t v = def;
esp_err_t err = storage_get_u8_sync(ns, key, &v, STORAGE_TO);
if (err == ESP_OK)
{
*out = v;
return ESP_OK;
}
// se não existir / erro, devolve default
*out = def;
return err;
}
// ------------------------------------------------------------------
/*
static void meter_manager_network_event_handler(void *arg, esp_event_base_t base, int32_t event_id, void *data)
{
(void)arg;
(void)data;
if (base != NETWORK_EVENTS)
return;
switch (event_id)
{
case NETWORK_EVENT_AP_STARTED:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_AP_STARTED, parando medidor de grid");
// meter_manager_grid_stop();
ESP_LOGD(TAG, "Recebido NETWORK_EVENT_AP_STARTED, parando medidor de grid");
break;
case NETWORK_EVENT_AP_STOP:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_AP_STOP, reiniciando medidor de grid");
// meter_manager_grid_start();
ESP_LOGD(TAG, "Recebido NETWORK_EVENT_AP_STOP, reiniciando medidor de grid");
break;
case NETWORK_EVENT_STA_GOT_IP:
ESP_LOGI(TAG, "Recebido NETWORK_EVENT_STA_GOT_IP");
// opcional: reiniciar ou logar
ESP_LOGD(TAG, "Recebido NETWORK_EVENT_STA_GOT_IP");
break;
default:
break;
}
}
*/
// Função unificada para ler ou inicializar um modelo de medidor
// Função unificada para ler ou inicializar um modelo de medidor (via storage_service)
static esp_err_t load_or_init_meter_model(const char *key, meter_type_t *type)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS handle for %s: %s", key, esp_err_to_name(err));
return err;
}
if (!type)
return ESP_ERR_INVALID_ARG;
// tenta ler
uint8_t value = 0xFF;
esp_err_t err = storage_get_u8_sync(STORE_NAMESPACE, key, &value, STORAGE_TO);
uint8_t value = 0;
err = nvs_get_u8(handle, key, &value);
if (err == ESP_OK && value < 255)
{
*type = (meter_type_t)value;
ESP_LOGI(TAG, "Loaded meter type %d from NVS key '%s'", value, key);
}
else
{
*type = METER_TYPE_NONE;
nvs_set_u8(handle, key, *type);
nvs_commit(handle);
ESP_LOGW(TAG, "Invalid or missing key '%s', setting default (NONE)", key);
ESP_LOGD(TAG, "Loaded meter type %u from storage key '%s/%s'", (unsigned)value, STORE_NAMESPACE, key);
return ESP_OK;
}
nvs_close(handle);
return ESP_OK;
// se não existir / inválido -> default NONE e grava
*type = METER_TYPE_NONE;
esp_err_t w = storage_try_set_u8(STORE_NAMESPACE, key, (uint8_t)(*type));
if (w != ESP_OK)
ESP_LOGE(TAG, "Failed to init key '%s/%s' to NONE: %s", STORE_NAMESPACE, key, esp_err_to_name(w));
(void)storage_try_flush();
ESP_LOGW(TAG, "Invalid/missing key '%s/%s' (read=%s), setting default (NONE)",
STORE_NAMESPACE, key, esp_err_to_name(err));
return ESP_OK; // seguimos com default
}
static esp_err_t write_meter_model_to_nvs(const char *key, meter_type_t meter_type)
static esp_err_t write_meter_model_to_storage(const char *key, meter_type_t meter_type)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
esp_err_t err = storage_try_set_u8(STORE_NAMESPACE, key, (uint8_t)meter_type);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to open NVS handle for writing");
ESP_LOGE(TAG, "Failed to write meter type to storage key '%s/%s': %s",
STORE_NAMESPACE, key, esp_err_to_name(err));
return err;
}
err = nvs_set_u8(handle, key, (uint8_t)meter_type);
if (err == ESP_OK)
{
err = nvs_commit(handle);
ESP_LOGI(TAG, "Saved meter type %d to NVS key '%s'", meter_type, key);
}
else
{
ESP_LOGE(TAG, "Failed to write meter type to NVS key '%s'", key);
}
nvs_close(handle);
return err;
(void)storage_try_flush();
ESP_LOGD(TAG, "Saved meter type %d to storage key '%s/%s'", (int)meter_type, STORE_NAMESPACE, key);
return ESP_OK;
}
/**
* @brief Initializes the meter manager system.
*
* This function initializes both the EVSE and GRID meters,
* and registers the event handler to listen for NETWORK_EVENTS
* (e.g., AP started/stopped, STA got IP).
*
* @return esp_err_t ESP_OK on success, or an error code.
* @brief Inicializa o sistema de meter manager.
*/
esp_err_t meter_manager_init(void)
{
esp_err_t err;
// garantir storage pronto
esp_err_t s = storage_service_init();
if (s != ESP_OK)
ESP_LOGE(TAG, "storage_service_init failed: %s", esp_err_to_name(s));
// Initialize EVSE meter (habilite quando quiser)
// err = meter_manager_evse_init();
// if (err != ESP_OK) return err;
esp_err_t err;
// Initialize GRID meter
err = meter_manager_grid_init();
if (err != ESP_OK)
return err;
// Register handler for custom network events
ESP_LOGI(TAG, "Registering network event handler");
return esp_event_handler_register(NETWORK_EVENTS,
ESP_EVENT_ANY_ID,
meter_manager_network_event_handler,
NULL);
// Regista handler para eventos de rede
/*
ESP_LOGD(TAG, "Registering network event handler");
err = esp_event_handler_register(
NETWORK_EVENTS,
ESP_EVENT_ANY_ID,
meter_manager_network_event_handler,
NULL);
*/
if (err != ESP_OK)
return err;
// Emite um evento inicial de configuração
meter_config_event_t ev = {
.grid_type = meter_manager_grid_get_model(),
.evse_type = meter_manager_evse_get_model(),
.timestamp_us = esp_timer_get_time()};
esp_event_post(METER_EVENT,
METER_EVENT_CONFIG_UPDATED,
&ev,
sizeof(ev),
0);
return ESP_OK;
}
/**
* @brief Starts all configured meters (EVSE and GRID).
*
* This function starts the EVSE and GRID meters based on their configured types.
* It does not register event handlers — that is handled by `meter_manager_init()`.
*
* @return esp_err_t ESP_OK on success, or an error code from one of the start calls.
*/
esp_err_t meter_manager_start(void)
{
esp_err_t err;
// Start EVSE meter (habilite quando quiser)
// err = meter_manager_evse_start();
// if (err != ESP_OK) return err;
// Start GRID meter
err = meter_manager_grid_start();
if (err != ESP_OK)
return err;
return ESP_OK;
return meter_manager_grid_start();
}
/**
* @brief Stops all meters and unregisters event handlers.
*
* This function gracefully stops the EVSE and GRID meters
* and unregisters the previously registered network event handler.
*
* @return esp_err_t ESP_OK on success, or an error code.
*/
esp_err_t meter_manager_stop(void)
{
esp_err_t err;
// Stop EVSE meter
// err = meter_manager_evse_stop();
// if (err != ESP_OK) return err;
// Stop GRID meter
err = meter_manager_grid_stop();
if (err != ESP_OK)
return err;
return ESP_OK;
return meter_manager_grid_stop();
}
// ---------- EVSE ----------
esp_err_t meter_manager_evse_init()
{
esp_err_t err = load_or_init_meter_model(NVS_EVSE_MODEL, &meter_evse_type);
esp_err_t err = load_or_init_meter_model(STORE_EVSE_MODEL, &meter_evse_type);
if (err != ESP_OK)
return err;
ESP_LOGI(TAG, "Initializing EVSE meter of type %s", meter_type_to_str(meter_evse_type));
ESP_LOGD(TAG, "Initializing EVSE meter of type %s", meter_type_to_str(meter_evse_type));
switch (meter_evse_type)
{
@@ -292,13 +318,13 @@ esp_err_t meter_manager_evse_stop(void)
esp_err_t meter_manager_grid_init()
{
esp_err_t err = load_or_init_meter_model(NVS_GRID_MODEL, &meter_grid_type);
esp_err_t err = load_or_init_meter_model(STORE_GRID_MODEL, &meter_grid_type);
if (err != ESP_OK)
return err;
ESP_LOGI(TAG, "Initializing GRID meter of type %s", meter_type_to_str(meter_grid_type));
ESP_LOGD(TAG, "Initializing GRID meter of type %s", meter_type_to_str(meter_grid_type));
switch (meter_grid_type) // corrigido: ORNO-513 -> driver orno513
switch (meter_grid_type)
{
case METER_TYPE_NONE:
return ESP_OK;
@@ -334,7 +360,7 @@ esp_err_t meter_manager_grid_start()
case METER_TYPE_ADE7758:
return meter_ade7758_start();
case METER_TYPE_ORNO513:
return meter_orno513_start(); // corrigido
return meter_orno513_start();
case METER_TYPE_ORNO516:
return meter_orno516_start();
case METER_TYPE_ORNO526:
@@ -364,7 +390,7 @@ esp_err_t meter_manager_grid_stop(void)
meter_ade7758_stop();
break;
case METER_TYPE_ORNO513:
meter_orno513_stop(); // corrigido
meter_orno513_stop();
break;
case METER_TYPE_ORNO516:
meter_orno516_stop();
@@ -391,18 +417,64 @@ esp_err_t meter_manager_grid_stop(void)
return ESP_OK;
}
// ---------- Utilidades ----------
// ---------- Utilidades / setters com evento ----------
esp_err_t meter_manager_evse_set_model(meter_type_t meter_type)
{
esp_err_t err;
err = meter_manager_evse_stop();
if (err != ESP_OK)
ESP_LOGW(TAG, "Failed to stop EVSE meter before changing model: %s", esp_err_to_name(err));
meter_evse_type = meter_type;
return write_meter_model_to_nvs(NVS_EVSE_MODEL, meter_evse_type);
err = write_meter_model_to_storage(STORE_EVSE_MODEL, meter_evse_type);
if (err != ESP_OK)
return err;
err = meter_manager_evse_init();
if (err == ESP_OK)
err = meter_manager_evse_start();
meter_config_event_t ev = {
.grid_type = meter_manager_grid_get_model(),
.evse_type = meter_manager_evse_get_model(),
.timestamp_us = esp_timer_get_time()};
esp_err_t evt_err = esp_event_post(METER_EVENT, METER_EVENT_CONFIG_UPDATED, &ev, sizeof(ev), portMAX_DELAY);
if (evt_err != ESP_OK)
ESP_LOGW(TAG, "Failed to post METER_EVENT_CONFIG_UPDATED (EVSE): %s", esp_err_to_name(evt_err));
return err;
}
esp_err_t meter_manager_grid_set_model(meter_type_t meter_type)
{
esp_err_t err;
err = meter_manager_grid_stop();
if (err != ESP_OK)
ESP_LOGW(TAG, "Failed to stop GRID meter before changing model: %s", esp_err_to_name(err));
meter_grid_type = meter_type;
return write_meter_model_to_nvs(NVS_GRID_MODEL, meter_grid_type);
err = write_meter_model_to_storage(STORE_GRID_MODEL, meter_grid_type);
if (err != ESP_OK)
return err;
err = meter_manager_grid_init();
if (err == ESP_OK)
err = meter_manager_grid_start();
meter_config_event_t ev = {
.grid_type = meter_manager_grid_get_model(),
.evse_type = meter_manager_evse_get_model(),
.timestamp_us = esp_timer_get_time()};
esp_err_t evt_err = esp_event_post(METER_EVENT, METER_EVENT_CONFIG_UPDATED, &ev, sizeof(ev), portMAX_DELAY);
if (evt_err != ESP_OK)
ESP_LOGW(TAG, "Failed to post METER_EVENT_CONFIG_UPDATED (GRID): %s", esp_err_to_name(evt_err));
return err;
}
bool meter_manager_evse_is_enabled(void)
@@ -410,15 +482,8 @@ bool meter_manager_evse_is_enabled(void)
return meter_manager_evse_get_model() != METER_TYPE_NONE;
}
meter_type_t meter_manager_evse_get_model(void)
{
return meter_evse_type;
}
meter_type_t meter_manager_grid_get_model(void)
{
return meter_grid_type;
}
meter_type_t meter_manager_evse_get_model(void) { return meter_evse_type; }
meter_type_t meter_manager_grid_get_model(void) { return meter_grid_type; }
const char *meter_type_to_str(meter_type_t type)
{
@@ -453,6 +518,7 @@ meter_type_t string_to_meter_type(const char *str)
{
if (!str)
return METER_TYPE_NONE;
if (strcmp(str, "IC ADE") == 0)
return METER_TYPE_ADE7758;
if (strcmp(str, "ORNO-513") == 0)
@@ -471,5 +537,6 @@ meter_type_t string_to_meter_type(const char *str)
return METER_TYPE_TRIF_ZIGBEE;
if (strcmp(str, "EA-777") == 0)
return METER_TYPE_EA777;
return METER_TYPE_NONE;
}

View File

@@ -5,4 +5,4 @@ set(srcs
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash esp_netif esp_wifi mdns esp_event)
PRIV_REQUIRES nvs_flash esp_netif esp_wifi storage_service mdns esp_event)

View File

@@ -11,22 +11,24 @@
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_mac.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "mdns.h"
#include "network_events.h"
#include "network.h"
// NEW:
#include "storage_service.h"
// -----------------------------------------------------------------------------
// Config
// -----------------------------------------------------------------------------
#define AP_SSID "plx-%02x%02x%02x" // SSID do AP (usa 3 bytes do MAC)
#define MDNS_SSID "plx%02x" // hostname mDNS (usa 2 bytes do MAC)
#define AP_SSID "plx-%02x%02x%02x"
#define MDNS_SSID "plx%02x"
#define NVS_NAMESPACE "wifi"
#define NVS_ENABLED "enabled"
@@ -35,60 +37,106 @@
// Comprimentos com terminador
#define SSID_MAX_LEN 32
#define PASS_MAX_LEN 64 // 63 chars + '\0'
#define SSID_BUF_SZ (SSID_MAX_LEN + 1) // 33
#define PASS_BUF_SZ (PASS_MAX_LEN + 1) // 65
#define PASS_MAX_LEN 64
#define SSID_BUF_SZ (SSID_MAX_LEN + 1)
#define PASS_BUF_SZ (PASS_MAX_LEN + 1)
static const char *TAG = "wifi";
// Storage timeouts
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
// -----------------------------------------------------------------------------
// Estado global
// -----------------------------------------------------------------------------
static nvs_handle_t nvs;
static esp_netif_t *sta_netif;
static esp_netif_t *ap_netif;
EventGroupHandle_t wifi_event_group;
// Backoff simples para reconexão STA (agora sem delay no event handler)
// Backoff simples para reconexão STA
static int s_retry_count = 0;
static const int s_retry_max = 7;
// -----------------------------------------------------------------------------
// Helpers
// Helpers storage (robustos)
// -----------------------------------------------------------------------------
// Lê string do NVS com segurança (trunca se necessário)
static esp_err_t nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, size_t out_sz)
static esp_err_t store_flush_best_effort(void)
{
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_str_async(ns, key, s);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// Lê string de forma segura (lê para buffer grande e depois trunca para out)
// Nota: isto ajuda se houver lixo antigo no NVS com strings maiores do que o esperado.
static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz)
{
if (!out || out_sz == 0)
return ESP_ERR_INVALID_ARG;
out[0] = '\0';
size_t need = 0;
esp_err_t err = nvs_get_str(h, key, NULL, &need);
if (err == ESP_ERR_NVS_NOT_FOUND)
return ESP_OK;
if (err != ESP_OK)
return err;
// buffer grande (sem heap): usa o máximo definido no storage_service
char tmp[STORAGE_MAX_VALUE_BYTES + 1];
memset(tmp, 0, sizeof(tmp));
if (need == 0)
return ESP_OK; // vazio
if (need > out_sz)
esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
{
// Truncar de forma segura
char *tmp = (char *)malloc(need);
if (!tmp)
return ESP_ERR_NO_MEM;
err = nvs_get_str(h, key, tmp, &need);
if (err == ESP_OK)
{
snprintf(out, out_sz, "%s", tmp);
}
free(tmp);
return err;
out[0] = '\0';
return ESP_OK;
}
return nvs_get_str(h, key, out, &need);
if (e != ESP_OK)
{
out[0] = '\0';
return e;
}
size_t n = strnlen(tmp, out_sz - 1); // no máximo out_sz-1
memcpy(out, tmp, n);
out[n] = '\0';
return ESP_OK;
}
// -----------------------------------------------------------------------------
@@ -96,6 +144,8 @@ static esp_err_t nvs_get_str_safe(nvs_handle_t h, const char *key, char *out, si
// -----------------------------------------------------------------------------
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
(void)arg;
if (event_base == WIFI_EVENT)
{
switch (event_id)
@@ -128,7 +178,6 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
case WIFI_EVENT_STA_CONNECTED:
{
ESP_LOGI(TAG, "STA associated (L2 connected)");
// dispara evento genérico de STA_CONNECTED
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_CONNECTED,
NULL,
@@ -144,14 +193,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
wifi_event_sta_disconnected_t *ev = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "STA disconnected, reason=%d", ev ? ev->reason : -1);
// dispara evento genérico de “STA desconectou”
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_DISCONNECTED,
ev,
ev ? sizeof(*ev) : 0,
portMAX_DELAY);
// NÃO bloquear o event loop com vTaskDelay
if (s_retry_count < s_retry_max)
{
esp_err_t err = esp_wifi_connect();
@@ -162,7 +209,7 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
}
else
{
ESP_LOGW(TAG, "esp_wifi_connect failed (%d)", err);
ESP_LOGW(TAG, "esp_wifi_connect failed (%s)", esp_err_to_name(err));
}
}
else
@@ -185,22 +232,12 @@ static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0;
// Dispara evento “STA GOT IP”
esp_event_post(NETWORK_EVENTS,
NETWORK_EVENT_STA_GOT_IP,
&event->ip_info,
sizeof(event->ip_info),
portMAX_DELAY);
}
else if (event_id == IP_EVENT_GOT_IP6)
{
ip_event_got_ip6_t *event = (ip_event_got_ip6_t *)event_data;
ESP_LOGI(TAG, "WiFi STA got ip6: " IPV6STR, IPV62STR(event->ip6_info.ip));
xEventGroupClearBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT);
xEventGroupSetBits(wifi_event_group, WIFI_STA_CONNECTED_BIT);
s_retry_count = 0;
// se quiseres, podes também propagar um NETWORK_EVENT_STA_GOT_IP aqui
}
else if (event_id == IP_EVENT_STA_LOST_IP)
{
ESP_LOGW(TAG, "STA lost IP");
@@ -227,28 +264,21 @@ static void sta_set_config(void)
wifi_config.sta.pmf_cfg.capable = true;
wifi_config.sta.pmf_cfg.required = false;
// buffers "seguros" vindos da NVS (estes sim têm terminador)
char ssid_buf[SSID_BUF_SZ] = {0}; // 33 (32 + '\0')
char pass_buf[PASS_BUF_SZ] = {0}; // 65 (64 + '\0')
char ssid_buf[SSID_BUF_SZ] = {0};
char pass_buf[PASS_BUF_SZ] = {0};
wifi_get_ssid(ssid_buf);
wifi_get_password(pass_buf);
// Copiar **sem** terminador para os campos do esp_wifi (32/64 bytes)
// SSID: max 32
size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN); // até 32
size_t ssid_len = strnlen(ssid_buf, SSID_MAX_LEN);
memcpy(wifi_config.sta.ssid, ssid_buf, ssid_len);
// Password WPA/WPA2: 8..63 chars (campo tem 64 bytes)
// (se usares rede aberta, pass_len pode ser 0)
size_t pass_len = strnlen(pass_buf, 63); // até 63
size_t pass_len = strnlen(pass_buf, 63);
memcpy(wifi_config.sta.password, pass_buf, pass_len);
if (pass_len <= 63)
{
wifi_config.sta.password[pass_len] = '\0';
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); // v5.x
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
}
static void ap_set_config(void)
@@ -257,14 +287,13 @@ static void ap_set_config(void)
wifi_config_t wifi_ap_config = {0};
wifi_ap_config.ap.max_connection = 1;
wifi_ap_config.ap.authmode = WIFI_AUTH_OPEN; // para portal cativo, por exemplo
wifi_ap_config.ap.authmode = WIFI_AUTH_OPEN;
uint8_t mac[6];
esp_wifi_get_mac(WIFI_IF_AP, mac);
snprintf((char *)wifi_ap_config.ap.ssid, sizeof(wifi_ap_config.ap.ssid),
AP_SSID, mac[3], mac[4], mac[5]); // "plx-XXXXXX"
AP_SSID, mac[3], mac[4], mac[5]);
// Só AP (não mexer na config STA aqui para não a limpar)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));
}
@@ -283,7 +312,7 @@ static void sta_try_start(void)
esp_err_t e = esp_wifi_start();
if (e != ESP_OK && e != ESP_ERR_WIFI_CONN)
{
ESP_LOGW(TAG, "esp_wifi_start returned %d", e);
ESP_LOGW(TAG, "esp_wifi_start returned %s", esp_err_to_name(e));
}
xEventGroupSetBits(wifi_event_group, WIFI_STA_MODE_BIT);
}
@@ -296,8 +325,10 @@ void wifi_ini(void)
{
ESP_LOGI(TAG, "Wifi init");
// Abre NVS (assume que nvs_flash_init() já foi chamado no boot geral)
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs));
// garante storage pronto (não assume NVS handle aberto aqui)
esp_err_t se = storage_service_init();
if (se != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(se));
wifi_event_group = xEventGroupCreate();
@@ -310,14 +341,11 @@ void wifi_ini(void)
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
// mDNS: usa dois bytes do MAC para identificar
uint8_t mac[6];
esp_wifi_get_mac(WIFI_IF_STA, mac);
char chargeid[16];
// ex.: "plx00"
snprintf(chargeid, sizeof(chargeid), MDNS_SSID, 0);
// Hostname das interfaces alinhado com o mDNS
ESP_ERROR_CHECK(esp_netif_set_hostname(sta_netif, chargeid));
ESP_ERROR_CHECK(esp_netif_set_hostname(ap_netif, chargeid));
@@ -335,10 +363,9 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
{
ESP_LOGI(TAG, "wifi_set_config(enabled=%d)", enabled);
// Validação (quando habilitar STA)
// Validação
if (enabled)
{
// SSID 1..32
if (ssid && (strlen(ssid) == 0 || strlen(ssid) > SSID_MAX_LEN))
{
ESP_LOGE(TAG, "SSID out of range");
@@ -346,15 +373,15 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
}
if (!ssid)
{
size_t len = 0;
esp_err_t e = nvs_get_str(nvs, NVS_SSID, NULL, &len);
if (e != ESP_OK || len <= 1)
char cur_ssid[SSID_BUF_SZ] = {0};
wifi_get_ssid(cur_ssid);
if (strlen(cur_ssid) == 0)
{
ESP_LOGE(TAG, "Required SSID");
return ESP_ERR_INVALID_ARG;
}
}
// Password: 8..63 (se não for vazia). Aceita "" para rede open (caso uses).
if (password)
{
size_t lp = strlen(password);
@@ -366,13 +393,27 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
}
}
// Persiste no NVS
ESP_ERROR_CHECK(nvs_set_u8(nvs, NVS_ENABLED, enabled));
// Persiste via storage_service
esp_err_t err = store_set_u8_best_effort(NVS_NAMESPACE, NVS_ENABLED, enabled ? 1 : 0);
if (err != ESP_OK)
return err;
if (ssid)
ESP_ERROR_CHECK(nvs_set_str(nvs, NVS_SSID, ssid));
{
err = store_set_str_best_effort(NVS_NAMESPACE, NVS_SSID, ssid);
if (err != ESP_OK)
return err;
}
if (password)
ESP_ERROR_CHECK(nvs_set_str(nvs, NVS_PASSWORD, password));
ESP_ERROR_CHECK(nvs_commit(nvs));
{
err = store_set_str_best_effort(NVS_NAMESPACE, NVS_PASSWORD, password);
if (err != ESP_OK)
return err;
}
// Força persistência (para sobreviver a reboot imediato)
(void)store_flush_best_effort();
// Reinicia modo
ESP_LOGI(TAG, "Stopping AP/STA");
@@ -380,7 +421,7 @@ esp_err_t wifi_set_config(bool enabled, const char *ssid, const char *password)
esp_err_t e = esp_wifi_stop();
if (e != ESP_OK && e != ESP_ERR_WIFI_NOT_INIT && e != ESP_ERR_WIFI_STOP_STATE)
{
ESP_LOGW(TAG, "esp_wifi_stop returned %d", e);
ESP_LOGW(TAG, "esp_wifi_stop returned %s", esp_err_to_name(e));
}
sta_try_start();
@@ -402,21 +443,21 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
esp_err_t err = esp_wifi_scan_start(NULL, true);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_wifi_scan_start failed (%d)", err);
ESP_LOGW(TAG, "esp_wifi_scan_start failed (%s)", esp_err_to_name(err));
return 0;
}
err = esp_wifi_scan_get_ap_records(&number, ap_info);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed (%d)", err);
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_records failed (%s)", esp_err_to_name(err));
return 0;
}
err = esp_wifi_scan_get_ap_num(&ap_count);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_num failed (%d)", err);
ESP_LOGW(TAG, "esp_wifi_scan_get_ap_num failed (%s)", esp_err_to_name(err));
return 0;
}
@@ -424,7 +465,6 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
for (int i = 0; (i < WIFI_SCAN_SCAN_LIST_SIZE) && (i < ap_count); i++)
{
// garante que scan_aps[i].ssid tenha pelo menos 33 bytes
snprintf(scan_aps[i].ssid, sizeof(scan_aps[i].ssid), "%s", (const char *)ap_info[i].ssid);
scan_aps[i].rssi = ap_info[i].rssi;
scan_aps[i].auth = ap_info[i].authmode != WIFI_AUTH_OPEN;
@@ -436,29 +476,29 @@ uint16_t wifi_scan(wifi_scan_ap_t *scan_aps)
bool wifi_get_enabled(void)
{
uint8_t value = 0;
esp_err_t e = nvs_get_u8(nvs, NVS_ENABLED, &value);
if (e == ESP_ERR_NVS_NOT_FOUND)
esp_err_t e = storage_get_u8_sync(NVS_NAMESPACE, NVS_ENABLED, &value, STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
return false;
if (e != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_u8(NVS_ENABLED) failed (%d), assuming disabled", e);
ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed (%s), assuming disabled", esp_err_to_name(e));
return false;
}
return value;
return (value != 0);
}
void wifi_get_ssid(char *value)
{
if (!value)
return;
nvs_get_str_safe(nvs, NVS_SSID, value, SSID_BUF_SZ); // 33
(void)store_get_str_safe(NVS_NAMESPACE, NVS_SSID, value, SSID_BUF_SZ);
}
void wifi_get_password(char *value)
{
if (!value)
return;
nvs_get_str_safe(nvs, NVS_PASSWORD, value, PASS_BUF_SZ); // 65
(void)store_get_str_safe(NVS_NAMESPACE, NVS_PASSWORD, value, PASS_BUF_SZ);
}
void wifi_ap_start(void)
@@ -474,7 +514,7 @@ void wifi_ap_start(void)
esp_err_t e = esp_wifi_start();
if (e != ESP_OK && e != ESP_ERR_WIFI_CONN)
{
ESP_LOGW(TAG, "esp_wifi_start (AP) returned %d", e);
ESP_LOGW(TAG, "esp_wifi_start (AP) returned %s", esp_err_to_name(e));
}
xEventGroupSetBits(wifi_event_group, WIFI_AP_MODE_BIT);
@@ -499,4 +539,4 @@ bool wifi_is_ap(void)
EventBits_t bits = xEventGroupGetBits(wifi_event_group);
return (bits & WIFI_AP_MODE_BIT) != 0;
}
}

View File

@@ -5,5 +5,4 @@ set(srcs
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash
REQUIRES esp_event config esp_wifi evse mongoose MicroOcpp MicroOcppMongoose)
REQUIRES esp_event storage_service config esp_wifi evse mongoose MicroOcpp MicroOcppMongoose)

View File

@@ -1,32 +1,37 @@
// components/ocpp/src/ocpp.c
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ocpp.h"
#include "ocpp_events.h"
#include "esp_wifi.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "evse_error.h"
#include "auth_events.h"
#include "evse_events.h"
#include "evse_state.h"
#include "meter_events.h"
#include "esp_timer.h"
#include <math.h>
/* MicroOcpp includes */
#include <mongoose.h>
#include <MicroOcpp_c.h> // C-facade of MicroOcpp
#include <MicroOcppMongooseClient_c.h> // WebSocket integration for ESP-IDF
// NEW storage layer
#include "storage_service.h"
#define NVS_NAMESPACE "ocpp"
#define NVS_OCPP_ENABLED "enabled"
#define NVS_OCPP_SERVER "ocpp_server"
@@ -59,19 +64,18 @@ static esp_event_handler_instance_t s_evse_available_inst = NULL;
// --- cache de medições vindas de METER_EVENT_DATA_READY ---
typedef struct
{
// dados por fase
float vrms[3];
float irms[3];
int32_t watt[3]; // ativo por fase (W)
float frequency;
float power_factor;
// acumulados
float total_energy_Wh;
// derivados práticos
int32_t sum_watt; // soma das 3 fases
float avg_voltage; // média das 3 fases
float sum_current; // soma das 3 fases
// flag de validade
int32_t sum_watt;
float avg_voltage;
float sum_current;
bool have_data;
} ocpp_meter_cache_t;
@@ -82,18 +86,101 @@ static esp_event_handler_instance_t s_meter_inst = NULL;
// valor de oferta (A por conector)
static float s_current_offered_A = 16.0f;
// novo input apropriado
static float getCurrentOffered(void)
{
return s_current_offered_A;
}
/* =========================
* Task / Main Loop
* ========================= *
*/
// -----------------------------------------------------------------------------
// Storage helpers (robustos)
// -----------------------------------------------------------------------------
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
static void storage_init_best_effort(void)
{
esp_err_t e = storage_service_init();
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(e));
}
static esp_err_t store_flush_best_effort(void)
{
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_str_async(ns, key, s ? s : "");
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(portMAX_DELAY);
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// Lê string de forma segura (buffer grande -> truncagem segura para out)
static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz)
{
if (!out || out_sz == 0)
return ESP_ERR_INVALID_ARG;
out[0] = '\0';
char tmp[STORAGE_MAX_VALUE_BYTES + 1];
memset(tmp, 0, sizeof(tmp));
esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
return ESP_OK;
if (e != ESP_OK)
return e;
size_t n = strnlen(tmp, out_sz - 1);
memcpy(out, tmp, n);
out[n] = '\0';
return ESP_OK;
}
// -----------------------------------------------------------------------------
// Task / Main Loop
// -----------------------------------------------------------------------------
static void ocpp_task_func(void *param)
{
(void)param;
while (true)
{
if (enabled)
@@ -106,7 +193,6 @@ static void ocpp_task_func(void *param)
{
s_evse_enabled = operative;
// >>> enviar OCPP_EVENT (remoto → local)
ocpp_operative_event_t ev = {
.operative = operative,
.timestamp_us = esp_timer_get_time()};
@@ -115,8 +201,7 @@ static void ocpp_task_func(void *param)
&ev, sizeof(ev),
portMAX_DELAY);
ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d",
(int)operative);
ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative);
}
}
else
@@ -126,33 +211,21 @@ static void ocpp_task_func(void *param)
}
}
/* =========================
* NVS GETs
* ========================= */
// -----------------------------------------------------------------------------
// Storage GETs
// -----------------------------------------------------------------------------
bool ocpp_get_enabled(void)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{
// namespace ainda não existe -> default: disabled
return false;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
return false;
}
storage_init_best_effort();
uint8_t value = 0;
err = nvs_get_u8(h, NVS_OCPP_ENABLED, &value);
nvs_close(h);
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO);
if (err == ESP_ERR_NVS_NOT_FOUND)
return false; // default
if (err == ESP_ERR_NOT_FOUND)
return false;
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_u8(enabled) failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed: %s", esp_err_to_name(err));
return false;
}
return value != 0;
@@ -164,31 +237,12 @@ void ocpp_get_server(char *value /* out, size>=64 */)
return;
value[0] = '\0';
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{
// namespace ainda não existe -> default: ""
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
return;
}
storage_init_best_effort();
size_t len = 64;
err = nvs_get_str(h, NVS_OCPP_SERVER, value, &len);
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND)
esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64);
if (e != ESP_OK)
{
value[0] = '\0';
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_str(server) failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "store_get_str_safe(server) failed: %s", esp_err_to_name(e));
value[0] = '\0';
}
}
@@ -199,92 +253,81 @@ void ocpp_get_charge_id(char *value /* out, size>=64 */)
return;
value[0] = '\0';
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err == ESP_ERR_NVS_NOT_FOUND)
{
// namespace ainda não existe -> default: ""
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
return;
}
storage_init_best_effort();
size_t len = 64;
err = nvs_get_str(h, NVS_OCPP_CHARGE_ID, value, &len);
nvs_close(h);
if (err == ESP_ERR_NVS_NOT_FOUND)
esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64);
if (e != ESP_OK)
{
value[0] = '\0';
return;
}
if (err != ESP_OK)
{
ESP_LOGW(TAG, "nvs_get_str(charge_id) failed: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "store_get_str_safe(charge_id) failed: %s", esp_err_to_name(e));
value[0] = '\0';
}
}
/* =========================
* NVS SETs
* ========================= */
// --- SETTERS: RW (cria o namespace na 1ª escrita), commit e fecha ---
// -----------------------------------------------------------------------------
// Storage SETs
// -----------------------------------------------------------------------------
void ocpp_set_enabled(bool value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
storage_init_best_effort();
ESP_LOGI(TAG, "set enabled %d", value);
esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0);
if (e != ESP_OK)
{
ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
ESP_LOGE(TAG, "store_set_u8_best_effort(enabled) failed: %s", esp_err_to_name(e));
return;
}
ESP_LOGI(TAG, "set enabled %d", value);
ESP_ERROR_CHECK(nvs_set_u8(h, NVS_OCPP_ENABLED, value ? 1 : 0));
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
(void)store_flush_best_effort();
enabled = value;
}
void ocpp_set_server(char *value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
storage_init_best_effort();
ESP_LOGI(TAG, "set server %s", value ? value : "(null)");
esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : "");
if (e != ESP_OK)
{
ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
ESP_LOGE(TAG, "store_set_str_best_effort(server) failed: %s", esp_err_to_name(e));
return;
}
ESP_LOGI(TAG, "set server %s", value ? value : "(null)");
ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_SERVER, value ? value : ""));
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
(void)store_flush_best_effort();
}
void ocpp_set_charge_id(char *value)
{
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
storage_init_best_effort();
ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");
esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : "");
if (e != ESP_OK)
{
ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err));
ESP_LOGE(TAG, "store_set_str_best_effort(charge_id) failed: %s", esp_err_to_name(e));
return;
}
ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");
ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_CHARGE_ID, value ? value : ""));
ESP_ERROR_CHECK(nvs_commit(h));
nvs_close(h);
(void)store_flush_best_effort();
}
// -----------------------------------------------------------------------------
// Event handlers (AUTH / EVSE / METER)
// -----------------------------------------------------------------------------
static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
(void)arg;
(void)base;
(void)id;
const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data;
if (!rq)
return;
// Sanitizar/copiar a idTag
char idtag[AUTH_TAG_MAX_LEN];
if (rq->tag[0] == '\0')
{
@@ -299,16 +342,13 @@ static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, vo
ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id);
// Se não está pronto, apenas regista e sai (podes adaptar conforme política)
if (!enabled || g_ocpp_conn == NULL)
{
ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) ignoring verify", enabled, (void *)g_ocpp_conn);
ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) ignoring verify",
enabled, (void *)g_ocpp_conn);
return;
}
// Regra pedida:
// - se já existe transação/charge em andamento -> terminar
// - senão -> iniciar com a IDTAG recebida
if (ocpp_isTransactionActive())
{
ESP_LOGI(TAG, "Transaction active -> ocpp_end_transaction(\"%s\")", idtag);
@@ -323,6 +363,7 @@ static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, vo
static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
return;
@@ -343,12 +384,11 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
case EVSE_STATE_EVENT_CHARGING:
s_ev_plugged = true;
s_ev_ready = true; // EV está a pedir/receber energia
s_ev_ready = true;
break;
case EVSE_STATE_EVENT_FAULT:
default:
// em falha, considera não pronto (mantém plugged se quiseres)
s_ev_ready = false;
break;
}
@@ -356,6 +396,7 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != EVSE_EVENTS || data == NULL)
return;
@@ -363,7 +404,8 @@ static void evse_enable_available_handler(void *arg, esp_event_base_t base, int3
{
const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data;
s_evse_enabled = e->enabled;
ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)", (int)e->enabled, (long long)e->timestamp_us);
ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)",
(int)e->enabled, (long long)e->timestamp_us);
return;
}
@@ -371,23 +413,24 @@ static void evse_enable_available_handler(void *arg, esp_event_base_t base, int3
{
const evse_available_event_data_t *e = (const evse_available_event_data_t *)data;
s_evse_available = e->available;
ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)", (int)e->available, (long long)e->timestamp_us);
ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)",
(int)e->available, (long long)e->timestamp_us);
return;
}
}
static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data)
return;
const meter_event_data_t *evt = (const meter_event_data_t *)data;
// Só queremos o medidor do EVSE (não o GRID)
if (!evt->source || strcmp(evt->source, "EVSE") != 0)
return;
// Derivados simples
int32_t sum_w = (int32_t)evt->watt[0] + (int32_t)evt->watt[1] + (int32_t)evt->watt[2];
float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f;
float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2];
@@ -408,22 +451,21 @@ static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *d
portEXIT_CRITICAL(&s_meter_mux);
}
/* =========================
* MicroOCPP Inputs/CBs
* ========================= */
// -----------------------------------------------------------------------------
// MicroOCPP Inputs/CBs
// -----------------------------------------------------------------------------
bool setConnectorPluggedInput(void)
{
return s_ev_plugged; // EV fisicamente ligado
return s_ev_plugged;
}
bool setEvReadyInput(void)
{
return s_ev_ready; // EV pede / pronto a carregar
return s_ev_ready;
}
bool setEvseReadyInput(void)
{
// EVSE autorizado / operacional
return s_evse_enabled && s_evse_available;
}
@@ -439,13 +481,10 @@ float setPowerMeterInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w);
}
return (float)w;
}
@@ -461,14 +500,11 @@ float setEnergyMeterInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh);
}
return wh; // agora devolve Wh
return wh;
}
int setEnergyInput(void)
@@ -491,13 +527,10 @@ float setCurrentInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a);
}
return a;
}
@@ -513,19 +546,16 @@ float setVoltageInput(void)
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
{
ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)");
}
else
{
ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v);
}
return v;
}
float setPowerInput(void)
{
float w = setPowerMeterInput(); // alias
float w = setPowerMeterInput();
ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w);
return w;
}
@@ -598,111 +628,63 @@ bool setOnResetNotify(bool value)
void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification)
{
(void)transaction;
ESP_LOGI(TAG, "TxNotification: %d", txNotification);
switch (txNotification)
{
case Authorized:
ESP_LOGI(TAG, "Authorized");
// TODO: send event ocpp Authorized
// evse_authorize();
// Opcional: enviar idTag no payload (se tiveres como obter do transaction)
// ocpp_idtag_event_t ev = {0};
// strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, &ev, sizeof(ev), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY);
break;
case AuthorizationRejected:
ESP_LOGI(TAG, "AuthorizationRejected");
// TODO: send event ocpp AuthorizationRejected
// ocpp_idtag_event_t ev = {0};
// strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, &ev, sizeof(ev), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY);
break;
case AuthorizationTimeout:
ESP_LOGI(TAG, "AuthorizationTimeout");
// TODO: send event ocpp AuthorizationTimeout
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY);
break;
case ReservationConflict:
ESP_LOGI(TAG, "ReservationConflict");
// TODO: send event ocpp ReservationConflict
// (Se quiseres, cria um ID específico no enum e publica aqui)
break;
case ConnectionTimeout:
ESP_LOGI(TAG, "ConnectionTimeout");
// TODO: send event ocpp ConnectionTimeout
// (Se quiseres, cria um ID específico no enum e publica aqui)
break;
case DeAuthorized:
ESP_LOGI(TAG, "DeAuthorized");
// TODO: send event ocpp DeAuthorized
// TODO: adapt to the new interface
// evse_set_authorized(false);
// evse_set_limit_reached(2);
// Poderias mapear para AUTH_REJECTED ou STOP_TX por política da aplicação:
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY);
break;
case RemoteStart:
ESP_LOGI(TAG, "RemoteStart");
// TODO: send event ocpp RemoteStart
// ocpp_idtag_event_t ev = {0};
// strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, &ev, sizeof(ev), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY);
break;
case RemoteStop:
ESP_LOGI(TAG, "RemoteStop");
// TODO: send event ocpp RemoteStop
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY);
break;
case StartTx:
ESP_LOGI(TAG, "StartTx");
// TODO: send event ocpp StartTx
// ocpp_tx_event_t tx = { .tx_id = ocpp_tx_get_id(transaction) };
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, &tx, sizeof(tx), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY);
break;
case StopTx:
ESP_LOGI(TAG, "StopTx");
// TODO: send event ocpp StopTx
// TODO: adapt to the new interface
// evse_set_authorized(false);
// evse_set_limit_reached(2);
// ocpp_reason_event_t rs = {0};
// strlcpy(rs.reason, "Local", sizeof(rs.reason));
// esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, &rs, sizeof(rs), portMAX_DELAY);
esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY);
break;
}
}
// Estado de conexão simples do OCPP
bool ocpp_is_connected(void)
{
// Se quiser algo mais preciso (WS aberto), substitui por uma chamada da MicroOcpp,
// mas como fallback isto já evita o undefined symbol.
return g_ocpp_conn != NULL;
}
@@ -728,12 +710,12 @@ const char *addErrorCodeInput(void)
else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
ptr = "OtherError";
return ptr; // NULL => sem erro
return ptr;
}
/* =========================
* Start / Stop OCPP
* ========================= */
// -----------------------------------------------------------------------------
// Start / Stop OCPP
// -----------------------------------------------------------------------------
void ocpp_start(void)
{
ESP_LOGI(TAG, "Starting OCPP");
@@ -744,6 +726,8 @@ void ocpp_start(void)
return;
}
storage_init_best_effort();
enabled = ocpp_get_enabled();
if (!enabled)
{
@@ -767,15 +751,14 @@ void ocpp_start(void)
return;
}
/* Inicializar Mongoose + MicroOcpp */
mg_mgr_init(&mgr);
mg_log_set(MG_LL_ERROR);
struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true};
g_ocpp_conn = ocpp_makeConnection(&mgr,
serverstr, /* ex: ws://host:port/OCPP16/... */
charge_id, /* ChargeBoxId / identity */
serverstr,
charge_id,
"",
"",
fsopt);
@@ -788,19 +771,16 @@ void ocpp_start(void)
ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false);
/* Inputs/outputs e callbacks */
ocpp_setEvReadyInput(&setEvReadyInput);
ocpp_setEvseReadyInput(&setEvseReadyInput);
ocpp_setConnectorPluggedInput(&setConnectorPluggedInput);
ocpp_setOnResetExecute(&OnResetExecute);
ocpp_setTxNotificationOutput(&notificationOutput);
// ocpp_setStartTxReadyInput(&setStartTxReadyInput);
ocpp_setStopTxReadyInput(&setStopTxReadyInput);
ocpp_setOnResetNotify(&setOnResetNotify);
ocpp_setEnergyMeterInput(&setEnergyInput); // inteiro Wh
ocpp_setEnergyMeterInput(&setEnergyInput);
/* Metering */
ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&getCurrentOffered, "Current.Offered", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL);
@@ -810,8 +790,7 @@ void ocpp_start(void)
ocpp_addErrorCodeInput(&addErrorCodeInput);
/* Task */
xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 5, &ocpp_task);
xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task);
if (!s_auth_verify_inst)
{
@@ -821,7 +800,6 @@ void ocpp_start(void)
ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener");
}
// ouvir mudanças de estado do EVSE
if (!s_evse_state_inst)
{
ESP_ERROR_CHECK(esp_event_handler_instance_register(
@@ -829,7 +807,6 @@ void ocpp_start(void)
&evse_event_handler, NULL, &s_evse_state_inst));
}
// ouvir mudanças de ENABLE / AVAILABLE do EVSE (Local → OCPP)
if (!s_evse_enable_inst)
{
ESP_ERROR_CHECK(esp_event_handler_instance_register(

View File

@@ -6,13 +6,11 @@ set(srcs
"src/ac_relay.c"
"src/socket_lock.c"
"src/rcm.c"
"src/onewire.c"
"src/ds18x20.c"
"src/temp_sensor.c"
"src/ntc_sensor.c"
)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash driver esp_adc esp_timer
REQUIRES config evse ntc_driver spi_bus_manager)
PRIV_REQUIRES driver esp_adc esp_timer
REQUIRES config evse ntc_driver spi_bus_manager storage_service)

View File

@@ -1,12 +1,35 @@
// components/peripherals/include/adc121s021_dma.h
#ifndef ADC_DMA_H_
#define ADC_DMA_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Inicializa o ADC121S021 no barramento SPI partilhado.
*
* - Garante que o spi_bus_manager foi inicializado.
* - Regista o dispositivo ADC no bus.
*/
void adc121s021_dma_init(void);
/**
* @brief Lê uma única amostra (12 bits) do ADC121S021.
*
* Esta função faz uma transação SPI bloqueante (polling), suficientemente
* rápida para uso em burst (100 amostras em ~23 ms).
*
* @param[out] sample Ponteiro onde será escrito o valor lido (0..4095).
* @return true em caso de sucesso, false se ocorrer erro.
*/
bool adc121s021_dma_get_sample(uint16_t *sample);
#ifdef __cplusplus
}
#endif
#endif /* ADC_DMA_h_ */
#endif /* ADC_DMA_H_ */

View File

@@ -1,254 +0,0 @@
/*
* Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
* Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
* Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _DS18X20_H
#define _DS18X20_H
#include <esp_err.h>
#include "onewire.h"
typedef onewire_addr_t ds18x20_addr_t;
/** An address value which can be used to indicate "any device on the bus" */
#define DS18X20_ANY ONEWIRE_NONE
/** Family ID (lower address byte) of DS18B20 sensors */
#define DS18B20_FAMILY_ID 0x28
/** Family ID (lower address byte) of DS18S20 sensors */
#define DS18S20_FAMILY_ID 0x10
/**
* @brief Find the addresses of all ds18x20 devices on the bus.
*
* Scans the bus for all devices and places their addresses in the supplied
* array. If there are more than `addr_count` devices on the bus, only the
* first `addr_count` are recorded.
*
* @param pin The GPIO pin connected to the ds18x20 bus
* @param addr_list A pointer to an array of ::ds18x20_addr_t values.
* This will be populated with the addresses of the found
* devices.
* @param addr_count Number of slots in the `addr_list` array. At most this
* many addresses will be returned.
* @param found The number of devices found. Note that this may be less
* than, equal to, or more than `addr_count`, depending on
* how many ds18x20 devices are attached to the bus.
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, size_t *found);
/**
* @brief Tell one or more sensors to perform a temperature measurement and
* conversion (CONVERT_T) operation.
*
* This operation can take up to 750ms to complete.
*
* If `wait=true`, this routine will automatically drive the pin high for the
* necessary 750ms after issuing the command to ensure parasitically-powered
* devices have enough power to perform the conversion operation (for
* non-parasitically-powered devices, this is not necessary but does not
* hurt). If `wait=false`, this routine will drive the pin high, but will
* then return immediately. It is up to the caller to wait the requisite time
* and then depower the bus using onewire_depower() or by issuing another
* command once conversion is done.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device on the bus. This can be set
* to ::DS18X20_ANY to send the command to all devices on the bus
* at the same time.
* @param wait Whether to wait for the necessary 750ms for the ds18x20 to
* finish performing the conversion before returning to the
* caller (You will normally want to do this).
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait);
/**
* @brief Read the value from the last CONVERT_T operation.
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Read the value from the last CONVERT_T operation (ds18b20 version).
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Read the value from the last CONVERT_T operation (ds18s20 version).
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Read the value from the last CONVERT_T operation for multiple devices.
*
* This should be called after ds18x20_measure() to fetch the result of the
* temperature measurement.
*
* @param pin The GPIO pin connected to the ds18x20 bus
* @param addr_list A list of addresses for devices to read.
* @param addr_count The number of entries in `addr_list`.
* @param result_list An array of int16_ts to hold the returned temperature
* values. It should have at least `addr_count` entries.
*
* @returns `ESP_OK` if all temperatures were fetched successfully
*/
esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list);
/** Perform a ds18x20_measure() followed by ds18s20_read_temperature()
*
* @param pin The GPIO pin connected to the ds18s20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*/
esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/** Perform a ds18x20_measure() followed by ds18b20_read_temperature()
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*/
esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/** Perform a ds18x20_measure() followed by ds18x20_read_temperature()
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param temperature The temperature in degrees Celsius
*/
esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t *temperature);
/**
* @brief Perform a ds18x20_measure() followed by ds18x20_read_temp_multi()
*
* @param pin The GPIO pin connected to the ds18x20 bus
* @param addr_list A list of addresses for devices to read.
* @param addr_count The number of entries in `addr_list`.
* @param result_list An array of int16_ts to hold the returned temperature
* values. It should have at least `addr_count` entries.
*
* @returns `ESP_OK` if all temperatures were fetched successfully
*/
esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t *addr_list, size_t addr_count, int16_t *result_list);
/**
* @brief Read the scratchpad data for a particular ds18x20 device.
*
* This is not generally necessary to do directly. It is done automatically
* as part of ds18x20_read_temperature().
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to read. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param buffer An 8-byte buffer to hold the read data.
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer);
/**
* @brief Write the scratchpad data for a particular ds18x20 device.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to write. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
* @param buffer An 3-byte buffer to hold the data to write
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t *buffer);
/**
* @brief Issue the copy scratchpad command, copying current scratchpad to
* EEPROM.
*
* @param pin The GPIO pin connected to the ds18x20 device
* @param addr The 64-bit address of the device to command. This can be set
* to ::DS18X20_ANY to read any device on the bus (but note
* that this will only work if there is exactly one device
* connected, or they will corrupt each others' transmissions)
*
* @returns `ESP_OK` if the command was successfully issued
*/
esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr);
#endif /* _DS18X20_H */

View File

@@ -1,277 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 zeroday nodemcu.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -------------------------------------------------------------------------------
* Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
* following additional terms:
*
* Except as contained in this notice, the name of Dallas Semiconductor
* shall not be used except as stated in the Dallas Semiconductor
* Branding Policy.
*/
#ifndef ONEWIRE_H_
#define ONEWIRE_H_
#include <stdbool.h>
#include <stdint.h>
#include "driver/gpio.h"
/**
* Type used to hold all 1-Wire device ROM addresses (64-bit)
*/
typedef uint64_t onewire_addr_t;
/**
* Structure to contain the current state for onewire_search_next(), etc
*/
typedef struct
{
uint8_t rom_no[8];
uint8_t last_discrepancy;
bool last_device_found;
} onewire_search_t;
/**
* ::ONEWIRE_NONE is an invalid ROM address that will never occur in a device
* (CRC mismatch), and so can be useful as an indicator for "no-such-device",
* etc.
*/
#define ONEWIRE_NONE ((onewire_addr_t)(0xffffffffffffffffLL))
/**
* @brief Perform a 1-Wire reset cycle.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return `true` if at least one device responds with a presence pulse,
* `false` if no devices were detected (or the bus is shorted, etc)
*/
bool onewire_reset(gpio_num_t pin);
/**
* @brief Issue a 1-Wire "ROM select" command to select a particular device.
*
* It is necessary to call ::onewire_reset() before calling this function.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param addr The ROM address of the device to select
*
* @return `true` if the "ROM select" command could be successfully issued,
* `false` if there was an error.
*/
bool onewire_select(gpio_num_t pin, const onewire_addr_t addr);
/**
* @brief Issue a 1-Wire "skip ROM" command to select *all* devices on the bus.
*
* It is necessary to call ::onewire_reset() before calling this function.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return `true` if the "skip ROM" command could be successfully issued,
* `false` if there was an error.
*/
bool onewire_skip_rom(gpio_num_t pin);
/**
* @brief Write a byte on the onewire bus.
*
* The writing code uses open-drain mode and expects the pullup resistor to
* pull the line high when not driven low. If you need strong power after the
* write (e.g. DS18B20 in parasite power mode) then call ::onewire_power()
* after this is complete to actively drive the line high.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param v The byte value to write
*
* @return `true` if successful, `false` on error.
*/
bool onewire_write(gpio_num_t pin, uint8_t v);
/**
* @brief Write multiple bytes on the 1-Wire bus.
*
* See ::onewire_write() for more info.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param buf A pointer to the buffer of bytes to be written
* @param count Number of bytes to write
*
* @return `true` if all bytes written successfully, `false` on error.
*/
bool onewire_write_bytes(gpio_num_t pin, const uint8_t *buf, size_t count);
/**
* @brief Read a byte from a 1-Wire device.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return the read byte on success, negative value on error.
*/
int onewire_read(gpio_num_t pin);
/**
* @brief Read multiple bytes from a 1-Wire device.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
* @param[out] buf A pointer to the buffer to contain the read bytes
* @param count Number of bytes to read
*
* @return `true` on success, `false` on error.
*/
bool onewire_read_bytes(gpio_num_t pin, uint8_t *buf, size_t count);
/**
* @brief Actively drive the bus high to provide extra power for certain
* operations of parasitically-powered devices.
*
* For parasitically-powered devices which need more power than can be
* provided via the normal pull-up resistor, it may be necessary for some
* operations to drive the bus actively high. This function can be used to
* perform that operation.
*
* The bus can be depowered once it is no longer needed by calling
* ::onewire_depower(), or it will be depowered automatically the next time
* ::onewire_reset() is called to start another command.
*
* @note Make sure the device(s) you are powering will not pull more current
* than the ESP32/ESP8266 is able to supply via its GPIO pins (this is
* especially important when multiple devices are on the same bus and
* they are all performing a power-intensive operation at the same time
* (i.e. multiple DS18B20 sensors, which have all been given a
* "convert T" operation by using ::onewire_skip_rom())).
*
* @note This routine will check to make sure that the bus is already high
* before driving it, to make sure it doesn't attempt to drive it high
* while something else is pulling it low (which could cause a reset or
* damage the ESP32/ESP8266).
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*
* @return `true` on success, `false` on error.
*/
bool onewire_power(gpio_num_t pin);
/**
* @brief Stop forcing power onto the bus.
*
* You only need to do this if you previously called ::onewire_power() to drive
* the bus high and now want to allow it to float instead. Note that
* onewire_reset() will also automatically depower the bus first, so you do
* not need to call this first if you just want to start a new operation.
*
* @param pin The GPIO pin connected to the 1-Wire bus.
*/
void onewire_depower(gpio_num_t pin);
/**
* @brief Clear the search state so that it will start from the beginning on
* the next call to ::onewire_search_next().
*
* @param[out] search The onewire_search_t structure to reset.
*/
void onewire_search_start(onewire_search_t *search);
/**
* @brief Setup the search to search for devices with the specified
* "family code".
*
* @param[out] search The onewire_search_t structure to update.
* @param family_code The "family code" to search for.
*/
void onewire_search_prefix(onewire_search_t *search, uint8_t family_code);
/**
* @brief Search for the next device on the bus.
*
* The order of returned device addresses is deterministic. You will always
* get the same devices in the same order.
*
* @note It might be a good idea to check the CRC to make sure you didn't get
* garbage.
*
* @return the address of the next device on the bus, or ::ONEWIRE_NONE if
* there is no next address. ::ONEWIRE_NONE might also mean that
* the bus is shorted, there are no devices, or you have already
* retrieved all of them.
*/
onewire_addr_t onewire_search_next(onewire_search_t *search, gpio_num_t pin);
/**
* @brief Compute a Dallas Semiconductor 8 bit CRC.
*
* These are used in the ROM address and scratchpad registers to verify the
* transmitted data is correct.
*/
uint8_t onewire_crc8(const uint8_t *data, uint8_t len);
/**
* @brief Compute the 1-Wire CRC16 and compare it against the received CRC.
*
* Example usage (reading a DS2408):
* @code{.c}
* // Put everything in a buffer so we can compute the CRC easily.
* uint8_t buf[13];
* buf[0] = 0xF0; // Read PIO Registers
* buf[1] = 0x88; // LSB address
* buf[2] = 0x00; // MSB address
* onewire_write_bytes(pin, buf, 3); // Write 3 cmd bytes
* onewire_read_bytes(pin, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
* if (!onewire_check_crc16(buf, 11, &buf[11])) {
* // TODO: Handle error.
* }
* @endcode
*
* @param input Array of bytes to checksum.
* @param len Number of bytes in `input`
* @param inverted_crc The two CRC16 bytes in the received data.
* This should just point into the received data,
* *not* at a 16-bit integer.
* @param crc_iv The crc starting value (optional)
*
* @return `true` if the CRC matches, `false` otherwise.
*/
bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv);
/**
* @brief Compute a Dallas Semiconductor 16 bit CRC.
*
* This is required to check the integrity of data received from many 1-Wire
* devices. Note that the CRC computed here is *not* what you'll get from the
* 1-Wire network, for two reasons:
*
* 1. The CRC is transmitted bitwise inverted.
* 2. Depending on the endian-ness of your processor, the binary
* representation of the two-byte return value may have a different
* byte order than the two bytes you get from 1-Wire.
*
* @param input Array of bytes to checksum.
* @param len How many bytes are in `input`.
* @param crc_iv The crc starting value (optional)
*
* @return the CRC16, as defined by Dallas Semiconductor.
*/
uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv);
#endif /* ONEWIRE_H_ */

View File

@@ -1,28 +1,41 @@
// components/peripherals/src/adc121s021_dma.c
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_err.h"
#include "adc121s021_dma.h"
#include "spi_bus_manager.h"
#define TAG "adc_dma"
#define PIN_NUM_CS 5
#define SAMPLE_SIZE_BYTES 2
#define ADC_BITS 12
#define SPI_CLOCK_HZ (6 * 1000 * 1000) // 6 MHz
// Pino de chip-select do ADC121S021 (ajusta se necessário)
#define PIN_NUM_CS 5
// ADC é 12-bit, mas transferimos 16 bits via SPI
#define ADC_BITS 12
// Clock SPI: 1 MHz → ~16 µs de transferência por amostra.
// Com um pequeno delay entre leituras, 100 amostras ficam em ~23 ms,
// o que é perfeito para analisar um PWM de 1 kHz a cada 100 ms.
#define SPI_CLOCK_HZ (1 * 1000 * 1000)
static spi_device_handle_t adc_spi = NULL;
void adc121s021_dma_init(void)
{
if (adc_spi) {
if (adc_spi)
{
ESP_LOGW(TAG, "ADC121S021 já foi inicializado.");
return;
}
if (!spi_bus_manager_is_initialized()) {
// Garante que o SPI bus partilhado está configurado
if (!spi_bus_manager_is_initialized())
{
ESP_LOGI(TAG, "SPI bus não inicializado. Inicializando...");
esp_err_t err = spi_bus_manager_init(); // 🔧 CORRIGIDO: sem argumentos
if (err != ESP_OK) {
esp_err_t err = spi_bus_manager_init();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Falha ao inicializar o SPI bus: %s", esp_err_to_name(err));
return;
}
@@ -32,44 +45,55 @@ void adc121s021_dma_init(void)
.clock_speed_hz = SPI_CLOCK_HZ,
.mode = 0,
.spics_io_num = PIN_NUM_CS,
.queue_size = 2,
.queue_size = 2, // suficiente para uso em burst
.flags = SPI_DEVICE_NO_DUMMY,
.pre_cb = NULL,
.post_cb = NULL,
};
esp_err_t err = spi_bus_add_device(spi_bus_manager_get_host(), &devcfg, &adc_spi);
if (err != ESP_OK) {
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Falha ao registrar ADC121S021 no SPI: %s", esp_err_to_name(err));
adc_spi = NULL;
return;
}
ESP_LOGI(TAG, "ADC121S021 registrado no SPI com sucesso.");
ESP_LOGI(TAG, "ADC121S021 registrado no SPI (CS=%d, fSPI=%d Hz).",
PIN_NUM_CS, SPI_CLOCK_HZ);
}
bool adc121s021_dma_get_sample(uint16_t *sample)
{
if (!adc_spi) {
ESP_LOGE(TAG, "ADC SPI não inicializado!");
if (!sample)
{
return false;
}
uint8_t tx_buffer[2] = {0x00, 0x00}; // Dummy
if (!adc_spi)
{
ESP_LOGE(TAG, "ADC SPI não inicializado! Chama adc121s021_dma_init() primeiro.");
return false;
}
uint8_t tx_buffer[2] = {0x00, 0x00}; // Dummy (ADC só precisa de clock)
uint8_t rx_buffer[2] = {0};
spi_transaction_t t = {
.length = 16,
.length = 16, // 16 bits
.tx_buffer = tx_buffer,
.rx_buffer = rx_buffer,
.flags = 0
};
.flags = 0};
esp_err_t err = spi_device_transmit(adc_spi, &t);
if (err != ESP_OK) {
// Polling transmit → menor overhead que fila + espera.
esp_err_t err = spi_device_polling_transmit(adc_spi, &t);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Erro na transmissão SPI: %s", esp_err_to_name(err));
return false;
}
*sample = ((rx_buffer[0] << 8) | rx_buffer[1]) & 0x0FFF;
// ADC121S021 devolve os 12 bits mais significativos em 16 bits.
*sample = (uint16_t)(((rx_buffer[0] << 8) | rx_buffer[1]) & 0x0FFF);
return true;
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright (c) 2016 Grzegorz Hetman <ghetman@gmail.com>
* Copyright (c) 2016 Alex Stewart <foogod@gmail.com>
* Copyright (c) 2018 Ruslan V. Uss <unclerus@gmail.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of itscontributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <math.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "ds18x20.h"
#define ds18x20_WRITE_SCRATCHPAD 0x4E
#define ds18x20_READ_SCRATCHPAD 0xBE
#define ds18x20_COPY_SCRATCHPAD 0x48
#define ds18x20_READ_EEPROM 0xB8
#define ds18x20_READ_PWRSUPPLY 0xB4
#define ds18x20_SEARCHROM 0xF0
#define ds18x20_SKIP_ROM 0xCC
#define ds18x20_READROM 0x33
#define ds18x20_MATCHROM 0x55
#define ds18x20_ALARMSEARCH 0xEC
#define ds18x20_CONVERT_T 0x44
#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
static const char* TAG = "ds18x20";
esp_err_t ds18x20_measure(gpio_num_t pin, ds18x20_addr_t addr, bool wait)
{
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
portENTER_CRITICAL(&mux);
onewire_write(pin, ds18x20_CONVERT_T);
// For parasitic devices, power must be applied within 10us after issuing
// the convert command.
onewire_power(pin);
portEXIT_CRITICAL(&mux);
if (wait){
vTaskDelay(pdMS_TO_TICKS(750));
onewire_depower(pin);
}
return ESP_OK;
}
esp_err_t ds18x20_read_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer)
{
CHECK_ARG(buffer);
uint8_t crc;
uint8_t expected_crc;
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
onewire_write(pin, ds18x20_READ_SCRATCHPAD);
for (int i = 0; i < 8; i++)
buffer[i] = onewire_read(pin);
crc = onewire_read(pin);
expected_crc = onewire_crc8(buffer, 8);
if (crc != expected_crc)
{
ESP_LOGE(TAG, "CRC check failed reading scratchpad: %02x %02x %02x %02x %02x %02x %02x %02x : %02x (expected %02x)", buffer[0], buffer[1],
buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], crc, expected_crc);
return ESP_ERR_INVALID_CRC;
}
return ESP_OK;
}
esp_err_t ds18x20_write_scratchpad(gpio_num_t pin, ds18x20_addr_t addr, uint8_t* buffer)
{
CHECK_ARG(buffer);
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
onewire_write(pin, ds18x20_WRITE_SCRATCHPAD);
for (int i = 0; i < 3; i++)
onewire_write(pin, buffer[i]);
return ESP_OK;
}
esp_err_t ds18x20_copy_scratchpad(gpio_num_t pin, ds18x20_addr_t addr)
{
if (!onewire_reset(pin))
return ESP_ERR_INVALID_RESPONSE;
if (addr == DS18X20_ANY)
onewire_skip_rom(pin);
else
onewire_select(pin, addr);
portENTER_CRITICAL(&mux);
onewire_write(pin, ds18x20_COPY_SCRATCHPAD);
// For parasitic devices, power must be applied within 10us after issuing
// the convert command.
onewire_power(pin);
portEXIT_CRITICAL(&mux);
// And then it needs to keep that power up for 10ms.
vTaskDelay(pdMS_TO_TICKS(10));
onewire_depower(pin);
return ESP_OK;
}
esp_err_t ds18b20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
uint8_t scratchpad[8];
int16_t temp;
CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad));
temp = scratchpad[1] << 8 | scratchpad[0];
*temperature = ((int16_t)temp * 625.0) / 100;
return ESP_OK;
}
esp_err_t ds18s20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
uint8_t scratchpad[8];
int16_t temp;
CHECK(ds18x20_read_scratchpad(pin, addr, scratchpad));
temp = scratchpad[1] << 8 | scratchpad[0];
temp = ((temp & 0xfffe) << 3) + (16 - scratchpad[6]) - 4;
*temperature = (temp * 625) / 100 - 25;
return ESP_OK;
}
esp_err_t ds18x20_read_temperature(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
if ((uint8_t)addr == DS18B20_FAMILY_ID) {
return ds18b20_read_temperature(pin, addr, temperature);
} else {
return ds18s20_read_temperature(pin, addr, temperature);
}
}
esp_err_t ds18b20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
CHECK(ds18x20_measure(pin, addr, true));
return ds18b20_read_temperature(pin, addr, temperature);
}
esp_err_t ds18s20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
CHECK(ds18x20_measure(pin, addr, true));
return ds18s20_read_temperature(pin, addr, temperature);
}
esp_err_t ds18x20_measure_and_read(gpio_num_t pin, ds18x20_addr_t addr, int16_t* temperature)
{
CHECK_ARG(temperature);
CHECK(ds18x20_measure(pin, addr, true));
return ds18x20_read_temperature(pin, addr, temperature);
}
esp_err_t ds18x20_measure_and_read_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list)
{
CHECK_ARG(result_list && addr_count);
CHECK(ds18x20_measure(pin, DS18X20_ANY, true));
return ds18x20_read_temp_multi(pin, addr_list, addr_count, result_list);
}
esp_err_t ds18x20_scan_devices(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, size_t* found)
{
CHECK_ARG(addr_list && addr_count);
onewire_search_t search;
onewire_addr_t addr;
*found = 0;
onewire_search_start(&search);
while ((addr = onewire_search_next(&search, pin)) != ONEWIRE_NONE)
{
uint8_t family_id = (uint8_t)addr;
if (family_id == DS18B20_FAMILY_ID || family_id == DS18S20_FAMILY_ID)
{
if (*found < addr_count)
addr_list[*found] = addr;
*found += 1;
}
}
return ESP_OK;
}
esp_err_t ds18x20_read_temp_multi(gpio_num_t pin, ds18x20_addr_t* addr_list, size_t addr_count, int16_t* result_list)
{
CHECK_ARG(result_list);
esp_err_t res = ESP_OK;
for (size_t i = 0; i < addr_count; i++)
{
esp_err_t tmp = ds18x20_read_temperature(pin, addr_list[i], &result_list[i]);
if (tmp != ESP_OK)
res = tmp;
}
return res;
}

View File

@@ -1,498 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 zeroday nodemcu.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* -------------------------------------------------------------------------------
* Portions copyright (C) 2000 Dallas Semiconductor Corporation, under the
* following additional terms:
*
* Except as contained in this notice, the name of Dallas Semiconductor
* shall not be used except as stated in the Dallas Semiconductor
* Branding Policy.
*/
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "rom/ets_sys.h"
#include "onewire.h"
#define ONEWIRE_SELECT_ROM 0x55
#define ONEWIRE_SKIP_ROM 0xcc
#define ONEWIRE_SEARCH 0xf0
#define ONEWIRE_CRC8_TABLE
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
// Waits up to `max_wait` microseconds for the specified pin to go high.
// Returns true if successful, false if the bus never comes high (likely
// shorted).
static inline bool _onewire_wait_for_bus(gpio_num_t pin, int max_wait)
{
bool state;
for (int i = 0; i < ((max_wait + 4) / 5); i++) {
if (gpio_get_level(pin))
break;
ets_delay_us(5);
}
state = gpio_get_level(pin);
// Wait an extra 1us to make sure the devices have an adequate recovery
// time before we drive things low again.
ets_delay_us(1);
return state;
}
static void setup_pin(gpio_num_t pin, bool open_drain)
{
gpio_set_direction(pin, open_drain ? GPIO_MODE_INPUT_OUTPUT_OD : GPIO_MODE_OUTPUT);
// gpio_set_pull_mode(pin, GPIO_PULLUP_ONLY);
}
// Perform the onewire reset function. We will wait up to 250uS for
// the bus to come high, if it doesn't then it is broken or shorted
// and we return false;
//
// Returns true if a device asserted a presence pulse, false otherwise.
//
bool onewire_reset(gpio_num_t pin)
{
setup_pin(pin, true);
gpio_set_level(pin, 1);
// wait until the wire is high... just in case
if (!_onewire_wait_for_bus(pin, 250))
return false;
gpio_set_level(pin, 0);
ets_delay_us(480);
portENTER_CRITICAL(&mux);
gpio_set_level(pin, 1); // allow it to float
ets_delay_us(70);
bool r = !gpio_get_level(pin);
portEXIT_CRITICAL(&mux);
// Wait for all devices to finish pulling the bus low before returning
if (!_onewire_wait_for_bus(pin, 410))
return false;
return r;
}
static bool _onewire_write_bit(gpio_num_t pin, bool v)
{
if (!_onewire_wait_for_bus(pin, 10))
return false;
portENTER_CRITICAL(&mux);
if (v) {
gpio_set_level(pin, 0); // drive output low
ets_delay_us(10);
gpio_set_level(pin, 1); // allow output high
ets_delay_us(55);
} else {
gpio_set_level(pin, 0); // drive output low
ets_delay_us(65);
gpio_set_level(pin, 1); // allow output high
}
ets_delay_us(1);
portEXIT_CRITICAL(&mux);
return true;
}
static int _onewire_read_bit(gpio_num_t pin)
{
if (!_onewire_wait_for_bus(pin, 10))
return -1;
portENTER_CRITICAL(&mux);
gpio_set_level(pin, 0);
ets_delay_us(2);
gpio_set_level(pin, 1); // let pin float, pull up will raise
ets_delay_us(11);
int r = gpio_get_level(pin); // Must sample within 15us of start
ets_delay_us(48);
portEXIT_CRITICAL(&mux);
return r;
}
// Write a byte. The writing code uses open-drain mode and expects the pullup
// resistor to pull the line high when not driven low. If you need strong
// power after the write (e.g. DS18B20 in parasite power mode) then call
// onewire_power() after this is complete to actively drive the line high.
//
bool onewire_write(gpio_num_t pin, uint8_t v)
{
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1)
if (!_onewire_write_bit(pin, (bitMask & v)))
return false;
return true;
}
bool onewire_write_bytes(gpio_num_t pin, const uint8_t* buf, size_t count)
{
for (size_t i = 0; i < count; i++)
if (!onewire_write(pin, buf[i]))
return false;
return true;
}
// Read a byte
//
int onewire_read(gpio_num_t pin)
{
int r = 0;
for (uint8_t bitMask = 0x01; bitMask; bitMask <<= 1) {
int bit = _onewire_read_bit(pin);
if (bit < 0)
return -1;
else if (bit)
r |= bitMask;
}
return r;
}
bool onewire_read_bytes(gpio_num_t pin, uint8_t* buf, size_t count)
{
size_t i;
int b;
for (i = 0; i < count; i++) {
b = onewire_read(pin);
if (b < 0)
return false;
buf[i] = b;
}
return true;
}
bool onewire_select(gpio_num_t pin, onewire_addr_t addr)
{
uint8_t i;
if (!onewire_write(pin, ONEWIRE_SELECT_ROM))
return false;
for (i = 0; i < 8; i++) {
if (!onewire_write(pin, addr & 0xff))
return false;
addr >>= 8;
}
return true;
}
bool onewire_skip_rom(gpio_num_t pin)
{
return onewire_write(pin, ONEWIRE_SKIP_ROM);
}
bool onewire_power(gpio_num_t pin)
{
// Make sure the bus is not being held low before driving it high, or we
// may end up shorting ourselves out.
if (!_onewire_wait_for_bus(pin, 10))
return false;
setup_pin(pin, false);
gpio_set_level(pin, 1);
return true;
}
void onewire_depower(gpio_num_t pin)
{
setup_pin(pin, true);
}
void onewire_search_start(onewire_search_t* search)
{
// reset the search state
memset(search, 0, sizeof(*search));
}
void onewire_search_prefix(onewire_search_t* search, uint8_t family_code)
{
uint8_t i;
search->rom_no[0] = family_code;
for (i = 1; i < 8; i++) {
search->rom_no[i] = 0;
}
search->last_discrepancy = 64;
search->last_device_found = false;
}
// Perform a search. If the next device has been successfully enumerated, its
// ROM address will be returned. If there are no devices, no further
// devices, or something horrible happens in the middle of the
// enumeration then ONEWIRE_NONE is returned. Use OneWire::reset_search() to
// start over.
//
// --- Replaced by the one from the Dallas Semiconductor web site ---
//--------------------------------------------------------------------------
// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
// search state.
// Return 1 : device found, ROM number in ROM_NO buffer
// 0 : device not found, end of search
//
onewire_addr_t onewire_search_next(onewire_search_t* search, gpio_num_t pin)
{
//TODO: add more checking for read/write errors
uint8_t id_bit_number;
uint8_t last_zero, search_result;
int rom_byte_number;
int8_t id_bit, cmp_id_bit;
onewire_addr_t addr;
unsigned char rom_byte_mask;
bool search_direction;
// initialize for search
id_bit_number = 1;
last_zero = 0;
rom_byte_number = 0;
rom_byte_mask = 1;
search_result = 0;
// if the last call was not the last one
if (!search->last_device_found) {
// 1-Wire reset
if (!onewire_reset(pin)) {
// reset the search
search->last_discrepancy = 0;
search->last_device_found = false;
return ONEWIRE_NONE;
}
// issue the search command
onewire_write(pin, ONEWIRE_SEARCH);
// loop to do the search
do {
// read a bit and its complement
id_bit = _onewire_read_bit(pin);
cmp_id_bit = _onewire_read_bit(pin);
if ((id_bit == 1) && (cmp_id_bit == 1))
break;
else {
// all devices coupled have 0 or 1
if (id_bit != cmp_id_bit)
search_direction = id_bit; // bit write value for search
else {
// if this discrepancy if before the Last Discrepancy
// on a previous next then pick the same as last time
if (id_bit_number < search->last_discrepancy)
search_direction = ((search->rom_no[rom_byte_number] & rom_byte_mask) > 0);
else
// if equal to last pick 1, if not then pick 0
search_direction = (id_bit_number == search->last_discrepancy);
// if 0 was picked then record its position in LastZero
if (!search_direction)
last_zero = id_bit_number;
}
// set or clear the bit in the ROM byte rom_byte_number
// with mask rom_byte_mask
if (search_direction)
search->rom_no[rom_byte_number] |= rom_byte_mask;
else
search->rom_no[rom_byte_number] &= ~rom_byte_mask;
// serial number search direction write bit
_onewire_write_bit(pin, search_direction);
// increment the byte counter id_bit_number
// and shift the mask rom_byte_mask
id_bit_number++;
rom_byte_mask <<= 1;
// if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
if (rom_byte_mask == 0) {
rom_byte_number++;
rom_byte_mask = 1;
}
}
} while (rom_byte_number < 8); // loop until through all ROM bytes 0-7
// if the search was successful then
if (!(id_bit_number < 65)) {
// search successful so set last_discrepancy,last_device_found,search_result
search->last_discrepancy = last_zero;
// check for last device
if (search->last_discrepancy == 0)
search->last_device_found = true;
search_result = 1;
}
}
// if no device found then reset counters so next 'search' will be like a first
if (!search_result || !search->rom_no[0]) {
search->last_discrepancy = 0;
search->last_device_found = false;
return ONEWIRE_NONE;
} else {
addr = 0;
for (rom_byte_number = 7; rom_byte_number >= 0; rom_byte_number--) {
addr = (addr << 8) | search->rom_no[rom_byte_number];
}
//printf("Ok I found something at %08x%08x...\n", (uint32_t)(addr >> 32), (uint32_t)addr);
}
return addr;
}
// The 1-Wire CRC scheme is described in Maxim Application Note 27:
// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
//
#ifdef ONEWIRE_CRC8_TABLE
// This table comes from Dallas sample code where it is freely reusable,
// though Copyright (c) 2000 Dallas Semiconductor Corporation
static const uint8_t dscrc_table[] = {
0, 94, 188, 226, 97, 63, 221, 131, 194, 156, 126, 32, 163, 253, 31, 65,
157, 195, 33, 127, 252, 162, 64, 30, 95, 1, 227, 189, 62, 96, 130, 220,
35, 125, 159, 193, 66, 28, 254, 160, 225, 191, 93, 3, 128, 222, 60, 98,
190, 224, 2, 92, 223, 129, 99, 61, 124, 34, 192, 158, 29, 67, 161, 255,
70, 24, 250, 164, 39, 121, 155, 197, 132, 218, 56, 102, 229, 187, 89, 7,
219, 133, 103, 57, 186, 228, 6, 88, 25, 71, 165, 251, 120, 38, 196, 154,
101, 59, 217, 135, 4, 90, 184, 230, 167, 249, 27, 69, 198, 152, 122, 36,
248, 166, 68, 26, 153, 199, 37, 123, 58, 100, 134, 216, 91, 5, 231, 185,
140, 210, 48, 110, 237, 179, 81, 15, 78, 16, 242, 172, 47, 113, 147, 205,
17, 79, 173, 243, 112, 46, 204, 146, 211, 141, 111, 49, 178, 236, 14, 80,
175, 241, 19, 77, 206, 144, 114, 44, 109, 51, 209, 143, 12, 82, 176, 238,
50, 108, 142, 208, 83, 13, 239, 177, 240, 174, 76, 18, 145, 207, 45, 115,
202, 148, 118, 40, 171, 245, 23, 73, 8, 86, 180, 234, 105, 55, 213, 139,
87, 9, 235, 181, 54, 104, 138, 212, 149, 203, 41, 119, 244, 170, 72, 22,
233, 183, 85, 11, 136, 214, 52, 106, 43, 117, 151, 201, 74, 20, 246, 168,
116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
};
//
// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
// and the registers. (note: this might better be done without to
// table, it would probably be smaller and certainly fast enough
// compared to all those delayMicrosecond() calls. But I got
// confused, so I use this table from the examples.)
//
uint8_t onewire_crc8(const uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
while (len--)
crc = dscrc_table[crc ^ *data++];
return crc;
}
#else
//
// Compute a Dallas Semiconductor 8 bit CRC directly.
// this is much slower, but much smaller, than the lookup table.
//
uint8_t onewire_crc8(const uint8_t* data, uint8_t len)
{
uint8_t crc = 0;
while (len--)
{
uint8_t inbyte = *data++;
for (int i = 8; i; i--)
{
uint8_t mix = (crc ^ inbyte) & 0x01;
crc >>= 1;
if (mix)
crc ^= 0x8C;
inbyte >>= 1;
}
}
return crc;
}
#endif /* ONEWIRE_CRC8_TABLE */
// Compute the 1-Wire CRC16 and compare it against the received CRC.
// Example usage (reading a DS2408):
// // Put everything in a buffer so we can compute the CRC easily.
// uint8_t buf[13];
// buf[0] = 0xF0; // Read PIO Registers
// buf[1] = 0x88; // LSB address
// buf[2] = 0x00; // MSB address
// WriteBytes(net, buf, 3); // Write 3 cmd bytes
// ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
// if (!CheckCRC16(buf, 11, &buf[11])) {
// // Handle error.
// }
//
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param inverted_crc - The two CRC16 bytes in the received data.
// This should just point into the received data,
// *not* at a 16-bit integer.
// @param crc - The crc starting value (optional)
// @return 1, iff the CRC matches.
bool onewire_check_crc16(const uint8_t* input, size_t len, const uint8_t* inverted_crc, uint16_t crc_iv)
{
uint16_t crc = ~onewire_crc16(input, len, crc_iv);
return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
}
// Compute a Dallas Semiconductor 16 bit CRC. This is required to check
// the integrity of data received from many 1-Wire devices. Note that the
// CRC computed here is *not* what you'll get from the 1-Wire network,
// for two reasons:
// 1) The CRC is transmitted bitwise inverted.
// 2) Depending on the endian-ness of your processor, the binary
// representation of the two-byte return value may have a different
// byte order than the two bytes you get from 1-Wire.
// @param input - Array of bytes to checksum.
// @param len - How many bytes to use.
// @param crc - The crc starting value (optional)
// @return The CRC16, as defined by Dallas Semiconductor.
uint16_t onewire_crc16(const uint8_t* input, size_t len, uint16_t crc_iv)
{
uint16_t crc = crc_iv;
static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
uint16_t i;
for (i = 0; i < len; i++) {
// Even though we're just copying a byte from the input,
// we'll be doing 16-bit computation with it.
uint16_t cdata = input[i];
cdata = (cdata ^ crc) & 0xff;
crc >>= 8;
if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
crc ^= 0xC001;
cdata <<= 6;
crc ^= cdata;
cdata <<= 1;
crc ^= cdata;
}
return crc;
}

View File

@@ -1,7 +1,5 @@
#include "peripherals.h"
#include "adc.h"
//#include "led.h"
// #include "buzzer.h"
#include "proximity.h"
#include "ac_relay.h"
#include "socket_lock.h"
@@ -11,13 +9,9 @@
void peripherals_init(void)
{
ac_relay_init();
// led_init();
// buzzer_init();
adc_init();
proximity_init();
// socket_lock_init();
// rcm_init();
// energy_meter_init();
// aux_init();
ntc_sensor_init();
}

View File

@@ -4,12 +4,15 @@
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "nvs.h"
#include "socket_lock.h"
#include "board_config.h"
// NEW:
#include "storage_service.h"
#define NVS_NAMESPACE "socket_lock"
#define NVS_OPERATING_TIME "op_time"
#define NVS_BREAK_TIME "break_time"
@@ -27,20 +30,68 @@
static const char* TAG = "socket_lock";
static nvs_handle_t nvs;
// Storage timeouts (ajusta se quiseres)
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
static uint16_t operating_time = 300;
static uint16_t break_time = 1000;
static bool detection_high;
static bool detection_high = false;
static uint8_t retry_count = 5;
static socket_lock_status_t status;
static TaskHandle_t socket_lock_task;
// -----------------------------------------------------------------------------
// Helpers storage (best effort) - iguais ao estilo do wifi.c
// -----------------------------------------------------------------------------
static esp_err_t store_flush_best_effort(void)
{
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK) return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_u16_best_effort(const char *ns, const char *key, uint16_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u16_async(ns, key, v);
if (e == ESP_OK) return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// -----------------------------------------------------------------------------
// Lock logic
// -----------------------------------------------------------------------------
static bool is_locked(void)
{
gpio_set_level(board_config.socket_lock_a_gpio, 1);
@@ -58,31 +109,42 @@ bool socket_lock_is_locked_state(void)
static void socket_lock_task_func(void* param)
{
uint32_t notification;
(void)param;
uint32_t notification;
TickType_t previous_tick = 0;
uint8_t attempt = 0;
while (true) {
if (xTaskNotifyWait(0x00, 0xff, &notification, portMAX_DELAY)) {
if (notification & (LOCK_BIT | UNLOCK_BIT)) {
while (true)
{
if (xTaskNotifyWait(0x00, 0xff, &notification, portMAX_DELAY))
{
if (notification & (LOCK_BIT | UNLOCK_BIT))
{
attempt = retry_count;
}
if (notification & (UNLOCK_BIT | REPEAT_UNLOCK_BIT)) {
if (notification & (UNLOCK_BIT | REPEAT_UNLOCK_BIT))
{
gpio_set_level(board_config.socket_lock_a_gpio, 0);
gpio_set_level(board_config.socket_lock_b_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(operating_time));
if (!is_locked()) {
if (!is_locked())
{
ESP_LOGI(TAG, "Unlock OK");
status = SOCKED_LOCK_STATUS_IDLE;
} else {
if (attempt > 1) {
}
else
{
if (attempt > 1)
{
ESP_LOGW(TAG, "Not unlocked yet, repeating...");
attempt--;
xTaskNotify(socket_lock_task, REPEAT_UNLOCK_BIT, eSetBits);
} else {
}
else
{
ESP_LOGE(TAG, "Not unlocked");
status = SOCKED_LOCK_STATUS_UNLOCKING_FAIL;
}
@@ -90,23 +152,33 @@ static void socket_lock_task_func(void* param)
gpio_set_level(board_config.socket_lock_a_gpio, 0);
gpio_set_level(board_config.socket_lock_b_gpio, 0);
} else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT)) {
if (notification & LOCK_BIT) {
vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); //delay before first lock attempt
}
else if (notification & (LOCK_BIT | REPEAT_LOCK_BIT))
{
if (notification & LOCK_BIT)
{
vTaskDelay(pdMS_TO_TICKS(LOCK_DELAY)); // delay before first lock attempt
}
gpio_set_level(board_config.socket_lock_a_gpio, 1);
gpio_set_level(board_config.socket_lock_b_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(operating_time));
if (is_locked()) {
if (is_locked())
{
ESP_LOGI(TAG, "Lock OK");
status = SOCKED_LOCK_STATUS_IDLE;
} else {
if (attempt > 1) {
}
else
{
if (attempt > 1)
{
ESP_LOGW(TAG, "Not locked yet, repeating...");
attempt--;
xTaskNotify(socket_lock_task, REPEAT_LOCK_BIT, eSetBits);
} else {
}
else
{
ESP_LOGE(TAG, "Not locked");
status = SOCKED_LOCK_STATUS_LOCKING_FAIL;
}
@@ -117,7 +189,8 @@ static void socket_lock_task_func(void* param)
}
TickType_t delay_tick = xTaskGetTickCount() - previous_tick;
if (delay_tick < pdMS_TO_TICKS(break_time)) {
if (delay_tick < pdMS_TO_TICKS(break_time))
{
vTaskDelay(pdMS_TO_TICKS(break_time) - delay_tick);
}
previous_tick = xTaskGetTickCount();
@@ -125,34 +198,56 @@ static void socket_lock_task_func(void* param)
}
}
// -----------------------------------------------------------------------------
// Init / API pública
// -----------------------------------------------------------------------------
void socket_lock_init(void)
{
if (board_config.socket_lock) {
ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs));
if (!board_config.socket_lock)
return;
nvs_get_u16(nvs, NVS_OPERATING_TIME, &operating_time);
// garante storage pronto
esp_err_t se = storage_service_init();
if (se != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(se));
nvs_get_u16(nvs, NVS_BREAK_TIME, &break_time);
// Load config (best effort; se não existir, fica default)
{
uint16_t u16 = 0;
uint8_t u8 = 0;
nvs_get_u8(nvs, NVS_RETRY_COUNT, &retry_count);
esp_err_t e = storage_get_u16_sync(NVS_NAMESPACE, NVS_OPERATING_TIME, &u16, STORE_TO);
if (e == ESP_OK) operating_time = u16;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_OPERATING_TIME, esp_err_to_name(e));
uint8_t u8;
if (nvs_get_u8(nvs, NVS_DETECTION_HIGH, &u8) == ESP_OK) {
detection_high = u8;
}
e = storage_get_u16_sync(NVS_NAMESPACE, NVS_BREAK_TIME, &u16, STORE_TO);
if (e == ESP_OK) break_time = u16;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_BREAK_TIME, esp_err_to_name(e));
gpio_config_t io_conf = {};
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_RETRY_COUNT, &u8, STORE_TO);
if (e == ESP_OK) retry_count = u8;
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_RETRY_COUNT, esp_err_to_name(e));
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 10, &socket_lock_task);
e = storage_get_u8_sync(NVS_NAMESPACE, NVS_DETECTION_HIGH, &u8, STORE_TO);
if (e == ESP_OK) detection_high = (u8 != 0);
else if (e != ESP_ERR_NOT_FOUND)
ESP_LOGW(TAG, "load %s failed: %s", NVS_DETECTION_HIGH, esp_err_to_name(e));
}
gpio_config_t io_conf = {};
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_a_gpio) | BIT64(board_config.socket_lock_b_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = BIT64(board_config.socket_lock_detection_gpio);
ESP_ERROR_CHECK(gpio_config(&io_conf));
xTaskCreate(socket_lock_task_func, "socket_lock_task", 2 * 1024, NULL, 2, &socket_lock_task);
}
bool socket_lock_is_detection_high(void)
@@ -164,8 +259,11 @@ void socket_lock_set_detection_high(bool _detection_high)
{
detection_high = _detection_high;
nvs_set_u8(nvs, NVS_DETECTION_HIGH, detection_high);
nvs_commit(nvs);
esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_DETECTION_HIGH, detection_high ? 1 : 0);
if (e != ESP_OK)
ESP_LOGW(TAG, "persist detect_hi failed: %s", esp_err_to_name(e));
(void)store_flush_best_effort();
}
uint16_t socket_lock_get_operating_time(void)
@@ -175,15 +273,22 @@ uint16_t socket_lock_get_operating_time(void)
esp_err_t socket_lock_set_operating_time(uint16_t _operating_time)
{
if (_operating_time < OPERATING_TIME_MIN || _operating_time > OPERATING_TIME_MAX) {
if (_operating_time < OPERATING_TIME_MIN || _operating_time > OPERATING_TIME_MAX)
{
ESP_LOGE(TAG, "Operating time out of range");
return ESP_ERR_INVALID_ARG;
}
operating_time = _operating_time;
nvs_set_u16(nvs, NVS_OPERATING_TIME, operating_time);
nvs_commit(nvs);
esp_err_t e = store_set_u16_best_effort(NVS_NAMESPACE, NVS_OPERATING_TIME, operating_time);
if (e != ESP_OK)
{
ESP_LOGW(TAG, "persist op_time failed: %s", esp_err_to_name(e));
return e;
}
(void)store_flush_best_effort();
return ESP_OK;
}
@@ -195,8 +300,12 @@ uint8_t socket_lock_get_retry_count(void)
void socket_lock_set_retry_count(uint8_t _retry_count)
{
retry_count = _retry_count;
nvs_set_u8(nvs, NVS_RETRY_COUNT, retry_count);
nvs_commit(nvs);
esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_RETRY_COUNT, retry_count);
if (e != ESP_OK)
ESP_LOGW(TAG, "persist retry_count failed: %s", esp_err_to_name(e));
(void)store_flush_best_effort();
}
uint16_t socket_lock_get_break_time(void)
@@ -206,15 +315,22 @@ uint16_t socket_lock_get_break_time(void)
esp_err_t socket_lock_set_break_time(uint16_t _break_time)
{
if (_break_time < board_config.socket_lock_min_break_time) {
ESP_LOGE(TAG, "Operating time out of range");
if (_break_time < board_config.socket_lock_min_break_time)
{
ESP_LOGE(TAG, "Break time out of range");
return ESP_ERR_INVALID_ARG;
}
break_time = _break_time;
nvs_set_u16(nvs, NVS_BREAK_TIME, break_time);
nvs_commit(nvs);
esp_err_t e = store_set_u16_best_effort(NVS_NAMESPACE, NVS_BREAK_TIME, break_time);
if (e != ESP_OK)
{
ESP_LOGW(TAG, "persist break_time failed: %s", esp_err_to_name(e));
return e;
}
(void)store_flush_best_effort();
return ESP_OK;
}
@@ -229,4 +345,4 @@ void socket_lock_set_locked(bool locked)
socket_lock_status_t socket_lock_get_status(void)
{
return status;
}
}

View File

@@ -38,7 +38,7 @@ void temp_sensor_init(void)
lm75a_init();
xTaskCreate(temp_sensor_task_func, "temp_sensor_task", 5 * 1024, NULL, 5, NULL);
xTaskCreate(temp_sensor_task_func, "temp_sensor_task", 5 * 1024, NULL, 2, NULL);
}
uint8_t temp_sensor_get_count(void)

View File

@@ -1,14 +1,12 @@
idf_component_register(
SRCS
"src/protocols.c"
"src/json.c"
"src/mqtt.c"
INCLUDE_DIRS
"include"
PRIV_INCLUDE_DIRS
"src"
PRIV_REQUIRES
nvs_flash
mqtt
cjson
vfs
@@ -19,6 +17,7 @@ idf_component_register(
config
evse
peripherals
meter_manager
ocpp
auth
)

View File

@@ -1,38 +0,0 @@
#ifndef JSON_H_
#define JSON_H_
#include <stdbool.h>
#include "esp_err.h"
#include "cJSON.h"
/**
* @brief Gera um objeto JSON com a configuração atual do EVSE.
*
* Contém parâmetros como corrente máxima, limites de tempo,
* trava do conector, temperatura e configuração do OCPP.
*
* @return Ponteiro para cJSON (deve ser liberado com cJSON_Delete()).
*/
cJSON* json_get_evse_config(void);
/**
* @brief Define a configuração do EVSE a partir de um objeto JSON.
*
* Aplica valores recebidos de protocolos como MQTT ou REST.
*
* @param root Objeto JSON com os campos válidos.
* @return ESP_OK se todos os parâmetros foram aplicados com sucesso.
*/
esp_err_t json_set_evse_config(cJSON* root);
/**
* @brief Retorna o estado atual do EVSE em formato JSON.
*
* Inclui estado de operação, erros, limites, sessão atual e medições elétricas.
*
* @return Ponteiro para cJSON (deve ser liberado com cJSON_Delete()).
*/
cJSON* json_get_state(void);
#endif /* JSON_H_ */

View File

@@ -1,114 +1,115 @@
#ifndef MQTT_H_
#define MQTT_H_
// === Início de: components/mqtt/include/mqtt.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <stdint.h>
#include "esp_err.h"
// Tamanhos máximos esperados pelos getters (buffers do chamador)
#define MQTT_SERVER_MAX_LEN 64 ///< host ou URI do broker
#define MQTT_BASE_TOPIC_MAX_LEN 64 ///< tópico base (ex: "evse")
#define MQTT_USERNAME_MAX_LEN 32
#define MQTT_PASSWORD_MAX_LEN 64
/**
* @file mqtt.h
* @brief MQTT configuration and control interface.
* @brief Inicializa o módulo MQTT.
*
* This module provides initialization, configuration,
* and runtime access functions for the MQTT client.
* - Carrega configuração da NVS
* - Regista handlers de eventos (AUTH, SCHED, LOADBALANCER, METER, EVSE, NETWORK)
* - Cria a task de telemetria periódica
*
* Configuration is persisted in NVS under namespace "mqtt".
* Não inicia a ligação ao broker imediatamente; isso acontece quando existir IP
* (NETWORK_EVENT_STA_GOT_IP) e `enabled == true`.
*/
/* -------------------------------------------------------------------------- */
/* Definitions */
/* -------------------------------------------------------------------------- */
#define MQTT_MAX_SERVER_LEN 64 /**< Max length for MQTT server URI */
#define MQTT_MAX_USER_LEN 32 /**< Max length for MQTT username */
#define MQTT_MAX_PASSWORD_LEN 64 /**< Max length for MQTT password */
#define MQTT_MAX_BASE_TOPIC_LEN 64 /**< Max length for MQTT base topic */
/* -------------------------------------------------------------------------- */
/* Public Functions */
/* -------------------------------------------------------------------------- */
esp_err_t mqtt_init(void);
/**
* @brief Initialize MQTT subsystem.
* @brief Força tentativa de ligação ao broker (normalmente não é necessário chamar).
*
* Loads configuration from NVS and starts background publish task
* if MQTT is enabled. Must be called once during system startup.
* Útil apenas se quiseres forçar manualmente depois de mudares config.
*/
void mqtt_init(void);
esp_err_t mqtt_start(void);
/**
* @brief Restart the MQTT client safely.
*
* Stops the current MQTT client (if running) and starts a new one
* with the configuration currently stored in NVS.
*
* Useful when changing Wi-Fi networks, credentials, or broker settings.
*
* @return ESP_OK on success, or an ESP_ERR_* code otherwise.
* @brief Pára o cliente MQTT e destrói o handle.
*/
esp_err_t mqtt_restart(void);
void mqtt_stop(void);
// =============================
// Getters de configuração
// =============================
/**
* @brief Set and persist MQTT configuration parameters in NVS.
*
* Any NULL parameter will be skipped (the previous value remains stored).
*
* @param enabled Whether MQTT should be enabled (true/false).
* @param server Broker URI (e.g. "mqtt://192.168.1.10").
* @param base_topic Base topic prefix for publish/subscribe.
* @param user MQTT username (optional).
* @param password MQTT password (optional).
* @param periodicity Publish interval (in seconds). Must be >0 when enabled.
*
* @return ESP_OK on success, or an ESP_ERR_* code otherwise.
*/
esp_err_t mqtt_set_config(bool enabled,
const char *server,
const char *base_topic,
const char *user,
const char *password,
uint16_t periodicity);
/**
* @brief Get whether MQTT is enabled.
*
* @return true if enabled, false otherwise.
* @brief Indica se o MQTT está ativo (flag em config).
*/
bool mqtt_get_enabled(void);
/**
* @brief Get MQTT broker URI stored in NVS.
* @brief Obtém o servidor MQTT (host ou URI).
*
* @param[out] value Buffer to receive the URI (min length: MQTT_MAX_SERVER_LEN).
* @param server buffer de saída, com pelo menos MQTT_SERVER_MAX_LEN bytes.
*/
void mqtt_get_server(char *value);
void mqtt_get_server(char *server);
/**
* @brief Get MQTT base topic stored in NVS.
* @brief Obtém o tópico base (ex.: "evse").
*
* @param[out] value Buffer to receive the base topic (min length: MQTT_MAX_BASE_TOPIC_LEN).
* @param base_topic buffer de saída, com pelo menos MQTT_BASE_TOPIC_MAX_LEN bytes.
*/
void mqtt_get_base_topic(char *value);
void mqtt_get_base_topic(char *base_topic);
/**
* @brief Get MQTT username stored in NVS.
* @brief Obtém o username MQTT (se configurado).
*
* @param[out] value Buffer to receive the username (min length: MQTT_MAX_USER_LEN).
* @param username buffer de saída, com pelo menos MQTT_USERNAME_MAX_LEN bytes.
*/
void mqtt_get_user(char *value);
void mqtt_get_user(char *username);
/**
* @brief Get MQTT password stored in NVS.
* @brief Obtém a password MQTT (se configurada).
*
* @param[out] value Buffer to receive the password (min length: MQTT_MAX_PASSWORD_LEN).
* @param password buffer de saída, com pelo menos MQTT_PASSWORD_MAX_LEN bytes.
*/
void mqtt_get_password(char *value);
void mqtt_get_password(char *password);
/**
* @brief Get MQTT publish periodicity in seconds.
*
* @return Publish interval in seconds (default: 30).
* @brief Obtém a periodicidade da telemetria periódica (em segundos).
*/
uint16_t mqtt_get_periodicity(void);
#endif /* MQTT_H_ */
// =============================
// Setter de configuração
// =============================
/**
* @brief Atualiza a configuração MQTT (tipicamente chamado pelo endpoint REST).
*
* - Grava em NVS
* - Se já existir cliente ligado, pára e recria com a nova config
* - Se `enabled == true` e houver IP, tenta ligar ao broker
*
* @param enabled true para ativar MQTT
* @param host hostname ou URI (ex: "broker.hivemq.com" ou "mqtt://x.y.z")
* @param topic tópico base (ex: "evse")
* @param username username MQTT (ou NULL para manter/limpar)
* @param password password MQTT (ou NULL para manter/limpar)
* @param periodicity período da task de telemetria em segundos (ex: 30)
*
* @return ESP_OK em caso de sucesso, ou erro de NVS / MQTT.
*/
esp_err_t mqtt_set_config(bool enabled,
const char *host,
const char *topic,
const char *username,
const char *password,
int periodicity);
#ifdef __cplusplus
}
#endif
// === Fim de: components/mqtt/include/mqtt.h ===

View File

@@ -1,204 +0,0 @@
// === Início de: components/protocols/src/json.c ===
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_timer.h"
#include "esp_chip_info.h"
#include "esp_mac.h"
#include "esp_log.h"
#include "json.h"
#include "mqtt.h"
#include "network.h"
#include "evse_error.h"
#include "evse_api.h"
#include "auth.h"
#include "evse_limits.h"
#include "evse_state.h"
#include "evse_config.h"
#include "ocpp.h"
#include "board_config.h"
#include "socket_lock.h"
#include "proximity.h"
#include "temp_sensor.h"
#include "evse_meter.h"
static const char *TAG = "json";
//
// ===== EVSE CONFIG JSON =====
//
cJSON *json_get_evse_config(void)
{
cJSON *root = cJSON_CreateObject();
if (!root)
return NULL;
cJSON_AddNumberToObject(root, "maxChargingCurrent", evse_get_max_charging_current());
cJSON_AddNumberToObject(root, "chargingCurrent", evse_get_charging_current());
cJSON_AddNumberToObject(root, "defaultChargingCurrent", evse_get_default_charging_current());
cJSON_AddBoolToObject(root, "requireAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "socketOutlet", evse_get_socket_outlet());
cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit());
//cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit());
//cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit());
//cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());
cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time());
cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time());
cJSON_AddBoolToObject(root, "socketLockDetectionHigh", socket_lock_is_detection_high());
cJSON_AddNumberToObject(root, "socketLockRetryCount", socket_lock_get_retry_count());
char str[64];
cJSON_AddBoolToObject(root, "enabledocpp", ocpp_get_enabled());
ocpp_get_server(str);
cJSON_AddStringToObject(root, "serverocpp", str);
return root;
}
//
// ===== SET EVSE CONFIG (from MQTT or REST) =====
//
esp_err_t json_set_evse_config(cJSON *root)
{
if (!root)
return ESP_ERR_INVALID_ARG;
// Alguns setters retornam esp_err_t, outros void. Para manter compatibilidade,
// chamamos sem propagar erro (se existir, será tratado internamente).
#define SET_NUM(key, fn) \
do \
{ \
const cJSON *item = cJSON_GetObjectItem(root, key); \
if (cJSON_IsNumber(item)) \
{ \
fn(item->valuedouble); \
} \
} while (0)
#define SET_BOOL(key, fn) \
do \
{ \
const cJSON *item = cJSON_GetObjectItem(root, key); \
if (cJSON_IsBool(item)) \
{ \
fn(cJSON_IsTrue(item)); \
} \
} while (0)
SET_NUM("maxChargingCurrent", evse_set_max_charging_current);
SET_NUM("chargingCurrent", evse_set_charging_current);
SET_NUM("defaultChargingCurrent", evse_set_default_charging_current);
SET_BOOL("socketOutlet", evse_set_socket_outlet);
SET_BOOL("rcm", evse_set_rcm);
SET_NUM("temperatureThreshold", evse_set_temp_threshold);
SET_NUM("consumptionLimit", evse_set_consumption_limit);
//SET_NUM("defaultConsumptionLimit", evse_set_default_consumption_limit);
SET_NUM("chargingTimeLimit", evse_set_charging_time_limit);
//SET_NUM("defaultChargingTimeLimit", evse_set_default_charging_time_limit);
SET_NUM("underPowerLimit", evse_set_under_power_limit);
//SET_NUM("defaultUnderPowerLimit", evse_set_default_under_power_limit);
SET_NUM("socketLockOperatingTime", socket_lock_set_operating_time);
SET_NUM("socketLockBreakTime", socket_lock_set_break_time);
const cJSON *retry = cJSON_GetObjectItem(root, "socketLockRetryCount");
if (cJSON_IsNumber(retry))
socket_lock_set_retry_count(retry->valueint);
const cJSON *detect = cJSON_GetObjectItem(root, "socketLockDetectionHigh");
if (cJSON_IsBool(detect))
socket_lock_set_detection_high(cJSON_IsTrue(detect));
const cJSON *ocpp_enabled = cJSON_GetObjectItem(root, "enabledocpp");
if (cJSON_IsBool(ocpp_enabled))
ocpp_set_enabled(cJSON_IsTrue(ocpp_enabled));
const cJSON *ocpp_server = cJSON_GetObjectItem(root, "serverocpp");
if (cJSON_IsString(ocpp_server))
ocpp_set_server(cJSON_GetStringValue(ocpp_server));
ESP_LOGI(TAG, "EVSE configuration updated successfully");
return ESP_OK;
}
//
// ===== EVSE STATE JSON =====
//
cJSON *json_get_state(void)
{
cJSON *root = cJSON_CreateObject();
if (!root)
return NULL;
cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state()));
cJSON_AddBoolToObject(root, "available", evse_config_is_available());
cJSON_AddBoolToObject(root, "enabled", evse_config_is_enabled());
cJSON_AddBoolToObject(root, "pendingAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached());
// Add error list
uint32_t error = evse_error_get_bits();
if (error == 0)
{
cJSON_AddNullToObject(root, "errors");
}
else
{
cJSON *errors = cJSON_CreateArray();
if (error & EVSE_ERR_PILOT_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("pilot_fault"));
if (error & EVSE_ERR_DIODE_SHORT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("diode_short"));
if (error & EVSE_ERR_LOCK_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("lock_fault"));
if (error & EVSE_ERR_UNLOCK_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("unlock_fault"));
if (error & EVSE_ERR_RCM_TRIGGERED_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_triggered"));
if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_selftest_fault"));
if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_high"));
if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_fault"));
cJSON_AddItemToObject(root, "errors", errors);
}
// Session info
evse_session_t sess;
if (evse_get_session(&sess))
{
cJSON_AddNumberToObject(root, "sessionTime", (double)sess.start_tick);
cJSON_AddNumberToObject(root, "chargingTime", (double)sess.duration_s);
cJSON_AddNumberToObject(root, "consumption", (double)sess.energy_wh);
}
else
{
cJSON_AddNullToObject(root, "sessionTime");
cJSON_AddNumberToObject(root, "chargingTime", 0);
cJSON_AddNumberToObject(root, "consumption", 0);
}
// Meter readings
float voltage[EVSE_METER_PHASE_COUNT];
float current[EVSE_METER_PHASE_COUNT];
int power[EVSE_METER_PHASE_COUNT];
evse_meter_get_voltage(voltage);
evse_meter_get_current(current);
evse_meter_get_power(power);
cJSON_AddItemToObject(root, "power", cJSON_CreateIntArray(power, EVSE_METER_PHASE_COUNT));
cJSON_AddItemToObject(root, "voltage", cJSON_CreateFloatArray(voltage, EVSE_METER_PHASE_COUNT));
cJSON_AddItemToObject(root, "current", cJSON_CreateFloatArray(current, EVSE_METER_PHASE_COUNT));
return root;
}
// === Fim de: components/protocols/src/json.c ===

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ static esp_err_t dashboard_get_handler(httpd_req_t *req) {
cJSON_AddNumberToObject(charger1, "id", 1);
cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state));
cJSON_AddNumberToObject(charger1, "current", evse_get_runtime_charging_current());
cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_max_charging_current());
cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_charging_current());
// Calcular a potência com base na corrente (considerando 230V)
int power = (evse_get_runtime_charging_current()) * 230;

View File

@@ -11,7 +11,7 @@ static esp_err_t link_config_get_handler(httpd_req_t *req) {
uint8_t mode = evse_link_get_mode(); // 0=MASTER,1=SLAVE
uint8_t self_id = evse_link_get_self_id();
ESP_LOGI(TAG, "GET link config: enabled=%d mode=%u id=%u",
ESP_LOGD(TAG, "GET link config: enabled=%d mode=%u id=%u",
enabled, mode, self_id);
httpd_resp_set_type(req, "application/json");
@@ -23,7 +23,7 @@ static esp_err_t link_config_get_handler(httpd_req_t *req) {
char *s = cJSON_Print(root);
httpd_resp_sendstr(req, s);
ESP_LOGI(TAG, " payload: %s", s);
ESP_LOGD(TAG, " payload: %s", s);
free(s);
cJSON_Delete(root);
return ESP_OK;
@@ -38,7 +38,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "POST link config: %s", buf);
ESP_LOGD(TAG, "POST link config: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json) {
@@ -50,7 +50,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
cJSON *j_en = cJSON_GetObjectItem(json, "linkEnabled");
if (j_en && cJSON_IsBool(j_en)) {
evse_link_set_enabled(cJSON_IsTrue(j_en));
ESP_LOGI(TAG, " set enabled = %d", cJSON_IsTrue(j_en));
ESP_LOGD(TAG, " set enabled = %d", cJSON_IsTrue(j_en));
}
// linkMode
@@ -67,7 +67,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
"Invalid linkMode (must be MASTER or SLAVE)");
return ESP_FAIL;
}
ESP_LOGI(TAG, " set mode = %s", m);
ESP_LOGD(TAG, " set mode = %s", m);
}
// linkSelfId
@@ -81,7 +81,7 @@ static esp_err_t link_config_post_handler(httpd_req_t *req) {
return ESP_FAIL;
}
evse_link_set_self_id((uint8_t)id);
ESP_LOGI(TAG, " set self_id = %d", id);
ESP_LOGD(TAG, " set self_id = %d", id);
}
cJSON_Delete(json);

View File

@@ -12,7 +12,7 @@ static const char *TAG = "evse_settings_api";
static esp_err_t config_settings_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
cJSON *config = cJSON_CreateObject();
cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current());
cJSON_AddNumberToObject(config, "currentLimit", evse_get_charging_current());
cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold());
const char *json_str = cJSON_Print(config);
httpd_resp_sendstr(req, json_str);
@@ -36,7 +36,7 @@ static esp_err_t config_settings_post_handler(httpd_req_t *req) {
}
cJSON *current = cJSON_GetObjectItem(json, "currentLimit");
if (current) evse_set_max_charging_current(current->valueint);
if (current) evse_set_charging_current(current->valueint);
cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit");
if (temp) evse_set_temp_threshold(temp->valueint);

View File

@@ -10,7 +10,7 @@ static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) {
bool enabled = loadbalancer_is_enabled();
uint8_t currentLimit = load_balancing_get_max_grid_current();
ESP_LOGI(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit);
ESP_LOGD(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit);
httpd_resp_set_type(req, "application/json");
@@ -21,7 +21,7 @@ static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) {
const char *json_str = cJSON_Print(config);
httpd_resp_sendstr(req, json_str);
ESP_LOGI(TAG, "Returned config: %s", json_str);
ESP_LOGD(TAG, "Returned config: %s", json_str);
free((void *)json_str);
cJSON_Delete(config);
@@ -40,7 +40,7 @@ static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) {
}
buf[len] = '\0';
ESP_LOGI(TAG, "Received POST data: %s", buf);
ESP_LOGD(TAG, "Received POST data: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json) {
@@ -54,7 +54,7 @@ static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) {
if (enabled_item && cJSON_IsBool(enabled_item)) {
bool isEnabled = cJSON_IsTrue(enabled_item);
loadbalancer_set_enabled(isEnabled);
ESP_LOGI(TAG, "Updated loadBalancingEnabled to: %d", isEnabled);
ESP_LOGD(TAG, "Updated loadBalancingEnabled to: %d", isEnabled);
}
// Atualizar limite de corrente
@@ -78,7 +78,7 @@ static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) {
return ESP_FAIL;
}
ESP_LOGI(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit);
ESP_LOGD(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit);
}
cJSON_Delete(json);

View File

@@ -158,13 +158,13 @@ void register_meters_data_handlers(httpd_handle_t server, void *ctx)
.user_ctx = ctx};
httpd_register_uri_handler(server, &live);
ESP_LOGI(TAG, "REST /api/v1/meters/live registrado");
ESP_LOGD(TAG, "REST /api/v1/meters/live registrado");
}
// Função para recuperar as configurações dos contadores
static esp_err_t meters_config_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Received GET request for /api/v1/config/meters");
ESP_LOGD(TAG, "Received GET request for /api/v1/config/meters");
httpd_resp_set_type(req, "application/json");
@@ -174,8 +174,8 @@ static esp_err_t meters_config_get_handler(httpd_req_t *req)
meter_type_t gridmeterType = meter_manager_grid_get_model();
meter_type_t evsemeterType = meter_manager_evse_get_model();
ESP_LOGI(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType));
ESP_LOGI(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType));
ESP_LOGD(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType));
ESP_LOGD(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType));
// Adicionando os tipos de contadores ao objeto JSON
cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType));
@@ -183,7 +183,7 @@ static esp_err_t meters_config_get_handler(httpd_req_t *req)
// Convertendo o objeto JSON para uma string
const char *json_str = cJSON_Print(config);
ESP_LOGI(TAG, "Returning meters config: %s", json_str);
ESP_LOGD(TAG, "Returning meters config: %s", json_str);
httpd_resp_sendstr(req, json_str);
@@ -197,7 +197,7 @@ static esp_err_t meters_config_get_handler(httpd_req_t *req)
// Função para atualizar as configurações dos contadores
static esp_err_t meters_config_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Received POST request for /api/v1/config/meters");
ESP_LOGD(TAG, "Received POST request for /api/v1/config/meters");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
@@ -211,7 +211,7 @@ static esp_err_t meters_config_post_handler(httpd_req_t *req)
buf[len] = '\0'; // Garantir que a string está terminada
ESP_LOGI(TAG, "Received POST data: %s", buf);
ESP_LOGD(TAG, "Received POST data: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json)
@@ -227,7 +227,7 @@ static esp_err_t meters_config_post_handler(httpd_req_t *req)
if (gridmeter)
{
meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type
ESP_LOGI(TAG, "Updating grid meter type to: %s", gridmeter->valuestring);
ESP_LOGD(TAG, "Updating grid meter type to: %s", gridmeter->valuestring);
meter_manager_grid_set_model(gridType);
}
@@ -235,14 +235,14 @@ static esp_err_t meters_config_post_handler(httpd_req_t *req)
if (evsemeter)
{
meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type
ESP_LOGI(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring);
ESP_LOGD(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring);
meter_manager_evse_set_model(evseType);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "Meters updated successfully");
ESP_LOGI(TAG, "Meters configuration updated successfully");
ESP_LOGD(TAG, "Meters configuration updated successfully");
return ESP_OK;
}

View File

@@ -19,14 +19,14 @@ typedef struct {
static void wifi_apply_config_task(void *param) {
wifi_task_data_t *data = (wifi_task_data_t *)param;
ESP_LOGI("wifi_task", "Applying Wi-Fi config in background task");
ESP_LOGD("wifi_task", "Applying Wi-Fi config in background task");
wifi_set_config(data->enabled, data->ssid, data->password);
free(data);
vTaskDelete(NULL);
}
static esp_err_t wifi_get_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Handling GET /api/v1/config/wifi");
ESP_LOGD(TAG, "Handling GET /api/v1/config/wifi");
httpd_resp_set_type(req, "application/json");
@@ -56,7 +56,7 @@ static esp_err_t wifi_get_handler(httpd_req_t *req) {
}
static esp_err_t wifi_post_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Handling POST /api/v1/config/wifi");
ESP_LOGD(TAG, "Handling POST /api/v1/config/wifi");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
@@ -112,7 +112,7 @@ static esp_err_t wifi_post_handler(httpd_req_t *req) {
static esp_err_t config_mqtt_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Handling GET /api/v1/config/mqtt");
ESP_LOGD(TAG, "Handling GET /api/v1/config/mqtt");
httpd_resp_set_type(req, "application/json");
@@ -128,13 +128,13 @@ static esp_err_t config_mqtt_get_handler(httpd_req_t *req)
mqtt_get_user(username);
mqtt_get_password(password);
ESP_LOGI(TAG, "MQTT Config:");
ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGI(TAG, " Server: %s", server);
ESP_LOGI(TAG, " Topic: %s", base_topic);
ESP_LOGI(TAG, " Username: %s", username);
ESP_LOGI(TAG, " Password: %s", password);
ESP_LOGI(TAG, " Periodicity: %d", periodicity);
ESP_LOGD(TAG, "MQTT Config:");
ESP_LOGD(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGD(TAG, " Server: %s", server);
ESP_LOGD(TAG, " Topic: %s", base_topic);
ESP_LOGD(TAG, " Username: %s", username);
ESP_LOGD(TAG, " Password: %s", password);
ESP_LOGD(TAG, " Periodicity: %d", periodicity);
cJSON *config = cJSON_CreateObject();
cJSON_AddBoolToObject(config, "enabled", enabled);
@@ -156,7 +156,7 @@ static esp_err_t config_mqtt_get_handler(httpd_req_t *req)
static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Handling POST /api/v1/config/mqtt");
ESP_LOGD(TAG, "Handling POST /api/v1/config/mqtt");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
@@ -166,7 +166,7 @@ static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "Received JSON: %s", buf);
ESP_LOGD(TAG, "Received JSON: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json) {
@@ -197,13 +197,13 @@ static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity");
if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint;
ESP_LOGI(TAG, "Applying MQTT config:");
ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGI(TAG, " Host: %s", host);
ESP_LOGI(TAG, " Topic: %s", topic);
ESP_LOGI(TAG, " Username: %s", username);
ESP_LOGI(TAG, " Password: %s", password);
ESP_LOGI(TAG, " Periodicity: %d", periodicity);
ESP_LOGD(TAG, "Applying MQTT config:");
ESP_LOGD(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGD(TAG, " Host: %s", host);
ESP_LOGD(TAG, " Topic: %s", topic);
ESP_LOGD(TAG, " Username: %s", username);
ESP_LOGD(TAG, " Password: %s", password);
ESP_LOGD(TAG, " Periodicity: %d", periodicity);
esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity);
if (err != ESP_OK) {

View File

@@ -11,6 +11,9 @@
#include "static_file_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static const char *TAG = "rest_main";
esp_err_t rest_server_init(const char *base_path)
@@ -30,6 +33,13 @@ esp_err_t rest_server_init(const char *base_path)
config.uri_match_fn = httpd_uri_match_wildcard;
config.max_uri_handlers = 32;
// Aumenta stack do httpd (handlers + cJSON + etc)
config.stack_size = 8192; // tenta 8192; se necessário 12288
config.task_priority = tskIDLE_PRIORITY + 5;
// Opcional (ajuda com muitas conexões/keep-alive)
config.lru_purge_enable = true;
httpd_handle_t server = NULL;
esp_err_t err = httpd_start(&server, &config);
if (err != ESP_OK)
@@ -41,21 +51,19 @@ esp_err_t rest_server_init(const char *base_path)
ESP_LOGI(TAG, "HTTP server started successfully");
// Register endpoint groups
register_evse_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_network_handlers(server, ctx); // Apenas chamando a função sem comparação
register_ocpp_handlers(server, ctx); // Apenas chamando a função sem comparação
register_auth_handlers(server, ctx); // Apenas chamando a função sem comparação
register_dashboard_handlers(server, ctx); // Apenas chamando a função sem comparação
register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
// Registar handlers
register_evse_settings_handlers(server, ctx);
register_network_handlers(server, ctx);
register_ocpp_handlers(server, ctx);
register_auth_handlers(server, ctx);
register_dashboard_handlers(server, ctx);
register_meters_settings_handlers(server, ctx);
register_loadbalancing_settings_handlers(server, ctx);
register_link_config_handlers(server, ctx);
register_meters_data_handlers(server, ctx);
register_scheduler_settings_handlers(server, ctx);
register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação
register_static_file_handlers(server, ctx);
ESP_LOGI(TAG, "All REST API endpoint groups registered successfully");
return ESP_OK;
}

View File

@@ -50,7 +50,7 @@ static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz)
* ========================= */
static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "GET /api/v1/config/scheduler");
ESP_LOGD(TAG, "GET /api/v1/config/scheduler");
httpd_resp_set_type(req, "application/json");
@@ -95,7 +95,7 @@ static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
* ========================= */
static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "POST /api/v1/config/scheduler");
ESP_LOGD(TAG, "POST /api/v1/config/scheduler");
// NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total,
// usa req->content_len e faz um loop com httpd_req_recv.
@@ -108,7 +108,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "Body: %s", buf);
ESP_LOGD(TAG, "Body: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json)
@@ -126,7 +126,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
if (cJSON_IsBool(j_enabled))
{
cfg.enabled = cJSON_IsTrue(j_enabled);
ESP_LOGI(TAG, " enabled = %d", cfg.enabled);
ESP_LOGD(TAG, " enabled = %d", cfg.enabled);
}
// mode
@@ -143,7 +143,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
cfg.mode = m;
ESP_LOGI(TAG, " mode = %s", sched_mode_to_str(cfg.mode));
ESP_LOGD(TAG, " mode = %s", sched_mode_to_str(cfg.mode));
}
// startTime (string "HH:MM")
@@ -160,7 +160,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
cfg.start_min = minutes;
ESP_LOGI(TAG, " start_min = %u", (unsigned)cfg.start_min);
ESP_LOGD(TAG, " start_min = %u", (unsigned)cfg.start_min);
}
// endTime (string "HH:MM")
@@ -177,7 +177,7 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
cfg.end_min = minutes;
ESP_LOGI(TAG, " end_min = %u", (unsigned)cfg.end_min);
ESP_LOGD(TAG, " end_min = %u", (unsigned)cfg.end_min);
}
// (Opcional) validações extra:
@@ -220,5 +220,5 @@ void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx)
.user_ctx = ctx};
httpd_register_uri_handler(server, &post_uri);
ESP_LOGI(TAG, "Scheduler REST handlers registered");
ESP_LOGD(TAG, "Scheduler REST handlers registered");
}

View File

@@ -13,7 +13,7 @@
}
</style>
<title>ChargeFlow</title>
<script type="module" crossorigin src="/assets/index-19gq1t3T.js"></script>
<script type="module" crossorigin src="/assets/index-CH8H7Z_T.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-SX00HfRO.css">
</head>
<body>

View File

@@ -3,5 +3,5 @@ set(srcs "src/scheduler_types.c" "src/scheduler.c" "src/scheduler_events.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash esp_timer
PRIV_REQUIRES esp_timer
REQUIRES esp_event evse)

View File

@@ -8,8 +8,9 @@
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_err.h"
#include "storage_service.h"
#include <time.h>
#include <string.h>
@@ -26,112 +27,154 @@ static sched_config_t s_cfg = {
static bool s_allowed_now = true;
static TaskHandle_t s_task_handle = NULL;
/* ===== NVS ===== */
/* ===== Storage ===== */
#define NVS_NAMESPACE "scheduler"
#define NVS_KEY_ENABLED "enabled"
#define NVS_KEY_MODE "mode"
#define NVS_KEY_START_MIN "start_min"
#define NVS_KEY_END_MIN "end_min"
#define NVS_KEY_ENABLED "enabled" // u8
#define NVS_KEY_MODE "mode" // u8
#define NVS_KEY_START_MIN "start_min" // u16
#define NVS_KEY_END_MIN "end_min" // u16
static void load_config_from_nvs(sched_config_t *out)
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
static bool cfg_sanitize(sched_config_t *cfg)
{
if (!cfg)
return false;
bool changed = false;
// enabled é bool
cfg->enabled = cfg->enabled ? true : false;
// mode válido
if (cfg->mode > SCHED_MODE_SIMPLE)
{
cfg->mode = SCHED_MODE_DISABLED;
changed = true;
}
// start/end em minutos (0..1440)
if (cfg->start_min > (24 * 60))
{
cfg->start_min = 0;
changed = true;
}
if (cfg->end_min > (24 * 60))
{
cfg->end_min = 24 * 60;
changed = true;
}
return changed;
}
static void load_config_from_storage(sched_config_t *out)
{
if (!out)
return;
*out = s_cfg; // defaults
// defaults
*out = s_cfg;
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No scheduler namespace in NVS (%s), using defaults",
esp_err_to_name(err));
return;
}
bool needs_flush = false;
uint8_t u8;
uint16_t u16;
uint8_t u8 = 0;
uint16_t u16 = 0;
esp_err_t err;
if (nvs_get_u8(h, NVS_KEY_ENABLED, &u8) == ESP_OK)
// enabled
err = storage_get_u8_sync(NVS_NAMESPACE, NVS_KEY_ENABLED, &u8, TO_TICKS_MS(800));
if (err == ESP_OK && u8 <= 1)
{
out->enabled = (u8 != 0);
}
if (nvs_get_u8(h, NVS_KEY_MODE, &u8) == ESP_OK)
else
{
if (u8 <= (uint8_t)SCHED_MODE_SIMPLE)
{
out->mode = (sched_mode_t)u8;
}
out->enabled = false;
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_ENABLED, 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing/invalid enabled (%s) -> default=false (persisted)",
esp_err_to_name(err));
}
if (nvs_get_u16(h, NVS_KEY_START_MIN, &u16) == ESP_OK)
// mode
err = storage_get_u8_sync(NVS_NAMESPACE, NVS_KEY_MODE, &u8, TO_TICKS_MS(800));
if (err == ESP_OK && u8 <= (uint8_t)SCHED_MODE_SIMPLE)
{
out->mode = (sched_mode_t)u8;
}
else
{
out->mode = SCHED_MODE_DISABLED;
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_MODE, (uint8_t)out->mode);
needs_flush = true;
ESP_LOGW(TAG, "Missing/invalid mode (%s) -> default=DISABLED (persisted)",
esp_err_to_name(err));
}
// start_min
err = storage_get_u16_sync(NVS_NAMESPACE, NVS_KEY_START_MIN, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 <= (24 * 60))
{
out->start_min = u16;
}
if (nvs_get_u16(h, NVS_KEY_END_MIN, &u16) == ESP_OK)
else
{
out->start_min = 0;
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_START_MIN, out->start_min);
needs_flush = true;
ESP_LOGW(TAG, "Missing/invalid start_min (%s) -> default=0 (persisted)",
esp_err_to_name(err));
}
// end_min
err = storage_get_u16_sync(NVS_NAMESPACE, NVS_KEY_END_MIN, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 <= (24 * 60))
{
out->end_min = u16;
}
else
{
out->end_min = 24 * 60;
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_END_MIN, out->end_min);
needs_flush = true;
ESP_LOGW(TAG, "Missing/invalid end_min (%s) -> default=1440 (persisted)",
esp_err_to_name(err));
}
nvs_close(h);
// sanity final (e persistir se ajustou algo)
if (cfg_sanitize(out))
{
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_ENABLED, out->enabled ? 1 : 0);
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_MODE, (uint8_t)out->mode);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_START_MIN, out->start_min);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_END_MIN, out->end_min);
needs_flush = true;
}
ESP_LOGI(TAG, "Loaded from NVS: enabled=%d mode=%d start=%u end=%u",
out->enabled, out->mode,
if (needs_flush)
{
(void)storage_flush_sync(TO_TICKS_MS(2000));
}
ESP_LOGI(TAG, "Loaded: enabled=%d mode=%d start=%u end=%u",
out->enabled, (int)out->mode,
(unsigned)out->start_min, (unsigned)out->end_min);
}
static void save_config_to_nvs(const sched_config_t *cfg)
static void save_config_to_storage(const sched_config_t *cfg)
{
if (!cfg)
return;
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
return;
}
// Debounced writes
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_ENABLED, cfg->enabled ? 1 : 0);
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_MODE, (uint8_t)cfg->mode);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_START_MIN, cfg->start_min);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_END_MIN, cfg->end_min);
err = nvs_set_u8(h, NVS_KEY_ENABLED, cfg->enabled ? 1 : 0);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(enabled) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u8(h, NVS_KEY_MODE, (uint8_t)cfg->mode);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u16(h, NVS_KEY_START_MIN, cfg->start_min);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u16(start_min) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u16(h, NVS_KEY_END_MIN, cfg->end_min);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u16(end_min) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_commit(h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed: %s", esp_err_to_name(err));
}
else
{
ESP_LOGI(TAG, "Scheduler config saved to NVS");
}
out:
nvs_close(h);
// opcional: flush imediato
// (void)storage_flush_async();
}
/* ===== Lógica de janelas ===== */
@@ -173,17 +216,18 @@ static void scheduler_recompute_and_emit(void)
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
esp_event_post(SCHED_EVENTS,
SCHED_EVENT_WINDOW_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
(void)esp_event_post(SCHED_EVENTS,
SCHED_EVENT_WINDOW_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
}
}
/* ===== Task do scheduler ===== */
static void scheduler_task(void *arg)
{
(void)arg;
const TickType_t period = pdMS_TO_TICKS(60000); // 60s
ESP_LOGI(TAG, "Scheduler task started");
@@ -198,8 +242,18 @@ static void scheduler_task(void *arg)
/* ===== API pública ===== */
void scheduler_init(void)
{
// 1) carregar config
load_config_from_nvs(&s_cfg);
// 0) garante storage
esp_err_t se = storage_service_init();
if (se != ESP_OK)
{
ESP_LOGE(TAG, "storage_service_init failed: %s (using defaults in RAM)", esp_err_to_name(se));
// segue com defaults
}
else
{
// 1) carregar config
load_config_from_storage(&s_cfg);
}
// 2) calcular estado inicial
s_allowed_now = compute_allowed_now(&s_cfg);
@@ -207,23 +261,23 @@ void scheduler_init(void)
// 3) enviar evento INIT
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
esp_event_post(SCHED_EVENTS,
SCHED_EVENT_INIT,
&ev,
sizeof(ev),
portMAX_DELAY);
(void)esp_event_post(SCHED_EVENTS,
SCHED_EVENT_INIT,
&ev,
sizeof(ev),
portMAX_DELAY);
ESP_LOGI(TAG, "Init: allowed_now=%d", s_allowed_now);
// 4) criar a task
if (s_task_handle == NULL)
{
xTaskCreate(
(void)xTaskCreate(
scheduler_task,
"scheduler_task",
4 * 1024,
NULL,
3, // prioridade razoável
3,
&s_task_handle);
}
}
@@ -234,7 +288,9 @@ void scheduler_set_config(const sched_config_t *cfg)
return;
s_cfg = *cfg;
save_config_to_nvs(&s_cfg);
(void)cfg_sanitize(&s_cfg);
save_config_to_storage(&s_cfg);
// recomputa imediatamente para refletir mudança
scheduler_recompute_and_emit();

View File

@@ -0,0 +1,8 @@
set(srcs
"src/storage_service.c"
)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
REQUIRES nvs_flash)

View File

@@ -0,0 +1,92 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "nvs.h" // para ESP_ERR_NVS_INVALID_LENGTH
#ifdef __cplusplus
extern "C"
{
#endif
/**
* NVS limita namespace e key a 15 chars (+ '\0')
*/
#define STORAGE_NS_MAX_LEN 16
#define STORAGE_KEY_MAX_LEN 16
#ifndef STORAGE_QUEUE_LEN
#define STORAGE_QUEUE_LEN 32
#endif
#ifndef STORAGE_MAX_PENDING
#define STORAGE_MAX_PENDING 48
#endif
#ifndef STORAGE_COMMIT_DEBOUNCE_MS
#define STORAGE_COMMIT_DEBOUNCE_MS 500
#endif
#ifndef STORAGE_MAX_VALUE_BYTES
#define STORAGE_MAX_VALUE_BYTES 96
#endif
/**
* Inicializa o serviço e cria a task interna.
*
* Requisitos:
* - nvs_flash_init() deve ter sido chamado antes.
*/
esp_err_t storage_service_init(void);
// -------------------- Async setters (não bloqueiam; commit é debounced) --------------------
esp_err_t storage_set_u8_async(const char *ns, const char *key, uint8_t v);
esp_err_t storage_set_u16_async(const char *ns, const char *key, uint16_t v);
esp_err_t storage_set_u32_async(const char *ns, const char *key, uint32_t v);
esp_err_t storage_set_str_async(const char *ns, const char *key, const char *str);
esp_err_t storage_set_blob_async(const char *ns, const char *key, const void *data, size_t len);
esp_err_t storage_erase_key_async(const char *ns, const char *key);
/** Força commit imediato (async). */
esp_err_t storage_flush_async(void);
// -------------------- Sync getters (bloqueiam até ler do NVS/pending) --------------------
esp_err_t storage_get_u8_sync(const char *ns, const char *key, uint8_t *out, TickType_t to);
esp_err_t storage_get_u16_sync(const char *ns, const char *key, uint16_t *out, TickType_t to);
esp_err_t storage_get_u32_sync(const char *ns, const char *key, uint32_t *out, TickType_t to);
esp_err_t storage_get_str_sync(const char *ns, const char *key, char *out, size_t out_sz, TickType_t to);
/**
* Blob sync (semântica semelhante a nvs_get_blob):
* - Se out == NULL: devolve ESP_OK e coloca em *inout_len o tamanho requerido
* - Se out != NULL e *inout_len < requerido: devolve ESP_ERR_NVS_INVALID_LENGTH e atualiza *inout_len com requerido
* - Se OK: copia e atualiza *inout_len com o tamanho real
*/
esp_err_t storage_get_blob_sync(const char *ns, const char *key, void *out, size_t *inout_len, TickType_t to);
/** Força commit imediato (sync). */
esp_err_t storage_flush_sync(TickType_t to);
// -------------------- (upgrade opcional) Sync setters --------------------
// Úteis no boot/migração quando queres garantia forte e/ou a fila async pode estar cheia.
esp_err_t storage_set_u8_sync(const char *ns, const char *key, uint8_t v, TickType_t to);
esp_err_t storage_set_u16_sync(const char *ns, const char *key, uint16_t v, TickType_t to);
esp_err_t storage_set_u32_sync(const char *ns, const char *key, uint32_t v, TickType_t to);
esp_err_t storage_set_str_sync(const char *ns, const char *key, const char *str, TickType_t to);
esp_err_t storage_set_blob_sync(const char *ns, const char *key, const void *data, size_t len, TickType_t to);
esp_err_t storage_erase_key_sync(const char *ns, const char *key, TickType_t to);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff