#:########################################################################################:# # TITLE: FRONT ENTRANCEWAY LED LIGHT CONTROLS # zorruno.com layout v1.1 2026 #:########################################################################################:# # REPO: # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-entranceoutsideledlights.yaml #:########################################################################################:# # VERSIONS: # V1.2 2026-03-10 Added per-channel maximum ON timers with auto-dim-to-off, restart-on-ON, and moved defaults into substitutions # V1.1 2026-03-10 Updated yaml to zorruno layout V1.1 # V1.0 2026-02-23 Initial version (converted from Tasmota) #:########################################################################################:# # HARDWARE: # - H801 (ESP8266EX) 5-channel PWM MOSFET LED controller #:########################################################################################:# # OPERATION NOTES: # - H801 PWM outputs (typical / ESPHome Devices): # PWM1 / R -> GPIO15 # PWM2 / G -> GPIO13 # PWM3 / B -> GPIO12 # PWM4 / W1 -> GPIO14 # PWM5 / W2 -> GPIO4 # Some templates swap G/B labeling (GPIO12 vs GPIO13). If outputs appear swapped, # just swap out2_pwm_pin and out3_pwm_pin below. # # - We expose helper entities per channel: # * On/Off switch (ramps using per-channel ramp time) # * Brightness slider 0-100% normalized to the per-channel hard limit # * Ramp time (seconds) for a full 0 -> hard limit transition # * Hard limit (percent) absolute cap for that channel # * Maximum ON time (minutes) after which the channel dims to OFF # # - A fresh ON command restarts the countdown timer for that channel. # - Setting channel brightness above 0 also restarts that channel timer. # # - Underlying PWM light entities are INTERNAL so HA cannot bypass the hard limit. #:########################################################################################:# # MQTT COMMANDS: # - None (no device-specific MQTT commands in this YAML; common_mqtt is included for standardisation) #:########################################################################################:# # OFFLINE NOTES: # a) Home Assistant OFFLINE, but Network and MQTT ONLINE # - Local PWM control continues via HA entities if HA is offline (entities unavailable until HA returns) # - Device continues running with last commanded PWM states # - Any already-running max ON timer continues locally on the device # b) MQTT OFFLINE (but WiFi/Network and HA API ONLINE) # - No impact unless you rely on MQTT for diagnostics; local control via HA/API continues # - Any already-running max ON timer continues locally on the device # c) Entire WiFi/Network OFFLINE # - No HA API and no MQTT connectivity # - Outputs remain in their last driven state (until power loss/reboot) # - Any already-running max ON timer continues locally on the device # - Accurate time is NOT needed (SNTP not required for operation) #:########################################################################################:# #:########################################################################################:# # SUBSTITUTIONS: Specific device variable substitutions # If NOT using a secrets file, just replace these with the passwords etc (in quotes) #:########################################################################################:# substitutions: # Device Naming device_name: "esp-entranceoutsideledlights" friendly_name: "Front Entranceway LED Light controls" description_comment: "H801 ESP8266 5-channel PWM LED controller (Entranceway) (Layout V1.1)" device_area: "Entranceway" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. # Project Naming project_name: "H801.5CH PWM LED Controller" project_version: "v1.2" # Passwords & Secrets api_key: !secret esp-api_key ota_pass: !secret esp-ota_pass static_ip_address: !secret esp-entranceoutsideledlights_ip # unfortunately you can't use substitutions inside secrets names # If we are changing IP addresses, you must update the current IP address here, otherwise it remains # Don't forget to switch it back when changed. current_ip_address: ${static_ip_address} # Device Settings log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG, VERBOSE, VERY_VERBOSE update_interval: "60s" # OUTPUT NAMES out1_name: "Main Entrance Outside LED Lights" out2_name: "Number 16 LED Light" out3_name: "Spare 3" out4_name: "Doorbell Illumination" out5_name: "Spare 5" # OUTPUT DEFAULT SETTINGS out1_ramp_default_s: "2.0" out2_ramp_default_s: "2.0" out3_ramp_default_s: "2.0" out4_ramp_default_s: "2.0" out5_ramp_default_s: "2.0" out1_max_limit_default_pct: "100" out2_max_limit_default_pct: "100" out3_max_limit_default_pct: "100" out4_max_limit_default_pct: "100" out5_max_limit_default_pct: "100" out1_max_on_time_default_mins: "240" out2_max_on_time_default_mins: "240" out3_max_on_time_default_mins: "10" out4_max_on_time_default_mins: "4" out5_max_on_time_default_mins: "10" # H801 PWM PINS (easy swap here if needed) out1_pwm_pin: "GPIO15" out2_pwm_pin: "GPIO13" out3_pwm_pin: "GPIO12" out4_pwm_pin: "GPIO14" out5_pwm_pin: "GPIO4" pwm_frequency: "1000 Hz" #:########################################################################################:# # PACKAGES: Included Common Packages # https://esphome.io/components/packages.html #:########################################################################################:# packages: #### WIFI, Network (Static/DHCP/IPV6 etc), Fallback AP, Safemode #### common_wifi: !include file: common/network_common.yaml vars: local_device_name: "${device_name}" local_static_ip_address: "${static_ip_address}" local_ota_pass: "${ota_pass}" local_current_ip_address: "${current_ip_address}" #### HOME ASSISTANT API (choose encryption or no encryption options) #### common_api: !include file: common/api_common.yaml #file: common/api_common_noencryption.yaml vars: local_api_key: "${api_key}" #### MQTT #### common_mqtt: !include file: common/mqtt_common.yaml vars: local_device_name: "${device_name}" #### SNTP (Only use if you want/need accurate timeclocks) #### common_sntp: !include common/sntp_common.yaml #### DIAGNOSTICS Sensors #### diag_basic: !include common/include_basic_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 #:########################################################################################:# # ESPHOME: # https://esphome.io/components/esphome.html #:########################################################################################:# esphome: name: "${device_name}" friendly_name: "${friendly_name}" comment: "${description_comment}" # appears on the esphome page in HA min_version: 2025.11.0 area: "${device_area}" project: name: "${project_name}" version: "${project_version}" #:########################################################################################:# # ESP PLATFORM AND FRAMEWORK: # https://esphome.io/components/esp8266.html #:########################################################################################:# esp8266: board: esp01_1m #:########################################################################################:# # LOGGING: # https://esphome.io/components/logger.html #:########################################################################################:# logger: level: "${log_level}" baud_rate: 0 # set to 0 for no logging via UART #:########################################################################################:# # OUTPUTS: H801 PWM MOSFET CHANNELS # https://esphome.io/components/output/esp8266_pwm.html #:########################################################################################:# output: - platform: esp8266_pwm id: out1_pwm pin: ${out1_pwm_pin} frequency: ${pwm_frequency} - platform: esp8266_pwm id: out2_pwm pin: ${out2_pwm_pin} frequency: ${pwm_frequency} - platform: esp8266_pwm id: out3_pwm pin: ${out3_pwm_pin} frequency: ${pwm_frequency} - platform: esp8266_pwm id: out4_pwm pin: ${out4_pwm_pin} frequency: ${pwm_frequency} - platform: esp8266_pwm id: out5_pwm pin: ${out5_pwm_pin} frequency: ${pwm_frequency} #:########################################################################################:# # LIGHTS: Internal raw PWM lights (not exposed to HA) # https://esphome.io/components/light/monochromatic.html #:########################################################################################:# light: - platform: monochromatic id: out1_light_raw name: "${out1_name} (RAW)" output: out1_pwm internal: true restore_mode: RESTORE_DEFAULT_OFF - platform: monochromatic id: out2_light_raw name: "${out2_name} (RAW)" output: out2_pwm internal: true restore_mode: RESTORE_DEFAULT_OFF - platform: monochromatic id: out3_light_raw name: "${out3_name} (RAW)" output: out3_pwm internal: true restore_mode: RESTORE_DEFAULT_OFF - platform: monochromatic id: out4_light_raw name: "${out4_name} (RAW)" output: out4_pwm internal: true restore_mode: RESTORE_DEFAULT_OFF - platform: monochromatic id: out5_light_raw name: "${out5_name} (RAW)" output: out5_pwm internal: true restore_mode: RESTORE_DEFAULT_OFF #:########################################################################################:# # GLOBALS: Store "last non-zero" brightness per channel # https://esphome.io/components/globals.html #:########################################################################################:# globals: - id: out1_last_nonzero_pct type: float restore_value: true initial_value: "100.0" - id: out2_last_nonzero_pct type: float restore_value: true initial_value: "100.0" - id: out3_last_nonzero_pct type: float restore_value: true initial_value: "100.0" - id: out4_last_nonzero_pct type: float restore_value: true initial_value: "100.0" - id: out5_last_nonzero_pct type: float restore_value: true initial_value: "100.0" #:########################################################################################:# # SCRIPTS: Apply normalized brightness + proportional ramping + per-channel auto-off timers # https://esphome.io/components/script.html #:########################################################################################:# script: ############################################# # OUT1 ############################################# - id: out1_apply_target mode: restart then: - lambda: |- const float slider_pct = id(out1_brightness_pct).state; const float limit_pct = id(out1_max_limit_pct).state; const float ramp_s = id(out1_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float slider_frac = slider_pct / 100.0f; if (slider_frac < 0.0f) slider_frac = 0.0f; if (slider_frac > 1.0f) slider_frac = 1.0f; float target = slider_frac * limit_frac; float current = 0.0f; if (id(out1_light_raw).current_values.is_on()) { current = id(out1_light_raw).current_values.get_brightness(); } float delta = target - current; if (delta < 0.0f) delta = -delta; float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (delta / full_scale)); if (trans_ms > 600000) trans_ms = 600000; if (target <= 0.0001f) { auto call = id(out1_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); } else { auto call = id(out1_light_raw).turn_on(); call.set_brightness(target); call.set_transition_length(trans_ms); call.perform(); } - id: out1_turn_off mode: restart then: - lambda: |- const float limit_pct = id(out1_max_limit_pct).state; const float ramp_s = id(out1_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float current = 0.0f; if (id(out1_light_raw).current_values.is_on()) { current = id(out1_light_raw).current_values.get_brightness(); } float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (current / full_scale)); if (trans_ms > 600000) trans_ms = 600000; auto call = id(out1_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); - id: out1_auto_off_timer mode: restart parameters: delay_ms: int then: - delay: !lambda "return delay_ms;" - script.execute: out1_turn_off ############################################# # OUT2 ############################################# - id: out2_apply_target mode: restart then: - lambda: |- const float slider_pct = id(out2_brightness_pct).state; const float limit_pct = id(out2_max_limit_pct).state; const float ramp_s = id(out2_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float slider_frac = slider_pct / 100.0f; if (slider_frac < 0.0f) slider_frac = 0.0f; if (slider_frac > 1.0f) slider_frac = 1.0f; float target = slider_frac * limit_frac; float current = 0.0f; if (id(out2_light_raw).current_values.is_on()) { current = id(out2_light_raw).current_values.get_brightness(); } float delta = target - current; if (delta < 0.0f) delta = -delta; float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (delta / full_scale)); if (trans_ms > 600000) trans_ms = 600000; if (target <= 0.0001f) { auto call = id(out2_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); } else { auto call = id(out2_light_raw).turn_on(); call.set_brightness(target); call.set_transition_length(trans_ms); call.perform(); } - id: out2_turn_off mode: restart then: - lambda: |- const float limit_pct = id(out2_max_limit_pct).state; const float ramp_s = id(out2_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float current = 0.0f; if (id(out2_light_raw).current_values.is_on()) { current = id(out2_light_raw).current_values.get_brightness(); } float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (current / full_scale)); if (trans_ms > 600000) trans_ms = 600000; auto call = id(out2_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); - id: out2_auto_off_timer mode: restart parameters: delay_ms: int then: - delay: !lambda "return delay_ms;" - script.execute: out2_turn_off ############################################# # OUT3 ############################################# - id: out3_apply_target mode: restart then: - lambda: |- const float slider_pct = id(out3_brightness_pct).state; const float limit_pct = id(out3_max_limit_pct).state; const float ramp_s = id(out3_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float slider_frac = slider_pct / 100.0f; if (slider_frac < 0.0f) slider_frac = 0.0f; if (slider_frac > 1.0f) slider_frac = 1.0f; float target = slider_frac * limit_frac; float current = 0.0f; if (id(out3_light_raw).current_values.is_on()) { current = id(out3_light_raw).current_values.get_brightness(); } float delta = target - current; if (delta < 0.0f) delta = -delta; float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (delta / full_scale)); if (trans_ms > 600000) trans_ms = 600000; if (target <= 0.0001f) { auto call = id(out3_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); } else { auto call = id(out3_light_raw).turn_on(); call.set_brightness(target); call.set_transition_length(trans_ms); call.perform(); } - id: out3_turn_off mode: restart then: - lambda: |- const float limit_pct = id(out3_max_limit_pct).state; const float ramp_s = id(out3_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float current = 0.0f; if (id(out3_light_raw).current_values.is_on()) { current = id(out3_light_raw).current_values.get_brightness(); } float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (current / full_scale)); if (trans_ms > 600000) trans_ms = 600000; auto call = id(out3_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); - id: out3_auto_off_timer mode: restart parameters: delay_ms: int then: - delay: !lambda "return delay_ms;" - script.execute: out3_turn_off ############################################# # OUT4 ############################################# - id: out4_apply_target mode: restart then: - lambda: |- const float slider_pct = id(out4_brightness_pct).state; const float limit_pct = id(out4_max_limit_pct).state; const float ramp_s = id(out4_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float slider_frac = slider_pct / 100.0f; if (slider_frac < 0.0f) slider_frac = 0.0f; if (slider_frac > 1.0f) slider_frac = 1.0f; float target = slider_frac * limit_frac; float current = 0.0f; if (id(out4_light_raw).current_values.is_on()) { current = id(out4_light_raw).current_values.get_brightness(); } float delta = target - current; if (delta < 0.0f) delta = -delta; float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (delta / full_scale)); if (trans_ms > 600000) trans_ms = 600000; if (target <= 0.0001f) { auto call = id(out4_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); } else { auto call = id(out4_light_raw).turn_on(); call.set_brightness(target); call.set_transition_length(trans_ms); call.perform(); } - id: out4_turn_off mode: restart then: - lambda: |- const float limit_pct = id(out4_max_limit_pct).state; const float ramp_s = id(out4_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float current = 0.0f; if (id(out4_light_raw).current_values.is_on()) { current = id(out4_light_raw).current_values.get_brightness(); } float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (current / full_scale)); if (trans_ms > 600000) trans_ms = 600000; auto call = id(out4_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); - id: out4_auto_off_timer mode: restart parameters: delay_ms: int then: - delay: !lambda "return delay_ms;" - script.execute: out4_turn_off ############################################# # OUT5 ############################################# - id: out5_apply_target mode: restart then: - lambda: |- const float slider_pct = id(out5_brightness_pct).state; const float limit_pct = id(out5_max_limit_pct).state; const float ramp_s = id(out5_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float slider_frac = slider_pct / 100.0f; if (slider_frac < 0.0f) slider_frac = 0.0f; if (slider_frac > 1.0f) slider_frac = 1.0f; float target = slider_frac * limit_frac; float current = 0.0f; if (id(out5_light_raw).current_values.is_on()) { current = id(out5_light_raw).current_values.get_brightness(); } float delta = target - current; if (delta < 0.0f) delta = -delta; float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (delta / full_scale)); if (trans_ms > 600000) trans_ms = 600000; if (target <= 0.0001f) { auto call = id(out5_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); } else { auto call = id(out5_light_raw).turn_on(); call.set_brightness(target); call.set_transition_length(trans_ms); call.perform(); } - id: out5_turn_off mode: restart then: - lambda: |- const float limit_pct = id(out5_max_limit_pct).state; const float ramp_s = id(out5_ramp_s).state; float limit_frac = limit_pct / 100.0f; if (limit_frac < 0.0f) limit_frac = 0.0f; if (limit_frac > 1.0f) limit_frac = 1.0f; float current = 0.0f; if (id(out5_light_raw).current_values.is_on()) { current = id(out5_light_raw).current_values.get_brightness(); } float full_scale = limit_frac; if (current > full_scale) full_scale = current; if (full_scale < 0.0001f) full_scale = 0.0001f; uint32_t trans_ms = (uint32_t) (ramp_s * 1000.0f * (current / full_scale)); if (trans_ms > 600000) trans_ms = 600000; auto call = id(out5_light_raw).turn_off(); call.set_transition_length(trans_ms); call.perform(); - id: out5_auto_off_timer mode: restart parameters: delay_ms: int then: - delay: !lambda "return delay_ms;" - script.execute: out5_turn_off #:########################################################################################:# # NUMBER: Brightness normalized to limit, plus per-channel ramp, hard limit, and max ON time # https://esphome.io/components/number/template.html #:########################################################################################:# number: ############################################# # OUT1 ############################################# - platform: template id: out1_ramp_s name: "${out1_name} Ramp" icon: mdi:timer-outline entity_category: config unit_of_measurement: "s" min_value: 0.0 max_value: 30.0 step: 0.1 restore_value: true initial_value: ${out1_ramp_default_s} optimistic: true set_action: - script.execute: out1_apply_target - platform: template id: out1_max_limit_pct name: "${out1_name} Max Limit" icon: mdi:brightness-percent entity_category: config unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: ${out1_max_limit_default_pct} optimistic: true set_action: - script.execute: out1_apply_target - platform: template id: out1_auto_off_mins name: "${out1_name} Max ON Time" icon: mdi:timer-lock-outline entity_category: config unit_of_measurement: "min" min_value: 1 max_value: 720 step: 1 restore_value: true initial_value: ${out1_max_on_time_default_mins} optimistic: true - platform: template id: out1_brightness_pct name: "${out1_name} Brightness" icon: mdi:lightbulb-on-outline unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: 100 optimistic: true set_action: - lambda: |- if (x > 0.0f) id(out1_last_nonzero_pct) = x; - script.execute: out1_apply_target - if: condition: lambda: |- return x > 0.0f; then: - script.execute: id: out1_auto_off_timer delay_ms: !lambda "return (int) (id(out1_auto_off_mins).state * 60000.0f);" else: - script.stop: out1_auto_off_timer ############################################# # OUT2 ############################################# - platform: template id: out2_ramp_s name: "${out2_name} Ramp" icon: mdi:timer-outline entity_category: config unit_of_measurement: "s" min_value: 0.0 max_value: 30.0 step: 0.1 restore_value: true initial_value: ${out2_ramp_default_s} optimistic: true set_action: - script.execute: out2_apply_target - platform: template id: out2_max_limit_pct name: "${out2_name} Max Limit" icon: mdi:brightness-percent entity_category: config unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: ${out2_max_limit_default_pct} optimistic: true set_action: - script.execute: out2_apply_target - platform: template id: out2_auto_off_mins name: "${out2_name} Max ON Time" icon: mdi:timer-lock-outline entity_category: config unit_of_measurement: "min" min_value: 1 max_value: 720 step: 1 restore_value: true initial_value: ${out2_max_on_time_default_mins} optimistic: true - platform: template id: out2_brightness_pct name: "${out2_name} Brightness" icon: mdi:lightbulb-on-outline unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: 100 optimistic: true set_action: - lambda: |- if (x > 0.0f) id(out2_last_nonzero_pct) = x; - script.execute: out2_apply_target - if: condition: lambda: |- return x > 0.0f; then: - script.execute: id: out2_auto_off_timer delay_ms: !lambda "return (int) (id(out2_auto_off_mins).state * 60000.0f);" else: - script.stop: out2_auto_off_timer ############################################# # OUT3 ############################################# - platform: template id: out3_ramp_s name: "${out3_name} Ramp" icon: mdi:timer-outline entity_category: config unit_of_measurement: "s" min_value: 0.0 max_value: 30.0 step: 0.1 restore_value: true initial_value: ${out3_ramp_default_s} optimistic: true set_action: - script.execute: out3_apply_target - platform: template id: out3_max_limit_pct name: "${out3_name} Max Limit" icon: mdi:brightness-percent entity_category: config unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: ${out3_max_limit_default_pct} optimistic: true set_action: - script.execute: out3_apply_target - platform: template id: out3_auto_off_mins name: "${out3_name} Max ON Time" icon: mdi:timer-lock-outline entity_category: config unit_of_measurement: "min" min_value: 1 max_value: 720 step: 1 restore_value: true initial_value: ${out3_max_on_time_default_mins} optimistic: true - platform: template id: out3_brightness_pct name: "${out3_name} Brightness" icon: mdi:lightbulb-on-outline unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: 100 optimistic: true set_action: - lambda: |- if (x > 0.0f) id(out3_last_nonzero_pct) = x; - script.execute: out3_apply_target - if: condition: lambda: |- return x > 0.0f; then: - script.execute: id: out3_auto_off_timer delay_ms: !lambda "return (int) (id(out3_auto_off_mins).state * 60000.0f);" else: - script.stop: out3_auto_off_timer ############################################# # OUT4 ############################################# - platform: template id: out4_ramp_s name: "${out4_name} Ramp" icon: mdi:timer-outline entity_category: config unit_of_measurement: "s" min_value: 0.0 max_value: 30.0 step: 0.1 restore_value: true initial_value: ${out4_ramp_default_s} optimistic: true set_action: - script.execute: out4_apply_target - platform: template id: out4_max_limit_pct name: "${out4_name} Max Limit" icon: mdi:brightness-percent entity_category: config unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: ${out4_max_limit_default_pct} optimistic: true set_action: - script.execute: out4_apply_target - platform: template id: out4_auto_off_mins name: "${out4_name} Max ON Time" icon: mdi:timer-lock-outline entity_category: config unit_of_measurement: "min" min_value: 1 max_value: 720 step: 1 restore_value: true initial_value: ${out4_max_on_time_default_mins} optimistic: true - platform: template id: out4_brightness_pct name: "${out4_name} Brightness" icon: mdi:lightbulb-on-outline unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: 100 optimistic: true set_action: - lambda: |- if (x > 0.0f) id(out4_last_nonzero_pct) = x; - script.execute: out4_apply_target - if: condition: lambda: |- return x > 0.0f; then: - script.execute: id: out4_auto_off_timer delay_ms: !lambda "return (int) (id(out4_auto_off_mins).state * 60000.0f);" else: - script.stop: out4_auto_off_timer ############################################# # OUT5 ############################################# - platform: template id: out5_ramp_s name: "${out5_name} Ramp" icon: mdi:timer-outline entity_category: config unit_of_measurement: "s" min_value: 0.0 max_value: 30.0 step: 0.1 restore_value: true initial_value: ${out5_ramp_default_s} optimistic: true set_action: - script.execute: out5_apply_target - platform: template id: out5_max_limit_pct name: "${out5_name} Max Limit" icon: mdi:brightness-percent entity_category: config unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: ${out5_max_limit_default_pct} optimistic: true set_action: - script.execute: out5_apply_target - platform: template id: out5_auto_off_mins name: "${out5_name} Max ON Time" icon: mdi:timer-lock-outline entity_category: config unit_of_measurement: "min" min_value: 1 max_value: 720 step: 1 restore_value: true initial_value: ${out5_max_on_time_default_mins} optimistic: true - platform: template id: out5_brightness_pct name: "${out5_name} Brightness" icon: mdi:lightbulb-on-outline unit_of_measurement: "%" min_value: 0 max_value: 100 step: 1 restore_value: true initial_value: 100 optimistic: true set_action: - lambda: |- if (x > 0.0f) id(out5_last_nonzero_pct) = x; - script.execute: out5_apply_target - if: condition: lambda: |- return x > 0.0f; then: - script.execute: id: out5_auto_off_timer delay_ms: !lambda "return (int) (id(out5_auto_off_mins).state * 60000.0f);" else: - script.stop: out5_auto_off_timer #:########################################################################################:# # SWITCH: On/Off that respects ramping; brightness slider remains unchanged when off # https://esphome.io/components/switch/template.html #:########################################################################################:# switch: - platform: template id: out1_switch name: "${out1_name}" icon: mdi:lightbulb lambda: |- return id(out1_light_raw).current_values.is_on(); turn_on_action: - if: condition: lambda: |- return id(out1_brightness_pct).state <= 0.0f; then: - number.set: id: out1_brightness_pct value: !lambda "return id(out1_last_nonzero_pct);" else: - script.execute: out1_apply_target - script.execute: id: out1_auto_off_timer delay_ms: !lambda "return (int) (id(out1_auto_off_mins).state * 60000.0f);" turn_off_action: - script.stop: out1_auto_off_timer - script.execute: out1_turn_off - platform: template id: out2_switch name: "${out2_name}" icon: mdi:lightbulb lambda: |- return id(out2_light_raw).current_values.is_on(); turn_on_action: - if: condition: lambda: |- return id(out2_brightness_pct).state <= 0.0f; then: - number.set: id: out2_brightness_pct value: !lambda "return id(out2_last_nonzero_pct);" else: - script.execute: out2_apply_target - script.execute: id: out2_auto_off_timer delay_ms: !lambda "return (int) (id(out2_auto_off_mins).state * 60000.0f);" turn_off_action: - script.stop: out2_auto_off_timer - script.execute: out2_turn_off - platform: template id: out3_switch name: "${out3_name}" icon: mdi:lightbulb lambda: |- return id(out3_light_raw).current_values.is_on(); turn_on_action: - if: condition: lambda: |- return id(out3_brightness_pct).state <= 0.0f; then: - number.set: id: out3_brightness_pct value: !lambda "return id(out3_last_nonzero_pct);" else: - script.execute: out3_apply_target - script.execute: id: out3_auto_off_timer delay_ms: !lambda "return (int) (id(out3_auto_off_mins).state * 60000.0f);" turn_off_action: - script.stop: out3_auto_off_timer - script.execute: out3_turn_off - platform: template id: out4_switch name: "${out4_name}" icon: mdi:lightbulb lambda: |- return id(out4_light_raw).current_values.is_on(); turn_on_action: - if: condition: lambda: |- return id(out4_brightness_pct).state <= 0.0f; then: - number.set: id: out4_brightness_pct value: !lambda "return id(out4_last_nonzero_pct);" else: - script.execute: out4_apply_target - script.execute: id: out4_auto_off_timer delay_ms: !lambda "return (int) (id(out4_auto_off_mins).state * 60000.0f);" turn_off_action: - script.stop: out4_auto_off_timer - script.execute: out4_turn_off - platform: template id: out5_switch name: "${out5_name}" icon: mdi:lightbulb lambda: |- return id(out5_light_raw).current_values.is_on(); turn_on_action: - if: condition: lambda: |- return id(out5_brightness_pct).state <= 0.0f; then: - number.set: id: out5_brightness_pct value: !lambda "return id(out5_last_nonzero_pct);" else: - script.execute: out5_apply_target - script.execute: id: out5_auto_off_timer delay_ms: !lambda "return (int) (id(out5_auto_off_mins).state * 60000.0f);" turn_off_action: - script.stop: out5_auto_off_timer - script.execute: out5_turn_off