#:########################################################################################:# # TITLE: GARAGE DB CONTROLS # zorruno.com layout v1.1 2026 #:########################################################################################:# # REPO: # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-garagedbcontrols.yaml #:########################################################################################:# # VERSIONS: # v1.1 2026-02-25 Updated yaml to zorruno layout V1.1 # v1.0 2025-10-23 Initial Version #:########################################################################################:# # HARDWARE: # - Sonoff 4CH R2 (ESP8266 / ESP8285) # - Reference YAML: https://github.com/jvyoralek/homeassistant-config/blob/master/esphome/sonoff-4ch.yaml # - Relays (4CH R2): GPIO12, GPIO5, GPIO4, GPIO15 # - Buttons (4CH R2): GPIO0, GPIO9, GPIO10, GPIO14 # - Status LED: GPIO13 (active-low) # - Extras (as used here): # - EV pulse input: GPIO2 (must be HIGH at boot; INPUT_PULLUP used) # - DHT sensor: GPIO3 (note: UART RX pin on ESP8266; ensure logger baud_rate=0) #:########################################################################################:# # OPERATION NOTES: # - Provides 4 relay outputs with matching local buttons. # - Relay restore modes are set per-output (some default ON, some default OFF). # - Includes EV charger energy monitoring via pulse input (2.0 Wh per pulse): # - "Power Now" derived from pulse rate (pulse_meter). # - Lifetime kWh total from pulse total (pulse_meter total). # - 1-hour sliding average for power. # - Daily kWh using total_daily_energy, with "Energy Yesterday" snap at 23:59 when time is valid. # - Publishes a Tasmota-like MQTT telemetry message once per 60s with windowed Wh delta. #:########################################################################################:# # MQTT COMMANDS: # - Publishes windowed EV Wh count every 60s: # Topic: stat/tasmo-s4chan-7594-garage-1/EnergyMeterCount # Payload example: {"EVChargerWhCount":123.456} #:########################################################################################:# # OFFLINE NOTES: # a) Home Assistant OFFLINE, WiFi OK # - Device continues relay/button operation and continues pulse metering locally. # - MQTT telemetry publishing depends on MQTT broker availability. # # b) MQTT OFFLINE (or broker unreachable) # - Relays/buttons still operate locally and via HA API (if HA is available). # - MQTT telemetry messages will not be delivered until broker returns. # # c) Entire WiFi/Network OFFLINE # - Accurate time IS needed for the "Energy Yesterday" snapshot at 23:59 (SNTP required). # - Power/energy totals still accumulate, but "Energy Yesterday" will not update without valid time. #:########################################################################################:# #:########################################################################################:# # 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-garagedbcontrols" friendly_name: "Garage DB Control" description_comment: "Garage DB Power, EV Power & Measure, Lighting :: Sonoff 4ch R2 (Layout V1.1)" device_area: "Garage" # Project Naming project_name: "Sonoff Technologies.Sonoff 4Ch R2" project_version: "v1.1" # Passwords & Secrets api_key: !secret esp-api_key ota_pass: !secret esp-ota_pass static_ip_address: !secret esp-garagedbcontrols_ip # 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} # General Settings log_level: "ERROR" update_interval: "60s" # I/O Naming switch_1_name: "Small EV Charger" button_1_name: "Button 1" relay_1_icon: "mdi:power-socket-au" switch_2_name: "Garage West Power" button_2_name: "Button 2" relay_2_icon: "mdi:power-socket-au" switch_3_name: "Spare 3" button_3_name: "Button 3" relay_3_icon: "mdi:toggle-switch" switch_4_name: "Spare 4" button_4_name: "Button 4" relay_4_icon: "mdi:toggle-switch" # EV pulse settings ev_pulse_wh: "2.0" # Wh per pulse ev_pulse_filter: "10ms" # debounce pulses ev_pulse_timeout: "2min" # set power to 0W if no pulses ev_tasmota_tele_topic: "stat/tasmo-s4chan-7594-garage-1/EnergyMeterCount" #:########################################################################################:# # 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}" area: "${device_area}" project: name: "${project_name}" version: "${project_version}" # On boot, initialise the window start so the first publish isn't huge on_boot: priority: -10 then: - lambda: |- if (isfinite(id(ev_energy_total_kwh).state)) { id(ev_last_total_kwh) = id(ev_energy_total_kwh).state; } else { id(ev_last_total_kwh) = 0.0f; } #:########################################################################################:# # ESP PLATFORM AND FRAMEWORK # https://esphome.io/components/esp8266.html #:########################################################################################:# esp8266: board: esp8285 restore_from_flash: true preferences: flash_write_interval: 5min mdns: disabled: false #:########################################################################################:# # LOGGING # https://esphome.io/components/logger.html #:########################################################################################:# logger: level: "${log_level}" baud_rate: 0 #:########################################################################################:# # STATUS LED # https://esphome.io/components/status_led.html #:########################################################################################:# status_led: pin: number: GPIO13 inverted: yes #:########################################################################################:# # GLOBALS # https://esphome.io/components/globals.html #:########################################################################################:# globals: # Store the last published total (kWh) for 60s delta windowing - id: ev_last_total_kwh type: float restore_value: true initial_value: "0.0" - id: ev_energy_yesterday_kwh type: float restore_value: true initial_value: "0.0" # Track last day-of-year we snapped so it only happens once per day - id: ev_last_snapshot_doy type: int restore_value: no initial_value: "-1" #:########################################################################################:# # SWITCHES # https://esphome.io/components/switch/ #:########################################################################################:# switch: # Sonoff 4Ch R2 Relays are GPIO12, GPIO5, GPIO4, GPIO15 - platform: gpio id: relay1 name: "${switch_1_name}" restore_mode: RESTORE_DEFAULT_OFF pin: GPIO12 icon: "${relay_1_icon}" - platform: gpio id: relay2 name: "${switch_2_name}" restore_mode: RESTORE_DEFAULT_ON pin: GPIO5 icon: "${relay_2_icon}" - platform: gpio id: relay3 name: "${switch_3_name}" restore_mode: RESTORE_DEFAULT_ON pin: GPIO4 icon: "${relay_3_icon}" - platform: gpio id: relay4 name: "${switch_4_name}" restore_mode: RESTORE_DEFAULT_OFF pin: GPIO15 icon: "${relay_4_icon}" #:########################################################################################:# # BINARY SENSORS # https://esphome.io/components/binary_sensor/ #:########################################################################################:# binary_sensor: # Sonoff 4Ch R2 Buttons are GPIO0, GPIO9, GPIO10, GPIO14 - platform: gpio id: button1 name: "${button_1_name}" pin: number: GPIO0 mode: INPUT_PULLUP inverted: true on_press: - switch.toggle: relay1 - platform: gpio id: button2 name: "${button_2_name}" pin: number: GPIO9 mode: INPUT_PULLUP inverted: true on_press: - switch.toggle: relay2 - platform: gpio id: button3 name: "${button_3_name}" pin: number: GPIO10 mode: INPUT_PULLUP inverted: true on_press: - switch.toggle: relay3 - platform: gpio id: button4 name: "${button_4_name}" pin: number: GPIO14 mode: INPUT_PULLUP inverted: true on_press: - switch.toggle: relay4 #:########################################################################################:# # SENSORS # https://esphome.io/components/sensor/ #:########################################################################################:# sensor: ######################################################################################## # EV CHARGER ENERGY (PULSE INPUT ON GPIO2, 2.0 Wh PER PULSE) # - Uses pulse_meter for stable pulse rate and total count # - Derives Power (W), 1h average Power, Today kWh, and lifetime kWh ######################################################################################## - platform: pulse_meter id: ev_pulse_pm pin: number: GPIO2 mode: INPUT_PULLUP # GPIO2 must be HIGH at boot inverted: false name: "${switch_1_name} Power Now" unit_of_measurement: "W" device_class: power state_class: measurement accuracy_decimals: 0 internal_filter: ${ev_pulse_filter} timeout: ${ev_pulse_timeout} # pulse_meter reports pulses/min; each pulse = 2 Wh => W = pulses/min * 2 * 60 = x120 filters: - multiply: 120.0 total: id: ev_energy_total_kwh name: "${switch_1_name} Energy Cumulative Total" unit_of_measurement: "kWh" device_class: energy state_class: total_increasing accuracy_decimals: 1 filters: # kWh per pulse (2.0 Wh = 0.002 kWh) - multiply: 0.002 - platform: template id: ev_power_avg_1h name: "${switch_1_name} Power (1h avg)" unit_of_measurement: "W" device_class: power state_class: measurement accuracy_decimals: 1 update_interval: 10s lambda: |- return id(ev_pulse_pm).state; filters: - sliding_window_moving_average: window_size: 360 # 10 s * 360 ≈ 1 hour send_every: 6 # update HA once per minute - platform: total_daily_energy id: ev_energy_today_kwh name: "${switch_1_name} Energy Today" power_id: ev_pulse_pm unit_of_measurement: "kWh" device_class: energy state_class: total accuracy_decimals: 1 filters: # total_daily_energy outputs Wh; convert to kWh - multiply: 0.001 - platform: template id: ev_energy_yesterday_sensor name: "${switch_1_name} Energy Yesterday" unit_of_measurement: "kWh" device_class: energy state_class: total accuracy_decimals: 1 update_interval: 30s lambda: |- return id(ev_energy_yesterday_kwh); ######################################################################################## # DHT SENSOR (GPIO3) # NOTE: GPIO3 is UART RX on ESP8266; logger baud_rate=0 avoids conflicts. ######################################################################################## - platform: dht pin: GPIO3 update_interval: 60s temperature: name: "${friendly_name} Temperature" humidity: name: "${friendly_name} Humidity" #:########################################################################################:# # INTERVALS # https://esphome.io/components/interval.html #:########################################################################################:# interval: # Teleperiod = 60s: publish JSON and advance the window - interval: 60s then: - mqtt.publish: topic: "${ev_tasmota_tele_topic}" qos: 0 retain: false payload: !lambda |- float current = id(ev_energy_total_kwh).state; if (!isfinite(current)) current = id(ev_last_total_kwh); float delta_kwh = current - id(ev_last_total_kwh); if (delta_kwh < 0.0f || !isfinite(delta_kwh)) delta_kwh = 0.0f; float delta_wh = delta_kwh * 1000.0f; id(ev_last_total_kwh) = current; char buf[64]; snprintf(buf, sizeof(buf), "{\"EVChargerWhCount\":%.3f}", delta_wh); return std::string(buf); # YESTERDAY ENERGY snapshot (once per day at 23:59, when time is valid) - interval: 60s then: - lambda: |- auto now = id(sntp_time).now(); if (!now.is_valid()) return; if (now.hour != 23 || now.minute != 59) return; if (id(ev_last_snapshot_doy) == now.day_of_year) return; if (isfinite(id(ev_energy_today_kwh).state)) { id(ev_energy_yesterday_kwh) = id(ev_energy_today_kwh).state; } id(ev_last_snapshot_doy) = now.day_of_year;