From f07cd6d0c42bad9cd3d809d04b0698437c36d08f Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Mon, 20 Jul 2026 12:19:25 +1200 Subject: [PATCH] Edit esp-bedside-panel.yaml --- esphome/esp-bedside-panel.yaml | 335 +++++++++++++++++++++------------ 1 file changed, 212 insertions(+), 123 deletions(-) diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index 4195ea3..b6d7709 100644 --- a/esphome/esp-bedside-panel.yaml +++ b/esphome/esp-bedside-panel.yaml @@ -6,6 +6,7 @@ # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedside-panel.yaml #:########################################################################################:# # VERSIONS: +# V1.41 2026-07-20 Fixed raw alarm audio startup and buffering so tones play instead of amplifier clicks # V1.40 2026-07-20 Added ES8311 speaker output, persistent alarm volume and local speaker test controls # V1.39 2026-07-19 Removed the Alarm Clock editor title format-truncation warning # V1.38 2026-07-19 Added per-alarm Snooze controls and a reusable 24-hour time editor @@ -165,7 +166,7 @@ substitutions: # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v1.40" + project_version: "v1.41" # Passwords & Secrets api_key: !secret esp-api_key @@ -238,7 +239,7 @@ substitutions: alarm_i2s_mclk_pin: "GPIO13" alarm_i2s_dout_pin: "GPIO9" alarm_speaker_enable_pin: "GPIO11" - alarm_audio_sample_rate: "16000" + alarm_audio_sample_rate: "48000" # Security Camera Settings # Change this only if homeassistant.local is not reachable from the panel. @@ -572,7 +573,10 @@ speaker: sample_rate: "${alarm_audio_sample_rate}" bits_per_sample: 16bit channel: mono - buffer_duration: 100ms + i2s_comm_fmt: stand_i2s + mclk_multiple: 256 + buffer_duration: 500ms + timeout: never #:########################################################################################:# # OUTPUT: @@ -2361,19 +2365,33 @@ script: ? std::string("Alarm\nON") : std::string("Alarm"); - - id: alarm_audio_beep + # Start the I2S task before submitting the real sound. A first speaker.play + # call made while the speaker is stopped starts the task, but may not accept + # the supplied audio bytes immediately. Priming it with silence prevents the + # audible test clip from being discarded during that startup transition. + - id: prepare_alarm_speaker mode: restart then: - - lambda: |- - id(alarm_audio_test_active) = false; + - audio_dac.mute_on: + id: alarm_audio_dac - - script.stop: alarm_audio_alarm_test + - switch.turn_off: alarm_speaker_amplifier - speaker.stop: id: alarm_speaker - - switch.turn_on: alarm_speaker_amplifier - - delay: 40ms + - wait_until: + condition: + speaker.is_stopped: + id: alarm_speaker + timeout: 750ms + + # This first small write starts the I2S task. It is intentionally silent. + - speaker.play: + id: alarm_speaker + data: [0, 0, 0, 0] + + - delay: 150ms - audio_dac.set_volume: id: alarm_audio_dac @@ -2384,65 +2402,113 @@ script: } return volume / 100.0f; + - switch.turn_on: alarm_speaker_amplifier + - delay: 20ms + - audio_dac.mute_off: id: alarm_audio_dac + - delay: 20ms + + - id: alarm_audio_beep + mode: restart + then: + - lambda: |- + id(alarm_audio_test_active) = false; + + - script.stop: alarm_audio_alarm_test + - script.execute: prepare_alarm_speaker + - script.wait: prepare_alarm_speaker + - lvgl.label.update: id: alarm_audio_beep_label text: "Beep\nON" - - speaker.play: - id: alarm_speaker - data: !lambda |- - const uint32_t sample_rate = 16000; - const uint32_t duration_ms = 360; - const uint32_t sample_count = - (sample_rate * duration_ms) / 1000; - const float frequency = 880.0f; - const uint32_t fade_samples = sample_rate / 100; + # Generate signed 16-bit little-endian mono PCM. Feed the ring buffer in + # a loop so the complete sound is queued even when it is larger than the + # speaker buffer's immediately available space. + - lambda: |- + const uint32_t sample_rate = 48000; + const uint32_t duration_ms = 480; + const uint32_t sample_count = + (sample_rate * duration_ms) / 1000; + const float frequency = 880.0f; + const uint32_t fade_samples = sample_rate / 80; - std::vector audio; - audio.reserve(sample_count * 2); + std::vector audio; + audio.reserve(sample_count * 2); - for (uint32_t i = 0; i < sample_count; i++) { - float envelope = 1.0f; + for (uint32_t i = 0; i < sample_count; i++) { + float envelope = 1.0f; - if (i < fade_samples) { - envelope = - static_cast(i) / fade_samples; - } else if (i > sample_count - fade_samples) { - envelope = - static_cast(sample_count - i) / - fade_samples; - } - - const float phase = - 6.28318530718f * frequency * - static_cast(i) / sample_rate; - const int16_t sample = static_cast( - 12000.0f * envelope * sinf(phase) - ); - - audio.push_back( - static_cast(sample & 0xFF) - ); - audio.push_back( - static_cast((sample >> 8) & 0xFF) - ); + if (i < fade_samples) { + envelope = static_cast(i) / fade_samples; + } else if (i > sample_count - fade_samples) { + envelope = static_cast(sample_count - i) / + fade_samples; } - return audio; + const float phase = + 6.28318530718f * frequency * + static_cast(i) / sample_rate; + const int16_t sample = static_cast( + 14000.0f * envelope * sinf(phase) + ); - - delay: 430ms + audio.push_back(static_cast(sample & 0xFF)); + audio.push_back( + static_cast((sample >> 8) & 0xFF) + ); + } + + size_t offset = 0; + uint8_t zero_write_count = 0; + + while (offset < audio.size()) { + const size_t written = id(alarm_speaker).play( + audio.data() + offset, + audio.size() - offset, + pdMS_TO_TICKS(250) + ); + + if (written == 0) { + zero_write_count++; + if (zero_write_count >= 8) { + ESP_LOGE( + "alarm_audio", + "Beep playback stalled after %u of %u bytes", + static_cast(offset), + static_cast(audio.size()) + ); + break; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } else { + offset += written; + zero_write_count = 0; + } + } + + ESP_LOGD( + "alarm_audio", + "Queued beep audio: %u of %u bytes", + static_cast(offset), + static_cast(audio.size()) + ); - speaker.finish: id: alarm_speaker - - delay: 80ms + - wait_until: + condition: + speaker.is_stopped: + id: alarm_speaker + timeout: 2s - audio_dac.mute_on: id: alarm_audio_dac + - delay: 20ms - switch.turn_off: alarm_speaker_amplifier - lvgl.label.update: @@ -2456,28 +2522,12 @@ script: then: - script.stop: alarm_audio_beep - - speaker.stop: - id: alarm_speaker - - lambda: |- id(alarm_audio_test_active) = true; - script.execute: refresh_alarm_audio_controls - - - switch.turn_on: alarm_speaker_amplifier - - delay: 40ms - - - audio_dac.set_volume: - id: alarm_audio_dac - volume: !lambda |- - float volume = id(alarm_volume).state; - if (isnan(volume)) { - volume = 35.0f; - } - return volume / 100.0f; - - - audio_dac.mute_off: - id: alarm_audio_dac + - script.execute: prepare_alarm_speaker + - script.wait: prepare_alarm_speaker - while: condition: @@ -2485,72 +2535,111 @@ script: return id(alarm_audio_test_active); then: - - speaker.play: - id: alarm_speaker - data: !lambda |- - const uint32_t sample_rate = 16000; - const uint32_t duration_ms = 900; - const uint32_t sample_count = - (sample_rate * duration_ms) / 1000; - const uint32_t fade_samples = sample_rate / 80; + # A two-tone 800 ms alarm cycle. The direct write loop prevents + # truncation when the clip is larger than the free ring-buffer area. + - lambda: |- + const uint32_t sample_rate = 48000; + const uint32_t duration_ms = 800; + const uint32_t sample_count = + (sample_rate * duration_ms) / 1000; + const uint32_t fade_samples = sample_rate / 100; - std::vector audio; - audio.reserve(sample_count * 2); + std::vector audio; + audio.reserve(sample_count * 2); - for (uint32_t i = 0; i < sample_count; i++) { - const uint32_t time_ms = - (i * 1000) / sample_rate; - float frequency = 0.0f; - uint32_t burst_start = 0; - uint32_t burst_end = 0; + for (uint32_t i = 0; i < sample_count; i++) { + const uint32_t time_ms = + (i * 1000) / sample_rate; - if (time_ms < 280) { - frequency = 740.0f; - burst_start = 0; - burst_end = - (sample_rate * 280) / 1000; - } else if (time_ms >= 360 && time_ms < 640) { - frequency = 990.0f; - burst_start = - (sample_rate * 360) / 1000; - burst_end = - (sample_rate * 640) / 1000; + float frequency = 0.0f; + uint32_t burst_start = 0; + uint32_t burst_end = 0; + + if (time_ms < 300) { + frequency = 740.0f; + burst_start = 0; + burst_end = (sample_rate * 300) / 1000; + } else if (time_ms >= 400 && time_ms < 700) { + frequency = 990.0f; + burst_start = (sample_rate * 400) / 1000; + burst_end = (sample_rate * 700) / 1000; + } + + int16_t sample = 0; + + if (frequency > 0.0f) { + float envelope = 1.0f; + + if (i < burst_start + fade_samples) { + envelope = static_cast(i - burst_start) / + fade_samples; + } else if (i > burst_end - fade_samples) { + envelope = static_cast(burst_end - i) / + fade_samples; } - int16_t sample = 0; - - if (frequency > 0.0f) { - float envelope = 1.0f; - - if (i < burst_start + fade_samples) { - envelope = static_cast( - i - burst_start - ) / fade_samples; - } else if (i > burst_end - fade_samples) { - envelope = static_cast( - burst_end - i - ) / fade_samples; - } - - const float phase = - 6.28318530718f * frequency * - static_cast(i) / sample_rate; - sample = static_cast( - 12000.0f * envelope * sinf(phase) - ); - } - - audio.push_back( - static_cast(sample & 0xFF) - ); - audio.push_back( - static_cast((sample >> 8) & 0xFF) + const float phase = + 6.28318530718f * frequency * + static_cast(i) / sample_rate; + sample = static_cast( + 14000.0f * envelope * sinf(phase) ); } - return audio; + audio.push_back( + static_cast(sample & 0xFF) + ); + audio.push_back( + static_cast((sample >> 8) & 0xFF) + ); + } - - delay: 950ms + size_t offset = 0; + uint8_t zero_write_count = 0; + + while ( + offset < audio.size() && + id(alarm_audio_test_active) + ) { + const size_t written = id(alarm_speaker).play( + audio.data() + offset, + audio.size() - offset, + pdMS_TO_TICKS(250) + ); + + if (written == 0) { + zero_write_count++; + if (zero_write_count >= 8) { + ESP_LOGE( + "alarm_audio", + "Alarm playback stalled after %u of %u bytes", + static_cast(offset), + static_cast(audio.size()) + ); + break; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } else { + offset += written; + zero_write_count = 0; + } + } + + ESP_LOGV( + "alarm_audio", + "Queued alarm cycle: %u of %u bytes", + static_cast(offset), + static_cast(audio.size()) + ); + + - wait_until: + condition: + not: + speaker.is_playing: + id: alarm_speaker + timeout: 2s + + - delay: 250ms - id: stop_alarm_audio mode: restart @@ -11700,4 +11789,4 @@ lvgl: text: "" text_font: font_small text_color: 0x887799 - clickable: false \ No newline at end of file + clickable: false