// buzzer.c #include "buzzer_events.h" #include "evse_events.h" #include "auth_events.h" #include "network_events.h" #include "esp_event.h" #include "esp_log.h" #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "driver/gpio.h" #include "driver/ledc.h" #include #include // ===================== Configuração padrão (pode migrar para Kconfig) ===================== #ifndef CONFIG_BUZZER_GPIO #define CONFIG_BUZZER_GPIO GPIO_NUM_27 #endif #ifndef CONFIG_BUZZER_MODE_PASSIVE // 1 = PASSIVE (PWM), 0 = ACTIVE (on/off) #define CONFIG_BUZZER_MODE_PASSIVE 1 #endif #ifndef CONFIG_BUZZER_FREQ_HZ #define CONFIG_BUZZER_FREQ_HZ 3500 #endif #ifndef CONFIG_BUZZER_DUTY_PCT #define CONFIG_BUZZER_DUTY_PCT 70 #endif #ifndef CONFIG_BUZZER_QUEUE_LEN #define CONFIG_BUZZER_QUEUE_LEN 8 #endif #ifndef CONFIG_BUZZER_TASK_STACK #define CONFIG_BUZZER_TASK_STACK 2048 #endif #ifndef CONFIG_BUZZER_TASK_PRIO #define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1) #endif #ifndef CONFIG_BUZZER_MIN_GAP_MS // anti-spam (gap mínimo entre toques) #define CONFIG_BUZZER_MIN_GAP_MS 70 #endif #ifndef CONFIG_BUZZER_ENABLE_DEFAULT #define CONFIG_BUZZER_ENABLE_DEFAULT 1 #endif #ifndef CONFIG_BUZZER_QUIET_START_MIN // quiet hours start (minutos desde 00:00) #define CONFIG_BUZZER_QUIET_START_MIN (22 * 60) #endif #ifndef CONFIG_BUZZER_QUIET_END_MIN // quiet hours end (minutos desde 00:00) #define CONFIG_BUZZER_QUIET_END_MIN (7 * 60) #endif // ===================== Tipos e estado ===================== static const char *TAG = "Buzzer"; typedef enum { BUZZER_MODE_ACTIVE = 0, BUZZER_MODE_PASSIVE } buzzer_mode_t; typedef struct { uint16_t on_ms; uint16_t off_ms; } buzzer_step_t; typedef struct { const buzzer_step_t *steps; size_t length; } buzzer_pattern_t; typedef struct { buzzer_mode_t mode; gpio_num_t gpio; // PASSIVE (PWM) uint32_t freq_hz; uint8_t duty_percent; // 0–100 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; // Controlo runtime static struct { bool enabled; uint16_t quiet_start_min; // [0..1440) uint16_t quiet_end_min; // [0..1440) bool quiet_enabled; } s_buzzer_ctl = { .enabled = CONFIG_BUZZER_ENABLE_DEFAULT, .quiet_start_min = CONFIG_BUZZER_QUIET_START_MIN, .quiet_end_min = CONFIG_BUZZER_QUIET_END_MIN, .quiet_enabled = false, }; // Hardware cfg static buzzer_config_t s_buzzer_cfg = { .mode = CONFIG_BUZZER_MODE_PASSIVE ? BUZZER_MODE_PASSIVE : BUZZER_MODE_ACTIVE, .gpio = CONFIG_BUZZER_GPIO, .freq_hz = CONFIG_BUZZER_FREQ_HZ, .duty_percent = CONFIG_BUZZER_DUTY_PCT, .ledc_speed_mode = LEDC_LOW_SPEED_MODE, .ledc_timer = LEDC_TIMER_1, .ledc_channel = LEDC_CHANNEL_1, .duty_resolution = LEDC_TIMER_10_BIT}; // Fila e task static QueueHandle_t s_buzzer_q = NULL; static TaskHandle_t s_buzzer_task = NULL; // Anti-spam básico static TickType_t s_last_play_tick = 0; static const TickType_t s_min_gap_ticks = pdMS_TO_TICKS(CONFIG_BUZZER_MIN_GAP_MS); // ===================== Padrões (afinados) ===================== // NOTA: os enums BUZZER_PATTERN_* vêm de buzzer_events.h // Curtos e claros static const buzzer_step_t pattern_plugged[] = {{180, 120}}; // ~300 ms static const buzzer_step_t pattern_card_read[] = {{80, 80}, {80, 0}}; // 160 ms audível // Um pouco mais longos, ritmo marcante static const buzzer_step_t pattern_unplugged[] = {{140, 140}, {140, 140}, {140, 0}}; // ~700 ms static const buzzer_step_t pattern_card_add[] = {{100, 100}, {100, 100}, {120, 0}}; // ~520 ms static const buzzer_step_t pattern_ap_start[] = {{250, 150}, {250, 0}}; // ~650 ms // Charging mais curta (evento frequente) static const buzzer_step_t pattern_charging[] = {{80, 90}, {90, 80}, {100, 0}}; // ~440 ms // Denied mais “duro” static const buzzer_step_t pattern_card_denied[] = {{250, 120}, {300, 0}}; // ~670 ms // Fault crítico (duas trincas, ~1.7 s) static const buzzer_step_t pattern_fault[] = { {350, 120}, {350, 120}, {450, 250}, // bloco 1 {350, 120}, {350, 120}, {450, 0} // bloco 2 }; static const buzzer_pattern_t buzzer_patterns[] = { [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)}, [BUZZER_PATTERN_AP_START] = {pattern_ap_start, sizeof(pattern_ap_start) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_CARD_READ] = {pattern_card_read, sizeof(pattern_card_read) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_CARD_ADD] = {pattern_card_add, sizeof(pattern_card_add) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_CARD_DENIED] = {pattern_card_denied, sizeof(pattern_card_denied) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_FAULT] = {pattern_fault, sizeof(pattern_fault) / sizeof(buzzer_step_t)}, }; // ===================== Helpers HW ===================== static inline uint32_t duty_from_percent(uint8_t pct, ledc_timer_bit_t res) { if (pct == 0) return 0; if (pct > 100) pct = 100; uint32_t max = (1U << res) - 1U; return (uint32_t)((pct * max) / 100U); } 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) { 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); } } static void buzzer_off(void) { 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)); } else { gpio_set_level(s_buzzer_cfg.gpio, 0); } } static bool in_quiet_hours(void) { if (!s_buzzer_ctl.quiet_enabled) return false; time_t now = time(NULL); struct tm lt; if (localtime_r(&now, <) == NULL) return false; uint16_t minutes = (uint16_t)(lt.tm_hour * 60 + lt.tm_min); uint16_t start = s_buzzer_ctl.quiet_start_min; uint16_t end = s_buzzer_ctl.quiet_end_min; if (start == end) return false; // desativado if (start < end) { return (minutes >= start && minutes < end); } else { // janela cruza meia-noite return (minutes >= start || minutes < end); } } // ===================== 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]; 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) { vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].off_ms)); } } } // ===================== Task & Fila ===================== static void buzzer_task(void *arg) { buzzer_pattern_id_t id; for (;;) { if (xQueueReceive(s_buzzer_q, &id, portMAX_DELAY) == pdTRUE) { ESP_LOGD(TAG, "dequeue pattern %d", id); // na buzzer_task() if (!s_buzzer_ctl.enabled) continue; // Quiet hours: permitir apenas alertas críticos/negativos if (in_quiet_hours()) { if (!(id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED)) { continue; } } // Anti-spam global TickType_t now = xTaskGetTickCount(); if ((now - s_last_play_tick) < s_min_gap_ticks) { continue; } s_last_play_tick = now; buzzer_execute(id); } } } // ===================== API pública ===================== void buzzer_play(buzzer_pattern_id_t id) { ESP_LOGD(TAG, "enqueue pattern %d", id); // dentro de buzzer_play() if (!is_pattern_valid(id) || s_buzzer_q == NULL) return; (void)xQueueSend(s_buzzer_q, &id, 0); } void buzzer_stop(void) { // Interrompe imediatamente: esvazia fila e desliga if (s_buzzer_q) xQueueReset(s_buzzer_q); buzzer_off(); } void buzzer_set_enabled(bool enabled) { s_buzzer_ctl.enabled = enabled; if (!enabled) buzzer_off(); } bool buzzer_get_enabled(void) { return s_buzzer_ctl.enabled; } void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min) { s_buzzer_ctl.quiet_enabled = enabled; if (start_min < 1440) s_buzzer_ctl.quiet_start_min = start_min; if (end_min < 1440) 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) { if (s_buzzer_cfg.mode != BUZZER_MODE_PASSIVE) return ESP_ERR_INVALID_STATE; if (hz < 50 || hz > 20000) return ESP_ERR_INVALID_ARG; s_buzzer_cfg.freq_hz = hz; return ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz); } void buzzer_set_duty_percent(uint8_t pct) { if (pct > 100) pct = 100; s_buzzer_cfg.duty_percent = pct; } // ===================== 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; buzzer_event_data_t *evt = (buzzer_event_data_t *)data; if (!is_pattern_valid(evt->pattern)) { ESP_LOGW(TAG, "Invalid buzzer pattern received: %d", evt->pattern); return; } buzzer_play(evt->pattern); } static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) { if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) return; const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data; ESP_LOGD(TAG, "EVSE event: state=%d", evt->state); buzzer_event_data_t buzzer_evt = {0}; switch (evt->state) { case EVSE_STATE_EVENT_IDLE: buzzer_evt.pattern = BUZZER_PATTERN_UNPLUGGED; break; case EVSE_STATE_EVENT_WAITING: buzzer_evt.pattern = BUZZER_PATTERN_PLUGGED; break; case EVSE_STATE_EVENT_CHARGING: 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; default: return; } esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY); } static void network_event_handler(void *handler_args, esp_event_base_t base, int32_t id, void *event_data) { if (base != NETWORK_EVENTS) return; if (id == NETWORK_EVENT_AP_STARTED) { buzzer_event_data_t evt = {.pattern = BUZZER_PATTERN_AP_START}; esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &evt, sizeof(evt), portMAX_DELAY); } } 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; buzzer_event_data_t buzzer_evt = {0}; if (id == AUTH_EVENT_TAG_PROCESSED) { const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)event_data; ESP_LOGD(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 { return; } esp_event_post(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, &buzzer_evt, sizeof(buzzer_evt), portMAX_DELAY); } // ===================== Inicialização / Deinit ===================== void buzzer_init(void) { // Config HW if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) { 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)); 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}; ESP_ERROR_CHECK(gpio_config(&io)); gpio_set_level(s_buzzer_cfg.gpio, 0); } buzzer_set_quiet_hours(false, 0, 0); buzzer_set_enabled(true); buzzer_off(); // 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", CONFIG_BUZZER_TASK_STACK, NULL, CONFIG_BUZZER_TASK_PRIO, &s_buzzer_task, tskNO_AFFINITY); configASSERT(ok == pdPASS); // 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)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, 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_LOGI(TAG, "Buzzer initialized on GPIO %d (%s), freq=%lu Hz, duty=%u%%, enabled=%d", s_buzzer_cfg.gpio, s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE ? "passive/PWM" : "active/ON-OFF", (unsigned long)s_buzzer_cfg.freq_hz, (unsigned)s_buzzer_cfg.duty_percent, (int)s_buzzer_ctl.enabled); } void buzzer_deinit(void) { (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, NETWORK_EVENT_AP_STARTED, network_event_handler); if (s_buzzer_q) { vQueueDelete(s_buzzer_q); s_buzzer_q = NULL; } if (s_buzzer_task) { vTaskDelete(s_buzzer_task); s_buzzer_task = NULL; } buzzer_off(); ESP_LOGI(TAG, "Buzzer deinitialized"); }