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, "chargingCurrent", evse_get_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, "rcm", evse_is_rcm());
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());
ocpp_get_server(str);
cJSON_AddStringToObject(root, "serverocpp", str);
ocpp_get_rfid(str);
cJSON_AddStringToObject(root, "rfid", str);
//ocpp_get_rfid(str);
//cJSON_AddStringToObject(root, "rfid", str);
return root;
}
@@ -93,7 +93,7 @@ esp_err_t json_set_evse_config(cJSON *root)
}
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")))
{
@@ -176,10 +176,10 @@ esp_err_t json_set_evse_config(cJSON *root)
ocpp_set_server(cJSON_GetStringValue(cJSON_GetObjectItem(root, "serverocpp")));
}
if (cJSON_IsString(cJSON_GetObjectItem(root, "rfid")))
{
ocpp_set_rfid(cJSON_GetStringValue(cJSON_GetObjectItem(root, "rfid")));
}
//if (cJSON_IsString(cJSON_GetObjectItem(root, "rfid")))
//{
// ocpp_set_rfid(cJSON_GetStringValue(cJSON_GetObjectItem(root, "rfid")));
//}
return ESP_OK;
}
@@ -343,7 +343,7 @@ cJSON *json_get_state(void)
cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state()));
cJSON_AddBoolToObject(root, "available", evse_config_is_available());
cJSON_AddBoolToObject(root, "enabled", evse_config_is_enabled());
cJSON_AddBoolToObject(root, "pendingAuth", auth_is_enabled());
cJSON_AddBoolToObject(root, "pendingAuth", auth_get_mode());
cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached());
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}"
INCLUDE_DIRS "include"

View File

@@ -1,115 +1,32 @@
#ifndef AUTH_H
#define AUTH_H
#pragma once
#include <stdbool.h>
#include <freertos/FreeRTOS.h>
#include "auth_types.h" // enum + MAX LEN para API
#ifdef __cplusplus
extern "C" {
#endif
/// Maximum length of an RFID tag (including null terminator)
#define AUTH_TAG_MAX_LEN 30
/// Event structure emitted after a tag is read
/* Evento auxiliar legado/útil (resultado local) */
typedef struct {
char tag[AUTH_TAG_MAX_LEN]; ///< The tag that was read
bool authorized; ///< true if the tag is valid
char tag[AUTH_TAG_MAX_LEN];
bool authorized;
} auth_event_t;
/**
* @brief Initializes the authentication system.
*
* - Loads configuration (enabled/disabled) from NVS
* - Starts the Wiegand reader
* - Emits AUTH_EVENT_INIT with current status
*/
void auth_init(void);
void auth_init(void);
void auth_set_mode(auth_mode_t mode);
auth_mode_t auth_get_mode(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);
/**
* @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);
/**
* @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);
/**
* @brief Logs all currently registered tags via ESP logging.
*/
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);
/**
* @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);
/**
* @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
*/
int auth_get_tag_count(void);
const char *auth_get_tag_by_index(int index);
#ifdef __cplusplus
}
#endif
#endif // AUTH_H

View File

