diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index c165211..1e3453e 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.33 2026-07-19 Raised the default night lux threshold after TEMT6000 testing +# V1.32 2026-07-19 Added persistent ambient-light thresholds, brightness levels and idle delays # V1.31 2026-07-19 Added TEMT6000 ambient-light voltage and illuminance sensors # V1.30 2026-07-18 Made Downstairs Guests red when occupied and removed duplicate OTA config # V1.29 2026-07-18 Perfected Atto charging alignment and expanded Security states @@ -115,8 +117,11 @@ # - The Climate page contains heat-pump, ceiling-fan, air-filter and Night Power controls. # - Sleep: 2 hrs starts a local 120-minute countdown and turns the heat pump off. # - Pressing the active Sleep button again cancels the countdown. -# - TEMT6000 voltage and approximate lux are exposed for initial calibration. -# - Automatic backlight adjustment is not enabled in this revision. +# - TEMT6000 voltage and approximate lux are exposed for calibration. +# - Touch immediately restores a day/night brightness selected from ambient lux. +# - The current page dims after a configurable idle delay. +# - A second configurable delay changes to the large Clock page. +# - Calibration, threshold, brightness and delay values persist across reboot. #:########################################################################################:# # OFFLINE NOTES: # - LVGL, the clock, touch and backlight operate locally. @@ -135,12 +140,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. TEMT6000 ambient-light sensing added for calibration and future automatic backlight control. (Layout V1.1)" + description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside control panel. Adaptive touch/idle brightness with a calibrated 20 lx default night threshold. (Layout V1.1)" device_area: "Bedroom" # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v1.31" + project_version: "v1.33" # Passwords & Secrets api_key: !secret esp-api_key @@ -166,10 +171,19 @@ substitutions: touch_interrupt_pin: "GPIO21" touch_reset_pin: "GPIO22" - # Clock / Screen Settings - clock_idle_timeout: "60s" - control_brightness: "80%" - clock_brightness: "4%" + # Adaptive Display Defaults + # + # These initialise the persistent ESPHome number entities on first install or + # after flash preferences are erased. Once adjusted through Home Assistant, + # restored values take precedence after reboot. + ambient_lux_multiplier_default: "200" + display_night_lux_threshold_default: "20" + display_day_active_brightness_default: "80" + display_night_active_brightness_default: "25" + display_day_dim_brightness_default: "15" + display_night_dim_brightness_default: "4" + display_dim_delay_default: "30" + display_clock_delay_default: "60" # Security Camera Settings # Change this only if homeassistant.local is not reachable from the panel. @@ -520,6 +534,11 @@ touchscreen: mode: output: true + # Brighten immediately on the first touch event. + on_touch: + then: + - script.execute: apply_active_backlight + #:########################################################################################:# # FONTS: # Reduced font set for lower startup memory use @@ -693,6 +712,126 @@ font: - "abcdefghijklmnopqrstuvwxyz" - "0123456789 .:/-%°()" +#:########################################################################################:# +# NUMBERS: +# Persistent ambient-light and display settings +#:########################################################################################:# +# +# These appear in Home Assistant as configuration entities and are restored +# from flash after reboot. Brightness values are percentages. +# +number: + - platform: template + id: ambient_lux_multiplier + name: "${friendly_name} Ambient Lux Multiplier" + icon: mdi:tune-vertical + entity_category: config + optimistic: true + restore_value: true + initial_value: "${ambient_lux_multiplier_default}" + min_value: 25 + max_value: 1000 + step: 5 + mode: box + + - platform: template + id: display_night_lux_threshold + name: "${friendly_name} Night Lux Threshold" + icon: mdi:weather-night + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_night_lux_threshold_default}" + unit_of_measurement: "lx" + min_value: 0 + max_value: 200 + step: 1 + mode: box + + - platform: template + id: display_day_active_brightness + name: "${friendly_name} Day Touch Brightness" + icon: mdi:brightness-7 + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_day_active_brightness_default}" + unit_of_measurement: "%" + min_value: 5 + max_value: 100 + step: 1 + mode: slider + + - platform: template + id: display_night_active_brightness + name: "${friendly_name} Night Touch Brightness" + icon: mdi:brightness-4 + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_night_active_brightness_default}" + unit_of_measurement: "%" + min_value: 2 + max_value: 80 + step: 1 + mode: slider + + - platform: template + id: display_day_dim_brightness + name: "${friendly_name} Day Dim Brightness" + icon: mdi:brightness-5 + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_day_dim_brightness_default}" + unit_of_measurement: "%" + min_value: 1 + max_value: 60 + step: 1 + mode: slider + + - platform: template + id: display_night_dim_brightness + name: "${friendly_name} Night Dim Brightness" + icon: mdi:brightness-3 + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_night_dim_brightness_default}" + unit_of_measurement: "%" + min_value: 1 + max_value: 40 + step: 1 + mode: slider + + - platform: template + id: display_dim_delay + name: "${friendly_name} Dim Delay" + icon: mdi:timer-sand + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_dim_delay_default}" + unit_of_measurement: "s" + min_value: 10 + max_value: 600 + step: 5 + mode: box + + - platform: template + id: display_clock_delay + name: "${friendly_name} Clock Page Delay" + icon: mdi:timer-outline + entity_category: config + optimistic: true + restore_value: true + initial_value: "${display_clock_delay_default}" + unit_of_measurement: "s" + min_value: 15 + max_value: 900 + step: 5 + mode: box + #:########################################################################################:# # HOME ASSISTANT NUMERIC SENSORS: # https://esphome.io/components/sensor/homeassistant.html @@ -715,10 +854,10 @@ sensor: # The ADC sensor exposes the measured SIG/OUT voltage for calibration. # The illuminance sensor uses the standard TEMT6000 breakout approximation: # - # lux = voltage x 200 + # lux = voltage x configurable multiplier # - # Automatic display-brightness control is intentionally not enabled yet. - # First record representative readings in darkness and normal room lighting. + # Calibration, threshold, brightness and delay values are persistent ESPHome + # number entities and can be adjusted from Home Assistant. - platform: adc id: ambient_light_voltage @@ -745,12 +884,17 @@ sensor: update_interval: 3s lambda: |- const float voltage = id(ambient_light_voltage).state; + float multiplier = id(ambient_lux_multiplier).state; if (isnan(voltage)) { return NAN; } - return voltage * 200.0f; + if (isnan(multiplier) || multiplier <= 0.0f) { + multiplier = 200.0f; + } + + return voltage * multiplier; - platform: homeassistant id: moon_phase @@ -1320,6 +1464,69 @@ globals: # Refresh all state-dependent LVGL button colours #:########################################################################################:# script: + #:######################################################################################:# + # ADAPTIVE DISPLAY BACKLIGHT + #:######################################################################################:# + # + # Brightness is selected only at touch/idle transitions. It is not changed + # continuously as lux moves, preventing feedback from the display backlight. + + - id: apply_active_backlight + mode: restart + then: + - light.turn_on: + id: display_backlight + brightness: !lambda |- + const float lux = id(ambient_light_illuminance).state; + const float threshold = id(display_night_lux_threshold).state; + float brightness_percent; + + if (isnan(lux) || lux <= threshold) { + brightness_percent = + id(display_night_active_brightness).state; + } else { + brightness_percent = + id(display_day_active_brightness).state; + } + + if (isnan(brightness_percent)) { + brightness_percent = 25.0f; + } + + brightness_percent = + std::max(1.0f, std::min(100.0f, brightness_percent)); + + return brightness_percent / 100.0f; + transition_length: 150ms + + - id: apply_dim_backlight + mode: restart + then: + - light.turn_on: + id: display_backlight + brightness: !lambda |- + const float lux = id(ambient_light_illuminance).state; + const float threshold = id(display_night_lux_threshold).state; + float brightness_percent; + + if (isnan(lux) || lux <= threshold) { + brightness_percent = + id(display_night_dim_brightness).state; + } else { + brightness_percent = + id(display_day_dim_brightness).state; + } + + if (isnan(brightness_percent)) { + brightness_percent = 4.0f; + } + + brightness_percent = + std::max(1.0f, std::min(100.0f, brightness_percent)); + + return brightness_percent / 100.0f; + transition_length: 1000ms + #:######################################################################################:# # BOTTOM NAVIGATION LIVE TIME #:######################################################################################:# @@ -4058,22 +4265,36 @@ lvgl: dark_mode: true on_idle: - - timeout: "${clock_idle_timeout}" + # First stage: dim the current page but leave it visible. + - timeout: !lambda |- + const float seconds = id(display_dim_delay).state; + return (uint32_t) ( + (isnan(seconds) ? 30.0f : std::max(10.0f, seconds)) + * 1000.0f + ); + then: + - script.execute: apply_dim_backlight + + # Second stage: show the Clock page and release any camera image. + - timeout: !lambda |- + const float seconds = id(display_clock_delay).state; + return (uint32_t) ( + (isnan(seconds) ? 60.0f : std::max(15.0f, seconds)) + * 1000.0f + ); then: - lambda: |- id(security_page_active) = false; - - online_image.release: security_camera_image - - lvgl.page.show: id: clock_page animation: FADE_IN time: 250ms - - light.turn_on: - id: display_backlight - brightness: "${clock_brightness}" - transition_length: 1500ms + - script.execute: apply_dim_backlight + + - delay: 50ms + - online_image.release: security_camera_image pages: @@ -4749,10 +4970,7 @@ lvgl: animation: MOVE_LEFT time: 220ms - - light.turn_on: - id: display_backlight - brightness: "${control_brightness}" - transition_length: 250ms + - script.execute: apply_active_backlight - script.execute: refresh_vehicle_controls @@ -4791,10 +5009,7 @@ lvgl: animation: NONE time: 0ms - - light.turn_on: - id: display_backlight - brightness: "${control_brightness}" - transition_length: 100ms + - script.execute: apply_active_backlight - script.execute: refresh_security_controls - script.execute: refresh_security_camera_buttons @@ -4830,10 +5045,7 @@ lvgl: animation: FADE_IN time: 250ms - - light.turn_on: - id: display_backlight - brightness: "${clock_brightness}" - transition_length: 1500ms + - script.execute: apply_dim_backlight #:####################################################################################:# # JOBS PAGE @@ -5269,10 +5481,7 @@ lvgl: animation: FADE_IN time: 250ms - - light.turn_on: - id: display_backlight - brightness: "${clock_brightness}" - transition_length: 1500ms + - script.execute: apply_dim_backlight #:####################################################################################:# # VEHICLES PAGE @@ -5952,10 +6161,7 @@ lvgl: animation: FADE_IN time: 250ms - - light.turn_on: - id: display_backlight - brightness: "${clock_brightness}" - transition_length: 1500ms + - script.execute: apply_dim_backlight #:####################################################################################:# # CLIMATE PAGE @@ -6724,10 +6930,7 @@ lvgl: animation: FADE_IN time: 250ms - - light.turn_on: - id: display_backlight - brightness: "${clock_brightness}" - transition_length: 1500ms + - script.execute: apply_dim_backlight #:####################################################################################:# # SECURITY PAGE @@ -7122,10 +7325,7 @@ lvgl: animation: NONE time: 0ms - - light.turn_on: - id: display_backlight - brightness: "${clock_brightness}" - transition_length: 250ms + - script.execute: apply_dim_backlight - delay: 50ms - online_image.release: security_camera_image @@ -7158,10 +7358,7 @@ lvgl: animation: FADE_OUT time: 250ms - - light.turn_on: - id: display_backlight - brightness: "${control_brightness}" - transition_length: 500ms + - script.execute: apply_active_backlight - label: id: clock_time_label