new release

This commit is contained in:
2026-05-15 12:20:47 +01:00
parent 286028b6a8
commit d0d431daf2
440 changed files with 2776 additions and 2462 deletions

View File

@@ -276,10 +276,7 @@ static void wiegand_sim_task(void *arg)
{
// lista fixa de idTags simuladas
static const char *idtaglist[] = {
"00000041349",
"W2602312345",
"W34ABCDE123",
};
"127239144069"};
const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]);
for (;;)
@@ -301,7 +298,7 @@ void initWiegand(void)
xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL);
// Para testes, podes ativar o simulador:
// ESP_LOGI(TAG, "Inicializando Wiegand simulado");
// xTaskCreate(wiegand_sim_task, "WiegandSim",
//ESP_LOGI(TAG, "Inicializando Wiegand simulado");
//xTaskCreate(wiegand_sim_task, "WiegandSim",
// configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
}

View File

@@ -3,8 +3,7 @@
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "buzzer_events.h" // para buzzer_pattern_id_t
#include "buzzer_events.h" // buzzer_pattern_id_t
#ifdef __cplusplus
extern "C" {
@@ -33,7 +32,7 @@ void buzzer_stop(void);
/**
* @brief Ativa/Desativa globalmente o buzzer (mute).
* Quando desativado, não toca e garante nível/desligado.
* Quando desativado, não toca e garante desligado.
*/
void buzzer_set_enabled(bool enabled);
@@ -52,9 +51,10 @@ void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min);
/**
* @brief Ajusta a frequência PWM (somente PASSIVE/LEDC; timer exclusivo recomendado).
* @return ESP_OK em sucesso; erro se modo inválido ou argumento fora do range.
* @param hz Frequência desejada.
* @return Frequência efetiva configurada (Hz). 0 em erro (modo inválido ou argumento fora do range).
*/
esp_err_t buzzer_set_frequency(uint32_t hz);
uint32_t buzzer_set_frequency(uint32_t hz);
/**
* @brief Define o duty cycle em porcentagem (0..100) para PASSIVE/LEDC.
@@ -63,4 +63,4 @@ void buzzer_set_duty_percent(uint8_t pct);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -1,5 +1,6 @@
// buzzer.c
#include "buzzer_events.h"
#include "buzzer.h"
#include "evse_events.h"
#include "auth_events.h"
#include "network_events.h"
@@ -11,27 +12,26 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/event_groups.h"
#include "driver/gpio.h"
#include "driver/ledc.h"
#include <time.h>
#include <string.h>
#include <stddef.h>
// ===================== Configuração padrão =====================
#define CONFIG_BUZZER_GPIO GPIO_NUM_27
// 1 = PASSIVE (PWM), 0 = ACTIVE (on/off)
#define CONFIG_BUZZER_MODE_PASSIVE 0
#define CONFIG_BUZZER_MODE_PASSIVE 1
#define CONFIG_BUZZER_FREQ_HZ 3500
#define CONFIG_BUZZER_DUTY_PCT 70
#define CONFIG_BUZZER_FREQ_HZ 2400
#define CONFIG_BUZZER_DUTY_PCT 40
#define CONFIG_BUZZER_QUEUE_LEN 8
#define CONFIG_BUZZER_TASK_STACK 2048
#define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1)
// anti-spam (gap mínimo entre toques)
@@ -41,7 +41,6 @@
// quiet hours start (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_START_MIN (22 * 60)
// quiet hours end (minutos desde 00:00)
#define CONFIG_BUZZER_QUIET_END_MIN (7 * 60)
@@ -113,6 +112,31 @@ static TaskHandle_t s_buzzer_task = NULL;
static TickType_t s_last_play_tick = 0;
static const TickType_t s_min_gap_ticks = pdMS_TO_TICKS(CONFIG_BUZZER_MIN_GAP_MS);
// STOP / preempt (robusto em dual-core)
static EventGroupHandle_t s_buzzer_eg = NULL;
#define BUZZER_EG_STOP_BIT (1u << 0)
static bool s_inited = false;
static inline bool buzzer_should_stop(void)
{
if (!s_buzzer_eg)
return false;
return (xEventGroupGetBits(s_buzzer_eg) & BUZZER_EG_STOP_BIT) != 0;
}
static inline void buzzer_clear_stop(void)
{
if (s_buzzer_eg)
xEventGroupClearBits(s_buzzer_eg, BUZZER_EG_STOP_BIT);
}
static inline void buzzer_set_stop(void)
{
if (s_buzzer_eg)
xEventGroupSetBits(s_buzzer_eg, BUZZER_EG_STOP_BIT);
}
// ===================== Padrões (afinados) =====================
// NOTA: os enums BUZZER_PATTERN_* vêm de buzzer_events.h
@@ -139,7 +163,8 @@ static const buzzer_step_t pattern_fault[] = {
{450, 0} // bloco 2
};
static const buzzer_pattern_t buzzer_patterns[] = {
// BUG importante corrigido: tamanho explícito = BUZZER_PATTERN_MAX
static const buzzer_pattern_t buzzer_patterns[BUZZER_PATTERN_MAX] = {
[BUZZER_PATTERN_PLUGGED] = {pattern_plugged, sizeof(pattern_plugged) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_UNPLUGGED] = {pattern_unplugged, sizeof(pattern_unplugged) / sizeof(buzzer_step_t)},
[BUZZER_PATTERN_CHARGING] = {pattern_charging, sizeof(pattern_charging) / sizeof(buzzer_step_t)},
@@ -161,38 +186,46 @@ static inline uint32_t duty_from_percent(uint8_t pct, ledc_timer_bit_t res)
return (uint32_t)((pct * max) / 100U);
}
static inline void buzzer_set_duty_raw(uint32_t duty)
{
ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, duty);
ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
}
static inline void buzzer_set_duty_pct(uint8_t pct)
{
buzzer_set_duty_raw(duty_from_percent(pct, s_buzzer_cfg.duty_resolution));
}
// Fade async (NO_WAIT) para permitir STOP responsivo (checamos por nós)
static inline void buzzer_fade_to_pct_async(uint8_t pct, uint32_t time_ms)
{
uint32_t duty = duty_from_percent(pct, s_buzzer_cfg.duty_resolution);
ledc_set_fade_with_time(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, duty, time_ms);
ledc_fade_start(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, LEDC_FADE_NO_WAIT);
}
static inline bool is_pattern_valid(buzzer_pattern_id_t id)
{
return (id > BUZZER_PATTERN_NONE && id < BUZZER_PATTERN_MAX);
}
static void buzzer_on(void)
static inline bool is_critical_pattern(buzzer_pattern_id_t id)
{
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
// Não mudar freq a cada beep; já foi configurada na init.
esp_err_t err1 = 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));
if (err1 != ESP_OK)
ESP_LOGW(TAG, "ledc_set_duty err=%s", esp_err_to_name(err1));
esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
if (err2 != ESP_OK)
ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2));
}
else
{
gpio_set_level(s_buzzer_cfg.gpio, 1);
}
return (id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED);
}
static void buzzer_off(void)
{
if (!s_inited)
return;
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
esp_err_t err1 = ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, 0);
if (err1 != ESP_OK)
ESP_LOGW(TAG, "ledc_set_duty(0) err=%s", esp_err_to_name(err1));
esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel);
if (err2 != ESP_OK)
ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2));
@@ -209,6 +242,11 @@ static bool in_quiet_hours(void)
return false;
time_t now = time(NULL);
// Se ainda não há tempo real (NTP não acertou), não aplicar quiet hours
if (now < 1700000000) // ~2023-11
return false;
struct tm lt;
if (localtime_r(&now, &lt) == NULL)
return false;
@@ -219,36 +257,103 @@ static bool in_quiet_hours(void)
if (start == end)
return false; // desativado
if (start < end)
{
return (minutes >= start && minutes < end);
}
else
{ // janela cruza meia-noite
return (minutes >= start || minutes < end);
}
return (minutes >= start || minutes < end); // cruza meia-noite
}
// ===================== Execução do padrão (apenas dentro da task) =====================
static void buzzer_execute(buzzer_pattern_id_t pattern_id)
{
if (!is_pattern_valid(pattern_id))
{
ESP_LOGW(TAG, "Invalid buzzer pattern id: %d", pattern_id);
return;
}
const buzzer_pattern_t *pattern = &buzzer_patterns[pattern_id];
if (!pattern->steps || pattern->length == 0)
return;
// Afinar aqui (para “premium”)
const uint32_t FADE_IN_MS = 10;
const uint32_t FADE_OUT_MS = 20;
// Não limpar STOP aqui — se alguém chamou buzzer_stop(), queremos respeitar já.
for (size_t i = 0; i < pattern->length; i++)
{
buzzer_on();
vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].on_ms));
buzzer_off();
if (pattern->steps[i].off_ms > 0)
if (buzzer_should_stop())
break;
uint32_t on_ms = pattern->steps[i].on_ms;
uint32_t off_ms = pattern->steps[i].off_ms;
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].off_ms));
// Sempre começar em 0 para evitar "click"
buzzer_set_duty_pct(0);
uint32_t fi = (on_ms > (FADE_IN_MS + FADE_OUT_MS)) ? FADE_IN_MS : (on_ms / 3);
uint32_t fo = (on_ms > (FADE_IN_MS + FADE_OUT_MS)) ? FADE_OUT_MS : (on_ms / 3);
uint32_t sustain = (on_ms > (fi + fo)) ? (on_ms - fi - fo) : 0;
// Fade in (async) + espera em passos para STOP rápido
if (fi > 0)
buzzer_fade_to_pct_async(s_buzzer_cfg.duty_percent, fi);
for (uint32_t t = 0; t < fi; t += 10)
{
if (buzzer_should_stop())
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
// Sustain
for (uint32_t t = 0; t < sustain; t += 10)
{
if (buzzer_should_stop())
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
if (buzzer_should_stop())
break;
// Fade out (async) + espera em passos
if (fo > 0)
buzzer_fade_to_pct_async(0, fo);
for (uint32_t t = 0; t < fo; t += 10)
{
if (buzzer_should_stop())
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
buzzer_set_duty_pct(0);
}
else
{
// ACTIVE (liga/desliga simples)
gpio_set_level(s_buzzer_cfg.gpio, 1);
for (uint32_t t = 0; t < on_ms; t += 10)
{
if (buzzer_should_stop())
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
gpio_set_level(s_buzzer_cfg.gpio, 0);
}
if (buzzer_should_stop())
break;
for (uint32_t t = 0; t < off_ms; t += 10)
{
if (buzzer_should_stop())
break;
vTaskDelay(pdMS_TO_TICKS(10));
}
}
buzzer_off();
}
// ===================== Task & Fila =====================
@@ -260,30 +365,31 @@ static void buzzer_task(void *arg)
{
if (xQueueReceive(s_buzzer_q, &id, portMAX_DELAY) == pdTRUE)
{
ESP_LOGD(TAG, "dequeue pattern %d", id); // na buzzer_task()
ESP_LOGD(TAG, "dequeue pattern %d", id);
if (!s_buzzer_ctl.enabled)
continue;
// Quiet hours: permitir apenas alertas críticos/negativos
if (in_quiet_hours())
if (in_quiet_hours() && !is_critical_pattern(id))
continue;
// Anti-spam global (NÃO aplicar a padrões críticos)
if (!is_critical_pattern(id))
{
if (!(id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED))
{
TickType_t now = xTaskGetTickCount();
if ((now - s_last_play_tick) < s_min_gap_ticks)
continue;
}
s_last_play_tick = now;
}
// Anti-spam global
TickType_t now = xTaskGetTickCount();
if ((now - s_last_play_tick) < s_min_gap_ticks)
{
continue;
}
s_last_play_tick = now;
// Se veio STOP pendente, limpamos antes de começar um novo padrão
buzzer_clear_stop();
buzzer_execute(id);
// Se alguém pediu stop durante execução, já paramos; limpamos o bit.
buzzer_clear_stop();
}
}
}
@@ -291,17 +397,32 @@ static void buzzer_task(void *arg)
// ===================== API pública =====================
void buzzer_play(buzzer_pattern_id_t id)
{
ESP_LOGD(TAG, "enqueue pattern %d", id);
ESP_LOGD(TAG, "enqueue pattern %d", id); // dentro de buzzer_play()
if (!is_pattern_valid(id) || s_buzzer_q == NULL)
if (!s_inited || !is_pattern_valid(id) || s_buzzer_q == NULL)
return;
(void)xQueueSend(s_buzzer_q, &id, 0);
// Críticos: preempt + vão para a frente
if (is_critical_pattern(id))
{
buzzer_stop();
if (xQueueSendToFront(s_buzzer_q, &id, pdMS_TO_TICKS(10)) != pdTRUE)
ESP_LOGW(TAG, "buzzer queue full, dropped critical pattern=%d", id);
return;
}
// Não-críticos: pequeno timeout + log se drop
if (xQueueSend(s_buzzer_q, &id, pdMS_TO_TICKS(10)) != pdTRUE)
ESP_LOGW(TAG, "buzzer queue full, dropped pattern=%d", id);
}
void buzzer_stop(void)
{
// Interrompe imediatamente: esvazia fila e desliga
if (!s_inited)
return;
// Interrompe imediatamente: sinaliza stop, esvazia fila e desliga
buzzer_set_stop();
if (s_buzzer_q)
xQueueReset(s_buzzer_q);
buzzer_off();
@@ -328,15 +449,22 @@ void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min)
s_buzzer_ctl.quiet_end_min = end_min;
}
// Opcional: mudar tom dinamicamente (apenas se o timer for exclusivo do buzzer)
esp_err_t buzzer_set_frequency(uint32_t hz)
// ledc_set_freq normalmente devolve a frequência efetiva (0 se falhar)
uint32_t buzzer_set_frequency(uint32_t hz)
{
if (!s_inited)
return 0;
if (s_buzzer_cfg.mode != BUZZER_MODE_PASSIVE)
return ESP_ERR_INVALID_STATE;
return 0;
if (hz < 50 || hz > 20000)
return ESP_ERR_INVALID_ARG;
return 0;
s_buzzer_cfg.freq_hz = hz;
return ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz);
uint32_t eff = ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz);
if (eff == 0)
ESP_LOGW(TAG, "ledc_set_freq failed (hz=%lu)", (unsigned long)hz);
return eff;
}
void buzzer_set_duty_percent(uint8_t pct)
@@ -382,7 +510,6 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
buzzer_evt.pattern = BUZZER_PATTERN_CHARGING;
break;
case EVSE_STATE_EVENT_FAULT:
// Preempta qualquer beep em andamento
buzzer_stop();
buzzer_evt.pattern = BUZZER_PATTERN_FAULT;
break;
@@ -406,12 +533,9 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int
{
case NETWORK_EVENT_AP_STARTED:
case NETWORK_EVENT_STA_CONNECTED:
// Usa padrão de AP_START para indicar rede disponível
evt.pattern = BUZZER_PATTERN_AP_START;
break;
default:
// Para já, ignorar outros eventos de rede
return;
}
@@ -446,6 +570,12 @@ static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
// ===================== Inicialização / Deinit =====================
void buzzer_init(void)
{
if (s_inited)
{
ESP_LOGW(TAG, "buzzer_init called twice");
return;
}
// Config HW
if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE)
{
@@ -466,6 +596,11 @@ void buzzer_init(void)
.duty = 0,
.hpoint = 0};
ESP_ERROR_CHECK(ledc_channel_config(&ccfg));
// Evita crash se já estiver instalado (init repetido em app maior)
esp_err_t fe = ledc_fade_func_install(0);
if (fe != ESP_OK && fe != ESP_ERR_INVALID_STATE)
ESP_ERROR_CHECK(fe);
}
else
{
@@ -479,14 +614,14 @@ void buzzer_init(void)
gpio_set_level(s_buzzer_cfg.gpio, 0);
}
buzzer_set_quiet_hours(false, 0, 0);
buzzer_set_enabled(true);
buzzer_off();
// STOP event group
s_buzzer_eg = xEventGroupCreate();
configASSERT(s_buzzer_eg);
// Fila + Task
s_buzzer_q = xQueueCreate(CONFIG_BUZZER_QUEUE_LEN, sizeof(buzzer_pattern_id_t));
configASSERT(s_buzzer_q != NULL);
BaseType_t ok = xTaskCreatePinnedToCore(
buzzer_task,
"buzzer_task",
@@ -497,6 +632,13 @@ void buzzer_init(void)
tskNO_AFFINITY);
configASSERT(ok == pdPASS);
// Runtime defaults
buzzer_set_quiet_hours(false, 0, 0);
buzzer_set_enabled(true);
s_inited = true;
buzzer_off();
// Handlers
ESP_ERROR_CHECK(esp_event_handler_register(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL));
@@ -514,24 +656,39 @@ void buzzer_init(void)
void buzzer_deinit(void)
{
if (!s_inited)
return;
(void)esp_event_handler_unregister(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler);
(void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler);
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler);
(void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler);
(void)esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler);
// Para já
buzzer_stop();
// Mata task ANTES de apagar a queue (evita uso-after-free raro)
if (s_buzzer_task)
{
vTaskDelete(s_buzzer_task);
s_buzzer_task = NULL;
}
if (s_buzzer_q)
{
vQueueDelete(s_buzzer_q);
s_buzzer_q = NULL;
}
if (s_buzzer_task)
if (s_buzzer_eg)
{
vTaskDelete(s_buzzer_task);
s_buzzer_task = NULL;
vEventGroupDelete(s_buzzer_eg);
s_buzzer_eg = NULL;
}
s_inited = false;
buzzer_off();
ESP_LOGI(TAG, "Buzzer deinitialized");
}
}

View File

@@ -105,6 +105,7 @@ static void evse_process(void)
if (evse_state_get_authorized())
{
ESP_LOGW(TAG, "Charging limit reached → revoking authorization");
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
}
}

View File

@@ -30,6 +30,7 @@ static SemaphoreHandle_t evse_mutex;
// ✅ Proteção para flags partilhadas (event handlers vs task)
static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED;
static bool auth_enabled = false;
static char s_pending_ocpp_tag[AUTH_TAG_MAX_LEN];
// Estado de pausa controlado pelo Load Balancer
static bool lb_paused = false;
@@ -41,6 +42,8 @@ static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED;
#define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo
static uint16_t s_sched_current_a = 0;
static void lb_clear_pause_state(void)
{
portENTER_CRITICAL(&s_mgr_mux);
@@ -61,15 +64,15 @@ bool evse_sched_is_allowed(void)
static void evse_manager_handle_auth_on_tick(void)
{
bool sched_allowed = evse_sched_is_allowed();
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
uint32_t err_bits = evse_get_error(); // inclui holdoff interno
bool has_error = (err_bits != 0);
bool local_auth_enabled;
bool local_lb_paused;
portENTER_CRITICAL(&s_mgr_mux);
local_auth_enabled = auth_enabled;
local_lb_paused = lb_paused;
local_lb_paused = lb_paused;
portEXIT_CRITICAL(&s_mgr_mux);
if (local_auth_enabled)
@@ -77,6 +80,7 @@ static void evse_manager_handle_auth_on_tick(void)
if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A)
{
ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization.");
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
lb_clear_pause_state();
}
@@ -86,6 +90,7 @@ static void evse_manager_handle_auth_on_tick(void)
ESP_LOGI(TAG,
"[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.",
err_bits);
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
lb_clear_pause_state();
}
@@ -93,12 +98,13 @@ static void evse_manager_handle_auth_on_tick(void)
if (!sched_allowed && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization.");
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
}
}
else
{
bool limit_hit = evse_is_limit_reached();
bool limit_hit = evse_is_limit_reached();
bool can_operate = evse_config_is_available() && evse_config_is_enabled();
if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) &&
@@ -114,6 +120,7 @@ static void evse_manager_handle_auth_on_tick(void)
!has_error && !limit_hit &&
!evse_state_get_authorized())
{
evse_session_clear_authorized_tag();
evse_state_set_authorized(true);
ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits).");
lb_clear_pause_state();
@@ -134,7 +141,8 @@ static void evse_manager_task(void *arg)
static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != AUTH_EVENTS || !data) return;
if (base != AUTH_EVENTS || !data)
return;
switch (id)
{
@@ -142,11 +150,30 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
{
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data;
ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
if (evt->authorized)
{
evse_session_set_authorized_tag(evt->tag);
}
else
{
evse_session_clear_authorized_tag();
}
evse_state_set_authorized(evt->authorized);
lb_clear_pause_state();
break;
}
case AUTH_EVENT_TAG_VERIFY:
{
const auth_tag_verify_event_t *evt = (const auth_tag_verify_event_t *)data;
ESP_LOGI(TAG, "Tag %s -> pending remote/OCPP verification", evt->tag);
portENTER_CRITICAL(&s_mgr_mux);
strncpy(s_pending_ocpp_tag, evt->tag, AUTH_TAG_MAX_LEN - 1);
s_pending_ocpp_tag[AUTH_TAG_MAX_LEN - 1] = '\0';
portEXIT_CRITICAL(&s_mgr_mux);
break;
}
case AUTH_EVENT_MODE_CHANGED:
case AUTH_EVENT_INIT:
{
@@ -155,8 +182,10 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da
portENTER_CRITICAL(&s_mgr_mux);
auth_enabled = (evt->mode != AUTH_MODE_OPEN);
s_pending_ocpp_tag[0] = '\0';
portEXIT_CRITICAL(&s_mgr_mux);
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
@@ -170,7 +199,8 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
(void)handler_arg;
(void)event_base;
if (!event_data) return;
if (!event_data)
return;
if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED)
{
@@ -211,7 +241,7 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
portENTER_CRITICAL(&s_mgr_mux);
was_paused = lb_paused;
prev_auth = lb_prev_authorized;
prev_auth = lb_prev_authorized;
portEXIT_CRITICAL(&s_mgr_mux);
if (was_paused)
@@ -263,27 +293,55 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base
static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != OCPP_EVENTS) return;
if (base != OCPP_EVENTS)
return;
switch (id)
{
case OCPP_EVENT_AUTHORIZED:
ESP_LOGI(TAG, "[OCPP] Authorized");
{
char tag[AUTH_TAG_MAX_LEN];
portENTER_CRITICAL(&s_mgr_mux);
strncpy(tag, s_pending_ocpp_tag, AUTH_TAG_MAX_LEN - 1);
tag[AUTH_TAG_MAX_LEN - 1] = '\0';
s_pending_ocpp_tag[0] = '\0';
portEXIT_CRITICAL(&s_mgr_mux);
if (tag[0] != '\0')
{
evse_session_set_authorized_tag(tag);
ESP_LOGI(TAG, "[OCPP] Authorized tag=%s", tag);
}
else
{
ESP_LOGI(TAG, "[OCPP] Authorized");
}
evse_state_set_authorized(true);
lb_clear_pause_state();
break;
}
case OCPP_EVENT_AUTH_REJECTED:
case OCPP_EVENT_AUTH_TIMEOUT:
case OCPP_EVENT_REMOTE_STOP:
case OCPP_EVENT_STOP_TX:
ESP_LOGW(TAG, "[OCPP] Authorization/Stop");
portENTER_CRITICAL(&s_mgr_mux);
s_pending_ocpp_tag[0] = '\0';
portEXIT_CRITICAL(&s_mgr_mux);
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
lb_clear_pause_state();
break;
case OCPP_EVENT_REMOTE_START:
ESP_LOGI(TAG, "[OCPP] RemoteStart");
portENTER_CRITICAL(&s_mgr_mux);
s_pending_ocpp_tag[0] = '\0';
portEXIT_CRITICAL(&s_mgr_mux);
evse_session_clear_authorized_tag();
evse_state_set_authorized(true);
lb_clear_pause_state();
break;
@@ -317,21 +375,31 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da
static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
if (base != SCHED_EVENTS || data == NULL) return;
if (base != SCHED_EVENTS || data == NULL)
return;
const sched_event_state_t *ev = (const sched_event_state_t *)data;
portENTER_CRITICAL(&s_sched_mux);
s_sched_allowed = ev->allowed_now;
s_sched_current_a = ev->current_limit_a;
portEXIT_CRITICAL(&s_sched_mux);
ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now);
ESP_LOGI(TAG, "[SCHED] allowed_now=%d current=%uA",
(int)ev->allowed_now, (unsigned)ev->current_limit_a);
if (!ev->allowed_now && evse_state_get_authorized())
{
ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)");
evse_session_clear_authorized_tag();
evse_state_set_authorized(false);
}
// ✅ aplica corrente quando permitido
if (ev->allowed_now && ev->current_limit_a > 0)
{
evse_set_runtime_charging_current(ev->current_limit_a);
}
}
void evse_manager_init(void)

View File

@@ -1,4 +1,5 @@
#include <inttypes.h>
#include <string.h>
#include "evse_session.h"
#include "evse_meter.h"
#include "freertos/FreeRTOS.h"
@@ -25,6 +26,8 @@ static uint64_t watt_microseconds = 0;
static evse_session_t last_session;
static bool last_session_valid = false;
static uint32_t session_counter = 0;
static char authorized_tag[AUTH_TAG_MAX_LEN];
static char current_session_tag[AUTH_TAG_MAX_LEN];
static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED;
@@ -52,9 +55,31 @@ void evse_session_init(void)
watt_microseconds = 0;
last_session_valid = false;
session_counter = 0;
authorized_tag[0] = '\0';
current_session_tag[0] = '\0';
portEXIT_CRITICAL(&session_mux);
}
void evse_session_set_authorized_tag(const char *tag)
{
portENTER_CRITICAL(&session_mux);
if (tag && tag[0] != '\0')
{
strncpy(authorized_tag, tag, AUTH_TAG_MAX_LEN - 1);
authorized_tag[AUTH_TAG_MAX_LEN - 1] = '\0';
}
else
{
authorized_tag[0] = '\0';
}
portEXIT_CRITICAL(&session_mux);
}
void evse_session_clear_authorized_tag(void)
{
evse_session_set_authorized_tag(NULL);
}
void evse_session_start(void)
{
TickType_t tick = xTaskGetTickCount();
@@ -67,12 +92,17 @@ void evse_session_start(void)
watt_microseconds = 0;
session_counter++;
uint32_t id = session_counter;
strncpy(current_session_tag, authorized_tag, AUTH_TAG_MAX_LEN - 1);
current_session_tag[AUTH_TAG_MAX_LEN - 1] = '\0';
char tag[AUTH_TAG_MAX_LEN];
strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1);
tag[AUTH_TAG_MAX_LEN - 1] = '\0';
portEXIT_CRITICAL(&session_mux);
evse_set_limit_reached(false);
ESP_LOGI(TAG, "Session started (id=%" PRIu32 ") tick=%u us=%" PRId64,
id, (unsigned)tick, now_us);
ESP_LOGI(TAG, "Session started (id=%" PRIu32 ", tag=%s) tick=%u us=%" PRId64,
id, tag[0] ? tag : "-", (unsigned)tick, now_us);
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_STARTED,
@@ -82,6 +112,8 @@ void evse_session_start(void)
.avg_power_w = 0,
.is_current = true,
};
strncpy(evt.tag, tag, AUTH_TAG_MAX_LEN - 1);
evt.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
post_session_event(&evt);
}
@@ -91,6 +123,7 @@ void evse_session_end(void)
int64_t start_us;
uint64_t w_us;
uint32_t id;
char tag[AUTH_TAG_MAX_LEN];
int64_t end_us = esp_timer_get_time();
@@ -106,11 +139,14 @@ void evse_session_end(void)
start_us = session_start_us;
w_us = watt_microseconds;
id = session_counter;
strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1);
tag[AUTH_TAG_MAX_LEN - 1] = '\0';
session_start_tick = 0;
session_start_us = 0;
last_tick_us = 0;
watt_microseconds = 0;
current_session_tag[0] = '\0';
portEXIT_CRITICAL(&session_mux);
uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0;
@@ -120,6 +156,9 @@ void evse_session_end(void)
portENTER_CRITICAL(&session_mux);
last_session.start_tick = start_tick;
last_session.session_id = id;
strncpy(last_session.tag, tag, AUTH_TAG_MAX_LEN - 1);
last_session.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
last_session.duration_s = duration_s;
last_session.energy_wh = energy_wh;
last_session.avg_power_w = avg_power;
@@ -129,8 +168,8 @@ void evse_session_end(void)
ESP_LOGI(TAG,
"Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32
" Wh, avg_power=%" PRIu32 " W",
id, duration_s, energy_wh, avg_power);
" Wh, avg_power=%" PRIu32 " W, tag=%s",
id, duration_s, energy_wh, avg_power, tag[0] ? tag : "-");
evse_session_event_data_t evt = {
.type = EVSE_SESSION_EVENT_FINISHED,
@@ -140,6 +179,8 @@ void evse_session_end(void)
.avg_power_w = avg_power,
.is_current = false,
};
strncpy(evt.tag, tag, AUTH_TAG_MAX_LEN - 1);
evt.tag[AUTH_TAG_MAX_LEN - 1] = '\0';
post_session_event(&evt);
}
@@ -185,6 +226,8 @@ bool evse_session_get(evse_session_t *out)
bool has_current;
evse_session_t last_copy;
bool last_valid;
uint32_t id;
char tag[AUTH_TAG_MAX_LEN];
int64_t now_us = esp_timer_get_time();
@@ -193,6 +236,9 @@ bool evse_session_get(evse_session_t *out)
start_us = session_start_us;
w_us = watt_microseconds;
has_current = (session_start_tick != 0);
id = session_counter;
strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1);
tag[AUTH_TAG_MAX_LEN - 1] = '\0';
last_copy = last_session;
last_valid = last_session_valid;
portEXIT_CRITICAL(&session_mux);
@@ -205,6 +251,9 @@ bool evse_session_get(evse_session_t *out)
uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0;
out->start_tick = start_tick;
out->session_id = id;
strncpy(out->tag, tag, AUTH_TAG_MAX_LEN - 1);
out->tag[AUTH_TAG_MAX_LEN - 1] = '\0';
out->duration_s = duration_s;
out->energy_wh = energy_wh;
out->avg_power_w = avg_power;

View File

@@ -6,6 +6,7 @@
#include <stdbool.h>
#include <stdint.h>
#include "esp_event.h"
#include "auth_types.h"
ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
@@ -45,6 +46,7 @@ typedef struct {
evse_session_event_type_t type; ///< STARTED / FINISHED
uint32_t session_id;
char tag[AUTH_TAG_MAX_LEN];
uint32_t duration_s;
uint32_t energy_wh;
uint32_t avg_power_w;

View File

@@ -4,19 +4,32 @@
#include <stdint.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "auth_types.h"
/**
* @brief Charging session statistics
*/
typedef struct {
TickType_t start_tick; ///< tick when session began (debug/trace)
uint32_t duration_s; ///< total duration in seconds (tempo real)
uint32_t energy_wh; ///< total energy consumed in Wh (tempo real)
uint32_t avg_power_w; ///< average power in W
bool is_current; ///< true if session still in progress
TickType_t start_tick; ///< tick when session began (debug/trace)
uint32_t session_id; ///< monotonically increasing session identifier
char tag[AUTH_TAG_MAX_LEN]; ///< RFID/auth tag associated with this session, empty if none
uint32_t duration_s; ///< total duration in seconds (tempo real)
uint32_t energy_wh; ///< total energy consumed in Wh (tempo real)
uint32_t avg_power_w; ///< average power in W
bool is_current; ///< true if session still in progress
} evse_session_t;
void evse_session_init(void);
/**
* @brief Stores the tag that authorized the next charging session.
*
* The tag is copied into the session when evse_session_start() is called.
* Passing NULL or an empty string clears the pending tag.
*/
void evse_session_set_authorized_tag(const char *tag);
void evse_session_clear_authorized_tag(void);
void evse_session_start(void);
void evse_session_end(void);