@@ -1,22 +1,29 @@
#pragma once
#include "esp_event.h"
#define AUTH_EVENT_TAG_MAX_LEN 32
#include "auth_types.h" // só tipos comuns; evita incluir auth.h
ESP_EVENT_DECLARE_BASE(AUTH_EVENTS);
/* IDs de eventos */
typedef enum {
AUTH_EVENT_TAG_PROCESSED,
AUTH_EVENT_TAG_SAVED,
AUTH_EVENT_ENABLED_CHANGED,
AUTH_EVENT_INIT,
AUTH_EVENT_TAG_PROCESSED = 0, // resultado LOCAL -> auth_tag_event_data_t
AUTH_EVENT_TAG_VERIFY, // pedir validação OCPP -> auth_tag_verify_event_t
AUTH_EVENT_TAG_SAVED, // registada (modo registo) -> auth_tag_event_data_t
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;
/* Payloads */
typedef struct {
char tag[AUTH_EVENT_TAG_MAX_LEN];
char tag[AUTH_TAG_MAX_LEN];
bool authorized;
} auth_tag_event_data_t;
typedef struct {
bool enabled;
} auth_enabled_event_data_t;
char tag[AUTH_TAG_MAX_LEN];
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_events.h"
#include "esp_event.h"
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <esp_log.h>
#include <string.h>
#include <strings.h> // <-- necessário para strcasecmp
#include "wiegand_reader.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_random.h"
#define MAX_TAGS 50
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 char valid_tags[MAX_TAGS][AUTH_TAG_MAX_LEN];
static int tag_count = 0;
static uint32_t s_next_req_id = 1;
// NVS keys
/* ===== NVS keys ===== */
#define NVS_NAMESPACE "auth"
#define NVS_TAG_PREFIX "tag_"
#define NVS_TAG_COUNT_KEY "count"
#define NVS_ENABLED_KEY "enabled"
// ===========================
// 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.");
}
}
#define NVS_MODE_KEY "mode" // uint8_t
/* =========================
* NVS Persistence (tags)
* ========================= */
static void load_tags_from_nvs(void) {
nvs_handle_t handle;
if (nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle) != ESP_OK) {
@@ -71,7 +49,6 @@ static void load_tags_from_nvs(void) {
}
tag_count = 0;
for (int i = 0; i < count && i < MAX_TAGS; i++) {
char key[16];
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);
}
// ===========================
// 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) {
for (int i = 0; i < tag_count; i++) {
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;
}
// ===========================
// Public API
// ===========================
/* =========================
* Public API
* ========================= */
void auth_init(void) {
load_auth_config();
load_mode_from_nvs();
load_tags_from_nvs();
if (enabled) {
if (s_mode == AUTH_MODE_LOCAL_RFID || s_mode == AUTH_MODE_OCPP_RFID) {
initWiegand();
ESP_LOGI(TAG, "Wiegand reader initialized (Auth enabled)");
ESP_LOGI(TAG, "Wiegand reader initialized (mode=%s)", auth_mode_to_str(s_mode));
} 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_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) {
enabled = value;
save_auth_config();
ESP_LOGI(TAG, "Auth %s", enabled ? "ENABLED" : "DISABLED");
void auth_set_mode(auth_mode_t mode) {
if (mode < AUTH_MODE_OPEN || mode > AUTH_MODE_OCPP_RFID) {
ESP_LOGW(TAG, "Invalid mode: %d", (int)mode);
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 };
esp_event_post(AUTH_EVENTS, AUTH_EVENT_ENABLED_CHANGED, &event, sizeof(event), portMAX_DELAY);
s_mode = mode;
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) {
return enabled;
auth_mode_t auth_get_mode(void) {
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) {
if (tag_count >= MAX_TAGS) 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);
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) {
if (s_mode != AUTH_MODE_LOCAL_RFID) {
ESP_LOGW(TAG, "Registration is only available in LOCAL mode");
return;
}
waiting_for_registration = true;
ESP_LOGI(TAG, "Tag registration mode enabled.");
}
void auth_process_tag(const char *tag) {
if (!tag || !auth_is_enabled()) {
ESP_LOGW(TAG, "Auth disabled or NULL tag received.");
if (!tag || !*tag) {
ESP_LOGW(TAG, "NULL/empty tag received");
return;
}
if (waiting_for_registration) {
if (auth_add_tag(tag)) {
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 = true;
esp_event_post(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, &event, sizeof(event), portMAX_DELAY);
ESP_LOGI(TAG, "Tag registered: %s", tag);
} else {
ESP_LOGW(TAG, "Failed to register tag: %s", tag);
switch (s_mode) {
case AUTH_MODE_OPEN: {
// Sem verificação; normalmente nem é necessário evento.
ESP_LOGI(TAG, "Mode OPEN: tag=%s (no verification)", tag);
break;
}
case AUTH_MODE_LOCAL_RFID: {
if (waiting_for_registration) {
waiting_for_registration = false;
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) {

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/task.h"
#include "driver/gpio.h"
#include "driver/ledc.h" // Para buzzer passivo
#define BUZZER_GPIO GPIO_NUM_27
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 {
uint16_t on_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)},
};
static void buzzer_on(void) { gpio_set_level(BUZZER_GPIO, 1); }
static void buzzer_off(void) { gpio_set_level(BUZZER_GPIO, 0); }
// --- Funções de controle ---
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) {
if ((int)pattern_id <= BUZZER_PATTERN_NONE || pattern_id >= BUZZER_PATTERN_MAX) {
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) {
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) {
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;
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;
} else if (id == AUTH_EVENT_TAG_SAVED) {
buzzer_evt.pattern = BUZZER_PATTERN_CARD_ADD;
} 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);
}
// --- Inicialização ---
void buzzer_init(void) {
gpio_config_t io = {
.pin_bit_mask = BIT64(BUZZER_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_down_en = 0,
.pull_up_en = 0,
.intr_type = GPIO_INTR_DISABLE
};
gpio_config(&io);
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) {
// Configura temporizador do PWM
ledc_timer_config_t tcfg = {
.speed_mode = s_buzzer_cfg.ledc_speed_mode,
.duty_resolution = s_buzzer_cfg.duty_resolution,
.timer_num = s_buzzer_cfg.ledc_timer,
.freq_hz = s_buzzer_cfg.freq_hz,
.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();
// Registro de handlers
@@ -155,14 +240,12 @@ void buzzer_init(void) {
AUTH_EVENT_TAG_SAVED,
auth_event_handler,
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(
NETWORK_EVENTS,
NETWORK_EVENT_AP_STARTED,
network_event_handler,
NULL
));
ESP_LOGI(TAG, "Buzzer initialized on GPIO %d", BUZZER_GPIO);
ESP_LOGI(TAG, "Buzzer initialized on GPIO %d (%s)",
s_buzzer_cfg.gpio,
s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE ? "passive/PWM" : "active/ON-OFF");
}

View File

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

View File

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

View File

@@ -6,6 +6,7 @@
#include "evse_api.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "evse_config.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -16,6 +17,7 @@
#include "auth_events.h"
#include "loadbalancer_events.h"
#include "ocpp_events.h"
#include "esp_event.h"
static const char *TAG = "EVSE_Manager";
@@ -23,66 +25,142 @@ static const char *TAG = "EVSE_Manager";
static SemaphoreHandle_t evse_mutex;
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 =====
static void evse_manager_task(void *arg) {
while (true) {
static void evse_manager_task(void *arg)
{
while (true)
{
evse_manager_tick();
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) {
if (base != AUTH_EVENTS || data == NULL) return;
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != AUTH_EVENTS || !data)
return;
switch (id) {
case AUTH_EVENT_TAG_PROCESSED: {
auth_tag_event_data_t *evt = (auth_tag_event_data_t*)data;
ESP_LOGI("EVSE", "Tag: %s | Autorized: %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
evse_state_set_authorized(evt->authorized);
break;
auth_mode_t g_mode = AUTH_MODE_OPEN;
switch (id)
{
case AUTH_EVENT_TAG_PROCESSED:
{
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;
}
case AUTH_EVENT_ENABLED_CHANGED:
case AUTH_EVENT_INIT: {
auth_enabled_event_data_t *evt = (auth_enabled_event_data_t*)data;
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;
else
{
evse_state_set_authorized(false);
auth_enabled = true;
}
break;
}
}
}
// ===== Tratador de eventos de loadbalancer =====
static void on_loadbalancer_event(void* handler_arg, esp_event_base_t event_base,
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;
static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base,
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;
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
evt->enabled ? "ENABLED" : "DISABLED", evt->timestamp_us);
// 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);
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 =====
void evse_manager_init(void) {
void evse_manager_init(void)
{
evse_mutex = xSemaphoreCreateMutex();
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(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.");
xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL);
}
// ===== Main Tick =====
void evse_manager_tick(void) {
void evse_manager_tick(void)
{
xSemaphoreTake(evse_mutex, portMAX_DELAY);
evse_hardware_tick();
@@ -109,15 +189,20 @@ void evse_manager_tick(void) {
evse_temperature_check();
evse_session_tick();
if (auth_enabled) {
if (auth_enabled)
{
// 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.");
evse_state_set_authorized(false);
}
} else {
}
else
{
// If authentication is disabled, ensure authorization is always granted
if (!evse_state_get_authorized()) {
if (!evse_state_get_authorized())
{
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization.");
}

View File

@@ -10,50 +10,57 @@
static const char *TAG = "evse_meter";
static SemaphoreHandle_t meter_mutex;
typedef struct {
typedef struct
{
uint32_t power_watts[EVSE_METER_PHASE_COUNT];
float voltage[EVSE_METER_PHASE_COUNT];
float current[EVSE_METER_PHASE_COUNT];
float voltage[EVSE_METER_PHASE_COUNT];
float current[EVSE_METER_PHASE_COUNT];
uint32_t energy_wh;
} evse_meter_data_t;
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) {
if (base == METER_EVENT && id == METER_EVENT_DATA_READY && 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)
{
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);
}
}
}
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;
if (!evt) return;
if (!evt)
return;
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.voltage[i] = evt->vrms[i];
meter_data.current[i] = evt->irms[i];
meter_data.voltage[i] = evt->vrms[i];
meter_data.current[i] = evt->irms[i];
}
meter_data.energy_wh = (uint32_t)(evt->total_energy * 1000.0f);
xSemaphoreGive(meter_mutex);
ESP_LOGI(TAG,
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, "
"total_energy=%" PRIu32 "Wh",
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.current[0], meter_data.current[1], meter_data.current[2],
meter_data.energy_wh
);
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, "
"total_energy=%" PRIu32 "Wh",
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.current[0], meter_data.current[1], meter_data.current[2],
meter_data.energy_wh);
}
void evse_meter_init(void) {
void evse_meter_init(void)
{
meter_mutex = xSemaphoreCreateMutex();
ESP_ERROR_CHECK(meter_mutex ? ESP_OK : ESP_FAIL);
ESP_ERROR_CHECK(esp_event_handler_register(
@@ -63,42 +70,51 @@ void evse_meter_init(void) {
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);
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];
}
xSemaphoreGive(meter_mutex);
return sum;
}
int evse_meter_get_total_energy(void) {
int evse_meter_get_total_energy(void)
{
xSemaphoreTake(meter_mutex, portMAX_DELAY);
int val = meter_data.energy_wh;
xSemaphoreGive(meter_mutex);
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);
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];
}
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);
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];
}
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);
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];
}
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_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
adc121s021_dma_init();
@@ -107,6 +107,12 @@ void pilot_set_amps(uint16_t amps)
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) {

View File

@@ -10,6 +10,8 @@ typedef enum {
EVSE_EVENT_INIT,
EVSE_EVENT_STATE_CHANGED,
EVSE_EVENT_CONFIG_UPDATED,
EVSE_EVENT_ENABLE_UPDATED,
EVSE_EVENT_AVAILABLE_UPDATED,
} evse_event_id_t;
typedef enum {
@@ -30,4 +32,15 @@ typedef struct {
int64_t timestamp_us; // Momento da atualização
} 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

View File

@@ -35,7 +35,7 @@ void pilot_set_level(bool level);
/**
* @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);

View File

@@ -11,19 +11,19 @@
static const char *TAG = "evse_link";
// NVS keys
#define _NVS_NAMESPACE "evse_link"
#define _NVS_MODE_KEY "mode"
#define _NVS_ID_KEY "self_id"
#define _NVS_NAMESPACE "evse_link"
#define _NVS_MODE_KEY "mode"
#define _NVS_ID_KEY "self_id"
#define _NVS_ENABLED_KEY "enabled"
// UART parameters
#define UART_PORT UART_NUM_2
#define UART_PORT UART_NUM_2
#define UART_RX_BUF_SIZE 256
// Runtime config
static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER;
static uint8_t _self_id = 0x01;
static bool _enabled = false;
static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER;
static uint8_t _self_id = 0x01;
static bool _enabled = false;
// Registered Rx callback
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);
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);
if (_rx_cb) {
if (_rx_cb)
{
_rx_cb(src, dest, payload, len);
}
}
// 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;
}
// Load config from NVS
enum { EV_OK = ESP_OK };
static void load_link_config(void) {
enum
{
EV_OK = ESP_OK
};
static void load_link_config(void)
{
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");
return;
}
uint8_t mode, id, en;
if (nvs_get_u8(handle, _NVS_MODE_KEY, &mode) == EV_OK &&
(mode == EVSE_LINK_MODE_MASTER || mode == EVSE_LINK_MODE_SLAVE)) {
(mode == EVSE_LINK_MODE_MASTER || mode == EVSE_LINK_MODE_SLAVE))
{
_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;
}
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);
}
nvs_close(handle);
}
// Save config to NVS
static void save_link_config(void) {
static void save_link_config(void)
{
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_ID_KEY, _self_id);
nvs_set_u8(handle, _NVS_ENABLED_KEY, _enabled ? 1 : 0);
nvs_commit(handle);
nvs_close(handle);
} else {
}
else
{
ESP_LOGE(TAG, "Failed to save NVS");
}
}
// Getters/setters
void evse_link_set_mode(evse_link_mode_t m) { _mode = m; save_link_config(); }
evse_link_mode_t evse_link_get_mode(void) { return _mode; }
void evse_link_set_self_id(uint8_t id) { _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; }
void evse_link_set_mode(evse_link_mode_t m)
{
_mode = m;
save_link_config();
}
evse_link_mode_t evse_link_get_mode(void) { return _mode; }
void evse_link_set_self_id(uint8_t id)
{
_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
static void evse_link_rx_task(void *arg) {
static void evse_link_rx_task(void *arg)
{
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));
if (len > 0) {
for (int i = 0; i < len; ++i) {
if (len > 0)
{
for (int i = 0; i < len; ++i)
{
evse_link_recv_byte(buf[i]);
}
}
@@ -105,13 +134,15 @@ static void evse_link_rx_task(void *arg) {
}
// Initialize EVSE-Link component
void evse_link_init(void) {
void evse_link_init(void)
{
load_link_config();
ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d",
_mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S',
_self_id, _enabled);
if (!_enabled) return;
if (!_enabled)
return;
// 1) framing layer init (sets up mutex, UART driver, etc.)
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);
// 3) delegate to master or slave
if (_mode == EVSE_LINK_MODE_MASTER) {
if (_mode == EVSE_LINK_MODE_MASTER)
{
evse_link_master_init();
} else {
}
else
{
evse_link_slave_init();
}
}
// Send a frame (delegates to framing module)
bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len) {
if (!evse_link_is_enabled()) return false;
bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len)
{
if (!evse_link_is_enabled())
return false;
uint8_t src = evse_link_get_self_id();
return evse_link_framing_send(dest, src, payload, len);
}
// 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);
}

View File

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

View File

@@ -1,8 +1,9 @@
set(srcs
"src/ocpp_events.c"
"src/ocpp.c"
)
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
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 <stdbool.h>
/**
* @brief Start ocpp
*/
void ocpp_start();
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Stop ocpp
*
* @brief Start OCPP
*/
void ocpp_start(void);
/**
* @brief Stop OCPP
*/
void ocpp_stop(void);
/* Config getters / setters */
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_get_server(char *value); // buffer >= 64
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);
void ocpp_begin_transaction_authorized(char *value);
void ocpp_end_transaction_authorized(char *value);
bool ocpp_is_TransactionActive();
void ocpp_set_plugged(bool value);
void ocpp_set_charging(bool value);
#ifdef __cplusplus
}
#endif
#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"
@@ -16,60 +16,101 @@ static const char *TAG = "auth_api";
static struct {
char username[128];
} users[10] = { /* {"admin"}, {"user1"} */ };
} users[10] = { {"admin"}, {"user1"} };
static int num_users = 2;
// =================================
// Handlers for Auth Methods (RFID)
// Helpers
// =================================
static esp_err_t auth_methods_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
cJSON *json = cJSON_CreateObject();
cJSON_AddBoolToObject(json, "RFID", auth_is_enabled());
static void send_json(httpd_req_t *req, cJSON *json) {
char *str = cJSON_PrintUnformatted(json);
httpd_resp_sendstr(req, str);
free(str);
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, str ? str : "{}");
if (str) free(str);
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;
}
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];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) {
int 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");
return ESP_FAIL;
}
buf[len] = '\0';
cJSON *json = cJSON_Parse(buf);
cJSON *json = cJSON_ParseWithLength(buf, len);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido");
return ESP_FAIL;
}
cJSON *rfid = cJSON_GetObjectItem(json, "RFID");
if (rfid && cJSON_IsBool(rfid)) {
auth_set_enabled(cJSON_IsTrue(rfid));
} else {
cJSON *mode_js = cJSON_GetObjectItem(json, "mode");
if (!cJSON_IsString(mode_js) || mode_js->valuestring == NULL) {
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;
}
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);
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;
}
// =================================
// User Management Handlers
/* Users (mock) */
// =================================
static esp_err_t users_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject();
cJSON *list = cJSON_CreateArray();
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_AddItemToObject(root, "users", list);
char *str = cJSON_PrintUnformatted(root);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(root);
send_json(req, root);
return ESP_OK;
}
static esp_err_t users_post_handler(httpd_req_t *req) {
char buf[128];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) return ESP_FAIL;
buf[len] = '\0';
int len = 0;
if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK || len <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Body vazio");
return ESP_FAIL;
}
if (num_users < 10) {
strlcpy(users[num_users].username, buf, sizeof(users[num_users].username));
num_users++;
httpd_resp_sendstr(req, "Usuário adicionado com sucesso");
return ESP_OK;
} else {
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) {
@@ -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) {
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject();
cJSON *list = cJSON_CreateArray();
int count = auth_get_tag_count();
for (int i = 0; i < count; i++) {
const char *tag = auth_get_tag_by_index(i);
if (tag) {
cJSON_AddItemToArray(list, cJSON_CreateString(tag));
}
if (tag) cJSON_AddItemToArray(list, cJSON_CreateString(tag));
}
cJSON_AddItemToObject(root, "tags", list);
char *str = cJSON_PrintUnformatted(root);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(root);
send_json(req, root);
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) {
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();
httpd_resp_sendstr(req, "Modo de registro de tag ativado");
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) {
// Auth methods
// Auth mode
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/auth-methods",
.uri = "/api/v1/config/auth-mode",
.method = HTTP_GET,
.handler = auth_methods_get_handler,
.handler = auth_mode_get_handler,
.user_ctx = ctx
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/auth-methods",
.uri = "/api/v1/config/auth-mode",
.method = HTTP_POST,
.handler = auth_methods_post_handler,
.handler = auth_mode_post_handler,
.user_ctx = ctx
});
// Users
// Users (mock)
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/users",
.method = HTTP_GET,

