This commit is contained in:
2025-08-24 11:17:48 +01:00
parent 0d0dc5b129
commit 96b2ab1f57
31 changed files with 2883 additions and 4054 deletions

View File

@@ -47,7 +47,7 @@ cJSON *json_get_evse_config(void)
cJSON_AddNumberToObject(root, "maxChargingCurrent", evse_get_max_charging_current()); cJSON_AddNumberToObject(root, "maxChargingCurrent", evse_get_max_charging_current());
cJSON_AddNumberToObject(root, "chargingCurrent", evse_get_charging_current()); cJSON_AddNumberToObject(root, "chargingCurrent", evse_get_charging_current());
cJSON_AddNumberToObject(root, "defaultChargingCurrent", evse_get_default_charging_current()); cJSON_AddNumberToObject(root, "defaultChargingCurrent", evse_get_default_charging_current());
cJSON_AddBoolToObject(root, "requireAuth", auth_is_enabled()); cJSON_AddBoolToObject(root, "requireAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "socketOutlet", evse_get_socket_outlet()); cJSON_AddBoolToObject(root, "socketOutlet", evse_get_socket_outlet());
cJSON_AddBoolToObject(root, "rcm", evse_is_rcm()); cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold()); cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
@@ -71,8 +71,8 @@ cJSON *json_get_evse_config(void)
cJSON_AddBoolToObject(root, "enabledocpp", ocpp_get_enabled()); cJSON_AddBoolToObject(root, "enabledocpp", ocpp_get_enabled());
ocpp_get_server(str); ocpp_get_server(str);
cJSON_AddStringToObject(root, "serverocpp", str); cJSON_AddStringToObject(root, "serverocpp", str);
ocpp_get_rfid(str); //ocpp_get_rfid(str);
cJSON_AddStringToObject(root, "rfid", str); //cJSON_AddStringToObject(root, "rfid", str);
return root; return root;
} }
@@ -93,7 +93,7 @@ esp_err_t json_set_evse_config(cJSON *root)
} }
if (cJSON_IsBool(cJSON_GetObjectItem(root, "requireAuth"))) if (cJSON_IsBool(cJSON_GetObjectItem(root, "requireAuth")))
{ {
auth_set_enabled(cJSON_IsTrue(cJSON_GetObjectItem(root, "requireAuth"))); //auth_set_mode(cJSON_IsTrue(cJSON_GetObjectItem(root, "requireAuth")));
} }
if (cJSON_IsBool(cJSON_GetObjectItem(root, "socketOutlet"))) if (cJSON_IsBool(cJSON_GetObjectItem(root, "socketOutlet")))
{ {
@@ -176,10 +176,10 @@ esp_err_t json_set_evse_config(cJSON *root)
ocpp_set_server(cJSON_GetStringValue(cJSON_GetObjectItem(root, "serverocpp"))); ocpp_set_server(cJSON_GetStringValue(cJSON_GetObjectItem(root, "serverocpp")));
} }
if (cJSON_IsString(cJSON_GetObjectItem(root, "rfid"))) //if (cJSON_IsString(cJSON_GetObjectItem(root, "rfid")))
{ //{
ocpp_set_rfid(cJSON_GetStringValue(cJSON_GetObjectItem(root, "rfid"))); // ocpp_set_rfid(cJSON_GetStringValue(cJSON_GetObjectItem(root, "rfid")));
} //}
return ESP_OK; return ESP_OK;
} }
@@ -343,7 +343,7 @@ cJSON *json_get_state(void)
cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state())); cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state()));
cJSON_AddBoolToObject(root, "available", evse_config_is_available()); cJSON_AddBoolToObject(root, "available", evse_config_is_available());
cJSON_AddBoolToObject(root, "enabled", evse_config_is_enabled()); cJSON_AddBoolToObject(root, "enabled", evse_config_is_enabled());
cJSON_AddBoolToObject(root, "pendingAuth", auth_is_enabled()); cJSON_AddBoolToObject(root, "pendingAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached()); cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached());
uint32_t error = evse_error_get_bits(); uint32_t error = evse_error_get_bits();

View File

@@ -1,4 +1,4 @@
set(srcs "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c" "src/auth_events.c") set(srcs "src/auth_types.c" "src/auth.c" "src/wiegand.c" "src/wiegand_reader.c" "src/auth_events.c")
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"

View File

@@ -1,115 +1,32 @@
#ifndef AUTH_H #pragma once
#define AUTH_H
#include <stdbool.h> #include <stdbool.h>
#include <freertos/FreeRTOS.h> #include "auth_types.h" // enum + MAX LEN para API
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/// Maximum length of an RFID tag (including null terminator) /* Evento auxiliar legado/útil (resultado local) */
#define AUTH_TAG_MAX_LEN 30
/// Event structure emitted after a tag is read
typedef struct { typedef struct {
char tag[AUTH_TAG_MAX_LEN]; ///< The tag that was read char tag[AUTH_TAG_MAX_LEN];
bool authorized; ///< true if the tag is valid bool authorized;
} auth_event_t; } auth_event_t;
/** void auth_init(void);
* @brief Initializes the authentication system. void auth_set_mode(auth_mode_t mode);
* auth_mode_t auth_get_mode(void);
* - Loads configuration (enabled/disabled) from NVS
* - Starts the Wiegand reader
* - Emits AUTH_EVENT_INIT with current status
*/
void auth_init(void);
/**
* @brief Enables or disables RFID-based authentication.
*
* This setting is persisted in NVS. If disabled,
* all tags will be treated as authorized.
*
* @param value true to enable authentication, false to disable
*/
void auth_set_enabled(bool value);
/**
* @brief Checks whether authentication is currently enabled.
*
* @return true if enabled, false if disabled
*/
bool auth_is_enabled(void);
/**
* @brief Adds a new RFID tag to the authorized list.
*
* @param tag The RFID tag (max AUTH_TAG_MAX_LEN-1 characters)
* @return true if the tag was added successfully,
* false if it already exists or is invalid
*/
bool auth_add_tag(const char *tag); bool auth_add_tag(const char *tag);
/**
* @brief Removes an existing RFID tag from the authorized list.
*
* @param tag The tag to remove
* @return true if the tag was removed, false if not found
*/
bool auth_remove_tag(const char *tag); bool auth_remove_tag(const char *tag);
/**
* @brief Checks whether a tag is already registered.
*
* @param tag The tag to check
* @return true if the tag exists, false otherwise
*/
bool auth_tag_exists(const char *tag); bool auth_tag_exists(const char *tag);
/**
* @brief Logs all currently registered tags via ESP logging.
*/
void auth_list_tags(void); void auth_list_tags(void);
/**
* @brief Processes a read RFID tag.
*
* - Checks whether it's authorized
* - Emits AUTH_EVENT_TAG_PROCESSED event
* - Starts expiration timer if authorized
*
* @param tag The tag that was read
*/
void auth_process_tag(const char *tag); void auth_process_tag(const char *tag);
/**
* @brief Enables registration mode for the next tag read.
*
* When registration mode is active, the next tag read
* will be added to the authorized list automatically.
* Mode is deactivated after one tag is registered.
*/
void auth_wait_for_tag_registration(void); void auth_wait_for_tag_registration(void);
/** int auth_get_tag_count(void);
* @brief Returns the total number of registered tags.
*
* @return Number of valid tags
*/
int auth_get_tag_count(void);
/**
* @brief Returns the tag string at the given index.
*
* @param index The index (0 ≤ index < auth_get_tag_count())
* @return Pointer to the tag string, or NULL if index is invalid
*/
const char *auth_get_tag_by_index(int index); const char *auth_get_tag_by_index(int index);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif // AUTH_H

View File

@@ -1,22 +1,29 @@
#pragma once #pragma once
#include "esp_event.h" #include "esp_event.h"
#include "auth_types.h" // só tipos comuns; evita incluir auth.h
#define AUTH_EVENT_TAG_MAX_LEN 32
ESP_EVENT_DECLARE_BASE(AUTH_EVENTS); ESP_EVENT_DECLARE_BASE(AUTH_EVENTS);
/* IDs de eventos */
typedef enum { typedef enum {
AUTH_EVENT_TAG_PROCESSED, AUTH_EVENT_TAG_PROCESSED = 0, // resultado LOCAL -> auth_tag_event_data_t
AUTH_EVENT_TAG_SAVED, AUTH_EVENT_TAG_VERIFY, // pedir validação OCPP -> auth_tag_verify_event_t
AUTH_EVENT_ENABLED_CHANGED, AUTH_EVENT_TAG_SAVED, // registada (modo registo) -> auth_tag_event_data_t
AUTH_EVENT_INIT, AUTH_EVENT_MODE_CHANGED, // modo alterado -> auth_mode_event_data_t
AUTH_EVENT_INIT, // estado inicial -> auth_mode_event_data_t
} auth_event_id_t; } auth_event_id_t;
/* Payloads */
typedef struct { typedef struct {
char tag[AUTH_EVENT_TAG_MAX_LEN]; char tag[AUTH_TAG_MAX_LEN];
bool authorized; bool authorized;
} auth_tag_event_data_t; } auth_tag_event_data_t;
typedef struct { typedef struct {
bool enabled; char tag[AUTH_TAG_MAX_LEN];
} auth_enabled_event_data_t; uint32_t req_id; // opcional p/ correlacionar
} auth_tag_verify_event_t;
typedef struct {
auth_mode_t mode;
} auth_mode_event_data_t;

View File

@@ -0,0 +1,26 @@
#pragma once
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Tamanho máx. da tag (inclui NUL) */
#define AUTH_TAG_MAX_LEN 30
/* Modos de autorização */
typedef enum {
AUTH_MODE_OPEN = 0, // Sem autenticação
AUTH_MODE_LOCAL_RFID, // Lista local (NVS)
AUTH_MODE_OCPP_RFID // Validação via OCPP/CSMS
} auth_mode_t;
/* Converte enum -> "open"|"local"|"ocpp" (nunca NULL) */
const char *auth_mode_to_str(auth_mode_t mode);
/* Converte "open"|"local"|"ocpp" (case-insensitive) -> enum */
bool auth_mode_from_str(const char *s, auth_mode_t *out);
#ifdef __cplusplus
}
#endif

View File

