From dc827a322874420dd1eb29db43c5476fdc32f44d Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Sat, 27 Jun 2026 23:23:31 +1200 Subject: [PATCH] Edit esp-entrancebathrmlights.yaml --- esphome/esp-entrancebathrmlights.yaml | 152 +++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 4 deletions(-) diff --git a/esphome/esp-entrancebathrmlights.yaml b/esphome/esp-entrancebathrmlights.yaml index 196200a..3e45c91 100644 --- a/esphome/esp-entrancebathrmlights.yaml +++ b/esphome/esp-entrancebathrmlights.yaml @@ -6,6 +6,7 @@ # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-entrancebathrmlights.yaml #:########################################################################################:# # VERSIONS: +# V4.0 2026-06-27 Added extract fan timer enable switch and minute countdown sensor # V3.9 2026-03-11 Updated yaml to zorruno layout V1.1 # V3.8 2026-02-25 Updated yaml to zorruno layout V1.1 # V3.7 2025-09-34 Upload to this bathroom (as noted in original header) @@ -21,16 +22,29 @@ # - Entrance bathroom switch with 2 outputs: # 1) Main Lights # 2) Extract Fan +# +# - Optional extract fan timer: +# - Controlled by the "Fan Timer Enabled" switch. +# - When enabled, if the light is OFF and the fan is ON, the fan will run for the +# configured fan_timer_duration_minutes, then turn OFF. +# - Countdown starts when the light turns OFF while the fan is ON. +# - Countdown also starts when the fan turns ON while the light is already OFF. +# - Countdown is cancelled/reset if the light turns ON or the fan turns OFF manually. +# - The timer remaining sensor shows whole minutes remaining. +# - When idle, the timer remaining sensor sits at the configured timer duration. #:########################################################################################:# # OFFLINE NOTES: # a) Home Assistant OFFLINE, but Network and MQTT ONLINE # - Local button control continues to toggle relays. +# - Fan timer logic continues locally on the ESP device. # - MQTT/API publishing to HA may not be available until HA returns. # b) MQTT OFFLINE (or broker unreachable) # - Local button control continues to toggle relays. +# - Fan timer logic continues locally on the ESP device. # - MQTT state updates (if any) will not publish. # c) Entire WiFi/Network OFFLINE # - Local button control continues to toggle relays. +# - Fan timer logic continues locally on the ESP device. # - Accurate time is NOT needed (SNTP not needed). #:########################################################################################:# @@ -42,12 +56,12 @@ substitutions: # Device Naming device_name: "esp-entrancebathrmlights" friendly_name: "Entrance Bathroom Lightswitch (2)" - description_comment: "Entrance bathroom lightswitch using a Zemismart KS-811 Double Push Button. Main Lights (1), Extract Fan (2) (Layout V1.1)" + description_comment: "Entrance bathroom lightswitch using a Zemismart KS-811 Double Push Button. Main Lights (1), Extract Fan (2), Fan Timer (Layout V1.1)" device_area: "Entranceway" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. # Project Naming project_name: "Zemismart Technologies.KS-811 Double" - project_version: "v3.9" + project_version: "v4.0" # Passwords & Secrets api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names @@ -66,6 +80,9 @@ substitutions: switch_1_name: "Main Lights" switch_2_name: "Extract Fan" + # Fan Timer Settings + fan_timer_duration_minutes: "10" + #:########################################################################################:# # PACKAGES: Included Common Packages # https://esphome.io/components/packages.html @@ -101,7 +118,7 @@ packages: #### DIAGNOSTICS Sensors #### diag_basic: !include common/include_basic_diag_sensors.yaml - diag_more: !include common/include_more_diag_sensors.yaml + #diag_more: !include common/include_more_diag_sensors.yaml #diag_debug: !include common/include_debug_diag_sensors.yaml #diag_resetcount: !include common/include_resetcount_diag_sensors.yaml @@ -146,6 +163,21 @@ status_led: number: GPIO2 inverted: true +#:########################################################################################:# +# GLOBALS +# https://esphome.io/components/globals.html +#:########################################################################################:# +globals: + - id: fan_timer_counting_down + type: bool + restore_value: false + initial_value: "false" + + - id: fan_timer_remaining_minutes + type: int + restore_value: false + initial_value: "0" + #:########################################################################################:# # BINARY SENSORS # https://esphome.io/components/binary_sensor/ @@ -175,12 +207,124 @@ binary_sensor: # https://esphome.io/components/switch/ #:########################################################################################:# switch: + - platform: template + name: "Fan Timer Enabled" + id: fan_timer_enabled + icon: "mdi:fan-clock" + optimistic: true + restore_mode: RESTORE_DEFAULT_ON + entity_category: config + turn_on_action: + - script.execute: fan_timer_evaluate + turn_off_action: + - script.execute: fan_timer_cancel + - platform: gpio name: "Relay 1: ${switch_1_name}" pin: GPIO13 id: Relay_1 + on_turn_on: + - script.execute: fan_timer_evaluate + on_turn_off: + - script.execute: fan_timer_evaluate - platform: gpio name: "Relay 2: ${switch_2_name}" pin: GPIO12 - id: Relay_2 \ No newline at end of file + id: Relay_2 + on_turn_on: + - script.execute: fan_timer_evaluate + on_turn_off: + - script.execute: fan_timer_evaluate + +#:########################################################################################:# +# SENSORS +# https://esphome.io/components/sensor/ +#:########################################################################################:# +sensor: + - platform: template + name: "Fan Timer Remaining Minutes" + id: fan_timer_remaining_minutes_sensor + icon: "mdi:timer-outline" + unit_of_measurement: "min" + accuracy_decimals: 0 + device_class: duration + update_interval: 60s + lambda: |- + if (id(fan_timer_counting_down)) { + return id(fan_timer_remaining_minutes); + } + + return ${fan_timer_duration_minutes}; + +#:########################################################################################:# +# SCRIPTS +# https://esphome.io/components/script.html +#:########################################################################################:# +script: + - id: fan_timer_cancel + mode: restart + then: + - script.stop: fan_timer_countdown + - lambda: |- + id(fan_timer_counting_down) = false; + id(fan_timer_remaining_minutes) = 0; + - component.update: fan_timer_remaining_minutes_sensor + + - id: fan_timer_evaluate + mode: restart + then: + - if: + condition: + lambda: |- + return id(fan_timer_enabled).state && !id(Relay_1).state && id(Relay_2).state; + then: + - script.execute: fan_timer_countdown + else: + - script.execute: fan_timer_cancel + + - id: fan_timer_countdown + mode: restart + then: + - lambda: |- + id(fan_timer_counting_down) = true; + id(fan_timer_remaining_minutes) = ${fan_timer_duration_minutes}; + - component.update: fan_timer_remaining_minutes_sensor + + - while: + condition: + lambda: |- + return id(fan_timer_counting_down) + && id(fan_timer_enabled).state + && !id(Relay_1).state + && id(Relay_2).state + && id(fan_timer_remaining_minutes) > 0; + then: + - delay: 60s + - if: + condition: + lambda: |- + return id(fan_timer_counting_down) + && id(fan_timer_enabled).state + && !id(Relay_1).state + && id(Relay_2).state + && id(fan_timer_remaining_minutes) > 0; + then: + - lambda: |- + id(fan_timer_remaining_minutes) -= 1; + - component.update: fan_timer_remaining_minutes_sensor + + - if: + condition: + lambda: |- + return id(fan_timer_counting_down) + && id(fan_timer_enabled).state + && !id(Relay_1).state + && id(Relay_2).state + && id(fan_timer_remaining_minutes) <= 0; + then: + - lambda: |- + id(fan_timer_counting_down) = false; + id(fan_timer_remaining_minutes) = 0; + - component.update: fan_timer_remaining_minutes_sensor + - switch.turn_off: Relay_2 \ No newline at end of file