From ea604b8a303b8fbd33f5ccb53b8316602c0d72dd Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Mon, 20 Jul 2026 12:01:52 +1200 Subject: [PATCH] Edit esp-bedside-panel.yaml --- esphome/esp-bedside-panel.yaml | 564 ++++++++++++++++++++++++++++++++- 1 file changed, 558 insertions(+), 6 deletions(-) diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index 23669e2..4195ea3 100644 --- a/esphome/esp-bedside-panel.yaml +++ b/esphome/esp-bedside-panel.yaml @@ -6,6 +6,8 @@ # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedside-panel.yaml #:########################################################################################:# # VERSIONS: +# 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 # V1.37 2026-07-19 Added four persistent Alarm Clock configuration rows # V1.36 2026-07-19 Added Alarm Clock navigation button and initial empty page @@ -98,6 +100,9 @@ # - Battery-backed RTC: RX8130 # - RTC I2C address: 0x32 # - TEMT6000 ambient-light sensor signal: GPIO20 / ADC1 +# - ES8311 audio codec: I2C address 0x18 +# - Audio I2S: MCLK GPIO13, BCLK GPIO12, LRCLK GPIO10, DOUT GPIO9 +# - Speaker amplifier enable: GPIO11 (active high on this board revision) #:########################################################################################:# # OPERATION NOTES: # - This build returns to the proven V0.8a structure. @@ -134,6 +139,9 @@ # - Enable, days, Snooze, vibration, sound and lamp choices are active. # - Tapping a time opens a shared circular 24-hour hour/minute roller editor. # - The Snooze duration is a persistent Home Assistant configuration entity. +# - Alarm speaker volume is persistent and adjustable from Home Assistant or the display. +# - The Alarm Clock page provides local Beep, repeating Alarm and Stop hardware tests. +# - Speaker tests generate audio locally and do not require Home Assistant or media files. #:########################################################################################:# # OFFLINE NOTES: # - LVGL, the clock, touch and backlight operate locally. @@ -152,12 +160,12 @@ substitutions: # Device Naming device_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 Snooze controls and a reusable 24-hour roller time editor. (Layout V1.1)" + description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Four-row Alarm Clock interface with local ES8311 speaker testing, persistent alarm volume and a reusable 24-hour roller time editor. (Layout V1.1)" device_area: "Bedroom" # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v1.38" + project_version: "v1.40" # Passwords & Secrets api_key: !secret esp-api_key @@ -219,6 +227,18 @@ substitutions: # Alarm Clock Defaults alarm_snooze_minutes_default: "10" + alarm_volume_default: "35" + + # Alarm Speaker Hardware + # GPIO11 is the onboard speaker power-amplifier enable pin. + # The rear expansion header diagram confirms GPIO20 is a separate exposed + # GPIO; this project already uses it for the external TEMT6000 sensor. + alarm_i2s_lrclk_pin: "GPIO10" + alarm_i2s_bclk_pin: "GPIO12" + alarm_i2s_mclk_pin: "GPIO13" + alarm_i2s_dout_pin: "GPIO9" + alarm_speaker_enable_pin: "GPIO11" + alarm_audio_sample_rate: "16000" # Security Camera Settings # Change this only if homeassistant.local is not reachable from the panel. @@ -380,6 +400,7 @@ esphome: - script.execute: refresh_navigation_time - script.execute: refresh_jobs_page - script.execute: refresh_alarm_clock_page + - script.execute: refresh_alarm_audio_controls #:########################################################################################:# # ESP PLATFORM AND FRAMEWORK: @@ -510,6 +531,49 @@ i2c: frequency: 400kHz scan: true +#:########################################################################################:# +# I2S AUDIO BUS: +# ES8311 clock and playback data connections +# https://esphome.io/components/i2s_audio/ +#:########################################################################################:# +i2s_audio: + - id: alarm_i2s_audio_bus + i2s_lrclk_pin: "${alarm_i2s_lrclk_pin}" + i2s_bclk_pin: "${alarm_i2s_bclk_pin}" + i2s_mclk_pin: "${alarm_i2s_mclk_pin}" + +#:########################################################################################:# +# AUDIO DAC: +# Onboard ES8311 mono audio codec +# https://esphome.io/components/audio_dac/es8311/ +#:########################################################################################:# +audio_dac: + - platform: es8311 + id: alarm_audio_dac + i2c_id: touch_i2c + address: 0x18 + bits_per_sample: 16bit + sample_rate: "${alarm_audio_sample_rate}" + use_mclk: true + use_microphone: false + +#:########################################################################################:# +# SPEAKER: +# Local 16-bit mono output through the onboard ES8311 and speaker amplifier +# https://esphome.io/components/speaker/i2s_audio/ +#:########################################################################################:# +speaker: + - platform: i2s_audio + id: alarm_speaker + i2s_audio_id: alarm_i2s_audio_bus + i2s_dout_pin: "${alarm_i2s_dout_pin}" + dac_type: external + audio_dac: alarm_audio_dac + sample_rate: "${alarm_audio_sample_rate}" + bits_per_sample: 16bit + channel: mono + buffer_duration: 100ms + #:########################################################################################:# # OUTPUT: # https://esphome.io/components/output/ledc.html @@ -955,6 +1019,29 @@ number: step: 1 mode: box + - platform: template + id: alarm_volume + name: "${friendly_name} Alarm Volume" + icon: mdi:volume-high + entity_category: config + optimistic: true + restore_value: true + initial_value: "${alarm_volume_default}" + unit_of_measurement: "%" + min_value: 5 + max_value: 100 + step: 5 + mode: slider + + on_value: + then: + - audio_dac.set_volume: + id: alarm_audio_dac + volume: !lambda |- + return x / 100.0f; + + - script.execute: refresh_alarm_audio_controls + #:########################################################################################:# # HOME ASSISTANT NUMERIC SENSORS: # https://esphome.io/components/sensor/homeassistant.html @@ -1657,6 +1744,12 @@ globals: restore_value: false initial_value: "0" + # True while the repeating local alarm speaker test is active. + - id: alarm_audio_test_active + type: bool + restore_value: false + initial_value: "false" + #:########################################################################################:# # SCRIPTS: # Refresh all state-dependent LVGL button colours @@ -2210,6 +2303,285 @@ script: (1U << 7) ) != 0; + - script.execute: refresh_alarm_audio_controls + + + #:######################################################################################:# + # ALARM CLOCK AUDIO TESTING + #:######################################################################################:# + - id: refresh_alarm_audio_controls + mode: restart + then: + - 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; + + - if: + condition: + lambda: |- + return id(lvgl_ready); + + then: + - lvgl.label.update: + id: alarm_audio_volume_label + text: !lambda |- + float volume = id(alarm_volume).state; + if (isnan(volume)) { + volume = 35.0f; + } + char buffer[16]; + snprintf( + buffer, + sizeof(buffer), + "Vol\n%.0f%%", + volume + ); + return std::string(buffer); + + - lvgl.widget.update: + id: alarm_audio_alarm_button + bg_color: !lambda |- + return id(alarm_audio_test_active) + ? lv_color_hex(0x8A3D2D) + : lv_color_hex(0x4D3B24); + border_color: !lambda |- + return id(alarm_audio_test_active) + ? lv_color_hex(0xD97055) + : lv_color_hex(0x846338); + + - lvgl.label.update: + id: alarm_audio_alarm_label + text: !lambda |- + return id(alarm_audio_test_active) + ? std::string("Alarm\nON") + : std::string("Alarm"); + + - id: alarm_audio_beep + mode: restart + then: + - lambda: |- + id(alarm_audio_test_active) = false; + + - script.stop: alarm_audio_alarm_test + + - speaker.stop: + id: alarm_speaker + + - 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 + + - 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; + + std::vector audio; + audio.reserve(sample_count * 2); + + 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) + ); + } + + return audio; + + - delay: 430ms + + - speaker.finish: + id: alarm_speaker + + - delay: 80ms + + - audio_dac.mute_on: + id: alarm_audio_dac + + - switch.turn_off: alarm_speaker_amplifier + + - lvgl.label.update: + id: alarm_audio_beep_label + text: "Beep" + + - script.execute: refresh_alarm_audio_controls + + - id: alarm_audio_alarm_test + mode: restart + 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 + + - while: + condition: + lambda: |- + 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; + + 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; + + 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; + } + + 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) + ); + } + + return audio; + + - delay: 950ms + + - id: stop_alarm_audio + mode: restart + then: + - lambda: |- + id(alarm_audio_test_active) = false; + + - script.stop: alarm_audio_beep + - script.stop: alarm_audio_alarm_test + + - speaker.stop: + id: alarm_speaker + + - audio_dac.mute_on: + id: alarm_audio_dac + + - delay: 20ms + - switch.turn_off: alarm_speaker_amplifier + + - if: + condition: + lambda: |- + return id(lvgl_ready); + + then: + - lvgl.label.update: + id: alarm_audio_beep_label + text: "Beep" + + - script.execute: refresh_alarm_audio_controls + #:######################################################################################:# # ALARM CLOCK TIME EDITOR @@ -2250,7 +2622,7 @@ script: - lvgl.label.update: id: alarm_time_editor_title text: !lambda |- - char buffer[20]; + char buffer[32]; snprintf( buffer, sizeof(buffer), @@ -4795,10 +5167,24 @@ interval: - script.execute: refresh_security_camera #:########################################################################################:# -# HOME ASSISTANT SWITCH COMPONENT: -# https://esphome.io/components/switch/homeassistant/ +# SWITCH COMPONENTS: +# Onboard speaker amplifier plus imported Home Assistant switches #:########################################################################################:# switch: + #:######################################################################################:# + # ALARM SPEAKER AMPLIFIER + #:######################################################################################:# + - platform: gpio + id: alarm_speaker_amplifier + pin: + number: "${alarm_speaker_enable_pin}" + mode: + output: true + restore_mode: ALWAYS_OFF + + #:######################################################################################:# + # HOME ASSISTANT SWITCHES + #:######################################################################################:# - platform: homeassistant id: ha_garage_lights entity_id: "${garage_lights_entity}" @@ -10562,6 +10948,168 @@ lvgl: - lambda: |- id(alarm_lamp_enabled)[3] = x; + #:################################################################################:# + # ALARM SPEAKER TEST CONTROLS + #:################################################################################:# + - obj: + id: alarm_audio_test_panel + x: 10 + y: 475 + width: 594 + height: 92 + bg_color: 0x171F29 + bg_opa: COVER + border_width: 1 + border_color: 0x364250 + outline_width: 0 + shadow_width: 0 + radius: 14 + pad_all: 0 + clickable: false + widgets: + - button: + id: alarm_audio_volume_down_button + x: 8 + y: 10 + width: 52 + height: 72 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x557895 + radius: 11 + pressed: + bg_color: 0x496681 + widgets: + - label: + align: CENTER + text: "-" + text_font: font_button + text_color: 0xFFFFFF + + on_click: + - number.decrement: + id: alarm_volume + cycle: false + + - obj: + x: 66 + y: 10 + width: 108 + height: 72 + bg_color: 0x202A36 + bg_opa: COVER + border_width: 1 + border_color: 0x445365 + radius: 11 + clickable: false + widgets: + - label: + id: alarm_audio_volume_label + align: CENTER + text: "Vol\n35%" + text_font: font_small + text_align: CENTER + text_color: 0xB9D9ED + + - button: + id: alarm_audio_volume_up_button + x: 180 + y: 10 + width: 52 + height: 72 + bg_color: 0x28313D + bg_opa: COVER + border_width: 2 + border_color: 0x557895 + radius: 11 + pressed: + bg_color: 0x496681 + widgets: + - label: + align: CENTER + text: "+" + text_font: font_button + text_color: 0xFFFFFF + + on_click: + - number.increment: + id: alarm_volume + cycle: false + + - button: + id: alarm_audio_beep_button + x: 238 + y: 10 + width: 112 + height: 72 + bg_color: 0x315843 + bg_opa: COVER + border_width: 2 + border_color: 0x72AE88 + radius: 11 + pressed: + bg_color: 0x416F55 + widgets: + - label: + id: alarm_audio_beep_label + align: CENTER + text: "Beep" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + + on_click: + - script.execute: alarm_audio_beep + + - button: + id: alarm_audio_alarm_button + x: 356 + y: 10 + width: 132 + height: 72 + bg_color: 0x4D3B24 + bg_opa: COVER + border_width: 2 + border_color: 0x846338 + radius: 11 + pressed: + bg_color: 0x6B502B + widgets: + - label: + id: alarm_audio_alarm_label + align: CENTER + text: "Alarm" + text_font: font_small + text_align: CENTER + text_color: 0xFFFFFF + + on_click: + - script.execute: alarm_audio_alarm_test + + - button: + id: alarm_audio_stop_button + x: 494 + y: 10 + width: 92 + height: 72 + bg_color: 0x673238 + bg_opa: COVER + border_width: 2 + border_color: 0xA45A63 + radius: 11 + pressed: + bg_color: 0x82414A + widgets: + - label: + align: CENTER + text: "Stop" + text_font: font_small + text_color: 0xFFFFFF + + on_click: + - script.execute: stop_alarm_audio + #:################################################################################:# # BOTTOM NAVIGATION #:################################################################################:# @@ -10586,6 +11134,8 @@ lvgl: text_color: 0xFFFFFF on_click: + - script.execute: stop_alarm_audio + - lvgl.page.show: id: bedroom_page animation: MOVE_RIGHT @@ -10615,6 +11165,8 @@ lvgl: text_color: 0xFFFFFF on_click: + - script.execute: stop_alarm_audio + - lvgl.page.show: id: clock_page animation: FADE_IN @@ -11148,4 +11700,4 @@ lvgl: text: "" text_font: font_small text_color: 0x887799 - clickable: false + clickable: false \ No newline at end of file