@@ -1,62 +1,40 @@
// components/auth/src/auth.c
#include "auth.h" #include "auth.h"
#include "auth_events.h" #include "auth_events.h"
#include "esp_event.h" #include "esp_event.h"
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/queue.h> #include <freertos/queue.h>
#include <esp_log.h> #include <esp_log.h>
#include <string.h> #include <string.h>
#include <strings.h> // <-- necessário para strcasecmp
#include "wiegand_reader.h" #include "wiegand_reader.h"
#include "nvs_flash.h" #include "nvs_flash.h"
#include "nvs.h" #include "nvs.h"
#include "esp_random.h" #include "esp_random.h"
#define MAX_TAGS 50 #define MAX_TAGS 50
static const char *TAG = "Auth"; static const char *TAG = "Auth";
static bool enabled = false; /* ===== Estado ===== */
static auth_mode_t s_mode = AUTH_MODE_OPEN;
static bool waiting_for_registration = false; static bool waiting_for_registration = false;
static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN]; static char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0; static int tag_count = 0;
static uint32_t s_next_req_id = 1;
// NVS keys /* ===== NVS keys ===== */
#define NVS_NAMESPACE "auth" #define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_" #define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count" #define NVS_TAG_COUNT_KEY "count"
#define NVS_ENABLED_KEY "enabled" #define NVS_MODE_KEY "mode" // uint8_t
// ===========================
// NVS Persistence
// ===========================
static void load_auth_config(void) {
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
if (err == ESP_OK) {
uint8_t val;
if (nvs_get_u8(handle, NVS_ENABLED_KEY, &val) == ESP_OK) {
enabled = val;
ESP_LOGI(TAG, "Loaded auth enabled = %d", enabled);
}
nvs_close(handle);
} else {
ESP_LOGW(TAG, "No stored auth config found. Using default.");
}
}
static void save_auth_config(void) {
nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle) == ESP_OK) {
nvs_set_u8(handle, NVS_ENABLED_KEY, enabled);
nvs_commit(handle);
nvs_close(handle);
ESP_LOGI(TAG, "Auth config saved: enabled = %d", enabled);
} else {
ESP_LOGE(TAG, "Failed to save auth config.");
}
}
/* =========================
* NVS Persistence (tags)
* ========================= */
static void load_tags_from_nvs(void) { static void load_tags_from_nvs(void) {
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) { if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) {
@@ -71,7 +49,6 @@ static void load_tags_from_nvs(void) {
} }
tag_count = 0; tag_count = 0;
for (int i = 0; i < count && i < MAX_TAGS; i++) { for (int i = 0; i < count && i < MAX_TAGS; i++) {
char key[16]; char key[16];
char tag_buf[AUTH_TAG_MAX_LEN]; char tag_buf[AUTH_TAG_MAX_LEN];
@@ -109,10 +86,38 @@ static void save_tags_to_nvs(void) {
ESP_LOGI(TAG, "Tags saved to NVS (%d tags)", tag_count); ESP_LOGI(TAG, "Tags saved to NVS (%d tags)", tag_count);
} }
// =========================== /* =========================
// Internal Helpers * NVS Persistence (mode)
// =========================== * ========================= */
static void load_mode_from_nvs(void) {
nvs_handle_t h;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &h) == ESP_OK) {
uint8_t u = (uint8_t)AUTH_MODE_OPEN;
if (nvs_get_u8(h, NVS_MODE_KEY, &u) == ESP_OK) {
if (u <= (uint8_t)AUTH_MODE_OCPP_RFID) s_mode = (auth_mode_t)u;
}
nvs_close(h);
} else {
ESP_LOGW(TAG, "No stored auth mode in NVS; default OPEN");
}
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;
if (nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h) == ESP_OK) {
nvs_set_u8(h, NVS_MODE_KEY, (uint8_t)mode);
nvs_commit(h);
nvs_close(h);
ESP_LOGI(TAG, "Saved mode = %d (%s)", (int)mode, auth_mode_to_str(mode));
} else {
ESP_LOGE(TAG, "Failed to save auth mode to NVS");
}
}
/* =========================
* Helpers
* ========================= */
static bool is_tag_valid(const char *tag) { static bool is_tag_valid(const char *tag) {
for (int i = 0; i < tag_count; i++) { for (int i = 0; i < tag_count; i++) {
if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) { if (strncmp(valid_tags[i], tag, AUTH_TAG_MAX_LEN) == 0) {
@@ -122,44 +127,74 @@ static bool is_tag_valid(const char *tag) {
return false; return false;
} }
// =========================== /* =========================
// Public API * Public API
// =========================== * ========================= */
void auth_init(void) { void auth_init(void) {
load_auth_config(); load_mode_from_nvs();
load_tags_from_nvs(); load_tags_from_nvs();
if (enabled) { if (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID) {
initWiegand(); initWiegand();
ESP_LOGI(TAG, "Wiegand reader initialized (Auth enabled)"); ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
} else { } else {
ESP_LOGI(TAG, "Auth disabled, Wiegand reader not started"); ESP_LOGI(TAG, "Mode OPEN: Wiegand not started");
} }
auth_enabled_event_data_t evt = { .enabled = enabled }; auth_mode_event_data_t evt = { .mode = s_mode };
esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); esp_event_post(AUTH_EVENTS, AUTH_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
ESP_LOGI(TAG, "Initial AUTH state sent (enabled = %d)", enabled); ESP_LOGI(TAG, "AUTH INIT sent (mode=%s)", auth_mode_to_str(s_mode));
} }
void auth_set_enabled(bool value) { void auth_set_mode(auth_mode_t mode) {
enabled = value; if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID) {
save_auth_config(); ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
ESP_LOGI(TAG, "Auth %s", enabled ? "ENABLED" : "DISABLED"); return;
}
if (mode == s_mode) {
ESP_LOGI(TAG, "Mode unchanged: %s", auth_mode_to_str(mode));
return;
}
auth_enabled_event_data_t event = { .enabled = enabled }; s_mode = mode;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_ENABLED_CHANGED, &event, sizeof(event), portMAX_DELAY); save_mode_to_nvs(mode);
// Nota: se precisares, aqui podes parar/iniciar o Wiegand consoante o modo.
if (s_mode == AUTH_MODE_OPEN) {
ESP_LOGI(TAG, "Mode set to OPEN");
} else {
ESP_LOGI(TAG, "Mode set to %s; ensure Wiegand reader is running", 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);
} }
bool auth_is_enabled(void) { auth_mode_t auth_get_mode(void) {
return enabled; return s_mode;
}
const char *auth_mode_to_str(auth_mode_t mode) {
switch (mode) {
case AUTH_MODE_OPEN: return "open";
case AUTH_MODE_LOCAL_RFID: return "local";
case AUTH_MODE_OCPP_RFID: return "ocpp";
default: return "open";
}
}
bool auth_mode_from_str(const char *s, auth_mode_t *out) {
if (!s || !out) return false;
if (!strcasecmp(s, "open")) { *out = AUTH_MODE_OPEN; return true; }
if (!strcasecmp(s, "local")) { *out = AUTH_MODE_LOCAL_RFID; return true; }
if (!strcasecmp(s, "ocpp")) { *out = AUTH_MODE_OCPP_RFID; return true; }
return false;
} }
bool auth_add_tag(const char *tag) { bool auth_add_tag(const char *tag) {
if (tag_count >= MAX_TAGS) return false; if (tag_count >= MAX_TAGS) return false;
if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN) return false; if (!tag || strlen(tag) >= AUTH_TAG_MAX_LEN) return false;
if (is_tag_valid(tag)) return true; // Already exists if (is_tag_valid(tag)) return true; // existe
strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1); strncpy(valid_tags[tag_count], tag, AUTH_TAG_MAX_LEN - 1);
valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0'; valid_tags[tag_count][AUTH_TAG_MAX_LEN - 1] = '\0';
@@ -198,40 +233,62 @@ void auth_list_tags(void) {
} }
void auth_wait_for_tag_registration(void) { void auth_wait_for_tag_registration(void) {
if (s_mode != AUTH_MODE_LOCAL_RFID) {
ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
return;
}
waiting_for_registration = true; waiting_for_registration = true;
ESP_LOGI(TAG, "Tag registration mode enabled."); ESP_LOGI(TAG, "Tag registration mode enabled.");
} }
void auth_process_tag(const char *tag) { void auth_process_tag(const char *tag) {
if (!tag || !auth_is_enabled()) { if (!tag || !*tag) {
ESP_LOGW(TAG, "Auth disabled or NULL tag received."); ESP_LOGW(TAG, "NULL/empty tag received");
return; return;
} }
if (waiting_for_registration) { switch (s_mode) {
if (auth_add_tag(tag)) { case AUTH_MODE_OPEN: {
auth_tag_event_data_t event; // Sem verificação; normalmente nem é necessário evento.
strncpy(event.tag, tag, AUTH_EVENT_TAG_MAX_LEN - 1); ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
event.tag[AUTH_EVENT_TAG_MAX_LEN - 1] = '\0'; break;
event.authorized = true; }
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, &event, sizeof(event), portMAX_DELAY); case AUTH_MODE_LOCAL_RFID: {
ESP_LOGI(TAG, "Tag registered: %s", tag); if (waiting_for_registration) {
} else { waiting_for_registration = false;
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
if (auth_add_tag(tag)) {
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.authorized = true;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, &ev, sizeof(ev), portMAX_DELAY);
ESP_LOGI(TAG, "Tag registered: %s", tag);
} else {
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
}
return;
}
auth_tag_event_data_t ev = {0};
strncpy(ev.tag, tag, AUTH_TAG_MAX_LEN - 1);
ev.authorized = is_tag_valid(tag);
ESP_LOGI(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;
}
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.req_id = s_next_req_id++;
ESP_LOGI(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;
} }
waiting_for_registration = false;
return;
} }
auth_tag_event_data_t event;
strncpy(event.tag, tag, AUTH_EVENT_TAG_MAX_LEN - 1);
event.tag[AUTH_EVENT_TAG_MAX_LEN - 1] = '\0';
event.authorized = is_tag_valid(tag);
ESP_LOGI(TAG, "Tag %s: %s", tag, event.authorized ? "AUTHORIZED" : "DENIED");
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, &event, sizeof(event), portMAX_DELAY);
} }
int auth_get_tag_count(void) { int auth_get_tag_count(void) {

View File

@@ -0,0 +1,19 @@
#include "auth_types.h"
#include <strings.h> // strcasecmp
const char *auth_mode_to_str(auth_mode_t mode) {
switch (mode) {
case AUTH_MODE_OPEN: return "open";
case AUTH_MODE_LOCAL_RFID: return "local";
case AUTH_MODE_OCPP_RFID: return "ocpp";
default: return "open";
}
}
bool auth_mode_from_str(const char *s, auth_mode_t *out) {
if (!s || !out) return false;
if (!strcasecmp(s, "open")) { *out = AUTH_MODE_OPEN; return true; }
if (!strcasecmp(s, "local")) { *out = AUTH_MODE_LOCAL_RFID; return true; }
if (!strcasecmp(s, "ocpp")) { *out = AUTH_MODE_OCPP_RFID; return true; }
return false;
}

View File

@@ -6,10 +6,42 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "driver/gpio.h" #include "driver/gpio.h"
#include "driver/ledc.h" // Para buzzer passivo
#define BUZZER_GPIO GPIO_NUM_27 #define BUZZER_GPIO GPIO_NUM_27
static const char *TAG = "Buzzer"; static const char *TAG = "Buzzer";
// --- Configuração de modo ---
typedef enum {
BUZZER_MODE_ACTIVE = 0,
BUZZER_MODE_PASSIVE
} buzzer_mode_t;
typedef struct {
buzzer_mode_t mode;
gpio_num_t gpio;
// Apenas para PASSIVE
uint32_t freq_hz; // ex: 4000
uint8_t duty_percent; // 0100
ledc_mode_t ledc_speed_mode; // LEDC_LOW_SPEED_MODE
ledc_timer_t ledc_timer; // LEDC_TIMER_1
ledc_channel_t ledc_channel; // LEDC_CHANNEL_1
ledc_timer_bit_t duty_resolution; // LEDC_TIMER_10_BIT
} buzzer_config_t;
static buzzer_config_t s_buzzer_cfg = {
.mode = BUZZER_MODE_PASSIVE, // Mude para PASSIVE se for buzzer passivo
.gpio = BUZZER_GPIO,
.freq_hz = 4000,
.duty_percent = 50,
.ledc_speed_mode = LEDC_LOW_SPEED_MODE,
.ledc_timer = LEDC_TIMER_1,
.ledc_channel = LEDC_CHANNEL_1,
.duty_resolution = LEDC_TIMER_10_BIT
};
// --- Estrutura de passos ---
typedef struct { typedef struct {
uint16_t on_ms; uint16_t on_ms;
uint16_t off_ms; uint16_t off_ms;
@@ -39,9 +71,37 @@ static const buzzer_pattern_t buzzer_patterns[] = {
[BUZZER_PATTERN_CARD_DENIED] = {pattern_card_denied, sizeof(pattern_card_denied) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_CARD_DENIED] = {pattern_card_denied, sizeof(pattern_card_denied) / sizeof(buzzer_step_t)},
}; };
static void buzzer_on(void) { gpio_set_level(BUZZER_GPIO, 1); } // --- Funções de controle ---
static void buzzer_off(void) { gpio_set_level(BUZZER_GPIO, 0); } static inline uint32_t duty_from_percent(uint8_t pct, ledc_timer_bit_t res) {
if (pct == 0) return 0;
uint32_t max = (1U << res) - 1U;
return (uint32_t)((pct * max) / 100U);
}
static void buzzer_on(void) {
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) {
ledc_set_freq(s_buzzer_cfg.ledc_speed_mode,
s_buzzer_cfg.ledc_timer,
s_buzzer_cfg.freq_hz);
ledc_set_duty(s_buzzer_cfg.ledc_speed_mode,
s_buzzer_cfg.ledc_channel,
duty_from_percent(s_buzzer_cfg.duty_percent, s_buzzer_cfg.duty_resolution));
ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
} else {
gpio_set_level(s_buzzer_cfg.gpio, 1);
}
}
static void buzzer_off(void) {
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) {
ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, 0);
ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
} else {
gpio_set_level(s_buzzer_cfg.gpio, 0);
}
}
// --- Execução de padrões ---
static void buzzer_execute(buzzer_pattern_id_t pattern_id) { static void buzzer_execute(buzzer_pattern_id_t pattern_id) {
if ((int)pattern_id <= BUZZER_PATTERN_NONE || pattern_id >= BUZZER_PATTERN_MAX) { if ((int)pattern_id <= BUZZER_PATTERN_NONE || pattern_id >= BUZZER_PATTERN_MAX) {
ESP_LOGW(TAG, "Invalid buzzer pattern id: %d", pattern_id); ESP_LOGW(TAG, "Invalid buzzer pattern id: %d", pattern_id);
@@ -60,6 +120,7 @@ static void buzzer_execute(buzzer_pattern_id_t pattern_id) {
} }
} }
// --- Event Handlers ---
static void buzzer_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) { static void buzzer_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) {
if (base != BUZZER_EVENTS || id != BUZZER_EVENT_PLAY_PATTERN || data == NULL) return; if (base != BUZZER_EVENTS || id != BUZZER_EVENT_PLAY_PATTERN || data == NULL) return;
@@ -107,7 +168,6 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int
} }
} }
static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data) { static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *event_data) {
if (base != AUTH_EVENTS || event_data == NULL) return; if (base != AUTH_EVENTS || event_data == NULL) return;
@@ -117,7 +177,6 @@ static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)event_data; const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)event_data;
ESP_LOGI(TAG, "AUTH processed: tag=%s authorized=%d", evt->tag, evt->authorized); ESP_LOGI(TAG, "AUTH processed: tag=%s authorized=%d", evt->tag, evt->authorized);
buzzer_evt.pattern = evt->authorized ? BUZZER_PATTERN_CARD_READ : BUZZER_PATTERN_CARD_DENIED; buzzer_evt.pattern = evt->authorized ? BUZZER_PATTERN_CARD_READ : BUZZER_PATTERN_CARD_DENIED;
} else if (id == AUTH_EVENT_TAG_SAVED) { } else if (id == AUTH_EVENT_TAG_SAVED) {
buzzer_evt.pattern = BUZZER_PATTERN_CARD_ADD; buzzer_evt.pattern = BUZZER_PATTERN_CARD_ADD;
} else { } else {
@@ -127,15 +186,41 @@ static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY); esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY);
} }
// --- Inicialização ---
void buzzer_init(void) { void buzzer_init(void) {
gpio_config_t io = { if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) {
.pin_bit_mask = BIT64(BUZZER_GPIO), // Configura temporizador do PWM
.mode = GPIO_MODE_OUTPUT, ledc_timer_config_t tcfg = {
.pull_down_en = 0, .speed_mode = s_buzzer_cfg.ledc_speed_mode,
.pull_up_en = 0, .duty_resolution = s_buzzer_cfg.duty_resolution,
.intr_type = GPIO_INTR_DISABLE .timer_num = s_buzzer_cfg.ledc_timer,
}; .freq_hz = s_buzzer_cfg.freq_hz,
gpio_config(&io); .clk_cfg = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&tcfg));
// Configura canal PWM
ledc_channel_config_t ccfg = {
.gpio_num = s_buzzer_cfg.gpio,
.speed_mode = s_buzzer_cfg.ledc_speed_mode,
.channel = s_buzzer_cfg.ledc_channel,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = s_buzzer_cfg.ledc_timer,
.duty = 0,
.hpoint = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ccfg));
} else {
gpio_config_t io = {
.pin_bit_mask = (1ULL << s_buzzer_cfg.gpio),
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = 0,
.pull_up_en = 0,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io);
}
buzzer_off(); buzzer_off();
// Registro de handlers // Registro de handlers
@@ -155,14 +240,12 @@ void buzzer_init(void) {
AUTH_EVENT_TAG_SAVED, AUTH_EVENT_TAG_SAVED,
auth_event_handler, auth_event_handler,
NULL)); NULL));
ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS,
NETWORK_EVENT_AP_STARTED,
network_event_handler,
NULL));
ESP_ERROR_CHECK(esp_event_handler_register( ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s)",
NETWORK_EVENTS, s_buzzer_cfg.gpio,
NETWORK_EVENT_AP_STARTED, s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE ? "passive/PWM" : "active/ON-OFF");
network_event_handler,
NULL
));
ESP_LOGI(TAG, "Buzzer initialized on GPIO %d", BUZZER_GPIO);
} }