View File

@@ -234,7 +234,7 @@ static void led_apply_state_mode(evse_state_event_t state)
{
case EVSE_STATE_EVENT_IDLE:
// IDLE → verde fixo (claro e visível)
led_apply_pattern(LED_ID_GREEN, LED_PATTERN_ON);
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
break;
case EVSE_STATE_EVENT_WAITING:
@@ -249,7 +249,7 @@ static void led_apply_state_mode(evse_state_event_t state)
case EVSE_STATE_EVENT_FAULT:
// FAULT → vermelho a piscar rápido
led_apply_pattern(LED_ID_RED, LED_PATTERN_BLINK_FAST);
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
break;
default:

View File

@@ -15,10 +15,10 @@
#define MB_PORT_NUM 1
#define MB_DEV_SPEED 9600
// Ajuste os pinos conforme seu hardware
#define MB_UART_TXD 21
#define MB_UART_RXD 22
#define MB_UART_RTS UART_PIN_NO_CHANGE // sem DE/RE
// Ajuste os pinos conforme seu hardware (evite GPIO2 para RTS/DE/RE se possível)
#define MB_UART_TXD 17
#define MB_UART_RXD 16
#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485
// ===== Timings =====
#define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS)
@@ -292,7 +292,6 @@ static void serial_mdb_ea777_task(void *param)
}
}
// ============ Init / Start / Stop ============
esp_err_t meter_ea777_init(void)
{
if (is_initialized)
@@ -301,14 +300,12 @@ esp_err_t meter_ea777_init(void)
return ESP_ERR_INVALID_STATE;
}
// limpa UART se já houver driver
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
ESP_LOGI(TAG, "UART driver deleted");
}
// destruir master anterior (ignora erro se não estiver init)
(void)mbc_master_destroy();
mb_communication_info_t comm = {
@@ -320,12 +317,14 @@ esp_err_t meter_ea777_init(void)
void *handler = NULL;
ESP_ERROR_CHECK(mbc_master_init(MB_PORT_SERIAL_MASTER, &handler));
ESP_ERROR_CHECK(mbc_master_setup(&comm));
// >>> RS-485 half duplex
ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM,
MB_UART_TXD, MB_UART_RXD,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
MB_UART_RTS, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(mbc_master_start());
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_UART));
// ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_UART));
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));
vTaskDelay(pdMS_TO_TICKS(50));
ESP_ERROR_CHECK(mbc_master_set_descriptor(device_parameters_ea777,

View File

@@ -179,7 +179,7 @@ static void handle_zigbee_frame(const uint8_t *buf, size_t len)
}
// Verifica se todas as 3 fases foram atualizadas
if (phase_updated[PHASE_L1] && phase_updated[PHASE_L2] && phase_updated[PHASE_L3])
if (phase_updated[PHASE_L1] /*&& phase_updated[PHASE_L2] && phase_updated[PHASE_L3]*/)
{
meter_zigbee_post_event();
memset(phase_updated, 0, sizeof(phase_updated));

0
components/mongoose/.gitmodules vendored Executable file → Normal file
View File

0
components/mongoose/CMakeLists.txt Executable file → Normal file
View File

0
components/mongoose/idf_component.yml Executable file → Normal file
View File

0
components/mongoose/mongoose/.clang-format Executable file → Normal file
View File

0
components/mongoose/mongoose/LICENSE Executable file → Normal file
View File

0
components/mongoose/mongoose/README.md Executable file → Normal file
View File

0
components/mongoose/mongoose/docs/README.md Executable file → Normal file
View File

View File

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

0
components/mongoose/mongoose/docs/images/mg_iobuf.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

0
components/mongoose/mongoose/docs/images/packed.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

View File

View File

View File

View File

View File

View File

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

0
components/mongoose/mongoose/examples/live-log/main.c Executable file → Normal file
View File

View File

0
components/mongoose/mongoose/examples/mip-pcap/main.c Executable file → Normal file
View File

View File

View File

View File

View File

Some files were not shown because too many files have changed in this diff Show More