From b571aff3266303ce03d534eefb246754179ba711 Mon Sep 17 00:00:00 2001 From: ESPHome Device Builder Date: Sat, 25 Jul 2026 12:53:51 +1200 Subject: [PATCH] Edit esp-officelights.yaml --- esphome/esp-officelights.yaml | 442 ++++++++++++++++++++++------------ 1 file changed, 288 insertions(+), 154 deletions(-) diff --git a/esphome/esp-officelights.yaml b/esphome/esp-officelights.yaml index b500b55..e780b0c 100644 --- a/esphome/esp-officelights.yaml +++ b/esphome/esp-officelights.yaml @@ -6,6 +6,10 @@ # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-officelights.yaml #:########################################################################################:# # VERSIONS: +# v3.0 2026-07-25 Refactored MQTT sync to use template HA switch actions; physical button and +# HA control now publish command topics to officeduallights; relay callbacks +# publish status only to prevent command ping-pong/chattering. 2-way sync with +# esp-officeduallights for offline HA-independent operation. # v2.1 2026-02-25 Updated yaml to zorruno layout V1.1 # v2.0 2025-06-05 YAML Tidyups # v1.0 2025-05-31 Initial Version @@ -16,26 +20,50 @@ #:########################################################################################:# # OPERATION NOTES: # - Office 3-gang KS-811 (Triple) wall switch. -# - Button 1 toggles Relay 1 (Nighttime Lights) - only relay actually wired to a load. -# - Button 2 toggles Relay 2 (Daytime Lights) - virtual only (no physical load connected). -# - Button 3 toggles Relay 3 (Spare) - virtual only (no physical load connected). -# - GPIO16 does not support interrupts; polling is used for Button 1. +# - Button 1 (top) toggles Bunker Lights (both local Relay 1 physical bunker + remote dual light_1 +# Right Bunker via MQTT). Local Relay 1 is a physical load; remote is the Sonoff Dual R1. +# - Button 2 (middle) toggles Overhead Lights (remote dual light_2 via MQTT). Local Relay 2 is +# a virtual mirror of the remote state only (no physical load connected). +# - Button 3 (bottom) is spare - no function assigned. +# - Home Assistant control of the exposed template switches also toggles the matching groups. +# - MQTT command topics request On/Off changes. +# - Relay state changes publish retained status only. +# - Relays never publish command topics directly. This prevents command ping-pong/chattering. +# - GPIO16 on ESP8266 does not support normal GPIO interrupts, so Button 1 uses polling mode. +#:########################################################################################:# +# MQTT COMMANDS: +# Bunker Lights (Button 1): +# - Command topic: ${mqtt_this_device_command_topic_bunker} +# Payloads: ${mqtt_command_on}, ${mqtt_command_off} +# - Status topic: ${mqtt_this_device_status_topic_bunker} +# Payloads mirror the actual bunker relay state and are published retained. +# +# Overhead Lights (Button 2): +# - Command topic: ${mqtt_this_device_command_topic_overhead} +# Payloads: ${mqtt_command_on}, ${mqtt_command_off} +# - Status topic: ${mqtt_this_device_status_topic_overhead} +# Payloads mirror the overhead virtual relay state and are published retained. +# +# This device subscribes to status topics from esp-officeduallights to mirror remote state. #:########################################################################################:# # OFFLINE NOTES: -# a) Home Assistant OFFLINE, WiFi OK -# - Physical buttons still control relays locally. -# - MQTT is optional; device operates as a normal switch without HA. -# -# b) MQTT OFFLINE (or broker unreachable) -# - Physical buttons still control relays locally. -# - HA/API control (if available) still works, but no MQTT command/status. -# -# c) Entire WiFi/Network OFFLINE -# - Physical buttons still control relays locally. -# - Accurate time is NOT needed (SNTP not needed). +# - a) Home Assistant offline (network and MQTT online): +# - Local button control continues for both bunker and overhead lights. +# - MQTT command/status operation continues for 2-way sync with esp-officeduallights. +# - HA entities are unavailable until HA returns. +# - b) MQTT offline (or Home Assistant and MQTT offline): +# - Local button control continues. +# - Bunker lights still toggle locally (physical relay). +# - Overhead lights cannot be toggled remotely (no MQTT transport). +# - If Home Assistant is online and connected by native ESPHome API, HA control can still +# change the local relays but remote MQTT sync is unavailable until MQTT returns. +# - c) Entire WiFi/network offline: +# - Local wall-switch button control continues for both buttons. +# - Bunker lights still toggle locally (physical relay). +# - Overhead lights cannot be controlled (no network transport). +# - Accurate time is not needed. SNTP is not used for this device. #:########################################################################################:# - #:########################################################################################:# # SUBSTITUTIONS: Specific device variable substitutions # If NOT using a secrets file, just replace these with the passwords etc (in quotes) @@ -44,12 +72,12 @@ substitutions: # Device Naming device_name: "esp-officelights" friendly_name: "Office Main Lightswitch (3)" - description_comment: "Office Main Lightswitch using a Zemismart KS-811 Triple Push Button. Bunker Light A (1), Spare A (2), Spare B (3) (Layout V1.1)" + description_comment: "Office Main Lightswitch using a Zemismart KS-811 Triple Push Button. Bunker Lights (1), Overhead Lights (2), Spare (3) (Layout V1.1)" device_area: "Office" # Project Naming project_name: "Zemismart Technologies.KS-811 Triple" - project_version: "v2.1" + project_version: "v3.0" # Passwords & Secrets api_key: !secret esp-api_key @@ -66,20 +94,34 @@ substitutions: log_level: "INFO" update_interval: "60s" - # MQTT LOCAL Controls - # Topic that remote things will use to command this locally without HA - mqtt_local_device1_name: "office-main-lights-left" - mqtt_local_command1_topic: "${mqtt_command_main_topic}/${mqtt_local_device1_name}" - mqtt_local_status1_topic: "${mqtt_status_main_topic}/${mqtt_local_device1_name}" + # Default relay restore mode + # Options: RESTORE_DEFAULT_OFF, RESTORE_DEFAULT_ON, ALWAYS_ON, ALWAYS_OFF + relay_restore_mode: "RESTORE_DEFAULT_OFF" - mqtt_local_device2_name: "office-main-lights-right" - mqtt_local_command2_topic: "${mqtt_command_main_topic}/${mqtt_local_device2_name}" - mqtt_local_status2_topic: "${mqtt_status_main_topic}/${mqtt_local_device2_name}" + # MQTT LOCAL Controls - Bunker Lights + # Command topics: this device publishes here when Button 1 or HA toggles bunker. + # Status topics: this device publishes retained status here on relay state changes. + # esp-officeduallights subscribes to mirror remote bunker state. + mqtt_this_device_function_bunker: "office-main-lights-left" + mqtt_this_device_command_topic_bunker: "${mqtt_command_main_topic}/${mqtt_this_device_function_bunker}" + mqtt_this_device_status_topic_bunker: "${mqtt_status_main_topic}/${mqtt_this_device_function_bunker}" - # Switch Naming - switch_1_name: "Nighttime Lights" # Only one light (Bunker Light) actually connected to this relay - switch_2_name: "Daytime Lights" # Virtual only, no power connected to 2nd relay - switch_3_name: "Spare" # Virtual only, no power connected to 3rd relay + # MQTT LOCAL Controls - Overhead Lights + # Command topics: this device publishes here when Button 2 or HA toggles overhead. + # Status topics: this device publishes retained status here on virtual relay state changes. + # esp-officeduallights subscribes to mirror remote overhead state. + mqtt_this_device_function_overhead: "office-main-lights-right" + mqtt_this_device_command_topic_overhead: "${mqtt_command_main_topic}/${mqtt_this_device_function_overhead}" + mqtt_this_device_status_topic_overhead: "${mqtt_status_main_topic}/${mqtt_this_device_function_overhead}" + + # MQTT Status/Command payloads + mqtt_command_on: "On" + mqtt_command_off: "Off" + + # Switch/Relay/Button Naming + switch_1_name: "Bunker Lights" # Physical relay (GPIO13) connected to bunker light load + switch_2_name: "Overhead Lights" # Virtual mirror of esp-officeduallights overhead state + switch_3_name: "Spare" # Virtual only, no function assigned #:########################################################################################:# # PACKAGES: Included Common Packages @@ -126,10 +168,6 @@ esphome: project: name: "${project_name}" version: "${project_version}" - #on_boot: - # priority: 200 - # then: - # - switch.turn_on: relay_3 #:########################################################################################:# # ESP PLATFORM AND FRAMEWORK @@ -137,7 +175,7 @@ esphome: #:########################################################################################:# esp8266: board: esp01_1m - early_pin_init: false + early_pin_init: false # Recommended false where relays/switches are involved board_flash_mode: dout #:########################################################################################:# @@ -158,6 +196,156 @@ status_led: number: GPIO2 inverted: true +#:########################################################################################:# +# MQTT: Device-specific MQTT command triggers +# This extends the common MQTT package with local custom command/status topics. +#:########################################################################################:# +mqtt: + # Republish current relay states whenever MQTT reconnects so retained status stays accurate + # for subscribers that reconnect later. + on_connect: + - mqtt.publish: + topic: "${mqtt_this_device_status_topic_bunker}" + payload: !lambda |- + return id(Relay_1).state ? "${mqtt_command_on}" : "${mqtt_command_off}"; + retain: true + - mqtt.publish: + topic: "${mqtt_this_device_status_topic_overhead}" + payload: !lambda |- + return id(Relay_2).state ? "${mqtt_command_on}" : "${mqtt_command_off}"; + retain: true + + on_message: + # Mirror remote overhead command onto virtual Relay_2, but only if a real state change + # is needed. Do not republish the command from here. Relay status is published by the + # relay callback below. + - topic: "${mqtt_this_device_command_topic_overhead}" + payload: "${mqtt_command_on}" + then: + - if: + condition: + lambda: "return !id(Relay_2).state;" + then: + - switch.turn_on: Relay_2 + + - topic: "${mqtt_this_device_command_topic_overhead}" + payload: "${mqtt_command_off}" + then: + - if: + condition: + lambda: "return id(Relay_2).state;" + then: + - switch.turn_off: Relay_2 + + # Mirror remote bunker command onto Relay_1, but only if a real state change is needed. + # This handles commands from esp-officeduallights when its local button toggles the + # physical bunker light and publishes status. + - topic: "${mqtt_this_device_status_topic_bunker}" + payload: "${mqtt_command_on}" + then: + - if: + condition: + lambda: "return !id(Relay_1).state;" + then: + - switch.turn_on: Relay_1 + + - topic: "${mqtt_this_device_status_topic_bunker}" + payload: "${mqtt_command_off}" + then: + - if: + condition: + lambda: "return id(Relay_1).state;" + then: + - switch.turn_off: Relay_1 + +#:########################################################################################:# +# SCRIPTS +# https://esphome.io/components/script.html +#:########################################################################################:# +script: + # Local/HA requested ON for Bunker Lights. Change the local relay immediately, then publish + # a non-retained command so esp-officeduallights follows. + - id: office_bunker_on + mode: restart + then: + - if: + condition: + lambda: "return !id(Relay_1).state;" + then: + - switch.turn_on: Relay_1 + - mqtt.publish: + topic: "${mqtt_this_device_command_topic_bunker}" + payload: "${mqtt_command_on}" + retain: false + + # Local/HA requested OFF for Bunker Lights. + - id: office_bunker_off + mode: restart + then: + - if: + condition: + lambda: "return id(Relay_1).state;" + then: + - switch.turn_off: Relay_1 + - mqtt.publish: + topic: "${mqtt_this_device_command_topic_bunker}" + payload: "${mqtt_command_off}" + retain: false + + # Physical Button 1 toggle logic for Bunker Lights. This is the only place that decides + # ON vs OFF for the bunker. + - id: office_bunker_toggle + mode: restart + then: + - if: + condition: + lambda: "return id(Relay_1).state;" + then: + - script.execute: office_bunker_off + else: + - script.execute: office_bunker_on + + # Local/HA requested ON for Overhead Lights. Change the virtual mirror immediately, then + # publish a non-retained command so esp-officeduallights follows. + - id: office_overhead_on + mode: restart + then: + - if: + condition: + lambda: "return !id(Relay_2).state;" + then: + - switch.turn_on: Relay_2 + - mqtt.publish: + topic: "${mqtt_this_device_command_topic_overhead}" + payload: "${mqtt_command_on}" + retain: false + + # Local/HA requested OFF for Overhead Lights. + - id: office_overhead_off + mode: restart + then: + - if: + condition: + lambda: "return id(Relay_2).state;" + then: + - switch.turn_off: Relay_2 + - mqtt.publish: + topic: "${mqtt_this_device_command_topic_overhead}" + payload: "${mqtt_command_off}" + retain: false + + # Physical Button 2 toggle logic for Overhead Lights. + - id: office_overhead_toggle + mode: restart + then: + - if: + condition: + lambda: "return id(Relay_2).state;" + then: + - script.execute: office_overhead_off + else: + - script.execute: office_overhead_on + #:########################################################################################:# # BINARY SENSORS # https://esphome.io/components/binary_sensor/ @@ -171,8 +359,11 @@ binary_sensor: inverted: true use_interrupt: false name: "Button 1: ${switch_1_name}" + filters: + - delayed_on: 10ms + - delayed_off: 10ms on_press: - - switch.toggle: Relay_1 + - script.execute: office_bunker_toggle # Button 2 (GPIO05) - platform: gpio @@ -181,146 +372,89 @@ binary_sensor: mode: INPUT inverted: true name: "Button 2: ${switch_2_name}" + filters: + - delayed_on: 10ms + - delayed_off: 10ms on_press: - - switch.toggle: Relay_2 + - script.execute: office_overhead_toggle - # Button 3 (GPIO4) + # Button 3 (GPIO4) - Spare - platform: gpio pin: number: GPIO4 mode: INPUT inverted: true name: "Button 3: ${switch_3_name}" - on_press: - - switch.toggle: Relay_3 - -#:########################################################################################:# -# MQTT COMPONENT: -# Device-specific MQTT command triggers -# https://esphome.io/components/mqtt.html -#:########################################################################################:# -mqtt: - on_message: - - topic: "${mqtt_local_command1_topic}" - payload: "On" - then: - - script.stop: relay_1_timer_script - - switch.turn_on: Relay_1 - - - topic: "${mqtt_local_command1_topic}" - payload: "Off" - then: - - script.stop: relay_1_timer_script - - switch.turn_off: Relay_1 - - - topic: "${mqtt_local_command1_topic}" - then: - - lambda: |- - if (x.empty()) { - return; - } - - bool numeric_payload = true; - for (char c : x) { - if (c < '0' || c > '9') { - numeric_payload = false; - break; - } - } - - if (!numeric_payload) { - return; - } - - const int timer_seconds = atoi(x.c_str()); - if (timer_seconds < 1 || timer_seconds > 3600) { - return; - } - - id(relay_1_timer_script)->execute(timer_seconds * 1000); - - - topic: "${mqtt_local_command2_topic}" - payload: "On" - then: - - script.stop: relay_2_timer_script - - switch.turn_on: Relay_2 - - - topic: "${mqtt_local_command2_topic}" - payload: "Off" - then: - - script.stop: relay_2_timer_script - - switch.turn_off: Relay_2 - - - topic: "${mqtt_local_command2_topic}" - then: - - lambda: |- - if (x.empty()) { - return; - } - - bool numeric_payload = true; - for (char c : x) { - if (c < '0' || c > '9') { - numeric_payload = false; - break; - } - } - - if (!numeric_payload) { - return; - } - - const int timer_seconds = atoi(x.c_str()); - if (timer_seconds < 1 || timer_seconds > 3600) { - return; - } - - id(relay_2_timer_script)->execute(timer_seconds * 1000); - -#:########################################################################################:# -# SCRIPT COMPONENT: -# Relay 1 timer script -# mode: restart means a new valid timer value restarts the timer from zero -# https://esphome.io/components/script.html -#:########################################################################################:# -script: - - id: relay_1_timer_script - mode: restart - parameters: - timer_duration_ms: int - then: - - switch.turn_on: Relay_1 - - delay: !lambda "return timer_duration_ms;" - - switch.turn_off: Relay_1 - - - id: relay_2_timer_script - mode: restart - parameters: - timer_duration_ms: int - then: - - switch.turn_on: Relay_2 - - delay: !lambda "return timer_duration_ms;" - - switch.turn_off: Relay_2 #:########################################################################################:# # SWITCH # https://esphome.io/components/switch/ #:########################################################################################:# switch: - # Relay 1 (GPIO13) - - platform: gpio + # Exposed Home Assistant control for Bunker Lights. This controls the local relay and + # publishes the matching command so esp-officeduallights follows. + - platform: template name: "Relay 1: ${switch_1_name}" + id: Relay_1_HA + lambda: |- + return id(Relay_1).state; + turn_on_action: + - script.execute: office_bunker_on + turn_off_action: + - script.execute: office_bunker_off + + # Exposed Home Assistant control for Overhead Lights. This publishes the matching command + # so esp-officeduallights follows. Local Relay_2 is a virtual mirror of remote state. + - platform: template + name: "Relay 2: ${switch_2_name}" + id: Relay_2_HA + lambda: |- + return id(Relay_2).state; + turn_on_action: + - script.execute: office_overhead_on + turn_off_action: + - script.execute: office_overhead_off + + # Physical Relay 1 (Bunker Lights). Kept internal so HA uses the template switch above. + # Relay callbacks publish retained status only, not commands. + - platform: gpio + name: "Internal Relay 1: ${switch_1_name}" + internal: true pin: GPIO13 id: Relay_1 + restore_mode: ${relay_restore_mode} + on_turn_on: + - mqtt.publish: + topic: "${mqtt_this_device_status_topic_bunker}" + payload: "${mqtt_command_on}" + retain: true + on_turn_off: + - mqtt.publish: + topic: "${mqtt_this_device_status_topic_bunker}" + payload: "${mqtt_command_off}" + retain: true - # Relay 2 (GPIO12) + # Virtual Relay 2 (Overhead Lights mirror). No physical load connected. Mirrors the state + # of esp-officeduallights overhead light via MQTT status subscription. - platform: gpio - name: "Relay 2: ${switch_2_name}" + name: "Internal Relay 2: ${switch_2_name}" + internal: true pin: GPIO12 id: Relay_2 + restore_mode: ${relay_restore_mode} + on_turn_on: + - mqtt.publish: + topic: "${mqtt_this_device_status_topic_overhead}" + payload: "${mqtt_command_on}" + retain: true + on_turn_off: + - mqtt.publish: + topic: "${mqtt_this_device_status_topic_overhead}" + payload: "${mqtt_command_off}" + retain: true - # Relay 3 (GPIO14) + # Relay 3 (Spare) - no function assigned - platform: gpio name: "Relay 3: ${switch_3_name}" pin: GPIO14 - id: Relay_3 \ No newline at end of file + id: Relay_3