View File

@@ -1,7 +1,8 @@
#include <inttypes.h> // For PRI macros #include <inttypes.h> // For PRI macros
#include "evse_config.h" #include "evse_config.h"
#include "board_config.h" #include "board_config.h"
#include "evse_limits.h" #include "evse_limits.h"
#include "evse_api.h" // <— para evse_get_state / evse_state_is_charging
#include "esp_log.h" #include "esp_log.h"
#include "nvs.h" #include "nvs.h"
#include "esp_timer.h" #include "esp_timer.h"
@@ -13,50 +14,63 @@ static nvs_handle_t nvs;
// ======================== // ========================
// Configurable parameters // Configurable parameters
// ======================== // ========================
static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT; static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
static uint16_t charging_current; // Persisted (NVS) static uint16_t charging_current; // Persisted (NVS)
static uint16_t charging_current_runtime = 0; // Runtime only static uint16_t charging_current_runtime = 0; // Runtime only
static bool socket_outlet; static bool socket_outlet;
static bool rcm; static bool rcm;
static uint8_t temp_threshold = 60; static uint8_t temp_threshold = 60;
static bool require_auth; static bool require_auth;
// Availability / Enable flags
static bool is_available = true;
static bool is_enabled = true;
// ======================== // ========================
// Initialization // Initialization
// ======================== // ========================
esp_err_t evse_config_init(void) { esp_err_t evse_config_init(void)
{
ESP_LOGD(TAG, "Initializing NVS configuration..."); ESP_LOGD(TAG, "Initializing NVS configuration...");
return nvs_open("evse", NVS_READWRITE, &nvs); return nvs_open("evse", NVS_READWRITE, &nvs);
} }
void evse_check_defaults(void) { void evse_check_defaults(void)
{
esp_err_t err; esp_err_t err;
uint8_t u8; uint8_t u8;
uint16_t u16; uint16_t u16;
uint32_t u32; uint32_t u32;
bool needs_commit = false; bool needs_commit = false;
uint8_t u8_bool;
ESP_LOGD(TAG, "Checking default parameters..."); ESP_LOGD(TAG, "Checking default parameters...");
// Max charging current // Max charging current
err = nvs_get_u8(nvs, "max_chrg_curr", &u8); err = nvs_get_u8(nvs, "max_chrg_curr", &u8);
if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT) { if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT)
{
max_charging_current = MAX_CHARGING_CURRENT_LIMIT; max_charging_current = MAX_CHARGING_CURRENT_LIMIT;
nvs_set_u8(nvs, "max_chrg_curr", max_charging_current); nvs_set_u8(nvs, "max_chrg_curr", max_charging_current);
needs_commit = true; needs_commit = true;
ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current); ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current);
} else { }
else
{
max_charging_current = u8; max_charging_current = u8;
} }
// Charging current (default, persisted) // Charging current (default, persisted)
err = nvs_get_u16(nvs, "def_chrg_curr", &u16); err = nvs_get_u16(nvs, "def_chrg_curr", &u16);
if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT) || u16 > (max_charging_current)) { if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT) || u16 > (max_charging_current))
{
charging_current = max_charging_current; charging_current = max_charging_current;
nvs_set_u16(nvs, "def_chrg_curr", charging_current); nvs_set_u16(nvs, "def_chrg_curr", charging_current);
needs_commit = true; needs_commit = true;
ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current); ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current);
} else { }
else
{
charging_current = u16; charging_current = u16;
} }
@@ -67,7 +81,8 @@ void evse_check_defaults(void) {
// Auth required // Auth required
err = nvs_get_u8(nvs, "require_auth", &u8); err = nvs_get_u8(nvs, "require_auth", &u8);
require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false; require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false;
if (err != ESP_OK) { if (err != ESP_OK)
{
nvs_set_u8(nvs, "require_auth", require_auth); nvs_set_u8(nvs, "require_auth", require_auth);
needs_commit = true; needs_commit = true;
} }
@@ -75,7 +90,8 @@ void evse_check_defaults(void) {
// Socket outlet // Socket outlet
err = nvs_get_u8(nvs, "socket_outlet", &u8); err = nvs_get_u8(nvs, "socket_outlet", &u8);
socket_outlet = (err == ESP_OK && u8) && board_config.proximity; socket_outlet = (err == ESP_OK && u8) && board_config.proximity;
if (err != ESP_OK) { if (err != ESP_OK)
{
nvs_set_u8(nvs, "socket_outlet", socket_outlet); nvs_set_u8(nvs, "socket_outlet", socket_outlet);
needs_commit = true; needs_commit = true;
} }
@@ -83,7 +99,8 @@ void evse_check_defaults(void) {
// RCM // RCM
err = nvs_get_u8(nvs, "rcm", &u8); err = nvs_get_u8(nvs, "rcm", &u8);
rcm = (err == ESP_OK && u8) && board_config.rcm; rcm = (err == ESP_OK && u8) && board_config.rcm;
if (err != ESP_OK) { if (err != ESP_OK)
{
nvs_set_u8(nvs, "rcm", rcm); nvs_set_u8(nvs, "rcm", rcm);
needs_commit = true; needs_commit = true;
} }
@@ -91,7 +108,8 @@ void evse_check_defaults(void) {
// Temp threshold // Temp threshold
err = nvs_get_u8(nvs, "temp_threshold", &u8); err = nvs_get_u8(nvs, "temp_threshold", &u8);
temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60; temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60;
if (err != ESP_OK) { if (err != ESP_OK)
{
nvs_set_u8(nvs, "temp_threshold", temp_threshold); nvs_set_u8(nvs, "temp_threshold", temp_threshold);
needs_commit = true; needs_commit = true;
} }
@@ -106,12 +124,42 @@ void evse_check_defaults(void) {
if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK) if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK)
evse_set_under_power_limit(u16); 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).");
}
// Save to NVS if needed // Save to NVS if needed
if (needs_commit) { if (needs_commit)
{
err = nvs_commit(nvs); err = nvs_commit(nvs);
if (err == ESP_OK) { if (err == ESP_OK)
{
ESP_LOGD(TAG, "Configuration committed to NVS."); ESP_LOGD(TAG, "Configuration committed to NVS.");
} else { }
else
{
ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err));
} }
} }
@@ -120,11 +168,13 @@ void evse_check_defaults(void) {
// ======================== // ========================
// Charging current getters/setters // Charging current getters/setters
// ======================== // ========================
uint8_t evse_get_max_charging_current(void) { uint8_t evse_get_max_charging_current(void)
{
return max_charging_current; return max_charging_current;
} }
esp_err_t evse_set_max_charging_current(uint8_t value) { esp_err_t evse_set_max_charging_current(uint8_t value)
{
if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT) if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
max_charging_current = value; max_charging_current = value;
@@ -133,11 +183,13 @@ esp_err_t evse_set_max_charging_current(uint8_t value) {
return nvs_commit(nvs); return nvs_commit(nvs);
} }
uint16_t evse_get_charging_current(void) { uint16_t evse_get_charging_current(void)
{
return charging_current; return charging_current;
} }
esp_err_t evse_set_charging_current(uint16_t value) { 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; return ESP_ERR_INVALID_ARG;
charging_current = value; charging_current = value;
@@ -145,14 +197,16 @@ esp_err_t evse_set_charging_current(uint16_t value) {
return nvs_commit(nvs); return nvs_commit(nvs);
} }
uint16_t evse_get_default_charging_current(void) { uint16_t evse_get_default_charging_current(void)
{
uint16_t value; uint16_t value;
if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK) if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK)
return value; return value;
return charging_current; return charging_current;
} }
esp_err_t evse_set_default_charging_current(uint16_t value) { esp_err_t evse_set_default_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; return ESP_ERR_INVALID_ARG;
nvs_set_u16(nvs, "def_chrg_curr", value); nvs_set_u16(nvs, "def_chrg_curr", value);
@@ -162,49 +216,51 @@ esp_err_t evse_set_default_charging_current(uint16_t value) {
// ======================== // ========================
// Runtime current (not saved) // Runtime current (not saved)
// ======================== // ========================
void evse_set_runtime_charging_current(uint16_t value) { void evse_set_runtime_charging_current(uint16_t value)
{
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime); if (value > max_charging_current)
{
if (value > max_charging_current) {
value = max_charging_current; value = max_charging_current;
} else if (value < MIN_CHARGING_CURRENT_LIMIT) { }
else if (value < MIN_CHARGING_CURRENT_LIMIT)
{
value = MIN_CHARGING_CURRENT_LIMIT; value = MIN_CHARGING_CURRENT_LIMIT;
} }
charging_current_runtime = value; charging_current_runtime = value;
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
// --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE --- // --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE ---
evse_config_event_data_t evt = { evse_config_event_data_t evt = {
.charging = evse_state_is_charging(evse_get_state()), .charging = evse_state_is_charging(evse_get_state()),
.hw_max_current = (float)evse_get_max_charging_current(), .hw_max_current = (float)evse_get_max_charging_current(),
.runtime_current = (float)charging_current_runtime, .runtime_current = (float)evse_get_runtime_charging_current(),
.timestamp_us = esp_timer_get_time() .timestamp_us = esp_timer_get_time()};
};
esp_event_post(EVSE_EVENTS, esp_event_post(EVSE_EVENTS,
EVSE_EVENT_CONFIG_UPDATED, EVSE_EVENT_CONFIG_UPDATED,
&evt, &evt,
sizeof(evt), sizeof(evt),
portMAX_DELAY); portMAX_DELAY);
} }
uint16_t evse_get_runtime_charging_current(void)
uint16_t evse_get_runtime_charging_current(void) { {
return charging_current_runtime; return charging_current_runtime;
} }
// ======================== // ========================
// Socket outlet // Socket outlet
// ======================== // ========================
bool evse_get_socket_outlet(void) { bool evse_get_socket_outlet(void)
{
return socket_outlet; return socket_outlet;
} }
esp_err_t evse_set_socket_outlet(bool value) { esp_err_t evse_set_socket_outlet(bool value)
{
if (value && !board_config.proximity) if (value && !board_config.proximity)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
socket_outlet = value; socket_outlet = value;
@@ -215,11 +271,13 @@ esp_err_t evse_set_socket_outlet(bool value) {
// ======================== // ========================
// RCM // RCM
// ======================== // ========================
bool evse_is_rcm(void) { bool evse_is_rcm(void)
{
return rcm; return rcm;
} }
esp_err_t evse_set_rcm(bool value) { esp_err_t evse_set_rcm(bool value)
{
if (value && !board_config.rcm) if (value && !board_config.rcm)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
rcm = value; rcm = value;
@@ -230,11 +288,13 @@ esp_err_t evse_set_rcm(bool value) {
// ======================== // ========================
// Temperature // Temperature
// ======================== // ========================
uint8_t evse_get_temp_threshold(void) { uint8_t evse_get_temp_threshold(void)
{
return temp_threshold; return temp_threshold;
} }
esp_err_t evse_set_temp_threshold(uint8_t value) { esp_err_t evse_set_temp_threshold(uint8_t value)
{
if (value < 40 || value > 80) if (value < 40 || value > 80)
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
temp_threshold = value; temp_threshold = value;
@@ -242,29 +302,58 @@ esp_err_t evse_set_temp_threshold(uint8_t value) {
return nvs_commit(nvs); return nvs_commit(nvs);
} }
// ======================== // ========================
// Availability // Availability
// ======================== // ========================
static bool is_available = true; bool evse_config_is_available(void)
{
bool evse_config_is_available(void) {
return is_available; return is_available;
} }
void evse_config_set_available(bool available) { void evse_config_set_available(bool available)
is_available = available; {
is_available = available ? true : false;
// Persist
esp_err_t err = nvs_set_u8(nvs, "available", (uint8_t)is_available);
if (err == ESP_OK)
err = nvs_commit(nvs);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist 'available': %s", esp_err_to_name(err));
}
// AVAILABLE_UPDATED
evse_available_event_data_t e = {
.available = is_available,
.timestamp_us = esp_timer_get_time()};
esp_event_post(EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, &e, sizeof(e), portMAX_DELAY);
} }
// ======================== // ========================
// Enable/Disable // Enable/Disable
// ======================== // ========================
static bool is_enabled = true; bool evse_config_is_enabled(void)
{
bool evse_config_is_enabled(void) {
return is_enabled; return is_enabled;
} }
void evse_config_set_enabled(bool enabled) { void evse_config_set_enabled(bool enabled)
is_enabled = enabled; {
is_enabled = enabled ? true : false;
// Persist
esp_err_t err = nvs_set_u8(nvs, "enabled", (uint8_t)is_enabled);
if (err == ESP_OK)
err = nvs_commit(nvs);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to persist 'enabled': %s", esp_err_to_name(err));
}
// ENABLE_UPDATED
evse_enable_event_data_t e = {
.enabled = is_enabled,
.timestamp_us = esp_timer_get_time()};
esp_event_post(EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, &e, sizeof(e), portMAX_DELAY);
} }

View File

@@ -61,9 +61,7 @@ void evse_process(void) {
evse_state_t current = evse_get_state(); evse_state_t current = evse_get_state();
if (current != last_state) { if (current != last_state) {
ESP_LOGI(TAG, "State changed: %s → %s", //ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current));
evse_state_to_str(last_state),
evse_state_to_str(current));
last_state = current; last_state = current;
} }

View File

@@ -83,12 +83,13 @@ static void update_outputs(evse_state_t state) {
break; break;
case EVSE_STATE_C1: case EVSE_STATE_C1:
case EVSE_STATE_D1: case EVSE_STATE_D1: {
pilot_set_level(true); pilot_set_amps(MIN(current, cable_max_current)); // mantém PWM
ac_relay_set_state(false); // relé ainda desligado
c1_d1_waiting = true; c1_d1_waiting = true;
c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000); c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000);
break; break;
}
case EVSE_STATE_C2: case EVSE_STATE_C2:
case EVSE_STATE_D2: case EVSE_STATE_D2:
pilot_set_amps(MIN(current, cable_max_current)); pilot_set_amps(MIN(current, cable_max_current));

View File

@@ -6,6 +6,7 @@
#include "evse_api.h" #include "evse_api.h"
#include "evse_meter.h" #include "evse_meter.h"
#include "evse_session.h" #include "evse_session.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
@@ -16,6 +17,7 @@
#include "auth_events.h" #include "auth_events.h"
#include "loadbalancer_events.h" #include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "esp_event.h" #include "esp_event.h"
static const char *TAG = "EVSE_Manager"; static const char *TAG = "EVSE_Manager";
@@ -23,66 +25,142 @@ static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex; static SemaphoreHandle_t evse_mutex;
static bool auth_enabled = false; static bool auth_enabled = false;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo #define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
// ===== Task de ciclo principal ===== // ===== Task de ciclo principal =====
static void evse_manager_task(void *arg) { static void evse_manager_task(void *arg)
while (true) { {
while (true)
{
evse_manager_tick(); evse_manager_tick();
vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS)); vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS));
} }
} }
// ===== Tratador de eventos de autenticação ===== static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
static void on_auth_event(void* arg, esp_event_base_t base, int32_t id, void* data) { {
if (base != AUTH_EVENTS || data == NULL) return; if (base != AUTH_EVENTS || !data)
return;
switch (id) { auth_mode_t g_mode = AUTH_MODE_OPEN;
case AUTH_EVENT_TAG_PROCESSED: {
auth_tag_event_data_t *evt = (auth_tag_event_data_t*)data; switch (id)
ESP_LOGI("EVSE", "Tag: %s | Autorized: %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED"); {
evse_state_set_authorized(evt->authorized); case AUTH_EVENT_TAG_PROCESSED:
break; {
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);
break;
}
case AUTH_EVENT_MODE_CHANGED:
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)
{
evse_state_set_authorized(true);
auth_enabled = false;
} }
else
case AUTH_EVENT_ENABLED_CHANGED: {
case AUTH_EVENT_INIT: { evse_state_set_authorized(false);
auth_enabled_event_data_t *evt = (auth_enabled_event_data_t*)data; auth_enabled = true;
auth_enabled = evt->enabled;
ESP_LOGI("EVSE", "Auth %s (%s)",
id == AUTH_EVENT_ENABLED_CHANGED ? "ficou" : "init",
evt->enabled ? "ATIVO" : "INATIVO");
if (!auth_enabled) {
evse_state_set_authorized(true);
ESP_LOGI("EVSE", "Autenticação desativada → autorização forçada.");
} else {
evse_state_set_authorized(false);
ESP_LOGI("EVSE", "Autenticação ativada → aguardando autorização por tag.");
}
break;
} }
break;
}
} }
} }
// ===== Tratador de eventos de loadbalancer ===== // ===== Tratador de eventos de loadbalancer =====
static void on_loadbalancer_event(void* handler_arg, esp_event_base_t event_base, static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) { int32_t event_id, void *event_data)
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED) { {
const loadbalancer_state_event_t* evt = (const loadbalancer_state_event_t*) event_data; if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{
const loadbalancer_state_event_t *evt = (const loadbalancer_state_event_t *)event_data;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)", ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED", evt->timestamp_us); evt->enabled ? "ENABLED" : "DISABLED", evt->timestamp_us);
// Ações adicionais podem ser adicionadas aqui conforme necessário // Ações adicionais podem ser adicionadas aqui conforme necessário
} else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT) { }
const loadbalancer_master_limit_event_t* evt = (const loadbalancer_master_limit_event_t*) event_data; else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT)
{
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)", evt->max_current, evt->timestamp_us); ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)", evt->max_current, evt->timestamp_us);
evse_set_runtime_charging_current(evt->max_current); evse_set_runtime_charging_current(evt->max_current);
} }
}
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != OCPP_EVENTS)
return;
switch (id)
{
case OCPP_EVENT_AUTHORIZED:
ESP_LOGI(TAG, "[OCPP] Authorized");
evse_state_set_authorized(true);
break;
case OCPP_EVENT_AUTH_REJECTED:
ESP_LOGW(TAG, "[OCPP] Authorization rejected");
evse_state_set_authorized(false);
break;
case OCPP_EVENT_AUTH_TIMEOUT:
ESP_LOGW(TAG, "[OCPP] Authorization timeout");
evse_state_set_authorized(false);
break;
case OCPP_EVENT_REMOTE_START:
ESP_LOGI(TAG, "[OCPP] RemoteStart");
evse_state_set_authorized(true);
break;
case OCPP_EVENT_REMOTE_STOP:
ESP_LOGI(TAG, "[OCPP] RemoteStop");
evse_state_set_authorized(false);
break;
case OCPP_EVENT_START_TX:
ESP_LOGI(TAG, "[OCPP] StartTx");
break;
case OCPP_EVENT_STOP_TX:
ESP_LOGI(TAG, "[OCPP] StopTx");
evse_state_set_authorized(false);
break;
// hegou ChangeAvailability remoto (operative/inoperative)
case OCPP_EVENT_OPERATIVE_UPDATED:
{
if (!data)
{
ESP_LOGW(TAG, "[OCPP] OperativeUpdated sem payload — ignorado");
break;
}
const ocpp_operative_event_t *ev = (const ocpp_operative_event_t *)data;
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;
}
default:
ESP_LOGD(TAG, "[OCPP] Unhandled event id=%" PRId32, id);
break;
}
} }
// ===== Inicialização ===== // ===== Inicialização =====
void evse_manager_init(void) { void evse_manager_init(void)
{
evse_mutex = xSemaphoreCreateMutex(); evse_mutex = xSemaphoreCreateMutex();
evse_config_init(); evse_config_init();
@@ -94,13 +172,15 @@ void evse_manager_init(void) {
ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(OCPP_EVENTS, ESP_EVENT_ANY_ID, &on_ocpp_event, NULL)); // <— AQUI
ESP_LOGI(TAG, "EVSE Manager inicializado."); ESP_LOGI(TAG, "EVSE Manager inicializado.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL); xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
} }
// ===== Main Tick ===== // ===== Main Tick =====
void evse_manager_tick(void) { void evse_manager_tick(void)
{
xSemaphoreTake(evse_mutex, portMAX_DELAY); xSemaphoreTake(evse_mutex, portMAX_DELAY);
evse_hardware_tick(); evse_hardware_tick();
@@ -109,15 +189,20 @@ void evse_manager_tick(void) {
evse_temperature_check(); evse_temperature_check();
evse_session_tick(); evse_session_tick();
if (auth_enabled) { if (auth_enabled)
{
// If the car is disconnected, revoke authorization // If the car is disconnected, revoke authorization
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A) { if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
{
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization."); ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
evse_state_set_authorized(false); evse_state_set_authorized(false);
} }
} else { }
else
{
// If authentication is disabled, ensure authorization is always granted // If authentication is disabled, ensure authorization is always granted
if (!evse_state_get_authorized()) { if (!evse_state_get_authorized())
{
evse_state_set_authorized(true); evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization."); ESP_LOGI(TAG, "Authentication disabled → forced authorization.");
} }

View File

@@ -10,50 +10,57 @@
static const char *TAG = "evse_meter"; static const char *TAG = "evse_meter";
static SemaphoreHandle_t meter_mutex; static SemaphoreHandle_t meter_mutex;
typedef struct { typedef struct
{
uint32_t power_watts[EVSE_METER_PHASE_COUNT]; uint32_t power_watts[EVSE_METER_PHASE_COUNT];
float voltage[EVSE_METER_PHASE_COUNT]; float voltage[EVSE_METER_PHASE_COUNT];
float current[EVSE_METER_PHASE_COUNT]; float current[EVSE_METER_PHASE_COUNT];
uint32_t energy_wh; uint32_t energy_wh;
} evse_meter_data_t; } evse_meter_data_t;
static evse_meter_data_t meter_data; static evse_meter_data_t meter_data;
static void on_meter_event_dispatcher(void* arg, esp_event_base_t base, int32_t id, void* data) { static void on_meter_event_dispatcher(void *arg, esp_event_base_t base, int32_t id, void *data)
if (base == METER_EVENT && id == METER_EVENT_DATA_READY && data) { {
if (base == METER_EVENT && id == METER_EVENT_DATA_READY && data)
{
const meter_event_data_t *evt = (const meter_event_data_t *)data; const meter_event_data_t *evt = (const meter_event_data_t *)data;
if (strcmp(evt->source, "EVSE") == 0) { if (strcmp(evt->source, "EVSE") == 0)
{
evse_meter_on_meter_event(arg, data); evse_meter_on_meter_event(arg, data);
} }
} }
} }
void evse_meter_on_meter_event(void* arg, void* event_data) { void evse_meter_on_meter_event(void *arg, void *event_data)
{
const meter_event_data_t *evt = (const meter_event_data_t *)event_data; const meter_event_data_t *evt = (const meter_event_data_t *)event_data;
if (!evt) return; if (!evt)
return;
xSemaphoreTake(meter_mutex, portMAX_DELAY); xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
meter_data.power_watts[i] = evt->watt[i]; meter_data.power_watts[i] = evt->watt[i];
meter_data.voltage[i] = evt->vrms[i]; meter_data.voltage[i] = evt->vrms[i];
meter_data.current[i] = evt->irms[i]; meter_data.current[i] = evt->irms[i];
} }
meter_data.energy_wh = (uint32_t)(evt->total_energy * 1000.0f); meter_data.energy_wh = (uint32_t)(evt->total_energy * 1000.0f);
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, " "Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, " "voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, " "current[A]={%.2f,%.2f,%.2f}, "
"total_energy=%" PRIu32 "Wh", "total_energy=%" PRIu32 "Wh",
meter_data.power_watts[0], meter_data.power_watts[1], meter_data.power_watts[2], meter_data.power_watts[0], meter_data.power_watts[1], meter_data.power_watts[2],
meter_data.voltage[0], meter_data.voltage[1], meter_data.voltage[2], meter_data.voltage[0], meter_data.voltage[1], meter_data.voltage[2],
meter_data.current[0], meter_data.current[1], meter_data.current[2], meter_data.current[0], meter_data.current[1], meter_data.current[2],
meter_data.energy_wh meter_data.energy_wh);
);
} }
void evse_meter_init(void) { void evse_meter_init(void)
{
meter_mutex = xSemaphoreCreateMutex(); meter_mutex = xSemaphoreCreateMutex();
ESP_ERROR_CHECK(meter_mutex ? ESP_OK : ESP_FAIL); ESP_ERROR_CHECK(meter_mutex ? ESP_OK : ESP_FAIL);
ESP_ERROR_CHECK(esp_event_handler_register( ESP_ERROR_CHECK(esp_event_handler_register(
@@ -63,42 +70,51 @@ void evse_meter_init(void) {
ESP_LOGI(TAG, "EVSE Meter listener registered."); ESP_LOGI(TAG, "EVSE Meter listener registered.");
} }
int evse_meter_get_instant_power(void) { int evse_meter_get_instant_power(void)
{
xSemaphoreTake(meter_mutex, portMAX_DELAY); xSemaphoreTake(meter_mutex, portMAX_DELAY);
int sum = 0; int sum = 0;
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
sum += meter_data.power_watts[i]; sum += meter_data.power_watts[i];
} }
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
return sum; return sum;
} }
int evse_meter_get_total_energy(void) { int evse_meter_get_total_energy(void)
{
xSemaphoreTake(meter_mutex, portMAX_DELAY); xSemaphoreTake(meter_mutex, portMAX_DELAY);
int val = meter_data.energy_wh; int val = meter_data.energy_wh;
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
return val; return val;
} }
void evse_meter_get_power(int power[EVSE_METER_PHASE_COUNT]) { void evse_meter_get_power(int power[EVSE_METER_PHASE_COUNT])
{
xSemaphoreTake(meter_mutex, portMAX_DELAY); xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
power[i] = meter_data.power_watts[i]; power[i] = meter_data.power_watts[i];
} }
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
} }
void evse_meter_get_voltage(float voltage[EVSE_METER_PHASE_COUNT]) { void evse_meter_get_voltage(float voltage[EVSE_METER_PHASE_COUNT])
{
xSemaphoreTake(meter_mutex, portMAX_DELAY); xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
voltage[i] = meter_data.voltage[i]; voltage[i] = meter_data.voltage[i];
} }
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);
} }
void evse_meter_get_current(float current[EVSE_METER_PHASE_COUNT]) { void evse_meter_get_current(float current[EVSE_METER_PHASE_COUNT])
{
xSemaphoreTake(meter_mutex, portMAX_DELAY); xSemaphoreTake(meter_mutex, portMAX_DELAY);
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i)
{
current[i] = meter_data.current[i]; current[i] = meter_data.current[i];
} }
xSemaphoreGive(meter_mutex); xSemaphoreGive(meter_mutex);