View File

@@ -1,45 +1,63 @@
// =========================
// ocpp_api.c
// =========================
#include "ocpp_api.h"
#include "ocpp.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_http_server.h"
#include "cJSON.h"
#include <string.h>
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) {
ESP_LOGD(TAG, "GET /api/v1/ocpp");
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_AddStringToObject(status, "status", "connected");
char *str = cJSON_Print(status);
cJSON_AddBoolToObject(status, "connected", ocpp_is_connected());
cJSON_AddStringToObject(status, "server", server);
cJSON_AddStringToObject(status, "charge_id", charge_id);
char *str = cJSON_PrintUnformatted(status);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(status);
return ESP_OK;
}
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");
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_AddStringToObject(json, "url", ocpp_config.url);
cJSON_AddStringToObject(json, "chargeBoxId", ocpp_config.chargeBoxId);
cJSON_AddStringToObject(json, "certificate", ocpp_config.certificate);
cJSON_AddStringToObject(json, "privateKey", ocpp_config.privateKey);
char *str = cJSON_Print(json);
cJSON_AddBoolToObject(json, "enabled", enabled);
cJSON_AddStringToObject(json, "url", server);
cJSON_AddStringToObject(json, "chargeBoxId", charge_id);
char *str = cJSON_PrintUnformatted(json);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(json);
return ESP_OK;
}
static esp_err_t ocpp_post_config_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "POST /api/v1/config/ocpp");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) {
@@ -47,19 +65,28 @@ static esp_err_t ocpp_post_config_handler(httpd_req_t *req) {
return ESP_FAIL;
}
buf[len] = '\0';
cJSON *json = cJSON_Parse(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
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");
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");
if (id) strlcpy(ocpp_config.chargeBoxId, id->valuestring, sizeof(ocpp_config.chargeBoxId));
cJSON *cert = cJSON_GetObjectItem(json, "certificate");
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));
if (cJSON_IsString(id)) {
ocpp_set_charge_id(id->valuestring);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "OCPP config atualizada com sucesso");
return ESP_OK;

