From adb533d6a31093ded23abdb45643f9acce47014a Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Tue, 28 Jul 2026 18:00:54 +1200 Subject: [PATCH] Edit esp-bedside-panel.yaml --- esphome/esp-bedside-panel.yaml | 281 +++++++++++++++++++++++++++++++-- 1 file changed, 266 insertions(+), 15 deletions(-) diff --git a/esphome/esp-bedside-panel.yaml b/esphome/esp-bedside-panel.yaml index b98ef17..88efacc 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.49 2026-07-28 Calibrated battery voltage, added approximate charge level and a subtle Clock-page battery indicator # V1.48 2026-07-28 Added raw onboard battery ADC voltage monitoring on GPIO53 for divider calibration # V1.47 2026-07-20 Widened the alarm volume range while retaining 5-100% controls and the proven 6000 Hz tone # V1.46 2026-07-20 Raised the alarm tone to 6000 Hz, limited volume to 5-100%, and remapped 100% to the previous displayed 30% level @@ -112,6 +113,7 @@ # - 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) +# - Main battery voltage sense: GPIO53 / ADC, calibrated multiplier 1.6667 #:########################################################################################:# # OPERATION NOTES: # - This build returns to the proven V0.8a structure. @@ -158,8 +160,10 @@ # - Beep is a short 6000 Hz electronic tone. # - Alarm repeats a quick 6000 Hz beep-beep-beep group followed by a pause. # - Speaker tests generate audio locally and do not require Home Assistant or media files. -# - Raw battery-sense ADC voltage is exposed from GPIO53 for calibration. -# - No battery divider multiplier or percentage estimate is applied in this test version. +# - GPIO53 exposes both raw ADC voltage and calibrated main-battery voltage. +# - Battery percentage is an approximate voltage-based estimate for a single 18650 cell. +# - The Clock page shows a subtle battery icon and percentage while on battery. +# - When USB power is inferred, the Clock page shows only a full battery icon. #:########################################################################################:# # OFFLINE NOTES: # - LVGL, the clock, touch and backlight operate locally. @@ -178,12 +182,12 @@ substitutions: # Device Naming device_name: "esp-bedside-panel" friendly_name: "ESP Bedside Panel" - description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside panel with four alarms, local speaker tests, battery ADC monitoring and a 24-hour time editor. (Layout V1.1)" + description_comment: "Guition JC1060P470C_I_W_Y ESP32-P4 bedside panel with four alarms, calibrated battery monitoring, Clock-page battery status and 24-hour alarm editing. (Layout V1.1)" device_area: "Bedroom" # Project Naming project_name: "Guition.JC1060P470C_I_W_Y" - project_version: "v1.48" + project_version: "v1.49" # Passwords & Secrets api_key: !secret esp-api_key @@ -203,11 +207,25 @@ substitutions: ambient_light_pin: "GPIO20" ambient_light_update_interval: "1s" - # Battery Voltage Test Settings - # GPIO53 is connected to the board battery-sense circuit. The raw ADC - # voltage is published first so the onboard divider can be calibrated. + # Battery Monitoring Settings + # GPIO53 is connected to the board's main battery-sense divider. + # Testing gave 2.369 V at the ADC for approximately 3.95 V at the cell and + # 2.520 V at the ADC for approximately 4.20 V while charging. This supports + # a divider multiplier of 5/3, or 1.6667. battery_voltage_pin: "GPIO53" battery_voltage_update_interval: "5s" + battery_voltage_multiplier: "1.6667" + + # No dedicated VBUS-status GPIO has been confirmed for this board revision. + # USB presence is therefore inferred from a sustained battery-voltage rise, + # a charge-end voltage, or a clear voltage drop when USB is removed. + battery_usb_detect_rise_threshold: "0.006" + battery_usb_detect_rise_samples: "2" + battery_usb_disconnect_drop_threshold: "0.025" + battery_usb_full_threshold: "4.17" + battery_usb_stable_voltage_threshold: "4.10" + battery_usb_stable_delta_threshold: "0.002" + battery_usb_stable_samples: "4" # Touchscreen Settings touch_sda_pin: "GPIO7" @@ -448,6 +466,7 @@ esphome: - script.execute: refresh_jobs_page - script.execute: refresh_alarm_clock_page - script.execute: refresh_alarm_audio_controls + - script.execute: refresh_clock_battery_indicator #:########################################################################################:# # ESP PLATFORM AND FRAMEWORK: @@ -1108,17 +1127,20 @@ sensor: update_interval: "${update_interval}" #:######################################################################################:# - # INTERNAL BATTERY ADC VOLTAGE # + # MAIN BATTERY MONITORING # #:######################################################################################:# # - # GPIO53 is connected to the JC1060P470 battery-sense circuit. + # GPIO53 is connected to the JC1060P470 battery-sense divider. The raw ADC + # reading remains available for diagnostics. A calibrated copy applies the + # tested 1.6667 multiplier to estimate the actual 18650 terminal voltage. # - # This sensor intentionally reports the raw voltage present at the ADC pin. - # Do not treat it as the actual 18650 voltage until it has been compared with - # a multimeter and the board's voltage-divider ratio has been established. + # Percentage is estimated from a piecewise single-cell Li-ion voltage curve. + # It is intentionally approximate because voltage changes with load, charge + # current, cell age and temperature. # - # The ADC samples every five seconds. A rolling median is published every - # third sample, giving a new filtered value approximately every 15 seconds. + # No confirmed board pin directly reports USB/VBUS presence. The external + # power state is inferred from sustained voltage rise, charge-end voltage and + # the clear voltage drop normally seen when USB power is removed. - platform: adc id: battery_adc_voltage @@ -1130,7 +1152,7 @@ sensor: device_class: voltage state_class: measurement unit_of_measurement: "V" - icon: mdi:battery + icon: mdi:battery-outline entity_category: diagnostic filters: - median: @@ -1138,6 +1160,139 @@ sensor: send_every: 3 send_first_at: 1 + - platform: copy + id: battery_voltage + name: "${friendly_name} Battery Voltage" + source_id: battery_adc_voltage + accuracy_decimals: 2 + device_class: voltage + state_class: measurement + unit_of_measurement: "V" + icon: mdi:battery + filters: + - multiply: "${battery_voltage_multiplier}" + + on_value: + then: + - lambda: |- + if (isnan(x)) { + return; + } + + const float previous = id(battery_previous_voltage); + bool external_power = id(battery_external_power_present); + + if (!isnan(previous)) { + const float delta = x - previous; + + if (delta <= -${battery_usb_disconnect_drop_threshold}f) { + // A clear downward step is the most dependable indication + // that USB power has just been removed. + external_power = false; + id(battery_voltage_rise_samples) = 0; + id(battery_voltage_stable_samples) = 0; + } else if (delta >= ${battery_usb_detect_rise_threshold}f) { + // Require more than one rising sample so normal ADC noise does + // not make the Clock page claim that USB is connected. + if (id(battery_voltage_rise_samples) < 255) { + id(battery_voltage_rise_samples)++; + } + id(battery_voltage_stable_samples) = 0; + + if ( + id(battery_voltage_rise_samples) >= + ${battery_usb_detect_rise_samples} + ) { + external_power = true; + } + } else { + id(battery_voltage_rise_samples) = 0; + + // If a previously inferred charging state becomes flat at a + // voltage well below charge-end, release it after four samples. + // This is mainly a recovery path for a missed unplug transition. + if ( + external_power && + x < ${battery_usb_stable_voltage_threshold}f && + fabsf(delta) <= ${battery_usb_stable_delta_threshold}f + ) { + if (id(battery_voltage_stable_samples) < 255) { + id(battery_voltage_stable_samples)++; + } + + if ( + id(battery_voltage_stable_samples) >= + ${battery_usb_stable_samples} + ) { + external_power = false; + id(battery_voltage_stable_samples) = 0; + } + } else { + id(battery_voltage_stable_samples) = 0; + } + } + } + + // Near charge-end, show the full icon. A freshly charged cell that + // is booted directly on battery can also meet this threshold, which + // is harmless because the cell is genuinely full at that point. + if (x >= ${battery_usb_full_threshold}f) { + external_power = true; + } + + id(battery_previous_voltage) = x; + id(battery_external_power_present) = external_power; + id(battery_external_power).publish_state(external_power); + + // Piecewise voltage-to-percentage approximation for a single + // 4.20 V Li-ion cell. Interpolate between the surrounding points. + static const float voltage_points[] = { + 3.30f, 3.50f, 3.60f, 3.70f, 3.75f, 3.80f, 3.85f, + 3.90f, 3.95f, 4.00f, 4.05f, 4.10f, 4.15f, 4.20f + }; + static const float percentage_points[] = { + 0.0f, 5.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, + 60.0f, 70.0f, 80.0f, 88.0f, 94.0f, 98.0f, 100.0f + }; + constexpr size_t point_count = + sizeof(voltage_points) / sizeof(voltage_points[0]); + + float percentage = 0.0f; + + if (x <= voltage_points[0]) { + percentage = percentage_points[0]; + } else if (x >= voltage_points[point_count - 1]) { + percentage = percentage_points[point_count - 1]; + } else { + for (size_t i = 1; i < point_count; i++) { + if (x <= voltage_points[i]) { + const float position = + (x - voltage_points[i - 1]) / + (voltage_points[i] - voltage_points[i - 1]); + percentage = + percentage_points[i - 1] + + position * + (percentage_points[i] - percentage_points[i - 1]); + break; + } + } + } + + percentage = fminf(fmaxf(percentage, 0.0f), 100.0f); + id(battery_level_percent).publish_state(percentage); + + - script.execute: refresh_clock_battery_indicator + + - platform: template + id: battery_level_percent + name: "${friendly_name} Battery Level" + device_class: battery + state_class: measurement + unit_of_measurement: "%" + accuracy_decimals: 0 + icon: mdi:battery + update_interval: never + #:######################################################################################:# # TEMT6000 AMBIENT LIGHT SENSOR #:######################################################################################:# @@ -1501,6 +1656,16 @@ sensor: # Imported states used to colour LVGL buttons #:########################################################################################:# binary_sensor: + #:######################################################################################:# + # BATTERY / EXTERNAL POWER STATUS # + #:######################################################################################:# + - platform: template + id: battery_external_power + name: "${friendly_name} USB Power Estimated" + device_class: plug + icon: mdi:power-plug + entity_category: diagnostic + #:######################################################################################:# # ATTO 3 BINARY STATES #:######################################################################################:# @@ -1759,6 +1924,29 @@ globals: restore_value: false initial_value: "1" + #:######################################################################################:# + # MAIN BATTERY STATUS # + #:######################################################################################:# + - id: battery_previous_voltage + type: float + restore_value: false + initial_value: "NAN" + + - id: battery_external_power_present + type: bool + restore_value: false + initial_value: "false" + + - id: battery_voltage_rise_samples + type: uint8_t + restore_value: false + initial_value: "0" + + - id: battery_voltage_stable_samples + type: uint8_t + restore_value: false + initial_value: "0" + #:######################################################################################:# # ALARM CLOCK SETTINGS #:######################################################################################:# @@ -1836,6 +2024,44 @@ globals: # Refresh all state-dependent LVGL button colours #:########################################################################################:# script: + #:######################################################################################:# + # CLOCK PAGE BATTERY INDICATOR # + #:######################################################################################:# + - id: refresh_clock_battery_indicator + mode: restart + then: + - if: + condition: + lambda: |- + return id(lvgl_ready); + + then: + - lvgl.label.update: + id: clock_battery_icon_label + text: "\U000F0079" + + - lvgl.label.update: + id: clock_battery_percent_label + text: !lambda |- + if (id(battery_external_power_present)) { + return std::string(""); + } + + const float percentage = id(battery_level_percent).state; + + if (isnan(percentage)) { + return std::string("--%"); + } + + char buffer[12]; + snprintf( + buffer, + sizeof(buffer), + "%.0f%%", + percentage + ); + return std::string(buffer); + #:######################################################################################:# # ALARM CLOCK PAGE STATE REFRESH #:######################################################################################:# @@ -11678,6 +11904,31 @@ lvgl: text_color: 0xE4DDD2 clickable: false + # Subtle battery status in the top-right corner. + # On battery: icon plus approximate percentage. + # With inferred USB power: full battery icon only. + - label: + id: clock_battery_icon_label + x: 912 + y: 20 + width: 30 + text: "\U000F0079" + text_font: font_mdi_vehicle + text_align: CENTER + text_color: 0x686D73 + clickable: false + + - label: + id: clock_battery_percent_label + x: 944 + y: 24 + width: 62 + text: "--%" + text_font: font_small + text_align: LEFT + text_color: 0x686D73 + clickable: false + - label: id: clock_date_label align: TOP_MID