View File

@@ -61,7 +61,7 @@ void pilot_init(void)
}; };
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0)); ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
ESP_ERROR_CHECK(ledc_fade_func_install(0)); //ESP_ERROR_CHECK(ledc_fade_func_install(0));
// Inicializa ADC121S021 externo // Inicializa ADC121S021 externo
adc121s021_dma_init(); adc121s021_dma_init();
@@ -107,6 +107,12 @@ void pilot_set_amps(uint16_t amps)
ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL); ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL);
} }
bool pilot_get_state(void) {
// true se estamos em 12V fixo; false se PWM ou -12V
// Se quiser diferenciar PWM, guarde um flag quando chamar set_amps.
return (last_pilot_level == 1) && (last_pwm_duty == 0);
}
static int compare_int(const void *a, const void *b) { static int compare_int(const void *a, const void *b) {

View File

@@ -10,6 +10,8 @@ typedef enum {
EVSE_EVENT_INIT, EVSE_EVENT_INIT,
EVSE_EVENT_STATE_CHANGED, EVSE_EVENT_STATE_CHANGED,
EVSE_EVENT_CONFIG_UPDATED, EVSE_EVENT_CONFIG_UPDATED,
EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED,
} evse_event_id_t; } evse_event_id_t;
typedef enum { typedef enum {
@@ -30,4 +32,15 @@ typedef struct {
int64_t timestamp_us; // Momento da atualização int64_t timestamp_us; // Momento da atualização
} evse_config_event_data_t; } evse_config_event_data_t;
// Eventos simples e específicos
typedef struct {
bool enabled; // novo estado de enabled
int64_t timestamp_us; // epoch micros
} evse_enable_event_data_t;
typedef struct {
bool available; // novo estado de available
int64_t timestamp_us; // epoch micros
} evse_available_event_data_t;
#endif // EVSE_EVENTS_H #endif // EVSE_EVENTS_H

View File

@@ -35,7 +35,7 @@ void pilot_set_level(bool level);
/** /**
* @brief Ativa o PWM do Pilot com corrente limitada * @brief Ativa o PWM do Pilot com corrente limitada
* *
* @param amps Corrente em décimos de ampère (ex: 160 = 16A) * @param amps Corrente em ampères (ex: 16 = 16A)
*/ */
void pilot_set_amps(uint16_t amps); void pilot_set_amps(uint16_t amps);

View File

@@ -11,19 +11,19 @@
static const char *TAG = "evse_link"; static const char *TAG = "evse_link";
// NVS keys // NVS keys
#define _NVS_NAMESPACE "evse_link" #define _NVS_NAMESPACE "evse_link"
#define _NVS_MODE_KEY "mode" #define _NVS_MODE_KEY "mode"
#define _NVS_ID_KEY "self_id" #define _NVS_ID_KEY "self_id"
#define _NVS_ENABLED_KEY "enabled" #define _NVS_ENABLED_KEY "enabled"
// UART parameters // UART parameters
#define UART_PORT UART_NUM_2 #define UART_PORT UART_NUM_2
#define UART_RX_BUF_SIZE 256 #define UART_RX_BUF_SIZE 256
// Runtime config // Runtime config
static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER; static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER;
static uint8_t _self_id = 0x01; static uint8_t _self_id = 0x01;
static bool _enabled = false; static bool _enabled = false;
// Registered Rx callback // Registered Rx callback
static evse_link_rx_cb_t _rx_cb = NULL; static evse_link_rx_cb_t _rx_cb = NULL;
@@ -33,71 +33,100 @@ extern void evse_link_master_init(void);
extern void evse_link_slave_init(void); extern void evse_link_slave_init(void);
static void framing_rx_cb(uint8_t src, uint8_t dest, static void framing_rx_cb(uint8_t src, uint8_t dest,
const uint8_t *payload, uint8_t len) { const uint8_t *payload, uint8_t len)
{
ESP_LOGD(TAG, "framing_rx_cb: src=0x%02X dest=0x%02X len=%u", src, dest, len); ESP_LOGD(TAG, "framing_rx_cb: src=0x%02X dest=0x%02X len=%u", src, dest, len);
if (_rx_cb) { if (_rx_cb)
{
_rx_cb(src, dest, payload, len); _rx_cb(src, dest, payload, len);
} }
} }
// Register protocol-level Rx callback // Register protocol-level Rx callback
void evse_link_register_rx_cb(evse_link_rx_cb_t cb) { void evse_link_register_rx_cb(evse_link_rx_cb_t cb)
{
_rx_cb = cb; _rx_cb = cb;
} }
// Load config from NVS // Load config from NVS
enum { EV_OK = ESP_OK }; enum
static void load_link_config(void) { {
EV_OK = ESP_OK
};
static void load_link_config(void)
{
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(_NVS_NAMESPACE, NVS_READONLY, &handle) != EV_OK) { if (nvs_open(_NVS_NAMESPACE, NVS_READONLY, &handle) != EV_OK)
{
ESP_LOGW(TAG, "NVS open failed, using defaults"); ESP_LOGW(TAG, "NVS open failed, using defaults");
return; return;
} }
uint8_t mode, id, en; uint8_t mode, id, en;
if (nvs_get_u8(handle, _NVS_MODE_KEY, &mode) == EV_OK && 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_MASTER || mode == EVSE_LINK_MODE_SLAVE))
{
_mode = (evse_link_mode_t)mode; _mode = (evse_link_mode_t)mode;
} }
if (nvs_get_u8(handle, _NVS_ID_KEY, &id) == EV_OK) { if (nvs_get_u8(handle, _NVS_ID_KEY, &id) == EV_OK)
{
_self_id = id; _self_id = id;
} }
if (nvs_get_u8(handle, _NVS_ENABLED_KEY, &en) == EV_OK) { if (nvs_get_u8(handle, _NVS_ENABLED_KEY, &en) == EV_OK)
{
_enabled = (en != 0); _enabled = (en != 0);
} }
nvs_close(handle); nvs_close(handle);
} }
// Save config to NVS // Save config to NVS
static void save_link_config(void) { static void save_link_config(void)
{
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open(_NVS_NAMESPACE, NVS_READWRITE, &handle) == EV_OK) { if (nvs_open(_NVS_NAMESPACE, NVS_READWRITE, &handle) == EV_OK)
{
nvs_set_u8(handle, _NVS_MODE_KEY, (uint8_t)_mode); nvs_set_u8(handle, _NVS_MODE_KEY, (uint8_t)_mode);
nvs_set_u8(handle, _NVS_ID_KEY, _self_id); nvs_set_u8(handle, _NVS_ID_KEY, _self_id);
nvs_set_u8(handle, _NVS_ENABLED_KEY, _enabled ? 1 : 0); nvs_set_u8(handle, _NVS_ENABLED_KEY, _enabled ? 1 : 0);
nvs_commit(handle); nvs_commit(handle);
nvs_close(handle); nvs_close(handle);
} else { }
else
{
ESP_LOGE(TAG, "Failed to save NVS"); ESP_LOGE(TAG, "Failed to save NVS");
} }
} }
// Getters/setters // Getters/setters
void evse_link_set_mode(evse_link_mode_t m) { _mode = m; save_link_config(); } void evse_link_set_mode(evse_link_mode_t m)
evse_link_mode_t evse_link_get_mode(void) { return _mode; } {
void evse_link_set_self_id(uint8_t id) { _self_id = id; save_link_config(); } _mode = m;
uint8_t evse_link_get_self_id(void) { return _self_id; } save_link_config();
void evse_link_set_enabled(bool en) { _enabled = en; save_link_config(); } }
bool evse_link_is_enabled(void) { return _enabled; } evse_link_mode_t evse_link_get_mode(void) { return _mode; }
void evse_link_set_self_id(uint8_t id)
{
_self_id = id;
save_link_config();
}
uint8_t evse_link_get_self_id(void) { return _self_id; }
void evse_link_set_enabled(bool en)
{
_enabled = en;
save_link_config();
}
bool evse_link_is_enabled(void) { return _enabled; }
// RX task: reads bytes from UART and feeds framing // RX task: reads bytes from UART and feeds framing
static void evse_link_rx_task(void *arg) { static void evse_link_rx_task(void *arg)
{
uint8_t buf[UART_RX_BUF_SIZE]; uint8_t buf[UART_RX_BUF_SIZE];
while (true) { while (true)
{
int len = uart_read_bytes(UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(1000)); int len = uart_read_bytes(UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(1000));
if (len > 0) { if (len > 0)
for (int i = 0; i < len; ++i) { {
for (int i = 0; i < len; ++i)
{
evse_link_recv_byte(buf[i]); evse_link_recv_byte(buf[i]);
} }
} }
@@ -105,13 +134,15 @@ static void evse_link_rx_task(void *arg) {
} }
// Initialize EVSE-Link component // Initialize EVSE-Link component
void evse_link_init(void) { void evse_link_init(void)
{
load_link_config(); load_link_config();
ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d", ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d",
_mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S', _mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S',
_self_id, _enabled); _self_id, _enabled);
if (!_enabled) return; if (!_enabled)
return;
// 1) framing layer init (sets up mutex, UART driver, etc.) // 1) framing layer init (sets up mutex, UART driver, etc.)
evse_link_framing_init(); evse_link_framing_init();
@@ -121,22 +152,27 @@ void evse_link_init(void) {
xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL); xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL);
// 3) delegate to master or slave // 3) delegate to master or slave
if (_mode == EVSE_LINK_MODE_MASTER) { if (_mode == EVSE_LINK_MODE_MASTER)
{
evse_link_master_init(); evse_link_master_init();
} else { }
else
{
evse_link_slave_init(); evse_link_slave_init();
} }
} }
// Send a frame (delegates to framing module) // Send a frame (delegates to framing module)
bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len) { bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len)
if (!evse_link_is_enabled()) return false; {
if (!evse_link_is_enabled())
return false;
uint8_t src = evse_link_get_self_id(); uint8_t src = evse_link_get_self_id();
return evse_link_framing_send(dest, src, payload, len); return evse_link_framing_send(dest, src, payload, len);
} }
// Receive byte (delegates to framing module) // Receive byte (delegates to framing module)
void evse_link_recv_byte(uint8_t byte) { void evse_link_recv_byte(uint8_t byte)
{
evse_link_framing_recv_byte(byte); evse_link_framing_recv_byte(byte);
} }