View File

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

View File

@@ -29,15 +29,15 @@
#include "meter_manager.h"
#include "buzzer.h"
#include "evse_link.h"
#include "ocpp.h"
#define EVSE_MANAGER_TICK_PERIOD_MS 1000
#define AP_CONNECTION_TIMEOUT 120000
#define RESET_HOLD_TIME 10000
#define DEBOUNCE_TIME_MS 50
#define AP_CONNECTION_TIMEOUT 120000
#define RESET_HOLD_TIME 30000
#define DEBOUNCE_TIME_MS 50
#define PRESS_BIT BIT0
#define RELEASED_BIT BIT1
#define PRESS_BIT BIT0
#define RELEASED_BIT BIT1
static const char *TAG = "app_main";
@@ -46,11 +46,11 @@ static TickType_t press_tick = 0;
static TickType_t last_interrupt_tick = 0;
static bool pressed = false;
//
// 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;
esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used);
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));
}
static void fs_init(void) {
static void fs_init(void)
{
esp_vfs_spiffs_conf_t cfg_conf = {
.base_path = "/cfg",
.partition_label = "cfg",
.max_files = 1,
.format_if_mount_failed = false
};
.format_if_mount_failed = false};
esp_vfs_spiffs_conf_t data_conf = {
.base_path = "/data",
.partition_label = "data",
.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(&data_conf));
@@ -83,116 +82,139 @@ static void fs_init(void) {
//
// 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;
for (;;) {
for (;;)
{
// Wait indefinitely until either AP or STA mode is entered
mode_bits = xEventGroupWaitBits(
wifi_event_group,
WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT,
pdFALSE, // do not clear bits on exit
pdFALSE, // wait for any bit
portMAX_DELAY
);
pdFALSE, // do not clear bits on exit
pdFALSE, // wait for any bit
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
if (xEventGroupWaitBits(
wifi_event_group,
WIFI_AP_CONNECTED_BIT,
pdFALSE,
pdFALSE,
pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)
) & WIFI_AP_CONNECTED_BIT) {
pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) &
WIFI_AP_CONNECTED_BIT)
{
// Once connected, block until the client disconnects
xEventGroupWaitBits(
wifi_event_group,
WIFI_AP_DISCONNECTED_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY
);
} else {
portMAX_DELAY);
}
else
{
// 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();
}
}
} 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
xEventGroupWaitBits(
wifi_event_group,
WIFI_STA_DISCONNECTED_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY
);
portMAX_DELAY);
}
// Prevent this task from hogging the CPU when idle
//vTaskDelay(pdMS_TO_TICKS(10));
// vTaskDelay(pdMS_TO_TICKS(10));
}
}
//
// Button press handler
//
static void handle_button_press(void) {
static void handle_button_press(void)
{
// 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");
wifi_ap_start();
}
}
// 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;
for (;;) {
for (;;)
{
// Wait for notification bits from ISR
if (xTaskNotifyWait(
0, // do not clear any bits on entry
UINT32_MAX, // clear all bits on exit
0, // do not clear any bits on entry
UINT32_MAX, // clear all bits on exit
&notification,
portMAX_DELAY)) {
portMAX_DELAY))
{
// Handle button press event
if (notification & PRESS_BIT) {
if (notification & PRESS_BIT)
{
press_tick = xTaskGetTickCount();
pressed = true;
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;
ESP_LOGI(TAG, "Button Released");
handle_button_press();
TickType_t held = xTaskGetTickCount() - press_tick;
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)
static void IRAM_ATTR button_isr_handler(void *arg) {
static void IRAM_ATTR button_isr_handler(void *arg)
{
BaseType_t higher_task_woken = pdFALSE;
TickType_t now = xTaskGetTickCountFromISR();
// 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;
}
last_interrupt_tick = now;
// Read GPIO level: 0 = button pressed, 1 = button released
int level = gpio_get_level(board_config.button_wifi_gpio);
if (level == 0) {
if (level == 0)
{
// Notify task: button pressed
xTaskNotifyFromISR(
user_input_task,
PRESS_BIT,
eSetBits,
&higher_task_woken);
} else {
}
else
{
// Notify task: button released
xTaskNotifyFromISR(
user_input_task,
@@ -202,20 +224,20 @@ static void IRAM_ATTR button_isr_handler(void *arg) {
}
// Yield to higher priority task if unblocked
if (higher_task_woken) {
if (higher_task_woken)
{
portYIELD_FROM_ISR();
}
}
static void button_init(void) {
static void button_init(void)
{
gpio_config_t conf = {
.pin_bit_mask = BIT64(board_config.button_wifi_gpio),
.mode = GPIO_MODE_INPUT,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.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_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
//
static void init_modules(void) {
static void init_modules(void)
{
peripherals_init();
//api_init();
wifi_ini();
// api_init();
buzzer_init();
ESP_ERROR_CHECK(rest_server_init("/data"));
protocols_init();
evse_manager_init();
evse_init(); // Cria a task para FSM
evse_init(); // Cria a task para FSM
button_init();
auth_init();
loadbalancer_init();
meter_manager_init();
meter_manager_start();
evse_link_init();
ocpp_start();
// wifi_ap_start();
// Outros módulos (descomente conforme necessário)
@@ -253,7 +278,8 @@ static void init_modules(void) {
//
// Função principal do firmware
//
void app_main(void) {
void app_main(void)
{
logger_init();
esp_log_set_vprintf(logger_vprintf);
@@ -261,7 +287,8 @@ void app_main(void) {
ESP_LOGI(TAG, "Reset reason: %d", reason);
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_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
@@ -274,8 +301,6 @@ void app_main(void) {
ESP_ERROR_CHECK(gpio_install_isr_service(0));
board_config_load();
wifi_ini();
//wifi_ap_start();
init_modules();
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
TAMANHO_MAX = 200000 # Limite por arquivo
TAMANHO_MAX = 150000 # Limite por arquivo
def coletar_arquivos(diretorios, extensoes=(".c", ".h")):
arquivos = []
@@ -53,7 +53,7 @@ def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX):
def main():
diretorio_main = "main"
componentes_escolhidos = [
"evse", "loadbalancer"
"ocpp"
]
diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos]