Edit esp-bedside-panel.yaml
This commit is contained in:
+127
-56
@@ -6,6 +6,8 @@
|
|||||||
# 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.45 2026-07-20 Shortened the project description to remain within ESPHome's 255-byte string limit
|
||||||
|
# V1.44 2026-07-20 Added a classic 4096 Hz digital-clock triplet alarm and remapped displayed 0-100% volume to a quieter 0-30% output ceiling
|
||||||
# V1.43 2026-07-20 Raised alarm-test pitch and capped volume at the proven 40% maximum
|
# V1.43 2026-07-20 Raised alarm-test pitch and capped volume at the proven 40% maximum
|
||||||
# V1.42 2026-07-20 Changed tests to higher single-tone sounds and added a useful perceptual volume curve
|
# V1.42 2026-07-20 Changed tests to higher single-tone sounds and added a useful perceptual volume curve
|
||||||
# V1.41 2026-07-20 Fixed raw alarm audio startup and buffering so tones play instead of amplifier clicks
|
# V1.41 2026-07-20 Fixed raw alarm audio startup and buffering so tones play instead of amplifier clicks
|
||||||
@@ -144,11 +146,13 @@
|
|||||||
# - The Snooze duration is a persistent Home Assistant configuration entity.
|
# - The Snooze duration is a persistent Home Assistant configuration entity.
|
||||||
# - Alarm speaker volume is persistent and adjustable from Home Assistant or the display.
|
# - Alarm speaker volume is persistent and adjustable from Home Assistant or the display.
|
||||||
# - The ES8311 remains at its 75% / 0 dB reference while local PCM amplitude
|
# - The ES8311 remains at its 75% / 0 dB reference while local PCM amplitude
|
||||||
# follows the proven perceptual curve across a deliberately capped 5-40% range.
|
# follows a perceptual curve across the displayed 0-100% volume range.
|
||||||
# - The 40% setting is the loudest available level because testing found it
|
# - Displayed 100% is calibrated to the previous internal 30% sound level, so
|
||||||
# to be the practical bedside maximum for the fitted speaker.
|
# the full user-facing range remains useful without exposing excessive volume.
|
||||||
|
# - Displayed 0% produces silent PCM; 5-100% spans a smooth 24 dB range.
|
||||||
# - The Alarm Clock page provides local Beep, repeating Alarm and Stop hardware tests.
|
# - The Alarm Clock page provides local Beep, repeating Alarm and Stop hardware tests.
|
||||||
# - Beep is a short 2800 Hz tone; Alarm repeats one 2600 Hz tone.
|
# - Beep is a short 4096 Hz electronic tone.
|
||||||
|
# - Alarm repeats a quick 4096 Hz beep-beep-beep group followed by a pause.
|
||||||
# - Speaker tests generate audio locally and do not require Home Assistant or media files.
|
# - Speaker tests generate audio locally and do not require Home Assistant or media files.
|
||||||
#:########################################################################################:#
|
#:########################################################################################:#
|
||||||
# OFFLINE NOTES:
|
# OFFLINE NOTES:
|
||||||
@@ -168,12 +172,12 @@ substitutions:
|
|||||||
# Device Naming
|
# Device Naming
|
||||||
device_name: "esp-bedside-panel"
|
device_name: "esp-bedside-panel"
|
||||||
friendly_name: "ESP Bedside Panel"
|
friendly_name: "ESP Bedside Panel"
|
||||||
description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Four-row Alarm Clock interface with higher-pitched single-tone speaker testing, a tested bedside-safe 40% volume ceiling and a reusable 24-hour roller time editor. (Layout V1.1)"
|
description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside panel with four alarms, a 4096 Hz triplet alarm, quiet mapped 0-100% volume and a 24-hour roller time editor. (Layout V1.1)"
|
||||||
device_area: "Bedroom"
|
device_area: "Bedroom"
|
||||||
|
|
||||||
# Project Naming
|
# Project Naming
|
||||||
project_name: "Guition.JC1060P470C_I_W_Y"
|
project_name: "Guition.JC1060P470C_I_W_Y"
|
||||||
project_version: "v1.43"
|
project_version: "v1.45"
|
||||||
|
|
||||||
# Passwords & Secrets
|
# Passwords & Secrets
|
||||||
api_key: !secret esp-api_key
|
api_key: !secret esp-api_key
|
||||||
@@ -235,7 +239,7 @@ substitutions:
|
|||||||
|
|
||||||
# Alarm Clock Defaults
|
# Alarm Clock Defaults
|
||||||
alarm_snooze_minutes_default: "10"
|
alarm_snooze_minutes_default: "10"
|
||||||
alarm_volume_default: "35"
|
alarm_volume_default: "50"
|
||||||
|
|
||||||
# Alarm Speaker Hardware
|
# Alarm Speaker Hardware
|
||||||
# GPIO11 is the onboard speaker power-amplifier enable pin.
|
# GPIO11 is the onboard speaker power-amplifier enable pin.
|
||||||
@@ -251,14 +255,26 @@ substitutions:
|
|||||||
# Alarm Test Sound
|
# Alarm Test Sound
|
||||||
# The ES8311 uses 75% as its 0 dB reference. Keep the codec at that
|
# The ES8311 uses 75% as its 0 dB reference. Keep the codec at that
|
||||||
# reference and apply the user volume to the generated PCM samples.
|
# reference and apply the user volume to the generated PCM samples.
|
||||||
|
#
|
||||||
|
# The display and Home Assistant expose a normal 0-100% range. Internally,
|
||||||
|
# displayed 100% is calibrated to the previous 30% sound level. Lower user
|
||||||
|
# settings then follow the configured perceptual attenuation range.
|
||||||
alarm_dac_reference_volume: "0.75"
|
alarm_dac_reference_volume: "0.75"
|
||||||
alarm_pcm_max_amplitude: "28000"
|
alarm_pcm_max_amplitude: "28000"
|
||||||
|
alarm_output_ceiling_percent: "30"
|
||||||
alarm_volume_attenuation_db: "24"
|
alarm_volume_attenuation_db: "24"
|
||||||
alarm_beep_frequency_hz: "2800"
|
|
||||||
alarm_beep_duration_ms: "250"
|
# 4096 Hz is the characteristic electronic-timepiece alarm frequency.
|
||||||
alarm_tone_frequency_hz: "2600"
|
# A bipolar square wave gives it the sharper digital-clock character.
|
||||||
alarm_tone_on_ms: "350"
|
alarm_beep_frequency_hz: "4096"
|
||||||
alarm_tone_off_ms: "250"
|
alarm_beep_duration_ms: "120"
|
||||||
|
|
||||||
|
# Default alarm pattern: beep-beep-beep, pause, repeat.
|
||||||
|
alarm_tone_frequency_hz: "4096"
|
||||||
|
alarm_tone_beep_ms: "100"
|
||||||
|
alarm_tone_gap_ms: "80"
|
||||||
|
alarm_tone_beeps_per_group: "3"
|
||||||
|
alarm_tone_group_pause_ms: "440"
|
||||||
|
|
||||||
# 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.
|
||||||
@@ -1051,8 +1067,8 @@ number:
|
|||||||
restore_value: true
|
restore_value: true
|
||||||
initial_value: "${alarm_volume_default}"
|
initial_value: "${alarm_volume_default}"
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
min_value: 5
|
min_value: 0
|
||||||
max_value: 40
|
max_value: 100
|
||||||
step: 5
|
step: 5
|
||||||
mode: slider
|
mode: slider
|
||||||
|
|
||||||
@@ -2351,7 +2367,7 @@ script:
|
|||||||
text: !lambda |-
|
text: !lambda |-
|
||||||
float volume = id(alarm_volume).state;
|
float volume = id(alarm_volume).state;
|
||||||
if (isnan(volume)) {
|
if (isnan(volume)) {
|
||||||
volume = 35.0f;
|
volume = ${alarm_volume_default}.0f;
|
||||||
}
|
}
|
||||||
char buffer[16];
|
char buffer[16];
|
||||||
snprintf(
|
snprintf(
|
||||||
@@ -2450,19 +2466,31 @@ script:
|
|||||||
if (isnan(ui_volume)) {
|
if (isnan(ui_volume)) {
|
||||||
ui_volume = ${alarm_volume_default}.0f;
|
ui_volume = ${alarm_volume_default}.0f;
|
||||||
}
|
}
|
||||||
ui_volume = fminf(fmaxf(ui_volume, 5.0f), 40.0f);
|
ui_volume = fminf(fmaxf(ui_volume, 0.0f), 100.0f);
|
||||||
|
|
||||||
// Equal 5% steps produce roughly equal perceived loudness changes.
|
// Displayed 100% equals the previous internal 30% loudness point.
|
||||||
// The 40% ceiling deliberately preserves the loudest level proven
|
// The full displayed range then spans a perceptual attenuation curve.
|
||||||
// suitable during bedside speaker testing.
|
float peak_amplitude = 0.0f;
|
||||||
const float volume_position =
|
if (ui_volume > 0.0f) {
|
||||||
(ui_volume - 5.0f) / 95.0f;
|
const float ceiling_position =
|
||||||
const float attenuation_db =
|
fminf(fmaxf(
|
||||||
|
(${alarm_output_ceiling_percent}.0f - 5.0f) / 95.0f,
|
||||||
|
0.0f
|
||||||
|
), 1.0f);
|
||||||
|
const float ceiling_attenuation_db =
|
||||||
-${alarm_volume_attenuation_db}.0f *
|
-${alarm_volume_attenuation_db}.0f *
|
||||||
(1.0f - volume_position);
|
(1.0f - ceiling_position);
|
||||||
const float peak_amplitude =
|
const float user_position = ui_volume / 100.0f;
|
||||||
|
const float user_attenuation_db =
|
||||||
|
-${alarm_volume_attenuation_db}.0f *
|
||||||
|
(1.0f - user_position);
|
||||||
|
peak_amplitude =
|
||||||
${alarm_pcm_max_amplitude}.0f *
|
${alarm_pcm_max_amplitude}.0f *
|
||||||
powf(10.0f, attenuation_db / 20.0f);
|
powf(
|
||||||
|
10.0f,
|
||||||
|
(ceiling_attenuation_db + user_attenuation_db) / 20.0f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> audio;
|
std::vector<uint8_t> audio;
|
||||||
audio.reserve(sample_count * 2);
|
audio.reserve(sample_count * 2);
|
||||||
@@ -2477,11 +2505,14 @@ script:
|
|||||||
fade_samples;
|
fade_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float phase =
|
const float cycle_position = fmodf(
|
||||||
6.28318530718f * frequency *
|
frequency * static_cast<float>(i) / sample_rate,
|
||||||
static_cast<float>(i) / sample_rate;
|
1.0f
|
||||||
|
);
|
||||||
|
const float waveform =
|
||||||
|
cycle_position < 0.5f ? 1.0f : -1.0f;
|
||||||
const int16_t sample = static_cast<int16_t>(
|
const int16_t sample = static_cast<int16_t>(
|
||||||
peak_amplitude * envelope * sinf(phase)
|
peak_amplitude * envelope * waveform
|
||||||
);
|
);
|
||||||
|
|
||||||
audio.push_back(static_cast<uint8_t>(sample & 0xFF));
|
audio.push_back(static_cast<uint8_t>(sample & 0xFF));
|
||||||
@@ -2564,35 +2595,55 @@ script:
|
|||||||
return id(alarm_audio_test_active);
|
return id(alarm_audio_test_active);
|
||||||
|
|
||||||
then:
|
then:
|
||||||
# A higher-pitched single-tone alarm pulse. The direct write loop
|
# A classic digital-clock triplet: three short 4096 Hz square-wave
|
||||||
# prevents truncation when the clip is larger than the free
|
# beeps followed by a longer pause. The direct write loop prevents
|
||||||
# ring-buffer area.
|
# truncation when the clip is larger than the free ring-buffer area.
|
||||||
- lambda: |-
|
- lambda: |-
|
||||||
const uint32_t sample_rate = ${alarm_audio_sample_rate};
|
const uint32_t sample_rate = ${alarm_audio_sample_rate};
|
||||||
const uint32_t tone_ms = ${alarm_tone_on_ms};
|
const uint32_t beep_ms = ${alarm_tone_beep_ms};
|
||||||
const uint32_t silence_ms = ${alarm_tone_off_ms};
|
const uint32_t gap_ms = ${alarm_tone_gap_ms};
|
||||||
const uint32_t duration_ms = tone_ms + silence_ms;
|
const uint32_t beep_count =
|
||||||
|
${alarm_tone_beeps_per_group};
|
||||||
|
const uint32_t group_pause_ms =
|
||||||
|
${alarm_tone_group_pause_ms};
|
||||||
|
const uint32_t duration_ms =
|
||||||
|
(beep_count * beep_ms) +
|
||||||
|
((beep_count - 1) * gap_ms) +
|
||||||
|
group_pause_ms;
|
||||||
const uint32_t sample_count =
|
const uint32_t sample_count =
|
||||||
(sample_rate * duration_ms) / 1000;
|
(sample_rate * duration_ms) / 1000;
|
||||||
const uint32_t tone_end =
|
const uint32_t fade_samples = sample_rate / 500;
|
||||||
(sample_rate * tone_ms) / 1000;
|
|
||||||
const uint32_t fade_samples = sample_rate / 100;
|
|
||||||
const float frequency = ${alarm_tone_frequency_hz}.0f;
|
const float frequency = ${alarm_tone_frequency_hz}.0f;
|
||||||
|
|
||||||
float ui_volume = id(alarm_volume).state;
|
float ui_volume = id(alarm_volume).state;
|
||||||
if (isnan(ui_volume)) {
|
if (isnan(ui_volume)) {
|
||||||
ui_volume = ${alarm_volume_default}.0f;
|
ui_volume = ${alarm_volume_default}.0f;
|
||||||
}
|
}
|
||||||
ui_volume = fminf(fmaxf(ui_volume, 5.0f), 40.0f);
|
ui_volume = fminf(fmaxf(ui_volume, 0.0f), 100.0f);
|
||||||
|
|
||||||
const float volume_position =
|
float peak_amplitude = 0.0f;
|
||||||
(ui_volume - 5.0f) / 95.0f;
|
if (ui_volume > 0.0f) {
|
||||||
const float attenuation_db =
|
const float ceiling_position =
|
||||||
|
fminf(fmaxf(
|
||||||
|
(${alarm_output_ceiling_percent}.0f - 5.0f) /
|
||||||
|
95.0f,
|
||||||
|
0.0f
|
||||||
|
), 1.0f);
|
||||||
|
const float ceiling_attenuation_db =
|
||||||
-${alarm_volume_attenuation_db}.0f *
|
-${alarm_volume_attenuation_db}.0f *
|
||||||
(1.0f - volume_position);
|
(1.0f - ceiling_position);
|
||||||
const float peak_amplitude =
|
const float user_position = ui_volume / 100.0f;
|
||||||
|
const float user_attenuation_db =
|
||||||
|
-${alarm_volume_attenuation_db}.0f *
|
||||||
|
(1.0f - user_position);
|
||||||
|
peak_amplitude =
|
||||||
${alarm_pcm_max_amplitude}.0f *
|
${alarm_pcm_max_amplitude}.0f *
|
||||||
powf(10.0f, attenuation_db / 20.0f);
|
powf(
|
||||||
|
10.0f,
|
||||||
|
(ceiling_attenuation_db +
|
||||||
|
user_attenuation_db) / 20.0f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> audio;
|
std::vector<uint8_t> audio;
|
||||||
audio.reserve(sample_count * 2);
|
audio.reserve(sample_count * 2);
|
||||||
@@ -2600,23 +2651,43 @@ script:
|
|||||||
for (uint32_t i = 0; i < sample_count; i++) {
|
for (uint32_t i = 0; i < sample_count; i++) {
|
||||||
int16_t sample = 0;
|
int16_t sample = 0;
|
||||||
|
|
||||||
if (i < tone_end) {
|
for (uint32_t beep = 0; beep < beep_count; beep++) {
|
||||||
|
const uint32_t start_ms = beep * (beep_ms + gap_ms);
|
||||||
|
const uint32_t end_ms = start_ms + beep_ms;
|
||||||
|
const uint32_t start_sample =
|
||||||
|
(sample_rate * start_ms) / 1000;
|
||||||
|
const uint32_t end_sample =
|
||||||
|
(sample_rate * end_ms) / 1000;
|
||||||
|
|
||||||
|
if (i >= start_sample && i < end_sample) {
|
||||||
|
const uint32_t local_sample = i - start_sample;
|
||||||
|
const uint32_t tone_samples =
|
||||||
|
end_sample - start_sample;
|
||||||
float envelope = 1.0f;
|
float envelope = 1.0f;
|
||||||
|
|
||||||
if (i < fade_samples) {
|
if (local_sample < fade_samples) {
|
||||||
envelope = static_cast<float>(i) /
|
envelope = static_cast<float>(local_sample) /
|
||||||
fade_samples;
|
fade_samples;
|
||||||
} else if (i > tone_end - fade_samples) {
|
} else if (
|
||||||
envelope = static_cast<float>(tone_end - i) /
|
local_sample > tone_samples - fade_samples
|
||||||
|
) {
|
||||||
|
envelope =
|
||||||
|
static_cast<float>(tone_samples - local_sample) /
|
||||||
fade_samples;
|
fade_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float phase =
|
const float cycle_position = fmodf(
|
||||||
6.28318530718f * frequency *
|
frequency * static_cast<float>(local_sample) /
|
||||||
static_cast<float>(i) / sample_rate;
|
sample_rate,
|
||||||
sample = static_cast<int16_t>(
|
1.0f
|
||||||
peak_amplitude * envelope * sinf(phase)
|
|
||||||
);
|
);
|
||||||
|
const float waveform =
|
||||||
|
cycle_position < 0.5f ? 1.0f : -1.0f;
|
||||||
|
sample = static_cast<int16_t>(
|
||||||
|
peak_amplitude * envelope * waveform
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audio.push_back(
|
audio.push_back(
|
||||||
@@ -11129,7 +11200,7 @@ lvgl:
|
|||||||
- label:
|
- label:
|
||||||
id: alarm_audio_volume_label
|
id: alarm_audio_volume_label
|
||||||
align: CENTER
|
align: CENTER
|
||||||
text: "Vol\n35%"
|
text: "Vol\n50%"
|
||||||
text_font: font_small
|
text_font: font_small
|
||||||
text_align: CENTER
|
text_align: CENTER
|
||||||
text_color: 0xB9D9ED
|
text_color: 0xB9D9ED
|
||||||
|
|||||||
Reference in New Issue
Block a user