View File

@@ -11,7 +11,7 @@
#include "meter_events.h" #include "meter_events.h"
#include "evse_events.h" #include "evse_events.h"
#include "math.h" #include "math.h"
#include <inttypes.h> // Necessário para PRIu64 #include <inttypes.h> // Necessário para PRIu64
#ifndef MIN #ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b))
@@ -20,10 +20,10 @@
static const char *TAG = "loadbalancer"; static const char *TAG = "loadbalancer";
// Limites configuráveis // Limites configuráveis
#define MIN_CHARGING_CURRENT_LIMIT 6 // A #define MIN_CHARGING_CURRENT_LIMIT 6 // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A #define MAX_CHARGING_CURRENT_LIMIT 32 // A
#define MIN_GRID_CURRENT_LIMIT 6 // A #define MIN_GRID_CURRENT_LIMIT 6 // A
#define MAX_GRID_CURRENT_LIMIT 100 // A #define MAX_GRID_CURRENT_LIMIT 100 // A
// Parâmetros // Parâmetros
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT; static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
@@ -38,15 +38,16 @@ static input_filter_t evse_filter;
#define CONNECTOR_COUNT (MAX_SLAVES + 1) #define CONNECTOR_COUNT (MAX_SLAVES + 1)
// Estrutura unificada para master e slaves // Estrutura unificada para master e slaves
typedef struct { typedef struct
uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave {
bool is_master; uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave
bool charging; bool is_master;
float hw_max_current; bool charging;
float runtime_current; float hw_max_current;
int64_t timestamp; // microssegundos float runtime_current;
bool online; int64_t timestamp; // microssegundos
float assigned; bool online;
float assigned;
} evse_connector_t; } evse_connector_t;
static evse_connector_t connectors[CONNECTOR_COUNT]; static evse_connector_t connectors[CONNECTOR_COUNT];
@@ -58,27 +59,26 @@ static void init_connectors(void)
{ {
// master em índice 0 // master em índice 0
connectors[0] = (evse_connector_t){ connectors[0] = (evse_connector_t){
.id = 0xFF, .id = 0xFF,
.is_master = true, .is_master = true,
.charging = false, .charging = false,
.hw_max_current = MAX_CHARGING_CURRENT_LIMIT, .hw_max_current = MAX_CHARGING_CURRENT_LIMIT,
.runtime_current = 0, .runtime_current = 0,
.timestamp = 0, .timestamp = 0,
.online = false, .online = false,
.assigned = 0.0f .assigned = 0.0f};
};
// slaves em 1..CONNECTOR_COUNT-1 // slaves em 1..CONNECTOR_COUNT-1
for (int i = 1; i < CONNECTOR_COUNT; i++) { for (int i = 1; i < CONNECTOR_COUNT; i++)
{
connectors[i] = (evse_connector_t){ connectors[i] = (evse_connector_t){
.id = (uint8_t)(i - 1), .id = (uint8_t)(i - 1),
.is_master = false, .is_master = false,
.charging = false, .charging = false,
.hw_max_current = 0.0f, .hw_max_current = 0.0f,
.runtime_current = 0.0f, .runtime_current = 0.0f,
.timestamp = 0, .timestamp = 0,
.online = false, .online = false,
.assigned = 0.0f .assigned = 0.0f};
};
} }
} }
@@ -93,17 +93,18 @@ static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id
{ {
const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data; const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data;
if (status->slave_id >= MAX_SLAVES) { if (status->slave_id >= MAX_SLAVES)
{
ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id); ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id);
return; return;
} }
int idx = status->slave_id + 1; // slaves começam no índice 1 int idx = status->slave_id + 1; // slaves começam no índice 1
connectors[idx].charging = status->charging; connectors[idx].charging = status->charging;
connectors[idx].hw_max_current = status->hw_max_current; connectors[idx].hw_max_current = status->hw_max_current;
connectors[idx].runtime_current = status->runtime_current; connectors[idx].runtime_current = status->runtime_current;
connectors[idx].timestamp = esp_timer_get_time(); connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true; connectors[idx].online = true;
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA", "Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA",
@@ -111,31 +112,24 @@ static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id
status->hw_max_current, status->runtime_current); status->hw_max_current, status->runtime_current);
} }
static void on_evse_config_event(void *handler_arg,
static void on_evse_config_event(void* handler_arg,
esp_event_base_t base, esp_event_base_t base,
int32_t id, int32_t id,
void* event_data) void *event_data)
{ {
const evse_config_event_data_t *evt = (const evse_config_event_data_t*) event_data; const evse_config_event_data_t *evt = (const evse_config_event_data_t *)event_data;
int idx = 0; // MASTER INDICE 0 int idx = 0; // MASTER INDICE 0
connectors[idx].charging = evt->charging; connectors[idx].charging = evt->charging;
connectors[idx].hw_max_current = evt->hw_max_current; connectors[idx].hw_max_current = evt->hw_max_current;
connectors[idx].runtime_current = evt->runtime_current; connectors[idx].runtime_current = evt->runtime_current;
connectors[idx].timestamp = esp_timer_get_time(); connectors[idx].timestamp = esp_timer_get_time();
connectors[idx].online = true; connectors[idx].online = true;
ESP_LOGI(TAG, "EVSE config updated: charging=%d hw_max_current=%.1f runtime_current=%.1f", ESP_LOGI(TAG, "EVSE config updated: charging=%d hw_max_current=%.1f runtime_current=%.1f",
evt->charging, evt->hw_max_current, evt->runtime_current); evt->charging, evt->hw_max_current, evt->runtime_current);
} }
// --- Handlers de eventos externos --- // --- Handlers de eventos externos ---
static void loadbalancer_meter_event_handler(void *handler_arg, static void loadbalancer_meter_event_handler(void *handler_arg,
esp_event_base_t base, esp_event_base_t base,
@@ -149,13 +143,18 @@ static void loadbalancer_meter_event_handler(void *handler_arg,
for (int i = 1; i < 3; ++i) for (int i = 1; i < 3; ++i)
if (evt->irms[i] > max_irms) if (evt->irms[i] > max_irms)
max_irms = evt->irms[i]; max_irms = evt->irms[i];
if (evt->source && strcmp(evt->source, "GRID") == 0) { if (evt->source && strcmp(evt->source, "GRID") == 0)
{
grid_current = input_filter_update(&grid_filter, max_irms); grid_current = input_filter_update(&grid_filter, max_irms);
ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current); ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current);
} else if (evt->source && strcmp(evt->source, "EVSE") == 0) { }
else if (evt->source && strcmp(evt->source, "EVSE") == 0)
{
evse_current = input_filter_update(&evse_filter, max_irms); evse_current = input_filter_update(&evse_filter, max_irms);
ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current); ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
} else { }
else
{
ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source); ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source);
} }
} }
@@ -175,7 +174,7 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
evt->state == EVSE_STATE_EVENT_IDLE ? "IDLE" : "WAITING", evt->state == EVSE_STATE_EVENT_IDLE ? "IDLE" : "WAITING",
evt->state == EVSE_STATE_EVENT_IDLE ? "dis" : ""); evt->state == EVSE_STATE_EVENT_IDLE ? "dis" : "");
connectors[0].charging = false; connectors[0].charging = false;
connectors[0].online = true; // master está sempre online connectors[0].online = true; // master está sempre online
break; break;
case EVSE_STATE_EVENT_CHARGING: case EVSE_STATE_EVENT_CHARGING:
@@ -184,15 +183,15 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
evse_current = 0.0f; evse_current = 0.0f;
input_filter_reset(&grid_filter); input_filter_reset(&grid_filter);
input_filter_reset(&evse_filter); input_filter_reset(&evse_filter);
connectors[0].charging = true; connectors[0].charging = true;
connectors[0].online = true; connectors[0].online = true;
connectors[0].timestamp = esp_timer_get_time(); connectors[0].timestamp = esp_timer_get_time();
break; break;
case EVSE_STATE_EVENT_FAULT: case EVSE_STATE_EVENT_FAULT:
ESP_LOGW(TAG, "Local EVSE is in FAULT state - disabling load balancing temporarily"); ESP_LOGW(TAG, "Local EVSE is in FAULT state - disabling load balancing temporarily");
connectors[0].charging = false; connectors[0].charging = false;
connectors[0].online = true; // EVSE está online mas com falha connectors[0].online = true; // EVSE está online mas com falha
break; break;
default: default:
@@ -201,8 +200,6 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
} }
} }
// --- Config persistência --- // --- Config persistência ---
static esp_err_t loadbalancer_load_config() static esp_err_t loadbalancer_load_config()
{ {
@@ -215,7 +212,8 @@ static esp_err_t loadbalancer_load_config()
err = nvs_get_u8(handle, "max_grid_curr", &temp_u8); err = nvs_get_u8(handle, "max_grid_curr", &temp_u8);
if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT) if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
max_grid_current = temp_u8; max_grid_current = temp_u8;
else { else
{
max_grid_current = MAX_GRID_CURRENT_LIMIT; max_grid_current = MAX_GRID_CURRENT_LIMIT;
nvs_set_u8(handle, "max_grid_curr", max_grid_current); nvs_set_u8(handle, "max_grid_curr", max_grid_current);
needs_commit = true; needs_commit = true;
@@ -223,7 +221,8 @@ static esp_err_t loadbalancer_load_config()
err = nvs_get_u8(handle, "enabled", &temp_u8); err = nvs_get_u8(handle, "enabled", &temp_u8);
if (err == ESP_OK && temp_u8 <= 1) if (err == ESP_OK && temp_u8 <= 1)
loadbalancer_enabled = (temp_u8 != 0); loadbalancer_enabled = (temp_u8 != 0);
else { else
{
loadbalancer_enabled = false; loadbalancer_enabled = false;
nvs_set_u8(handle, "enabled", 0); nvs_set_u8(handle, "enabled", 0);
needs_commit = true; needs_commit = true;
@@ -238,13 +237,14 @@ static esp_err_t loadbalancer_load_config()
void loadbalancer_set_enabled(bool enabled) void loadbalancer_set_enabled(bool enabled)
{ {
nvs_handle_t handle; nvs_handle_t handle;
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK) { if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK)
{
nvs_set_u8(handle, "enabled", enabled ? 1 : 0); nvs_set_u8(handle, "enabled", enabled ? 1 : 0);
nvs_commit(handle); nvs_commit(handle);
nvs_close(handle); nvs_close(handle);
} }
loadbalancer_enabled = enabled; loadbalancer_enabled = enabled;
loadbalancer_state_event_t evt = { .enabled = enabled, .timestamp_us = esp_timer_get_time() }; loadbalancer_state_event_t evt = {.enabled = enabled, .timestamp_us = esp_timer_get_time()};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED, esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
&evt, sizeof(evt), portMAX_DELAY); &evt, sizeof(evt), portMAX_DELAY);
} }
@@ -276,8 +276,10 @@ bool loadbalancer_is_enabled(void)
// --- Task principal --- // --- Task principal ---
void loadbalancer_task(void *param) void loadbalancer_task(void *param)
{ {
while (true) { while (true)
if (!loadbalancer_is_enabled()) { {
if (!loadbalancer_is_enabled())
{
vTaskDelay(pdMS_TO_TICKS(5000)); vTaskDelay(pdMS_TO_TICKS(5000));
continue; continue;
} }
@@ -287,16 +289,19 @@ void loadbalancer_task(void *param)
int64_t now = esp_timer_get_time(); int64_t now = esp_timer_get_time();
// --- Atualiza estado online e conta ativos --- // --- Atualiza estado online e conta ativos ---
for (int i = 0; i < CONNECTOR_COUNT; i++) { for (int i = 0; i < CONNECTOR_COUNT; i++)
{
// --- Master nunca pode ficar offline --- // --- Master nunca pode ficar offline ---
if (connectors[i].is_master) { if (connectors[i].is_master)
{
connectors[i].online = true; connectors[i].online = true;
ESP_LOGI(TAG, "Connector[%d] ONLINE (MASTER, charging=%d, hw_max_current=%.1f)", ESP_LOGI(TAG, "Connector[%d] ONLINE (MASTER, charging=%d, hw_max_current=%.1f)",
i, connectors[i].charging, connectors[i].hw_max_current); i, connectors[i].charging, connectors[i].hw_max_current);
if (connectors[i].charging) { if (connectors[i].charging)
{
idxs[active_cnt++] = i; idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i); ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
} }
@@ -304,12 +309,14 @@ void loadbalancer_task(void *param)
} }
// --- Ignora conectores já marcados como offline --- // --- Ignora conectores já marcados como offline ---
if (!connectors[i].online) { if (!connectors[i].online)
{
continue; continue;
} }
// --- Timeout de heartbeat para escravos --- // --- Timeout de heartbeat para escravos ---
if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US) { if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US)
{
connectors[i].online = false; connectors[i].online = false;
ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)", ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)",
i, connectors[i].charging, (long long)(now - connectors[i].timestamp)); i, connectors[i].charging, (long long)(now - connectors[i].timestamp));
@@ -320,7 +327,8 @@ void loadbalancer_task(void *param)
i, connectors[i].charging, connectors[i].hw_max_current, i, connectors[i].charging, connectors[i].hw_max_current,
(long long)(now - connectors[i].timestamp)); (long long)(now - connectors[i].timestamp));
if (connectors[i].charging) { if (connectors[i].charging)
{
idxs[active_cnt++] = i; idxs[active_cnt++] = i;
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i); ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
} }
@@ -330,17 +338,23 @@ void loadbalancer_task(void *param)
// --- Calcula corrente disponível --- // --- Calcula corrente disponível ---
float available = max_grid_current - grid_current; float available = max_grid_current - grid_current;
if (available < MIN_CHARGING_CURRENT_LIMIT) { if (available < MIN_CHARGING_CURRENT_LIMIT)
{
available = MIN_CHARGING_CURRENT_LIMIT; available = MIN_CHARGING_CURRENT_LIMIT;
} else if (available > max_grid_current) { }
else if (available > max_grid_current)
{
available = max_grid_current; available = max_grid_current;
} }
ESP_LOGI(TAG, "LB Calc: available=%.1fA, active_connectors=%d", available, active_cnt); ESP_LOGI(TAG, "LB Calc: available=%.1fA, active_connectors=%d", available, active_cnt);
// --- Ordena conectores por hw_max_current (bubble sort simples) --- // --- Ordena conectores por hw_max_current (bubble sort simples) ---
for (int a = 0; a < active_cnt - 1; a++) { for (int a = 0; a < active_cnt - 1; a++)
for (int b = 0; b < active_cnt - 1 - a; b++) { {
if (connectors[idxs[b]].hw_max_current > connectors[idxs[b + 1]].hw_max_current) { for (int b = 0; b < active_cnt - 1 - a; b++)
{
if (connectors[idxs[b]].hw_max_current > connectors[idxs[b + 1]].hw_max_current)
{
int tmp = idxs[b]; int tmp = idxs[b];
idxs[b] = idxs[b + 1]; idxs[b] = idxs[b + 1];
idxs[b + 1] = tmp; idxs[b + 1] = tmp;
@@ -351,15 +365,20 @@ void loadbalancer_task(void *param)
// --- Distribui corrente (water-filling) --- // --- Distribui corrente (water-filling) ---
float remaining = available; float remaining = available;
int remaining_cnt = active_cnt; int remaining_cnt = active_cnt;
for (int k = 0; k < active_cnt; k++) { for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k]; int i = idxs[k];
float share = remaining / remaining_cnt; float share = remaining / remaining_cnt;
if (share >= connectors[i].hw_max_current) { if (share >= connectors[i].hw_max_current)
{
connectors[i].assigned = connectors[i].hw_max_current; connectors[i].assigned = connectors[i].hw_max_current;
remaining -= connectors[i].assigned; remaining -= connectors[i].assigned;
remaining_cnt--; remaining_cnt--;
} else { }
for (int m = k; m < active_cnt; m++) { else
{
for (int m = k; m < active_cnt; m++)
{
connectors[idxs[m]].assigned = share; connectors[idxs[m]].assigned = share;
} }
break; break;
@@ -367,39 +386,44 @@ void loadbalancer_task(void *param)
} }
// --- Aplica piso mínimo --- // --- Aplica piso mínimo ---
for (int k = 0; k < active_cnt; k++) { for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k]; int i = idxs[k];
if (connectors[i].assigned < MIN_CHARGING_CURRENT_LIMIT) { if (connectors[i].assigned < MIN_CHARGING_CURRENT_LIMIT)
{
connectors[i].assigned = MIN_CHARGING_CURRENT_LIMIT; connectors[i].assigned = MIN_CHARGING_CURRENT_LIMIT;
} }
} }
// --- Publica limites de corrente --- // --- Publica limites de corrente ---
for (int k = 0; k < active_cnt; k++) { for (int k = 0; k < active_cnt; k++)
{
int i = idxs[k]; int i = idxs[k];
uint16_t max_cur = (uint16_t)MIN(connectors[i].assigned, MAX_CHARGING_CURRENT_LIMIT); uint16_t max_cur = (uint16_t)MIN(connectors[i].assigned, MAX_CHARGING_CURRENT_LIMIT);
uint16_t current_rounded = (uint16_t)roundf(connectors[i].runtime_current); uint16_t current_rounded = (uint16_t)roundf(connectors[i].runtime_current);
if (current_rounded == max_cur) { if (current_rounded == max_cur)
{
continue; // sem alteração continue; // sem alteração
} }
if (connectors[i].is_master) { if (connectors[i].is_master)
{
loadbalancer_master_limit_event_t master_evt = { loadbalancer_master_limit_event_t master_evt = {
.slave_id = connectors[i].id, .slave_id = connectors[i].id,
.max_current = max_cur, .max_current = max_cur,
.timestamp_us = now .timestamp_us = now};
};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT, esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
&master_evt, sizeof(master_evt), portMAX_DELAY); &master_evt, sizeof(master_evt), portMAX_DELAY);
ESP_LOGI(TAG, "Master limit changed -> %.1f A (runtime=%.1f A)", ESP_LOGI(TAG, "Master limit changed -> %.1f A (runtime=%.1f A)",
(float)max_cur, connectors[i].runtime_current); (float)max_cur, connectors[i].runtime_current);
} else { }
else
{
loadbalancer_slave_limit_event_t slave_evt = { loadbalancer_slave_limit_event_t slave_evt = {
.slave_id = connectors[i].id, .slave_id = connectors[i].id,
.max_current = max_cur, .max_current = max_cur,
.timestamp_us = now .timestamp_us = now};
};
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
&slave_evt, sizeof(slave_evt), portMAX_DELAY); &slave_evt, sizeof(slave_evt), portMAX_DELAY);
ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (runtime=%.1f A)", ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (runtime=%.1f A)",
@@ -432,9 +456,9 @@ void loadbalancer_init(void)
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
&loadbalancer_evse_event_handler, NULL)); &loadbalancer_evse_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS,EVSE_EVENT_CONFIG_UPDATED, ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_CONFIG_UPDATED,
&on_evse_config_event, NULL)); &on_evse_config_event, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS,LOADBALANCER_EVENT_SLAVE_STATUS, ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_STATUS,
&on_slave_status, NULL)); &on_slave_status, NULL));
} }

