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

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