#include "alert.h" #include "codec_bsp.h" #include "i2c_bsp.h" #include #include #include #include #include #include static const char *TAG = "alert"; /* Audio params: 24kHz, 16-bit, stereo */ #define SAMPLE_RATE 24000 #define CHANNELS 2 #define BITS 16 #define TONE_FREQ 1000 #define TONE_DURATION_MS 200 /* Tone buffer: 200ms at 24kHz * 2ch * 2bytes = 19200 bytes */ #define TONE_SAMPLES (SAMPLE_RATE * TONE_DURATION_MS / 1000) #define TONE_BUF_SIZE (TONE_SAMPLES * CHANNELS * (BITS / 8)) static int16_t *s_tone_buf = NULL; static bool s_muted = false; static CodecPort *s_codec = NULL; static SemaphoreHandle_t s_alert_mutex = NULL; /* Per-type cooldown tracking (microseconds) */ static int64_t s_last_trigger[ALERT_TYPE_COUNT] = {}; /* Cooldown periods in microseconds */ static const int64_t s_cooldown_us[ALERT_TYPE_COUNT] = { 60 * 1000000LL, /* SERVICE_DOWN: 60s */ 60 * 1000000LL, /* HIGH_TEMP: 60s */ 30 * 1000000LL, /* WS_DISCONNECT: 30s */ 0, /* CONNECT_OK: no cooldown */ }; /* Beep patterns: number of beeps per alert type */ static const int s_beep_count[ALERT_TYPE_COUNT] = { 0, /* SERVICE_DOWN: triple beep */ /* DISABLED FOR TESTING */ 2, /* HIGH_TEMP: double beep */ 1, /* WS_DISCONNECT: single beep */ 1, /* CONNECT_OK: single short beep */ }; static void generate_tone_buffer(void) { /* Allocate in PSRAM */ s_tone_buf = (int16_t *)heap_caps_malloc(TONE_BUF_SIZE, MALLOC_CAP_SPIRAM); if (!s_tone_buf) { ESP_LOGE(TAG, "Failed to allocate tone buffer"); return; } /* Generate 1kHz square wave */ int half_period = SAMPLE_RATE / (TONE_FREQ * 2); int16_t amplitude = 16000; /* ~50% of max to avoid clipping */ for (int i = 0; i < TONE_SAMPLES; i++) { int16_t val = ((i / half_period) % 2 == 0) ? amplitude : -amplitude; /* Stereo: write same value to L and R */ s_tone_buf[i * 2] = val; s_tone_buf[i * 2 + 1] = val; } } void alert_init(void) { s_alert_mutex = xSemaphoreCreateMutex(); generate_tone_buffer(); memset(s_last_trigger, 0, sizeof(s_last_trigger)); ESP_LOGI(TAG, "Alert system initialized"); } void alert_set_codec(void *codec) { s_codec = (CodecPort *)codec; } void alert_trigger(alert_type_t type) { if (type >= ALERT_TYPE_COUNT) return; if (s_muted) return; if (!s_tone_buf || !s_codec) return; if (xSemaphoreTake(s_alert_mutex, pdMS_TO_TICKS(50)) != pdTRUE) return; /* Check cooldown */ int64_t now = esp_timer_get_time(); if (s_cooldown_us[type] > 0 && (now - s_last_trigger[type]) < s_cooldown_us[type]) { xSemaphoreGive(s_alert_mutex); return; } s_last_trigger[type] = now; ESP_LOGI(TAG, "Alert triggered: type=%d", type); /* Open codec for playback */ s_codec->CodecPort_SetInfo("es8311", 1, SAMPLE_RATE, CHANNELS, BITS); s_codec->CodecPort_SetSpeakerVol(70); /* Play beep pattern */ int beeps = s_beep_count[type]; for (int b = 0; b < beeps; b++) { /* Play tone in chunks */ uint8_t *ptr = (uint8_t *)s_tone_buf; int remaining = TONE_BUF_SIZE; while (remaining > 0) { int chunk = (remaining > 512) ? 512 : remaining; s_codec->CodecPort_PlayWrite(ptr, chunk); ptr += chunk; remaining -= chunk; } /* Gap between beeps */ if (b < beeps - 1) { vTaskDelay(pdMS_TO_TICKS(150)); } } xSemaphoreGive(s_alert_mutex); } void alert_mute(bool muted) { s_muted = muted; ESP_LOGI(TAG, "Alerts %s", muted ? "MUTED" : "UNMUTED"); } bool alert_is_muted(void) { return s_muted; }