View File

@@ -1,8 +1,9 @@
set(srcs set(srcs
"src/ocpp_events.c"
"src/ocpp.c" "src/ocpp.c"
) )
idf_component_register(SRCS "${srcs}" idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include" INCLUDE_DIRS "include"
PRIV_REQUIRES nvs_flash PRIV_REQUIRES nvs_flash
REQUIRES config esp_wifi evse mongoose MicroOcpp MicroOcppMongoose) REQUIRES esp_event config esp_wifi evse mongoose MicroOcpp MicroOcppMongoose)

View File

@@ -4,41 +4,35 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
/** #ifdef __cplusplus
* @brief Start ocpp extern "C" {
*/ #endif
void ocpp_start();
/** /**
* @brief Stop ocpp * @brief Start OCPP
* */
void ocpp_start(void);
/**
* @brief Stop OCPP
*/ */
void ocpp_stop(void); void ocpp_stop(void);
/* Config getters / setters */
bool ocpp_get_enabled(void); bool ocpp_get_enabled(void);
void ocpp_get_server(char *value);
void ocpp_get_rfid(char *value);
void ocpp_set_enabled(bool value); void ocpp_set_enabled(bool value);
void ocpp_get_server(char *value); // buffer >= 64
void ocpp_set_server(char *value); void ocpp_set_server(char *value);
void ocpp_set_rfid(char *value); void ocpp_get_charge_id(char *value); // buffer >= 64
void ocpp_set_charge_id(char *value);
void ocpp_begin_transaction(char *value); /* Estado de conexão */
bool ocpp_is_connected(void);
void ocpp_end_transaction(char *value); #ifdef __cplusplus
}
void ocpp_begin_transaction_authorized(char *value); #endif
void ocpp_end_transaction_authorized(char *value);
bool ocpp_is_TransactionActive();
void ocpp_set_plugged(bool value);
void ocpp_set_charging(bool value);
#endif /* OCPP_H_ */ #endif /* OCPP_H_ */

View File

@@ -0,0 +1,53 @@
#pragma once
#include "esp_event.h"
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Base de eventos do OCPP (igual ao padrão usado em auth_events.h) */
ESP_EVENT_DECLARE_BASE(OCPP_EVENTS);
/* IDs de eventos do OCPP */
typedef enum {
OCPP_EVENT_CONNECTED = 0, // payload: const char* (server URL) opcional
OCPP_EVENT_DISCONNECTED, // payload: NULL
OCPP_EVENT_AUTHORIZED, // payload: ocpp_idtag_event_t (opcional)
OCPP_EVENT_AUTH_REJECTED, // payload: ocpp_idtag_event_t (opcional)
OCPP_EVENT_AUTH_TIMEOUT, // payload: NULL
OCPP_EVENT_REMOTE_START, // payload: ocpp_idtag_event_t (opcional)
OCPP_EVENT_REMOTE_STOP, // payload: NULL
OCPP_EVENT_START_TX, // payload: ocpp_tx_event_t (opcional)
OCPP_EVENT_STOP_TX, // payload: ocpp_reason_event_t (opcional)
OCPP_EVENT_RESET, // payload: NULL
OCPP_EVENT_OPERATIVE_UPDATED
} ocpp_event_id_t;
/* Limites de strings simples (evita dependência de auth.h) */
#define OCPP_IDTAG_MAX 32
#define OCPP_REASON_MAX 32
/* Payloads opcionais */
typedef struct {
char idTag[OCPP_IDTAG_MAX];
} ocpp_idtag_event_t;
typedef struct {
int tx_id; // se disponível
} ocpp_tx_event_t;
typedef struct {
char reason[OCPP_REASON_MAX];
} ocpp_reason_event_t;
// Payload do novo evento
typedef struct {
bool operative; // true = Operative, false = Inoperative
int64_t timestamp_us; // esp_timer_get_time()
} ocpp_operative_event_t;
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
#include "ocpp_events.h"
/* Define a base, como em components/auth/src/auth_events.c */
ESP_EVENT_DEFINE_BASE(OCPP_EVENTS);

View File

@@ -1,5 +1,5 @@
// ========================= // =========================
// auth_api.c // auth_api.c (nova interface por modo)
// ========================= // =========================
#include "auth_api.h" #include "auth_api.h"
@@ -16,60 +16,101 @@ static const char *TAG = "auth_api";
static struct { static struct {
char username[128]; char username[128];
} users[10] = { /* {"admin"}, {"user1"} */ }; } users[10] = { {"admin"}, {"user1"} };
static int num_users = 2; static int num_users = 2;
// ================================= // =================================
// Handlers for Auth Methods (RFID) // Helpers
// ================================= // =================================
static esp_err_t auth_methods_get_handler(httpd_req_t *req) { static void send_json(httpd_req_t *req, cJSON *json) {
httpd_resp_set_type(req, "application/json");
cJSON *json = cJSON_CreateObject();
cJSON_AddBoolToObject(json, "RFID", auth_is_enabled());
char *str = cJSON_PrintUnformatted(json); char *str = cJSON_PrintUnformatted(json);
httpd_resp_sendstr(req, str); httpd_resp_set_type(req, "application/json");
free(str); httpd_resp_sendstr(req, str ? str : "{}");
if (str) free(str);
cJSON_Delete(json); cJSON_Delete(json);
}
static esp_err_t recv_body(httpd_req_t *req, char *buf, size_t buf_sz, int *out_len) {
int remaining = req->content_len;
int received = 0;
if (remaining <= 0) {
*out_len = 0;
return ESP_OK;
}
while (remaining > 0 && received < (int)(buf_sz - 1)) {
int chunk = remaining;
int space = (int)(buf_sz - 1 - received);
if (chunk > space) chunk = space;
int ret = httpd_req_recv(req, buf + received, chunk);
if (ret <= 0) return ESP_FAIL;
received += ret;
remaining -= ret;
}
buf[received] = '\0';
*out_len = received;
return ESP_OK; return ESP_OK;
} }
static esp_err_t auth_methods_post_handler(httpd_req_t *req) { // =================================
// Auth Mode (NEW API)
// =================================
static esp_err_t auth_mode_get_handler(httpd_req_t *req) {
auth_mode_t mode = auth_get_mode();
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "mode", auth_mode_to_str(mode));
send_json(req, json);
return ESP_OK;
}
static esp_err_t auth_mode_post_handler(httpd_req_t *req) {
char buf[256]; char buf[256];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); int len = 0;
if (len <= 0) { if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao receber dados"); httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao receber dados");
return ESP_FAIL; return ESP_FAIL;
} }
buf[len] = '\0'; cJSON *json = cJSON_ParseWithLength(buf, len);
cJSON *json = cJSON_Parse(buf);
if (!json) { if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido");
return ESP_FAIL; return ESP_FAIL;
} }
cJSON *rfid = cJSON_GetObjectItem(json, "RFID"); cJSON *mode_js = cJSON_GetObjectItem(json, "mode");
if (rfid && cJSON_IsBool(rfid)) { if (!cJSON_IsString(mode_js) || mode_js->valuestring == NULL) {
auth_set_enabled(cJSON_IsTrue(rfid));
} else {
cJSON_Delete(json); cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'RFID' inválido ou ausente"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'mode' inválido ou ausente");
return ESP_FAIL; return ESP_FAIL;
} }
auth_mode_t mode;
if (!auth_mode_from_str(mode_js->valuestring, &mode)) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Valor de 'mode' inválido (use: open|local|ocpp)");
return ESP_FAIL;
}
auth_set_mode(mode);
cJSON_Delete(json); cJSON_Delete(json);
httpd_resp_sendstr(req, "Métodos de autenticação atualizados");
cJSON *resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "mode", auth_mode_to_str(mode));
send_json(req, resp);
return ESP_OK; return ESP_OK;
} }
// ================================= // =================================
// User Management Handlers /* Users (mock) */
// ================================= // =================================
static esp_err_t users_get_handler(httpd_req_t *req) { static esp_err_t users_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject(); cJSON *root = cJSON_CreateObject();
cJSON *list = cJSON_CreateArray(); cJSON *list = cJSON_CreateArray();
for (int i = 0; i < num_users; ++i) { for (int i = 0; i < num_users; ++i) {
@@ -78,28 +119,27 @@ static esp_err_t users_get_handler(httpd_req_t *req) {
cJSON_AddItemToArray(list, u); cJSON_AddItemToArray(list, u);
} }
cJSON_AddItemToObject(root, "users", list); cJSON_AddItemToObject(root, "users", list);
char *str = cJSON_PrintUnformatted(root); send_json(req, root);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(root);
return ESP_OK; return ESP_OK;
} }
static esp_err_t users_post_handler(httpd_req_t *req) { static esp_err_t users_post_handler(httpd_req_t *req) {
char buf[128]; char buf[128];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); int len = 0;
if (len <= 0) return ESP_FAIL; if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK || len <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Body vazio");
buf[len] = '\0'; return ESP_FAIL;
}
if (num_users < 10) { if (num_users < 10) {
strlcpy(users[num_users].username, buf, sizeof(users[num_users].username)); strlcpy(users[num_users].username, buf, sizeof(users[num_users].username));
num_users++; num_users++;
httpd_resp_sendstr(req, "Usuário adicionado com sucesso"); httpd_resp_sendstr(req, "Usuário adicionado com sucesso");
return ESP_OK;
} else { } else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido");
return ESP_FAIL;
} }
return ESP_OK;
} }
static esp_err_t users_delete_handler(httpd_req_t *req) { static esp_err_t users_delete_handler(httpd_req_t *req) {
@@ -124,27 +164,21 @@ static esp_err_t users_delete_handler(httpd_req_t *req) {
} }
// ================================= // =================================
// Tag Management Handlers // Tags (apenas úteis no modo LOCAL)
// ================================= // =================================
static esp_err_t tags_get_handler(httpd_req_t *req) { static esp_err_t tags_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject(); cJSON *root = cJSON_CreateObject();
cJSON *list = cJSON_CreateArray(); cJSON *list = cJSON_CreateArray();
int count = auth_get_tag_count(); int count = auth_get_tag_count();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
const char *tag = auth_get_tag_by_index(i); const char *tag = auth_get_tag_by_index(i);
if (tag) { if (tag) cJSON_AddItemToArray(list, cJSON_CreateString(tag));
cJSON_AddItemToArray(list, cJSON_CreateString(tag));
}
} }
cJSON_AddItemToObject(root, "tags", list); cJSON_AddItemToObject(root, "tags", list);
char *str = cJSON_PrintUnformatted(root); send_json(req, root);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(root);
return ESP_OK; return ESP_OK;
} }
@@ -164,6 +198,10 @@ static esp_err_t tags_delete_handler(httpd_req_t *req) {
} }
static esp_err_t tags_register_handler(httpd_req_t *req) { static esp_err_t tags_register_handler(httpd_req_t *req) {
if (auth_get_mode() != AUTH_MODE_LOCAL_RFID) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Registo de tags disponível apenas no modo LOCAL");
return ESP_FAIL;
}
auth_wait_for_tag_registration(); auth_wait_for_tag_registration();
httpd_resp_sendstr(req, "Modo de registro de tag ativado"); httpd_resp_sendstr(req, "Modo de registro de tag ativado");
return ESP_OK; return ESP_OK;
@@ -174,21 +212,21 @@ static esp_err_t tags_register_handler(httpd_req_t *req) {
// ================================= // =================================
void register_auth_handlers(httpd_handle_t server, void *ctx) { void register_auth_handlers(httpd_handle_t server, void *ctx) {
// Auth methods // Auth mode
httpd_register_uri_handler(server, &(httpd_uri_t){ httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/auth-methods", .uri = "/api/v1/config/auth-mode",
.method = HTTP_GET, .method = HTTP_GET,
.handler = auth_methods_get_handler, .handler = auth_mode_get_handler,
.user_ctx = ctx .user_ctx = ctx
}); });
httpd_register_uri_handler(server, &(httpd_uri_t){ httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/auth-methods", .uri = "/api/v1/config/auth-mode",
.method = HTTP_POST, .method = HTTP_POST,
.handler = auth_methods_post_handler, .handler = auth_mode_post_handler,
.user_ctx = ctx .user_ctx = ctx
}); });
// Users // Users (mock)
httpd_register_uri_handler(server, &(httpd_uri_t){ httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/users", .uri = "/api/v1/config/users",
.method = HTTP_GET, .method = HTTP_GET,

View File

@@ -1,45 +1,63 @@
// ========================= // =========================
// ocpp_api.c // ocpp_api.c
// ========================= // =========================
#include "ocpp_api.h" #include "ocpp.h"
#include "esp_log.h" #include "esp_log.h"
#include "esp_err.h"
#include "esp_http_server.h"
#include "cJSON.h" #include "cJSON.h"
#include <string.h>
static const char *TAG = "ocpp_api"; static const char *TAG = "ocpp_api";
static struct {
char url[256];
char chargeBoxId[128];
char certificate[256];
char privateKey[256];
} ocpp_config = {"", "", "", ""};
static esp_err_t ocpp_get_status_handler(httpd_req_t *req) { static esp_err_t ocpp_get_status_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "GET /api/v1/ocpp");
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
char server[64] = {0};
char charge_id[64] = {0};
ocpp_get_server(server);
ocpp_get_charge_id(charge_id);
cJSON *status = cJSON_CreateObject(); cJSON *status = cJSON_CreateObject();
cJSON_AddStringToObject(status, "status", "connected"); cJSON_AddBoolToObject(status, "connected", ocpp_is_connected());
char *str = cJSON_Print(status); cJSON_AddStringToObject(status, "server", server);
cJSON_AddStringToObject(status, "charge_id", charge_id);
char *str = cJSON_PrintUnformatted(status);
httpd_resp_sendstr(req, str); httpd_resp_sendstr(req, str);
free(str); free(str);
cJSON_Delete(status); cJSON_Delete(status);
return ESP_OK; return ESP_OK;
} }
static esp_err_t ocpp_get_config_handler(httpd_req_t *req) { static esp_err_t ocpp_get_config_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "GET /api/v1/config/ocpp");
httpd_resp_set_type(req, "application/json"); httpd_resp_set_type(req, "application/json");
char server[64] = {0};
char charge_id[64] = {0};
bool enabled = ocpp_get_enabled();
ocpp_get_server(server);
ocpp_get_charge_id(charge_id);
cJSON *json = cJSON_CreateObject(); cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "url", ocpp_config.url); cJSON_AddBoolToObject(json, "enabled", enabled);
cJSON_AddStringToObject(json, "chargeBoxId", ocpp_config.chargeBoxId); cJSON_AddStringToObject(json, "url", server);
cJSON_AddStringToObject(json, "certificate", ocpp_config.certificate); cJSON_AddStringToObject(json, "chargeBoxId", charge_id);
cJSON_AddStringToObject(json, "privateKey", ocpp_config.privateKey);
char *str = cJSON_Print(json); char *str = cJSON_PrintUnformatted(json);
httpd_resp_sendstr(req, str); httpd_resp_sendstr(req, str);
free(str); free(str);
cJSON_Delete(json); cJSON_Delete(json);
return ESP_OK; return ESP_OK;
} }
static esp_err_t ocpp_post_config_handler(httpd_req_t *req) { static esp_err_t ocpp_post_config_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "POST /api/v1/config/ocpp");
char buf[512]; char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1); int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) { if (len <= 0) {
@@ -47,19 +65,28 @@ static esp_err_t ocpp_post_config_handler(httpd_req_t *req) {
return ESP_FAIL; return ESP_FAIL;
} }
buf[len] = '\0'; buf[len] = '\0';
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (!json) { if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL; return ESP_FAIL;
} }
cJSON *enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(enabled)) {
ocpp_set_enabled(cJSON_IsTrue(enabled));
}
cJSON *url = cJSON_GetObjectItem(json, "url"); cJSON *url = cJSON_GetObjectItem(json, "url");
if (url) strlcpy(ocpp_config.url, url->valuestring, sizeof(ocpp_config.url)); if (cJSON_IsString(url)) {
ocpp_set_server(url->valuestring);
}
cJSON *id = cJSON_GetObjectItem(json, "chargeBoxId"); cJSON *id = cJSON_GetObjectItem(json, "chargeBoxId");
if (id) strlcpy(ocpp_config.chargeBoxId, id->valuestring, sizeof(ocpp_config.chargeBoxId)); if (cJSON_IsString(id)) {
cJSON *cert = cJSON_GetObjectItem(json, "certificate"); ocpp_set_charge_id(id->valuestring);
if (cert) strlcpy(ocpp_config.certificate, cert->valuestring, sizeof(ocpp_config.certificate)); }
cJSON *key = cJSON_GetObjectItem(json, "privateKey");
if (key) strlcpy(ocpp_config.privateKey, key->valuestring, sizeof(ocpp_config.privateKey));
cJSON_Delete(json); cJSON_Delete(json);
httpd_resp_sendstr(req, "OCPP config atualizada com sucesso"); httpd_resp_sendstr(req, "OCPP config atualizada com sucesso");
return ESP_OK; return ESP_OK;

