Edit esp-bedside-panel.yaml

This commit is contained in:
ESPHome Device Builder
2026-07-20 12:19:25 +12:00
parent ea604b8a30
commit f07cd6d0c4
+211 -122
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedside-panel.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedside-panel.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # 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.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.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 # 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 Naming
project_name: "Guition.JC1060P470C_I_W_Y" project_name: "Guition.JC1060P470C_I_W_Y"
project_version: "v1.40" project_version: "v1.41"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -238,7 +239,7 @@ substitutions:
alarm_i2s_mclk_pin: "GPIO13" alarm_i2s_mclk_pin: "GPIO13"
alarm_i2s_dout_pin: "GPIO9" alarm_i2s_dout_pin: "GPIO9"
alarm_speaker_enable_pin: "GPIO11" alarm_speaker_enable_pin: "GPIO11"
alarm_audio_sample_rate: "16000" alarm_audio_sample_rate: "48000"
# Security Camera Settings # Security Camera Settings
# Change this only if homeassistant.local is not reachable from the panel. # Change this only if homeassistant.local is not reachable from the panel.
@@ -572,7 +573,10 @@ speaker:
sample_rate: "${alarm_audio_sample_rate}" sample_rate: "${alarm_audio_sample_rate}"
bits_per_sample: 16bit bits_per_sample: 16bit
channel: mono channel: mono
buffer_duration: 100ms i2s_comm_fmt: stand_i2s
mclk_multiple: 256
buffer_duration: 500ms
timeout: never
#:########################################################################################:# #:########################################################################################:#
# OUTPUT: # OUTPUT:
@@ -2361,19 +2365,33 @@ script:
? std::string("Alarm\nON") ? std::string("Alarm\nON")
: std::string("Alarm"); : 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 mode: restart
then: then:
- lambda: |- - audio_dac.mute_on:
id(alarm_audio_test_active) = false; id: alarm_audio_dac
- script.stop: alarm_audio_alarm_test - switch.turn_off: alarm_speaker_amplifier
- speaker.stop: - speaker.stop:
id: alarm_speaker id: alarm_speaker
- switch.turn_on: alarm_speaker_amplifier - wait_until:
- delay: 40ms 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: - audio_dac.set_volume:
id: alarm_audio_dac id: alarm_audio_dac
@@ -2384,65 +2402,113 @@ script:
} }
return volume / 100.0f; return volume / 100.0f;
- switch.turn_on: alarm_speaker_amplifier
- delay: 20ms
- audio_dac.mute_off: - audio_dac.mute_off:
id: alarm_audio_dac 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: - lvgl.label.update:
id: alarm_audio_beep_label id: alarm_audio_beep_label
text: "Beep\nON" text: "Beep\nON"
- speaker.play: # Generate signed 16-bit little-endian mono PCM. Feed the ring buffer in
id: alarm_speaker # a loop so the complete sound is queued even when it is larger than the
data: !lambda |- # speaker buffer's immediately available space.
const uint32_t sample_rate = 16000; - lambda: |-
const uint32_t duration_ms = 360; const uint32_t sample_rate = 48000;
const uint32_t sample_count = const uint32_t duration_ms = 480;
(sample_rate * duration_ms) / 1000; const uint32_t sample_count =
const float frequency = 880.0f; (sample_rate * duration_ms) / 1000;
const uint32_t fade_samples = sample_rate / 100; const float frequency = 880.0f;
const uint32_t fade_samples = sample_rate / 80;
std::vector<uint8_t> audio; std::vector<uint8_t> audio;
audio.reserve(sample_count * 2); audio.reserve(sample_count * 2);
for (uint32_t i = 0; i < sample_count; i++) { for (uint32_t i = 0; i < sample_count; i++) {
float envelope = 1.0f; float envelope = 1.0f;
if (i < fade_samples) { if (i < fade_samples) {
envelope = envelope = static_cast<float>(i) / fade_samples;
static_cast<float>(i) / fade_samples; } else if (i > sample_count - fade_samples) {
} else if (i > sample_count - fade_samples) { envelope = static_cast<float>(sample_count - i) /
envelope = fade_samples;
static_cast<float>(sample_count - i) /
fade_samples;
}
const float phase =
6.28318530718f * frequency *
static_cast<float>(i) / sample_rate;
const int16_t sample = static_cast<int16_t>(
12000.0f * envelope * sinf(phase)
);
audio.push_back(
static_cast<uint8_t>(sample & 0xFF)
);
audio.push_back(
static_cast<uint8_t>((sample >> 8) & 0xFF)
);
} }
return audio; const float phase =
6.28318530718f * frequency *
static_cast<float>(i) / sample_rate;
const int16_t sample = static_cast<int16_t>(
14000.0f * envelope * sinf(phase)
);
- delay: 430ms audio.push_back(static_cast<uint8_t>(sample & 0xFF));
audio.push_back(
static_cast<uint8_t>((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<unsigned>(offset),
static_cast<unsigned>(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<unsigned>(offset),
static_cast<unsigned>(audio.size())
);
- speaker.finish: - speaker.finish:
id: alarm_speaker id: alarm_speaker
- delay: 80ms - wait_until:
condition:
speaker.is_stopped:
id: alarm_speaker
timeout: 2s
- audio_dac.mute_on: - audio_dac.mute_on:
id: alarm_audio_dac id: alarm_audio_dac
- delay: 20ms
- switch.turn_off: alarm_speaker_amplifier - switch.turn_off: alarm_speaker_amplifier
- lvgl.label.update: - lvgl.label.update:
@@ -2456,28 +2522,12 @@ script:
then: then:
- script.stop: alarm_audio_beep - script.stop: alarm_audio_beep
- speaker.stop:
id: alarm_speaker
- lambda: |- - lambda: |-
id(alarm_audio_test_active) = true; id(alarm_audio_test_active) = true;
- script.execute: refresh_alarm_audio_controls - script.execute: refresh_alarm_audio_controls
- script.execute: prepare_alarm_speaker
- switch.turn_on: alarm_speaker_amplifier - script.wait: prepare_alarm_speaker
- 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
- while: - while:
condition: condition:
@@ -2485,72 +2535,111 @@ script:
return id(alarm_audio_test_active); return id(alarm_audio_test_active);
then: then:
- speaker.play: # A two-tone 800 ms alarm cycle. The direct write loop prevents
id: alarm_speaker # truncation when the clip is larger than the free ring-buffer area.
data: !lambda |- - lambda: |-
const uint32_t sample_rate = 16000; const uint32_t sample_rate = 48000;
const uint32_t duration_ms = 900; const uint32_t duration_ms = 800;
const uint32_t sample_count = const uint32_t sample_count =
(sample_rate * duration_ms) / 1000; (sample_rate * duration_ms) / 1000;
const uint32_t fade_samples = sample_rate / 80; const uint32_t fade_samples = sample_rate / 100;
std::vector<uint8_t> audio; std::vector<uint8_t> audio;
audio.reserve(sample_count * 2); audio.reserve(sample_count * 2);
for (uint32_t i = 0; i < sample_count; i++) { for (uint32_t i = 0; i < sample_count; i++) {
const uint32_t time_ms = const uint32_t time_ms =
(i * 1000) / sample_rate; (i * 1000) / sample_rate;
float frequency = 0.0f;
uint32_t burst_start = 0;
uint32_t burst_end = 0;
if (time_ms < 280) { float frequency = 0.0f;
frequency = 740.0f; uint32_t burst_start = 0;
burst_start = 0; uint32_t burst_end = 0;
burst_end =
(sample_rate * 280) / 1000; if (time_ms < 300) {
} else if (time_ms >= 360 && time_ms < 640) { frequency = 740.0f;
frequency = 990.0f; burst_start = 0;
burst_start = burst_end = (sample_rate * 300) / 1000;
(sample_rate * 360) / 1000; } else if (time_ms >= 400 && time_ms < 700) {
burst_end = frequency = 990.0f;
(sample_rate * 640) / 1000; 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<float>(i - burst_start) /
fade_samples;
} else if (i > burst_end - fade_samples) {
envelope = static_cast<float>(burst_end - i) /
fade_samples;
} }
int16_t sample = 0; const float phase =
6.28318530718f * frequency *
if (frequency > 0.0f) { static_cast<float>(i) / sample_rate;
float envelope = 1.0f; sample = static_cast<int16_t>(
14000.0f * envelope * sinf(phase)
if (i < burst_start + fade_samples) {
envelope = static_cast<float>(
i - burst_start
) / fade_samples;
} else if (i > burst_end - fade_samples) {
envelope = static_cast<float>(
burst_end - i
) / fade_samples;
}
const float phase =
6.28318530718f * frequency *
static_cast<float>(i) / sample_rate;
sample = static_cast<int16_t>(
12000.0f * envelope * sinf(phase)
);
}
audio.push_back(
static_cast<uint8_t>(sample & 0xFF)
);
audio.push_back(
static_cast<uint8_t>((sample >> 8) & 0xFF)
); );
} }
return audio; audio.push_back(
static_cast<uint8_t>(sample & 0xFF)
);
audio.push_back(
static_cast<uint8_t>((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<unsigned>(offset),
static_cast<unsigned>(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<unsigned>(offset),
static_cast<unsigned>(audio.size())
);
- wait_until:
condition:
not:
speaker.is_playing:
id: alarm_speaker
timeout: 2s
- delay: 250ms
- id: stop_alarm_audio - id: stop_alarm_audio
mode: restart mode: restart