View File

@@ -13,8 +13,8 @@
} }
</style> </style>
<title>Vite + React</title> <title>Vite + React</title>
<script type="module" crossorigin src="/assets/index-ClgQvp_F.js"></script> <script type="module" crossorigin src="/assets/index-CmjuW5AW.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-AuNGQ-2m.css"> <link rel="stylesheet" crossorigin href="/assets/index-Bc9ibDeR.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -18,7 +18,7 @@
#include "network.h" #include "network.h"
#include "board_config.h" #include "board_config.h"
#include "logger.h" #include "logger.h"
#include "rest_main.h" #include "rest_main.h"
#include "peripherals.h" #include "peripherals.h"
#include "protocols.h" #include "protocols.h"
@@ -29,15 +29,15 @@
#include "meter_manager.h" #include "meter_manager.h"
#include "buzzer.h" #include "buzzer.h"
#include "evse_link.h" #include "evse_link.h"
#include "ocpp.h"
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 #define EVSE_MANAGER_TICK_PERIOD_MS 1000
#define AP_CONNECTION_TIMEOUT 120000 #define AP_CONNECTION_TIMEOUT 120000
#define RESET_HOLD_TIME 10000 #define RESET_HOLD_TIME 30000
#define DEBOUNCE_TIME_MS 50 #define DEBOUNCE_TIME_MS 50
#define PRESS_BIT BIT0 #define PRESS_BIT BIT0
#define RELEASED_BIT BIT1 #define RELEASED_BIT BIT1
static const char *TAG = "app_main"; static const char *TAG = "app_main";
@@ -46,11 +46,11 @@ static TickType_t press_tick = 0;
static TickType_t last_interrupt_tick = 0; static TickType_t last_interrupt_tick = 0;
static bool pressed = false; static bool pressed = false;
// //
// File system (SPIFFS) init and info // File system (SPIFFS) init and info
// //
static void fs_info(esp_vfs_spiffs_conf_t *conf) { static void fs_info(esp_vfs_spiffs_conf_t *conf)
{
size_t total = 0, used = 0; size_t total = 0, used = 0;
esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used); esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used);
if (ret == ESP_OK) if (ret == ESP_OK)
@@ -59,20 +59,19 @@ static void fs_info(esp_vfs_spiffs_conf_t *conf) {
ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", esp_err_to_name(ret)); ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", esp_err_to_name(ret));
} }
static void fs_init(void) { static void fs_init(void)
{
esp_vfs_spiffs_conf_t cfg_conf = { esp_vfs_spiffs_conf_t cfg_conf = {
.base_path = "/cfg", .base_path = "/cfg",
.partition_label = "cfg", .partition_label = "cfg",
.max_files = 1, .max_files = 1,
.format_if_mount_failed = false .format_if_mount_failed = false};
};
esp_vfs_spiffs_conf_t data_conf = { esp_vfs_spiffs_conf_t data_conf = {
.base_path = "/data", .base_path = "/data",
.partition_label = "data", .partition_label = "data",
.max_files = 5, .max_files = 5,
.format_if_mount_failed = true .format_if_mount_failed = true};
};
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf)); ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf));
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf)); ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf));
@@ -83,116 +82,139 @@ static void fs_init(void) {
// //
// Wi-Fi event monitoring task // Wi-Fi event monitoring task
// //
static void wifi_event_task_func(void *param) { static void wifi_event_task_func(void *param)
{
EventBits_t mode_bits; EventBits_t mode_bits;
for (;;) { for (;;)
{
// Wait indefinitely until either AP or STA mode is entered // Wait indefinitely until either AP or STA mode is entered
mode_bits = xEventGroupWaitBits( mode_bits = xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT,
pdFALSE, // do not clear bits on exit pdFALSE, // do not clear bits on exit
pdFALSE, // wait for any bit pdFALSE, // wait for any bit
portMAX_DELAY portMAX_DELAY);
);
if (mode_bits & WIFI_AP_MODE_BIT) { if (mode_bits & WIFI_AP_MODE_BIT)
{
// We're in AP mode: wait for a client to connect within the timeout // We're in AP mode: wait for a client to connect within the timeout
if (xEventGroupWaitBits( if (xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_AP_CONNECTED_BIT, WIFI_AP_CONNECTED_BIT,
pdFALSE, pdFALSE,
pdFALSE, pdFALSE,
pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT) pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) &
) & WIFI_AP_CONNECTED_BIT) { WIFI_AP_CONNECTED_BIT)
{
// Once connected, block until the client disconnects // Once connected, block until the client disconnects
xEventGroupWaitBits( xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_AP_DISCONNECTED_BIT, WIFI_AP_DISCONNECTED_BIT,
pdFALSE, pdFALSE,
pdFALSE, pdFALSE,
portMAX_DELAY portMAX_DELAY);
); }
} else { else
{
// Timeout expired with no client—optionally stop the AP // Timeout expired with no client—optionally stop the AP
if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)
{
// wifi_ap_stop(); // wifi_ap_stop();
} }
} }
} else if (mode_bits & WIFI_STA_MODE_BIT) { }
else if (mode_bits & WIFI_STA_MODE_BIT)
{
// We're in STA mode: block until disconnected from the AP // We're in STA mode: block until disconnected from the AP
xEventGroupWaitBits( xEventGroupWaitBits(
wifi_event_group, wifi_event_group,
WIFI_STA_DISCONNECTED_BIT, WIFI_STA_DISCONNECTED_BIT,
pdFALSE, pdFALSE,
pdFALSE, pdFALSE,
portMAX_DELAY portMAX_DELAY);
);
} }
// Prevent this task from hogging the CPU when idle // Prevent this task from hogging the CPU when idle
//vTaskDelay(pdMS_TO_TICKS(10)); // vTaskDelay(pdMS_TO_TICKS(10));
} }
} }
// //
// Button press handler // Button press handler
// //
static void handle_button_press(void) { static void handle_button_press(void)
{
// If not already in AP mode, start it // If not already in AP mode, start it
if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) { if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT))
{
ESP_LOGI(TAG, "Starting Wi-Fi AP mode"); ESP_LOGI(TAG, "Starting Wi-Fi AP mode");
wifi_ap_start(); wifi_ap_start();
} }
} }
// Task to handle button press/release notifications // Task to handle button press/release notifications
static void user_input_task_func(void *param) { static void user_input_task_func(void *param)
{
uint32_t notification; uint32_t notification;
for (;;) { for (;;)
{
// Wait for notification bits from ISR // Wait for notification bits from ISR
if (xTaskNotifyWait( if (xTaskNotifyWait(
0, // do not clear any bits on entry 0, // do not clear any bits on entry
UINT32_MAX, // clear all bits on exit UINT32_MAX, // clear all bits on exit
&notification, &notification,
portMAX_DELAY)) { portMAX_DELAY))
{
// Handle button press event // Handle button press event
if (notification & PRESS_BIT) { if (notification & PRESS_BIT)
{
press_tick = xTaskGetTickCount(); press_tick = xTaskGetTickCount();
pressed = true; pressed = true;
ESP_LOGI(TAG, "Button Pressed"); ESP_LOGI(TAG, "Button Pressed");
handle_button_press(); handle_button_press(); // só aqui
} }
// Handle button release event (only if previously pressed) if ((notification & RELEASED_BIT) && pressed)
if ((notification & RELEASED_BIT) && pressed) { {
pressed = false; pressed = false;
ESP_LOGI(TAG, "Button Released"); TickType_t held = xTaskGetTickCount() - press_tick;
handle_button_press(); ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)pdTICKS_TO_MS(held));
if (held >= pdMS_TO_TICKS(RESET_HOLD_TIME))
{
ESP_LOGW(TAG, "Long press: erasing NVS + reboot");
nvs_flash_erase();
esp_restart();
}
} }
} }
} }
} }
// ISR for button GPIO interrupt (active-low) // ISR for button GPIO interrupt (active-low)
static void IRAM_ATTR button_isr_handler(void *arg) { static void IRAM_ATTR button_isr_handler(void *arg)
{
BaseType_t higher_task_woken = pdFALSE; BaseType_t higher_task_woken = pdFALSE;
TickType_t now = xTaskGetTickCountFromISR(); TickType_t now = xTaskGetTickCountFromISR();
// Debounce: ignore interrupts occurring too close together // Debounce: ignore interrupts occurring too close together
if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) { if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS))
{
return; return;
} }
last_interrupt_tick = now; last_interrupt_tick = now;
// Read GPIO level: 0 = button pressed, 1 = button released // Read GPIO level: 0 = button pressed, 1 = button released
int level = gpio_get_level(board_config.button_wifi_gpio); int level = gpio_get_level(board_config.button_wifi_gpio);
if (level == 0) { if (level == 0)
{
// Notify task: button pressed // Notify task: button pressed
xTaskNotifyFromISR( xTaskNotifyFromISR(
user_input_task, user_input_task,
PRESS_BIT, PRESS_BIT,
eSetBits, eSetBits,
&higher_task_woken); &higher_task_woken);
} else { }
else
{
// Notify task: button released // Notify task: button released
xTaskNotifyFromISR( xTaskNotifyFromISR(
user_input_task, user_input_task,
@@ -202,20 +224,20 @@ static void IRAM_ATTR button_isr_handler(void *arg) {
} }
// Yield to higher priority task if unblocked // Yield to higher priority task if unblocked
if (higher_task_woken) { if (higher_task_woken)
{
portYIELD_FROM_ISR(); portYIELD_FROM_ISR();
} }
} }
static void button_init(void)
static void button_init(void) { {
gpio_config_t conf = { gpio_config_t conf = {
.pin_bit_mask = BIT64(board_config.button_wifi_gpio), .pin_bit_mask = BIT64(board_config.button_wifi_gpio),
.mode = GPIO_MODE_INPUT, .mode = GPIO_MODE_INPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE,
.pull_up_en = GPIO_PULLUP_ENABLE, .pull_up_en = GPIO_PULLUP_ENABLE,
.intr_type = GPIO_INTR_ANYEDGE .intr_type = GPIO_INTR_ANYEDGE};
};
ESP_ERROR_CHECK(gpio_config(&conf)); ESP_ERROR_CHECK(gpio_config(&conf));
ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL)); ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL));
} }
@@ -223,20 +245,23 @@ static void button_init(void) {
// //
// Inicialização dos módulos do sistema // Inicialização dos módulos do sistema
// //
static void init_modules(void) { static void init_modules(void)
{
peripherals_init(); peripherals_init();
//api_init(); wifi_ini();
// api_init();
buzzer_init(); buzzer_init();
ESP_ERROR_CHECK(rest_server_init("/data")); ESP_ERROR_CHECK(rest_server_init("/data"));
protocols_init(); protocols_init();
evse_manager_init(); evse_manager_init();
evse_init(); // Cria a task para FSM evse_init(); // Cria a task para FSM
button_init(); button_init();
auth_init(); auth_init();
loadbalancer_init(); loadbalancer_init();
meter_manager_init(); meter_manager_init();
meter_manager_start(); meter_manager_start();
evse_link_init(); evse_link_init();
ocpp_start();
// wifi_ap_start(); // wifi_ap_start();
// Outros módulos (descomente conforme necessário) // Outros módulos (descomente conforme necessário)
@@ -253,7 +278,8 @@ static void init_modules(void) {
// //
// Função principal do firmware // Função principal do firmware
// //
void app_main(void) { void app_main(void)
{
logger_init(); logger_init();
esp_log_set_vprintf(logger_vprintf); esp_log_set_vprintf(logger_vprintf);
@@ -261,7 +287,8 @@ void app_main(void) {
ESP_LOGI(TAG, "Reset reason: %d", reason); ESP_LOGI(TAG, "Reset reason: %d", reason);
esp_err_t ret = nvs_flash_init(); esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_LOGW(TAG, "Erasing NVS flash"); ESP_LOGW(TAG, "Erasing NVS flash");
ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init(); ret = nvs_flash_init();
@@ -274,8 +301,6 @@ void app_main(void) {
ESP_ERROR_CHECK(gpio_install_isr_service(0)); ESP_ERROR_CHECK(gpio_install_isr_service(0));
board_config_load(); board_config_load();
wifi_ini();
//wifi_ap_start();
init_modules(); init_modules();
xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL);

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import os import os
TAMANHO_MAX = 200000 # Limite por arquivo TAMANHO_MAX = 150000 # Limite por arquivo
def coletar_arquivos(diretorios, extensoes=(".c", ".h")): def coletar_arquivos(diretorios, extensoes=(".c", ".h")):
arquivos = [] arquivos = []
@@ -53,7 +53,7 @@ def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX):
def main(): def main():
diretorio_main = "main" diretorio_main = "main"
componentes_escolhidos = [ componentes_escolhidos = [
"evse", "loadbalancer" "ocpp"
] ]
diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos] diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos]