Compare commits

...

125 Commits

Author SHA1 Message Date
zorruno fe6feee250 various tidyups 2026-07-24 18:55:12 +12:00
ESPHome Device Builder fce8c7b0bc Edit esp-3dprinterpow.yaml 2026-07-24 18:21:35 +12:00
ESPHome Device Builder f46b29e6ab Edit esp-spare1-athomv1.yaml 2026-07-24 18:21:35 +12:00
ESPHome Device Builder 32df6ce407 Edit esp-rocktumbler.yaml 2026-07-24 18:21:35 +12:00
zorruno 8fdcb128a3 various tidyups 2026-07-22 22:15:25 +12:00
ESPHome Device Builder b9e9c98f7e Edit esp-centralstairs-bottom.yaml 2026-07-21 18:28:33 +12:00
ESPHome Device Builder 6dc9b56ff7 Edit esp-centralstairs-top.yaml 2026-07-21 18:28:33 +12:00
zorruno 4e2107f7f7 various tidyups 2026-07-21 13:40:06 +12:00
ESPHome Device Builder 66bee0ccf7 Edit esp-masterbdrmsouth.yaml 2026-07-20 21:02:12 +12:00
ESPHome Device Builder 5b675e1d62 Edit esp-masterbdrmwest.yaml 2026-07-20 21:00:48 +12:00
ESPHome Device Builder a76dbb6b16 Edit esp-downstkitchlights.yaml 2026-07-20 20:47:14 +12:00
ESPHome Device Builder e20e36cdb5 Edit esp-downstloungeentry.yaml 2026-07-20 20:40:18 +12:00
ESPHome Device Builder a2fb86aa93 Edit esp-downstloungemain.yaml 2026-07-20 20:37:50 +12:00
ESPHome Device Builder c6e8d7721c Edit esp-officeusbhubpower.yaml 2026-07-20 20:32:29 +12:00
ESPHome Device Builder 3ff67b883b Edit esp-downstairskitchleds.yaml 2026-07-20 20:17:04 +12:00
ESPHome Device Builder 2e645793f0 Edit esp-bedrm1arlecbulb.yaml 2026-07-20 20:09:41 +12:00
ESPHome Device Builder 4ff90ea60a Edit esp-breakfastbarleds.yaml 2026-07-20 19:59:40 +12:00
ESPHome Device Builder dcd43a18c1 Edit esp-loungecabinetleds.yaml 2026-07-20 19:44:53 +12:00
ESPHome Device Builder 2b6b91f829 Edit esp-loungebookshelfleds.yaml 2026-07-20 19:27:48 +12:00
zorruno d8bcf05bf6 various packages 2026-07-20 19:24:35 +12:00
ESPHome Device Builder afcbf1a94b Edit esp-reterminal-e1001.yaml 2026-07-20 17:17:52 +12:00
ESPHome Device Builder 027af09e40 Edit esp-reterminal-e1001.yaml 2026-07-20 16:27:51 +12:00
ESPHome Device Builder 90a6469161 Edit esp-reterminal-e1001.yaml 2026-07-20 15:58:44 +12:00
ESPHome Device Builder bffcda38d9 Edit esp-reterminal-e1001.yaml 2026-07-20 15:17:25 +12:00
ESPHome Device Builder cb95ceacea Edit esp-reterminal-e1001.yaml 2026-07-20 15:12:31 +12:00
ESPHome Device Builder 941334ecfd Add esp-reterminal-e1001.yaml 2026-07-20 15:10:47 +12:00
ESPHome Device Builder a8b0990000 Edit esp-bedside-panel.yaml 2026-07-20 15:05:51 +12:00
ESPHome Device Builder 809f9ada66 Edit esp-bedside-panel.yaml 2026-07-20 14:46:50 +12:00
ESPHome Device Builder 14d65a8cf1 Edit esp-bedside-panel.yaml 2026-07-20 14:20:39 +12:00
ESPHome Device Builder 9afee6e638 Edit esp-bedside-panel.yaml 2026-07-20 12:50:34 +12:00
ESPHome Device Builder fd2fe422d0 Edit esp-bedside-panel.yaml 2026-07-20 12:30:38 +12:00
ESPHome Device Builder f07cd6d0c4 Edit esp-bedside-panel.yaml 2026-07-20 12:19:25 +12:00
ESPHome Device Builder ea604b8a30 Edit esp-bedside-panel.yaml 2026-07-20 12:01:52 +12:00
ESPHome Device Builder 241a4954bd Edit esp-bedside-panel.yaml 2026-07-19 23:15:43 +12:00
ESPHome Device Builder b2876e937a Edit esp-bedside-panel.yaml 2026-07-19 22:47:41 +12:00
ESPHome Device Builder 0dd442f34a Edit esp-bedside-panel.yaml 2026-07-19 22:07:43 +12:00
ESPHome Device Builder 67a8ed7436 Edit esp-bedside-panel.yaml 2026-07-19 21:45:20 +12:00
ESPHome Device Builder f92ffa3eab Edit esp-bedside-panel.yaml 2026-07-19 21:33:10 +12:00
ESPHome Device Builder b893249959 Edit esp-bedside-panel.yaml 2026-07-19 21:14:31 +12:00
ESPHome Device Builder c658792f5d Edit esp-bedside-panel.yaml 2026-07-19 20:47:52 +12:00
ESPHome Device Builder ca60f73b9d Edit esp-bedside-panel.yaml 2026-07-19 00:01:55 +12:00
ESPHome Device Builder 5747968e96 Edit esp-bedside-panel.yaml 2026-07-18 23:47:46 +12:00
ESPHome Device Builder 3938515564 Edit esp-bedside-panel.yaml 2026-07-18 23:11:05 +12:00
ESPHome Device Builder 526fd23567 Edit esp-bedside-panel.yaml 2026-07-18 22:46:54 +12:00
ESPHome Device Builder c8e9992c32 Edit esp-bedside-panel.yaml 2026-07-18 22:18:21 +12:00
ESPHome Device Builder 5e731f791e Edit esp-bedside-panel.yaml 2026-07-18 21:50:31 +12:00
ESPHome Device Builder 4bc8c308a6 Edit esp-bedside-panel.yaml 2026-07-18 21:35:40 +12:00
ESPHome Device Builder dcf28a3c8f Edit esp-bedside-panel.yaml 2026-07-18 21:27:21 +12:00
ESPHome Device Builder 9bcc9b9b4d Edit esp-bedside-panel.yaml 2026-07-18 21:14:53 +12:00
ESPHome Device Builder a95036e0c5 Edit esp-bedside-panel.yaml 2026-07-18 20:44:27 +12:00
ESPHome Device Builder 347e20f552 Edit esp-bedside-panel.yaml 2026-07-18 20:23:54 +12:00
ESPHome Device Builder ec195d33ce Edit esp-bedside-panel.yaml 2026-07-18 19:58:48 +12:00
ESPHome Device Builder d0df25acf3 Edit esp-bedside-panel.yaml 2026-07-18 19:22:39 +12:00
ESPHome Device Builder 1dbe748e19 Edit esp-bedside-panel.yaml 2026-07-18 19:01:36 +12:00
ESPHome Device Builder 3dcaa1b06b Edit esp-bedside-panel.yaml 2026-07-18 00:23:12 +12:00
ESPHome Device Builder d324ec568b Edit esp-bedside-panel.yaml 2026-07-18 00:11:51 +12:00
ESPHome Device Builder 97669cb664 Edit esp-bedside-panel.yaml 2026-07-18 00:03:22 +12:00
ESPHome Device Builder a81f56a261 Edit esp-bedside-panel.yaml 2026-07-17 23:47:31 +12:00
ESPHome Device Builder 3cc5a757bc Edit esp-bedside-panel.yaml 2026-07-17 23:35:13 +12:00
ESPHome Device Builder 0794008667 Edit esp-bedside-panel.yaml 2026-07-17 23:18:32 +12:00
ESPHome Device Builder cafc856a3c Edit esp-bedside-panel.yaml 2026-07-17 23:03:40 +12:00
ESPHome Device Builder 38281df1dd Edit esp-bedside-panel.yaml 2026-07-17 22:50:45 +12:00
ESPHome Device Builder c197172608 Edit esp-bedside-panel.yaml 2026-07-17 22:46:34 +12:00
ESPHome Device Builder 034c87317f Edit esp-bedside-panel.yaml 2026-07-17 22:17:32 +12:00
ESPHome Device Builder 824fd3fac6 Edit esp-bedside-panel.yaml 2026-07-17 21:54:17 +12:00
ESPHome Device Builder 41836a3dee Edit esp-bedside-panel.yaml 2026-07-17 11:42:50 +12:00
ESPHome Device Builder 45f1f0540a Edit esp-bedside-panel.yaml 2026-07-17 11:29:52 +12:00
ESPHome Device Builder c374e32ded Edit esp-bedside-panel.yaml 2026-07-16 23:11:55 +12:00
ESPHome Device Builder 3dc72898c9 Edit esp-bedside-panel.yaml 2026-07-16 22:54:59 +12:00
ESPHome Device Builder 324cefef4c Edit esp-bedside-panel.yaml 2026-07-16 22:42:55 +12:00
ESPHome Device Builder 07048708a9 Edit esp-bedside-panel.yaml 2026-07-16 22:27:27 +12:00
ESPHome Device Builder 355b3166f7 Edit esp-bedside-panel.yaml 2026-07-16 22:18:19 +12:00
ESPHome Device Builder 8b31b780b0 Edit esp-bedside-panel.yaml 2026-07-16 22:03:57 +12:00
ESPHome Device Builder ee08838c1f Edit esp-bedside-panel.yaml 2026-07-16 21:49:11 +12:00
ESPHome Device Builder aa4b632916 Edit esp-bedside-panel.yaml 2026-07-16 21:38:34 +12:00
ESPHome Device Builder 5be9125eec Edit esp-bedside-panel.yaml 2026-07-16 21:23:23 +12:00
ESPHome Device Builder 5f540de6af Edit esp-bedside-panel.yaml 2026-07-16 21:00:16 +12:00
ESPHome Device Builder 95835550de Edit esp-bedside-panel.yaml 2026-07-16 20:48:57 +12:00
ESPHome Device Builder 20a40b2b3f Edit esp-bedside-panel.yaml 2026-07-16 20:26:16 +12:00
ESPHome Device Builder cf857f322f Edit esp-bedside-panel.yaml 2026-07-16 20:16:10 +12:00
ESPHome Device Builder 497cf553ad Edit esp-bedside-panel.yaml 2026-07-16 19:57:28 +12:00
ESPHome Device Builder caa4d906c4 Edit esp-bedside-panel.yaml 2026-07-16 19:54:10 +12:00
ESPHome Device Builder b8681f7741 Edit esp-bedside-panel.yaml 2026-07-16 19:25:20 +12:00
ESPHome Device Builder 5403d27f10 Edit esp-bedside-panel.yaml 2026-07-16 19:07:51 +12:00
ESPHome Device Builder 54f4da6c18 Edit esp-bedside-panel.yaml 2026-07-16 18:53:31 +12:00
ESPHome Device Builder 19ba5a0c8c Edit esp-bedside-panel.yaml 2026-07-16 18:36:54 +12:00
ESPHome Device Builder 6ae711d922 Edit esp-bedside-panel.yaml 2026-07-16 18:23:14 +12:00
ESPHome Device Builder e90b6b2b8c Edit esp-bedside-panel.yaml 2026-07-16 18:15:36 +12:00
ESPHome Device Builder 24bb66893a Edit esp-bedside-panel.yaml 2026-07-16 18:03:48 +12:00
ESPHome Device Builder 905a16236b Edit esp-bedside-panel.yaml 2026-07-16 17:58:24 +12:00
ESPHome Device Builder cc720cfd00 Edit esp-bedside-panel.yaml 2026-07-15 21:08:18 +12:00
ESPHome Device Builder 34a84d0d0c Edit esp-bedside-panel.yaml 2026-07-15 21:01:53 +12:00
ESPHome Device Builder 5855dbb5a9 Edit esp-bedside-panel.yaml 2026-07-15 20:55:38 +12:00
ESPHome Device Builder 2a8c9584d7 Edit esp-bedside-panel.yaml 2026-07-15 20:41:49 +12:00
ESPHome Device Builder bb97b2ac12 Edit esp-bedside-panel.yaml 2026-07-15 20:35:28 +12:00
ESPHome Device Builder 0ca6fefd08 Edit esp-bedside-panel.yaml 2026-07-15 20:28:10 +12:00
ESPHome Device Builder ea7eedff93 Edit esp-bedside-panel.yaml 2026-07-15 20:16:38 +12:00
ESPHome Device Builder 79054490d0 Edit esp-bedside-panel.yaml 2026-07-15 20:06:42 +12:00
ESPHome Device Builder d27ba8bd14 Edit esp-bedside-panel.yaml 2026-07-14 22:56:17 +12:00
ESPHome Device Builder 63209b5364 Edit esp-bedside-panel.yaml 2026-07-14 22:54:48 +12:00
ESPHome Device Builder 1b9552e328 Edit esp-bedside-panel.yaml 2026-07-14 22:50:56 +12:00
ESPHome Device Builder ae566427c2 Edit esp-bedside-panel.yaml 2026-07-14 22:39:41 +12:00
ESPHome Device Builder f63ff4bfe2 Edit esp-bedside-panel.yaml 2026-07-14 22:36:27 +12:00
ESPHome Device Builder 7bd3e3a0c2 Edit esp-bedside-panel.yaml 2026-07-14 22:34:05 +12:00
ESPHome Device Builder 06a482b43f Edit esp-bedside-panel.yaml 2026-07-14 22:15:13 +12:00
ESPHome Device Builder e013bab5c9 Edit esp-bedside-panel.yaml 2026-07-14 22:06:30 +12:00
ESPHome Device Builder 4fab9755ec Edit esp-bedside-panel.yaml 2026-07-14 22:02:46 +12:00
ESPHome Device Builder 769cb34eb6 Edit esp-bedside-panel.yaml 2026-07-14 21:51:48 +12:00
ESPHome Device Builder ce7a456d1f Edit esp-bedside-panel.yaml 2026-07-14 21:42:57 +12:00
ESPHome Device Builder e586ff8f28 Edit esp-bedside-panel.yaml 2026-07-14 21:19:44 +12:00
ESPHome Device Builder f571919352 Edit esp-bedside-panel.yaml 2026-07-14 20:58:49 +12:00
ESPHome Device Builder 2e9a37a9b9 Add esp-bedside-panel.yaml 2026-07-14 20:56:47 +12:00
ESPHome Device Builder d45474036c Edit esp-mainpowermonitor.yaml 2026-07-12 13:36:54 +12:00
ESPHome Device Builder 5c5256caa9 Edit esp-3dprinterpow.yaml 2026-07-12 13:05:32 +12:00
ESPHome Device Builder 2a5725b16d Edit esp-laundrywashpow.yaml 2026-07-12 12:56:38 +12:00
ESPHome Device Builder 706844e6e8 Edit esp-laundrydrypow.yaml 2026-07-12 12:12:18 +12:00
ESPHome Device Builder 2b2b8edc37 Edit esp-laundrydrypow.yaml 2026-07-12 11:52:15 +12:00
ESPHome Device Builder c11728dba6 Edit esp-3dprinterpow.yaml 2026-07-11 23:37:03 +12:00
ESPHome Device Builder 0377bf0b08 Edit esp-espnow_test_transmitter1.yaml 2026-07-11 18:19:37 +12:00
ESPHome Device Builder 893ed9dcfc Edit esp-espnow_test_receiver.yaml 2026-07-11 17:58:38 +12:00
ESPHome Device Builder 1d00da6514 Edit esp-espnow_test_receiver.yaml 2026-07-11 17:57:51 +12:00
ESPHome Device Builder 1aa5985c2c Edit esp-espnow_test_receiver.yaml 2026-07-11 17:55:33 +12:00
ESPHome Device Builder 97efa38777 Edit esp-espnow_test_transmitter1.yaml 2026-07-11 17:45:18 +12:00
ESPHome Device Builder cd39558b1f Add esp-espnow_test_transmitter1.yaml 2026-07-11 17:42:25 +12:00
ESPHome Device Builder 5b7a9d39d2 Add esp-espnow_test_receiver.yaml 2026-07-11 17:18:06 +12:00
108 changed files with 21951 additions and 10701 deletions
+1 -1
View File
@@ -1 +1 @@
{"pid": 71, "version": 1, "ha_version": "2026.7.1", "start_ts": 1783321380.267702} {"pid": 74, "version": 1, "ha_version": "2026.7.3", "start_ts": 1784842567.418518}
+43
View File
@@ -0,0 +1,43 @@
# =============================================================================
# Prettier Configuration for Home Assistant YAML
# Reference: https://developers.home-assistant.io/docs/documenting/yaml-style-guide/
# =============================================================================
#
# Prettier auto-formats YAML files written by OpenCode. This config aligns
# with the official Home Assistant YAML Style Guide where possible.
#
# WHAT PRETTIER ENFORCES:
# - 2-space indentation (no tabs)
# - Double quotes for strings (HA convention)
# - Trailing newline at end of file
# - Consistent spacing and formatting
# - Preserves HA custom tags (!include, !secret, !input, !env_var)
# - Preserves block style sequences and mappings
# - Preserves multiline strings (literal | and folded > styles)
# - Preserves comments
#
# WHAT PRETTIER DOES NOT ENFORCE (must be handled manually or by the AI):
# - Converting flow style [1, 2, 3] to block style sequences
# - Converting flow style { key: val } to block style mappings
# - Normalizing null/~ to implicit null (just "key:" with no value)
# - Normalizing truthy booleans (Yes/On/TRUE) to lowercase true/false
# - Wrapping long lines (templates, etc.)
#
# Users can customize this file. See: https://prettier.io/docs/en/options
# =============================================================================
# HA YAML Style Guide: "An indentation of 2 spaces must be used"
tabWidth: 2
useTabs: false
# HA YAML Style Guide: "Strings are preferably quoted with double quotes"
singleQuote: false
# Keep printWidth generous to avoid prettier breaking long YAML keys into
# explicit key notation (?). HA templates can be long but should be manually
# split using literal (|) or folded (>) block scalars per the style guide.
printWidth: 120
# Preserve multiline string formatting (literal |, folded >, etc.)
# HA YAML Style Guide: "make use of the literal style and folded style strings"
proseWrap: "preserve"
-114
View File
@@ -1,114 +0,0 @@
- id: '1778927514490'
alias: NSPanel Easy Configuration (v2026.5.5)
description: ''
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f
weather_entity: weather.astroweather_backyard
indoortemp: sensor.lounge_inside_temperature
- id: '1778927924212'
alias: NSPanel Easy Configuration (v2026.5.5)
description: ''
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f
button_page01_label: Lounge Scenes
button_pages_icon_size: '8'
entity01: script.nspanel_lounge_scene_bright
entity01_name: Bright
entity01_icon: mdi:brightness-7
entity01_confirm: false
entity02: script.nspanel_lounge_scene_tv_general
entity02_name: TV General
entity02_icon: mdi:television
entity02_confirm: false
entity03: script.nspanel_lounge_scene_movie
entity03_name: Movie
entity03_icon: mdi:movie-open
entity03_confirm: false
entity04: script.nspanel_lounge_scene_balanced
entity04_name: Balanced
entity04_icon: mdi:scale-balance
entity04_confirm: false
entity05: script.nspanel_lounge_scene_warm_cozy
entity05_name: Warm Cozy
entity05_icon: mdi:fireplace
entity05_confirm: false
entity06: script.nspanel_lounge_scene_reading
entity06_name: Reading
entity06_icon: mdi:book-open-page-variant
entity06_confirm: false
entity07: script.nspanel_lounge_scene_dining
entity07_name: Dining
entity07_icon: mdi:silverware-fork-knife
entity07_confirm: false
entity08: script.nspanel_lounge_scene_all_off
entity08_name: All Off
entity08_icon: mdi:power
entity08_confirm: false
mode: single
- id: '1778929633433'
alias: NSPanel Easy Configuration (v2026.5.5)
description: ''
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f
button_page01_label: Lounge Scenes
button_pages_icon_size: '8'
entity01: script.nspanel_lounge_scene_bright
entity01_name: Bright
entity01_icon: mdi:brightness-7
entity01_confirm: false
entity02: script.nspanel_lounge_scene_tv_general
entity02_name: TV General
entity02_icon: mdi:television
entity02_confirm: false
entity03: script.nspanel_lounge_scene_movie
entity03_name: Movie
entity03_icon: mdi:movie-open
entity03_confirm: false
entity04: script.nspanel_lounge_scene_balanced
entity04_name: Balanced
entity04_icon: mdi:scale-balance
entity04_confirm: false
entity05: script.nspanel_lounge_scene_warm_cozy
entity05_name: Warm Cozy
entity05_icon: mdi:fireplace
entity05_confirm: false
entity06: script.nspanel_lounge_scene_reading
entity06_name: Reading
entity06_icon: mdi:book-open-page-variant
entity06_confirm: false
entity07: script.nspanel_lounge_scene_dining
entity07_name: Dining
entity07_icon: mdi:silverware-fork-knife
entity07_confirm: false
entity08: script.nspanel_lounge_scene_all_off
entity08_name: All Off
entity08_icon: mdi:power
entity08_confirm: false
mode: single
- id: '1778930023427'
alias: NSPanel Lounge Controller
description: ''
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f
- id: '1778930202677'
alias: NSPanel Lounge Controller
description: ''
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f
- id: '1778930948144'
alias: Lounge NSPanel Controls
description: ''
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 369e58c3779ff3dd9a0b4c59fadeac4f
-26
View File
@@ -1,26 +0,0 @@
- id: do_random_actions_when_away
alias: Activate Random Actions for Security
mode: parallel # Enable parallel executions
trigger:
- platform: time_pattern
minutes: /5
condition:
alias: "Away routine active, nearly sunset, and all family not home"
condition: and
conditions:
- condition: sun
after: sunset
after_offset: -00:30:00 #30 mins before
- condition: state
entity_id: input_boolean.away_occupied_routine
state: "on"
- condition: state
entity_id: group.family_away
state: "not_home"
action:
service: script.light_duration
data:
# Pass randomly selected light from "group.simulation_lights" as script variable "light"
light: "{{states.group.simulation_lights.attributes.entity_id | random}}"
# random value between 5 & 29
duration: "00:{{ '{:02}'.format(range(5,29) | random | int) }}:00"
@@ -1,15 +0,0 @@
- id: "1635929281159"
alias: Weekly Low battery level detection
description: ""
use_blueprint:
path: sbyx/low-battery-level-detection-notification-for-all-battery-sensors.yaml
input:
day: 1
time: "21:00:00"
actions:
- service: notify.pushover_zorruno
data:
message: >
Weekly Battery Check. Low Devices:
{{sensors}}
title: View Road Sensors
@@ -1,32 +0,0 @@
- id: notify_if_batteries_are_low
alias: Daily alert that zigbee batteries Are Low
initial_state: true
trigger:
- platform: time
at: "19:00:00"
condition:
- condition: or
conditions:
- condition: template
value_template: >
{% set min_battery_level = 20 -%}
{% set ns = namespace(found=false) -%}
{% for entity_id in states.group.battery_levels.attributes.entity_id -%}
{% set parts = entity_id.split('.') -%}
{% if (states(entity_id) | replace("%","") | int) < min_battery_level -%}
{% set ns.found = true -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
action:
- service: notify.pushover_zorruno
data:
title: View Road Sensors
message: >
{%- set min_battery_level = 20 -%}
{%- for entity_id in states.group.battery_levels.attributes.entity_id -%}
{%- set parts = entity_id.split('.') -%}
{%- if (states(entity_id) | replace("%","") | int) < min_battery_level -%}
{{ states[parts[0]][parts[1]].name }} level is {{ states(entity_id) }}%.{{ '\n' }}
{%- endif -%}
{%- endfor -%}
-3
View File
@@ -39,6 +39,3 @@ zha:
salus_provider: true salus_provider: true
inovelli_provider: true inovelli_provider: true
thirdreality_provider: true thirdreality_provider: true
device_tracker:
- platform: bluetooth_le_tracker
-137
View File
@@ -1,137 +0,0 @@
"""Quirk for TS0207 rain sensors."""
import zigpy.types as t
from typing import Any, Type
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice, CustomCluster
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Identify,
OnOff,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zigpy.zcl.clusters.lightlink import LightLink
from zigpy.zcl.clusters.security import IasZone
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya.mcu import TuyaMCUCluster
from zhaquirks.tuya import (
TuyaManufCluster,
DPToAttributeMapping,
EnchantedDevice,
TuyaNoBindPowerConfigurationCluster,
)
ZONE_TYPE = 0x0001
class TuyaSolarRainSensorCluster(TuyaMCUCluster):
"""Tuya manufacturer cluster."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
0xEF65: ("light_intensity", t.uint32_t, True),
0xEF66: ("average_light_intensity_20mins", t.uint32_t, True),
0xEF67: ("todays_max_light_intensity", t.uint32_t, True),
0xEF68: ("cleaning_reminder", t.Bool, True),
0xEF69: ("rain_sensor_voltage", t.uint32_t, True),
}
)
dp_to_attribute: dict[int, DPToAttributeMapping] = {
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"light_intensity",
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"average_light_intensity_20mins",
),
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"todays_max_light_intensity",
),
104: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"cleaning_reminder",
),
105: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"rain_sensor_voltage",
),
}
data_point_handlers = {
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
103: "_dp_2_attr_update",
104: "_dp_2_attr_update",
105: "_dp_2_attr_update",
}
class TuyaIasZone(CustomCluster, IasZone):
"""IAS Zone for rain sensors."""
_CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Water_Sensor}
class TuyaSolarRainSensor(EnchantedDevice):
"""TS0207 Rain sensor quirk."""
signature = {
MODELS_INFO: [("_TZ3210_tgvtvdoc", "TS0207")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
IasZone.cluster_id,
TuyaMCUCluster.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
OnOff.cluster_id,
Time.cluster_id,
Ota.cluster_id,
LightLink.cluster_id,
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaNoBindPowerConfigurationCluster,
TuyaIasZone,
TuyaSolarRainSensorCluster,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
-358
View File
@@ -1,358 +0,0 @@
# rivsc https://blog.rivsc.ovh
import logging
from typing import Final
from zigpy.quirks.v2 import QuirkBuilder, BinarySensorDeviceClass
import zigpy.types as t
from zigpy.zcl.foundation import ZCLAttributeDef
from zigpy.zcl.clusters.measurement import (
IlluminanceMeasurement,
OccupancySensing,
)
from zigpy.zcl.clusters.security import IasZone
from zigpy.quirks.v2.homeassistant import EntityPlatform, EntityType
from zhaquirks.tuya import (
TuyaLocalCluster,
TuyaPowerConfigurationCluster2AAA,
)
from zhaquirks.tuya.mcu import TuyaMCUCluster, DPToAttributeMapping
class PresenceState(t.enum8):
"""Presence State values"""
none = 0x00
presence = 0x01
peaceful = 0x02
small_movement = 0x03
large_movement = 0x04
class TuyaOccupancySensing(OccupancySensing, TuyaLocalCluster):
"""Tuya local OccupancySensing cluster."""
class TuyaIlluminanceMeasurement(IlluminanceMeasurement, TuyaLocalCluster):
"""Tuya local IlluminanceMeasurement cluster."""
class HumanPresenceSensorManufCluster(TuyaMCUCluster):
"""Human Presence Sensor ZG-205Z (5.8GHz)"""
class AttributeDefs(TuyaMCUCluster.AttributeDefs):
"""Tuya DataPoints attributes"""
# Presence state
presence_state: Final = ZCLAttributeDef(
id=0x0001, # DP 1
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# Target distance
target_distance: Final = ZCLAttributeDef(
id=0x0101, # DP 101
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# Illuminance value
illuminance_lux: Final = ZCLAttributeDef(
id=0x0102, # DP 102
type=t.uint16_t,
access="rp",
is_manufacturer_specific=True,
)
# None delay time (presence keep time)
none_delay_time: Final = ZCLAttributeDef(
id=0x0103, # DP 103
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Indicator
indicator: Final = ZCLAttributeDef(
id=0x0104, # DP 104
type=t.Bool,
is_manufacturer_specific=True,
)
# Move detection max distance
move_detection_max: Final = ZCLAttributeDef(
id=0x0107, # DP 107
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Move detection min distance
move_detection_min: Final = ZCLAttributeDef(
id=0x0108, # DP 108
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Breath detection max distance
breath_detection_max: Final = ZCLAttributeDef(
id=0x0109, # DP 109
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Breath detection min distance
breath_detection_min: Final = ZCLAttributeDef(
id=0x0110, # DP 110
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Small move detection max distance
small_move_detection_max: Final = ZCLAttributeDef(
id=0x0114, # DP 114
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Small move detection min distance
small_move_detection_min: Final = ZCLAttributeDef(
id=0x0115, # DP 115
type=t.uint16_t,
is_manufacturer_specific=True,
)
# Move sensitivity
move_sensitivity: Final = ZCLAttributeDef(
id=0x0116, # DP 116
type=t.uint8_t,
is_manufacturer_specific=True,
)
# Small move sensitivity
small_move_sensitivity: Final = ZCLAttributeDef(
id=0x0117, # DP 117
type=t.uint8_t,
is_manufacturer_specific=True,
)
# Breath sensitivity
breath_sensitivity: Final = ZCLAttributeDef(
id=0x0118, # DP 118
type=t.uint8_t,
is_manufacturer_specific=True,
)
dp_to_attribute: dict[int, DPToAttributeMapping] = {
1: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"presence_state",
converter=PresenceState
),
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"target_distance",
converter=lambda x: x / 100 if x is not None else 0
),
102: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"illuminance_lux",
),
103: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"none_delay_time",
converter=lambda x: x if x is not None else 30
),
104: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"indicator",
),
107: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"move_detection_max",
converter=lambda x: x / 100 if x is not None else 10
),
108: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"move_detection_min",
converter=lambda x: x / 100 if x is not None else 0
),
109: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"breath_detection_max",
converter=lambda x: x / 100 if x is not None else 6
),
110: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"breath_detection_min",
converter=lambda x: x / 100 if x is not None else 0
),
114: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"small_move_detection_max",
converter=lambda x: x / 100 if x is not None else 6
),
115: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"small_move_detection_min",
converter=lambda x: x / 100 if x is not None else 0
),
116: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"move_sensitivity",
converter=lambda x: x if x is not None else 5
),
117: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"small_move_sensitivity",
converter=lambda x: x if x is not None else 5
),
118: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"breath_sensitivity",
converter=lambda x: x if x is not None else 5
),
}
data_point_handlers = {
1: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
103: "_dp_2_attr_update",
104: "_dp_2_attr_update",
107: "_dp_2_attr_update",
108: "_dp_2_attr_update",
109: "_dp_2_attr_update",
110: "_dp_2_attr_update",
114: "_dp_2_attr_update",
115: "_dp_2_attr_update",
116: "_dp_2_attr_update",
117: "_dp_2_attr_update",
118: "_dp_2_attr_update",
}
(
QuirkBuilder("_TZE204_dapwryy7", "TS0601")
.skip_configuration()
.removes(IasZone.cluster_id)
.adds(HumanPresenceSensorManufCluster)
#.adds(TuyaOccupancySensing)
.replaces(TuyaPowerConfigurationCluster2AAA)
.replaces(TuyaIlluminanceMeasurement)
.binary_sensor(
"presence_state",
HumanPresenceSensorManufCluster.cluster_id,
endpoint_id=1,
#entity_type=EntityType.STANDARD, # very soon (zigpy channel #dev on github
device_class=BinarySensorDeviceClass.OCCUPANCY,
fallback_name="Presence"
)
.enum(
HumanPresenceSensorManufCluster.AttributeDefs.presence_state.name,
PresenceState,
HumanPresenceSensorManufCluster.cluster_id,
entity_platform=EntityPlatform.SENSOR,
entity_type=EntityType.STANDARD,
fallback_name="Presence State",
translation_key="presence_state"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.target_distance.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=10,
#unit="m", # fail :/
fallback_name="Target Distance (m)",
translation_key="target_distance",
#entity_type=EntityType.STANDARD # not yet :/
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.none_delay_time.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=28800,
unit="s",
fallback_name="Hold Delay Time",
translation_key="none_delay_time"
)
.switch(
HumanPresenceSensorManufCluster.AttributeDefs.indicator.name,
HumanPresenceSensorManufCluster.cluster_id,
fallback_name="LED Indicator",
translation_key="indicator"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.move_detection_max.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=10,
#unit="m",
fallback_name="Move Detection Max Distance (m)",
translation_key="move_detection_max"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.move_detection_min.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=10,
#unit="m",
fallback_name="Move Detection Min Distance (m)",
translation_key="move_detection_min"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_max.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Small Move Detection Max Distance (m)",
translation_key="small_move_detection_max"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.small_move_detection_min.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Small Move Detection Min Distance (m)",
translation_key="small_move_detection_min"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_max.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Breath Detection Max Distance (m)",
translation_key="breath_detection_max"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.breath_detection_min.name,
HumanPresenceSensorManufCluster.cluster_id,
step=0.01,
min_value=0,
max_value=6,
#unit="m",
fallback_name="Breath Detection Min Distance (m)",
translation_key="breath_detection_min"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.move_sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Move Sensitivity",
translation_key="move_sensitivity"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.small_move_sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Small Move Sensitivity",
translation_key="small_move_sensitivity"
)
.number(
HumanPresenceSensorManufCluster.AttributeDefs.breath_sensitivity.name,
HumanPresenceSensorManufCluster.cluster_id,
step=1,
min_value=0,
max_value=10,
fallback_name="Breath Sensitivity",
translation_key="breath_sensitivity"
)
.add_to_registry()
)
@@ -1,293 +0,0 @@
"""Tuya Din Power Meter."""
from zigpy.profiles import zha
import zigpy.types as t
from zigpy.zcl.clusters.general import Basic, Groups, Ota, Scenes, Time
from zigpy.zcl.clusters.homeautomation import ElectricalMeasurement
from zigpy.zcl.clusters.smartenergy import Metering
from zhaquirks import Bus, LocalDataCluster
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import TuyaManufClusterAttributes, TuyaOnOff, TuyaSwitch
TUYA_TOTAL_ENERGY_ATTR = 0x0211
TUYA_CURRENT_ATTR = 0x0212
TUYA_POWER_ATTR = 0x0213
TUYA_VOLTAGE_ATTR = 0x0214
TUYA_DIN_SWITCH_ATTR = 0x0101
SWITCH_EVENT = "switch_event"
"""Hiking Power Meter Attributes"""
HIKING_DIN_SWITCH_ATTR = 0x0110
HIKING_TOTAL_ENERGY_DELIVERED_ATTR = 0x0201
HIKING_TOTAL_ENERGY_RECEIVED_ATTR = 0x0266
HIKING_VOLTAGE_CURRENT_ATTR = 0x0006
HIKING_POWER_ATTR = 0x0267
HIKING_FREQUENCY_ATTR = 0x0269
HIKING_POWER_FACTOR_ATTR = 0x026F
HIKING_TOTAL_REACTIVE_ATTR = 0x026D
HIKING_REACTIVE_POWER_ATTR = 0x026E
class TuyaManufClusterDinPower(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the Tuya Power Meter device."""
attributes = {
TUYA_TOTAL_ENERGY_ATTR: ("energy", t.uint32_t, True),
TUYA_CURRENT_ATTR: ("current", t.int16s, True),
TUYA_POWER_ATTR: ("power", t.uint16_t, True),
TUYA_VOLTAGE_ATTR: ("voltage", t.uint16_t, True),
TUYA_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == TUYA_TOTAL_ENERGY_ATTR:
self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100)
elif attrid == TUYA_CURRENT_ATTR:
self.endpoint.electrical_measurement.current_reported(value)
elif attrid == TUYA_POWER_ATTR:
self.endpoint.electrical_measurement.power_reported(value / 10)
elif attrid == TUYA_VOLTAGE_ATTR:
self.endpoint.electrical_measurement.voltage_reported(value / 10)
elif attrid == TUYA_DIN_SWITCH_ATTR:
self.endpoint.device.switch_bus.listener_event(
SWITCH_EVENT, self.endpoint.endpoint_id, value
)
class TuyaPowerMeasurement(LocalDataCluster, ElectricalMeasurement):
"""Custom class for power, voltage and current measurement."""
POWER_ID = 0x050B
VOLTAGE_ID = 0x0505
CURRENT_ID = 0x0508
REACTIVE_POWER_ID = 0x050E
AC_FREQUENCY_ID = 0x0300
TOTAL_REACTIVE_POWER_ID = 0x0305
POWER_FACTOR_ID = 0x0510
AC_CURRENT_MULTIPLIER = 0x0602
AC_CURRENT_DIVISOR = 0x0603
AC_FREQUENCY_MULTIPLIER = 0x0400
AC_FREQUENCY_DIVISOR = 0x0401
_CONSTANT_ATTRIBUTES = {
AC_CURRENT_MULTIPLIER: 1,
AC_CURRENT_DIVISOR: 1000,
AC_FREQUENCY_MULTIPLIER: 1,
AC_FREQUENCY_DIVISOR: 100,
}
def voltage_reported(self, value):
"""Voltage reported."""
self._update_attribute(self.VOLTAGE_ID, value)
def power_reported(self, value):
"""Power reported."""
self._update_attribute(self.POWER_ID, value)
def power_factor_reported(self, value):
"""Power Factor reported."""
self._update_attribute(self.POWER_FACTOR_ID, value)
def reactive_power_reported(self, value):
"""Reactive Power reported."""
self._update_attribute(self.REACTIVE_POWER_ID, value)
def current_reported(self, value):
"""Ampers reported."""
self._update_attribute(self.CURRENT_ID, value)
def frequency_reported(self, value):
"""AC Frequency reported."""
self._update_attribute(self.AC_FREQUENCY_ID, value)
def reactive_energy_reported(self, value):
"""Summation Reactive Energy reported."""
self._update_attribute(self.TOTAL_REACTIVE_POWER_ID, value)
class TuyaElectricalMeasurement(LocalDataCluster, Metering):
"""Custom class for total energy measurement."""
CURRENT_DELIVERED_ID = 0x0000
CURRENT_RECEIVED_ID = 0x0001
POWER_WATT = 0x0000
"""Setting unit of measurement."""
_CONSTANT_ATTRIBUTES = {0x0300: POWER_WATT}
def energy_deliver_reported(self, value):
"""Summation Energy Deliver reported."""
self._update_attribute(self.CURRENT_DELIVERED_ID, value)
def energy_receive_reported(self, value):
"""Summation Energy Receive reported."""
self._update_attribute(self.CURRENT_RECEIVED_ID, value)
class HikingManufClusterDinPower(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the Hiking Power Meter device."""
attributes = {
HIKING_DIN_SWITCH_ATTR: ("switch", t.uint8_t, True),
HIKING_TOTAL_ENERGY_DELIVERED_ATTR: ("energy_delivered", t.uint32_t, True),
HIKING_TOTAL_ENERGY_RECEIVED_ATTR: ("energy_received", t.uint16_t, True),
HIKING_VOLTAGE_CURRENT_ATTR: ("voltage_current", t.uint32_t, True),
HIKING_POWER_ATTR: ("power", t.uint16_t, True),
HIKING_FREQUENCY_ATTR: ("frequency", t.uint16_t, True),
HIKING_TOTAL_REACTIVE_ATTR: ("total_reactive_energy", t.int32s, True),
HIKING_REACTIVE_POWER_ATTR: ("reactive_power", t.int16s, True),
HIKING_POWER_FACTOR_ATTR: ("power_factor", t.uint16_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == HIKING_DIN_SWITCH_ATTR:
self.endpoint.device.switch_bus.listener_event(SWITCH_EVENT, 16, value)
elif attrid == HIKING_TOTAL_ENERGY_DELIVERED_ATTR:
self.endpoint.smartenergy_metering.energy_deliver_reported(value / 100)
elif attrid == HIKING_TOTAL_ENERGY_RECEIVED_ATTR:
self.endpoint.smartenergy_metering.energy_receive_reported(value / 100)
elif attrid == HIKING_VOLTAGE_CURRENT_ATTR:
self.endpoint.electrical_measurement.current_reported(value >> 16)
self.endpoint.electrical_measurement.voltage_reported(
(value & 0x0000FFFF) / 10
)
elif attrid == HIKING_POWER_ATTR:
self.endpoint.electrical_measurement.power_reported(value)
elif attrid == HIKING_FREQUENCY_ATTR:
self.endpoint.electrical_measurement.frequency_reported(value)
elif attrid == HIKING_TOTAL_REACTIVE_ATTR:
self.endpoint.electrical_measurement.reactive_energy_reported(value)
elif attrid == HIKING_REACTIVE_POWER_ATTR:
self.endpoint.electrical_measurement.reactive_power_reported(value)
elif attrid == HIKING_POWER_FACTOR_ATTR:
self.endpoint.electrical_measurement.power_factor_reported(value / 10)
class TuyaPowerMeter(TuyaSwitch):
"""Tuya power meter device."""
def __init__(self, *args, **kwargs):
"""Init device."""
self.switch_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
# "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
# maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
# maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
# device_version=1
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]
MODELS_INFO: [
("_TZE200_byzdayie", "TS0601"),
("_TZE200_ewxhg6o9", "TS0601"),
],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=51
# device_version=1
# input_clusters=[0, 4, 5, 61184]
# output_clusters=[10, 25]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterAttributes.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterDinPower,
TuyaPowerMeasurement,
TuyaElectricalMeasurement,
TuyaOnOff,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
}
class HikingPowerMeter(TuyaSwitch):
"""Hiking Power Meter Device - DDS238-2."""
signature = {
# "node_descriptor": "<NodeDescriptor byte1=1 byte2=64 mac_capability_flags=142 manufacturer_code=4098
# maximum_buffer_size=82 maximum_incoming_transfer_size=82 server_mask=11264
# maximum_outgoing_transfer_size=82 descriptor_capability_field=0>",
# device_version=1
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]
MODELS_INFO: [
("_TZE200_bkkmqmyo", "TS0601"),
("_TZE204_81yrt3lo", "TS0601"),
],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=51
# device_version=1
# input_clusters=[0, 4, 5, 61184]
# output_clusters=[10, 25]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufClusterAttributes.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
HikingManufClusterDinPower,
TuyaElectricalMeasurement,
TuyaPowerMeasurement,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
},
16: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
TuyaOnOff,
],
OUTPUT_CLUSTERS: [],
},
}
}
-175
View File
@@ -1,175 +0,0 @@
"""
DY-YG400A Smoke Sensor, TS0601 from _TZE204_ntcy3xu1
https://www.aliexpress.com/item/1005005863519099.html
https://www.aliexpress.com/item/1005005854203557.html
"""
import logging
import zigpy.profiles.zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Ota,
Scenes,
Time,
BinaryInput,
BatterySize,
)
from zigpy.zcl.clusters.security import IasZone
from zhaquirks import Bus
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
ZONE_TYPE,
)
from zhaquirks.tuya import (
TuyaLocalCluster,
TuyaManufCluster,
TuyaManufClusterAttributes,
TuyaPowerConfigurationCluster,
)
_LOGGER = logging.getLogger(__name__)
TUYA_SMOKE_DETECTED_ATTR = 0x0401 # [0]/[1] [Detected]/[Clear]!
TUYA_SMOKE_TAMPERED_ATTR = 0x0104 # [0]/[1] [Clear]/[Tampered]!
TUYA_SMOKE_BATTERY_ATTR = 0x040e # [0]/[1]/[2] [Low]/[Med]]/[Full]!
class TuyaSmokeDetectorCluster(TuyaManufClusterAttributes):
"""Manufacturer Specific Cluster of the TS0601 smoke detector."""
attributes = {
TUYA_SMOKE_DETECTED_ATTR: ("smoke_detected", t.uint8_t, True),
TUYA_SMOKE_TAMPERED_ATTR: ("tampered_device", t.uint8_t, True),
TUYA_SMOKE_BATTERY_ATTR: ("battery_status", t.uint8_t, True),
}
def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == TUYA_SMOKE_DETECTED_ATTR:
status = IasZone.ZoneStatus.Alarm_1 if value == 0 else 0
self.endpoint.device.ias_bus.listener_event("update_attribute", "zone_status", status)
elif attrid == TUYA_SMOKE_TAMPERED_ATTR:
self.endpoint.device.tamper_detection_bus.listener_event(
"update_attribute", "present_value", bool(value)
)
elif attrid == TUYA_SMOKE_BATTERY_ATTR:
batt = 5 if value == 0 else 40 if value == 1 else 100
self.endpoint.device.battery_bus.listener_event("battery_change", batt)
else:
_LOGGER.warning(
"[0x%04x:%s:0x%04x] unhandled attribute: 0x%04x",
self.endpoint.device.nwk,
self.endpoint.endpoint_id,
self.cluster_id,
attrid,
)
class TuyaIasZone(TuyaLocalCluster, IasZone):
"""
IAS Zone: this generates the "Smoke" entity for HA.
Receives updates from TuyaSmokeDetectorCluster.
"""
_CONSTANT_ATTRIBUTES = {ZONE_TYPE: IasZone.ZoneType.Fire_Sensor}
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.ias_bus.add_listener(self)
class TuyaPowerConfigCluster(TuyaPowerConfigurationCluster):
"""
Power Config Cluster: this generates the "Battery" entity for HA.
Receives updates from TuyaSmokeDetectorCluster.
"""
_CONSTANT_ATTRIBUTES = {
TuyaPowerConfigurationCluster.attributes_by_name["battery_size"].id: BatterySize.AAA,
TuyaPowerConfigurationCluster.attributes_by_name["battery_quantity"].id: 2,
TuyaPowerConfigurationCluster.attributes_by_name["battery_rated_voltage"].id: 15,
}
class TuyaTamperDetection(TuyaLocalCluster, BinaryInput):
"""
Tamper Detection Cluster: this generates the "Binary input" entity for HA, which is updated
with the Tampered state. Receives updates from TuyaSmokeDetectorCluster.
"""
_CONSTANT_ATTRIBUTES = {
BinaryInput.attributes_by_name["description"].id: "Tamper Detected",
BinaryInput.attributes_by_name["active_text"].id: "Tampered",
BinaryInput.attributes_by_name["inactive_text"].id: "Clear",
}
def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self.endpoint.device.tamper_detection_bus.add_listener(self)
class TuyaSmokeDetector0601(CustomDevice):
"""TS0601 Smoke detector quirk."""
def __init__(self, *args, **kwargs):
"""Init."""
self.ias_bus = Bus()
self.battery_bus = Bus()
self.tamper_detection_bus = Bus()
super().__init__(*args, **kwargs)
signature = {
MODELS_INFO: [
"""("_TZE200_uebojraa", "TS0601"),"""
("_TZE200_uebojraa"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
DEVICE_TYPE: zigpy.profiles.zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zigpy.profiles.zha.PROFILE_ID,
DEVICE_TYPE: zigpy.profiles.zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaIasZone,
TuyaSmokeDetectorCluster,
TuyaPowerConfigCluster,
TuyaTamperDetection,
],
OUTPUT_CLUSTERS: [
Time.cluster_id,
Ota.cluster_id,
],
},
},
}
@@ -1,151 +0,0 @@
"""Tuya valve devices."""
import logging
from typing import Dict
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
import zigpy.types as t
from zigpy.zcl import foundation
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Ota,
PowerConfiguration,
Scenes,
Time,
)
from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)
from zhaquirks.tuya import NoManufacturerCluster
from zhaquirks.tuya.mcu import (
DPToAttributeMapping,
TuyaAttributesCluster,
TuyaDPType,
TuyaLevelControl,
TuyaMCUCluster,
TuyaOnOff,
TuyaOnOffNM,
TuyaPowerConfigurationCluster,
)
_LOGGER = logging.getLogger(__name__)
class TuyaLevelControlNM(NoManufacturerCluster, TuyaLevelControl):
"""Tuya LevelControl cluster with NoManufacturerID."""
class TuyaValveManufCluster(TuyaMCUCluster):
"""Tuya valve manufacturer cluster."""
attributes = TuyaMCUCluster.attributes.copy()
attributes.update(
{
0xEF03: ("dp_3", t.uint32_t, True),
0xEF65: ("dp_101", t.uint32_t, True),
0xEF66: ("dp_102", t.uint32_t, True), # <-- new cluster attribute with fake IDs (102=0x66).Format= ID: ("name", type, True)
}
)
dp_to_attribute: Dict[int, DPToAttributeMapping] = {
2: DPToAttributeMapping(
TuyaOnOffNM.ep_attribute,
"on_off",
dp_type=TuyaDPType.BOOL,
),
1: DPToAttributeMapping(
TuyaLevelControlNM.ep_attribute,
"current_level",
dp_type=TuyaDPType.VALUE,
converter=lambda x: (x * 255) // 100,
dp_converter=lambda x: (x * 100) // 255,
),
3: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"dp_3",
dp_type=TuyaDPType.VALUE,
),
101: DPToAttributeMapping(
TuyaMCUCluster.ep_attribute,
"dp_101",
dp_type=TuyaDPType.VALUE,
),
102: DPToAttributeMapping( # <-- device DP102
TuyaMCUCluster.ep_attribute, # <-- reference to the cluster which has the attribute
"dp_102", # <-- attribute "name"
dp_type=TuyaDPType.VALUE, # <-- DP Type it is related to the attribute type
),
108: DPToAttributeMapping(
TuyaPowerConfigurationCluster.ep_attribute,
"battery_percentage_remaining",
dp_type=TuyaDPType.VALUE,
),
}
data_point_handlers = {
2: "_dp_2_attr_update",
1: "_dp_2_attr_update",
3: "_dp_2_attr_update",
101: "_dp_2_attr_update",
102: "_dp_2_attr_update",
108: "_dp_2_attr_update",
}
async def write_attributes(self, attributes, manufacturer=None):
"""Overwrite to force manufacturer code."""
return await super().write_attributes(
attributes, manufacturer=foundation.ZCLHeader.NO_MANUFACTURER_ID
)
class TuyaValve(CustomDevice):
"""Tuya valve device."""
signature = {
"""MODELS_INFO: [("_TZE200_sh1btabb", "TS0601")],"""
MODELS_INFO: [("_TZE200_sh1btabb")],
ENDPOINTS: {
# <SimpleDescriptor endpoint=1 profile=260 device_type=0x0051
# input_clusters=[0x0000, 0x0004, 0x0005, 0xef00]
# output_clusters=[0x000a, 0x0019]>
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.SMART_PLUG,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaValveManufCluster.cluster_id,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
},
}
replacement = {
ENDPOINTS: {
1: {
DEVICE_TYPE: zha.DeviceType.ON_OFF_LIGHT,
INPUT_CLUSTERS: [
Basic.cluster_id,
Groups.cluster_id,
Scenes.cluster_id,
TuyaValveManufCluster,
TuyaOnOffNM,
TuyaLevelControlNM,
TuyaPowerConfigurationCluster,
],
OUTPUT_CLUSTERS: [Time.cluster_id, Ota.cluster_id],
}
}
}
+2625 -879
View File
File diff suppressed because it is too large Load Diff
+266 -79
View File
@@ -6,79 +6,119 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-3dprinterpow.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-3dprinterpow.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V1.1 2026-02-25 Updated yaml to zorruno layout V1.1 # V1.3 2026-07-12
# V1.0 2025-06-29 Initial Version # - Added cumulative Load Energy integration sensor for Home Assistant.
# - Added seven-day maximum power sensor.
# - Maximum power resets after 168 powered hours.
# - Added a single-process compile limit to reduce Builder memory use.
#
# V1.2 2026-07-11
# - Removed the optimistic template relay switch.
# - Exposed the actual GPIO12 relay directly to Home Assistant.
# - Physical button and MQTT commands now control the same relay entity.
# - Status LED is now synchronised from the real relay state callbacks.
# - Prevents the Home Assistant switch state becoming desynchronised from the relay.
#
# V1.1 2026-02-25
# - Updated YAML to zorruno layout V1.1.
#
# V1.0 2025-06-29
# - Initial version.
#:########################################################################################:# #:########################################################################################:#
# HARDWARE: # HARDWARE:
# Sonoff POW R1 (ESP8266) # Sonoff POW R1 (ESP8266)
# https://devices.esphome.io/devices/Sonoff-POW-R1 # https://devices.esphome.io/devices/Sonoff-POW-R1
#:########################################################################################:# #:########################################################################################:#
# OPERATION NOTES: # OPERATION NOTES:
# - Inline power monitor (HLW8012) plus relay control for the 3D printer power feed # - Inline power monitor using the HLW8012.
# - Local MQTT control (ON/OFF) without needing Home Assistant # - Reports current, voltage, instantaneous power, and cumulative energy.
# - Publishes current relay state on boot (burst) and on relay changes # - Load Energy integrates measured power over time and reports kWh.
# - "Running" binary sensor is derived from measured power > 4 W (with delayed_off) # - Load Energy resets to zero if the ESPHome device reboots.
# - Home Assistant handles the reset because the sensor uses total_increasing.
# - Load Maximum Power 7 Days records the highest measured power.
# - Maximum power resets after 168 powered hours.
# - Maximum power and the elapsed-hour counter are retained across reboots.
# - The maximum value is checked for saving every five minutes.
# - The elapsed-hour counter is checked for saving every six hours.
# - An unexpected power loss may extend the peak reset period by up to about six hours.
# - GPIO12 directly controls the 3D printer power relay.
# - The physical button toggles the real relay.
# - Home Assistant directly controls and reports the real relay state.
# - Local MQTT ON/OFF control works without needing Home Assistant.
# - The status LED follows the actual relay state.
# - Publishes current relay state on boot and whenever the relay changes.
# - "Running" binary sensor is derived from measured power greater than 4 W.
#:########################################################################################:# #:########################################################################################:#
# MQTT COMMANDS: # MQTT COMMANDS:
# - Command topic (local): # - Command topic:
# ${mqtt_local_command_topic} # ${mqtt_local_command_topic}
# Payload "ON" -> relay ON + status LED ON
# Payload "OFF" -> relay OFF + status LED OFF
# #
# - Status topic (local): # Payload "ON" -> relay ON
# Payload "OFF" -> relay OFF
#
# - Status topic:
# ${mqtt_local_status_topic} # ${mqtt_local_status_topic}
# Payload "ON"/"OFF" published on relay state changes, plus a short burst on boot #
# Payload "ON"/"OFF" is published:
# - whenever the actual relay state changes;
# - as a short status burst following boot.
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE # a) Home Assistant OFFLINE, but Network and MQTT ONLINE
# - Device remains controllable via MQTT local command topic # - Device remains controllable through the MQTT local command topic.
# - Status publishes to MQTT still occur (relay state burst + changes) # - Relay status changes continue to publish through MQTT.
# - HA entities are unavailable until HA returns # - Energy integration and maximum power monitoring continue locally.
# b) MQTT OFFLINE (but WiFi/Network and HA API ONLINE) # - Home Assistant entities remain unavailable until HA returns.
# - Device remains controllable from Home Assistant via API (if enabled) #
# - Local MQTT commands will not work and status publishes will fail while MQTT is down # b) MQTT OFFLINE, but WiFi/Network and HA API ONLINE
# - Device remains controllable from Home Assistant through the native API.
# - Local MQTT commands do not work while MQTT is unavailable.
# - Energy integration and maximum power monitoring continue locally.
# - MQTT status publishing resumes when MQTT reconnects.
#
# c) Entire WiFi/Network OFFLINE # c) Entire WiFi/Network OFFLINE
# - No HA API and no MQTT control available # - No Home Assistant API or MQTT control is available.
# - Local button still toggles the relay (and status LED follows) # - The local physical button continues to toggle the relay.
# - Accurate time is NOT needed (SNTP not needed) # - The status LED continues to follow the real relay state.
# - Energy integration and maximum power monitoring continue locally.
# - Accurate time is not needed for the seven-day powered-time reset.
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If not using a secrets file, replace these with the required values in quotes.
#:########################################################################################:# #:########################################################################################:#
substitutions: substitutions:
# Device Naming # Device Naming
device_name: "esp-3dprinterpow" device_name: "esp-3dprinterpow"
friendly_name: "3D Printer Power" friendly_name: "3D Printer Power"
description_comment: "3D Printer inline monitor and switch override using a Sonoff Pow R1. (Layout V1.1)" description_comment: "3D Printer inline monitor and switch override using a Sonoff POW R1. (Layout V1.1)"
device_area: "Underhouse" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. device_area: "Underhouse"
# Project Naming # Project Naming
project_name: "Sonoff Technologies.POW R1" # Project Details project_name: "Sonoff Technologies.POW R1"
project_version: "v1.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version project_version: "v1.3"
# Passwords & Secrets # Passwords and Secrets
api_key: !secret esp-api_key api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass ota_pass: !secret esp-ota_pass
static_ip_address: !secret esp-3dprinterpow_ip # unfortunately you can't use substitutions inside secrets names static_ip_address: !secret esp-3dprinterpow_ip
mqtt_command_main_topic: !secret mqtt_command_main_topic mqtt_command_main_topic: !secret mqtt_command_main_topic
mqtt_status_main_topic: !secret mqtt_status_main_topic mqtt_status_main_topic: !secret mqtt_status_main_topic
# If we are changing IP addresses, you must update the current IP address here, otherwise it remains # If changing IP addresses, update the current IP address here.
# Don't forget to switch it back when changed. # Switch it back once the address change is complete.
current_ip_address: ${static_ip_address} current_ip_address: ${static_ip_address}
# MQTT LOCAL Controls # MQTT Local Controls
mqtt_local_device_name: "3dprinter-power" mqtt_local_device_name: "3dprinter-power"
mqtt_local_command_topic: "${mqtt_command_main_topic}/${mqtt_local_device_name}" # Topic we will use to command this locally without HA mqtt_local_command_topic: "${mqtt_command_main_topic}/${mqtt_local_device_name}"
mqtt_local_status_topic: "${mqtt_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status locally without HA mqtt_local_status_topic: "${mqtt_status_main_topic}/${mqtt_local_device_name}"
# Device Settings # Device Settings
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE log_level: "INFO"
update_interval: "60s" # update time for for general sensors etc update_interval: "60s"
entity_prefix: "Load" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current. entity_prefix: "Load"
#:########################################################################################:# #:########################################################################################:#
# PACKAGES: Included Common Packages # PACKAGES: Included Common Packages
@@ -126,14 +166,29 @@ packages:
esphome: esphome:
name: "${device_name}" name: "${device_name}"
friendly_name: "${friendly_name}" friendly_name: "${friendly_name}"
comment: "${description_comment}" # Appears on the esphome page in HA comment: "${description_comment}"
area: "${device_area}" area: "${device_area}"
# Restrict PlatformIO to one simultaneous compiler process to reduce host RAM use.
compile_process_limit: 1
project: project:
name: "${project_name}" name: "${project_name}"
version: "${project_version}" version: "${project_version}"
on_boot: on_boot:
priority: -10 priority: -10
then: then:
# Ensure the status LED reflects the restored physical relay state.
- if:
condition:
switch.is_on: relay
then:
- light.turn_on: led
else:
- light.turn_off: led
# Publish a short MQTT status burst after startup.
- script.execute: publish_state_burst - script.execute: publish_state_burst
#:########################################################################################:# #:########################################################################################:#
@@ -142,112 +197,239 @@ esphome:
#:########################################################################################:# #:########################################################################################:#
esp8266: esp8266:
board: esp01_1m board: esp01_1m
early_pin_init: false # Recommended false where switches are involved. Defaults to true.
board_flash_mode: dout # Default is dout # Recommended false where switches are involved.
early_pin_init: false
# Default is DOUT for this hardware.
board_flash_mode: dout
#:########################################################################################:# #:########################################################################################:#
# LOGGING: ESPHome Logging Enable # LOGGING: ESPHome Logging Enable
# https://esphome.io/components/logger.html # https://esphome.io/components/logger.html
#:########################################################################################:# #:########################################################################################:#
logger: logger:
level: "${log_level}" # INFO Level suggested, or DEBUG for testing level: "${log_level}"
#baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
# Set to 0 if the UART is required for another component.
#baud_rate: 0
#esp8266_store_log_strings_in_flash: false #esp8266_store_log_strings_in_flash: false
#tx_buffer_size: 64 #tx_buffer_size: 64
#:########################################################################################:# #:########################################################################################:#
# MQTT: Device-specific MQTT command triggers # MQTT: Device-specific MQTT command triggers
# This adds device-specific MQTT command triggers to the common MQTT configuration. # Adds local ON/OFF relay control to the common MQTT configuration.
#:########################################################################################:# #:########################################################################################:#
mqtt: mqtt:
on_message: on_message:
# Switch control from MQTT
- topic: "${mqtt_local_command_topic}" - topic: "${mqtt_local_command_topic}"
payload: "ON" payload: "ON"
then: then:
- switch.turn_on: relay - switch.turn_on: relay
- light.turn_on: led
- topic: "${mqtt_local_command_topic}" - topic: "${mqtt_local_command_topic}"
payload: "OFF" payload: "OFF"
then: then:
- switch.turn_off: relay - switch.turn_off: relay
- light.turn_off: led
#:########################################################################################:#
# GLOBALS:
# Retained maximum power and elapsed-hour counter
# https://esphome.io/components/globals.html
#:########################################################################################:#
globals:
# Highest measured power during the current seven-day period.
# Changed values are checked for saving every five minutes.
- id: max_power_7d
type: float
restore_value: true
initial_value: "0.0"
update_interval: 5min
# Powered hours elapsed during the current seven-day period.
# Changed values are checked for saving every six hours to reduce flash writes.
- id: max_power_hours_elapsed
type: uint16_t
restore_value: true
initial_value: "0"
update_interval: 6h
#:########################################################################################:# #:########################################################################################:#
# BINARY SENSORS: # BINARY SENSORS:
# https://esphome.io/components/binary_sensor/ # https://esphome.io/components/binary_sensor/
#:########################################################################################:# #:########################################################################################:#
binary_sensor: binary_sensor:
# Physical Sonoff button.
- platform: gpio - platform: gpio
name: "Button"
pin: pin:
number: GPIO0 number: GPIO0
mode: INPUT_PULLUP mode: INPUT_PULLUP
inverted: true inverted: true
name: "Button"
on_press:
- switch.toggle: virtual_button
on_press:
then:
- switch.toggle: relay
# Indicates when the connected 3D printer is consuming more than 4 W.
- platform: template - platform: template
name: "${entity_prefix} Running" name: "${entity_prefix} Running"
filters: filters:
- delayed_off: 15s - delayed_off: 15s
lambda: |- lambda: |-
if (isnan(id(power).state)) { if (isnan(id(power).state)) {
return {}; return {};
} else if (id(power).state > 4) {
return true;
} else {
return false;
} }
return id(power).state > 4.0f;
#:########################################################################################:# #:########################################################################################:#
# SENSORS COMPONENT: # SENSOR COMPONENT:
# https://esphome.io/components/sensor/ # https://esphome.io/components/sensor/
#:########################################################################################:# #:########################################################################################:#
sensor: sensor:
- platform: hlw8012 - platform: hlw8012
sel_pin: 5 sel_pin: GPIO5
cf_pin: 14 cf_pin: GPIO14
cf1_pin: 13 cf1_pin: GPIO13
update_interval: 2s update_interval: 2s
current: current:
name: "${entity_prefix} Current" name: "${entity_prefix} Current"
device_class: current
state_class: measurement
unit_of_measurement: "A"
accuracy_decimals: 2
voltage: voltage:
name: "${entity_prefix} Voltage" name: "${entity_prefix} Voltage"
device_class: voltage
state_class: measurement
unit_of_measurement: "V"
accuracy_decimals: 1
power: power:
name: "${entity_prefix} Power" name: "${entity_prefix} Power"
id: power id: power
device_class: power
state_class: measurement
unit_of_measurement: "W"
accuracy_decimals: 1
# Update the seven-day maximum whenever a new peak is measured.
on_value:
then:
- lambda: |-
if (!isnan(x) && x > id(max_power_7d)) {
id(max_power_7d) = x;
id(max_power_7d_sensor).publish_state(x);
ESP_LOGI(
"max_power",
"New seven-day maximum power: %.1f W",
x
);
}
# Integrates instantaneous watts over time.
# Watts integrated over hours produces Wh; the filter converts Wh to kWh.
- platform: integration
name: "${entity_prefix} Energy"
id: energy
sensor: power
time_unit: h
integration_method: trapezoid
restore: false
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3
filters:
- multiply: 0.001
# Highest power recorded during the current 168 powered-hour period.
- platform: template
name: "${entity_prefix} Maximum Power 7 Days"
id: max_power_7d_sensor
unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 1
update_interval: 60s
lambda: |-
return id(max_power_7d);
#:########################################################################################:#
# INTERVAL:
# Seven-day maximum power reset
# https://esphome.io/components/interval.html
#:########################################################################################:#
interval:
# Count one powered hour at a time.
# 168 hours equals seven days.
- interval: 1h
then:
- lambda: |-
id(max_power_hours_elapsed) += 1;
ESP_LOGD(
"max_power",
"Seven-day maximum power period: %u of 168 powered hours elapsed",
id(max_power_hours_elapsed)
);
if (id(max_power_hours_elapsed) >= 168) {
id(max_power_hours_elapsed) = 0;
id(max_power_7d) = 0.0f;
id(max_power_7d_sensor).publish_state(0.0f);
ESP_LOGI(
"max_power",
"Seven-day maximum power value has been reset"
);
}
#:########################################################################################:# #:########################################################################################:#
# SWITCH COMPONENT: # SWITCH COMPONENT:
# https://esphome.io/components/switch/ # https://esphome.io/components/switch/
#:########################################################################################:# #:########################################################################################:#
switch: switch:
- platform: template # The actual GPIO relay is exposed directly to Home Assistant.
name: "Relay" # This is the single authoritative relay state for:
optimistic: true # - Home Assistant API control;
id: virtual_button # - MQTT control;
turn_on_action: # - physical button control;
- switch.turn_on: relay # - relay state restoration;
- light.turn_on: led # - MQTT status publishing;
turn_off_action: # - status LED synchronisation.
- switch.turn_off: relay
- light.turn_off: led
- platform: gpio - platform: gpio
name: "Relay"
id: relay id: relay
pin: GPIO12 pin: GPIO12
restore_mode: RESTORE_DEFAULT_ON
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on: on_turn_on:
- mqtt.publish: then:
topic: "${mqtt_local_status_topic}" - light.turn_on: led
payload: "ON"
retain: false - mqtt.publish:
topic: "${mqtt_local_status_topic}"
payload: "ON"
retain: false
on_turn_off: on_turn_off:
- mqtt.publish: then:
topic: "${mqtt_local_status_topic}" - light.turn_off: led
payload: "OFF"
retain: false - mqtt.publish:
topic: "${mqtt_local_status_topic}"
payload: "OFF"
retain: false
#:########################################################################################:# #:########################################################################################:#
# OUTPUT COMPONENT: # OUTPUT COMPONENT:
@@ -256,6 +438,7 @@ switch:
output: output:
- platform: esp8266_pwm - platform: esp8266_pwm
id: pow_blue_led id: pow_blue_led
pin: pin:
number: GPIO15 number: GPIO15
inverted: true inverted: true
@@ -267,8 +450,8 @@ output:
light: light:
- platform: monochromatic - platform: monochromatic
name: "Status LED" name: "Status LED"
output: pow_blue_led
id: led id: led
output: pow_blue_led
#:########################################################################################:# #:########################################################################################:#
# SCRIPT COMPONENT: # SCRIPT COMPONENT:
@@ -277,13 +460,17 @@ light:
script: script:
- id: publish_state_burst - id: publish_state_burst
mode: restart mode: restart
then: then:
- repeat: - repeat:
count: 5 count: 5
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}" topic: "${mqtt_local_status_topic}"
payload: !lambda 'return id(relay).state ? "ON" : "OFF";' payload: !lambda |-
return id(relay).state ? "ON" : "OFF";
retain: false retain: false
qos: 1 qos: 1
- delay: 5s
- delay: 5s
File diff suppressed because it is too large Load Diff
+39 -39
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedrm1arlecbulb.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedrm1arlecbulb.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v1.3 2026-07-20 Simplified local MQTT command and status topics to the device root
# v1.2 2026-02-25 Updated yaml to zorruno layout V1.1 # v1.2 2026-02-25 Updated yaml to zorruno layout V1.1
# v1.1 2026-02-21 Add "Jump Fade Up/Down" switch + configurable Jump Fade (%) # v1.1 2026-02-21 Add "Jump Fade Up/Down" switch + configurable Jump Fade (%)
# v1.0 2026-02-21 Initial version (ported from Athom bulb YAML to Arlec GLD120HA) # v1.0 2026-02-21 Initial version (ported from Athom bulb YAML to Arlec GLD120HA)
@@ -42,7 +43,6 @@
# - Accurate time is NOT needed (SNTP not needed). # - Accurate time is NOT needed (SNTP not needed).
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -56,7 +56,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Grid Connect GLD120HA RGBWW.Arlec" project_name: "Grid Connect GLD120HA RGBWW.Arlec"
project_version: "v1.2" project_version: "v1.3"
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names) # Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
# Re-using your existing bulb flasher secrets (adjust if you prefer per-device secrets) # Re-using your existing bulb flasher secrets (adjust if you prefer per-device secrets)
@@ -156,32 +156,32 @@ esphome:
# Mode 0: Fade up to full (obeys fade settings; forces Warm endpoint) # Mode 0: Fade up to full (obeys fade settings; forces Warm endpoint)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 0;' lambda: "return id(restart_mode) == 0;"
then: then:
- lambda: 'id(ramp_switch_target_on) = true;' - lambda: "id(ramp_switch_target_on) = true;"
- script.execute: ramp_on_script - script.execute: ramp_on_script
# Mode 1: Restore Brightness (deferred; forces Warm endpoint) # Mode 1: Restore Brightness (deferred; forces Warm endpoint)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 1;' lambda: "return id(restart_mode) == 1;"
then: then:
- script.execute: deferred_restore_brightness - script.execute: deferred_restore_brightness
# Mode 2: Remain Off # Mode 2: Remain Off
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 2;' lambda: "return id(restart_mode) == 2;"
then: then:
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: bulb_light id: bulb_light
transition_length: 0s transition_length: 0s
# Boot complete: allow OFF handlers again # Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;' - lambda: "id(booting) = false;"
#:########################################################################################:# #:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK # ESP PLATFORM AND FRAMEWORK
@@ -301,12 +301,12 @@ logger:
#:########################################################################################:# #:########################################################################################:#
mqtt: mqtt:
on_message: on_message:
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
then: then:
- switch.turn_on: bulb_ramp_switch - switch.turn_on: bulb_ramp_switch
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
then: then:
- switch.turn_off: bulb_ramp_switch - switch.turn_off: bulb_ramp_switch
@@ -536,7 +536,7 @@ light:
on_turn_on: on_turn_on:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -546,20 +546,20 @@ light:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
on_turn_off: on_turn_off:
- if: - if:
condition: condition:
lambda: 'return !id(booting);' lambda: "return !id(booting);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
retain: true retain: true
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- lambda: |- - lambda: |-
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
@@ -667,9 +667,9 @@ number:
set_action: set_action:
- if: - if:
condition: condition:
lambda: 'return x <= 0.0f;' lambda: "return x <= 0.0f;"
then: then:
- lambda: 'id(suppress_slider_sync) = true;' - lambda: "id(suppress_slider_sync) = true;"
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- light.turn_off: - light.turn_off:
@@ -681,7 +681,7 @@ number:
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
id(last_set_pos) = 0; id(last_set_pos) = 0;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
- lambda: |- - lambda: |-
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
@@ -713,7 +713,7 @@ number:
if (out_pct > maxp) out_pct = maxp; if (out_pct > maxp) out_pct = maxp;
id(last_brightness_pct) = out_pct; id(last_brightness_pct) = out_pct;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
- platform: template - platform: template
id: cfg_max_on_hours id: cfg_max_on_hours
@@ -736,12 +736,12 @@ number:
id(cfg_max_on_hours).publish_state((float) hrs); id(cfg_max_on_hours).publish_state((float) hrs);
- if: - if:
condition: condition:
lambda: 'return id(bulb_light).current_values.is_on();' lambda: "return id(bulb_light).current_values.is_on();"
then: then:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -753,7 +753,7 @@ script:
- id: ramp_on_script - id: ramp_on_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
- if: - if:
@@ -766,13 +766,13 @@ script:
- light.turn_on: - light.turn_on:
id: bulb_light id: bulb_light
color_temperature: "${bulb_warm_kelvin}" color_temperature: "${bulb_warm_kelvin}"
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
- light.turn_on: - light.turn_on:
id: bulb_light id: bulb_light
color_temperature: "${bulb_warm_kelvin}" color_temperature: "${bulb_warm_kelvin}"
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;' brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(bulb_light).current_values; const auto &cv = id(bulb_light).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -795,7 +795,7 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(bulb_light).current_values; const auto &cv = id(bulb_light).current_values;
@@ -807,7 +807,7 @@ script:
- id: ramp_to_target_script - id: ramp_to_target_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
- if: - if:
@@ -820,7 +820,7 @@ script:
- light.turn_on: - light.turn_on:
id: bulb_light id: bulb_light
color_temperature: "${bulb_warm_kelvin}" color_temperature: "${bulb_warm_kelvin}"
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
- light.turn_on: - light.turn_on:
@@ -858,7 +858,7 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(bulb_light).current_values; const auto &cv = id(bulb_light).current_values;
@@ -878,7 +878,7 @@ script:
- light.turn_on: - light.turn_on:
id: bulb_light id: bulb_light
color_temperature: "${bulb_warm_kelvin}" color_temperature: "${bulb_warm_kelvin}"
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(bulb_light).current_values; const auto &cv = id(bulb_light).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -895,7 +895,7 @@ script:
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- light.turn_off: - light.turn_off:
id: bulb_light id: bulb_light
transition_length: 150ms transition_length: 150ms
@@ -913,12 +913,12 @@ script:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);' - delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
- if: - if:
condition: condition:
lambda: 'return id(bulb_light).current_values.is_on();' lambda: "return id(bulb_light).current_values.is_on();"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = false; id(ramp_switch_target_on) = false;
@@ -942,17 +942,17 @@ script:
- if: - if:
condition: condition:
lambda: 'return id(restore_target_pct) > 0.5f;' lambda: "return id(restore_target_pct) > 0.5f;"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = true; id(ramp_switch_target_on) = true;
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
- script.stop: ramp_off_script - script.stop: ramp_off_script
- script.execute: ramp_to_target_script - script.execute: ramp_to_target_script
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: bulb_light id: bulb_light
transition_length: 0s transition_length: 0s
@@ -1013,7 +1013,7 @@ script:
- if: - if:
condition: condition:
lambda: 'return id(bulb_light).current_values.is_on();' lambda: "return id(bulb_light).current_values.is_on();"
then: then:
- light.turn_on: - light.turn_on:
id: bulb_light id: bulb_light
@@ -1043,4 +1043,4 @@ script:
else: else:
- light.turn_off: - light.turn_off:
id: bulb_light id: bulb_light
transition_length: 0s transition_length: 0s
File diff suppressed because it is too large Load Diff
+42 -42
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-breakfastbarleds.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-breakfastbarleds.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V2.1 2026-07-20 Simplified local MQTT command and status topics to the device root
# V2.0 2025-09-15 A bunch of fixes and made it more standard across my devices # V2.0 2025-09-15 A bunch of fixes and made it more standard across my devices
# V1.0 2025-08-17 First setup (and replacement of Tasmota) # V1.0 2025-08-17 First setup (and replacement of Tasmota)
#:########################################################################################:# #:########################################################################################:#
@@ -47,10 +48,10 @@
#:########################################################################################:# #:########################################################################################:#
# MQTT COMMANDS: # MQTT COMMANDS:
# Local MQTT light control: # Local MQTT light control:
# - ${mqtt_local_command_topic}/light/set -> ON,OFF # - ${mqtt_local_command_topic} -> ON,OFF
# #
# Local MQTT light status: # Local MQTT light status:
# - ${mqtt_local_status_topic}/light/state -> ON,OFF # - ${mqtt_local_status_topic} -> ON,OFF
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE # a) Home Assistant OFFLINE, but Network and MQTT ONLINE
@@ -80,7 +81,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Magichome Single Colour V1.1 LED Controller.Generic ESP8266" # Manufacturer before the dot, device after the dot. project_name: "Magichome Single Colour V1.1 LED Controller.Generic ESP8266" # Manufacturer before the dot, device after the dot.
project_version: "v2.0" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version project_version: "v2.1" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names) # Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -130,8 +131,7 @@ packages:
local_current_ip_address: "${current_ip_address}" local_current_ip_address: "${current_ip_address}"
#### HOME ASSISTANT API (choose encryption or no encryption options) #### #### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include common_api: !include #file: common/api_common.yaml
#file: common/api_common.yaml
file: common/api_common_noencryption.yaml file: common/api_common_noencryption.yaml
vars: vars:
local_api_key: "${api_key}" local_api_key: "${api_key}"
@@ -178,32 +178,32 @@ esphome:
# Mode 0: Fade up to full (obeys fade settings; no on/off flicker) # Mode 0: Fade up to full (obeys fade settings; no on/off flicker)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 0;' lambda: "return id(restart_mode) == 0;"
then: then:
- lambda: 'id(ramp_switch_target_on) = true;' - lambda: "id(ramp_switch_target_on) = true;"
- script.execute: ramp_on_script - script.execute: ramp_on_script
# Mode 1: Restore Brightness (deferred to avoid races) # Mode 1: Restore Brightness (deferred to avoid races)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 1;' lambda: "return id(restart_mode) == 1;"
then: then:
- script.execute: deferred_restore_brightness - script.execute: deferred_restore_brightness
# Mode 2: Remain Off (no blip; stays off) # Mode 2: Remain Off (no blip; stays off)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 2;' lambda: "return id(restart_mode) == 2;"
then: then:
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
# Boot complete: allow OFF handlers again # Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;' - lambda: "id(booting) = false;"
#:########################################################################################:# #:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK: # ESP PLATFORM AND FRAMEWORK:
@@ -345,13 +345,13 @@ status_led:
mqtt: mqtt:
on_message: on_message:
# Light control to ramp up # Light control to ramp up
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
then: then:
- switch.turn_on: mosfet_ramp_switch - switch.turn_on: mosfet_ramp_switch
# Light control to ramp down # Light control to ramp down
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
then: then:
- switch.turn_off: mosfet_ramp_switch - switch.turn_off: mosfet_ramp_switch
@@ -482,7 +482,7 @@ binary_sensor:
on_press: on_press:
- if: - if:
condition: condition:
lambda: 'return id(ramp_switch_target_on);' lambda: "return id(ramp_switch_target_on);"
then: then:
# Target is currently ON -> press should go OFF (start ramp-down) # Target is currently ON -> press should go OFF (start ramp-down)
- lambda: |- - lambda: |-
@@ -584,7 +584,7 @@ light:
on_turn_on: on_turn_on:
- output.turn_on: green_led_out - output.turn_on: green_led_out
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -595,7 +595,7 @@ light:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -605,13 +605,13 @@ light:
- output.turn_off: green_led_out - output.turn_off: green_led_out
- if: - if:
condition: condition:
lambda: 'return !id(booting);' lambda: "return !id(booting);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
retain: true retain: true
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- lambda: |- - lambda: |-
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
@@ -699,9 +699,9 @@ number:
set_action: set_action:
- if: - if:
condition: condition:
lambda: 'return x <= 0.0f;' lambda: "return x <= 0.0f;"
then: then:
- lambda: 'id(suppress_slider_sync) = true;' - lambda: "id(suppress_slider_sync) = true;"
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- light.turn_off: - light.turn_off:
@@ -713,7 +713,7 @@ number:
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
id(last_set_pos) = 0; id(last_set_pos) = 0;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
- lambda: |- - lambda: |-
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
@@ -744,7 +744,7 @@ number:
if (out_pct > maxp) out_pct = maxp; if (out_pct > maxp) out_pct = maxp;
id(last_brightness_pct) = out_pct; id(last_brightness_pct) = out_pct;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
- platform: template - platform: template
id: cfg_max_on_hours id: cfg_max_on_hours
@@ -767,12 +767,12 @@ number:
id(cfg_max_on_hours).publish_state((float) hrs); id(cfg_max_on_hours).publish_state((float) hrs);
- if: - if:
condition: condition:
lambda: 'return id(mosfet_leds).current_values.is_on();' lambda: "return id(mosfet_leds).current_values.is_on();"
then: then:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -785,7 +785,7 @@ script:
- id: ramp_on_script - id: ramp_on_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
# Ensure we start at at least the floor without a visible "pop". # Ensure we start at at least the floor without a visible "pop".
@@ -798,13 +798,13 @@ script:
then: then:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
# Ramp from current (>= floor) to cap over a fraction of ramp_up_ms. # Ramp from current (>= floor) to cap over a fraction of ramp_up_ms.
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;' brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -825,7 +825,7 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
@@ -838,7 +838,7 @@ script:
- id: ramp_to_target_script - id: ramp_to_target_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
# Ensure we start at the floor cleanly. # Ensure we start at the floor cleanly.
@@ -851,7 +851,7 @@ script:
then: then:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
# Ramp from current (>= floor) to restore_target_pct over a fraction of ramp_up_ms. # Ramp from current (>= floor) to restore_target_pct over a fraction of ramp_up_ms.
@@ -884,7 +884,7 @@ script:
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
@@ -904,7 +904,7 @@ script:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -918,7 +918,7 @@ script:
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 150ms transition_length: 150ms
@@ -935,12 +935,12 @@ script:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);' - delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
- if: - if:
condition: condition:
lambda: 'return id(mosfet_leds).current_values.is_on();' lambda: "return id(mosfet_leds).current_values.is_on();"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = false; id(ramp_switch_target_on) = false;
@@ -967,18 +967,18 @@ script:
- if: - if:
condition: condition:
lambda: 'return id(restore_target_pct) > 0.5f;' lambda: "return id(restore_target_pct) > 0.5f;"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = true; id(ramp_switch_target_on) = true;
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
- script.stop: ramp_off_script - script.stop: ramp_off_script
- script.execute: ramp_to_target_script - script.execute: ramp_to_target_script
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
# Exact last was 0 -> remain OFF # Exact last was 0 -> remain OFF
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
+33 -10
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-centralstairs-bottom.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-centralstairs-bottom.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V3.9 2026-07-21 Added retained MQTT status mirroring for HA-independent two-way switching
# V3.8 2026-06-09 Added Downstairs Lights Bulk Activation virtual switch toggled by Button 1 double-click # V3.8 2026-06-09 Added Downstairs Lights Bulk Activation virtual switch toggled by Button 1 double-click
# V3.7 2025-09-24 Upload to this device # V3.7 2025-09-24 Upload to this device
# V3.5 2025-07-24 YAML tidyups # V3.5 2025-07-24 YAML tidyups
@@ -83,7 +84,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Double" # Project Details project_name: "Zemismart Technologies.KS-811 Double" # Project Details
project_version: "v2.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version project_version: "v3.9" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
# MQTT Controls # MQTT Controls
mqtt_command_main_topic: !secret mqtt_command_main_topic mqtt_command_main_topic: !secret mqtt_command_main_topic
@@ -184,19 +185,19 @@ mqtt:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(Relay_1).state;' lambda: "return id(Relay_1).state;"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_1}" topic: "${mqtt_status_topic_1}"
payload: "${mqtt_command_ON}" payload: "${mqtt_command_ON}"
qos: 0 qos: 1
retain: false retain: true
else: else:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_1}" topic: "${mqtt_status_topic_1}"
payload: "${mqtt_command_OFF}" payload: "${mqtt_command_OFF}"
qos: 0 qos: 1
retain: false retain: true
on_message: on_message:
# Relay 1: only turn ON if currently OFF # Relay 1: only turn ON if currently OFF
@@ -241,6 +242,26 @@ mqtt:
then: then:
- switch.turn_off: Relay_2 - switch.turn_off: Relay_2
# Relay 2 is a mirror of the remote physical Stair Footer Lights relay.
- topic: "${mqtt_status_topic_2}"
payload: "${mqtt_command_ON}"
then:
- if:
condition:
not:
- switch.is_on: Relay_2
then:
- switch.turn_on: Relay_2
- topic: "${mqtt_status_topic_2}"
payload: "${mqtt_command_OFF}"
then:
- if:
condition:
- switch.is_on: Relay_2
then:
- switch.turn_off: Relay_2
#:########################################################################################:# #:########################################################################################:#
# STATUS LED: # STATUS LED:
# https://esphome.io/components/status_led.html # https://esphome.io/components/status_led.html
@@ -298,7 +319,7 @@ binary_sensor:
# Toggle the remote Footer Lights via COMMAND topic, based on our mirrored state # Toggle the remote Footer Lights via COMMAND topic, based on our mirrored state
- if: - if:
condition: condition:
lambda: 'return id(Relay_2).state;' lambda: "return id(Relay_2).state;"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_command_topic_2}" topic: "${mqtt_command_topic_2}"
@@ -332,15 +353,17 @@ switch:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_1}" topic: "${mqtt_status_topic_1}"
payload: "${mqtt_command_ON}" payload: "${mqtt_command_ON}"
retain: false qos: 1
retain: true
on_turn_off: on_turn_off:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_1}" topic: "${mqtt_status_topic_1}"
payload: "${mqtt_command_OFF}" payload: "${mqtt_command_OFF}"
retain: false qos: 1
retain: true
- platform: gpio - platform: gpio
name: "Relay 2: ${switch_2_name}" name: "Relay 2: ${switch_2_name}"
pin: GPIO12 pin: GPIO12
id: Relay_2 id: Relay_2
# No mqtt.publish here (this is a mirror only) # No mqtt.publish here (this is a mirror only)
+32 -9
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-centralstairs-top.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-centralstairs-top.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V3.9 2026-07-21 Added retained MQTT status mirroring for HA-independent two-way switching
# V3.8 2026-04-26 Added Downstairs Lights Bulk Activation virtual switch toggled by Button 1 double-click # V3.8 2026-04-26 Added Downstairs Lights Bulk Activation virtual switch toggled by Button 1 double-click
# V3.7 2025-09-24 Upload to this device # V3.7 2025-09-24 Upload to this device
# V3.5 2025-07-24 YAML tidyups # V3.5 2025-07-24 YAML tidyups
@@ -83,7 +84,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Double" # Project Details project_name: "Zemismart Technologies.KS-811 Double" # Project Details
project_version: "v2.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version project_version: "v3.9" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
# MQTT Controls # MQTT Controls
mqtt_command_main_topic: !secret mqtt_command_main_topic mqtt_command_main_topic: !secret mqtt_command_main_topic
@@ -184,19 +185,19 @@ mqtt:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(Relay_2).state;' lambda: "return id(Relay_2).state;"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_2}" topic: "${mqtt_status_topic_2}"
payload: "${mqtt_command_ON}" payload: "${mqtt_command_ON}"
qos: 0 qos: 1
retain: false retain: true
else: else:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_2}" topic: "${mqtt_status_topic_2}"
payload: "${mqtt_command_OFF}" payload: "${mqtt_command_OFF}"
qos: 0 qos: 1
retain: false retain: true
on_message: on_message:
# Relay 1: only turn ON if currently OFF # Relay 1: only turn ON if currently OFF
@@ -241,6 +242,26 @@ mqtt:
then: then:
- switch.turn_off: Relay_2 - switch.turn_off: Relay_2
# Relay 1 is a mirror of the remote physical Main Stair Lights relay.
- topic: "${mqtt_status_topic_1}"
payload: "${mqtt_command_ON}"
then:
- if:
condition:
not:
- switch.is_on: Relay_1
then:
- switch.turn_on: Relay_1
- topic: "${mqtt_status_topic_1}"
payload: "${mqtt_command_OFF}"
then:
- if:
condition:
- switch.is_on: Relay_1
then:
- switch.turn_off: Relay_1
#:########################################################################################:# #:########################################################################################:#
# STATUS LED: # STATUS LED:
# https://esphome.io/components/status_led.html # https://esphome.io/components/status_led.html
@@ -286,7 +307,7 @@ binary_sensor:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(Relay_1).state;' lambda: "return id(Relay_1).state;"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_command_topic_1}" topic: "${mqtt_command_topic_1}"
@@ -338,9 +359,11 @@ switch:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_2}" topic: "${mqtt_status_topic_2}"
payload: "${mqtt_command_ON}" payload: "${mqtt_command_ON}"
retain: false qos: 1
retain: true
on_turn_off: on_turn_off:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic_2}" topic: "${mqtt_status_topic_2}"
payload: "${mqtt_command_OFF}" payload: "${mqtt_command_OFF}"
retain: false qos: 1
retain: true
+42 -43
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstairskitchleds.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstairskitchleds.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v2.2 2026-07-20 Simplified local MQTT command and status topics to the device root
# v2.1 2026-02-25 Updated yaml to zorruno layout V1.1 # v2.1 2026-02-25 Updated yaml to zorruno layout V1.1
# v2.0 2025-09-15 A bunch of fixes. # v2.0 2025-09-15 A bunch of fixes.
# v1.5 2025-09-04 Adopt ramped "Restore Brightness" (0->last non-zero), avoid boot blip (ALWAYS_OFF + high-priority on_boot), # v1.5 2025-09-04 Adopt ramped "Restore Brightness" (0->last non-zero), avoid boot blip (ALWAYS_OFF + high-priority on_boot),
@@ -60,12 +61,12 @@
#:########################################################################################:# #:########################################################################################:#
# MQTT COMMANDS: # MQTT COMMANDS:
# - Command topic: # - Command topic:
# ${mqtt_local_command_topic}/light/set # ${mqtt_local_command_topic}
# Payload "${mqtt_local_device_command_ON}" -> fade up (switch ON) # Payload "${mqtt_local_device_command_ON}" -> fade up (switch ON)
# Payload "${mqtt_local_device_command_OFF}" -> fade down (switch OFF) # Payload "${mqtt_local_device_command_OFF}" -> fade down (switch OFF)
# #
# - Status topic: # - Status topic:
# ${mqtt_local_status_topic}/light/state # ${mqtt_local_status_topic}
# Payload "${mqtt_local_device_command_ON}" / "${mqtt_local_device_command_OFF}" # Payload "${mqtt_local_device_command_ON}" / "${mqtt_local_device_command_OFF}"
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
@@ -81,7 +82,6 @@
# - Accurate time is NOT needed (SNTP not needed) # - Accurate time is NOT needed (SNTP not needed)
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -95,7 +95,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot. project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot.
project_version: "v2.1" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version project_version: "v2.2" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names) # Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -145,8 +145,7 @@ packages:
local_current_ip_address: "${current_ip_address}" local_current_ip_address: "${current_ip_address}"
#### HOME ASSISTANT API (choose encryption or no encryption options) #### #### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include common_api: !include #file: common/api_common.yaml
#file: common/api_common.yaml
file: common/api_common_noencryption.yaml file: common/api_common_noencryption.yaml
vars: vars:
local_api_key: "${api_key}" local_api_key: "${api_key}"
@@ -193,32 +192,32 @@ esphome:
# Mode 0: Fade up to full (obeys fade settings; no on/off flicker) # Mode 0: Fade up to full (obeys fade settings; no on/off flicker)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 0;' lambda: "return id(restart_mode) == 0;"
then: then:
- lambda: 'id(ramp_switch_target_on) = true;' - lambda: "id(ramp_switch_target_on) = true;"
- script.execute: ramp_on_script - script.execute: ramp_on_script
# Mode 1: Restore Brightness (deferred to avoid races) # Mode 1: Restore Brightness (deferred to avoid races)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 1;' lambda: "return id(restart_mode) == 1;"
then: then:
- script.execute: deferred_restore_brightness - script.execute: deferred_restore_brightness
# Mode 2: Remain Off (no blip; stays off) # Mode 2: Remain Off (no blip; stays off)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 2;' lambda: "return id(restart_mode) == 2;"
then: then:
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
# Boot complete: allow OFF handlers again # Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;' - lambda: "id(booting) = false;"
#:########################################################################################:# #:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK # ESP PLATFORM AND FRAMEWORK
@@ -358,13 +357,13 @@ status_led:
mqtt: mqtt:
on_message: on_message:
# Light control to ramp up # Light control to ramp up
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
then: then:
- switch.turn_on: mosfet_ramp_switch - switch.turn_on: mosfet_ramp_switch
# Light control to ramp down # Light control to ramp down
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
then: then:
- switch.turn_off: mosfet_ramp_switch - switch.turn_off: mosfet_ramp_switch
@@ -492,7 +491,7 @@ binary_sensor:
on_press: on_press:
- if: - if:
condition: condition:
lambda: 'return id(ramp_switch_target_on);' lambda: "return id(ramp_switch_target_on);"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = false; id(ramp_switch_target_on) = false;
@@ -589,7 +588,7 @@ light:
on_turn_on: on_turn_on:
- output.turn_on: green_led_out - output.turn_on: green_led_out
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -599,7 +598,7 @@ light:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -607,13 +606,13 @@ light:
- output.turn_off: green_led_out - output.turn_off: green_led_out
- if: - if:
condition: condition:
lambda: 'return !id(booting);' lambda: "return !id(booting);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
retain: true retain: true
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- lambda: |- - lambda: |-
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
@@ -700,9 +699,9 @@ number:
set_action: set_action:
- if: - if:
condition: condition:
lambda: 'return x <= 0.0f;' lambda: "return x <= 0.0f;"
then: then:
- lambda: 'id(suppress_slider_sync) = true;' - lambda: "id(suppress_slider_sync) = true;"
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- light.turn_off: - light.turn_off:
@@ -714,7 +713,7 @@ number:
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
id(last_set_pos) = 0; id(last_set_pos) = 0;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
- lambda: |- - lambda: |-
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
@@ -745,7 +744,7 @@ number:
if (out_pct > maxp) out_pct = maxp; if (out_pct > maxp) out_pct = maxp;
id(last_brightness_pct) = out_pct; id(last_brightness_pct) = out_pct;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
- platform: template - platform: template
id: cfg_max_on_hours id: cfg_max_on_hours
@@ -768,12 +767,12 @@ number:
id(cfg_max_on_hours).publish_state((float) hrs); id(cfg_max_on_hours).publish_state((float) hrs);
- if: - if:
condition: condition:
lambda: 'return id(mosfet_leds).current_values.is_on();' lambda: "return id(mosfet_leds).current_values.is_on();"
then: then:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -785,7 +784,7 @@ script:
- id: ramp_on_script - id: ramp_on_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
- if: - if:
condition: condition:
@@ -796,11 +795,11 @@ script:
then: then:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;' brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -820,7 +819,7 @@ script:
if (frac > 1.0f) frac = 1.0f; if (frac > 1.0f) frac = 1.0f;
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
@@ -832,7 +831,7 @@ script:
- id: ramp_to_target_script - id: ramp_to_target_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
- if: - if:
condition: condition:
@@ -843,7 +842,7 @@ script:
then: then:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
@@ -873,7 +872,7 @@ script:
if (frac > 1.0f) frac = 1.0f; if (frac > 1.0f) frac = 1.0f;
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
@@ -891,7 +890,7 @@ script:
- script.stop: ramp_on_script - script.stop: ramp_on_script
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -904,7 +903,7 @@ script:
if (frac > 1.0f) frac = 1.0f; if (frac > 1.0f) frac = 1.0f;
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 150ms transition_length: 150ms
@@ -921,12 +920,12 @@ script:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);' - delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
- if: - if:
condition: condition:
lambda: 'return id(mosfet_leds).current_values.is_on();' lambda: "return id(mosfet_leds).current_values.is_on();"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = false; id(ramp_switch_target_on) = false;
@@ -950,17 +949,17 @@ script:
- if: - if:
condition: condition:
lambda: 'return id(restore_target_pct) > 0.5f;' lambda: "return id(restore_target_pct) > 0.5f;"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = true; id(ramp_switch_target_on) = true;
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
- script.stop: ramp_off_script - script.stop: ramp_off_script
- script.execute: ramp_to_target_script - script.execute: ramp_to_target_script
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
+25 -3
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstkitchlights.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstkitchlights.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v2.2 2026-07-20 Restored direct MQTT control of pantry LEDs using the flattened topic
# v2.1 2026-02-25 Updated yaml to zorruno layout V1.1 # v2.1 2026-02-25 Updated yaml to zorruno layout V1.1
# v2.0 2025-06-05 YAML tidyups # v2.0 2025-06-05 YAML tidyups
# v1.0 2025-03-28 Initial Version # v1.0 2025-03-28 Initial Version
@@ -21,6 +22,13 @@
# - Relay 1: Dining Light # - Relay 1: Dining Light
# - Relay 2: Kitchen Light # - Relay 2: Kitchen Light
# - Relay 3: Extract Fan (virtual only, no power connected to 3rd relay) # - Relay 3: Extract Fan (virtual only, no power connected to 3rd relay)
# - Relay 2 publishes ON/OFF commands directly to the downstairs kitchen pantry LEDs.
#:########################################################################################:#
# MQTT COMMANDS:
# - Remote command topic:
# ${mqtt_remote_device_command_topic}
# Payload "${mqtt_remote_device_command_on}" -> pantry LEDs ON
# Payload "${mqtt_remote_device_command_off}" -> pantry LEDs OFF
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE # a) Home Assistant OFFLINE, but Network and MQTT ONLINE
@@ -36,7 +44,6 @@
# - Accurate time is NOT needed (SNTP not needed) # - Accurate time is NOT needed (SNTP not needed)
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -50,7 +57,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Triple" project_name: "Zemismart Technologies.KS-811 Triple"
project_version: "v2.1" project_version: "v2.2"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
@@ -61,6 +68,13 @@ substitutions:
# Don't forget to switch it back when changed. # Don't forget to switch it back when changed.
current_ip_address: ${static_ip_address} current_ip_address: ${static_ip_address}
# MQTT REMOTE Controls
mqtt_command_main_topic: !secret mqtt_command_main_topic
mqtt_remote_device_name: "downstairskitchen-pantryleds"
mqtt_remote_device_command_topic: "${mqtt_command_main_topic}/${mqtt_remote_device_name}"
mqtt_remote_device_command_on: "ON"
mqtt_remote_device_command_off: "OFF"
# Device Settings # Device Settings
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
update_interval: "60s" # update time for general sensors etc update_interval: "60s" # update time for general sensors etc
@@ -195,8 +209,16 @@ switch:
name: "Relay 2: ${switch_2_name}" name: "Relay 2: ${switch_2_name}"
pin: GPIO12 pin: GPIO12
id: Relay_2 id: Relay_2
on_turn_on:
- mqtt.publish:
topic: "${mqtt_remote_device_command_topic}"
payload: "${mqtt_remote_device_command_on}"
on_turn_off:
- mqtt.publish:
topic: "${mqtt_remote_device_command_topic}"
payload: "${mqtt_remote_device_command_off}"
- platform: gpio - platform: gpio
name: "Relay 3: ${switch_3_name}" name: "Relay 3: ${switch_3_name}"
pin: GPIO14 pin: GPIO14
id: Relay_3 id: Relay_3
+7 -7
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstloungeentry.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstloungeentry.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v3.8 2026-07-20 Updated remote MQTT topics for the flattened downstairs lounge main lights topic
# v3.7 2026-02-25 Updated yaml to zorruno layout V1.1 # v3.7 2026-02-25 Updated yaml to zorruno layout V1.1
# v3.6 2025-07-24 YAML tidyups # v3.6 2025-07-24 YAML tidyups
#:########################################################################################:# #:########################################################################################:#
@@ -45,7 +46,6 @@
# - Accurate time is NOT needed (SNTP not needed) # - Accurate time is NOT needed (SNTP not needed)
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -59,7 +59,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Single" project_name: "Zemismart Technologies.KS-811 Single"
project_version: "v3.7" project_version: "v3.8"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
@@ -78,8 +78,8 @@ substitutions:
# MQTT REMOTE Controls # MQTT REMOTE Controls
mqtt_remote_device_name: "downst-lounge-main-lights" mqtt_remote_device_name: "downst-lounge-main-lights"
mqtt_remote_device_command_topic: "${mqtt_command_main_topic}/${mqtt_remote_device_name}/light1/set" mqtt_remote_device_command_topic: "${mqtt_command_main_topic}/${mqtt_remote_device_name}"
mqtt_remote_status_topic: "${mqtt_status_main_topic}/${mqtt_remote_device_name}/light1/state" mqtt_remote_status_topic: "${mqtt_status_main_topic}/${mqtt_remote_device_name}"
mqtt_remote_device_command_on: "On" mqtt_remote_device_command_on: "On"
mqtt_remote_device_command_off: "Off" mqtt_remote_device_command_off: "Off"
@@ -170,7 +170,7 @@ mqtt:
- delay: 50ms - delay: 50ms
- if: - if:
condition: condition:
lambda: 'return !id(Relay_1).state;' lambda: "return !id(Relay_1).state;"
then: then:
- switch.turn_on: Relay_1 - switch.turn_on: Relay_1
@@ -180,7 +180,7 @@ mqtt:
- delay: 50ms - delay: 50ms
- if: - if:
condition: condition:
lambda: 'return id(Relay_1).state;' lambda: "return id(Relay_1).state;"
then: then:
- switch.turn_off: Relay_1 - switch.turn_off: Relay_1
@@ -224,4 +224,4 @@ switch:
- platform: gpio - platform: gpio
name: "Relay 1: ${switch_1_name}" name: "Relay 1: ${switch_1_name}"
pin: GPIO12 pin: GPIO12
id: Relay_1 id: Relay_1
+9 -9
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstloungemain.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstloungemain.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v3.7 2026-07-20 Simplified local MQTT command and status topics to the device root
# v3.6 2026-02-25 Updated yaml to zorruno layout V1.1 # v3.6 2026-02-25 Updated yaml to zorruno layout V1.1
# v3.5 2025-07-28 YAML tidyups # v3.5 2025-07-28 YAML tidyups
#:########################################################################################:# #:########################################################################################:#
@@ -23,12 +24,12 @@
#:########################################################################################:# #:########################################################################################:#
# MQTT COMMANDS: # MQTT COMMANDS:
# - Local command topic (Main Lights): # - Local command topic (Main Lights):
# ${mqtt_local_command_topic}/light1/set # ${mqtt_local_command_topic}
# Payload "On" -> Relay 1 ON # Payload "On" -> Relay 1 ON
# Payload "Off" -> Relay 1 OFF # Payload "Off" -> Relay 1 OFF
# #
# - Local status topic (Main Lights): # - Local status topic (Main Lights):
# ${mqtt_local_status_topic}/light1/state # ${mqtt_local_status_topic}
# Payload "On" / "Off" published when Relay 1 changes state # Payload "On" / "Off" published when Relay 1 changes state
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
@@ -45,7 +46,6 @@
# - Accurate time is NOT needed (SNTP not needed). # - Accurate time is NOT needed (SNTP not needed).
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -59,7 +59,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Triple" project_name: "Zemismart Technologies.KS-811 Triple"
project_version: "v3.6" project_version: "v3.7"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -156,11 +156,11 @@ logger:
mqtt: mqtt:
on_message: on_message:
# Main Lights control (Relay 1) # Main Lights control (Relay 1)
- topic: "${mqtt_local_command_topic}/light1/set" - topic: "${mqtt_local_command_topic}"
payload: "On" payload: "On"
then: then:
- switch.turn_on: relay_1 - switch.turn_on: relay_1
- topic: "${mqtt_local_command_topic}/light1/set" - topic: "${mqtt_local_command_topic}"
payload: "Off" payload: "Off"
then: then:
- switch.turn_off: relay_1 - switch.turn_off: relay_1
@@ -222,11 +222,11 @@ switch:
id: relay_1 id: relay_1
on_turn_on: on_turn_on:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light1/state" topic: "${mqtt_local_status_topic}"
payload: "On" payload: "On"
on_turn_off: on_turn_off:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light1/state" topic: "${mqtt_local_status_topic}"
payload: "Off" payload: "Off"
# Relay 2 (GPIO12) # Relay 2 (GPIO12)
@@ -239,4 +239,4 @@ switch:
- platform: gpio - platform: gpio
name: "Relay 3: ${switch_3_name}" name: "Relay 3: ${switch_3_name}"
pin: GPIO14 pin: GPIO14
id: relay_3 id: relay_3
+270
View File
@@ -0,0 +1,270 @@
#:########################################################################################:#
# ESP-NOW Test Receiver
#:########################################################################################:#
#
# DEVICE:
# ESP32 DevKit V1 / ESP-WROOM-32
#
# VERSION:
# 1.0 - 2026-07-11
# - Initial ESP-NOW receiver test build.
# - Receives broadcast packets from ESP32-S3-Zero transmitter.
# - Publishes last sender MAC, payload, RSSI, received count and duplicate count.
# - Creates a short Home Assistant event pulse for each unique press_id.
#
# NOTES:
# - This device is the powered receiver.
# - The transmitter should send the same packet 2-3 times for reliability.
# - This receiver de-duplicates repeated packets using the full payload string.
# - Expected test payload format from transmitter:
# REMOTE_A:BUTTON_1:123
#
#:########################################################################################:#
substitutions:
device_name: esp-espnow_test_receiver
friendly_name: "ESPNow Test Receiver"
area: "Test Bench"
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
# Re-using your existing bulb flasher secrets (adjust if you prefer per-device secrets)
api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass
#static_ip_address: !secret esp-bedrm1arlecbulb_ip
#:########################################################################################:#
# PACKAGES: Included Common Packages
# https://esphome.io/components/packages.html
#:########################################################################################:#
packages:
#### WIFI, Network (DHCP/IPV6 etc), Fallback AP, Safemode ####
common_wifi: !include
file: common/network_common_dhcp.yaml
vars:
local_device_name: "${device_name}"
local_ota_pass: "${ota_pass}"
#### 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}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
common_webportal: !include common/webportal_common_nopasswd.yaml
#### 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_nomqtt.yaml
#diag_debug: !include common/include_debug_diag_sensors.yaml
#diag_resetcount: !include common/include_resetcount_diag_sensors.yaml
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
area: ${area}
esp32:
board: esp32dev
framework:
type: arduino
#:########################################################################################:#
# ESP-NOW Receiver
#:########################################################################################:#
#
# Do not set channel here while using Wi-Fi.
# The receiver's ESP-NOW channel follows the connected Wi-Fi channel.
#
espnow:
auto_add_peer: true
on_broadcast:
- then:
- lambda: |-
char src_mac[18];
snprintf(src_mac, sizeof(src_mac), "%02X:%02X:%02X:%02X:%02X:%02X",
info.src_addr[0], info.src_addr[1], info.src_addr[2],
info.src_addr[3], info.src_addr[4], info.src_addr[5]);
std::string payload(reinterpret_cast<const char *>(data), size);
int rssi = info.rx_ctrl ? info.rx_ctrl->rssi : 0;
id(last_sender_mac).publish_state(src_mac);
id(last_payload).publish_state(payload);
id(last_rssi).publish_state(rssi);
id(received_count) += 1;
id(received_count_sensor).publish_state(id(received_count));
ESP_LOGI("espnow_rx", "ESP-NOW broadcast from %s, RSSI %d dBm, payload: %s",
src_mac, rssi, payload.c_str());
// Test payload format:
// REMOTE_A:BUTTON_1:123
//
// For now, use the whole payload as the duplicate key.
// This means the 2nd and 3rd repeated sends from one button press are ignored.
if (payload == id(last_unique_payload)) {
id(duplicate_count) += 1;
id(duplicate_count_sensor).publish_state(id(duplicate_count));
id(packet_is_unique) = false;
ESP_LOGD("espnow_rx", "Duplicate ESP-NOW payload ignored: %s", payload.c_str());
return;
}
id(last_unique_payload) = payload;
id(packet_is_unique) = true;
// Split simple colon-delimited payload for HA visibility.
size_t first_colon = payload.find(':');
size_t second_colon = payload.find(':', first_colon + 1);
if (first_colon != std::string::npos && second_colon != std::string::npos) {
std::string remote_id = payload.substr(0, first_colon);
std::string button_id = payload.substr(first_colon + 1, second_colon - first_colon - 1);
std::string press_id = payload.substr(second_colon + 1);
id(last_remote_id).publish_state(remote_id);
id(last_button_id).publish_state(button_id);
id(last_press_id).publish_state(press_id);
ESP_LOGI("espnow_rx", "Unique press: remote=%s button=%s press_id=%s",
remote_id.c_str(), button_id.c_str(), press_id.c_str());
} else {
id(last_remote_id).publish_state("unknown");
id(last_button_id).publish_state("unknown");
id(last_press_id).publish_state("unknown");
ESP_LOGW("espnow_rx", "Payload did not match expected format: %s", payload.c_str());
}
- if:
condition:
lambda: |-
return id(packet_is_unique);
then:
- binary_sensor.template.publish:
id: espnow_unique_press_event
state: ON
- script.execute: clear_unique_press_event
#:########################################################################################:#
# Globals
#:########################################################################################:#
globals:
- id: received_count
type: int
restore_value: true
initial_value: '0'
- id: duplicate_count
type: int
restore_value: true
initial_value: '0'
- id: last_unique_payload
type: std::string
restore_value: true
initial_value: '""'
- id: packet_is_unique
type: bool
restore_value: false
initial_value: 'false'
#:########################################################################################:#
# Scripts
#:########################################################################################:#
script:
- id: clear_unique_press_event
mode: restart
then:
- delay: 500ms
- binary_sensor.template.publish:
id: espnow_unique_press_event
state: OFF
#:########################################################################################:#
# Home Assistant Entities
#:########################################################################################:#
text_sensor:
- platform: template
name: "Last Sender MAC"
id: last_sender_mac
icon: mdi:access-point-network
- platform: template
name: "Last Payload"
id: last_payload
icon: mdi:message-text
- platform: template
name: "Last Remote ID"
id: last_remote_id
icon: mdi:remote
- platform: template
name: "Last Button ID"
id: last_button_id
icon: mdi:gesture-tap-button
- platform: template
name: "Last Press ID"
id: last_press_id
icon: mdi:identifier
sensor:
- platform: template
name: "Last RSSI"
id: last_rssi
unit_of_measurement: "dBm"
accuracy_decimals: 0
icon: mdi:wifi-strength-2
- platform: template
name: "Received Count"
id: received_count_sensor
accuracy_decimals: 0
icon: mdi:counter
- platform: template
name: "Duplicate Count"
id: duplicate_count_sensor
accuracy_decimals: 0
icon: mdi:counter
binary_sensor:
- platform: template
name: "Unique Press Event"
id: espnow_unique_press_event
icon: mdi:gesture-tap-button
button:
- platform: template
name: "Reset ESP-NOW Test Counts"
icon: mdi:counter
on_press:
then:
- lambda: |-
id(received_count) = 0;
id(duplicate_count) = 0;
id(last_unique_payload) = "";
id(received_count_sensor).publish_state(0);
id(duplicate_count_sensor).publish_state(0);
+294
View File
@@ -0,0 +1,294 @@
#:########################################################################################:#
# ESP-NOW Test Transmitter 1
#:########################################################################################:#
#
# DEVICE:
# ESP32-S3-Zero / ESP32-S3 Super Mini
#
# VERSION:
# 1.2 - 2026-07-11
# - Changed GPIO4 wake source to ESP32 EXT1 ANY_LOW.
# - Added delay to allow ESP-NOW to initialise before transmission.
# - Added GPIO4 input and pull-up initialisation.
# - Waits for the wake button to be released before entering deep sleep.
# - Logs the ESP32 wake cause.
# - Sends each message three times for improved broadcast reliability.
#
# WIRING:
#
# 3.3 V
# |
# 10 kΩ
# |
# +-------- GPIO4
# |
# Push button
# |
# GND
#
# OPERATION:
# - Power-up or reset causes one transmission.
# - The transmitter sends the same ESP-NOW message three times.
# - It waits for GPIO4 to return HIGH.
# - It then enters deep sleep.
# - Pulling GPIO4 LOW wakes the transmitter and repeats the sequence.
#
# MESSAGE FORMAT:
# REMOTE_A:BUTTON_1:<random press ID>
#
# IMPORTANT:
# - ESP-NOW channel must match the receiver network channel.
# - The receiver is currently operating on channel 5.
# - This transmitter deliberately does not connect to normal Wi-Fi.
# - Firmware updates are performed by USB.
#
#:########################################################################################:#
substitutions:
device_name: esp-espnow-test-transmitter1
friendly_name: "ESPNow Test Transmitter 1"
remote_id: "REMOTE_A"
button_id: "BUTTON_1"
espnow_channel: "5"
wake_gpio: "4"
#:########################################################################################:#
# ESPHome Core
#:########################################################################################:#
esphome:
name: ${device_name}
friendly_name: ${friendly_name}
on_boot:
priority: -100
then:
- lambda: |-
// Explicitly configure GPIO4 as an input with a pull-up.
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_INPUT);
gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_ONLY);
const esp_sleep_wakeup_cause_t wake_cause =
esp_sleep_get_wakeup_cause();
ESP_LOGI(
"wake",
"ESP-NOW transmitter booted. Wake cause: %d",
static_cast<int>(wake_cause)
);
if (wake_cause == ESP_SLEEP_WAKEUP_EXT1) {
ESP_LOGI("wake", "Wake was caused by the GPIO4 EXT1 input.");
} else if (wake_cause == ESP_SLEEP_WAKEUP_UNDEFINED) {
ESP_LOGI("wake", "Wake was caused by power-up or reset.");
} else {
ESP_LOGI(
"wake",
"Wake was caused by another source: %d",
static_cast<int>(wake_cause)
);
}
# Allow all ESPHome components, including ESP-NOW, to finish setup.
- delay: 750ms
- lambda: |-
id(press_id) = esp_random();
char payload_buffer[96];
snprintf(
payload_buffer,
sizeof(payload_buffer),
"${remote_id}:${button_id}:%lu",
static_cast<unsigned long>(id(press_id))
);
id(transmit_payload) = payload_buffer;
ESP_LOGI(
"espnow_tx",
"Prepared payload: %s",
id(transmit_payload).c_str()
);
- script.execute: send_espnow_press
- script.wait: send_espnow_press
- logger.log:
level: INFO
format: "Transmission sequence completed."
# Do not enter sleep while GPIO4 is still held LOW.
- logger.log:
level: INFO
format: "Waiting for GPIO4 button release."
- wait_until:
condition:
lambda: |-
return gpio_get_level(GPIO_NUM_4) == 1;
- delay: 100ms
- logger.log:
level: INFO
format: "GPIO4 released. Entering deep sleep."
- delay: 100ms
- deep_sleep.enter: deep_sleep_control
#:########################################################################################:#
# ESP32 Hardware
#:########################################################################################:#
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
#:########################################################################################:#
# Logging
#:########################################################################################:#
logger:
level: DEBUG
#:########################################################################################:#
# ESP-NOW
#:########################################################################################:#
#
# The transmitter does not connect to Wi-Fi, so its radio channel is set
# explicitly. Both transmitter and receiver must use the same channel.
#
espnow:
channel: ${espnow_channel}
enable_on_boot: true
#:########################################################################################:#
# Globals
#:########################################################################################:#
globals:
- id: press_id
type: uint32_t
restore_value: false
initial_value: '0'
- id: transmit_payload
type: std::string
restore_value: false
initial_value: '""'
#:########################################################################################:#
# ESP-NOW Transmission Script
#:########################################################################################:#
script:
- id: send_espnow_press
mode: single
then:
- logger.log:
level: INFO
format: "Sending ESP-NOW broadcast 1 of 3."
- espnow.broadcast:
data: !lambda |-
return std::vector<uint8_t>(
id(transmit_payload).begin(),
id(transmit_payload).end()
);
wait_for_sent: true
continue_on_error: true
on_sent:
- logger.log:
level: DEBUG
format: "Broadcast 1 submitted successfully."
on_error:
- logger.log:
level: WARN
format: "Broadcast 1 reported an error."
- delay: 75ms
- logger.log:
level: INFO
format: "Sending ESP-NOW broadcast 2 of 3."
- espnow.broadcast:
data: !lambda |-
return std::vector<uint8_t>(
id(transmit_payload).begin(),
id(transmit_payload).end()
);
wait_for_sent: true
continue_on_error: true
on_sent:
- logger.log:
level: DEBUG
format: "Broadcast 2 submitted successfully."
on_error:
- logger.log:
level: WARN
format: "Broadcast 2 reported an error."
- delay: 75ms
- logger.log:
level: INFO
format: "Sending ESP-NOW broadcast 3 of 3."
- espnow.broadcast:
data: !lambda |-
return std::vector<uint8_t>(
id(transmit_payload).begin(),
id(transmit_payload).end()
);
wait_for_sent: true
continue_on_error: true
on_sent:
- logger.log:
level: DEBUG
format: "Broadcast 3 submitted successfully."
on_error:
- logger.log:
level: WARN
format: "Broadcast 3 reported an error."
- delay: 75ms
#:########################################################################################:#
# Deep Sleep and GPIO4 Wake
#:########################################################################################:#
#
# ESP32-S3 supports EXT1 ANY_LOW wake.
#
# With one configured pin, ANY_LOW means:
# Wake when GPIO4 is pulled LOW.
#
# An external 10 kΩ pull-up between GPIO4 and 3.3 V is recommended.
#
deep_sleep:
id: deep_sleep_control
esp32_ext1_wakeup:
pins:
- GPIO${wake_gpio}
mode: ANY_LOW
File diff suppressed because it is too large Load Diff
+135 -4
View File
@@ -6,6 +6,9 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-laundrydrypow.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-laundrydrypow.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V1.2 2026-07-12 Added cumulative Load Energy integration sensor
# Added seven-day maximum power sensor
# Maximum power resets after 168 powered hours
# V1.1 2026-03-11 Updated yaml to zorruno layout V1.1 # V1.1 2026-03-11 Updated yaml to zorruno layout V1.1
# V1.0 2025-05-28 Initial Version # V1.0 2025-05-28 Initial Version
#:########################################################################################:# #:########################################################################################:#
@@ -20,7 +23,16 @@
# - Reference: https://devices.esphome.io/devices/Sonoff-POW-R1 # - Reference: https://devices.esphome.io/devices/Sonoff-POW-R1
#:########################################################################################:# #:########################################################################################:#
# OPERATION NOTES: # OPERATION NOTES:
# - Monitors dryer load current, voltage, and power # - Monitors dryer load current, voltage, power, and cumulative energy
# - Load Energy integrates measured power over time and reports kWh
# - Load Energy resets to zero if the ESPHome device reboots
# - Home Assistant handles the reset because the sensor uses total_increasing
# - Load Maximum Power 7 Days records the highest measured power
# - Maximum power resets after 168 powered hours
# - The maximum power value and elapsed-hour counter are retained across reboots
# - The elapsed-hour counter is saved every six hours to reduce flash writes
# - An unexpected power loss may extend the reset period by up to approximately six hours
# - No accurate clock or SNTP connection is required for the seven-day reset
# - Provides a virtual relay switch that controls the physical relay and LED together # - Provides a virtual relay switch that controls the physical relay and LED together
# - Local button toggles the virtual relay switch # - Local button toggles the virtual relay switch
# - On boot, the virtual relay is turned ON # - On boot, the virtual relay is turned ON
@@ -31,15 +43,18 @@
# a) Home Assistant offline (network and MQTT online): # a) Home Assistant offline (network and MQTT online):
# - Device continues running normally # - Device continues running normally
# - Local button still works # - Local button still works
# - MQTT remains available if your common MQTT package is enabled and broker is reachable # - Power, energy, running state, and maximum power continue operating locally
# - MQTT remains available if the common MQTT package is enabled and broker is reachable
# #
# b) MQTT offline (or HA/MQTT offline): # b) MQTT offline (or HA/MQTT offline):
# - Device continues running locally # - Device continues running locally
# - Local button, relay control, LED control, and power monitoring still work # - Local button, relay control, LED control, and power monitoring still work
# - Energy integration and maximum power monitoring continue locally
# #
# c) Entire WiFi/Network offline: # c) Entire WiFi/Network offline:
# - Device continues running locally # - Device continues running locally
# - Local button, relay control, LED control, and power monitoring still work # - Local button, relay control, LED control, and power monitoring still work
# - Energy integration and maximum power monitoring continue locally
# - Accurate time is not needed; SNTP is not required for operation # - Accurate time is not needed; SNTP is not required for operation
#:########################################################################################:# #:########################################################################################:#
@@ -57,7 +72,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Sonoff Technologies.POW R1" project_name: "Sonoff Technologies.POW R1"
project_version: "v1.1" project_version: "v1.2"
# Entity Naming # Entity Naming
entity_prefix: "Load" entity_prefix: "Load"
@@ -121,9 +136,14 @@ esphome:
friendly_name: "${friendly_name}" friendly_name: "${friendly_name}"
comment: "${description_comment}" comment: "${description_comment}"
area: "${device_area}" area: "${device_area}"
# Limit parallel compiler processes to reduce host RAM usage.
compile_process_limit: 1
project: project:
name: "${project_name}" name: "${project_name}"
version: "${project_version}" version: "${project_version}"
on_boot: on_boot:
priority: 200 priority: 200
then: then:
@@ -157,6 +177,28 @@ logger:
# number: GPIO2 # number: GPIO2
# inverted: true # inverted: true
#:########################################################################################:#
# GLOBALS:
# Retained maximum power and elapsed-hour counter
# https://esphome.io/components/globals.html
#:########################################################################################:#
globals:
# Highest measured power during the current seven-day period.
# The value is checked for changes every five minutes before being saved.
- id: max_power_7d
type: float
restore_value: true
initial_value: '0.0'
update_interval: 5min
# Number of powered hours elapsed in the current seven-day period.
# Saved every six hours to reduce ESP8266 flash writes.
- id: max_power_hours_elapsed
type: uint16_t
restore_value: true
initial_value: '0'
update_interval: 6h
#:########################################################################################:# #:########################################################################################:#
# BINARY SENSORS: # BINARY SENSORS:
# https://esphome.io/components/binary_sensor/ # https://esphome.io/components/binary_sensor/
@@ -178,7 +220,7 @@ binary_sensor:
lambda: |- lambda: |-
if (isnan(id(power).state)) { if (isnan(id(power).state)) {
return {}; return {};
} else if (id(power).state > 4) { } else if (id(power).state > 4.0f) {
return true; return true;
} else { } else {
return false; return false;
@@ -194,13 +236,44 @@ sensor:
cf_pin: GPIO14 cf_pin: GPIO14
cf1_pin: GPIO13 cf1_pin: GPIO13
update_interval: 2s update_interval: 2s
current: current:
name: "${entity_prefix} Current" name: "${entity_prefix} Current"
device_class: current
state_class: measurement
unit_of_measurement: "A"
accuracy_decimals: 2
voltage: voltage:
name: "${entity_prefix} Voltage" name: "${entity_prefix} Voltage"
device_class: voltage
state_class: measurement
unit_of_measurement: "V"
accuracy_decimals: 1
power: power:
name: "${entity_prefix} Power" name: "${entity_prefix} Power"
id: power id: power
device_class: power
state_class: measurement
unit_of_measurement: "W"
accuracy_decimals: 1
# Update the seven-day maximum whenever a new peak is measured.
on_value:
then:
- lambda: |-
if (!isnan(x) && x > id(max_power_7d)) {
id(max_power_7d) = x;
id(max_power_7d_sensor).publish_state(x);
ESP_LOGI(
"max_power",
"New seven-day maximum power: %.1f W",
x
);
}
# on_value_range: # on_value_range:
# - above: 4.0 # - above: 4.0
# then: # then:
@@ -209,6 +282,64 @@ sensor:
# then: # then:
# - light.turn_off: led # - light.turn_off: led
# Integrates instantaneous watts over time.
# Watts integrated over hours produces Wh, then multiply converts Wh to kWh.
- platform: integration
name: "${entity_prefix} Energy"
id: energy
sensor: power
time_unit: h
integration_method: trapezoid
restore: false
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3
filters:
- multiply: 0.001
# Highest power reading recorded during the current 168 powered-hour period.
- platform: template
name: "${entity_prefix} Maximum Power 7 Days"
id: max_power_7d_sensor
unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 1
update_interval: 60s
lambda: |-
return id(max_power_7d);
#:########################################################################################:#
# INTERVAL:
# Seven-day maximum power reset
# https://esphome.io/components/interval.html
#:########################################################################################:#
interval:
# Count one powered hour at a time.
# 168 hours equals seven days.
- interval: 1h
then:
- lambda: |-
id(max_power_hours_elapsed) += 1;
ESP_LOGD(
"max_power",
"Seven-day maximum power period: %u of 168 powered hours elapsed",
id(max_power_hours_elapsed)
);
if (id(max_power_hours_elapsed) >= 168) {
id(max_power_hours_elapsed) = 0;
id(max_power_7d) = 0.0f;
id(max_power_7d_sensor).publish_state(0.0f);
ESP_LOGI(
"max_power",
"Seven-day maximum power value has been reset"
);
}
#:########################################################################################:# #:########################################################################################:#
# SWITCH: # SWITCH:
# https://esphome.io/components/switch/ # https://esphome.io/components/switch/
+137 -4
View File
@@ -6,6 +6,9 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-laundrywashpow.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-laundrywashpow.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V1.2 2026-07-12 Added cumulative Load Energy integration sensor
# Added seven-day maximum power sensor
# Maximum power resets after 168 powered hours
# V1.1 2026-03-11 Updated yaml to zorruno layout V1.1 # V1.1 2026-03-11 Updated yaml to zorruno layout V1.1
# V1.0 2025-03-28 Initial Version # V1.0 2025-03-28 Initial Version
#:########################################################################################:# #:########################################################################################:#
@@ -20,7 +23,18 @@
# - Reference: https://devices.esphome.io/devices/Sonoff-POW-R1 # - Reference: https://devices.esphome.io/devices/Sonoff-POW-R1
#:########################################################################################:# #:########################################################################################:#
# OPERATION NOTES: # OPERATION NOTES:
# - Monitors washing machine load current, voltage, and power # - Monitors washing machine load current, voltage, power, and cumulative energy
# - Load Energy integrates measured power over time and reports kWh
# - Load Energy resets to zero if the ESPHome device reboots
# - Home Assistant handles the reset because the sensor uses total_increasing
# - Load Maximum Power 7 Days records the highest measured power
# - Maximum power resets after 168 powered hours
# - The maximum power value and elapsed-hour counter are retained across reboots
# - The maximum power value is checked for saving every five minutes
# - The elapsed-hour counter is checked for saving every six hours
# - Reduced save frequency limits unnecessary ESP8266 flash writes
# - An unexpected power loss may extend the reset period by up to approximately six hours
# - No accurate clock or SNTP connection is required for the seven-day reset
# - Provides a virtual relay switch that controls the physical relay and LED together # - Provides a virtual relay switch that controls the physical relay and LED together
# - Local button toggles the virtual relay switch # - Local button toggles the virtual relay switch
# - On boot, the virtual relay is turned ON # - On boot, the virtual relay is turned ON
@@ -31,15 +45,18 @@
# a) Home Assistant offline (network and MQTT online): # a) Home Assistant offline (network and MQTT online):
# - Device continues running normally # - Device continues running normally
# - Local button still works # - Local button still works
# - MQTT remains available if your common MQTT package is enabled and broker is reachable # - Power, energy, running state, and maximum power continue operating locally
# - MQTT remains available if the common MQTT package is enabled and broker is reachable
# #
# b) MQTT offline (or HA/MQTT offline): # b) MQTT offline (or HA/MQTT offline):
# - Device continues running locally # - Device continues running locally
# - Local button, relay control, LED control, and power monitoring still work # - Local button, relay control, LED control, and power monitoring still work
# - Energy integration and maximum power monitoring continue locally
# #
# c) Entire WiFi/Network offline: # c) Entire WiFi/Network offline:
# - Device continues running locally # - Device continues running locally
# - Local button, relay control, LED control, and power monitoring still work # - Local button, relay control, LED control, and power monitoring still work
# - Energy integration and maximum power monitoring continue locally
# - Accurate time is not needed; SNTP is not required for operation # - Accurate time is not needed; SNTP is not required for operation
#:########################################################################################:# #:########################################################################################:#
@@ -57,7 +74,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Sonoff Technologies.POW R1" project_name: "Sonoff Technologies.POW R1"
project_version: "v1.1" project_version: "v1.2"
# Entity Naming # Entity Naming
entity_prefix: "Load" entity_prefix: "Load"
@@ -121,9 +138,14 @@ esphome:
friendly_name: "${friendly_name}" friendly_name: "${friendly_name}"
comment: "${description_comment}" comment: "${description_comment}"
area: "${device_area}" area: "${device_area}"
# Limit parallel compiler processes to reduce host RAM usage.
compile_process_limit: 1
project: project:
name: "${project_name}" name: "${project_name}"
version: "${project_version}" version: "${project_version}"
on_boot: on_boot:
priority: 200 priority: 200
then: then:
@@ -157,6 +179,28 @@ logger:
# number: GPIO2 # number: GPIO2
# inverted: true # inverted: true
#:########################################################################################:#
# GLOBALS:
# Retained maximum power and elapsed-hour counter
# https://esphome.io/components/globals.html
#:########################################################################################:#
globals:
# Highest measured power during the current seven-day period.
# The value is checked for changes every five minutes before being saved.
- id: max_power_7d
type: float
restore_value: true
initial_value: '0.0'
update_interval: 5min
# Number of powered hours elapsed in the current seven-day period.
# The value is checked for saving every six hours to reduce flash writes.
- id: max_power_hours_elapsed
type: uint16_t
restore_value: true
initial_value: '0'
update_interval: 6h
#:########################################################################################:# #:########################################################################################:#
# BINARY SENSORS: # BINARY SENSORS:
# https://esphome.io/components/binary_sensor/ # https://esphome.io/components/binary_sensor/
@@ -178,7 +222,7 @@ binary_sensor:
lambda: |- lambda: |-
if (isnan(id(power).state)) { if (isnan(id(power).state)) {
return {}; return {};
} else if (id(power).state > 4) { } else if (id(power).state > 4.0f) {
return true; return true;
} else { } else {
return false; return false;
@@ -194,13 +238,44 @@ sensor:
cf_pin: GPIO14 cf_pin: GPIO14
cf1_pin: GPIO13 cf1_pin: GPIO13
update_interval: 2s update_interval: 2s
current: current:
name: "${entity_prefix} Current" name: "${entity_prefix} Current"
device_class: current
state_class: measurement
unit_of_measurement: "A"
accuracy_decimals: 2
voltage: voltage:
name: "${entity_prefix} Voltage" name: "${entity_prefix} Voltage"
device_class: voltage
state_class: measurement
unit_of_measurement: "V"
accuracy_decimals: 1
power: power:
name: "${entity_prefix} Power" name: "${entity_prefix} Power"
id: power id: power
device_class: power
state_class: measurement
unit_of_measurement: "W"
accuracy_decimals: 1
# Update the seven-day maximum whenever a new peak is measured.
on_value:
then:
- lambda: |-
if (!isnan(x) && x > id(max_power_7d)) {
id(max_power_7d) = x;
id(max_power_7d_sensor).publish_state(x);
ESP_LOGI(
"max_power",
"New seven-day maximum power: %.1f W",
x
);
}
# on_value_range: # on_value_range:
# - above: 4.0 # - above: 4.0
# then: # then:
@@ -209,6 +284,64 @@ sensor:
# then: # then:
# - light.turn_off: led # - light.turn_off: led
# Integrates instantaneous watts over time.
# Watts integrated over hours produces Wh, then multiply converts Wh to kWh.
- platform: integration
name: "${entity_prefix} Energy"
id: energy
sensor: power
time_unit: h
integration_method: trapezoid
restore: false
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3
filters:
- multiply: 0.001
# Highest power reading recorded during the current 168 powered-hour period.
- platform: template
name: "${entity_prefix} Maximum Power 7 Days"
id: max_power_7d_sensor
unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 1
update_interval: 60s
lambda: |-
return id(max_power_7d);
#:########################################################################################:#
# INTERVAL:
# Seven-day maximum power reset
# https://esphome.io/components/interval.html
#:########################################################################################:#
interval:
# Count one powered hour at a time.
# 168 hours equals seven days.
- interval: 1h
then:
- lambda: |-
id(max_power_hours_elapsed) += 1;
ESP_LOGD(
"max_power",
"Seven-day maximum power period: %u of 168 powered hours elapsed",
id(max_power_hours_elapsed)
);
if (id(max_power_hours_elapsed) >= 168) {
id(max_power_hours_elapsed) = 0;
id(max_power_7d) = 0.0f;
id(max_power_7d_sensor).publish_state(0.0f);
ESP_LOGI(
"max_power",
"Seven-day maximum power value has been reset"
);
}
#:########################################################################################:# #:########################################################################################:#
# SWITCH: # SWITCH:
# https://esphome.io/components/switch/ # https://esphome.io/components/switch/
-916
View File
@@ -1,916 +0,0 @@
#:########################################################################################:#
# TITLE: LED MATRIX 1
# zorruno.com layout v1.1 2026
# ESP32-C3 SuperMini MAX7219 LED matrix clock and scroll display.
#:########################################################################################:#
# REFERENCES:
# Original Project Reference https://github.com/RealDeco/matrixclock-esphome
# Fonts: https://github.com/RealDeco/matrixclock-esphome/tree/main/fonts
# 3D Printed Case (includes project notes in the 3mf files)
# https://github.com/RealDeco/matrixclock-esphome/tree/main/3DPrint
#:########################################################################################:#
# REPO:
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-ledmatrix1.yaml
#:########################################################################################:#
# VERSIONS:
# V1.3 2026-05-24 Added native API action for Home Assistant direct scroll text commands.
# V1.2 2026-05-24 Added 1-15 brightness level slider and improved operation notes.
# V1.1 2026-05-23 Updated yaml to zorruno layout V1.1, cleaned comments, fixed SNTP time IDs,
# added current_mins fallback display, removed unused MQTT topic, and fixed
# blank scroll handling.
# V1.0 2026-05-23 Initial Version
#:########################################################################################:#
# HARDWARE:
# - ESP32-C3 SuperMini.
# - MAX7219 4-chip LED matrix display, typically 32 x 8 pixels.
# - SPI wiring configured below:
# - MOSI: GPIO8
# - CS: GPIO9
# - SCK: GPIO10
# - Power note: 4 MAX7219 modules needs a suitable external 5 V supply.
# - Keep ESP32-C3 GND and matrix power supply GND connected together.
#:########################################################################################:#
# OPERATION NOTES:
# - Main display:
# - Shows a 32 x 8 LED matrix clock using the MAX7219 display component.
# - Uses common/sntp_common.yaml as the primary time source via id(sntp_time).
# - If SNTP is not valid, uses id(current_mins) as a fallback clock.
# - In fallback mode the colon stays solid, so it is obvious time is not fully synced.
#
# - Clock controls:
# - Clock format can be selected as 12h or 24h from Home Assistant or MQTT.
# - 12h mode draws a small A or P indicator at the far right of the display.
#
# - Brightness controls:
# - The Display light entity can turn the matrix on/off and set proportional brightness.
# - The Brightness Level number slider sets the exact MAX7219 intensity from 1 to 15.
# - MQTT brightness accepts 0 to turn the display off, or 1-15 to set exact intensity.
# - The 1-15 slider keeps the last non-zero brightness value when the display is off.
#
# - Scroll text controls:
# - Scroll Text is exposed as a Home Assistant text entity.
# - Native API action esphome.esp_ledmatrix1_scroll_text can scroll text directly from Home Assistant.
# - The text box clears itself after a valid scroll request is accepted.
# - Empty scroll requests are ignored and do not replay the previous message.
# - Scroll Delay controls the delay in milliseconds between scroll steps.
#
# - Scroll transitions:
# - Before scrolling, the clock always slides down and off the display.
# - After scrolling, the selected re-entry effect runs.
# - Available re-entry effects are Slide up, Slide down, and Pixel fall.
# - Pixel fall hides the clock until the particle effect finishes.
#:########################################################################################:#
# MQTT COMMANDS:
# - ${mqtt_command_topic}/scroll : Text payload to scroll once across the display.
# - ${mqtt_command_topic}/mode : Payload 12 or 24 to select clock format.
# - ${mqtt_command_topic}/bright : Payload 0 turns display off, 1-15 sets exact brightness.
# - ${mqtt_status_topic}/status : Publishes online/offline status.
#:########################################################################################:#
# HOME ASSISTANT API ACTIONS:
# - esphome.esp_ledmatrix1_scroll_text
# data:
# message: "Text to scroll"
#:########################################################################################:#
# OFFLINE NOTES:
# a) Home Assistant offline, but WiFi/network and MQTT online:
# - Clock display should continue using SNTP if the NTP servers are reachable.
# - MQTT scroll, brightness, and 12h/24h mode commands should still work.
#
# b) MQTT offline, but WiFi/network and Home Assistant API online:
# - Home Assistant text, light, number, and select entities should still work.
# - Clock display should continue using SNTP if the NTP servers are reachable.
#
# c) Entire WiFi/network offline:
# - SNTP will not be available after boot.
# - Clock display will use the current_mins fallback from common/sntp_common.yaml.
# - Accurate time is needed for the clock, so fallback time will drift.
# - For a rough reset, power-cycle the device at 12:00 noon.
# - Home Assistant and MQTT controls will not be available while offline.
#:########################################################################################:#
#:########################################################################################:#
# 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-ledmatrix1"
friendly_name: "LED Matrix Display"
description_comment: "LED Matrix Display (Layout V1.1)"
device_area: "Lounge" # Allows ESP device to be automatically linked to an Area in Home Assistant.
# Project Naming
project_name: "Generic.ESP32 Supermini"
project_version: "v1.3"
# Passwords & Secrets
api_key: !secret esp-api_key
ota_pass: !secret esp-ota_pass
static_ip_address: !secret esp-ledmatrix1_ip # Substitutions cannot be used inside secret names.
mqtt_command_main_topic: !secret mqtt_command_main_topic
mqtt_status_main_topic: !secret mqtt_status_main_topic
# If changing IP addresses, update current_ip_address here, otherwise it remains static_ip_address.
# Do not forget to switch it back when the address change is complete.
current_ip_address: ${static_ip_address}
# MQTT LOCAL Controls
mqtt_device_name: "ledmatrix1"
mqtt_command_topic: "${mqtt_command_main_topic}/${mqtt_device_name}"
mqtt_status_topic: "${mqtt_status_main_topic}/${mqtt_device_name}"
# Device Settings
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG, VERBOSE, VERY_VERBOSE
update_interval: "60s"
# Matrix Display Settings
pin_mosi: GPIO8
pin_cs: GPIO9
pin_sck: GPIO10
num_chips: "4"
scroll_delay_ms: "15" # ms between scroll steps
matrix_initial_brightness: "2" # Default MAX7219 intensity, 1-15.
y_offset: "0"
y_scroll_offset: "0"
#:########################################################################################:#
# 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}"
#### WEB PORTAL ####
common_webportal: !include common/webportal_common.yaml
#### SNTP (Needed for the clock display) ####
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 Home Assistant.
area: "${device_area}"
project:
name: "${project_name}"
version: "${project_version}"
on_boot:
priority: 600
then:
# Ensure Home Assistant shows the text box as empty rather than unknown.
- delay: 500ms
- lambda: |-
id(ha_scroll_text).publish_state("");
- component.update: matrix
#:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK:
# https://esphome.io/components/esp32/
#:########################################################################################:#
esp32:
variant: esp32c3
framework:
type: esp-idf
version: recommended
#esp8266:
# board: esp01_1m
# early_pin_init: false # Recommended false where switches are involved. Defaults to true.
# board_flash_mode: dout # Default is dout.
#:########################################################################################:#
# LOGGER: ESPHome Logging Enable
# https://esphome.io/components/logger.html
#:########################################################################################:#
logger:
level: "${log_level}" # INFO suggested, or DEBUG for testing.
#baud_rate: 0 # Set to 0 for no UART logging if UART is used for another device.
#esp8266_store_log_strings_in_flash: false
#tx_buffer_size: 64
#:########################################################################################:#
# API COMPONENT: Device-specific Home Assistant native API actions
# https://esphome.io/components/api.html
#:########################################################################################:#
api:
actions:
# Home Assistant action: esphome.esp_ledmatrix1_scroll_text
# Example HA data: { message: "Hello from Home Assistant" }
- action: scroll_text
variables:
message: string
then:
- lambda: |-
std::string s = message;
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20; }), s.end());
// Ignore empty API scroll commands completely.
if (s.empty()) return;
// Keep the HA text entity visually in sync with this direct API action.
id(ha_scroll_text).publish_state(s.c_str());
id(scroll_text) = s;
id(scroll_x) = ${num_chips} * 8;
id(start_scroll_sequence).execute();
id(clear_scroll_text_box).execute();
#:########################################################################################:#
# GLOBALS:
# https://esphome.io/components/globals.html
#:########################################################################################:#
globals:
# Text currently being scrolled across the matrix.
- id: scroll_text
type: std::string
initial_value: ""
# Current X position of the scrolling text.
- id: scroll_x
type: int
initial_value: '0'
# True while a scroll message is active.
- id: scrolling
type: bool
initial_value: 'false'
# False = 24h mode, true = 12h mode.
- id: use_12h_mode
type: bool
restore_value: true
initial_value: 'false'
# Runtime scroll delay, controlled by the Scroll Delay number entity.
- id: scroll_delay_runtime
type: int
restore_value: true
initial_value: '${scroll_delay_ms}'
# MAX7219 intensity: 0=OFF, 1..15=ON. Driven by the HA light and brightness slider.
- id: matrix_intensity
type: int
restore_value: false
initial_value: '${matrix_initial_brightness}'
# Last non-zero brightness level selected by HA, MQTT, or the 1-15 slider.
# This allows the exact slider to keep its value when the display is switched off.
- id: matrix_last_nonzero_intensity
type: int
restore_value: true
initial_value: '${matrix_initial_brightness}'
# Brightness sync loop protection for HA light <-> MQTT brightness updates.
- id: suppress_mqtt_bright_sync
type: bool
restore_value: false
initial_value: 'false'
- id: target_mqtt_bright
type: int
restore_value: false
initial_value: '-1'
- id: last_mqtt_bright
type: int
restore_value: false
initial_value: '-1'
# Re-entry effect after scrolling: 0=Slide up, 1=Slide down, 2=Pixel fall.
- id: transition_mode
type: int
restore_value: true
initial_value: '0'
# Slide transition state.
- id: transition_active
type: bool
restore_value: false
initial_value: 'false'
- id: transition_y
type: int
restore_value: false
initial_value: '0'
# Pixel-fall transition state.
- id: pixel_fall_active
type: bool
restore_value: false
initial_value: 'false'
- id: pixel_fall_gen
type: int
restore_value: false
initial_value: '0'
#:########################################################################################:#
# FONT COMPONENT:
# https://esphome.io/components/font/
#:########################################################################################:#
font:
# Clock digits. Keep glyphs limited to reduce firmware size.
- file: "fonts/Eight-Bit-Dragon.ttf"
id: digit5
size: 8
glyphs: ["0123456789: -"]
# General 5x8 font for scroll text.
- file: "fonts/5x8.bdf"
id: font5x8
size: 8
#:########################################################################################:#
# SPI COMPONENT:
# https://esphome.io/components/spi.html
#:########################################################################################:#
spi:
clk_pin: ${pin_sck}
mosi_pin: ${pin_mosi}
#:########################################################################################:#
# MQTT: Device-specific MQTT command triggers
# This adds device-specific MQTT command triggers to the common MQTT configuration.
#:########################################################################################:#
mqtt:
discovery: false
birth_message:
topic: "${mqtt_status_topic}/status"
payload: "online"
qos: 0
retain: true
will_message:
topic: "${mqtt_status_topic}/status"
payload: "offline"
qos: 0
retain: true
on_message:
# Scroll a text payload once across the matrix.
- topic: "${mqtt_command_topic}/scroll"
then:
- lambda: |-
std::string s = x.c_str();
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20; }), s.end());
// Ignore empty MQTT scroll commands completely.
if (s.empty()) return;
id(scroll_text) = s;
id(scroll_x) = ${num_chips} * 8;
id(start_scroll_sequence).execute();
# Set 12h or 24h clock mode from MQTT.
- topic: "${mqtt_command_topic}/mode"
then:
- lambda: |-
if (x == "12") {
id(use_12h_mode) = true;
} else if (x == "24") {
id(use_12h_mode) = false;
}
- component.update: clock_format_select
- component.update: matrix
# Brightness from MQTT. 0=OFF, 1..15=ON brightness level.
- topic: "${mqtt_command_topic}/bright"
then:
- lambda: |-
std::string s = x.c_str();
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20 || c == 0x7F; }), s.end());
s.erase(s.begin(), std::find_if(s.begin(), s.end(),
[](unsigned char ch){ return !std::isspace(ch); }));
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char ch){ return !std::isspace(ch); }).base(), s.end());
if (s.empty() || !std::all_of(s.begin(), s.end(),
[](unsigned char c){ return std::isdigit(c); })) return;
int v = atoi(s.c_str());
if (v < 0) v = 0;
if (v > 15) v = 15;
id(suppress_mqtt_bright_sync) = true;
if (v == 0) {
id(matrix_light).turn_off().perform();
} else {
id(matrix_last_nonzero_intensity) = v;
id(matrix_brightness_number).publish_state((float) v);
auto call = id(matrix_light).turn_on();
call.set_brightness((float) v / 15.0f);
call.perform();
}
id(last_mqtt_bright) = v;
id(suppress_mqtt_bright_sync) = false;
#:########################################################################################:#
# TEXT COMPONENT:
# https://esphome.io/components/text/template/
#:########################################################################################:#
text:
- platform: template
id: ha_scroll_text
name: "${friendly_name} Scroll Text"
icon: "mdi:message-text-outline"
mode: text
max_length: 80
optimistic: false
set_action:
- lambda: |-
std::string s = x.c_str();
s.erase(std::remove(s.begin(), s.end(), '\r'), s.end());
s.erase(std::remove(s.begin(), s.end(), '\n'), s.end());
s.erase(std::remove_if(s.begin(), s.end(),
[](unsigned char c){ return c < 0x20; }), s.end());
// Publish the cleaned text so the HA UI updates immediately.
id(ha_scroll_text).publish_state(s.c_str());
// Empty text only clears the entity. It should not start a scroll.
if (s.empty()) return;
id(scroll_text) = s;
id(scroll_x) = ${num_chips} * 8;
id(start_scroll_sequence).execute();
id(clear_scroll_text_box).execute();
#:########################################################################################:#
# NUMBER COMPONENT:
# https://esphome.io/components/number/template/
#:########################################################################################:#
number:
- platform: template
id: scroll_delay_number
name: "${friendly_name} Scroll Delay"
icon: "mdi:timer-outline"
unit_of_measurement: "ms"
min_value: 1
max_value: 100
step: 1
restore_value: true
initial_value: ${scroll_delay_ms}
optimistic: true
set_action:
- lambda: |-
id(scroll_delay_runtime) = (int) x;
- platform: template
id: matrix_brightness_number
name: "${friendly_name} Brightness Level"
icon: "mdi:brightness-6"
min_value: 1
max_value: 15
step: 1
restore_value: true
initial_value: ${matrix_initial_brightness}
optimistic: true
set_action:
- lambda: |-
int v = (int) lroundf(x);
if (v < 1) v = 1;
if (v > 15) v = 15;
id(matrix_last_nonzero_intensity) = v;
// Setting the exact brightness slider also turns the display on.
auto call = id(matrix_light).turn_on();
call.set_brightness((float) v / 15.0f);
call.perform();
#:########################################################################################:#
# OUTPUT COMPONENT:
# https://esphome.io/components/output/
#:########################################################################################:#
output:
# Template output lets the HA light and brightness slider control MAX7219 intensity.
- platform: template
id: matrix_brightness_out
type: float
write_action:
- lambda: |-
int pub = 0;
if (state <= 0.001f) {
id(matrix_intensity) = 0;
id(matrix).turn_on_off(false);
pub = 0;
} else {
int v = (int) lroundf(state * 15.0f);
if (v < 1) v = 1;
if (v > 15) v = 15;
id(matrix_intensity) = v;
id(matrix_last_nonzero_intensity) = v;
id(matrix_brightness_number).publish_state((float) v);
id(matrix).turn_on_off(true);
pub = v;
}
id(target_mqtt_bright) = pub;
- component.update: matrix
# Publish brightness back to MQTT unless this update came from MQTT.
- if:
condition:
lambda: |-
return !id(suppress_mqtt_bright_sync)
&& (id(target_mqtt_bright) != id(last_mqtt_bright));
then:
- mqtt.publish:
topic: "${mqtt_command_topic}/bright"
payload: !lambda |-
char buf[6];
snprintf(buf, sizeof(buf), "%d", id(target_mqtt_bright));
return std::string(buf);
qos: 0
retain: true
- lambda: |-
id(last_mqtt_bright) = id(target_mqtt_bright);
#:########################################################################################:#
# LIGHT COMPONENT:
# https://esphome.io/components/light/monochromatic/
#:########################################################################################:#
light:
- platform: monochromatic
id: matrix_light
name: "${friendly_name} Display"
icon: "mdi:brightness-6"
output: matrix_brightness_out
gamma_correct: 1.0
default_transition_length: 0s
restore_mode: RESTORE_DEFAULT_ON
#:########################################################################################:#
# SELECT COMPONENT:
# https://esphome.io/components/select/template/
#:########################################################################################:#
select:
- platform: template
id: clock_format_select
name: "${friendly_name} Clock Format"
icon: "mdi:clock-time-three-outline"
options:
- "24"
- "12"
lambda: |-
return esphome::optional<std::string>(std::string(id(use_12h_mode) ? "12" : "24"));
set_action:
- lambda: |-
id(use_12h_mode) = (x == "12");
- component.update: clock_format_select
- component.update: matrix
- platform: template
id: transition_select
name: "${friendly_name} Transition Effect"
icon: "mdi:transition"
options:
- "Slide up"
- "Slide down"
- "Pixel fall"
lambda: |-
if (id(transition_mode) == 2) return std::string("Pixel fall");
if (id(transition_mode) == 1) return std::string("Slide down");
return std::string("Slide up");
set_action:
- lambda: |-
if (x == "Pixel fall") id(transition_mode) = 2;
else if (x == "Slide down") id(transition_mode) = 1;
else id(transition_mode) = 0;
- component.update: transition_select
#:########################################################################################:#
# SCRIPT COMPONENT:
# https://esphome.io/components/script.html
#:########################################################################################:#
script:
# Clear the HA text box shortly after a scroll request has been accepted.
- id: clear_scroll_text_box
mode: restart
then:
- delay: 400ms
- lambda: |-
id(ha_scroll_text).publish_state("");
# Sequence: exit slide-down -> scroll -> selected re-entry.
- id: start_scroll_sequence
mode: restart
then:
- lambda: |-
id(scrolling) = false;
id(pixel_fall_active) = false;
- component.update: matrix
- script.execute: clock_exit_down
- script.wait: clock_exit_down
- lambda: |-
id(scrolling) = true;
- script.execute: do_scroll
# Scroll the stored text until it has moved fully off the left side.
- id: do_scroll
mode: restart
then:
- while:
condition:
lambda: |-
return id(scrolling);
then:
- lambda: |-
id(scroll_x) -= 1;
const int end_limit = - (int(id(scroll_text).length()) * 6);
if (id(scroll_x) < end_limit) {
id(scrolling) = false;
if (id(transition_mode) == 1) {
id(clock_slide_down_entry).execute();
} else if (id(transition_mode) == 2) {
id(clock_pixel_fall_transition).execute();
} else {
id(clock_slide_up_entry).execute();
}
}
- component.update: matrix
- delay: !lambda |-
return (uint32_t) id(scroll_delay_runtime);
# Exit: slide clock down off-screen and hold it there to prevent a one-frame flash.
- id: clock_exit_down
mode: restart
then:
- lambda: |-
id(transition_active) = true;
id(transition_y) = ${y_offset};
- repeat:
count: 9
then:
- lambda: |-
id(transition_y) += 1;
- component.update: matrix
- delay: 45ms
- lambda: |-
id(transition_active) = true;
id(transition_y) = 8;
- component.update: matrix
# Re-entry: clock slides down into place from above.
- id: clock_slide_down_entry
mode: restart
then:
- lambda: |-
id(transition_active) = true;
id(transition_y) = -8;
- repeat:
count: 9
then:
- lambda: |-
id(transition_y) += 1;
- component.update: matrix
- delay: 60ms
- lambda: |-
id(transition_active) = false;
id(transition_y) = ${y_offset};
- component.update: matrix
# Re-entry: clock slides up into place from below.
- id: clock_slide_up_entry
mode: restart
then:
- lambda: |-
id(transition_active) = true;
id(transition_y) = 8;
- repeat:
count: 9
then:
- lambda: |-
id(transition_y) -= 1;
- component.update: matrix
- delay: 60ms
- lambda: |-
id(transition_active) = false;
id(transition_y) = ${y_offset};
- component.update: matrix
# Re-entry: falling pixels effect before the clock redraws.
- id: clock_pixel_fall_transition
mode: restart
then:
- lambda: |-
id(transition_active) = false;
id(transition_y) = ${y_offset};
id(pixel_fall_active) = true;
id(pixel_fall_gen) += 1;
- repeat:
count: 18
then:
- component.update: matrix
- delay: 55ms
- lambda: |-
id(pixel_fall_active) = false;
id(transition_active) = false;
id(transition_y) = ${y_offset};
- component.update: matrix
#:########################################################################################:#
# DISPLAY COMPONENT:
# https://esphome.io/components/display/max7219digit/
#:########################################################################################:#
display:
- platform: max7219digit
id: matrix
cs_pin: ${pin_cs}
num_chips: ${num_chips}
intensity: 2
update_interval: 500ms
rotate_chip: 0
reverse_enable: false
flip_x: false
lambda: |-
// Apply brightness only when it changes to reduce flicker.
static int last_brightness = -1;
if (id(matrix_intensity) != last_brightness) {
if (id(matrix_intensity) > 0) {
int v = id(matrix_intensity);
if (v > 15) v = 15;
it.intensity((uint8_t) v);
}
last_brightness = id(matrix_intensity);
}
if (id(matrix_intensity) == 0) return;
it.clear();
// Scroll text has priority over clock display.
if (id(scrolling)) {
it.print(id(scroll_x), ${y_scroll_offset}, id(font5x8), id(scroll_text).c_str());
return;
}
// Pixel fall shows only particles while active.
if (id(pixel_fall_active)) {
static int last_gen = -1;
static int px[24];
static int py[24];
static uint32_t rng = 1;
auto rand_u32 = [&]() -> uint32_t {
rng = rng * 1664525u + 1013904223u;
return rng;
};
if (last_gen != id(pixel_fall_gen)) {
last_gen = id(pixel_fall_gen);
rng = 0xA5A5A5A5u ^ (uint32_t) id(pixel_fall_gen);
for (int i = 0; i < 24; i++) {
px[i] = (int)(rand_u32() % 32);
py[i] = - (int)(rand_u32() % 16);
}
}
for (int i = 0; i < 24; i++) {
int spd = 1 + (int)(rand_u32() % 2);
py[i] += spd;
if (py[i] > 10) {
px[i] = (int)(rand_u32() % 32);
py[i] = - (int)(rand_u32() % 10);
}
for (int t = 0; t < 2; t++) {
int yy = py[i] - t;
int xx = px[i];
if (xx >= 0 && xx <= 31 && yy >= 0 && yy <= 7) {
it.draw_pixel_at(xx, yy, Color::WHITE);
}
}
}
return;
}
// Prefer real SNTP time. Fall back to current_mins from common/sntp_common.yaml.
auto now = id(sntp_time).now();
bool time_valid = now.is_valid();
int raw_hour = 0;
int minute_to_show = 0;
int second_to_show = 0;
if (time_valid) {
raw_hour = now.hour;
minute_to_show = now.minute;
second_to_show = now.second;
} else {
int mins = id(current_mins);
if (mins < 0) mins = 0;
if (mins >= 1440) mins = mins % 1440;
raw_hour = mins / 60;
minute_to_show = mins % 60;
second_to_show = 0;
}
int hour_to_show = raw_hour;
if (id(use_12h_mode)) {
hour_to_show = raw_hour % 12;
if (hour_to_show == 0) hour_to_show = 12;
}
char hh[3], mm[3];
snprintf(hh, sizeof(hh), "%02d", hour_to_show);
snprintf(mm, sizeof(mm), "%02d", minute_to_show);
const int x_h1 = 3;
const int x_h2 = 9;
const int x_m1 = 17;
const int x_m2 = 23;
const int y = (id(transition_active) ? id(transition_y) : ${y_offset});
char ch[2] = { hh[0], 0 };
it.print(x_h1, y, id(digit5), ch);
ch[0] = hh[1];
it.print(x_h2, y, id(digit5), ch);
ch[0] = mm[0];
it.print(x_m1, y, id(digit5), ch);
ch[0] = mm[1];
it.print(x_m2, y, id(digit5), ch);
// In 12h mode, draw a tiny A or P indicator at the far right.
if (id(use_12h_mode)) {
const bool is_pm = raw_hour >= 12;
const uint8_t A_rows[4] = { 2, 5, 7, 5 };
const uint8_t P_rows[4] = { 6, 5, 6, 4 };
const uint8_t *L = is_pm ? P_rows : A_rows;
const int x_ampm = 29;
const int y_ampm = y + 4;
for (int r = 0; r < 4; r++) {
uint8_t row = L[r];
for (int c = 0; c < 3; c++) {
if (row & (1 << (2 - c))) {
int xx = x_ampm + c;
int yy = y_ampm + r;
if (yy >= 0 && yy <= 7 && xx >= 0 && xx <= 31) {
it.draw_pixel_at(xx, yy, Color::WHITE);
}
}
}
}
}
// Blink the colon when SNTP is valid. Keep it solid during fallback time.
const bool colon_on = time_valid ? ((second_to_show % 2) == 0) : true;
if (colon_on) {
const int x_col = (x_h2 + x_m1) / 2 + 2;
int y1 = y + 2;
int y2 = y + 5;
if (y1 >= 0 && y1 <= 7) it.draw_pixel_at(x_col, y1, Color::WHITE);
if (y2 >= 0 && y2 <= 7) it.draw_pixel_at(x_col, y2, Color::WHITE);
}
File diff suppressed because it is too large Load Diff
+31 -31
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungebookshelfleds.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungebookshelfleds.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V2.2 2026-07-20 Simplified local MQTT command and status topics to the device root
# V2.1 2026-03-11 Updated yaml to zorruno layout V1.1 # V2.1 2026-03-11 Updated yaml to zorruno layout V1.1
# V2.0 2025-09-15 A bunch of fixes and made it more standard across my devices # V2.0 2025-09-15 A bunch of fixes and made it more standard across my devices
# V1.0 2025-08-17 First setup (and replacement of Tasmota) # V1.0 2025-08-17 First setup (and replacement of Tasmota)
@@ -48,10 +49,10 @@
# - Maximum ON time before automatic fade-down (1-48 h, 0 = no limit) # - Maximum ON time before automatic fade-down (1-48 h, 0 = no limit)
# #
# MQTT COMMANDS: # MQTT COMMANDS:
# - ${mqtt_local_command_topic}/light/set = ON # - ${mqtt_local_command_topic} = ON
# - ${mqtt_local_command_topic}/light/set = OFF # - ${mqtt_local_command_topic} = OFF
# - State publishes to: # - State publishes to:
# - ${mqtt_local_status_topic}/light/state # - ${mqtt_local_status_topic}
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
# a) Home Assistant offline (network and MQTT online): # a) Home Assistant offline (network and MQTT online):
@@ -84,7 +85,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Magichome Single Colour V1.1 LED Controller.Generic ESP8266" # Project details. Manufacturer before the dot, device after the dot. project_name: "Magichome Single Colour V1.1 LED Controller.Generic ESP8266" # Project details. Manufacturer before the dot, device after the dot.
project_version: "v2.1" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version project_version: "v2.2" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names) # Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key # If using HA API api_key: !secret esp-api_key # If using HA API
@@ -102,19 +103,19 @@ substitutions:
mqtt_local_device_command_OFF: "OFF" mqtt_local_device_command_OFF: "OFF"
# Device Specific Settings # Device Specific Settings
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
update_interval: "20s" # Update time for general sensors, etc. update_interval: "20s" # Update time for general sensors, etc.
led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM led_gamma: "1.2" # Gamma from 1.2-3 is sensible to normalise the LED fading vs PWM
minimum_led_output: "1" # % If at this value or below, we will switch it completely off minimum_led_output: "1" # % If at this value or below, we will switch it completely off
maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics maximum_led_output: "90" # % Maximum output; it is sometimes nice to limit the output for longevity or aesthetics
max_on_default_hours: "24" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off max_on_default_hours: "24" # The maximum time the LEDs will be on, in case they get left on. 0 = no automatic turn-off
pwm_frequency: "1000" # PWM output Frequency in Hz. High enough to avoid audible/visible artifacts pwm_frequency: "1000" # PWM output Frequency in Hz. High enough to avoid audible/visible artifacts
# Device Specific GPIO (so we can easily update for other devices) # Device Specific GPIO (so we can easily update for other devices)
device_status_led: GPIO2 device_status_led: GPIO2
device_mosfet_out: GPIO12 device_mosfet_out: GPIO12
device_usr_button: GPIO4 # if no button, this is just a fake/safeish choice so it compiles with the generic yaml device_usr_button: GPIO4 # if no button, this is just a fake/safeish choice so it compiles with the generic yaml
device_fading_led: GPIO13 # if no LED, this is just a fake/safeish choice so it compiles with the generic yaml device_fading_led: GPIO13 # if no LED, this is just a fake/safeish choice so it compiles with the generic yaml
#:########################################################################################:# #:########################################################################################:#
# PACKAGES: # PACKAGES:
@@ -132,8 +133,7 @@ packages:
local_ota_pass: "${ota_pass}" local_ota_pass: "${ota_pass}"
#### HOME ASSISTANT API (choose encryption or no encryption options) #### #### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include common_api: !include #file: common/api_common.yaml
#file: common/api_common.yaml
file: common/api_common_noencryption.yaml file: common/api_common_noencryption.yaml
vars: vars:
local_api_key: "${api_key}" local_api_key: "${api_key}"
@@ -245,12 +245,12 @@ globals:
- id: min_brightness_pct - id: min_brightness_pct
type: int type: int
restore_value: true restore_value: true
initial_value: "${minimum_led_output}" # start/finish at X% initial_value: "${minimum_led_output}" # start/finish at X%
# Maximum Brightness % for LEDs (should never go beyond this) # Maximum Brightness % for LEDs (should never go beyond this)
- id: max_brightness_pct - id: max_brightness_pct
type: int type: int
restore_value: false restore_value: false
initial_value: "${maximum_led_output}" # hard cap; never exceed this initial_value: "${maximum_led_output}" # hard cap; never exceed this
# The maximum time the lights will stay on, in hours. Just in case they are left on. 0 = forever # The maximum time the lights will stay on, in hours. Just in case they are left on. 0 = forever
- id: max_on_hours - id: max_on_hours
type: int type: int
@@ -260,23 +260,23 @@ globals:
- id: led_gamma_f - id: led_gamma_f
type: float type: float
restore_value: false restore_value: false
initial_value: ${led_gamma} # numeric at compile time initial_value: ${led_gamma} # numeric at compile time
# Default Fading Up Time (Selectable and will be retained) # Default Fading Up Time (Selectable and will be retained)
- id: ramp_up_ms # fade-in when turned ON - id: ramp_up_ms # fade-in when turned ON
type: int type: int
restore_value: true restore_value: true
initial_value: "5000" # 5 s initial_value: "5000" # 5 s
# Default Fading Down Time (Selectable and will be retained) # Default Fading Down Time (Selectable and will be retained)
- id: ramp_down_ms # fade-out when turned OFF - id: ramp_down_ms # fade-out when turned OFF
type: int type: int
restore_value: true restore_value: true
initial_value: "10000" # 10 s initial_value: "10000" # 10 s
# Action on Restart. (0=Fade full, 1=Restore brightness, 2=Remain off) # Action on Restart. (0=Fade full, 1=Restore brightness, 2=Remain off)
- id: restart_mode - id: restart_mode
type: int type: int
restore_value: true restore_value: true
initial_value: "0" # default = Fade Up to Full (so can be deployed with no other setup) initial_value: "0" # default = Fade Up to Full (so can be deployed with no other setup)
# Determine last fade direction. # Determine last fade direction.
# true when you asked the light to end up ON (Ramp Up) # true when you asked the light to end up ON (Ramp Up)
@@ -332,7 +332,7 @@ globals:
# Logs all log messages through the serial port and through MQTT topics. # Logs all log messages through the serial port and through MQTT topics.
#:########################################################################################:# #:########################################################################################:#
logger: logger:
level: "${log_level}" # INFO Level suggested, or DEBUG for testing level: "${log_level}" # INFO Level suggested, or DEBUG for testing
baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control) baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM, Serial control)
#:########################################################################################:# #:########################################################################################:#
@@ -351,12 +351,12 @@ status_led:
mqtt: mqtt:
on_message: on_message:
# Light control to ramp up # Light control to ramp up
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
then: then:
- switch.turn_on: mosfet_ramp_switch - switch.turn_on: mosfet_ramp_switch
# Light control to ramp down # Light control to ramp down
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
then: then:
- switch.turn_off: mosfet_ramp_switch - switch.turn_off: mosfet_ramp_switch
@@ -570,7 +570,7 @@ output:
frequency: ${pwm_frequency} frequency: ${pwm_frequency}
- platform: gpio - platform: gpio
id: green_led_out # Green LED id: green_led_out # Green LED
pin: pin:
number: ${device_fading_led} number: ${device_fading_led}
inverted: false inverted: false
@@ -593,7 +593,7 @@ light:
on_turn_on: on_turn_on:
- output.turn_on: green_led_out - output.turn_on: green_led_out
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -617,7 +617,7 @@ light:
lambda: "return !id(booting);" lambda: "return !id(booting);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
retain: true retain: true
- lambda: "id(ramp_switch_target_on) = false;" - lambda: "id(ramp_switch_target_on) = false;"
@@ -957,7 +957,7 @@ script:
- id: deferred_restore_brightness - id: deferred_restore_brightness
mode: restart mode: restart
then: then:
- delay: 5s # let preferences/globals load and light settle - delay: 5s # let preferences/globals load and light settle
- lambda: |- - lambda: |-
// Use ONLY the exact last saved brightness. // Use ONLY the exact last saved brightness.
float target = id(last_brightness_pct); float target = id(last_brightness_pct);
@@ -987,4 +987,4 @@ script:
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
# (Removed: output.turn_off: green_led_out) # make sure LED is off if no ramp # (Removed: output.turn_off: green_led_out) # make sure LED is off if no ramp
+43 -43
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungecabinetleds.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungecabinetleds.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V2.2 2026-07-20 Simplified local MQTT command and status topics to the device root
# V2.1 2026-03-11 Updated yaml to zorruno layout V1.1 # V2.1 2026-03-11 Updated yaml to zorruno layout V1.1
# V2.0 2025-09-15 A bunch of fixes, this is now the master for other similar mosfet controllers of mine # V2.0 2025-09-15 A bunch of fixes, this is now the master for other similar mosfet controllers of mine
# V1.5 2025-09-11 First setup (copied from another template) # V1.5 2025-09-11 First setup (copied from another template)
@@ -50,10 +51,10 @@
# - Maximum ON time before automatic fade-down (1-48 h, 0 = no limit) # - Maximum ON time before automatic fade-down (1-48 h, 0 = no limit)
# #
# MQTT COMMANDS: # MQTT COMMANDS:
# - ${mqtt_local_command_topic}/light/set = ON # - ${mqtt_local_command_topic} = ON
# - ${mqtt_local_command_topic}/light/set = OFF # - ${mqtt_local_command_topic} = OFF
# - State publishes to: # - State publishes to:
# - ${mqtt_local_status_topic}/light/state # - ${mqtt_local_status_topic}
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
# a) Home Assistant offline (network and MQTT online) # a) Home Assistant offline (network and MQTT online)
@@ -80,7 +81,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot. project_name: "Sinilink.XY-WFMS" # Project details. Manufacturer before the dot, device after the dot.
project_version: "v2.1" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version project_version: "v2.2" # Project version denotes release of the YAML file, allowing checking of deployed vs latest version
# Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names) # Passwords & Secrets (Unfortunately, you can't use substitutions inside secret names)
api_key: !secret esp-api_key # If using HA API api_key: !secret esp-api_key # If using HA API
@@ -130,8 +131,7 @@ packages:
local_current_ip_address: "${current_ip_address}" local_current_ip_address: "${current_ip_address}"
#### HOME ASSISTANT API (choose encryption or no encryption options) #### #### HOME ASSISTANT API (choose encryption or no encryption options) ####
common_api: !include common_api: !include #file: common/api_common.yaml
#file: common/api_common.yaml
file: common/api_common_noencryption.yaml file: common/api_common_noencryption.yaml
vars: vars:
local_api_key: "${api_key}" local_api_key: "${api_key}"
@@ -181,32 +181,32 @@ esphome:
# Mode 0: Fade up to full (obeys fade settings; no on/off flicker) # Mode 0: Fade up to full (obeys fade settings; no on/off flicker)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 0;' lambda: "return id(restart_mode) == 0;"
then: then:
- lambda: 'id(ramp_switch_target_on) = true;' - lambda: "id(ramp_switch_target_on) = true;"
- script.execute: ramp_on_script - script.execute: ramp_on_script
# Mode 1: Restore Brightness (deferred to avoid races) # Mode 1: Restore Brightness (deferred to avoid races)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 1;' lambda: "return id(restart_mode) == 1;"
then: then:
- script.execute: deferred_restore_brightness - script.execute: deferred_restore_brightness
# Mode 2: Remain Off (no blip; stays off) # Mode 2: Remain Off (no blip; stays off)
- if: - if:
condition: condition:
lambda: 'return id(restart_mode) == 2;' lambda: "return id(restart_mode) == 2;"
then: then:
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
# Boot complete: allow OFF handlers again # Boot complete: allow OFF handlers again
- lambda: 'id(booting) = false;' - lambda: "id(booting) = false;"
# NEW: force indicator OFF after boot logic # NEW: force indicator OFF after boot logic
# (Removed: output.turn_off: green_led_out) # (Removed: output.turn_off: green_led_out)
@@ -365,13 +365,13 @@ status_led:
mqtt: mqtt:
on_message: on_message:
# Light control to ramp up # Light control to ramp up
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
then: then:
- switch.turn_on: mosfet_ramp_switch - switch.turn_on: mosfet_ramp_switch
# Light control to ramp down # Light control to ramp down
- topic: "${mqtt_local_command_topic}/light/set" - topic: "${mqtt_local_command_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
then: then:
- switch.turn_off: mosfet_ramp_switch - switch.turn_off: mosfet_ramp_switch
@@ -503,7 +503,7 @@ binary_sensor:
on_press: on_press:
- if: - if:
condition: condition:
lambda: 'return id(ramp_switch_target_on);' lambda: "return id(ramp_switch_target_on);"
then: then:
# Target is currently ON -> press should go OFF (start ramp-down) # Target is currently ON -> press should go OFF (start ramp-down)
- lambda: |- - lambda: |-
@@ -608,7 +608,7 @@ light:
on_turn_on: on_turn_on:
- output.turn_on: green_led_out - output.turn_on: green_led_out
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_ON}" payload: "${mqtt_local_device_command_ON}"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -619,7 +619,7 @@ light:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -629,13 +629,13 @@ light:
- output.turn_off: green_led_out - output.turn_off: green_led_out
- if: - if:
condition: condition:
lambda: 'return !id(booting);' lambda: "return !id(booting);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic}/light/state" topic: "${mqtt_local_status_topic}"
payload: "${mqtt_local_device_command_OFF}" payload: "${mqtt_local_device_command_OFF}"
retain: true retain: true
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- lambda: |- - lambda: |-
id(last_brightness_pct) = 0.0f; id(last_brightness_pct) = 0.0f;
@@ -724,10 +724,10 @@ number:
set_action: set_action:
- if: - if:
condition: condition:
lambda: 'return x <= 0.0f;' lambda: "return x <= 0.0f;"
then: then:
# 0 means OFF # 0 means OFF
- lambda: 'id(suppress_slider_sync) = true;' - lambda: "id(suppress_slider_sync) = true;"
- script.stop: ramp_on_script - script.stop: ramp_on_script
- script.stop: ramp_off_script - script.stop: ramp_off_script
- light.turn_off: - light.turn_off:
@@ -739,7 +739,7 @@ number:
id(last_brightness_pct) = 0.0f; // persist OFF immediately id(last_brightness_pct) = 0.0f; // persist OFF immediately
id(last_set_pos) = 0; id(last_set_pos) = 0;
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
# Map 1..100 - [min..max] and set ON # Map 1..100 - [min..max] and set ON
- lambda: |- - lambda: |-
@@ -771,7 +771,7 @@ number:
if (out_pct > maxp) out_pct = maxp; if (out_pct > maxp) out_pct = maxp;
id(last_brightness_pct) = out_pct; // persist exact target now id(last_brightness_pct) = out_pct; // persist exact target now
- delay: 400ms - delay: 400ms
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
- platform: template - platform: template
id: cfg_max_on_hours id: cfg_max_on_hours
@@ -794,12 +794,12 @@ number:
id(cfg_max_on_hours).publish_state((float) hrs); id(cfg_max_on_hours).publish_state((float) hrs);
- if: - if:
condition: condition:
lambda: 'return id(mosfet_leds).current_values.is_on();' lambda: "return id(mosfet_leds).current_values.is_on();"
then: then:
- script.stop: max_on_watchdog - script.stop: max_on_watchdog
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- script.execute: max_on_watchdog - script.execute: max_on_watchdog
@@ -813,7 +813,7 @@ script:
- id: ramp_on_script - id: ramp_on_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
# Ensure we start at at least the floor without a visible "pop". # Ensure we start at at least the floor without a visible "pop".
- if: - if:
@@ -825,12 +825,12 @@ script:
then: then:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
# Ramp from current (>= floor) to cap over a fraction of ramp_up_ms. # Ramp from current (>= floor) to cap over a fraction of ramp_up_ms.
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;' brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -850,7 +850,7 @@ script:
if (frac > 1.0f) frac = 1.0f; if (frac > 1.0f) frac = 1.0f;
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
@@ -863,7 +863,7 @@ script:
- id: ramp_to_target_script - id: ramp_to_target_script
mode: restart mode: restart
then: then:
- lambda: 'id(is_ramping) = true;' - lambda: "id(is_ramping) = true;"
- script.stop: ramp_off_script - script.stop: ramp_off_script
# Ensure we start at the floor cleanly. # Ensure we start at the floor cleanly.
- if: - if:
@@ -875,7 +875,7 @@ script:
then: then:
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: 80ms transition_length: 80ms
# Ramp from current (>= floor) to restore_target_pct over a fraction of ramp_up_ms. # Ramp from current (>= floor) to restore_target_pct over a fraction of ramp_up_ms.
- light.turn_on: - light.turn_on:
@@ -906,7 +906,7 @@ script:
if (frac > 1.0f) frac = 1.0f; if (frac > 1.0f) frac = 1.0f;
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: |- - lambda: |-
id(is_ramping) = false; id(is_ramping) = false;
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
@@ -925,7 +925,7 @@ script:
- script.stop: ramp_on_script - script.stop: ramp_on_script
- light.turn_on: - light.turn_on:
id: mosfet_leds id: mosfet_leds
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;' brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
transition_length: !lambda |- transition_length: !lambda |-
const auto &cv = id(mosfet_leds).current_values; const auto &cv = id(mosfet_leds).current_values;
const float floor = id(min_brightness_pct) / 100.0f; const float floor = id(min_brightness_pct) / 100.0f;
@@ -938,7 +938,7 @@ script:
if (frac > 1.0f) frac = 1.0f; if (frac > 1.0f) frac = 1.0f;
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac); id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
return (uint32_t) id(last_ramp_ms); return (uint32_t) id(last_ramp_ms);
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 150ms transition_length: 150ms
@@ -955,12 +955,12 @@ script:
then: then:
- if: - if:
condition: condition:
lambda: 'return id(max_on_hours) > 0;' lambda: "return id(max_on_hours) > 0;"
then: then:
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);' - delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
- if: - if:
condition: condition:
lambda: 'return id(mosfet_leds).current_values.is_on();' lambda: "return id(mosfet_leds).current_values.is_on();"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = false; id(ramp_switch_target_on) = false;
@@ -987,19 +987,19 @@ script:
- if: - if:
condition: condition:
lambda: 'return id(restore_target_pct) > 0.5f;' lambda: "return id(restore_target_pct) > 0.5f;"
then: then:
- lambda: |- - lambda: |-
id(ramp_switch_target_on) = true; // reflect our intent id(ramp_switch_target_on) = true; // reflect our intent
id(suppress_slider_sync) = true; id(suppress_slider_sync) = true;
- script.stop: ramp_off_script - script.stop: ramp_off_script
- script.execute: ramp_to_target_script - script.execute: ramp_to_target_script
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);' - delay: !lambda "return (uint32_t) id(last_ramp_ms);"
- lambda: 'id(suppress_slider_sync) = false;' - lambda: "id(suppress_slider_sync) = false;"
else: else:
# Exact last was 0 -> remain OFF # Exact last was 0 -> remain OFF
- lambda: 'id(ramp_switch_target_on) = false;' - lambda: "id(ramp_switch_target_on) = false;"
- light.turn_off: - light.turn_off:
id: mosfet_leds id: mosfet_leds
transition_length: 0s transition_length: 0s
# (Removed: output.turn_off: green_led_out) # make sure LED is off if no ramp # (Removed: output.turn_off: green_led_out) # make sure LED is off if no ramp
-507
View File
@@ -1,507 +0,0 @@
#:########################################################################################:#
# TITLE: LOUNGE MIDDLE LIGHTSWITCH
# zorruno.com layout v1.1 2026
#:########################################################################################:#
# REPO:
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungemiddleswitch.yaml
#:########################################################################################:#
# VERSIONS:
# V3.6 2026-03-11 Updated yaml to zorruno layout V1.1
# V3.5 2025-07-28 YAML tidyups
#:########################################################################################:#
# HARDWARE:
# - Zemismart KS-811 Triple push button
# - ESP8266 based
# - pinout/schematic:
# https://community.home-assistant.io/t/zemismart-ks-811-working-with-esphome/
# - Relay 1 output on GPIO13
# - Relay 2 output on GPIO12
# - Relay 3 output on GPIO14
# - Button 1 input on GPIO16
# - Button 2 input on GPIO5
# - Button 3 input on GPIO4
# - Status LED on GPIO2
#:########################################################################################:#
# OPERATION NOTES:
# - Button 1:
# - Online with HA Loss manual override ON and HA Loss Test OFF:
# - Uses Home Assistant last-scene logic
# - If last scene is not "All Off", set "All Off"
# - If last scene is "All Off", set "Bright"
# - Offline/test with HA Loss manual override ON:
# - Toggles Relay_1 locally
# - Button 2:
# - Online with HA Loss manual override ON and HA Loss Test OFF:
# - Sets Home Assistant scene "Movie"
# - Offline/test with HA Loss manual override ON:
# - Toggles Relay_2 locally
# - Button 3:
# - Online with HA Loss manual override ON and HA Loss Test OFF:
# - Sets Home Assistant scene "Reading On/Off"
# - Offline/test with HA Loss manual override ON:
# - Toggles Relay_3 locally
# - On API connect:
# - If HA Loss Test is OFF, Relay_1 and Relay_2 follow HA Loss manual override state
# - If HA Loss Test is ON, relay override is not applied
# - HA Loss manual override defaults ON and persists across restarts
# - HA Loss Test defaults OFF and can force offline/test behaviour even when HA is connected
#:########################################################################################:#
# OFFLINE NOTES:
# a) Home Assistant offline (network online):
# - Device switches to manual/offline behaviour
# - Buttons still operate local relays if HA Loss manual override is ON
# - Home Assistant scene calls are unavailable
# b) MQTT offline (or HA/MQTT offline):
# - This yaml does not depend on MQTT for its button logic
# - If Home Assistant/API is offline, device falls back to manual/offline behaviour
# - Buttons still operate local relays if HA Loss manual override is ON
# c) Entire WiFi/Network offline:
# - Device operates in local manual/offline behaviour only
# - Buttons still operate local relays if HA Loss manual override is ON
# - Accurate time is not needed; SNTP is 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-loungemiddleswitch"
friendly_name: "Lounge Middle Lightswitch (3)"
description_comment: "Lounge Main Lightswitch using a Zemismart KS-811 Triple Push Button. Downlights South (1), Downlights Middle (2), Downlights North (3) (Layout V1.1)"
device_area: "Lounge" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
# Project Naming
project_name: "Zemismart Technologies.KS-811 Triple" # Project Details
project_version: "v3.6" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
# Passwords & Secrets
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
ota_pass: !secret esp-ota_pass # unfortunately you can't use substitutions inside secrets names
static_ip_address: !secret esp-loungemiddleswitch_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}
# Device Settings
log_level: "INFO" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
update_interval: "60s" # update time for for general sensors etc
# MQTT LOCAL Controls
#mqtt_device_name: "lounge-main-lights"
#mqtt_local_command_topic: "${mqtt_command_main_topic}/${mqtt_device_name}" # Topic we will use to command this locally without HA
#mqtt_local_status_topic: "${mqtt_status_main_topic}/${mqtt_device_name}" # Topic we will use to view status locally without HA
# MQTT REMOTE Controls
# Switch/Relay/Button Naming & Icons
#relay_icon: "mdi:lightbulb-group"
switch_1_name: "Downlights South" # Lights are connected via DMX dimmer (another esphome controller) (4 Downlights)
switch_2_name: "Downlights Middle" # Lights are connected via DMX dimmer (another esphome controller) (6 Downlights)
switch_3_name: "Downlights North" # Lights are directly connected (2 downlights)
#:########################################################################################:#
# 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_current_ip_address: "${current_ip_address}"
local_ota_pass: "${ota_pass}"
#### 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}"
#### WEB PORTAL ####
#common_webportal: !include common/webportal_common.yaml
#### 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
area: "${device_area}"
project:
name: "${project_name}"
version: "${project_version}"
#:########################################################################################:#
# HOME ASSISTANT API:
#:########################################################################################:#
api:
on_client_connected:
then:
- logger.log:
level: INFO
format: "API connected: override=%s, ha_loss_test=%s"
args:
- 'id(ha_loss_override).state ? "true" : "false"'
- 'id(ha_loss_test).state ? "true" : "false"'
- if:
condition:
switch.is_on: ha_loss_test
then:
- logger.log:
level: WARN
format: "HA Loss Test is ON: not applying relay override on connect"
else:
- if:
condition:
switch.is_on: ha_loss_override
then:
- switch.turn_on: Relay_1
- switch.turn_on: Relay_2
else:
- switch.turn_off: Relay_1
- switch.turn_off: Relay_2
on_client_disconnected:
then:
- logger.log:
level: WARN
format: "API disconnected: switching to manual/offline behavior"
#:########################################################################################:#
# ESP PLATFORM AND FRAMEWORK:
# https://esphome.io/components/esp8266.html
#:########################################################################################:#
esp8266:
board: esp01_1m
early_pin_init: false # Initialise pins early to known values. Recommended false where switches are involved. Defaults to True.
board_flash_mode: dout # Default is dout
#:########################################################################################:#
# LOGGER:
# https://esphome.io/components/logger.html
#:########################################################################################:#
logger:
level: "${log_level}" # INFO Level suggested, or DEBUG for testing
#baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
#esp8266_store_log_strings_in_flash: false
#tx_buffer_size: 64
#:########################################################################################:#
# STATUS LED:
# https://esphome.io/components/status_led.html
#:########################################################################################:#
status_led:
pin:
number: GPIO2
inverted: true
#:########################################################################################:#
# BINARY SENSORS:
# https://esphome.io/components/binary_sensor/
#:########################################################################################:#
binary_sensor:
# GPIO16 for KS-811 first button UNLESS
# it is a single button KS-811 in which case it is GPIO00
- platform: gpio
pin:
number: GPIO16
mode: INPUT
inverted: true
use_interrupt: false # This pin doesn't support interrupts so use polling. Only suppresses a warning.
name: "Button 1: ${switch_1_name}"
on_multi_click:
# --- Single short click ---
# Online (and override ON, and NOT test mode): use HA's last scene to decide.
# Offline (override ON): toggle physical Relay_1
# Test mode (override ON): behave like offline even if HA is connected
- timing:
- ON for at most 0.5s
- OFF for at least 0.5s
then:
- if:
condition:
and:
- switch.is_on: ha_loss_override
- api.connected:
- not:
switch.is_on: ha_loss_test
then:
- if:
condition:
lambda: |-
// Defensive: treat empty/unknown as "not All Off" so we go to a safe "All Off".
auto s = id(ha_last_scene).state;
if (s.empty() || s == "unknown" || s == "unavailable") return true;
return s != "All Off";
then:
- homeassistant.service:
service: script.lounge_set_scene
data:
scene_name: "All Off"
- logger.log:
level: INFO
format: "BTN1: Single click (online) -> Last scene was not 'All Off' -> Set 'All Off'"
else:
- homeassistant.service:
service: script.lounge_set_scene
data:
scene_name: "Bright"
- logger.log:
level: INFO
format: "BTN1: Single click (online) -> Last scene was 'All Off' -> Set 'Bright'"
else:
- if:
condition:
and:
- switch.is_on: ha_loss_override
- or:
- not:
api.connected:
- switch.is_on: ha_loss_test
then:
- if:
condition:
lambda: |-
return id(Relay_1).state;
then:
- switch.turn_off: Relay_1
- logger.log:
level: INFO
format: "BTN1: Single click (offline/test) -> Relay_1 OFF"
else:
- switch.turn_on: Relay_1
- logger.log:
level: INFO
format: "BTN1: Single click (offline/test) -> Relay_1 ON"
else:
- logger.log:
level: INFO
format: "BTN1: Single click ignored (override OFF)"
# GPIO05 for second button (only for KS-811-2 Double or -3 Triple)
- platform: gpio
pin:
number: GPIO5
mode: INPUT
inverted: true
name: "Button 2: ${switch_2_name}"
on_multi_click:
# --- Single short click ---
# Online (override ON, NOT test mode): set scene
# Offline/test (override ON): toggle Relay_2
- timing:
- ON for at most 0.5s
- OFF for at least 0.5s
then:
- if:
condition:
and:
- switch.is_on: ha_loss_override
- api.connected:
- not:
switch.is_on: ha_loss_test
then:
- homeassistant.service:
service: script.lounge_set_scene
data:
scene_name: "Movie"
- logger.log:
level: INFO
format: "BTN2: Single click (online) -> Set 'Movie' Scene"
else:
- if:
condition:
and:
- switch.is_on: ha_loss_override
- or:
- not:
api.connected:
- switch.is_on: ha_loss_test
then:
- if:
condition:
lambda: |-
return id(Relay_2).state;
then:
- switch.turn_off: Relay_2
- logger.log:
level: INFO
format: "BTN2: Single click (offline/test) -> Relay_2 OFF"
else:
- switch.turn_on: Relay_2
- logger.log:
level: INFO
format: "BTN2: Single click (offline/test) -> Relay_2 ON"
else:
- logger.log:
level: INFO
format: "BTN2: Single click ignored (override OFF)"
# GPIO04 for third button (only for KS-811-3 Triple)
- platform: gpio
pin:
number: GPIO4
mode: INPUT
inverted: true
name: "Button 3: ${switch_3_name}"
on_multi_click:
# --- Single short click ---
# Online (override ON, NOT test mode): set scene
# Offline/test (override ON): toggle Relay_3
- timing:
- ON for at most 0.5s
- OFF for at least 0.5s
then:
- if:
condition:
and:
- switch.is_on: ha_loss_override
- api.connected:
- not:
switch.is_on: ha_loss_test
then:
- homeassistant.service:
service: script.lounge_set_scene
data:
scene_name: "Reading On/Off"
- logger.log:
level: INFO
format: "BTN3: Single click (online) -> Set 'Reading On/Off' Scene"
else:
- if:
condition:
and:
- switch.is_on: ha_loss_override
- or:
- not:
api.connected:
- switch.is_on: ha_loss_test
then:
- if:
condition:
lambda: |-
return id(Relay_3).state;
then:
- switch.turn_off: Relay_3
- logger.log:
level: INFO
format: "BTN3: Single click (offline/test) -> Relay_3 OFF"
else:
- switch.turn_on: Relay_3
- logger.log:
level: INFO
format: "BTN3: Single click (offline/test) -> Relay_3 ON"
else:
- logger.log:
level: INFO
format: "BTN3: Single click ignored (override OFF)"
#:########################################################################################:#
# TEXT SENSOR COMPONENT:
# https://esphome.io/components/text_sensor/
#:########################################################################################:#
text_sensor:
- platform: homeassistant
id: ha_last_scene
entity_id: input_text.lounge_last_scene
on_value:
- logger.log:
level: DEBUG
format: "HA last scene now: %s"
args: ["x.c_str()"]
#:########################################################################################:#
# SWITCH COMPONENT:
# https://esphome.io/components/switch/
#:########################################################################################:#
switch:
# GPIO13 for KS-811 first button UNLESS it is KS-811-1 then it is GPIO12
- platform: gpio
name: "Relay 1: ${switch_1_name}"
pin: GPIO13
id: Relay_1
restore_mode: RESTORE_DEFAULT_ON
# GPIO12 for second relay (only for KS-811-2 Double or -3 Triple)
- platform: gpio
name: "Relay 2: ${switch_2_name}"
pin: GPIO12
id: Relay_2
restore_mode: RESTORE_DEFAULT_ON
# GPIO14 for third relay (only for KS-811-3 Triple)
- platform: gpio
name: "Relay 3: ${switch_3_name}"
pin: GPIO14
id: Relay_3
# HA Loss manual override (default ON; persists across restarts)
- platform: template
name: "HA Loss manual override"
id: ha_loss_override
optimistic: true
restore_mode: ALWAYS_ON
# HA Loss Test (default OFF): when ON, force "offline behavior" even if HA is connected
- platform: template
name: "HA Loss Test"
id: ha_loss_test
optimistic: true
restore_mode: ALWAYS_OFF
on_turn_on:
- logger.log:
level: WARN
format: "HA Loss Test turned ON: forcing offline/test behavior for buttons"
on_turn_off:
- logger.log:
level: INFO
format: "HA Loss Test turned OFF: forcing Relay_1 and Relay_2 ON, then restoring last scene in 30s"
- switch.turn_on: Relay_1
- switch.turn_on: Relay_2
- delay: 30s
- if:
condition:
and:
- api.connected:
- lambda: |-
auto s = id(ha_last_scene).state;
return !(s.empty() || s == "unknown" || s == "unavailable");
then:
- homeassistant.service:
service: script.lounge_set_scene
data:
scene_name: !lambda |-
return id(ha_last_scene).state;
- logger.log:
level: INFO
format: "HA Loss Test OFF: restored last scene: %s"
args: ['id(ha_last_scene).state.c_str()']
else:
- logger.log:
level: WARN
format: "HA Loss Test OFF: cannot restore scene (API not connected or last scene unknown)"
+38 -1
View File
@@ -6,6 +6,11 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-mainpowermonitor.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-mainpowermonitor.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v1.4 2026-07-12 Added Home Assistant Energy Dashboard metadata.
# Marked cumulative PZEM, mains total, and 32A Wallcharger total energy
# sensors as energy / total_increasing.
# Marked mains and EV power sensors as power / measurement.
# Added single-process compilation limit to reduce Builder memory use.
# v1.3 2026-02-25 Updated yaml to zorruno layout V1.1 # v1.3 2026-02-25 Updated yaml to zorruno layout V1.1
# v1.2 2026-02-25 Updated yaml to zorruno layout V1.1. Added EV charging binary sensor # v1.2 2026-02-25 Updated yaml to zorruno layout V1.1. Added EV charging binary sensor
# and EV power rolling average (default 5 min). Clarified EV pulse notes. # and EV power rolling average (default 5 min). Clarified EV pulse notes.
@@ -21,6 +26,11 @@
# OPERATION NOTES: # OPERATION NOTES:
# - Power Monitor using 3x PZEM-004T V3 (Modbus) for 3-phase monitoring # - Power Monitor using 3x PZEM-004T V3 (Modbus) for 3-phase monitoring
# - Pulse input energy monitoring for 32A EV Charger (2 Wh per pulse) # - Pulse input energy monitoring for 32A EV Charger (2 Wh per pulse)
# - 32A Wallcharger Total Energy is cumulative pulse-derived energy in kWh
# - Cumulative active-energy sensors are exposed to Home Assistant with:
# device_class: energy
# state_class: total_increasing
# - This allows the cumulative totals to be selected in the HA Energy Dashboard
# - Adds a 5 minute rolling average for EV power to avoid spiky readings at low loads # - Adds a 5 minute rolling average for EV power to avoid spiky readings at low loads
# - Adds a binary sensor to indicate when the EV charger is operating (threshold configurable) # - Adds a binary sensor to indicate when the EV charger is operating (threshold configurable)
# #
@@ -60,7 +70,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Wemos.D1Mini PZEM-004T V3 3-Phase Power Monitor" project_name: "Wemos.D1Mini PZEM-004T V3 3-Phase Power Monitor"
project_version: "v1.3" project_version: "v1.4"
# Passwords # Passwords
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -202,6 +212,10 @@ esphome:
friendly_name: "${friendly_name}" friendly_name: "${friendly_name}"
comment: "${description_comment}" comment: "${description_comment}"
area: "${device_area}" area: "${device_area}"
# Restrict PlatformIO to one simultaneous compiler process to reduce host RAM use.
compile_process_limit: 1
project: project:
name: "${project_name}" name: "${project_name}"
version: "${project_version}" version: "${project_version}"
@@ -467,11 +481,16 @@ sensor:
power: power:
name: "${pzem_red_p_name}" name: "${pzem_red_p_name}"
id: pzem_red_power_w id: pzem_red_power_w
unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 0 accuracy_decimals: 0
energy: energy:
name: "${pzem_red_e_name}" name: "${pzem_red_e_name}"
id: pzem_red_energy_kwh id: pzem_red_energy_kwh
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3 accuracy_decimals: 3
filters: filters:
- multiply: 0.001 - multiply: 0.001
@@ -537,11 +556,16 @@ sensor:
power: power:
name: "${pzem_yellow_p_name}" name: "${pzem_yellow_p_name}"
id: pzem_yellow_power_w id: pzem_yellow_power_w
unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 0 accuracy_decimals: 0
energy: energy:
name: "${pzem_yellow_e_name}" name: "${pzem_yellow_e_name}"
id: pzem_yellow_energy_kwh id: pzem_yellow_energy_kwh
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3 accuracy_decimals: 3
filters: filters:
- multiply: 0.001 - multiply: 0.001
@@ -607,11 +631,16 @@ sensor:
power: power:
name: "${pzem_blue_p_name}" name: "${pzem_blue_p_name}"
id: pzem_blue_power_w id: pzem_blue_power_w
unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 0 accuracy_decimals: 0
energy: energy:
name: "${pzem_blue_e_name}" name: "${pzem_blue_e_name}"
id: pzem_blue_energy_kwh id: pzem_blue_energy_kwh
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3 accuracy_decimals: 3
filters: filters:
- multiply: 0.001 - multiply: 0.001
@@ -665,6 +694,8 @@ sensor:
name: "${mains_total_p_name}" name: "${mains_total_p_name}"
id: mains_total_active_power_w id: mains_total_active_power_w
unit_of_measurement: "W" unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 0 accuracy_decimals: 0
update_interval: ${pzem_update_interval} update_interval: ${pzem_update_interval}
lambda: |- lambda: |-
@@ -711,6 +742,8 @@ sensor:
name: "${mains_total_e_name}" name: "${mains_total_e_name}"
id: mains_total_active_energy_kwh id: mains_total_active_energy_kwh
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3 accuracy_decimals: 3
update_interval: ${pzem_update_interval} update_interval: ${pzem_update_interval}
lambda: |- lambda: |-
@@ -957,6 +990,8 @@ sensor:
name: "${ev_power_name}" name: "${ev_power_name}"
id: ev_power_avg_w id: ev_power_avg_w
unit_of_measurement: "W" unit_of_measurement: "W"
device_class: power
state_class: measurement
accuracy_decimals: 0 accuracy_decimals: 0
update_interval: "${ev_power_avg_sample_interval}" update_interval: "${ev_power_avg_sample_interval}"
lambda: |- lambda: |-
@@ -966,6 +1001,8 @@ sensor:
name: "${ev_total_energy_name}" name: "${ev_total_energy_name}"
id: ev_total_energy_kwh id: ev_total_energy_kwh
unit_of_measurement: "kWh" unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
accuracy_decimals: 3 accuracy_decimals: 3
update_interval: 10s update_interval: 10s
lambda: |- lambda: |-
+12 -12
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-masterbdrmsouth.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-masterbdrmsouth.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v1.4 2026-07-20 Flattened retained North and South MQTT status topics to the device roots.
# v1.3 2026-06-12 Reassigned logical controls after live wiring checks. # v1.3 2026-06-12 Reassigned logical controls after live wiring checks.
# Relay 1 on both switches = North Lights virtual/Tasmota control. # Relay 1 on both switches = North Lights virtual/Tasmota control.
# Relay 2 on both switches = South Lights control; WEST Relay 2 is physical load. # Relay 2 on both switches = South Lights control; WEST Relay 2 is physical load.
@@ -55,7 +56,7 @@
# NOTE: TOGGLE is intentionally not handled here to avoid double-toggle if both switches receive it. # NOTE: TOGGLE is intentionally not handled here to avoid double-toggle if both switches receive it.
# #
# - North Lights retained status/sync topic: # - North Lights retained status/sync topic:
# ${mqtt_local_status_topic1}/state # ${mqtt_local_status_topic1}
# Payload "On" / "Off" mirrors the actual North Lights state. # Payload "On" / "Off" mirrors the actual North Lights state.
# #
# - South Lights local command topic: # - South Lights local command topic:
@@ -65,7 +66,7 @@
# NOTE: TOGGLE is intentionally not handled here to avoid ambiguous multi-device toggles. # NOTE: TOGGLE is intentionally not handled here to avoid ambiguous multi-device toggles.
# #
# - South Lights retained status/sync topic: # - South Lights retained status/sync topic:
# ${mqtt_local_status_topic2}/state # ${mqtt_local_status_topic2}
# Payload "On" / "Off" mirrors the actual South Lights state. # Payload "On" / "Off" mirrors the actual South Lights state.
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
@@ -87,7 +88,6 @@
# - Accurate time is NOT needed (SNTP not needed). # - Accurate time is NOT needed (SNTP not needed).
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -101,7 +101,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Triple" project_name: "Zemismart Technologies.KS-811 Triple"
project_version: "v1.3" project_version: "v1.4"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -218,22 +218,22 @@ globals:
- id: north_update_from_sync - id: north_update_from_sync
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
- id: south_update_from_sync - id: south_update_from_sync
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
- id: north_lights_state - id: north_lights_state
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
- id: south_lights_state - id: south_lights_state
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
#:########################################################################################:# #:########################################################################################:#
# MQTT: Device-specific MQTT command + retained status/sync triggers # MQTT: Device-specific MQTT command + retained status/sync triggers
@@ -275,7 +275,7 @@ mqtt:
} }
# Retained North status is a secondary mirror/sync path. # Retained North status is a secondary mirror/sync path.
- topic: "${mqtt_local_status_topic1}/state" - topic: "${mqtt_local_status_topic1}"
then: then:
- lambda: |- - lambda: |-
if (x == "On" || x == "ON" || x == "on") { if (x == "On" || x == "ON" || x == "on") {
@@ -291,7 +291,7 @@ mqtt:
# retained status after the physical Relay 2 changes, and this switch mirrors # retained status after the physical Relay 2 changes, and this switch mirrors
# that status onto its local Relay 2. # that status onto its local Relay 2.
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
- topic: "${mqtt_local_status_topic2}/state" - topic: "${mqtt_local_status_topic2}"
then: then:
- lambda: |- - lambda: |-
if (x == "On" || x == "ON" || x == "on") { if (x == "On" || x == "ON" || x == "on") {
@@ -410,7 +410,7 @@ script:
id(north_update_from_sync) = true; id(north_update_from_sync) = true;
- switch.turn_on: relay_1 - switch.turn_on: relay_1
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic1}/state" topic: "${mqtt_local_status_topic1}"
payload: "On" payload: "On"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -424,7 +424,7 @@ script:
id(north_update_from_sync) = true; id(north_update_from_sync) = true;
- switch.turn_off: relay_1 - switch.turn_off: relay_1
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic1}/state" topic: "${mqtt_local_status_topic1}"
payload: "Off" payload: "Off"
retain: true retain: true
- lambda: |- - lambda: |-
+16 -16
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-masterbdrmwest.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-masterbdrmwest.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# v1.4 2026-07-20 Flattened retained North and South MQTT status topics to the device roots.
# v1.3 2026-06-12 Reassigned logical controls after live wiring checks. # v1.3 2026-06-12 Reassigned logical controls after live wiring checks.
# Relay 1 on both switches = North Lights virtual/Tasmota control. # Relay 1 on both switches = North Lights virtual/Tasmota control.
# Relay 2 on both switches = South Lights control; WEST Relay 2 is physical load. # Relay 2 on both switches = South Lights control; WEST Relay 2 is physical load.
@@ -55,7 +56,7 @@
# NOTE: TOGGLE is intentionally not handled here to avoid double-toggle if both switches receive it. # NOTE: TOGGLE is intentionally not handled here to avoid double-toggle if both switches receive it.
# #
# - North Lights retained status/sync topic: # - North Lights retained status/sync topic:
# ${mqtt_local_status_topic1}/state # ${mqtt_local_status_topic1}
# Payload "On" / "Off" mirrors the actual North Lights state. # Payload "On" / "Off" mirrors the actual North Lights state.
# #
# - South Lights local command topic: # - South Lights local command topic:
@@ -65,7 +66,7 @@
# NOTE: TOGGLE is intentionally not handled here to avoid ambiguous multi-device toggles. # NOTE: TOGGLE is intentionally not handled here to avoid ambiguous multi-device toggles.
# #
# - South Lights retained status/sync topic: # - South Lights retained status/sync topic:
# ${mqtt_local_status_topic2}/state # ${mqtt_local_status_topic2}
# Payload "On" / "Off" published when Relay 2 changes state. # Payload "On" / "Off" published when Relay 2 changes state.
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
@@ -88,7 +89,6 @@
# - Accurate time is NOT needed (SNTP not needed). # - Accurate time is NOT needed (SNTP not needed).
#:########################################################################################:# #:########################################################################################:#
#:########################################################################################:# #:########################################################################################:#
# SUBSTITUTIONS: Specific device variable substitutions # SUBSTITUTIONS: Specific device variable substitutions
# If NOT using a secrets file, just replace these with the passwords etc (in quotes) # If NOT using a secrets file, just replace these with the passwords etc (in quotes)
@@ -102,7 +102,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Zemismart Technologies.KS-811 Triple" project_name: "Zemismart Technologies.KS-811 Triple"
project_version: "v1.3" project_version: "v1.4"
# Passwords & Secrets # Passwords & Secrets
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -217,22 +217,22 @@ globals:
- id: north_update_from_sync - id: north_update_from_sync
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
- id: south_update_from_sync - id: south_update_from_sync
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
- id: north_lights_state - id: north_lights_state
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
- id: south_lights_state - id: south_lights_state
type: bool type: bool
restore_value: false restore_value: false
initial_value: 'false' initial_value: "false"
#:########################################################################################:# #:########################################################################################:#
# MQTT: Device-specific MQTT command + retained status/sync triggers # MQTT: Device-specific MQTT command + retained status/sync triggers
@@ -274,7 +274,7 @@ mqtt:
} }
# Retained North status is a secondary mirror/sync path. # Retained North status is a secondary mirror/sync path.
- topic: "${mqtt_local_status_topic1}/state" - topic: "${mqtt_local_status_topic1}"
then: then:
- lambda: |- - lambda: |-
if (x == "On" || x == "ON" || x == "on") { if (x == "On" || x == "ON" || x == "on") {
@@ -296,7 +296,7 @@ mqtt:
} }
# Retained South status keeps this physical relay and the SOUTH mirror in step. # Retained South status keeps this physical relay and the SOUTH mirror in step.
- topic: "${mqtt_local_status_topic2}/state" - topic: "${mqtt_local_status_topic2}"
then: then:
- lambda: |- - lambda: |-
if (x == "On" || x == "ON" || x == "on") { if (x == "On" || x == "ON" || x == "on") {
@@ -415,7 +415,7 @@ script:
id(north_update_from_sync) = true; id(north_update_from_sync) = true;
- switch.turn_on: relay_1 - switch.turn_on: relay_1
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic1}/state" topic: "${mqtt_local_status_topic1}"
payload: "On" payload: "On"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -429,7 +429,7 @@ script:
id(north_update_from_sync) = true; id(north_update_from_sync) = true;
- switch.turn_off: relay_1 - switch.turn_off: relay_1
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic1}/state" topic: "${mqtt_local_status_topic1}"
payload: "Off" payload: "Off"
retain: true retain: true
- lambda: |- - lambda: |-
@@ -554,10 +554,10 @@ switch:
} }
- if: - if:
condition: condition:
lambda: 'return !id(south_update_from_sync);' lambda: "return !id(south_update_from_sync);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic2}/state" topic: "${mqtt_local_status_topic2}"
payload: "On" payload: "On"
retain: true retain: true
on_turn_off: on_turn_off:
@@ -568,10 +568,10 @@ switch:
} }
- if: - if:
condition: condition:
lambda: 'return !id(south_update_from_sync);' lambda: "return !id(south_update_from_sync);"
then: then:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_local_status_topic2}/state" topic: "${mqtt_local_status_topic2}"
payload: "Off" payload: "Off"
retain: true retain: true
-477
View File
@@ -1,477 +0,0 @@
#############################################
#############################################
#
# ESP Home
# NFC Tag Reader for Home Assistant
#
#############################################
#############################################
# Project Link:
# https://github.com/adonno/tagreader
#############################################
# Remember to set the switches on the PN532
# for i2C, Switch1: On (up), Switch2: Off (down)
#############################################
#############################################
# Substitution Text
#############################################
substitutions:
name: esp-nfcaccess1-33ad74
friendly_name: TagReader
#############################################
# Inport the project from Github
#############################################
#dashboard_import:
# package_import_url: github://adonno/tagreader/tagreader.yaml
#############################################
# Use Wifi
#############################################
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Details for fallback hotspot (captive portal)
# in case wifi connection fails
ap:
ssid: "${name} Hotspot"
password: !secret fallback_ap_password
#############################################
# Improv
# Use the open Improv standard for configuring Wi-Fi on the
# device by using a serial connection to the device, eg. USB.
#############################################
improv_serial:
#############################################
# Fallback captive portal
# Enable the captive portal for inital WiFi setup
#############################################
captive_portal:
#############################################
# Enable Over the Air Update Capability
# Safe mode will detect boot loops
#############################################
ota:
safe_mode: true # Avoid boot loops
password: !secret esp-nfcaccess1-33ad74_ota_pass
#############################################
# ESPHome Logging Enable
#############################################
logger:
# baud_rate: 0 # Set baud rate to 0 if using the UART for soething else
# level: VERY_VERBOSE
# level: VERBOSE
#############################################
# Web Portal for display and monitoring
#############################################
#web_server:
# port: 80
# auth:
# username: !secret web_server_username
# password: !secret web_server_password
########################################
# Specific board for ESPHome device
########################################
esp8266:
board: d1_mini
#############################################
# ESPHome
#############################################
esphome:
name: ${name}
# Automatically add the mac address to the name
# so you can use a single firmware for all devices
name_add_mac_suffix: false
# This will allow for (future) project identification,
# configuration and updates.
project:
name: adonno.tag_reader
version: "1.4"
# If buzzer is enabled, notify on api connection success
on_boot:
priority: -10
then:
- wait_until:
api.connected:
- logger.log: API is connected!
- rtttl.play: "success:d=24,o=5,b=100:c,g,b"
- light.turn_on:
id: activity_led
brightness: 100%
red: 0%
green: 0%
blue: 100%
flash_length: 500ms
- switch.turn_on: buzzer_enabled
- switch.turn_on: led_enabled
#############################################
# Global variables
#############################################
globals:
- id: source
type: std::string
- id: url
type: std::string
- id: info
type: std::string
#############################################
#############################################
# MAIN SENSORS
#############################################
#############################################
#############################################
# Switches
# Define switches to control LED and buzzer from HA
#############################################
switch:
- platform: template
name: "${friendly_name} Buzzer Enabled"
id: buzzer_enabled
icon: mdi:volume-high
optimistic: true
restore_state: true
entity_category: config
- platform: template
name: "${friendly_name} LED enabled"
id: led_enabled
icon: mdi:alarm-light-outline
optimistic: true
restore_state: true
entity_category: config
#############################################
# Buttons
# Define buttons for writing tags via HA
#############################################
button:
- platform: template
name: Write Tag Random
id: write_tag_random
# Optional variables:
icon: "mdi:pencil-box"
on_press:
then:
- light.turn_on:
id: activity_led
brightness: 100%
red: 100%
green: 0%
blue: 100%
- lambda: |-
static const char alphanum[] = "0123456789abcdef";
std::string uri = "https://www.home-assistant.io/tag/";
for (int i = 0; i < 8; i++)
uri += alphanum[random_uint32() % (sizeof(alphanum) - 1)];
uri += "-";
for (int j = 0; j < 3; j++) {
for (int i = 0; i < 4; i++)
uri += alphanum[random_uint32() % (sizeof(alphanum) - 1)];
uri += "-";
}
for (int i = 0; i < 12; i++)
uri += alphanum[random_uint32() % (sizeof(alphanum) - 1)];
auto message = new nfc::NdefMessage();
message->add_uri_record(uri);
ESP_LOGD("tagreader", "Writing payload: %s", uri.c_str());
id(pn532_board).write_mode(message);
- rtttl.play: "write:d=24,o=5,b=100:b"
- wait_until:
not:
pn532.is_writing:
- light.turn_off:
id: activity_led
- rtttl.play: "write:d=24,o=5,b=100:b,b"
- platform: template
name: Clean Tag
id: clean_tag
icon: "mdi:nfc-variant-off"
on_press:
then:
- light.turn_on:
id: activity_led
brightness: 100%
red: 100%
green: 64.7%
blue: 0%
- lambda: 'id(pn532_board).clean_mode();'
- rtttl.play: "write:d=24,o=5,b=100:b"
- wait_until:
not:
pn532.is_writing:
- light.turn_off:
id: activity_led
- rtttl.play: "write:d=24,o=5,b=100:b,b"
- platform: template
name: Cancel writing
id: cancel_writing
icon: "mdi:pencil-off"
on_press:
then:
- lambda: 'id(pn532_board).read_mode();'
- light.turn_off:
id: activity_led
- rtttl.play: "write:d=24,o=5,b=100:b,b"
- platform: restart
name: "${friendly_name} Restart"
entity_category: config
#############################################
# HA API
# Enable Home Assistant API
#############################################
api:
encryption:
key: "K2H5vBNv8NLegc3RmwTQVFV7ULtba2DWDOlABmXmGBM="
services:
- service: rfidreader_tag_ok
then:
- rtttl.play: "beep:d=16,o=5,b=100:b"
- service: rfidreader_tag_ko
then:
- rtttl.play: "beep:d=8,o=5,b=100:b"
- service: play_rtttl
variables:
song_str: string
then:
- rtttl.play: !lambda 'return song_str;'
- service: write_tag_id
variables:
tag_id: string
then:
- light.turn_on:
id: activity_led
brightness: 100%
red: 100%
green: 0%
blue: 0%
- lambda: |-
auto message = new nfc::NdefMessage();
std::string uri = "https://www.home-assistant.io/tag/";
uri += tag_id;
message->add_uri_record(uri);
id(pn532_board).write_mode(message);
- rtttl.play: "write:d=24,o=5,b=100:b"
- wait_until:
not:
pn532.is_writing:
- light.turn_off:
id: activity_led
- rtttl.play: "write:d=24,o=5,b=100:b,b"
- service: write_music_tag
variables:
music_url: string
music_info: string
then:
- light.turn_on:
id: activity_led
brightness: 100%
red: 100%
green: 0%
blue: 0%
- lambda: |-
auto message = new nfc::NdefMessage();
std::string uri = "";
std::string text = "";
uri += music_url;
text += music_info;
if ( music_url != "" ) {
message->add_uri_record(uri);
}
if ( music_info != "" ) {
message->add_text_record(text);
}
id(pn532_board).write_mode(message);
- rtttl.play: "write:d=24,o=5,b=100:b"
- wait_until:
not:
pn532.is_writing:
- light.turn_off:
id: activity_led
- rtttl.play: "write:d=24,o=5,b=100:b,b"
#############################################
# i2C bus
#############################################
i2c:
scan: False
frequency: 400kHz
#############################################
# NFC Reader
#############################################
pn532_i2c:
id: pn532_board
on_tag:
then:
- if:
condition:
switch.is_on: led_enabled
then:
- light.turn_on:
id: activity_led
brightness: 100%
red: 0%
green: 100%
blue: 0%
flash_length: 500ms
- delay: 0.15s #to fix slow component
- lambda: |-
id(source)="";
id(url)="";
id(info)="";
if (tag.has_ndef_message()) {
auto message = tag.get_ndef_message();
auto records = message->get_records();
for (auto &record : records) {
std::string payload = record->get_payload();
std::string type = record->get_type();
size_t hass = payload.find("https://www.home-assistant.io/tag/");
size_t applemusic = payload.find("https://music.apple.com");
size_t spotify = payload.find("https://open.spotify.com");
size_t sonos = payload.find("sonos-2://");
if (type == "U" and hass != std::string::npos ) {
ESP_LOGD("tagreader", "Found Home Assistant tag NDEF");
id(source)="hass";
id(url)=payload;
id(info)=payload.substr(hass + 34);
}
else if (type == "U" and applemusic != std::string::npos ) {
ESP_LOGD("tagreader", "Found Apple Music tag NDEF");
id(source)="amusic";
id(url)=payload;
}
else if (type == "U" and spotify != std::string::npos ) {
ESP_LOGD("tagreader", "Found Spotify tag NDEF");
id(source)="spotify";
id(url)=payload;
}
else if (type == "U" and sonos != std::string::npos ) {
ESP_LOGD("tagreader", "Found Sonos app tag NDEF");
id(source)="sonos";
id(url)=payload;
}
else if (type == "T" ) {
ESP_LOGD("tagreader", "Found music info tag NDEF");
id(info)=payload;
}
else if ( id(source)=="" ) {
id(source)="uid";
}
}
}
else {
id(source)="uid";
}
- if:
condition:
lambda: 'return ( id(source)=="uid" );'
then:
- homeassistant.tag_scanned: !lambda |-
ESP_LOGD("tagreader", "No HA NDEF, using UID");
return x;
else:
- if:
condition:
lambda: 'return ( id(source)=="hass" );'
then:
- homeassistant.tag_scanned: !lambda 'return id(info);'
else:
- homeassistant.event:
event: esphome.music_tag
data:
reader: !lambda |-
return App.get_name().c_str();
source: !lambda |-
return id(source);
url: !lambda |-
return id(url);
info: !lambda |-
return id(info);
- if:
condition:
switch.is_on: buzzer_enabled
then:
- rtttl.play: "success:d=24,o=5,b=100:c,g,b"
on_tag_removed:
then:
- homeassistant.event:
event: esphome.tag_removed
#############################################
# OUTPUTS
#############################################
# Define the buzzer output
output:
- platform: esp8266_pwm
pin: D7
id: buzzer
#############################################
# BINARY SENSORS
#############################################
binary_sensor:
- platform: status
name: "${friendly_name} Status"
entity_category: diagnostic
#############################################
# TEXT SENSORS
#############################################
text_sensor:
- platform: version
hide_timestamp: true
name: "${friendly_name} ESPHome Version"
entity_category: diagnostic
- platform: wifi_info
ip_address:
name: "${friendly_name} IP Address"
icon: mdi:wifi
entity_category: diagnostic
ssid:
name: "${friendly_name} Connected SSID"
icon: mdi:wifi-strength-2
entity_category: diagnostic
#############################################
# Ring Tone Text Transfer Language for melody
#############################################
# Define buzzer as output for RTTTL
rtttl:
output: buzzer
#############################################
# LED
#############################################
# Configure LED
light:
- platform: neopixelbus
variant: WS2812
pin: D8
num_leds: 1
flash_transition_length: 500ms
type: GRB
id: activity_led
name: "${friendly_name} LED"
restore_mode: ALWAYS_OFF
+9 -8
View File
@@ -6,6 +6,7 @@
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-officeusbhubpower.yaml # https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-officeusbhubpower.yaml
#:########################################################################################:# #:########################################################################################:#
# VERSIONS: # VERSIONS:
# V1.2 2026-07-20 Simplified local MQTT command and status topics to the device root
# V1.1 2026-02-25 Updated yaml to zorruno layout V1.1 # V1.1 2026-02-25 Updated yaml to zorruno layout V1.1
# V1.0 2025-06-14 Initial Version # V1.0 2025-06-14 Initial Version
#:########################################################################################:# #:########################################################################################:#
@@ -22,10 +23,10 @@
#:########################################################################################:# #:########################################################################################:#
# MQTT COMMANDS: # MQTT COMMANDS:
# Command (set relay): # Command (set relay):
# - ${mqtt_command_topic}/relay1/set -> ON, OFF # - ${mqtt_command_topic} -> ON, OFF
# #
# Status (published from template sensor): # Status (published from template sensor):
# - ${mqtt_status_topic}/relay1/state -> ON, OFF # - ${mqtt_status_topic} -> ON, OFF
#:########################################################################################:# #:########################################################################################:#
# OFFLINE NOTES: # OFFLINE NOTES:
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE # a) Home Assistant OFFLINE, but Network and MQTT ONLINE
@@ -54,7 +55,7 @@ substitutions:
# Project Naming # Project Naming
project_name: "Sonoff Technologies.Sonoff Basic V1" project_name: "Sonoff Technologies.Sonoff Basic V1"
project_version: "v1.1" project_version: "v1.2"
# Passwords & Secrets (Unfortunately, you cannot use substitutions inside secret names) # Passwords & Secrets (Unfortunately, you cannot use substitutions inside secret names)
api_key: !secret esp-api_key api_key: !secret esp-api_key
@@ -201,12 +202,12 @@ binary_sensor:
return id(relay1).state; return id(relay1).state;
on_press: on_press:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic}/relay1/state" topic: "${mqtt_status_topic}"
payload: "ON" payload: "ON"
retain: false retain: false
on_release: on_release:
- mqtt.publish: - mqtt.publish:
topic: "${mqtt_status_topic}/relay1/state" topic: "${mqtt_status_topic}"
payload: "OFF" payload: "OFF"
retain: false retain: false
@@ -227,12 +228,12 @@ status_led:
#:########################################################################################:# #:########################################################################################:#
mqtt: mqtt:
on_message: on_message:
- topic: "${mqtt_command_topic}/relay1/set" - topic: "${mqtt_command_topic}"
payload: "ON" payload: "ON"
then: then:
- switch.turn_on: relay1 - switch.turn_on: relay1
- topic: "${mqtt_command_topic}/relay1/set" - topic: "${mqtt_command_topic}"
payload: "OFF" payload: "OFF"
then: then:
- switch.turn_off: relay1 - switch.turn_off: relay1
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -191,7 +191,7 @@ globals:
- id: restore_mode - id: restore_mode
type: int type: int
restore_value: yes restore_value: yes
initial_value: "2" initial_value: "0"
#:########################################################################################:# #:########################################################################################:#
# SELECT COMPONENT: # SELECT COMPONENT:
@@ -210,4 +210,4 @@ select:
on_value: on_value:
then: then:
- lambda: |- - lambda: |-
id(restore_mode) = i + 1; id(restore_mode) = i + 1;
+18 -24
View File
@@ -6,7 +6,7 @@
# #
# V1.1 2025-06-12 package added for energy entities # V1.1 2025-06-12 package added for energy entities
# V1.0 2025-06-10 YAML Tidyups # V1.0 2025-06-10 YAML Tidyups
# #
########################################################################################## ##########################################################################################
########################################################################################## ##########################################################################################
@@ -21,11 +21,11 @@ substitutions:
#friendly_name: "Main Dishwashers Power" #friendly_name: "Main Dishwashers Power"
friendly_name: "Spare1 Athomv1" friendly_name: "Spare1 Athomv1"
description_comment: "Main Dishwashers Power Monitor :: Athom Smart Plug Power Monitor V1" description_comment: "Main Dishwashers Power Monitor :: Athom Smart Plug Power Monitor V1"
device_area: "Kitchen" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant. device_area: "Kitchen" # Allows ESP device to be automatically linked to an 'Area' in Home Assistant.
# Project Naming # Project Naming
project_name: "Athom Technology.Smart Plug V1" # Project Details project_name: "Athom Technology.Smart Plug V1" # Project Details
project_version: "v1.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version project_version: "v1.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
# Passwords # Passwords
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
@@ -38,8 +38,8 @@ substitutions:
# Device Settings # Device Settings
relay_icon: "mdi:dishwasher" relay_icon: "mdi:dishwasher"
current_limit : "10" # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16. current_limit: "10" # Current Limit in Amps. AU Plug = 10. IL, BR, EU, UK, US Plug = 16.
########################################################################################## ##########################################################################################
# PACKAGES: Included Common Packages # PACKAGES: Included Common Packages
# https://esphome.io/components/packages.html # https://esphome.io/components/packages.html
@@ -84,40 +84,35 @@ esphome:
name: "${device_name}" name: "${device_name}"
friendly_name: "${friendly_name}" friendly_name: "${friendly_name}"
comment: "${description_comment}" # Appears on the esphome page in HA comment: "${description_comment}" # Appears on the esphome page in HA
area: "${device_area}" area: "${device_area}"
name_add_mac_suffix: False name_add_mac_suffix: False
min_version: 2024.6.0 min_version: 2024.6.0
project: project:
name: "${project_name}" name: "${project_name}"
version: "${project_version}" version: "${project_version}"
on_boot:
priority: 200
then:
- switch.turn_on: "relay"
########################################################################################## ##########################################################################################
# ESP Platform and Framework # ESP Platform and Framework
# https://esphome.io/components/esp32.html # https://esphome.io/components/esp32.html
########################################################################################## ##########################################################################################
esp8266: esp8266:
board: esp8285 board: esp8285
restore_from_flash: True # mainly for calculating cumulative energy, but not that important here restore_from_flash: True # mainly for calculating cumulative energy, but not that important here
preferences: preferences:
flash_write_interval: 5min flash_write_interval: 5min
mdns: mdns:
disabled: false disabled: false
#dashboard_import: #dashboard_import:
# package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml # package_import_url: github://athom-tech/esp32-configs/athom-smart-plug.yaml
########################################################################################## ##########################################################################################
# ESPHome LOGGING # ESPHome LOGGING
# https://esphome.io/components/logger.html # https://esphome.io/components/logger.html
########################################################################################## ##########################################################################################
logger: logger:
level: "${log_level}" #INFO Level suggested, or DEBUG for testing level: "${log_level}" #INFO Level suggested, or DEBUG for testing
#baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM) #baud_rate: 0 # set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
#esp8266_store_log_strings_in_flash: false #esp8266_store_log_strings_in_flash: false
#tx_buffer_size: 64 #tx_buffer_size: 64
@@ -149,9 +144,9 @@ binary_sensor:
- min_length: 20ms - min_length: 20ms
max_length: 500ms max_length: 500ms
then: then:
- switch.toggle: - switch.toggle:
id: relay id: relay
- platform: template - platform: template
name: "Relay Status" name: "Relay Status"
lambda: |- lambda: |-
@@ -166,7 +161,6 @@ switch:
name: "Power Output" name: "Power Output"
pin: GPIO14 pin: GPIO14
id: relay id: relay
restore_mode: RESTORE_DEFAULT_ON # Ensures the relay is restored (or off) at boot restore_mode: RESTORE_DEFAULT_OFF # Restore previous state, default off
#internal: true # Hides the switch from Home Assistant #internal: true # Hides the switch from Home Assistant
icon: "${relay_icon}" icon: "${relay_icon}"
-136
View File
@@ -1,136 +0,0 @@
substitutions:
name: m5stack-atom-echo-80be58
friendly_name: M5Stack Atom Echo 80be58
esphome:
name: "${name}"
friendly_name: "${friendly_name}"
name_add_mac_suffix: false
project:
name: m5stack.atom-echo-voice-assistant
version: "1.0"
min_version: 2023.5.0
api:
encryption:
key: XmXtlYVEuioSNapvz8G/9caKCI0T7aRW/CFy/f83D6g=
esp32:
board: m5stack-atom
framework:
type: esp-idf
logger:
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
dashboard_import:
package_import_url: github://esphome/firmware/voice-assistant/m5stack-atom-echo.yaml@main
improv_serial:
i2s_audio:
i2s_lrclk_pin: GPIO33
i2s_bclk_pin: GPIO19
microphone:
- platform: i2s_audio
id: echo_microphone
i2s_din_pin: GPIO23
adc_type: external
pdm: true
speaker:
- platform: i2s_audio
id: echo_speaker
i2s_dout_pin: GPIO22
dac_type: external
mode: mono
voice_assistant:
microphone: echo_microphone
speaker: echo_speaker
on_start:
- light.turn_on:
id: led
blue: 100%
red: 0%
green: 0%
effect: none
on_tts_start:
- light.turn_on:
id: led
blue: 0%
red: 0%
green: 100%
effect: none
on_tts_end:
- light.turn_on:
id: led
blue: 0%
red: 0%
green: 100%
effect: pulse
on_end:
- delay: 1s
- wait_until:
not:
speaker.is_playing:
- light.turn_off: led
on_error:
- light.turn_on:
id: led
blue: 0%
red: 100%
green: 0%
effect: none
- delay: 1s
- light.turn_off: led
binary_sensor:
- platform: gpio
pin:
number: GPIO39
inverted: true
name: Button
disabled_by_default: true
entity_category: diagnostic
id: echo_button
on_multi_click:
- timing:
- ON FOR AT LEAST 350ms
then:
- voice_assistant.start:
- timing:
- ON FOR AT LEAST 350ms
- OFF FOR AT LEAST 10ms
then:
- light.turn_on:
id: led
blue: 100%
red: 0%
green: 0%
effect: pulse
- voice_assistant.stop:
light:
- platform: esp32_rmt_led_strip
id: led
name: None
disabled_by_default: true
entity_category: config
pin: GPIO27
default_transition_length: 0s
chipset: SK6812
num_leds: 1
rgb_order: grb
rmt_channel: 0
effects:
- pulse:
transition_length: 250ms
update_interval: 250ms
@@ -1,17 +0,0 @@
substitutions:
name: m5stack-atom-echo-82d244
friendly_name: M5Stack Atom Echo 82d244
packages:
m5stack.atom-echo-voice-assistant: github://esphome/firmware/voice-assistant/m5stack-atom-echo.yaml@main
esphome:
name: ${name}
name_add_mac_suffix: false
friendly_name: ${friendly_name}
api:
encryption:
key: Oh2XL+cpsK1jjTO8lW31IZ6CQiQNdqCpUgCZpoe4eYI=
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
-8
View File
@@ -1,8 +0,0 @@
basement_lights:
name: Basement Lights
entities:
- switch.esp_officelights_relay_1_nighttime_lights
- switch.esp_officelights_relay_2_daytime_lights
- switch.esp_underhouselights_underhouse_entrance_lights
- switch.esp_underhouselights_underhouse_storage_lights
- switch.esp_laundrylights_relay_1_laundry_lights
-6
View File
@@ -1,6 +0,0 @@
family_away:
name: All Family Members Away
entities:
- !secret person_1
- !secret person_2
- !secret person_3
-1
View File
@@ -1 +0,0 @@
+44 -1
View File
@@ -4,17 +4,60 @@
# PACKAGE: 16A EV Charger Control # PACKAGE: 16A EV Charger Control
############################################################################### ###############################################################################
# #
# FILE:
# packages/16a_ev_charging_controls.yaml
#
# VERSION: # VERSION:
# V1.2 2026-07-20
# - Added structured mode, timing, and restart behavior documentation.
#
# V1.1 2026-05-15 # V1.1 2026-05-15
# - Reworked 1hr topup logic to use a hard deadline and watchdog. # - Reworked 1hr topup logic to use a hard deadline and watchdog.
# - Removed dependency on timer.finished event for relay shutoff. # - Removed dependency on timer.finished event for relay shutoff.
# - Offpeak until charged now continues past offpeak end until charging stops. # - Offpeak until charged now continues past offpeak end until charging stops.
# - 1hr topup now now rolls into Normal Offpeak if the hour ends during # - 1hr topup now rolls into Normal Offpeak if the hour ends during
# the offpeak window, without switching the relay off and back on. # the offpeak window, without switching the relay off and back on.
# #
# V1.0 2026-05-11 # V1.0 2026-05-11
# - Initial package for 16A EV charger offpeak and charge-until-full control. # - Initial package for 16A EV charger offpeak and charge-until-full control.
# #
# PURPOSE:
# Controls the Leaf 16A charger relay using scheduled offpeak operation and
# user-selectable immediate or charge-until-full modes.
#
# DEPENDENCIES:
# - switch.garage_db_control_16a_wallcharger_operation controls charger power.
# - binary_sensor.garage_db_control_16a_wallcharger_charging reports whether
# the vehicle is actively charging.
#
# CHARGE MODES:
# - Normal (Offpeak) turns the relay on and off at the configured offpeak
# start and end times. Selecting this mode takes no immediate action.
# - Offpeak until charged starts at the offpeak time, or immediately if
# selected during offpeak, and may continue beyond offpeak end until the
# vehicle stops charging.
# - 1hr topup now turns the relay on immediately and records a hard deadline
# one hour ahead.
# - Charge now until full turns the relay on immediately and keeps it on until
# the vehicle stops charging.
# - Stop charging now turns the relay off immediately and returns the selector
# to Normal (Offpeak).
#
# TIMING AND COMPLETION:
# - Charge-until-full modes allow five minutes for charging to start. This
# also handles a vehicle that is already full.
# - Charging is considered complete after the charging sensor remains off for
# five minutes.
# - The one-hour topup deadline is stored in input_datetime and checked every
# minute and when Home Assistant starts.
# - If a topup ends during offpeak, the relay remains on and the mode returns
# to Normal (Offpeak), avoiding an off/on relay cycle.
# - Equal offpeak start and end times are interpreted as an all-day window.
#
# OPERATION NOTES:
# - Normal offpeak start and end use exact-time triggers.
# - There is no startup catch-up if Home Assistant is offline at either time.
#
# PACKAGE SETTINGS: # PACKAGE SETTINGS:
# Car display name: # Car display name:
# - Leaf # - Leaf
@@ -2,8 +2,8 @@
# PACKAGE: Appliance Monitor Processor # PACKAGE: Appliance Monitor Processor
################################################################################ ################################################################################
# #
# Version: 1.2 # Version: 1.3
# Date: 2026-06-09 # Date: 2026-07-11
# #
# Purpose: # Purpose:
# Shared appliance power monitoring processor. # Shared appliance power monitoring processor.
@@ -20,6 +20,7 @@
# - One MQTT sensor per appliance. # - One MQTT sensor per appliance.
# - Timestamp-based energy and cost calculation. # - Timestamp-based energy and cost calculation.
# - Rolling average state detection. # - Rolling average state detection.
# - Completion announcements through script.view_road_announcement.
# #
# Default values: # Default values:
# debug_flow: false # debug_flow: false
@@ -40,6 +41,16 @@
# Finished # Finished
# #
# Notes: # Notes:
# Version 1.3:
# - Updated completion announcements to support all current View Road
# announcement router channels.
# - Added pushover_kim to the default appliance completion channels.
# - Added mqtt_view_rd_feed support by passing mqtt_feed to the announcement
# router.
# - Added led_matrix_2 support.
# - Added optional announcement_enabled and announcement_payload fields.
# - Kept existing field names for backwards compatibility.
#
# Version 1.2: # Version 1.2:
# - Added min_cycle_notify_seconds. # - Added min_cycle_notify_seconds.
# - Finished cycles shorter than this are still recorded, but do not send # - Finished cycles shorter than this are still recorded, but do not send
@@ -53,17 +64,39 @@
# #
################################################################################ ################################################################################
# FINISHED ANNOUNCEMENT # FINISHED ANNOUNCEMENT
# The finishing script calls the package view_road_announcement_router.yaml. ################################################################################
# If you want to use your own announcement system, it will occur at the end #
# at am_cycle_notify_allowed being true, so change to your own announcement # The finishing script calls:
# method there. #
# script.view_road_announcement
#
# Current View Road announcement router channels:
# pushover_zorruno
# pushover_kim
# sony_tv_lounge
# lounge_google_home_voice
# google_home_voice
# lounge_touchscreen_voice
# mqtt_view_rd_feed
# led_matrix_1
# led_matrix_2
#
# Notes:
# - google_home_voice is an alias for lounge_google_home_voice.
# - lounge_touchscreen_voice is currently a placeholder route in the router.
# - mqtt_view_rd_feed requires mqtt_feed to be populated.
# - led_matrix_1 and led_matrix_2 use short_announcement and indicator.
# #
# Announcements can use: # Announcements can use:
# am_announcement_channels # announcement_enabled
# am_announcement_title # announcement_channels
# am_short_announcement # title
# am_long_announcement # short_announcement
# am_matrix_indicator # long_announcement
# announcement_payload
# payload
# matrix_indicator
# mqtt_feed
# #
################################################################################ ################################################################################
@@ -127,6 +160,59 @@ script:
step: 1 step: 1
unit_of_measurement: s unit_of_measurement: s
announcement_enabled:
name: Announcement Enabled
description: Whether a valid finished cycle should send an announcement.
default: true
selector:
boolean:
announcement_channels:
name: Announcement Channels
description: Comma-separated View Road announcement router channels.
default: "pushover_zorruno, pushover_kim, sony_tv_lounge, lounge_google_home_voice, mqtt_view_rd_feed, led_matrix_1"
selector:
text:
title:
name: Announcement Title
description: Announcement title passed to the View Road announcement router.
selector:
text:
short_announcement:
name: Short Announcement
description: Short announcement text for TV, voice and LED matrix.
selector:
text:
long_announcement:
name: Long Announcement
description: Long announcement text for Pushover and detailed channels.
selector:
text:
multiline: true
announcement_payload:
name: Announcement Payload
description: Optional payload passed to the announcement router.
selector:
text:
matrix_indicator:
name: Matrix Indicator
description: LED matrix indicator data.
example: "wash,1,15,10,6,5,1,0"
selector:
text:
mqtt_feed:
name: MQTT Activity Feed
description: Activity feed topic suffix used by mqtt_view_rd_feed.
example: "washing_machine"
selector:
text:
sequence: sequence:
########################################################################## ##########################################################################
# CORE DEFAULTS AND PASSED VALUES # CORE DEFAULTS AND PASSED VALUES
@@ -156,10 +242,36 @@ script:
########################################################################## ##########################################################################
# ANNOUNCEMENT DEFAULTS AND PASSED VALUES # ANNOUNCEMENT DEFAULTS AND PASSED VALUES
########################################################################## ##########################################################################
#
# These values are passed to script.view_road_announcement when an
# appliance cycle successfully finishes.
#
# Current supported router channels:
# pushover_zorruno
# pushover_kim
# sony_tv_lounge
# lounge_google_home_voice
# google_home_voice
# lounge_touchscreen_voice
# mqtt_view_rd_feed
# led_matrix_1
# led_matrix_2
#
# Notes:
# - google_home_voice is an alias for lounge_google_home_voice.
# - lounge_touchscreen_voice is currently a placeholder route.
# - mqtt_view_rd_feed requires mqtt_feed to be populated.
#
##########################################################################
- variables: - variables:
am_announcement_enabled: "{{ announcement_enabled | default(true, true) | bool(true) }}"
am_default_announcement_channels: >-
pushover_zorruno, pushover_kim, sony_tv_lounge, lounge_google_home_voice, mqtt_view_rd_feed, led_matrix_1
am_announcement_channels: >- am_announcement_channels: >-
{{ announcement_channels | default('pushover_zorruno, sony_tv_lounge, lounge_google_home_voice, led_matrix_1', true) | string }} {{ announcement_channels | default(am_default_announcement_channels, true) | string }}
am_short_announcement: >- am_short_announcement: >-
{{ short_announcement | default((am_appliance_action | title) ~ ' complete', true) | string }} {{ short_announcement | default((am_appliance_action | title) ~ ' complete', true) | string }}
@@ -170,7 +282,20 @@ script:
am_announcement_title: >- am_announcement_title: >-
{{ title | default(am_appliance_name ~ ' Notification', true) | string }} {{ title | default(am_appliance_name ~ ' Notification', true) | string }}
am_matrix_indicator: "{{ matrix_indicator | default('', true) | string }}" am_announcement_payload: >-
{% if announcement_payload is defined %}
{{ announcement_payload }}
{% elif payload is defined %}
{{ payload }}
{% else %}
{{ '' }}
{% endif %}
am_matrix_indicator: >-
{{ matrix_indicator | default('', true) | string }}
am_mqtt_feed: >-
{{ mqtt_feed | default(am_appliance_id, true) | string }}
########################################################################## ##########################################################################
# PREVIOUS STATE FROM MQTT MONITOR SENSOR ATTRIBUTES # PREVIOUS STATE FROM MQTT MONITOR SENSOR ATTRIBUTES
@@ -622,6 +747,12 @@ script:
and (am_cycle_duration_seconds_new | int(0)) >= (am_min_cycle_notify_seconds | int(300)) and (am_cycle_duration_seconds_new | int(0)) >= (am_min_cycle_notify_seconds | int(300))
}} }}
am_cycle_announcement_allowed: >-
{{
am_cycle_notify_allowed | bool(false)
and am_announcement_enabled | bool(true)
}}
am_cycle_notification_suppressed: >- am_cycle_notification_suppressed: >-
{{ {{
am_cycle_finished | bool(false) am_cycle_finished | bool(false)
@@ -631,6 +762,8 @@ script:
am_cycle_notification_suppressed_reason: >- am_cycle_notification_suppressed_reason: >-
{% if am_cycle_notification_suppressed | bool(false) %} {% if am_cycle_notification_suppressed | bool(false) %}
Cycle was shorter than {{ am_min_cycle_notify_seconds | int(300) }} seconds Cycle was shorter than {{ am_min_cycle_notify_seconds | int(300) }} seconds
{% elif am_cycle_notify_allowed | bool(false) and not (am_announcement_enabled | bool(true)) %}
Announcement disabled for this appliance
{% endif %} {% endif %}
########################################################################## ##########################################################################
@@ -673,6 +806,10 @@ script:
min_cycle_notify_seconds=am_min_cycle_notify_seconds | int(300), min_cycle_notify_seconds=am_min_cycle_notify_seconds | int(300),
cycle_notify_allowed=am_cycle_notify_allowed | bool(false), cycle_notify_allowed=am_cycle_notify_allowed | bool(false),
cycle_announcement_allowed=am_cycle_announcement_allowed | bool(false),
announcement_enabled=am_announcement_enabled | bool(true),
announcement_channels=am_announcement_channels,
mqtt_feed=am_mqtt_feed,
cycle_notification_suppressed=am_cycle_notification_suppressed | bool(false), cycle_notification_suppressed=am_cycle_notification_suppressed | bool(false),
cycle_notification_suppressed_reason=am_cycle_notification_suppressed_reason | trim, cycle_notification_suppressed_reason=am_cycle_notification_suppressed_reason | trim,
@@ -740,18 +877,45 @@ script:
threshold. No announcement was sent. threshold. No announcement was sent.
########################################################################## ##########################################################################
# FINISHED ANNOUNCEMENT # ANNOUNCEMENT DISABLED LOG
##########################################################################
# This script is in additional package view_road_announcement_router.yaml.
# If you want to use your own announcement system, it will occur here
# at am_cycle_notify_allowed being true.
#
# Change to your own announcement method here if needed.
########################################################################## ##########################################################################
- if: - if:
- condition: template - condition: template
value_template: "{{ am_cycle_notify_allowed | bool(false) }}" value_template: >-
{{
am_cycle_notify_allowed | bool(false)
and not (am_announcement_enabled | bool(true))
}}
then:
- action: logbook.log
data:
name: "Appliance Monitor"
message: >-
{{ am_appliance_name }} finished after
{{ am_cycle_duration_formatted | trim }}, but appliance
completion announcements are disabled for this appliance.
##########################################################################
# FINISHED ANNOUNCEMENT
##########################################################################
# This calls script.view_road_announcement from the announcement router
# package when a valid appliance cycle finishes.
#
# All supported router channels can be used through am_announcement_channels.
#
# mqtt_view_rd_feed requires:
# mqtt_feed
#
# led_matrix_1 and led_matrix_2 require:
# short_announcement
# indicator
#
##########################################################################
- if:
- condition: template
value_template: "{{ am_cycle_announcement_allowed | bool(false) }}"
then: then:
- action: script.view_road_announcement - action: script.view_road_announcement
data: data:
@@ -759,5 +923,6 @@ script:
title: "{{ am_announcement_title }}" title: "{{ am_announcement_title }}"
short_announcement: "{{ am_short_announcement }}" short_announcement: "{{ am_short_announcement }}"
long_announcement: "{{ am_long_announcement }}" long_announcement: "{{ am_long_announcement }}"
payload: "" payload: "{{ am_announcement_payload }}"
indicator: "{{ am_matrix_indicator }}" indicator: "{{ am_matrix_indicator }}"
mqtt_feed: "{{ am_mqtt_feed }}"
@@ -1,49 +1,85 @@
################################################################################ #:########################################################################################:#
# PACKAGE: Appliances Status Monitoring # Appliances Status Monitoring Package #
################################################################################ #:########################################################################################:#
# #
# Version: 1.4 # FILE:
# Date: 2026-06-09 # appliances_status_monitoring.yaml
# #
# Purpose: # VERSION:
# V1.5 2026-07-11
#
# PURPOSE:
# Appliance status monitoring package for multiple appliances. # Appliance status monitoring package for multiple appliances.
# #
# This package defines a central appliance registry and one automation that: # This package defines a central appliance registry and one automation that:
# #
# 1. Publishes MQTT discovery config for each appliance monitor sensor. # 1. Publishes MQTT Discovery configuration for each appliance monitor
# sensor.
# 2. Samples appliance power sensors once per minute. # 2. Samples appliance power sensors once per minute.
# 3. Calls script.appliance_monitor_process_sample. # 3. Calls script.appliance_monitor_process_sample.
# 4. Passes per-appliance completion announcement settings to the processor.
# #
# Processor package required: # REQUIRED PACKAGES:
# appliance_monitor_processor.yaml # appliance_monitor_processor.yaml V1.3 or newer
# # view_road_announcement_router.yaml V1.8 or newer
# Announcement router required:
# view_road_announcement_router.yaml
# #
# MQTT: # MQTT:
# MQTT is used only as retained state storage for appliance monitor sensors. # MQTT is used as retained state storage for appliance monitor sensors.
# Sensor entities are created using MQTT discovery. # Sensor entities are created using MQTT Discovery.
# #
# Adding another appliance: # ADDING AN APPLIANCE:
# Add one entry to the am_appliances list. # Add one entry to the am_appliances registry below.
# #
# Version 1.4: # ANNOUNCEMENT ROUTER CHANNELS:
# Added min_cycle_notify_seconds support. # The following channel IDs are accepted by the current announcement router:
# Default is 300 seconds, so cycles shorter than 5 minutes are recorded but
# do not send finished notifications.
# Per-appliance overrides can be set in the am_appliances list.
# #
# Version 1.3: # pushover_zorruno
# Removed the is_number gate before calling the processor. The processor is # pushover_kim
# now always called once per appliance per sample, and power is safely converted # sony_tv_lounge
# to 0 if the source sensor is unavailable or non-numeric. # lounge_google_home_voice
# google_home_voice
# lounge_touchscreen_voice
# mqtt_view_rd_feed
# led_matrix_1
# led_matrix_2
# #
################################################################################ # Notes:
# - google_home_voice is an alias for lounge_google_home_voice.
# - lounge_touchscreen_voice is currently a placeholder route.
# - mqtt_view_rd_feed requires mqtt_feed.
# - led_matrix_1 and led_matrix_2 use short_announcement and
# matrix_indicator.
#
# VERSION HISTORY:
# V1.5 2026-07-11
# - Added full View Road announcement router parameter support.
# - Added per-appliance announcement_enabled.
# - Added per-appliance announcement_payload.
# - Added per-appliance mqtt_feed for mqtt_view_rd_feed.
# - Added safe defaults for short announcement and matrix indicator.
# - Documented all current announcement router channel names.
# - Existing per-appliance channel selections retained unchanged.
#
# V1.4 2026-06-09
# - Added min_cycle_notify_seconds support.
# - Default is 300 seconds, so cycles shorter than 5 minutes are recorded
# but do not send finished notifications.
# - Per-appliance overrides can be set in the am_appliances list.
#
# V1.3:
# - Removed the is_number gate before calling the processor.
# - The processor is always called once per appliance per sample.
# - Power is safely converted to 0 if unavailable or non-numeric.
#
#:########################################################################################:#
automation: automation:
- id: appliances_status_monitoring_process_samples - id: appliances_status_monitoring_process_samples
alias: Appliances Status Monitoring - Process Samples alias: "Appliances Status Monitoring - Process Samples"
description: Publish MQTT discovery, sample appliance power sensors, and process appliance status/cycle data. description: >-
Publishes MQTT Discovery, samples appliance power sensors, processes
appliance status and cycle data, and passes completion announcement
settings to the shared processor.
mode: queued mode: queued
max: 20 max: 20
@@ -57,17 +93,17 @@ automation:
id: periodic_sample id: periodic_sample
variables: variables:
########################################################################## #:####################################################################################:#
# MQTT LOCAL CONTROLS # MQTT LOCAL CONTROLS #
########################################################################## #:####################################################################################:#
mqtt_device_name: "appliance_monitor" mqtt_device_name: "appliance_monitor"
mqtt_local_status_topic: !secret mqtt_status_main_topic mqtt_local_status_topic: !secret mqtt_status_main_topic
mqtt_discovery_prefix: "homeassistant" mqtt_discovery_prefix: "homeassistant"
########################################################################## #:####################################################################################:#
# DEFAULTS # PROCESSOR DEFAULTS #
########################################################################## #:####################################################################################:#
am_default_debug_flow: false am_default_debug_flow: false
am_default_resolution: 2 am_default_resolution: 2
@@ -75,91 +111,184 @@ automation:
am_default_operating_power_min: 12 am_default_operating_power_min: 12
am_default_min_cycle_notify_seconds: 300 am_default_min_cycle_notify_seconds: 300
#am_default_announcement_channels: "pushover_zorruno, sony_tv_lounge, lounge_google_home_voice, led_matrix_1" #:####################################################################################:#
# ANNOUNCEMENT DEFAULTS #
#:####################################################################################:#
#
# These defaults apply when an appliance does not define its own values.
#
# A conservative default channel is retained to prevent additional
# notifications suddenly being enabled for every appliance.
#
# To make all implemented channels the default, this could instead be:
#
# am_default_announcement_channels: >-
# pushover_zorruno,pushover_kim,sony_tv_lounge,
# lounge_google_home_voice,mqtt_view_rd_feed,
# led_matrix_1,led_matrix_2
#
# Available channels:
# pushover_zorruno
# pushover_kim
# sony_tv_lounge
# lounge_google_home_voice
# google_home_voice
# lounge_touchscreen_voice
# mqtt_view_rd_feed
# led_matrix_1
# led_matrix_2
am_default_announcement_enabled: true
am_default_announcement_channels: "pushover_zorruno" am_default_announcement_channels: "pushover_zorruno"
am_default_announcement_payload: ""
# Optional appliance-specific overrides available: #:####################################################################################:#
# resolution: 4 # OPTIONAL PER-APPLIANCE OVERRIDES #
# The number of samples for the average, with one sample per minute. #:####################################################################################:#
# #
# standby_power: 3 # debug_flow: true
# Below this value, the appliance will show "Off". # Writes state-processing details to the Home Assistant logbook.
# Between this value and operating_power_min, it will show "Standby".
# #
# operating_power_min: 20 # resolution: 4
# The minimum average power needed for the appliance to show "Operating". # Number of one-minute samples used for the rolling average.
# #
# min_cycle_notify_seconds: 600 # standby_power: 3
# Finished cycles shorter than this are recorded, but no notification # Below this value, the appliance shows Off.
# is sent. Default is 300 seconds. # Between standby_power and operating_power_min, it shows Standby.
# #
# announcement_channels: "pushover_zorruno" # operating_power_min: 20
# Announcement channels for this appliance. # Minimum rolling-average power required for Operating.
# #
# long_announcement: "The device has finished. Please empty it when convenient." # min_cycle_notify_seconds: 600
# Override the long announcement. # Minimum cycle duration before a completion announcement is allowed.
# #
# min_cycle_notify_seconds: 600 # announcement_enabled: false
# Minimum time for an appliance to be operating before a completion announcement generated. # Disables cycle-completion announcements for this appliance.
#
# announcement_channels: >-
# pushover_zorruno,pushover_kim,sony_tv_lounge,
# lounge_google_home_voice,mqtt_view_rd_feed,
# led_matrix_1,led_matrix_2
# Comma-separated View Road announcement router channels.
#
# title: "Washing Machine Notification"
# Optional announcement title.
#
# short_announcement: "Washing complete"
# Used by TV, voice and LED matrix channels.
#
# long_announcement: "The washing machine has finished."
# Used by Pushover and other detailed channels.
# If omitted, the processor creates a detailed cycle summary.
#
# announcement_payload: ""
# Optional payload passed to the announcement router.
#
# matrix_indicator: "wash,1,15,10,6,5,1,0"
# LED matrix indicator data.
#
# mqtt_feed: "washing_machine"
# Activity-feed suffix used by mqtt_view_rd_feed.
# If omitted, appliance_id is used.
#
#:####################################################################################:#
########################################################################## #:####################################################################################:#
# APPLIANCE REGISTRY # APPLIANCE REGISTRY #
########################################################################## #:####################################################################################:#
am_appliances: am_appliances:
#:##################################################################################:#
# Washing Machine #
#:##################################################################################:#
- appliance_id: washing_machine - appliance_id: washing_machine
appliance_name: "Washing Machine" appliance_name: "Washing Machine"
appliance_action: "washing" appliance_action: "washing"
power_entity: "sensor.esp_laundrywashpow_load_power" power_entity: "sensor.esp_laundrywashpow_load_power"
icon: "mdi:washing-machine" icon: "mdi:washing-machine"
short_announcement: "/s/sWashing complete"
# long_announcement: # Created by the processor
matrix_indicator: "wash,1,15,10,6,5,1,0"
# Optional washing-machine-specific overrides
resolution: 5 resolution: 5
standby_power: 3 standby_power: 3
operating_power_min: 10 operating_power_min: 10
announcement_channels: "pushover_zorruno"
announcement_enabled: true
announcement_channels: "pushover_zorruno,pushover_kim,sony_tv_lounge,led_matrix_1"
short_announcement: "/s/sWashing complete"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "wash,1,15,10,6,5,1,0"
mqtt_feed: "Washer_complete"
#:##################################################################################:#
# Dryer #
#:##################################################################################:#
- appliance_id: dryer - appliance_id: dryer
appliance_name: "Dryer" appliance_name: "Dryer"
appliance_action: "drying" appliance_action: "drying"
power_entity: "sensor.esp_laundrydrypow_load_power" power_entity: "sensor.esp_laundrydrypow_load_power"
icon: "mdi:tumble-dryer" icon: "mdi:tumble-dryer"
short_announcement: "/s/sDryer complete"
# long_announcement: # Created by the processor
matrix_indicator: "dry,1,15,10,6,5,1,0"
# Optional dryer-specific overrides
resolution: 4 resolution: 4
standby_power: 3 standby_power: 3
operating_power_min: 10 operating_power_min: 10
announcement_channels: "pushover_zorruno"
announcement_enabled: true
announcement_channels: "pushover_zorruno,pushover_kim,sony_tv_lounge,led_matrix_1"
short_announcement: "/s/sDryer complete"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "dry,1,15,10,6,5,1,0"
mqtt_feed: "Dryer_complete"
#:##################################################################################:#
# Main Dishwasher #
#:##################################################################################:#
- appliance_id: main_dishwasher - appliance_id: main_dishwasher
appliance_name: "Main Dishwasher" appliance_name: "Main Dishwasher"
appliance_action: "dishwashing" appliance_action: "dishwashing"
power_entity: "sensor.esp_maindishwasherpower_power" power_entity: "sensor.esp_maindishwasherpower_power"
icon: "mdi:dishwasher" icon: "mdi:dishwasher"
short_announcement: "/sDishwasher complete"
# long_announcement: # Created by the processor
matrix_indicator: "dish,1,15,10,6,5,1,0"
resolution: 5 resolution: 5
standby_power: 3 standby_power: 3
operating_power_min: 30 operating_power_min: 30
announcement_channels: "pushover_zorruno"
announcement_enabled: true
announcement_channels: "pushover_zorruno,pushover_kim,sony_tv_lounge,led_matrix_1"
short_announcement: "/sDishwasher complete"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "dish,1,15,10,6,5,1,0"
mqtt_feed: "Dishwasher_complete"
#:##################################################################################:#
# Downstairs Dishwasher #
#:##################################################################################:#
- appliance_id: downstairs_dishwasher - appliance_id: downstairs_dishwasher
appliance_name: "Downstairs Dishwasher" appliance_name: "Downstairs Dishwasher"
appliance_action: "dishwashing" appliance_action: "dishwashing"
power_entity: "sensor.esp_downstdishwasher_power" power_entity: "sensor.esp_downstdishwasher_power"
icon: "mdi:dishwasher" icon: "mdi:dishwasher"
short_announcement: "/sDownstairs Dishwasher complete"
# long_announcement: # Created by the processor
matrix_indicator: "dish2,1,15,10,6,5,1,0"
resolution: 5 resolution: 5
standby_power: 3 standby_power: 3
operating_power_min: 30 operating_power_min: 30
announcement_enabled: true
announcement_channels: "pushover_zorruno" announcement_channels: "pushover_zorruno"
short_announcement: "/sDownstairs Dishwasher complete"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "dish2,1,15,10,6,5,1,0"
mqtt_feed: "Downstairs_dishwasher_complete"
#:##################################################################################:#
# EV Charger 32A #
#:##################################################################################:#
- appliance_id: evcharger_32a - appliance_id: evcharger_32a
appliance_name: "EV Charger (32A)" appliance_name: "EV Charger (32A)"
@@ -167,88 +296,134 @@ automation:
power_entity: "sensor.main_house_3_phase_power_monitor_32a_wallcharger_power" power_entity: "sensor.main_house_3_phase_power_monitor_32a_wallcharger_power"
monitor_sensor: "sensor.appliance_monitor_ev_charger_32a_monitor" monitor_sensor: "sensor.appliance_monitor_ev_charger_32a_monitor"
icon: "mdi:ev-plug-type2" icon: "mdi:ev-plug-type2"
short_announcement: "/s/s32A EV Charging complete"
# long_announcement: # Created by the processor
matrix_indicator: "ev32a,1,15,10,6,5,1,0"
resolution: 6 resolution: 6
standby_power: 3 standby_power: 3
operating_power_min: 100 operating_power_min: 100
announcement_channels: "pushover_zorruno"
announcement_enabled: true
announcement_channels: "pushover_zorruno,pushover_kim"
short_announcement: "/s/s32A EV Charging complete"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "ev32a,1,15,10,6,5,1,0"
mqtt_feed: "Atto_Charger_complete"
#:##################################################################################:#
# EV Charger 16A #
#:##################################################################################:#
- appliance_id: evcharger_16a - appliance_id: evcharger_16a
appliance_name: "EV Charger (16A)" appliance_name: "EV Charger (16A)"
appliance_action: "charging" appliance_action: "charging"
power_entity: "sensor.main_house_3_phase_power_monitor_32a_wallcharger_power" power_entity: "sensor.garage_db_control_16a_wallcharger_power"
monitor_sensor: "sensor.garage_db_control_16a_wallcharger_power" monitor_sensor: "sensor.appliance_monitor_ev_charger_16a_monitor"
icon: "mdi:ev-plug-type1" icon: "mdi:ev-plug-type1"
short_announcement: "/s/s16A EV Charging complete"
# long_announcement: # Created by the processor
matrix_indicator: "ev16a,1,15,10,6,5,1,0"
resolution: 6 resolution: 6
standby_power: 3 standby_power: 3
operating_power_min: 100 operating_power_min: 100
announcement_enabled: true
announcement_channels: "pushover_zorruno" announcement_channels: "pushover_zorruno"
short_announcement: "/s/s16A EV Charging complete"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "ev16a,1,15,10,6,5,1,0"
mqtt_feed: "Leaf_Charger_complete"
#:##################################################################################:#
# Ryobi Charger Left #
#:##################################################################################:#
- appliance_id: ryobicharger_left - appliance_id: ryobicharger_left
appliance_name: "Ryobi Charger Left" appliance_name: "Ryobi Charger Left"
appliance_action: "charging" appliance_action: "charging"
power_entity: "sensor.ryobi_charger_left_power_monitor_x17pp_power" power_entity: "sensor.ryobi_charger_left_power_monitor_x17pp_power"
#monitor_sensor: "sensor.ryobi_charger_left_power_monitor_x17pp_power"
icon: "mdi:battery-charging-medium" icon: "mdi:battery-charging-medium"
short_announcement: "/s/sRyobi Charging Left"
# long_announcement: # Created by the processor
matrix_indicator: "ryo1,1,15,10,6,5,1,0"
resolution: 4 resolution: 4
standby_power: 1 standby_power: 1
operating_power_min: 20 operating_power_min: 20
announcement_enabled: true
announcement_channels: "pushover_zorruno" announcement_channels: "pushover_zorruno"
short_announcement: "/s/sRyobi Charging Left"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "ryo1,1,15,10,6,5,1,0"
mqtt_feed: "Ryobi_Charger_1_complete"
#:##################################################################################:#
# Ryobi Charger Right #
#:##################################################################################:#
- appliance_id: ryobicharger_right - appliance_id: ryobicharger_right
appliance_name: "Ryobi Charger Right" appliance_name: "Ryobi Charger Right"
appliance_action: "charging" appliance_action: "charging"
power_entity: "sensor.ryobi_charger_right_power_monitor_x16pp_power" power_entity: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
#monitor_sensor: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
icon: "mdi:battery-charging-medium" icon: "mdi:battery-charging-medium"
short_announcement: "/s/sRyobi Charging Right"
# long_announcement: # Created by the processor
matrix_indicator: "ryo2,1,15,10,6,5,1,0"
resolution: 4 resolution: 4
standby_power: 1 standby_power: 1
operating_power_min: 20 operating_power_min: 20
announcement_enabled: true
announcement_channels: "pushover_zorruno" announcement_channels: "pushover_zorruno"
short_announcement: "/s/sRyobi Charging Right"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "ryo2,1,15,10,6,5,1,0"
mqtt_feed: "Ryobi_Charger_2_complete"
#:##################################################################################:#
# Pool Pump #
#:##################################################################################:#
- appliance_id: pool_pump - appliance_id: pool_pump
appliance_name: "Pool Pump" appliance_name: "Pool Pump"
appliance_action: "pumping" appliance_action: "pumping"
power_entity: "sensor.esp_poolpumppower_power" power_entity: "sensor.esp_poolpumppower_power"
#monitor_sensor: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
icon: "mdi:pool" icon: "mdi:pool"
short_announcement: "Pool Pump Off"
# long_announcement: # Created by the processor
matrix_indicator: "pump,1,15,10,6,5,1,0"
resolution: 4 resolution: 4
standby_power: 5 standby_power: 5
operating_power_min: 20 operating_power_min: 20
announcement_enabled: true
announcement_channels: "pushover_zorruno" announcement_channels: "pushover_zorruno"
short_announcement: "Pool Pump Off"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "pump,1,15,10,6,5,1,0"
mqtt_feed: "pool_pump"
#:##################################################################################:#
# 3D Printer #
#:##################################################################################:#
- appliance_id: 3d_printer - appliance_id: 3d_printer
appliance_name: "3D Printer" appliance_name: "3D Printer"
appliance_action: "printing" appliance_action: "printing"
power_entity: "sensor.esp_3dprinterpow_load_power" power_entity: "sensor.esp_3dprinterpow_load_power"
#monitor_sensor: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
icon: "mdi:printer-3d" icon: "mdi:printer-3d"
short_announcement: "3D printer finished"
# long_announcement: # Created by the processor
matrix_indicator: "pump,1,15,10,6,5,1,0"
resolution: 4 resolution: 4
standby_power: 2 standby_power: 2
operating_power_min: 20 operating_power_min: 20
announcement_enabled: true
announcement_channels: "pushover_zorruno" announcement_channels: "pushover_zorruno"
short_announcement: "3D printer finished"
# long_announcement: # Generated by the processor
announcement_payload: ""
matrix_indicator: "pump,1,15,10,6,5,1,0"
mqtt_feed: "3d_printer"
action: action:
########################################################################## #:####################################################################################:#
# PUBLISH / REFRESH MQTT DISCOVERY CONFIG # PUBLISH / REFRESH MQTT DISCOVERY CONFIG #
########################################################################## #:####################################################################################:#
- repeat: - repeat:
for_each: "{{ am_appliances }}" for_each: "{{ am_appliances }}"
@@ -285,19 +460,18 @@ automation:
identifiers=[mqtt_device_name], identifiers=[mqtt_device_name],
name='Appliance Monitor', name='Appliance Monitor',
manufacturer='View Road Home Assistant', manufacturer='View Road Home Assistant',
model='Shared appliance power monitor' model='Shared appliance power monitor',
sw_version='1.5'
) )
) | to_json ) | to_json
}} }}
########################################################################## #:####################################################################################:#
# PROCESS SAMPLES # PROCESS SAMPLES #
########################################################################## #:####################################################################################:#
# #
# Always process every appliance. The processor receives power_w as a safe # Always process every appliance. The processor receives power_w as a safe
# float value, so unavailable/non-numeric sensors become 0 W. # float value, so unavailable or non-numeric sensors become 0 W.
#
##########################################################################
- repeat: - repeat:
for_each: "{{ am_appliances }}" for_each: "{{ am_appliances }}"
@@ -309,36 +483,97 @@ automation:
am_monitor_sensor: >- am_monitor_sensor: >-
{{ {{
repeat.item.monitor_sensor repeat.item.monitor_sensor
| default('sensor.' ~ mqtt_device_name ~ '_' ~ repeat.item.appliance_id ~ '_monitor', true) | default(
'sensor.'
~ mqtt_device_name
~ '_'
~ repeat.item.appliance_id
~ '_monitor',
true
)
}} }}
am_title: >- am_title: >-
{{ repeat.item.title | default(repeat.item.appliance_name ~ ' Notification', true) }} {{
repeat.item.title
| default(
repeat.item.appliance_name ~ ' Notification',
true
)
}}
am_announcement_enabled: >-
{{
repeat.item.announcement_enabled
| default(am_default_announcement_enabled)
}}
am_announcement_channels: >- am_announcement_channels: >-
{{ repeat.item.announcement_channels | default(am_default_announcement_channels, true) }} {{
repeat.item.announcement_channels
| default(am_default_announcement_channels, true)
}}
am_debug_flow: >- am_announcement_payload: >-
{{ repeat.item.debug_flow | default(am_default_debug_flow, true) }} {{
repeat.item.announcement_payload
| default(am_default_announcement_payload, true)
}}
am_resolution: >- am_mqtt_feed: >-
{{ repeat.item.resolution | default(am_default_resolution, true) }} {{
repeat.item.mqtt_feed
| default(repeat.item.appliance_id, true)
}}
am_standby_power: >- am_short_announcement: >-
{{ repeat.item.standby_power | default(am_default_standby_power, true) }} {{
repeat.item.short_announcement
| default(
(repeat.item.appliance_action | title) ~ ' complete',
true
)
}}
am_operating_power_min: >- am_matrix_indicator: >-
{{ repeat.item.operating_power_min | default(am_default_operating_power_min, true) }} {{ repeat.item.matrix_indicator | default('', true) }}
am_min_cycle_notify_seconds: >-
{{ repeat.item.min_cycle_notify_seconds | default(am_default_min_cycle_notify_seconds, true) }}
am_power_w: >-
{{ states(repeat.item.power_entity) | float(0) }}
am_long_announcement: >- am_long_announcement: >-
{{ repeat.item.long_announcement | default('', true) }} {{ repeat.item.long_announcement | default('', true) }}
am_debug_flow: >-
{{
repeat.item.debug_flow
| default(am_default_debug_flow)
}}
am_resolution: >-
{{
repeat.item.resolution
| default(am_default_resolution, true)
}}
am_standby_power: >-
{{
repeat.item.standby_power
| default(am_default_standby_power, true)
}}
am_operating_power_min: >-
{{
repeat.item.operating_power_min
| default(am_default_operating_power_min, true)
}}
am_min_cycle_notify_seconds: >-
{{
repeat.item.min_cycle_notify_seconds
| default(am_default_min_cycle_notify_seconds, true)
}}
am_power_w: >-
{{ states(repeat.item.power_entity) | float(0) }}
- action: script.appliance_monitor_process_sample - action: script.appliance_monitor_process_sample
data: data:
appliance_id: "{{ repeat.item.appliance_id }}" appliance_id: "{{ repeat.item.appliance_id }}"
@@ -355,8 +590,11 @@ automation:
operating_power_min: "{{ am_operating_power_min }}" operating_power_min: "{{ am_operating_power_min }}"
min_cycle_notify_seconds: "{{ am_min_cycle_notify_seconds }}" min_cycle_notify_seconds: "{{ am_min_cycle_notify_seconds }}"
announcement_enabled: "{{ am_announcement_enabled }}"
announcement_channels: "{{ am_announcement_channels }}" announcement_channels: "{{ am_announcement_channels }}"
short_announcement: "{{ repeat.item.short_announcement }}"
long_announcement: "{{ am_long_announcement }}"
title: "{{ am_title }}" title: "{{ am_title }}"
matrix_indicator: "{{ repeat.item.matrix_indicator }}" short_announcement: "{{ am_short_announcement }}"
long_announcement: "{{ am_long_announcement }}"
announcement_payload: "{{ am_announcement_payload }}"
matrix_indicator: "{{ am_matrix_indicator }}"
mqtt_feed: "{{ am_mqtt_feed }}"
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
################################################################################ #:########################################################################################:#
# Jobs Tracker Processor Package # Jobs Tracker Processor Package #
################################################################################ #:########################################################################################:#
# #
# TITLE: # TITLE:
# Jobs Tracker Processor # Jobs Tracker Processor
@@ -9,20 +9,122 @@
# jobs_tracker_processor.yaml # jobs_tracker_processor.yaml
# #
# VERSION: # VERSION:
# V1.3.1 2026-06-20 # V1.6 2026-07-10
# #
# CHANGELOG: # PURPOSE:
# V1.3.1: # Generic processing engine for the Jobs Tracker system.
#
# OPERATION:
# This package is the generic processing engine for the Jobs Tracker system.
# It is not normally edited when adding, removing or changing household jobs.
# Jobs are defined in the separate job-list package, which calls this processor
# through script.jobs_tracker_update_job. The processor calculates whether each
# job is Not Due, Missed, Due, Overdue or Complete, then publishes the
# result as retained MQTT JSON to that job's configured MQTT topic. The retained
# MQTT payload stores the last completion timestamp, so after a Home Assistant
# restart the processor can recalculate the correct state from the last physical
# button/action press.
#
# Normal operation is automatic. The job-list package refreshes all jobs on a
# regular timer and after Home Assistant starts, and it calls this processor with
# mark_complete: true when a physical button, event, sensor, MQTT button or other
# completion action confirms a job has been completed. A job is considered
# Complete when its stored last_completed_ts is within the current reset period.
#
# Daily jobs reset at 00:00 but only become Due at their configured due_time.
# Weekly jobs reset at the week rollover, currently Monday 00:00. Monthly jobs
# reset at the month rollover, currently the 1st day of the month at 00:00.
#
# If the previous reset period was missed, jobs without a completion window are
# marked Missed after the reset and before the current due time. Jobs with a
# completion window are marked Missed after the current completion window ends.
# This lets jobs such as morning dog feeding show Missed after the morning
# window, but then reset to Not Due/Due again the next morning.
#
# Once the current due time arrives, a job changes to Due, then Overdue after
# the configured threshold unless the job has reached the end of its completion
# window, in which case it changes to Missed. Completing a job early is allowed
# once the current reset period has started.
#
# SETTINGS:
# The job-list package passes these settings into this processor for each job:
#
# job_id:
# Machine-safe ID used for matching and MQTT topic naming.
# Example: dog_feeding_morning
#
# job_name:
# Human-readable job name used in the retained MQTT JSON.
# Example: Dog Feeding Morning
#
# repeat_type:
# Schedule type. Valid values are daily, weekly or monthly.
#
# due_time:
# Time the job is due, in HH:MM 24-hour format.
# Example: "06:00"
#
# due_weekday:
# Weekly jobs only. Monday=0, Tuesday=1, Wednesday=2, Thursday=3,
# Friday=4, Saturday=5, Sunday=6.
#
# due_day:
# Monthly jobs only. Use 1-28 for best reliability across all months.
#
# mqtt_topic:
# Retained MQTT state topic for this job.
# Example: viewroad-status/jobs_tracker/dog_feeding_morning
#
# state_entity:
# MQTT sensor entity that reads this job's retained JSON payload.
# Example: sensor.jobs_tracker_dog_feeding_morning
#
# mark_complete:
# false when refreshing/calculating the job state.
# true when a physical/manual completion action has just occurred.
#
# completion_window_start / completion_window_end:
# Optional HH:MM window used for jobs that share a physical button or have
# a clear daily completion window. If the current window ends before the
# job is completed, the job is marked Missed instead of remaining Overdue.
# Example: "00:00" to "11:59" for morning dog feeding.
#
# Current built-in overdue thresholds are:
# - Daily jobs: 2 hours after due_time
# - Weekly jobs: 2 days after due_time
# - Monthly jobs: 2 weeks after due_time
#
# VERSION HISTORY:
# V1.6 2026-07-10
# - Renamed displayed state from Missed to Missed.
# - Added optional completion_window_start and completion_window_end fields.
# - Windowed jobs now become Missed when the completion window has ended.
# - Windowed jobs reset normally into Not Due / Due in the next period.
#
# V1.5 2026-07-07
# - Documentation update for the refactored registry-driven job-list package.
# - No functional processing change from V1.4.
#
# V1.4 2026-07-06
# - Added Missed state.
# - If the previous reset period was missed, the job now shows Previous
# Missed until the current period's due time.
# - Missed applies to daily, weekly and monthly jobs.
# - Due now starts at the configured due time for daily, weekly and monthly
# jobs.
# - Early completion remains supported after the current reset period starts.
#
# V1.3.1 2026-06-20
# - Corrected script field definitions so Home Assistant reliably creates # - Corrected script field definitions so Home Assistant reliably creates
# script.jobs_tracker_update_job. # script.jobs_tracker_update_job.
# #
# V1.3: # V1.3 2026-06-20
# - Daily jobs reset at 00:00 but do not become Due until their configured # - Daily jobs reset at 00:00 but do not become Due until their configured
# due time. # due time.
# - Added "Not Due" state. # - Added Not Due state.
# - Completion is recalculated from retained last_completed_ts after restart. # - Completion is recalculated from retained last_completed_ts after restart.
# #
################################################################################ #:########################################################################################:#
script: script:
jobs_tracker_update_job: jobs_tracker_update_job:
@@ -59,6 +161,12 @@ script:
mark_complete: mark_complete:
description: "Set true when a human has completed the job." description: "Set true when a human has completed the job."
example: true example: true
completion_window_start:
description: "Optional HH:MM completion window start."
example: "00:00"
completion_window_end:
description: "Optional HH:MM completion window end."
example: "11:59"
sequence: sequence:
- variables: - variables:
@@ -73,6 +181,17 @@ script:
now_ts: "{{ as_timestamp(now()) }}" now_ts: "{{ as_timestamp(now()) }}"
today_midnight_ts: "{{ as_timestamp(today_at('00:00')) }}" today_midnight_ts: "{{ as_timestamp(today_at('00:00')) }}"
topic_safe: "{{ mqtt_topic | default('viewroad-status/jobs_tracker/' ~ job_id) }}" topic_safe: "{{ mqtt_topic | default('viewroad-status/jobs_tracker/' ~ job_id) }}"
completion_window_start_safe: "{{ completion_window_start | default('') }}"
completion_window_end_safe: "{{ completion_window_end | default('') }}"
has_completion_window: >-
{{
completion_window_start_safe not in ['', none, 'unknown', 'unavailable']
and completion_window_end_safe not in ['', none, 'unknown', 'unavailable']
}}
#:####################################################################################:#
# Current and next reset periods #
#:####################################################################################:#
- variables: - variables:
current_period_start_ts: >- current_period_start_ts: >-
@@ -98,6 +217,28 @@ script:
{{ (today_midnight_ts | float(0)) + 86400 }} {{ (today_midnight_ts | float(0)) + 86400 }}
{% endif %} {% endif %}
#:####################################################################################:#
# Previous reset period #
#:####################################################################################:#
- variables:
previous_period_start_ts: >-
{% if repeat_type_safe == 'daily' %}
{{ (current_period_start_ts | float(0)) - 86400 }}
{% elif repeat_type_safe == 'weekly' %}
{{ (current_period_start_ts | float(0)) - 604800 }}
{% elif repeat_type_safe == 'monthly' %}
{% set first_day_this_month = now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) %}
{% set last_day_previous_month = first_day_this_month - timedelta(days=1) %}
{{ as_timestamp(last_day_previous_month.replace(day=1, hour=0, minute=0, second=0, microsecond=0)) }}
{% else %}
{{ (current_period_start_ts | float(0)) - 86400 }}
{% endif %}
#:####################################################################################:#
# Due times #
#:####################################################################################:#
- variables: - variables:
current_due_ts: >- current_due_ts: >-
{% if repeat_type_safe == 'daily' %} {% if repeat_type_safe == 'daily' %}
@@ -110,6 +251,19 @@ script:
{{ (current_period_start_ts | float(0)) + (due_seconds | int) }} {{ (current_period_start_ts | float(0)) + (due_seconds | int) }}
{% endif %} {% endif %}
previous_due_ts: >-
{% if repeat_type_safe == 'daily' %}
{{ (previous_period_start_ts | float(0)) + (due_seconds | int) }}
{% elif repeat_type_safe == 'weekly' %}
{{ (previous_period_start_ts | float(0)) + ((due_weekday_safe | int) * 86400) + (due_seconds | int) }}
{% elif repeat_type_safe == 'monthly' %}
{% set first_day_this_month = now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) %}
{% set last_day_previous_month = first_day_this_month - timedelta(days=1) %}
{{ as_timestamp(last_day_previous_month.replace(day=(due_day_safe | int), hour=(due_hour | int), minute=(due_minute | int), second=0, microsecond=0)) }}
{% else %}
{{ (previous_period_start_ts | float(0)) + (due_seconds | int) }}
{% endif %}
next_due_ts: >- next_due_ts: >-
{% if repeat_type_safe == 'daily' %} {% if repeat_type_safe == 'daily' %}
{{ (next_period_start_ts | float(0)) + (due_seconds | int) }} {{ (next_period_start_ts | float(0)) + (due_seconds | int) }}
@@ -133,6 +287,60 @@ script:
120 120
{% endif %} {% endif %}
#:####################################################################################:#
# Completion window timing #
#:####################################################################################:#
#
# For windowed jobs, Missed begins when the completion window has ended.
# The window is anchored to the current due date. End times are treated as
# inclusive through the final minute, so "11:59" ends at 11:59:59.
- variables:
completion_window_start_seconds: >-
{% if has_completion_window | bool %}
{% set t = completion_window_start_safe.split(':') %}
{{ ((t[0] | int(0)) * 3600) + ((t[1] | int(0)) * 60) }}
{% else %}
0
{% endif %}
completion_window_end_seconds: >-
{% if has_completion_window | bool %}
{% set t = completion_window_end_safe.split(':') %}
{{ ((t[0] | int(23)) * 3600) + ((t[1] | int(59)) * 60) + 59 }}
{% else %}
86399
{% endif %}
current_due_day_start_ts: >-
{% set d = (current_due_ts | float(0)) | timestamp_custom('%Y-%m-%d', true) %}
{{ as_timestamp(strptime(d ~ ' 00:00:00', '%Y-%m-%d %H:%M:%S')) }}
current_completion_window_start_ts: >-
{% if has_completion_window | bool %}
{{ (current_due_day_start_ts | float(0)) + (completion_window_start_seconds | int(0)) }}
{% else %}
0
{% endif %}
current_completion_window_end_ts: >-
{% if has_completion_window | bool %}
{% set start_seconds = completion_window_start_seconds | int(0) %}
{% set end_seconds = completion_window_end_seconds | int(86399) %}
{% set base_end = (current_due_day_start_ts | float(0)) + end_seconds %}
{% if end_seconds < start_seconds %}
{{ base_end + 86400 }}
{% else %}
{{ base_end }}
{% endif %}
{% else %}
0
{% endif %}
#:####################################################################################:#
# Restore retained completion timestamp #
#:####################################################################################:#
- variables: - variables:
stored_last_completed_ts: >- stored_last_completed_ts: >-
{% set v = state_attr(state_entity, 'last_completed_ts') %} {% set v = state_attr(state_entity, 'last_completed_ts') %}
@@ -141,20 +349,52 @@ script:
last_completed_ts: >- last_completed_ts: >-
{{ now_ts if mark_complete_bool else stored_last_completed_ts }} {{ now_ts if mark_complete_bool else stored_last_completed_ts }}
#:####################################################################################:#
# Completion and timing state #
#:####################################################################################:#
- variables: - variables:
complete_bool: >- complete_bool: >-
{{ (last_completed_ts | float(0)) >= (current_period_start_ts | float(0)) }} {{ (last_completed_ts | float(0)) >= (current_period_start_ts | float(0)) }}
previous_completed_bool: >-
{{
(last_completed_ts | float(0)) >= (previous_period_start_ts | float(0))
and (last_completed_ts | float(0)) < (current_period_start_ts | float(0))
}}
previous_missed_bool: >-
{{ not (previous_completed_bool | bool) }}
due_started_bool: >- due_started_bool: >-
{% if repeat_type_safe == 'daily' %} {{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
{{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
{% else %}
true
{% endif %}
overdue_started_bool: >- overdue_started_bool: >-
{{ (now_ts | float(0)) >= ((current_due_ts | float(0)) + ((overdue_after_minutes | int(0)) * 60)) }} {{ (now_ts | float(0)) >= ((current_due_ts | float(0)) + ((overdue_after_minutes | int(0)) * 60)) }}
previous_missed_active_bool: >-
{{
previous_missed_bool | bool
and not (complete_bool | bool)
and not (due_started_bool | bool)
}}
completion_window_missed_bool: >-
{{
has_completion_window | bool
and not (complete_bool | bool)
and (now_ts | float(0)) > (current_completion_window_end_ts | float(0))
}}
missed_bool: >-
{{
completion_window_missed_bool | bool
or (
previous_missed_active_bool | bool
and not (has_completion_window | bool)
)
}}
minutes_since_required: >- minutes_since_required: >-
{% if (now_ts | float(0)) <= (current_due_ts | float(0)) %} {% if (now_ts | float(0)) <= (current_due_ts | float(0)) %}
0 0
@@ -169,10 +409,16 @@ script:
{{ (((now_ts | float(0)) - (current_due_ts | float(0))) / 3600) | round(0) | int }} {{ (((now_ts | float(0)) - (current_due_ts | float(0))) / 3600) | round(0) | int }}
{% endif %} {% endif %}
#:####################################################################################:#
# Display state #
#:####################################################################################:#
- variables: - variables:
job_status: >- job_status: >-
{% if complete_bool | bool %} {% if complete_bool | bool %}
Complete Complete
{% elif missed_bool | bool %}
Missed
{% elif not (due_started_bool | bool) %} {% elif not (due_started_bool | bool) %}
Not Due Not Due
{% elif overdue_started_bool | bool %} {% elif overdue_started_bool | bool %}
@@ -265,52 +511,64 @@ script:
{{ ts | timestamp_custom('%Y-%m-%d @ %H:%M', true) }} {{ ts | timestamp_custom('%Y-%m-%d @ %H:%M', true) }}
{% endif %} {% endif %}
#:####################################################################################:#
# Publish retained MQTT JSON #
#:####################################################################################:#
- variables:
completed_for_period_ts_safe: >-
{% if complete_bool | bool %}
{{ current_period_start_ts | float(0) }}
{% else %}
0
{% endif %}
- action: mqtt.publish - action: mqtt.publish
data: data:
topic: "{{ topic_safe }}" topic: "{{ topic_safe }}"
retain: true retain: true
qos: 1 qos: 1
payload: >- payload: >-
{{ {
{ "job_id": {{ job_id | tojson }},
"job_id": job_id, "job_name": {{ job_name | tojson }},
"job_name": job_name, "repeat": {{ repeat_type_safe | tojson }},
"repeat": repeat_type_safe, "due_time": {{ due_time_safe | tojson }},
"due_time": due_time_safe, "due_weekday": {{ due_weekday_safe | int }},
"due_weekday": due_weekday_safe | int, "due_day": {{ due_day_safe | int }},
"due_day": due_day_safe | int, "state": {{ job_status | trim | tojson }},
"complete": {{ complete_bool | bool | tojson }},
"state": job_status | trim, "last_completed_ts": {{ last_completed_ts | float(0) }},
"complete": complete_bool | bool, "last_completed_time": {{ last_completed_time | tojson }},
"last_completed_human": {{ last_completed_human | tojson }},
"last_completed_ts": last_completed_ts | float(0), "previous_period_start_ts": {{ previous_period_start_ts | float(0) }},
"last_completed_time": last_completed_time, "previous_due_ts": {{ previous_due_ts | float(0) }},
"last_completed_human": last_completed_human, "previous_completed": {{ previous_completed_bool | bool | tojson }},
"previous_missed": {{ previous_missed_bool | bool | tojson }},
"completed_for_period_ts": current_period_start_ts | float(0) if complete_bool | bool else 0, "previous_missed_active": {{ previous_missed_active_bool | bool | tojson }},
"completed_for_due_ts": current_period_start_ts | float(0) if complete_bool | bool else 0, "missed": {{ missed_bool | bool | tojson }},
"completion_window_start": {{ completion_window_start_safe | tojson }},
"current_period_start_ts": current_period_start_ts | float(0), "completion_window_end": {{ completion_window_end_safe | tojson }},
"next_period_start_ts": next_period_start_ts | float(0), "has_completion_window": {{ has_completion_window | bool | tojson }},
"completion_window_missed": {{ completion_window_missed_bool | bool | tojson }},
"current_due_ts": current_due_ts | float(0), "current_completion_window_start_ts": {{ current_completion_window_start_ts | float(0) }},
"next_due_ts": next_due_ts | float(0), "current_completion_window_end_ts": {{ current_completion_window_end_ts | float(0) }},
"display_due_ts": display_due_ts | float(0), "completed_for_period_ts": {{ completed_for_period_ts_safe | float(0) }},
"next_due_human": next_due_human, "completed_for_due_ts": {{ completed_for_period_ts_safe | float(0) }},
"current_period_start_ts": {{ current_period_start_ts | float(0) }},
"due_started": due_started_bool | bool, "next_period_start_ts": {{ next_period_start_ts | float(0) }},
"overdue_started": overdue_started_bool | bool, "current_due_ts": {{ current_due_ts | float(0) }},
"next_due_ts": {{ next_due_ts | float(0) }},
"minutes_since_required": minutes_since_required | int(0), "display_due_ts": {{ display_due_ts | float(0) }},
"hours_since_required": hours_since_required | int(0), "next_due_human": {{ next_due_human | tojson }},
"due_started": {{ due_started_bool | bool | tojson }},
"overdue_after_minutes": overdue_after_minutes | int(0), "overdue_started": {{ overdue_started_bool | bool | tojson }},
"overdue_minutes": overdue_minutes | int(0), "minutes_since_required": {{ minutes_since_required | int(0) }},
"overdue_hours": overdue_hours | int(0), "hours_since_required": {{ hours_since_required | int(0) }},
"overdue_after_minutes": {{ overdue_after_minutes | int(0) }},
"hours_since_completed": hours_since_completed, "overdue_minutes": {{ overdue_minutes | int(0) }},
"overdue_hours": {{ overdue_hours | int(0) }},
"last_updated_local": (now_ts | float(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true), "hours_since_completed": {{ hours_since_completed | tojson }},
"mqtt_topic": topic_safe "last_updated_local": {{ ((now_ts | float(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true)) | tojson }},
} | tojson "mqtt_topic": {{ topic_safe | tojson }}
}} }
@@ -1,3 +1,27 @@
#:########################################################################################:#
# Package: Central Stairway Two-Way Synchronization
# File: Lighting_2Way_Sync/central_stairway_2way_sync.yaml
# Version: v1.1.0
# Date: 2026-07-22
#
# Purpose:
# Keeps the upper and lower controls for the main stair lights and stair footer
# lights synchronized through Home Assistant.
#
# Behaviour:
# - Synchronizes each two-member relay pair in both directions.
# - Synchronizes state after Home Assistant starts, following a five-second delay.
# - Leaves direct device-level MQTT synchronization to the ESPHome devices.
#
# Dependency:
# - Blueprint: zorruno/two_three_way_sync.yaml
#
# Version History:
# - v1.1.0 (2026-07-22): Removed the superseded lounge-middle relay and retained
# two active relays in each synchronization pair.
# - v1.0.0 (2026-06-22): Initial central stairway synchronization package.
#:########################################################################################:#
automation: automation:
# Stair Lights Top # Stair Lights Top
- id: stair_lights_top_2way_sync - id: stair_lights_top_2way_sync
@@ -9,7 +33,6 @@ automation:
members: members:
- switch.esp_centralstairs_top_relay_1_main_stair_lights - switch.esp_centralstairs_top_relay_1_main_stair_lights
- switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower - switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
- switch.esp_loungemiddleswitch_relay_3_downlights_north
sync_on_start: true sync_on_start: true
startup_delay: 5 startup_delay: 5
@@ -1,3 +1,24 @@
#:########################################################################################:#
# Package: Garage Lighting Two-Way Synchronization
# File: Lighting_2Way_Sync/garage_lights_2way_sync.yaml
# Version: v1.0.0
# Date: 2026-06-22
#
# Purpose:
# Keeps the paired controls for the garage-entry corridor/outside lighting and
# the internal garage lighting synchronized through Home Assistant.
#
# Behaviour:
# - Synchronizes each two-member relay pair in both directions.
# - Synchronizes state after Home Assistant starts, following a five-second delay.
#
# Dependency:
# - Blueprint: zorruno/two_three_way_sync.yaml
#
# Version History:
# - v1.0.0 (2026-06-22): Initial garage lighting synchronization package.
#:########################################################################################:#
automation: automation:
# Two lights and two switches for these corridor lights # Two lights and two switches for these corridor lights
- id: garage_entry_outside_2way_sync - id: garage_entry_outside_2way_sync
@@ -6,12 +6,16 @@
# Pool Light Operation # Pool Light Operation
# #
# FILE: # FILE:
# packages/pool_light_boost_switch.yaml # packages/Pool_Controls/pool_light_operation.yaml
# #
# VERSION: # VERSION:
# V1.1 2026-06-29 # V1.2 2026-07-20
# #
# VERSION HISTORY: # VERSION HISTORY:
# V1.2 2026-07-20
# - Corrected the documented package filename.
# - Documented the relationship with the native ESPHome controls.
#
# V1.1 2026-06-29 # V1.1 2026-06-29
# - Tidied layout, comments, naming and automation IDs. # - Tidied layout, comments, naming and automation IDs.
# - Added structured package header and section comments. # - Added structured package header and section comments.
@@ -38,8 +42,15 @@
# Operation topic: # Operation topic:
# viewroad-commands/poollight-timer/operation # viewroad-commands/poollight-timer/operation
# #
# DEPENDENCIES:
# - switch.esp_poollightpower2_switch provides the real output state.
# - The ESPHome pool light device subscribes to the MQTT operation topic.
# - Native ESPHome mode, boost, and pool-light-mode entities coexist with
# this one-tap control.
#
# NOTES: # NOTES:
# The input_boolean.timer_light_midnight_pending helper is internal to this package. # The input_boolean.timer_light_midnight_pending helper is internal to this package.
# Dashboard cards currently use input_boolean.poollights_mode_switch.
# #
#:########################################################################################:# #:########################################################################################:#
@@ -172,7 +183,3 @@ automation:
- service: input_boolean.turn_off - service: input_boolean.turn_off
target: target:
entity_id: input_boolean.poollights_mode_switch entity_id: input_boolean.poollights_mode_switch
- service: input_boolean.turn_on
target:
entity_id: input_boolean.timer_light_midnight_pending
@@ -1,10 +1,68 @@
# packages/poolpump_operation.yaml #:########################################################################################:#
# Pool Pump Operation Package #
#:########################################################################################:#
#
# TITLE:
# Pool Pump Operation
#
# FILE:
# packages/Pool_Controls/pool_pump_operation.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured package documentation and section comments.
# - Documented the relationship with the native ESPHome controls.
#
# V1.0
# - Initial package.
# - Provides a Home Assistant UI switch for pool pump BOOST / OFF behaviour.
# - Sends TIMER mode again at midnight when required.
#
# PURPOSE:
# Provides a one-tap Home Assistant UI switch for controlling the pool pump.
#
# Behaviour:
# - Turning the UI switch ON sends BOOST if the real pump output is OFF.
# - Turning the UI switch OFF sends OFF if the real pump output is ON.
# - After OFF is requested, TIMER mode is restored at midnight.
# - The UI switch follows the real ESPHome output without publishing MQTT.
#
# MQTT:
# Operation topic:
# viewroad-commands/poolpump-timer/operation
#
# Payloads used by this package:
# BOOST
# OFF
# TIMER
#
# DEPENDENCIES:
# - switch.esp_poolpumppower_power_output provides the real output state.
# - The ESPHome pool pump device subscribes to the MQTT operation topic.
# - Native ESPHome mode and boost entities coexist with this one-tap control.
#
# NOTES:
# - input_boolean.timer_midnight_pending is internal to this package.
# - Dashboard cards currently use input_boolean.poolpump_mode_switch.
#
#:########################################################################################:#
#:########################################################################################:#
# Home Assistant Customisation #
#:########################################################################################:#
homeassistant: homeassistant:
customize: customize:
input_boolean.poolpump_mode_switch: input_boolean.poolpump_mode_switch:
icon: mdi:pump icon: mdi:pump
#:########################################################################################:#
# Input Booleans #
#:########################################################################################:#
input_boolean: input_boolean:
poolpump_mode_switch: poolpump_mode_switch:
name: "Pool Pump Operation" name: "Pool Pump Operation"
@@ -14,6 +72,10 @@ input_boolean:
name: "Send TIMER at Midnight" name: "Send TIMER at Midnight"
initial: false initial: false
#:########################################################################################:#
# Automations #
#:########################################################################################:#
automation: automation:
- alias: "Pool Pump → send BOOST when user turns UI switch ON" - alias: "Pool Pump → send BOOST when user turns UI switch ON"
trigger: trigger:
@@ -90,6 +152,3 @@ automation:
- service: input_boolean.turn_off - service: input_boolean.turn_off
data: data:
entity_id: input_boolean.poolpump_mode_switch entity_id: input_boolean.poolpump_mode_switch
- service: input_boolean.turn_on
data:
entity_id: input_boolean.timer_midnight_pending
@@ -6,12 +6,16 @@
# Pool Timer Selection # Pool Timer Selection
# #
# FILE: # FILE:
# pool_timer_selection.yaml # packages/Pool_Controls/pool_timers_selections.yaml
# #
# VERSION: # VERSION:
# V1.0 2026-06-29 # V1.1 2026-07-20
# #
# VERSION HISTORY: # VERSION HISTORY:
# V1.1 2026-07-20
# - Corrected the documented package filename.
# - Documented command selectors versus ESPHome timer feedback.
#
# V1.0 2026-06-29 # V1.0 2026-06-29
# - Initial package. # - Initial package.
# - Adds Home Assistant input_select helpers for Pool Pump and Pool Light timer times. # - Adds Home Assistant input_select helpers for Pool Pump and Pool Light timer times.
@@ -36,6 +40,16 @@
# #
# Payload format is HH:MM. # Payload format is HH:MM.
# #
# STATUS AND FEEDBACK:
# - These input_select helpers send retained MQTT commands to ESPHome.
# - They do not synchronize their displayed options from the ESPHome devices.
# - The ESPHome Timeclock sensors are authoritative for the currently applied
# timer values.
# - A selector displaying "Default" does not prove that the device currently
# uses its default time.
# - Native ESPHome mode controls coexist with this package, while timer
# schedule changes continue to use the MQTT topics above.
#
# IMPORTANT: # IMPORTANT:
# The current ESPHome timer logic only handles same-day timer windows: # The current ESPHome timer logic only handles same-day timer windows:
# current_mins >= on_time AND current_mins < off_time # current_mins >= on_time AND current_mins < off_time
+49
View File
@@ -1,5 +1,52 @@
#:########################################################################################:#
# Alexa TP-Link Guest Wi-Fi Package #
#:########################################################################################:#
#
# TITLE:
# Alexa TP-Link Guest Wi-Fi
#
# FILE:
# packages/alexa_tplink_actions.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation and automation descriptions.
#
# V1.0
# - Initial Alexa-mediated TP-Link guest-network controls.
#
# PURPOSE:
# Enables or disables the TP-Link guest Wi-Fi network by sending custom Alexa
# requests through the kitchen Echo Show.
#
# DEPENDENCIES:
# - input_boolean.wifi_guest_network is a UI-managed helper that represents
# the requested guest-network state.
# - media_player.view_rd_kitchen_echo_show must be available through the Alexa
# Media Player integration.
# - The Alexa account must have a working, linked TP-Link skill.
#
# ALEXA REQUESTS:
# - On: ask t.p.link to enable guest network
# - Off: ask t.p.link to disable guest network
#
# OPERATION NOTES:
# - The helper records requested state; it does not confirm router state.
# - If Alexa or the TP-Link skill fails, the helper can disagree with the
# actual guest-network state.
# - After the helper remains on for two hours, it is turned off. That state
# change then sends the Alexa disable request.
# - The two-hour state-trigger period does not persist through a Home Assistant
# restart or automation reload.
#
#:########################################################################################:#
automation: automation:
- alias: "Turn on the Wifi Guest Network" - alias: "Turn on the Wifi Guest Network"
description: "Asks the linked TP-Link Alexa skill to enable the guest Wi-Fi network."
mode: single mode: single
triggers: triggers:
- trigger: state - trigger: state
@@ -14,6 +61,7 @@ automation:
media_content_id: ask t.p.link to enable guest network media_content_id: ask t.p.link to enable guest network
- alias: "Turn off the Wifi Guest Network" - alias: "Turn off the Wifi Guest Network"
description: "Asks the linked TP-Link Alexa skill to disable the guest Wi-Fi network."
mode: single mode: single
triggers: triggers:
- trigger: state - trigger: state
@@ -28,6 +76,7 @@ automation:
media_content_id: ask t.p.link to disable guest network media_content_id: ask t.p.link to disable guest network
- alias: "Switch guest network off after time" - alias: "Switch guest network off after time"
description: "Turns off the guest-network helper after it remains on for two hours."
mode: single mode: single
triggers: triggers:
- trigger: state - trigger: state
+20
View File
@@ -1,3 +1,23 @@
#:########################################################################################:#
# Package: Anniversary Events
# File: anniversaries.yaml
# Version: v1.0.0
# Date: 2024-09-04
#
# Purpose:
# Provides an anniversary sensor that reports the number of days until the
# next configured event and exposes event details as attributes.
#
# Dependency:
# - Anniversary custom integration (platform: anniversary).
#
# Output:
# - sensor.events: Days until the configured holiday event.
#
# Version History:
# - v1.0.0 (2024-09-04): Initial anniversary event sensor package.
#:########################################################################################:#
sensor: sensor:
- platform: anniversary - platform: anniversary
name: events name: events
+30
View File
@@ -1,3 +1,29 @@
#:########################################################################################:#
# Package: Appliance Activity Feed Sensors
# File: appliance_status_mqttfeed.yaml
# Version: v1.2.0
# Date: 2026-07-20
#
# Purpose:
# Provides MQTT sensors for appliance, vehicle-charger, and tool-charger
# completion messages published to the household activity feed.
#
# Dependencies:
# - Home Assistant MQTT integration and broker connectivity.
# - External publishers or the announcement router must publish completion
# payloads to the configured activity-feed topics.
#
# Known Limitation:
# - sensor.washer_finished has device_class timestamp and therefore requires
# an ISO 8601 timestamp. P013-F018 records that the current router may publish
# a time-only payload, which is not valid for this device class.
#
# Version History:
# - v1.2.0 (2026-07-20): Added the downstairs dishwasher completion sensor.
# - v1.1.0 (2025-05-18): Set the washer completion sensor as a timestamp.
# - v1.0.0 (2023-10-20): Added the initial MQTT activity-feed sensors.
#:########################################################################################:#
mqtt: mqtt:
sensor: sensor:
- unique_id: washing_machine_finished - unique_id: washing_machine_finished
@@ -13,6 +39,10 @@ mqtt:
name: "Dishwasher Finished" name: "Dishwasher Finished"
state_topic: "viewroad-status/activityfeed/Dishwasher_complete" state_topic: "viewroad-status/activityfeed/Dishwasher_complete"
icon: mdi:dishwasher icon: mdi:dishwasher
- unique_id: downstairs_dishwasher_finished
name: "Downstairs Dishwasher Finished"
state_topic: "viewroad-status/activityfeed/Downstairs_dishwasher_complete"
icon: mdi:dishwasher
- unique_id: 16Acharger_finished - unique_id: 16Acharger_finished
name: "Leaf Done" name: "Leaf Done"
state_topic: "viewroad-status/activityfeed/Leaf_Charger_complete" state_topic: "viewroad-status/activityfeed/Leaf_Charger_complete"
+18 -17
View File
@@ -1,23 +1,24 @@
################################################################################ #:########################################################################################:#
# PACKAGE: Underhouse Entrance Auto Lights # Package: Underhouse Entrance Auto Lights
################################################################################ # File: auto_lights_underhouse_entrance.yaml
# Version: v1.2.0
# Date: 2026-05-12
# #
# Version: # Purpose:
# 1.2 - 2026-05-12 # Replaces the Node-RED automation for the underhouse entrance lights. Uses
# - Added fast Zigbee2MQTT FP1-style event triggers: # Zigbee2MQTT mmWave event triggers (enter/approach/presence) to turn lights
# sensor.underhouse_mmwave_sensor_x23ms_presence_event = enter # on instantly, then off after 400 seconds of sustained clear presence.
# sensor.underhouse_mmwave_sensor_x23ms_presence_event = approach
# - Keeps binary presence as a backup trigger and as the clear/off condition.
# - This should behave closer to the previous Node-RED automation.
# #
# Notes: # Behaviour:
# - The UI may show the binary sensor as "Detected", but the YAML state is # - Fast Zigbee2MQTT FP1-style event triggers (enter, approach) turn lights on.
# normally "on". # - Binary presence sensor serves as a backup trigger and clearance condition.
# - The presence_event sensor can react earlier than the binary presence sensor. # - Lights turn off 400 seconds after presence clears.
# - The event sensor is used to turn lights on quickly, but not to decide when
# the area is clear, because it may remain on its last event value.
# #
################################################################################ # Version History:
# - v1.2.0 (2026-05-12): Added fast Zigbee2MQTT FP1-style event triggers and
# converted header to standard package format.
#
#:########################################################################################:#
automation: automation:
- id: underhouse_entrance_auto_lights - id: underhouse_entrance_auto_lights
+17 -23
View File
@@ -1,28 +1,24 @@
#:########################################################################################:# #:########################################################################################:#
# TITLE: UNDERHOUSE STORAGE LIGHTS AUTO LIGHTS PACKAGE # Package: Underhouse Storage Lights
#:########################################################################################:# # File: auto_lights_underhouse_storage.yaml
# DESCRIPTION: # Version: v1.1.0
# Date: 2026-06-14
#
# Purpose:
# Replaces the Node-RED automation for the underhouse storage lights. # Replaces the Node-RED automation for the underhouse storage lights.
# Doors, PIR sensors, a remote switch, and local relay changes can start or # Doors, PIR sensors, a remote switch, and local relay changes can start or
# extend the storage lighting timer. # extend the storage lighting timer.
# #
# VERSION: # Behaviour:
# V1.1 2026-06-14 # - Individual door-open triggers turn lights on immediately.
# - Door-open trigger now watches each real door contact individually. # - All doors closed turns lights off immediately.
# - This means any door changing from closed to open turns the lights on # - PIR occupancy can start/restart the motion timer.
# immediately, even if another door is already open. # - Remote switch press starts or extends the light timer.
# - All doors closed now turns the lights off immediately.
# - PIR occupancy can still start/restart the motion timer after that.
# #
# V1.0 2026-05-12 # Version History:
# - Initial Home Assistant package version. # - v1.1.0 (2026-06-14): Door-open trigger now watches each real door contact
# # individually; all doors closed turns lights off immediately.
# EDIT NOTES: # - v1.0.0 (2026-05-12): Initial Home Assistant package version.
# - Add future door contacts in all places marked FUTURE DOOR.
# - Add future PIR sensors in the PIR group marked FUTURE PIR.
# - The generated group entities used below should be:
# binary_sensor.underhouse_storage_door_contacts
# binary_sensor.underhouse_storage_pir_presence
#:########################################################################################:# #:########################################################################################:#
binary_sensor: binary_sensor:
@@ -112,8 +108,7 @@ automation:
entity_id: entity_id:
- binary_sensor.underhouse_stealth_door_x07rs_contact - binary_sensor.underhouse_stealth_door_x07rs_contact
- binary_sensor.underhouse_north_door_door - binary_sensor.underhouse_north_door_door
# FUTURE DOOR: replace this with the actual third underhouse storage door contact. - binary_sensor.under_house_underhouse_east_stealth_door_door
#- binary_sensor.underhouse_future_door_contact
from: "off" from: "off"
to: "on" to: "on"
@@ -213,8 +208,7 @@ automation:
entity_id: entity_id:
- binary_sensor.underhouse_stealth_door_x07rs_contact - binary_sensor.underhouse_stealth_door_x07rs_contact
- binary_sensor.underhouse_north_door_door - binary_sensor.underhouse_north_door_door
# FUTURE DOOR: replace this with the actual third underhouse storage door contact. - binary_sensor.under_house_underhouse_east_stealth_door_door
#- binary_sensor.underhouse_future_door_contact
from: "on" from: "on"
to: "off" to: "off"
action: action:
+24
View File
@@ -0,0 +1,24 @@
#:########################################################################################:#
# Package: Basement Lights
# File: basement_lights.yaml
# Version: v1.0.1
# Date: 2026-07-24
#
# Purpose:
# Consolidated basement lighting configuration for two-way switching synchronization
# between the basement hallway and front door area lights.
#
# Version History:
# - v1.0.0 (2024-12-20): Created basement lighting package with two-way sync.
# - v1.0.1 (2026-07-24): Standardized header format with additional metadata.
#:########################################################################################:#
group:
basement_lights:
name: "Basement Lights"
entities:
- switch.esp_officelights_relay_1_nighttime_lights
- switch.esp_officelights_relay_2_daytime_lights
- switch.esp_underhouselights_underhouse_entrance_lights
- switch.esp_underhouselights_underhouse_storage_lights
- switch.esp_laundrylights_relay_1_laundry_lights
+11 -6
View File
@@ -1,8 +1,8 @@
############################################################################### ###############################################################################
# Bedroom 1 Ikea Air Filter Package # Bedroom 1 Ikea Air Filter Package
# File: /config/packages/bedroom_1_ikea_air_filter.yaml # File: /config/packages/bedroom_1_ikea_air_filter.yaml
# Version: 1.1 # Version: 1.2
# Date: 2026-05-08 # Date: 2026-07-20
# #
# Purpose: # Purpose:
# - Creates a dropdown helper called "Bedroom 1 Ikea Fan". # - Creates a dropdown helper called "Bedroom 1 Ikea Fan".
@@ -11,7 +11,7 @@
# #
# Behaviour notes: # Behaviour notes:
# - Quiet time turning on sets fan speed to 2. # - Quiet time turning on sets fan speed to 2.
# - Bedside lamp turning on sets fan speed to 2. # - Bedside lamp or Jump Fade control turning on sets fan speed to 2.
# - Bedside lamp turning off does not change the fan speed. # - Bedside lamp turning off does not change the fan speed.
# - There is no 10-minute re-check, so the fan will not ramp back up just # - There is no 10-minute re-check, so the fan will not ramp back up just
# because the bedside lamp was switched off. # because the bedside lamp was switched off.
@@ -80,12 +80,16 @@ automation:
entity_id: input_boolean.quiet_time entity_id: input_boolean.quiet_time
to: "on" to: "on"
# Bedside lamp only forces low when it turns on. # The lamp state catches direct light control.
# Turning the lamp off does not change the fan speed.
- trigger: state - trigger: state
entity_id: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb entity_id: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb
to: "on" to: "on"
# The Jump Fade switch catches a bedside press while the lamp is already on.
- trigger: state
entity_id: switch.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb_jump_fade_up_down
to: "on"
# Main timing boundaries. # Main timing boundaries.
- trigger: time - trigger: time
at: at:
@@ -104,9 +108,10 @@ automation:
- variables: - variables:
trigger_entity: "{{ trigger.entity_id | default('') }}" trigger_entity: "{{ trigger.entity_id | default('') }}"
bedside_lamp_entity: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb bedside_lamp_entity: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb
bedside_lamp_switch_entity: switch.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb_jump_fade_up_down
bedside_lamp_on_trigger: >- bedside_lamp_on_trigger: >-
{{ {{
trigger_entity == bedside_lamp_entity trigger_entity in [bedside_lamp_entity, bedside_lamp_switch_entity]
and trigger.to_state is defined and trigger.to_state is defined
and trigger.to_state.state == 'on' and trigger.to_state.state == 'on'
}} }}
+23
View File
@@ -1,3 +1,26 @@
#:########################################################################################:#
# Package: Bedroom 3 Window LED Actions
# File: bedroom_3_led_actions.yaml
# Version: v1.0.0
# Date: 2023-10-20
#
# Purpose:
# Provides a selectable control for publishing Bedroom 3 window LED commands
# to the household MQTT command topic.
#
# Behaviour:
# - The Off option publishes the off command.
# - The 1 Hour Fade option publishes the fade_1 command.
# - The placeholder option preserves the neutral selector state.
#
# Dependencies:
# - Home Assistant MQTT integration and broker connectivity.
# - An external MQTT consumer for the Bedroom 3 window LED action topic.
#
# Version History:
# - v1.0.0 (2023-10-20): Initial Bedroom 3 window LED action package.
#:########################################################################################:#
input_select: input_select:
bedroom_3_led_actions: bedroom_3_led_actions:
icon: mdi:led-strip icon: mdi:led-strip
+6
View File
@@ -67,6 +67,12 @@ automation:
variables: variables:
bedtime_delayed_off_targets: bedtime_delayed_off_targets:
- entity_id: switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
delay_seconds: 0
- entity_id: switch.esp_centralstairs_bottom_relay_2_stair_footer_lights
delay_seconds: 0
- entity_id: switch.esp_mainkitchenlights_relay_1_main_lights - entity_id: switch.esp_mainkitchenlights_relay_1_main_lights
delay_seconds: 4 delay_seconds: 4
+120 -31
View File
@@ -1,8 +1,8 @@
#:########################################################################################:# #:########################################################################################:#
# Package: BYD Charging Schedule At Home # Package: BYD Charging Schedule At Home
# File: byd_charging_schedule_athome.yaml # File: byd_charging_schedule_athome.yaml
# Version: v1.0.1 # Version: v1.1.0
# Date: 2026-06-19 # Date: 2026-07-23
# #
# Purpose: # Purpose:
# Enables the BYD Atto 3 charging schedule when the car arrives home. # Enables the BYD Atto 3 charging schedule when the car arrives home.
@@ -11,11 +11,12 @@
# #
# Behaviour: # Behaviour:
# - Confirmed arrival home: # - Confirmed arrival home:
# Turns ON switch.atto_3_schedule_enabled, # Sets the schedule to 9:00 PM through midnight with charge-to-full disabled,
# but only if the car had previously been confirmed away. # enables it, and retries cloud reconciliation once per minute.
# #
# - Confirmed away: # - Confirmed away:
# After away_confirm_minutes, turns OFF switch.atto_3_schedule_enabled. # After away_confirm_minutes, disables the schedule and retries cloud
# reconciliation once per minute.
# #
# - Manual control: # - Manual control:
# If the car is already home and the user or another HA automation turns # If the car is already home and the user or another HA automation turns
@@ -35,24 +36,35 @@ input_boolean:
automation: automation:
#:######################################################################################:# #:######################################################################################:#
# ARRIVAL - Turn schedule ON only after a confirmed away period # ARRIVAL - Reconcile and enable schedule after a confirmed away period
#:######################################################################################:# #:######################################################################################:#
- id: byd_atto3_charging_schedule_arrival_enable - id: byd_atto3_charging_schedule_arrival_enable
alias: "BYD Atto 3 Charging Schedule - Arrival Enable" alias: "BYD Atto 3 Charging Schedule - Arrival Enable"
description: > description: >
Turns the BYD charging schedule ON when the car arrives home, but only if Reconciles the BYD charging schedule to 9:00 PM through midnight and turns
it was previously confirmed away. This prevents brief away/home glitches it ON when the car arrives home after a confirmed away period. Rechecks the
from undoing a manual OFF while the car is actually still at home. cloud-confirmed state once per minute and retries failed updates.
mode: single mode: single
variables: &byd_charging_schedule_vars variables: &byd_charging_schedule_vars
tracker_entity: device_tracker.byd_atto3_ibeacon_bur4c2a_bermuda_tracker tracker_entity: device_tracker.byd_atto3_ibeacon_bur4c2a_bermuda_tracker
schedule_switch_entity: switch.atto_3_schedule_enabled schedule_switch_entity: switch.atto_3_schedule_enabled
schedule_start_time_entity: time.atto_3_start_time
schedule_end_time_entity: time.atto_3_end_time
schedule_charge_to_full_entity: switch.atto_3_charge_to_full
# HA normally stores "At Home" as "home". # HA normally stores "At Home" as "home".
home_state: "home" home_state: "home"
# Required charging schedule settings.
expected_start_time: "21:00:00"
expected_end_time: "00:00:00"
# Adjustable YAML-only delay before the car is treated as truly away. # Adjustable YAML-only delay before the car is treated as truly away.
away_confirm_minutes: 15 away_confirm_minutes: 10
# Verify cloud-confirmed schedule state at one-minute intervals.
schedule_verify_attempts: 6
schedule_verify_interval: "00:01:00"
# These states will not be treated as confirmed away. # These states will not be treated as confirmed away.
ignored_tracker_states: ignored_tracker_states:
@@ -69,7 +81,7 @@ automation:
{{ trigger.to_state is not none {{ trigger.to_state is not none
and trigger.from_state is not none and trigger.from_state is not none
and trigger.to_state.state == home_state and trigger.to_state.state == home_state
and trigger.from_state.state != home_state }} and trigger.from_state.state == 'not_home' }}
actions: actions:
- choose: - choose:
@@ -78,23 +90,72 @@ automation:
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
state: "on" state: "on"
sequence: sequence:
- action: switch.turn_on - action: input_boolean.turn_off
target: target:
entity_id: "{{ schedule_switch_entity }}" entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
- action: input_boolean.turn_off - repeat:
target: count: "{{ schedule_verify_attempts | int(6) }}"
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away sequence:
- if:
- condition: template
value_template: "{{ is_state(tracker_entity, home_state) }}"
then:
- if:
- condition: template
value_template: >-
{{ states(schedule_start_time_entity) != expected_start_time }}
then:
- action: time.set_value
continue_on_error: true
target:
entity_id: "{{ schedule_start_time_entity }}"
data:
time: "{{ expected_start_time }}"
- if:
- condition: template
value_template: >-
{{ states(schedule_end_time_entity) != expected_end_time }}
then:
- action: time.set_value
continue_on_error: true
target:
entity_id: "{{ schedule_end_time_entity }}"
data:
time: "{{ expected_end_time }}"
- if:
- condition: template
value_template: >-
{{ not is_state(schedule_charge_to_full_entity, 'off') }}
then:
- action: switch.turn_off
continue_on_error: true
target:
entity_id: "{{ schedule_charge_to_full_entity }}"
- if:
- condition: template
value_template: >-
{{ not is_state(schedule_switch_entity, 'on') }}
then:
- action: switch.turn_on
continue_on_error: true
target:
entity_id: "{{ schedule_switch_entity }}"
- delay: "{{ schedule_verify_interval }}"
#:######################################################################################:# #:######################################################################################:#
# DEPARTURE - Turn schedule OFF only after confirmed away timeout # DEPARTURE - Disable schedule after confirmed away timeout
#:######################################################################################:# #:######################################################################################:#
- id: byd_atto3_charging_schedule_departure_disable_after_timeout - id: byd_atto3_charging_schedule_departure_disable_after_timeout
alias: "BYD Atto 3 Charging Schedule - Departure Disable After Timeout" alias: "BYD Atto 3 Charging Schedule - Departure Disable After Timeout"
description: > description: >
When the car leaves home, waits for the configured away timeout. If the car When the car leaves home, waits for the configured away timeout. If the car
is still away after that delay, turns the BYD charging schedule OFF and is still away, marks it confirmed away, turns the BYD charging schedule OFF,
marks the car as confirmed away. and retries failed cloud updates once per minute.
mode: restart mode: restart
variables: *byd_charging_schedule_vars variables: *byd_charging_schedule_vars
@@ -111,7 +172,7 @@ automation:
actions: actions:
- delay: - delay:
minutes: "{{ away_confirm_minutes | int(15) }}" minutes: "{{ away_confirm_minutes | int(10) }}"
- condition: template - condition: template
value_template: >- value_template: >-
@@ -119,14 +180,28 @@ automation:
{{ current_state != home_state {{ current_state != home_state
and current_state not in ignored_tracker_states }} and current_state not in ignored_tracker_states }}
- action: switch.turn_off
target:
entity_id: "{{ schedule_switch_entity }}"
- action: input_boolean.turn_on - action: input_boolean.turn_on
target: target:
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
- repeat:
count: "{{ schedule_verify_attempts | int(6) }}"
sequence:
- if:
- condition: template
value_template: >-
{% set current_state = states(tracker_entity) %}
{{ current_state != home_state
and current_state not in ignored_tracker_states
and not is_state(schedule_switch_entity, 'off') }}
then:
- action: switch.turn_off
continue_on_error: true
target:
entity_id: "{{ schedule_switch_entity }}"
- delay: "{{ schedule_verify_interval }}"
#:######################################################################################:# #:######################################################################################:#
# STARTUP SAFETY - If HA restarts while the car is away, re-arm arrival logic # STARTUP SAFETY - If HA restarts while the car is away, re-arm arrival logic
#:######################################################################################:# #:######################################################################################:#
@@ -134,8 +209,8 @@ automation:
alias: "BYD Atto 3 Charging Schedule - Startup Away Check" alias: "BYD Atto 3 Charging Schedule - Startup Away Check"
description: > description: >
On Home Assistant startup, if the car is already away and remains away On Home Assistant startup, if the car is already away and remains away
beyond the configured timeout, turn the schedule OFF and mark the car as beyond the configured timeout, mark it confirmed away and reconcile the
confirmed away so that the next confirmed arrival can enable the schedule. charging schedule OFF so the next confirmed arrival can enable it.
mode: single mode: single
variables: *byd_charging_schedule_vars variables: *byd_charging_schedule_vars
@@ -153,7 +228,7 @@ automation:
and current_state not in ignored_tracker_states }} and current_state not in ignored_tracker_states }}
- delay: - delay:
minutes: "{{ away_confirm_minutes | int(15) }}" minutes: "{{ away_confirm_minutes | int(10) }}"
- condition: template - condition: template
value_template: >- value_template: >-
@@ -161,10 +236,24 @@ automation:
{{ current_state != home_state {{ current_state != home_state
and current_state not in ignored_tracker_states }} and current_state not in ignored_tracker_states }}
- action: switch.turn_off
target:
entity_id: "{{ schedule_switch_entity }}"
- action: input_boolean.turn_on - action: input_boolean.turn_on
target: target:
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
- repeat:
count: "{{ schedule_verify_attempts | int(6) }}"
sequence:
- if:
- condition: template
value_template: >-
{% set current_state = states(tracker_entity) %}
{{ current_state != home_state
and current_state not in ignored_tracker_states
and not is_state(schedule_switch_entity, 'off') }}
then:
- action: switch.turn_off
continue_on_error: true
target:
entity_id: "{{ schedule_switch_entity }}"
- delay: "{{ schedule_verify_interval }}"
+22
View File
@@ -1,6 +1,28 @@
#:########################################################################################:#
# Package: CCTV Motion Feed
# File: cctv_controls.yaml
# Version: v1.1.0
# Date: 2026-07-24
#
# Purpose:
# Provides a Home Assistant binary sensor for front-door motion messages
# published by the external CCTV system over MQTT.
#
# Dependencies:
# - Home Assistant MQTT integration and broker connectivity.
# - An external CCTV publisher for the configured motion topic.
#
#
# Version History:
# - v1.1.0 (2026-07-24): Added explicit numeric ON/OFF payloads (P013-F039).
# - v1.0.0 (2022-10-11): Initial CCTV front-door motion sensor package.
#:########################################################################################:#
mqtt: mqtt:
binary_sensor: binary_sensor:
- unique_id: front_door_movement_camera_corner - unique_id: front_door_movement_camera_corner
name: "Front Door Movement" name: "Front Door Movement"
state_topic: "viewroad-status/cctv/front_door_motion" state_topic: "viewroad-status/cctv/front_door_motion"
payload_on: "1"
payload_off: "0"
icon: mdi:exit-run icon: mdi:exit-run
+20
View File
@@ -1,3 +1,23 @@
#:########################################################################################:#
# Package: Dashboard Helpers
# File: dashboard_helpers.yaml
# Version: v1.0.1
# Date: 2026-07-24
#
# Purpose:
# Provides helper entities used by stored Home Assistant dashboards for
# temporary display behavior that does not belong to a device integration.
#
# Behaviour:
# - The moon-phase overlay helper controls whether the percentage is displayed.
# - The companion script enables the overlay for ten seconds and then disables it.
# - Restart mode restarts the ten-second display period when called repeatedly.
#
# Version History:
# - v1.0.0 (2026-02-16): Added the temporary moon-phase percentage overlay helper.
# - v1.0.1 (2026-07-24): Standardized header format with additional metadata.
#:########################################################################################:#
input_boolean: input_boolean:
moon_phase_percent_overlay: moon_phase_percent_overlay:
name: Moon phase percent overlay name: Moon phase percent overlay
@@ -1,9 +1,9 @@
################################################################################ ################################################################################
# PACKAGE: Downstairs Lights Bulk Activation # PACKAGE: Downstairs Lights Bulk Activation
# FILE: packages/downstairs_lights_bulk_activation.yaml # FILE: packages/downstairs_bulk_lights_on_from_stairs.yaml
# #
# Version: 1.0 # Version: 1.1
# Date: 2026-04-26 # Date: 2026-07-20
# #
# Notes: # Notes:
# - Uses the virtual switch: # - Uses the virtual switch:
@@ -16,16 +16,16 @@
# Turns OFF the listed downstairs lights, but only if each light is currently ON. # Turns OFF the listed downstairs lights, but only if each light is currently ON.
# #
# - No action is taken unless: # - No action is taken unless:
# input_boolean.downstairs_flat_occupied is ON. # input_boolean.downstairs_flat_occupied is OFF.
################################################################################ ################################################################################
automation: automation:
- id: downstairs_lights_bulk_activation - id: downstairs_lights_bulk_activation
alias: "Downstairs Lights Bulk Activation" alias: "Downstairs Lights Bulk Activation"
description: > description: >
V1.0 - 2026-04-26: Bulk ON/OFF control for downstairs lights from the V1.1 - 2026-07-20: Bulk ON/OFF control for downstairs lights from the
central stair lightswitch virtual switch. Only operates when the downstairs central stair lightswitch virtual switch. Only operates when the downstairs
flat occupied input_boolean is ON. flat is not occupied.
mode: single mode: single
+476
View File
@@ -0,0 +1,476 @@
#:########################################################################################:#
# Package: EdgeRouter SNMP Monitoring
# File: edgerouter_snmp.yaml
# Version: v1.0.1
# Date: 2026-07-21
#
# Purpose:
# Monitors the EdgeRouter X through read-only SNMPv2c after the previous EdgeOS
# integration became incompatible with EdgeOS v3.0.x. Provides traffic rates
# for the major physical interfaces and system health sensors for the router.
#
# Interfaces:
# - eth0 / ifIndex 4: WAN and Internet traffic.
# - eth1 / ifIndex 5: Home LAN traffic.
# - eth2 / ifIndex 6: Home Wireless traffic.
# - eth3 / ifIndex 7: Home Automation traffic.
# - eth4 / ifIndex 8: Secondary Home Automation traffic.
#
# Behaviour:
# - Polls 64-bit IF-MIB counters so totals remain reliable on busy interfaces.
# - Converts cumulative received/transmitted byte counters into kB/s rates.
# - Polls firmware, uptime, CPU idle, load averages, and memory counters.
# - Calculates memory usage from total, free, buffer, and cache values.
#
# Polling:
# - Traffic counters: Every 60 seconds.
# - Uptime, CPU, load, and memory: Every 60 seconds.
# - Firmware description: Every 300 seconds.
#
# Notes:
# - The SNMP community is stored in secrets.yaml and is never hardcoded here.
# - WAN traffic intentionally uses physical eth0 because hardware-offloaded
# traffic is under-reported by the logical pppoe0 and eth0.10 interfaces.
# - Received traffic on LAN interfaces is upload toward the router; transmitted
# traffic is download from the router toward clients on that interface.
# - SNMP, derivative, and template platforms create standalone entities rather
# than a combined Home Assistant device entry.
#
# Version History:
# - v1.0.1 (2026-07-21): Moved the EdgeRouter host address to secrets.yaml.
# - v1.0.0 (2026-07-21): Initial documented SNMP traffic and health package.
#:########################################################################################:#
sensor:
#:######################################################################################:#
# WAN TRAFFIC - eth0 / ifIndex 4
#:######################################################################################:#
# ifHCInOctets counts bytes received from the Internet. The physical interface
# is used so hardware-offloaded traffic is included in the total.
- platform: snmp
name: "EdgeRouter WAN Received Bytes"
unique_id: "edgerouter_wan_received_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.6.4
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# ifHCOutOctets counts bytes transmitted from the router to the Internet.
- platform: snmp
name: "EdgeRouter WAN Transmitted Bytes"
unique_id: "edgerouter_wan_transmitted_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.10.4
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Convert the cumulative received-byte counter into the WAN download rate.
- platform: derivative
name: "EdgeRouter WAN Download Rate"
unique_id: "edgerouter_wan_download_rate"
source: sensor.edgerouter_wan_received_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
# Convert the cumulative transmitted-byte counter into the WAN upload rate.
- platform: derivative
name: "EdgeRouter WAN Upload Rate"
unique_id: "edgerouter_wan_upload_rate"
source: sensor.edgerouter_wan_transmitted_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
#:######################################################################################:#
# HOME LAN TRAFFIC - eth1 / ifIndex 5
#:######################################################################################:#
# Bytes received by the router from Home LAN clients become the LAN upload rate.
- platform: snmp
name: "EdgeRouter Home LAN Received Bytes"
unique_id: "edgerouter_home_lan_received_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.6.5
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Bytes transmitted by the router to Home LAN clients become the LAN download rate.
- platform: snmp
name: "EdgeRouter Home LAN Transmitted Bytes"
unique_id: "edgerouter_home_lan_transmitted_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.10.5
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Derive the client-to-router upload rate from the received-byte counter.
- platform: derivative
name: "EdgeRouter Home LAN Upload Rate"
unique_id: "edgerouter_home_lan_upload_rate"
source: sensor.edgerouter_home_lan_received_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
# Derive the router-to-client download rate from the transmitted-byte counter.
- platform: derivative
name: "EdgeRouter Home LAN Download Rate"
unique_id: "edgerouter_home_lan_download_rate"
source: sensor.edgerouter_home_lan_transmitted_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
#:######################################################################################:#
# HOME WIRELESS TRAFFIC - eth2 / ifIndex 6
#:######################################################################################:#
# Bytes received by the router from the wireless network become its upload rate.
- platform: snmp
name: "EdgeRouter Home Wireless Received Bytes"
unique_id: "edgerouter_home_wireless_received_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.6.6
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Bytes transmitted by the router to the wireless network become its download rate.
- platform: snmp
name: "EdgeRouter Home Wireless Transmitted Bytes"
unique_id: "edgerouter_home_wireless_transmitted_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.10.6
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Derive the wireless-to-router upload rate from the received-byte counter.
- platform: derivative
name: "EdgeRouter Home Wireless Upload Rate"
unique_id: "edgerouter_home_wireless_upload_rate"
source: sensor.edgerouter_home_wireless_received_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
# Derive the router-to-wireless download rate from the transmitted-byte counter.
- platform: derivative
name: "EdgeRouter Home Wireless Download Rate"
unique_id: "edgerouter_home_wireless_download_rate"
source: sensor.edgerouter_home_wireless_transmitted_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
#:######################################################################################:#
# HOME AUTOMATION TRAFFIC - eth3 / ifIndex 7
#:######################################################################################:#
# Bytes received by the router from Home Automation become its upload rate.
- platform: snmp
name: "EdgeRouter Home Automation Received Bytes"
unique_id: "edgerouter_home_automation_received_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.6.7
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Bytes transmitted by the router to Home Automation become its download rate.
- platform: snmp
name: "EdgeRouter Home Automation Transmitted Bytes"
unique_id: "edgerouter_home_automation_transmitted_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.10.7
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Derive the Home Automation upload rate from the received-byte counter.
- platform: derivative
name: "EdgeRouter Home Automation Upload Rate"
unique_id: "edgerouter_home_automation_upload_rate"
source: sensor.edgerouter_home_automation_received_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
# Derive the Home Automation download rate from the transmitted-byte counter.
- platform: derivative
name: "EdgeRouter Home Automation Download Rate"
unique_id: "edgerouter_home_automation_download_rate"
source: sensor.edgerouter_home_automation_transmitted_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
#:######################################################################################:#
# SECONDARY HOME AUTOMATION TRAFFIC - eth4 / ifIndex 8
#:######################################################################################:#
# Bytes received by the router from this network become its upload rate.
- platform: snmp
name: "EdgeRouter Home Automation 2 Received Bytes"
unique_id: "edgerouter_home_automation_2_received_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.6.8
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Bytes transmitted by the router to this network become its download rate.
- platform: snmp
name: "EdgeRouter Home Automation 2 Transmitted Bytes"
unique_id: "edgerouter_home_automation_2_transmitted_bytes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.31.1.1.1.10.8
accept_errors: true
device_class: data_size
state_class: total_increasing
unit_of_measurement: "B"
scan_interval: 60
# Derive the secondary Home Automation upload rate from received bytes.
- platform: derivative
name: "EdgeRouter Home Automation 2 Upload Rate"
unique_id: "edgerouter_home_automation_2_upload_rate"
source: sensor.edgerouter_home_automation_2_received_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
# Derive the secondary Home Automation download rate from transmitted bytes.
- platform: derivative
name: "EdgeRouter Home Automation 2 Download Rate"
unique_id: "edgerouter_home_automation_2_download_rate"
source: sensor.edgerouter_home_automation_2_transmitted_bytes
unit_prefix: k
unit_time: s
round: 1
time_window: "00:00:30"
#:######################################################################################:#
# ROUTER IDENTITY AND UPTIME
#:######################################################################################:#
# sysDescr reports the installed EdgeOS firmware and build information.
- platform: snmp
name: "EdgeRouter Firmware"
unique_id: "edgerouter_firmware"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.1.1.0
accept_errors: true
scan_interval: 300
# hrSystemUptime is reported in hundredths of a second and converted to seconds.
- platform: snmp
name: "EdgeRouter Uptime"
unique_id: "edgerouter_uptime"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.2.1.25.1.1.0
accept_errors: true
device_class: duration
state_class: measurement
unit_of_measurement: "s"
value_template: "{{ ((value | default(0) | float(0)) / 100) | round(0) }}"
scan_interval: 60
#:######################################################################################:#
# CPU USAGE AND LOAD AVERAGES
#:######################################################################################:#
# UCD-SNMP reports CPU idle percentage, so usage is calculated as 100 - idle.
- platform: snmp
name: "EdgeRouter CPU Usage"
unique_id: "edgerouter_cpu_usage"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.11.11.0
accept_errors: true
state_class: measurement
unit_of_measurement: "%"
value_template: "{{ 100 - (value | default(100) | int(100)) }}"
scan_interval: 60
# One-minute load average from the UCD-SNMP load table.
- platform: snmp
name: "EdgeRouter Load 1 Minute"
unique_id: "edgerouter_load_1_minute"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.10.1.3.1
accept_errors: true
state_class: measurement
value_template: "{{ value | default(0) | float(0) }}"
scan_interval: 60
# Five-minute load average from the UCD-SNMP load table.
- platform: snmp
name: "EdgeRouter Load 5 Minutes"
unique_id: "edgerouter_load_5_minutes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.10.1.3.2
accept_errors: true
state_class: measurement
value_template: "{{ value | default(0) | float(0) }}"
scan_interval: 60
# Fifteen-minute load average from the UCD-SNMP load table.
- platform: snmp
name: "EdgeRouter Load 15 Minutes"
unique_id: "edgerouter_load_15_minutes"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.10.1.3.3
accept_errors: true
state_class: measurement
value_template: "{{ value | default(0) | float(0) }}"
scan_interval: 60
#:######################################################################################:#
# MEMORY COUNTERS
#:######################################################################################:#
# Total physical memory reported by UCD-SNMP in KiB.
- platform: snmp
name: "EdgeRouter Memory Total"
unique_id: "edgerouter_memory_total"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.4.5.0
accept_errors: true
device_class: data_size
state_class: measurement
unit_of_measurement: "KiB"
scan_interval: 60
# Currently free memory reported by UCD-SNMP in KiB.
- platform: snmp
name: "EdgeRouter Memory Free"
unique_id: "edgerouter_memory_free"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.4.6.0
accept_errors: true
device_class: data_size
state_class: measurement
unit_of_measurement: "KiB"
scan_interval: 60
# Memory allocated to buffers, excluded from calculated active usage.
- platform: snmp
name: "EdgeRouter Memory Buffers"
unique_id: "edgerouter_memory_buffers"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.4.14.0
accept_errors: true
device_class: data_size
state_class: measurement
unit_of_measurement: "KiB"
scan_interval: 60
# Memory allocated to cache, excluded from calculated active usage.
- platform: snmp
name: "EdgeRouter Memory Cached"
unique_id: "edgerouter_memory_cached"
host: !secret edgerouter_host
community: !secret edgerouter_snmp_community
version: 2c
baseoid: 1.3.6.1.4.1.2021.4.15.0
accept_errors: true
device_class: data_size
state_class: measurement
unit_of_measurement: "KiB"
scan_interval: 60
#:######################################################################################:#
# DERIVED MEMORY USAGE
#:######################################################################################:#
# Calculate active memory as total minus free, buffers, and cache. The template
# remains unavailable until every source counter has a valid state.
template:
- sensor:
- name: "EdgeRouter Memory Usage"
unique_id: "edgerouter_memory_usage"
unit_of_measurement: "%"
state_class: measurement
availability: >-
{{
has_value('sensor.edgerouter_memory_total')
and has_value('sensor.edgerouter_memory_free')
and has_value('sensor.edgerouter_memory_buffers')
and has_value('sensor.edgerouter_memory_cached')
}}
state: >-
{{
(
(
(states('sensor.edgerouter_memory_total') | float(0))
- (states('sensor.edgerouter_memory_free') | float(0))
- (states('sensor.edgerouter_memory_buffers') | float(0))
- (states('sensor.edgerouter_memory_cached') | float(0))
)
/ (states('sensor.edgerouter_memory_total') | float(1))
* 100
)
| round(1)
}}
+47
View File
@@ -1,3 +1,50 @@
#:########################################################################################:#
# Firewall Internet Control Package #
#:########################################################################################:#
#
# TITLE:
# Firewall Internet Control
#
# FILE:
# packages/firewall_control.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured MQTT and external-firewall documentation.
#
# V1.0
# - Initial persistent and temporary internet-blocking controls.
#
# PURPOSE:
# Provides Home Assistant controls for persistent and temporary internet
# blocking of Mollie's devices through an external MQTT firewall service.
#
# PERSISTENT CONTROL:
# - Blocked enables persistent internet blocking.
# - Unblocked disables persistent internet blocking.
# - The switch state is reported by the external firewall service.
#
# TEMPORARY CONTROLS:
# - One-hour block publishes Blocked to the temporary-block topic.
# - 15-minute unblock publishes Unblocked to the temporary-unblock topic.
# - One-hour unblock publishes Unblocked to its one-hour topic.
#
# EXTERNAL DEPENDENCY:
# - Home Assistant only publishes command requests.
# - The external firewall service implements temporary durations, restores the
# appropriate state afterward, and publishes persistent switch feedback.
#
# MQTT NOTES:
# - All commands are non-retained.
# - No availability topic is configured for these entities.
# - The persistent status topic retains the legacy device label
# block-unblock-mollie-iphone8.
#
#:########################################################################################:#
mqtt: mqtt:
switch: switch:
- unique_id: mollies.internet - unique_id: mollies.internet
+23
View File
@@ -1,3 +1,26 @@
#:########################################################################################:#
# Package: Refrigerator Temperature MQTT Publishing
# File: fridge_temperatures.yaml
# Version: v1.0.0
# Date: 2024-09-04
#
# Purpose:
# Publishes refrigerator and freezer temperature changes to household MQTT
# status topics for use by external displays and consumers.
#
# Behaviour:
# - Publishes the source sensor state whenever a valid temperature changes.
# - Suppresses unknown and unavailable source states.
# - Covers the drinks fridge, drinks freezer, and main kitchen fridge.
#
# Dependencies:
# - The three configured Home Assistant temperature sensors.
# - Home Assistant MQTT integration and broker connectivity.
#
# Version History:
# - v1.0.0 (2024-09-04): Initial refrigerator temperature publishing package.
#:########################################################################################:#
automation: automation:
- id: publish_fridge_temps_drinks_fridge - id: publish_fridge_temps_drinks_fridge
alias: publish_fridge_temps_drinks_fridge alias: publish_fridge_temps_drinks_fridge
-42
View File
@@ -1,42 +0,0 @@
rest_command:
chores1_request:
url: !secret grocy_api_uri_chore6
method: PUT
headers:
accept: "*/*"
GROCY-API-KEY: !secret grocy_api_key
content_type: "application/json"
payload: '{
"name" : "{{ name }}" ,
"next_execution_assigned_to_user_id" : "{{ assigned_to }}"
}'
# payload: '{"name" : "{{ name }}" , "next_estimated_execution_time" : "{{ next_time }}", "next_execution_assigned_to_user_id" : "{{ assigned_to }}" }'
verify_ssl: false
sensor:
- platform: rest
name: Dog Motel Cleaning
resource: !secret grocy_api_uri_chore3
method: GET
value_template: "{{ (((as_timestamp(value_json.next_estimated_execution_time))-as_timestamp(now())) | int /60/1440) | round(0) }}"
json_attributes:
- last_tracked
- estimated_execution_time
headers:
Accept: application/json
Content-Type: application/json
GROCY-API-KEY: !secret grocy_api_key
unit_of_measurement: "Days"
- platform: rest
name: Red Bin Out
resource: !secret grocy_api_uri_chore2
method: GET
value_template: "{{ (((as_timestamp(value_json.next_estimated_execution_time))-as_timestamp(now())) | int /60/1440) | round(0) }}"
json_attributes:
- last_tracked
- estimated_execution_time
headers:
Accept: application/json
Content-Type: application/json
GROCY-API-KEY: !secret grocy_api_key
unit_of_measurement: "Days"
+47 -4
View File
@@ -1,11 +1,54 @@
#:########################################################################################:# #:########################################################################################:#
# Home Assistant Summary Package - V2 # # Home Assistant Summary Package - V2 #
#:########################################################################################:# #:########################################################################################:#
# #
# Uses V2 entity IDs to avoid stale/ghost entities from earlier versions. # TITLE:
# Home Assistant Summary V2
# #
# Dashboard entity prefix: # FILE:
# sensor.ha_summary_v2_... # packages/ha_summary.yaml
#
# VERSION:
# V2.1 2026-07-20
#
# VERSION HISTORY:
# V2.1 2026-07-20
# - Added structured documentation for refresh behavior and counting rules.
# - Documented heuristic classifications and their limitations.
#
# V2.0
# - Introduced V2 entity IDs to avoid stale entities from earlier versions.
#
# PURPOSE:
# Provides dashboard summary sensors for Home Assistant entities, devices,
# areas, cameras, scenes, scripts, automations, add-ons, and updates.
#
# It also provides device and entity counts for ESPHome, Tasmota, ZHA,
# Zigbee2MQTT, Matter, and selected cloud integrations.
#
# OUTPUTS:
# Dashboard entity prefix:
# sensor.ha_summary_v2_...
#
# REFRESH:
# Sensors refresh when Home Assistant starts, when the entity, device, or area
# registries change, when core configuration changes, and every five minutes.
#
# COUNTING NOTES:
# - Total devices counts distinct device IDs associated with current entities.
# Devices without entities are not included.
# - Broken entities are entities whose state is unknown or unavailable.
# - Add-ons are inferred from Supervisor update entities, excluding the Core,
# Supervisor, and Operating System update entities.
# - Zigbee2MQTT devices and entities are identified by searching MQTT device
# metadata for "zigbee2mqtt".
# - Matter Wi-Fi and Thread classifications search device metadata for known
# transport terms. These are heuristics rather than authoritative transport
# data, and a device can match both categories.
# - Matter unclassified counts subtract Wi-Fi and Thread counts from totals, so
# overlapping classifications can affect the result.
# - Cloud counts use the explicit integration list in the final two sensors.
# Add or remove integration domains there when maintaining cloud coverage.
# #
template: template:
+27
View File
@@ -1,3 +1,30 @@
#:########################################################################################:#
# Package: Node-RED Health Checks
# File: health_checks.yaml
# Version: v1.2.0
# Date: 2025-08-25
#
# Purpose:
# Monitors the Node-RED Panda instance through independent HTTP and MQTT health
# signals and combines them into one connectivity status.
#
# Behaviour:
# - Polls the HTTP health endpoint every 30 seconds with a three-second timeout.
# - Tracks the MQTT birth/will topic with QoS 1 and a 180-second expiry.
# - Reports the combined check as available when either source is available.
# - Reports the combined check as connected when either source is connected.
#
# Dependencies:
# - Reachability of the configured Node-RED HTTP health endpoint.
# - Home Assistant MQTT integration and the Node-RED birth/will publisher.
#
# Version History:
# - v1.2.0 (2025-08-25): Reduced MQTT expiry to 180 seconds and clarified the
# combined HTTP/MQTT sensor name.
# - v1.1.0 (2025-08-22): Split HTTP and MQTT probes and added a combined sensor.
# - v1.0.0 (2025-08-21): Initial Node-RED health-check package.
#:########################################################################################:#
# ---- HTTP health probe ---- # ---- HTTP health probe ----
binary_sensor: binary_sensor:
- platform: rest - platform: rest
@@ -1,5 +1,57 @@
########################################################################################## ##########################################################################################
# heat_pump_master_bedroom_schedules.yaml # Master Bedroom Heat Pump Schedules
##########################################################################################
#
# TITLE:
# Master Bedroom Heat Pump Schedules
#
# FILE:
# packages/heat_pump_master_bedroom_schedules.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation for schedules, boost, and helper behavior.
#
# V1.0
# - Initial master bedroom heat pump schedules and boost controls.
#
# PURPOSE:
# Provides optional 21:00 start and midnight stop schedules, a combined Night
# Power mode, and a manual one-hour boost for the master bedroom heat pump.
#
# DEPENDENCIES:
# - climate.master_bedroom provides the Panasonic Comfort Cloud heat pump.
# - The climate entity must support the "boost" and "none" preset modes.
# - packages/heat_pump_master_bedroom_helpers.yaml provides companion
# dashboard, setpoint, HVAC mode, profile, and sleep-timer controls.
#
# SCHEDULE MODES:
# - Night Power Mode turns the heat pump on at 21:00, applies boost for 30
# minutes, returns it to the normal preset, and turns it off at midnight.
# - 9pm On turns the heat pump on at 21:00 without changing its preset.
# - Midnight Off turns the heat pump off at midnight.
# - Enabling Night Power disables the separate 9pm On and Midnight Off modes.
# - Enabling either separate mode disables Night Power. The separate 9pm On
# and Midnight Off modes may be enabled together.
#
# MANUAL BOOST:
# - script.master_bedroom_boost_1hr applies boost for one hour.
# - If the heat pump was off when boost started, it is turned off afterward.
# - If it was already running, boost ends by restoring the normal preset and
# leaving the heat pump running.
#
# INTERNAL STATE:
# - input_boolean.master_bedroom_boost_restore_off records whether manual
# boost should restore the heat pump to off.
# - timer.master_bedroom_boost_timer controls manual and scheduled boost time.
#
# OPERATION NOTES:
# - The 21:00 and midnight schedules use exact-time triggers.
# - There is no startup catch-up if Home Assistant is offline at either time.
#
########################################################################################## ##########################################################################################
########################################################################################## ##########################################################################################
+37 -7
View File
@@ -1,25 +1,55 @@
#:########################################################################################:#
# Package: Internet Traffic Summary
# File: internet_traffic.yaml
# Version: v1.2.0
# Date: 2026-07-22
#
# Purpose:
# Preserves the household Internet download, upload, and total traffic sensors
# while sourcing current WAN rates from the EdgeRouter SNMP package.
#
# Behaviour:
# - Mirrors the EdgeRouter WAN download and upload rates in kB/s.
# - Calculates total traffic as the sum of download and upload rates.
# - Marks each sensor unavailable when its required source is unavailable.
#
# Dependency:
# - packages/edgerouter_snmp.yaml
#
# Version History:
# - v1.2.0 (2026-07-22): Migrated source data from EdgeOS attributes to the
# EdgeRouter SNMP WAN rate sensors and added availability handling.
# - v1.1.0 (2025-12-21): Migrated the three sensors to modern template syntax.
# - v1.0.0 (2022-10-12): Added the initial EdgeOS-based traffic sensors.
#:########################################################################################:#
template: template:
- sensor: - sensor:
- name: "Internet Download Traffic" - name: "Internet Download Traffic"
unique_id: internet_downtraffic unique_id: internet_downtraffic
unit_of_measurement: "kB/s" unit_of_measurement: "kB/s"
state_class: measurement state_class: measurement
state: > availability: "{{ has_value('sensor.edgerouter_wan_download_rate') }}"
{{ state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Received)') | float(0) }} state: "{{ states('sensor.edgerouter_wan_download_rate') | float(0) }}"
- name: "Internet Upload Traffic" - name: "Internet Upload Traffic"
unique_id: internet_uptraffic unique_id: internet_uptraffic
unit_of_measurement: "kB/s" unit_of_measurement: "kB/s"
state_class: measurement state_class: measurement
state: > availability: "{{ has_value('sensor.edgerouter_wan_upload_rate') }}"
{{ state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Sent)') | float(0) }} state: "{{ states('sensor.edgerouter_wan_upload_rate') | float(0) }}"
- name: "Internet Total Traffic" - name: "Internet Total Traffic"
unique_id: internet_totaltraffic unique_id: internet_totaltraffic
unit_of_measurement: "kB/s" unit_of_measurement: "kB/s"
state_class: measurement state_class: measurement
state: > availability: >-
{{ {{
(state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Received)') | float(0)) has_value('sensor.edgerouter_wan_download_rate')
+ (state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Sent)') | float(0)) and has_value('sensor.edgerouter_wan_upload_rate')
}}
state: >-
{{
(states('sensor.edgerouter_wan_download_rate') | float(0))
+ (states('sensor.edgerouter_wan_upload_rate') | float(0))
}} }}
+27
View File
@@ -1,3 +1,30 @@
#:########################################################################################:#
# Package: Kogan Kettle MQTT Control
# File: kogan_kettle.yaml
# Version: v1.0.0
# Date: 2022-10-11
#
# Purpose:
# Provides kettle temperature monitoring, power control, and selectable
# keep-warm temperatures through a Tasmota/TuyaMCU MQTT interface.
#
# Behaviour:
# - Reads temperature data from the kettle's TuyaReceived MQTT payload.
# - Controls kettle power through its MQTT power command topic.
# - Maps the keep-warm selector to the kettle's TuyaSend4 commands.
#
# Dependencies:
# - Home Assistant MQTT integration and broker connectivity.
# - The configured Tasmota/TuyaMCU kettle MQTT topics and payload structure.
#
# Known Limitation:
# - P013-F026 records that temperature parsing assumes a fixed nested JSON
# payload. Malformed or unrelated messages may cause template errors.
#
# Version History:
# - v1.0.0 (2022-10-11): Initial kettle monitoring and control package.
#:########################################################################################:#
mqtt: mqtt:
sensor: sensor:
- unique_id: kettle.temp - unique_id: kettle.temp
+7 -7
View File
@@ -126,7 +126,7 @@ script:
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "on" #switch.esp_loungemiddleswitch_relay_3_downlights_north: "on"
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "on" # non dimmable pair switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "on" # non dimmable pair
light.esp_breakfastbarleds_breakfast_bar_leds: 90 light.esp_breakfastbarleds_breakfast_bar_leds: 90
light.esp_loungecabinetleds2_lounge_cabinet_leds: 90 light.esp_loungecabinetleds_lounge_cabinet_leds: 90
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 90 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 90
switch.lounge_cupboard_dual_usb_xus09_l2: "on" switch.lounge_cupboard_dual_usb_xus09_l2: "on"
switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "on" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "on"
@@ -146,7 +146,7 @@ script:
light.esp_lounge6chdimmer_lounge_downlights_south: 60 light.esp_lounge6chdimmer_lounge_downlights_south: 60
light.esp_lounge6chdimmer_lounge_rafter_buttons: 40 light.esp_lounge6chdimmer_lounge_rafter_buttons: 40
light.esp_breakfastbarleds_breakfast_bar_leds: 60 light.esp_breakfastbarleds_breakfast_bar_leds: 60
light.esp_loungecabinetleds2_lounge_cabinet_leds: 60 light.esp_loungecabinetleds_lounge_cabinet_leds: 60
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 60 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 60
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair
@@ -167,7 +167,7 @@ script:
light.esp_lounge6chdimmer_lounge_downlights_south: 40 light.esp_lounge6chdimmer_lounge_downlights_south: 40
light.esp_lounge6chdimmer_lounge_rafter_buttons: 30 light.esp_lounge6chdimmer_lounge_rafter_buttons: 30
light.esp_breakfastbarleds_breakfast_bar_leds: 50 light.esp_breakfastbarleds_breakfast_bar_leds: 50
light.esp_loungecabinetleds2_lounge_cabinet_leds: 50 light.esp_loungecabinetleds_lounge_cabinet_leds: 50
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 50 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 50
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" # non-dimmable pair
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair
@@ -188,7 +188,7 @@ script:
light.esp_lounge6chdimmer_lounge_downlights_south: 30 light.esp_lounge6chdimmer_lounge_downlights_south: 30
light.esp_lounge6chdimmer_lounge_rafter_buttons: 0 light.esp_lounge6chdimmer_lounge_rafter_buttons: 0
light.esp_breakfastbarleds_breakfast_bar_leds: 40 light.esp_breakfastbarleds_breakfast_bar_leds: 40
light.esp_loungecabinetleds2_lounge_cabinet_leds: 40 light.esp_loungecabinetleds_lounge_cabinet_leds: 40
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 40 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 40
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off"
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair
@@ -228,7 +228,7 @@ script:
light.esp_lounge6chdimmer_lounge_downlights_south: 0 light.esp_lounge6chdimmer_lounge_downlights_south: 0
light.esp_lounge6chdimmer_lounge_rafter_buttons: 0 light.esp_lounge6chdimmer_lounge_rafter_buttons: 0
light.esp_breakfastbarleds_breakfast_bar_leds: 5 light.esp_breakfastbarleds_breakfast_bar_leds: 5
light.esp_loungecabinetleds2_lounge_cabinet_leds: 5 light.esp_loungecabinetleds_lounge_cabinet_leds: 5
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 5 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 5
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off"
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair
@@ -246,7 +246,7 @@ script:
light.esp_lounge6chdimmer_couch_spots_right: 20 light.esp_lounge6chdimmer_couch_spots_right: 20
light.esp_lounge6chdimmer_lounge_rafter_buttons: 15 light.esp_lounge6chdimmer_lounge_rafter_buttons: 15
light.esp_breakfastbarleds_breakfast_bar_leds: 25 light.esp_breakfastbarleds_breakfast_bar_leds: 25
light.esp_loungecabinetleds2_lounge_cabinet_leds: 30 light.esp_loungecabinetleds_lounge_cabinet_leds: 30
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 30 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 30
#light.tasmo_h801_loungeled1_6180_a: 25 #light.tasmo_h801_loungeled1_6180_a: 25
#light.tasmo_h801_loungeled1_6180_b: 25 #light.tasmo_h801_loungeled1_6180_b: 25
@@ -270,7 +270,7 @@ script:
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off" #switch.esp_loungemiddleswitch_relay_3_downlights_north: "off"
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair
light.esp_breakfastbarleds_breakfast_bar_leds: 0 light.esp_breakfastbarleds_breakfast_bar_leds: 0
light.esp_loungecabinetleds2_lounge_cabinet_leds: 0 light.esp_loungecabinetleds_lounge_cabinet_leds: 0
light.esp_loungebookshelfleds_lounge_bookshelf_leds: 0 light.esp_loungebookshelfleds_lounge_bookshelf_leds: 0
switch.lounge_cupboard_dual_usb_xus09_l2: "off" switch.lounge_cupboard_dual_usb_xus09_l2: "off"
switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off" switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off"
+71 -16
View File
@@ -1,3 +1,49 @@
#:########################################################################################:#
# Low Battery Monitoring Package #
#:########################################################################################:#
#
# TITLE:
# Low Battery Monitoring
#
# FILE:
# packages/low_battery_device_quantity.yaml
#
# VERSION:
# V1.2 2026-07-23
#
# VERSION HISTORY:
# V1.3 2026-07-24
# - Truncated list sensor state to count-only summary; full list stays in attributes.
#
# V1.2 2026-07-23
# - Moved the weekly low-battery blueprint automation into this package.
# - Preserved the Monday 21:00 schedule and Zorruno Pushover notification.
#
# V1.1 2026-07-20
# - Changed the low-battery threshold from 97% to 20%.
# - Added structured documentation for scope, exclusions, and outputs.
#
# V1.0
# - Initial low-battery count and list sensors.
#
# PURPOSE:
# Finds numeric sensor entities with device_class battery whose value is 20%
# or lower, exposes count and list sensors, and runs a weekly notification.
#
# SCOPE AND EXCLUSIONS:
# - Only entities in the sensor domain with device_class battery are checked.
# - Unknown, unavailable, non-numeric, and "Ok" states are excluded.
# - Battery entities in other domains are not included in the summaries.
# - The notification blueprint also checks battery binary sensors.
#
# OUTPUTS:
# - sensor.devices_with_low_battery reports the number of matching sensors.
# - sensor.devices_with_low_battery_list reports names and percentages.
# - The list sensor also exposes count, entities, and names attributes.
# - A Pushover notification runs each Monday at 21:00 when batteries are low.
#
#:########################################################################################:#
template: template:
- sensor: - sensor:
- name: "Devices with low battery" - name: "Devices with low battery"
@@ -9,7 +55,7 @@ template:
{% if s.attributes.device_class == 'battery' %} {% if s.attributes.device_class == 'battery' %}
{% set st = s.state %} {% set st = s.state %}
{% if st not in ['unknown', 'unavailable', 'Ok'] %} {% if st not in ['unknown', 'unavailable', 'Ok'] %}
{% if st | int(-1) != -1 and (st | int(0)) <= 97 %} {% if st | int(-1) != -1 and (st | int(0)) <= 20 %}
{% set ns.c = ns.c + 1 %} {% set ns.c = ns.c + 1 %}
{% endif %} {% endif %}
{% endif %} {% endif %}
@@ -32,21 +78,11 @@ template:
mdi:battery-alert mdi:battery-alert
{% endif %} {% endif %}
state: > state: >
{% set ns = namespace(names=[]) %} {% set count = states('sensor.devices_with_low_battery') | int(0) %}
{% for s in states.sensor %} {% if count == 0 %}
{% if s.attributes.device_class == 'battery' %}
{% set st = s.state %}
{% if st not in ['unknown', 'unavailable', 'Ok'] %}
{% if st | int(-1) != -1 and (st | int(0)) <= 97 %}
{% set ns.names = ns.names + [s.name ~ ' (' ~ (st | int(0)) ~ '%)'] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% if ns.names | length == 0 %}
None None
{% else %} {% else %}
{{ ns.names | join(', ') }} {{ count }} devices
{% endif %} {% endif %}
attributes: attributes:
count: "{{ states('sensor.devices_with_low_battery') | int(0) }}" count: "{{ states('sensor.devices_with_low_battery') | int(0) }}"
@@ -56,7 +92,7 @@ template:
{% if s.attributes.device_class == 'battery' %} {% if s.attributes.device_class == 'battery' %}
{% set st = s.state %} {% set st = s.state %}
{% if st not in ['unknown', 'unavailable', 'Ok'] %} {% if st not in ['unknown', 'unavailable', 'Ok'] %}
{% if st | int(-1) != -1 and (st | int(0)) <= 97 %} {% if st | int(-1) != -1 and (st | int(0)) <= 20 %}
{% set ns.e = ns.e + [s.entity_id] %} {% set ns.e = ns.e + [s.entity_id] %}
{% endif %} {% endif %}
{% endif %} {% endif %}
@@ -69,10 +105,29 @@ template:
{% if s.attributes.device_class == 'battery' %} {% if s.attributes.device_class == 'battery' %}
{% set st = s.state %} {% set st = s.state %}
{% if st not in ['unknown', 'unavailable', 'Ok'] %} {% if st not in ['unknown', 'unavailable', 'Ok'] %}
{% if st | int(-1) != -1 and (st | int(0)) <= 97 %} {% if st | int(-1) != -1 and (st | int(0)) <= 20 %}
{% set ns.n = ns.n + [s.name] %} {% set ns.n = ns.n + [s.name] %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{{ ns.n }} {{ ns.n }}
automation:
- id: "1635929281159"
alias: "Weekly Low battery level detection"
description: >
Checks battery-class entities each Monday and notifies Zorruno when any
are below the blueprint threshold.
use_blueprint:
path: sbyx/low-battery-level-detection-notification-for-all-battery-sensors.yaml
input:
day: 1
time: "21:00:00"
actions:
- action: notify.pushover_zorruno
data:
title: "View Road Sensors"
message: >
Weekly Battery Check. Low Devices:
{{ sensors }}
+47 -1
View File
@@ -1,7 +1,52 @@
#:########################################################################################:#
# Main Sony TV Automations Package #
#:########################################################################################:#
#
# TITLE:
# Main Sony TV Automations
#
# FILE:
# packages/main_sony_tv_automations.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation and automation descriptions.
#
# V1.0
# - Initial MQTT TV-off and surround-amplifier following automations.
#
# PURPOSE:
# Accepts an MQTT command to turn off the lounge TV and keeps the loft
# surround-amplifier power synchronized with the TV remote state.
#
# MQTT COMMAND:
# Topic:
# viewroad-commands/maintv/action
#
# Payload:
# OFF
#
# The payload is uppercase and this package does not provide an MQTT ON action.
#
# AMPLIFIER FOLLOWING:
# - remote.lounge_tv is the source-of-truth state for the amplifier.
# - The remote state must remain on or off for five seconds before action.
# - Restart mode cancels the previous wait when another state change occurs,
# preventing brief transitions from toggling amplifier power.
#
# DEPENDENCIES:
# - remote.lounge_tv controls and reports the Sony lounge TV state.
# - switch.tv_surround_amp_power_in_loft_x27pp controls amplifier power.
#
#:########################################################################################:#
automation: automation:
- id: "1669966515993" - id: "1669966515993"
alias: Turn off Lounge TV alias: Turn off Lounge TV
description: "" description: "Turns off the lounge TV when the MQTT OFF command is received."
trigger: trigger:
- platform: mqtt - platform: mqtt
topic: viewroad-commands/maintv/action topic: viewroad-commands/maintv/action
@@ -16,6 +61,7 @@ automation:
- id: tv_amp_follow_remote_state - id: tv_amp_follow_remote_state
alias: Lounge surround amp follows TV remote (5s debounce) alias: Lounge surround amp follows TV remote (5s debounce)
description: "Follows the stable lounge TV remote state after a five-second debounce."
mode: restart mode: restart
trigger: trigger:
- platform: state - platform: state
-5
View File
@@ -1,5 +0,0 @@
#media_player:
# MPD Media Player on Lounge Touchscreen
# - platform: mpd
# host: !secret lounge_mpd_player
# scan_interval: 1
-197
View File
@@ -1,197 +0,0 @@
# === Add friendly names, icons, and area for printer controls ===
homeassistant:
customize:
rest_command.3d_printer_emergency_stop:
friendly_name: "Emergency Stop (K1C)"
area_id: Laundry
icon: mdi:alert-octagon
rest_command.3d_printer_firmware_restart:
friendly_name: "Firmware Restart (K1C)"
area_id: Laundry
icon: mdi:restart
rest_command.3d_printer_cancel:
friendly_name: "Cancel Print (K1C)"
area_id: Laundry
icon: mdi:cancel
rest_command.3d_printer_pause:
friendly_name: "Pause Print (K1C)"
area_id: Laundry
icon: mdi:pause-circle
rest_command.3d_printer_resume:
friendly_name: "Resume Print (K1C)"
area_id: Laundry
icon: mdi:play-circle
sensor.printer_3d_file_metadata:
area_id: Laundry
friendly_name: "3D Printer (K1C) Print file data"
sensor.printer_3d_sensor:
area_id: Laundry
friendly_name: "3D Printer (K1C) Sensors"
sensor.printer_3d_info:
area_id: Laundry
friendly_name: "3D Printer (K1C) State"
sensor.printer_3d_server:
area_id: Laundry
friendly_name: "3D Printer (K1C) Print Server"
sensor.3d_printer_chamber_temp:
area_id: Laundry
sensor.3d_printer_hotend_target:
area_id: Laundry
sensor.3d_printer_hotend_actual:
area_id: Laundry
sensor.3d_printer_hotend_power:
area_id: Laundry
sensor.3d_printer_bed_target:
area_id: Laundry
sensor.3d_printer_bed_actual:
area_id: Laundry
sensor.3d_printer_bed_power:
area_id: Laundry
sensor.3d_printer_state:
area_id: Laundry
sensor.3d_printer_current_print:
area_id: Laundry
sensor.3d_printer_current_progress:
area_id: Laundry
sensor.3d_printer_print_time:
area_id: Laundry
sensor.3d_printer_time_remaining:
area_id: Laundry
sensor.3d_printer_eta:
area_id: Laundry
sensor.3d_printer_message:
area_id: Laundry
sensor.3d_printer_layer_height:
area_id: Laundry
sensor.3d_printer_object_height:
area_id: Laundry
sensor.3d_printer_current_height:
area_id: Laundry
sensor.3d_printer_current_layer:
area_id: Laundry
sensor.3d_printer_total_layers:
area_id: Laundry
sensor.3d_printer_actual_layer:
area_id: Laundry
sensor.3d_printer_object_thumbnails:
area_id: Laundry
sensor.3d_printer_state_message:
area_id: Laundry
sensor.3d_printer_heaters_power:
area_id: Laundry
sensor.3d_printer_printername:
area_id: Laundry
binary_sensor.3d_printer_filament:
area_id: Laundry
# === REST sensor: File metadata for current 3D print ===
rest:
scan_interval: 5
resource_template: "http://192.168.2.31:7125/server/files/metadata?filename={{ states('sensor.3d_printer_current_print') | urlencode }}"
sensor:
- name: printer_3d_file_metadata
unique_id: "192.168.2.315ec44d90-419c-419c-802d-d34071639c08"
icon: mdi:file-document-outline
json_attributes_path: "$.result"
json_attributes:
- layer_height
- object_height
- thumbnails
value_template: "OK"
rest_command:
3d_printer_emergency_stop:
url: "http://192.168.2.31:7125/printer/emergency_stop"
method: post
3d_printer_firmware_restart:
url: "http://192.168.2.31:7125/printer/firmware_restart"
method: post
3d_printer_cancel:
url: "http://192.168.2.31:7125/printer/print/cancel"
method: post
3d_printer_pause:
url: "http://192.168.2.31:7125/printer/print/pause"
method: post
3d_printer_resume:
url: "http://192.168.2.31:7125/printer/print/resume"
method: post
sensor:
# === REST sensor: Full 3D printer status (Klipper printer/objects/query) ===
- platform: rest
name: printer_3d_sensor
unique_id: "192.168.2.313a0c25fa-297d-4c19-b03c-ddf04840682b"
icon: mdi:printer-3d-nozzle
resource: "http://192.168.2.31:7125/printer/objects/query?heater_bed&extruder&print_stats&toolhead&display_status&virtual_sdcard&gcode_move&filament_motion_sensor%20btt_smart_filament&temperature_sensor%20Chamber_Temp"
json_attributes_path: "$.result.status"
json_attributes:
- heater_bed
- extruder
- print_stats
- toolhead
- display_status
- virtual_sdcard
- gcode_move
- "filament_motion_sensor btt_smart_filament_sensor"
- "temperature_sensor Chamber_Temp"
value_template: "OK"
force_update: true
scan_interval: 1
# === REST sensor: Basic printer state and message ===
- platform: rest
name: printer_3d_info
unique_id: "192.168.2.311cba6677-02bd-4273-9083-b8301bf6943b"
icon: mdi:information-outline
scan_interval: 1
resource_template: "http://192.168.2.31:7125/printer/info"
json_attributes_path: "$.result"
json_attributes:
- state_message
- state
value_template: "OK"
# === REST sensor: Server configuration details (e.g., printer name) ===
- platform: rest
name: printer_3d_server
unique_id: "192.168.2.311cba6677-02bd-4273-9083-b8301bf6949"
icon: mdi:server
scan_interval: 1
resource_template: "http://192.168.2.31:7125/server/database/item?namespace=mainsail&key=general"
json_attributes_path: "$.result.value"
json_attributes:
- printername
value_template: "OK"
+22
View File
@@ -1,3 +1,25 @@
#:########################################################################################:#
# Package: NIWA Local Tides
# File: niwa_tides.yaml
# Version: v1.0.0
# Date: 2022-10-12
#
# Purpose:
# Provides local tide level and forecast information from NIWA for use in Home
# Assistant dashboards, automations, and templates.
#
# Output:
# - sensor.local_tides reports the current tide level and exposes upcoming high
# and low tide times, levels, phase, and percentage as attributes.
#
# Dependencies:
# - NIWA Tides custom integration (platform: niwa_tides).
# - NIWA API key and location coordinates stored in secrets.yaml.
#
# Version History:
# - v1.0.0 (2022-10-12): Initial local NIWA tide sensor package.
#:########################################################################################:#
sensor: sensor:
- platform: niwa_tides - platform: niwa_tides
api_key: !secret niwa_tides_api_key api_key: !secret niwa_tides_api_key
@@ -1,52 +0,0 @@
automation:
- id: nspanel_lounge_controller
alias: "NSPanel Master Bedroom Controller"
description: ""
use_blueprint:
path: edwardtfn/nspanel_easy_blueprint.yaml
input:
nspanel_name: 084fb5b4a94e636d01244b1838156446
button_page01_label: Lounge Lighting Scenes
button_pages_icon_size: "10"
entity01: script.nspanel_lounge_scene_bright
entity01_name: "Bright"
entity01_icon: mdi:brightness-7
entity01_confirm: false
entity02: script.nspanel_lounge_scene_tv_general
entity02_name: "TV General"
entity02_icon: mdi:television
entity02_confirm: false
entity03: script.nspanel_lounge_scene_movie
entity03_name: "Movie"
entity03_icon: mdi:movie-open
entity03_confirm: false
entity04: script.nspanel_lounge_scene_balanced
entity04_name: "Balance"
entity04_icon: mdi:scale-balance
entity04_confirm: false
entity05: script.nspanel_lounge_scene_warm_cozy
entity05_name: "Warm Cozy"
entity05_icon: mdi:fireplace
entity05_confirm: false
entity06: script.nspanel_lounge_scene_reading
entity06_name: "Reading On/Off"
entity06_icon: mdi:book-open-page-variant
entity06_confirm: false
entity07: script.nspanel_lounge_scene_dining
entity07_name: "Dining On/Off"
entity07_icon: mdi:silverware-fork-knife
entity07_confirm: false
entity08: script.nspanel_lounge_scene_all_off
entity08_name: "All Off"
entity08_icon: mdi:power
entity08_confirm: false
mode: single
+56 -3
View File
@@ -1,3 +1,49 @@
#:########################################################################################:#
# Office Media Buttons Package #
#:########################################################################################:#
#
# TITLE:
# Office Media Buttons
#
# FILE:
# packages/office_media_buttons.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation and automation descriptions.
# - Identified the active ZHA implementation and disabled alternatives.
#
# V1.0
# - Initial office media-button automations.
#
# PURPOSE:
# Controls office TV playback from a Zigbee media button using direct ZHA
# click events.
#
# ACTIVE BUTTON MAPPING:
# - Single press pauses playback.
# - Double press starts or resumes playback.
# - Triple press seeks backward by 30 seconds.
#
# DEPENDENCIES:
# - ZHA event source: device IEEE 00:15:8d:00:06:79:46:c8.
# - media_player.office_tv provides the current playback position.
# - The media actions target the office TV device registered in Home Assistant.
#
# NOTES:
# - seek_amount is not normally provided by the event and defaults to -30.
# - Commented MQTT and entity-state implementations below are disabled
# historical alternatives retained for reference.
#
#:########################################################################################:#
#:########################################################################################:#
# Historical Disabled MQTT Sensor #
#:########################################################################################:#
#mqtt: #mqtt:
# sensor: # sensor:
# - unique_id: office_media_button_1 # - unique_id: office_media_button_1
@@ -6,10 +52,14 @@
# unit_of_measurement: "Press" # unit_of_measurement: "Press"
# value_template: "{{ value_json.ZbReceived.Office_Media_Button.Power }}" # value_template: "{{ value_json.ZbReceived.Office_Media_Button.Power }}"
#:########################################################################################:#
# Active ZHA Automations #
#:########################################################################################:#
automation: automation:
- id: pause_office_tv_on_media_button - id: pause_office_tv_on_media_button
alias: Pause Office TV on Media Button alias: Pause Office TV on Media Button
description: "" description: "Pauses the office TV when the media button is pressed once."
trigger: trigger:
- platform: event - platform: event
event_type: zha_event event_type: zha_event
@@ -26,7 +76,7 @@ automation:
mode: single mode: single
- id: play_office_tv_on_media_button - id: play_office_tv_on_media_button
alias: Play Office TV on Media Button alias: Play Office TV on Media Button
description: "" description: "Starts or resumes office TV playback when the media button is pressed twice."
trigger: trigger:
- platform: event - platform: event
event_type: zha_event event_type: zha_event
@@ -43,7 +93,7 @@ automation:
mode: single mode: single
- id: seek_office_tv_on_media_button - id: seek_office_tv_on_media_button
alias: Seek Office TV on Media Button alias: Seek Office TV on Media Button
description: "" description: "Seeks office TV playback backward 30 seconds when the media button is pressed three times."
trigger: trigger:
- platform: event - platform: event
event_type: zha_event event_type: zha_event
@@ -62,6 +112,8 @@ automation:
target: target:
device_id: abb6b5a6b4e4925dcb3a77ea2c293eaa device_id: abb6b5a6b4e4925dcb3a77ea2c293eaa
mode: single mode: single
# Historical disabled MQTT and entity-state alternatives.
# - id: "1629271273958" # - id: "1629271273958"
# alias: Pause office TV on MQTT # alias: Pause office TV on MQTT
# description: "" # description: ""
@@ -90,6 +142,7 @@ automation:
# seek_position: 30 # seek_position: 30
# mode: single # mode: single
# Historical disabled entity-state automation block.
#automation: #automation:
# - id: "1629271273952" # - id: "1629271273952"
# unique_id: pause_office_tv_on_media_button # unique_id: pause_office_tv_on_media_button
+277
View File
@@ -0,0 +1,277 @@
#:########################################################################################:#
# Open-Meteo Rain Chance Package #
#:########################################################################################:#
#
# TITLE:
# Open-Meteo Rain Chance
#
# FILE:
# open_meteo_rain_chance.yaml
#
# VERSION:
# V1.2 2026-07-19
# - Changed today's forecast period to use remaining daylight hours.
# - Before sunrise, today's forecast covers sunrise through sunset.
# - During daylight, today's forecast covers now through sunset.
# - At sunset or later, today's forecast covers now through midnight.
# - Tomorrow continues to cover daylight hours only.
#
# V1.1 2026-07-19
# - Changed today's prediction to use only the remaining forecast hours
# from the current local hour until midnight.
# - Changed tomorrow's prediction to cover daylight hours only.
# - Added hourly precipitation probability data.
# - Added today's and tomorrow's sunrise and sunset data.
#
# V1.0 2026-07-18
# - Initial version.
# - Used the full-day maximum precipitation probability for today and
# tomorrow.
#
# PURPOSE:
# Creates dedicated Home Assistant sensors for the maximum hourly
# probability of precipitation during useful forecast periods today and
# tomorrow.
#
# SOURCE:
# Open-Meteo Forecast API
#
# LOCATION:
# Uses the latitude and longitude attributes of zone.home.
#
# OUTPUT ENTITIES:
# sensor.open_meteo_rain_chance_today
# sensor.open_meteo_rain_chance_tomorrow
#
# FORECAST PERIODS:
# Today before sunrise:
# Sunrise through sunset.
#
# Today during daylight:
# Current time through sunset.
#
# Today at or after sunset:
# Current time through midnight.
#
# Tomorrow:
# Sunrise through sunset.
#
# UPDATE RATE:
# Every 30 minutes.
#
# NOTES:
# - No API key is required.
# - timezone=auto makes Open-Meteo return forecast times in the local
# timezone determined from the zone.home coordinates.
# - Each Open-Meteo hourly precipitation probability applies to the
# preceding hour.
# - Hourly forecast periods that partly overlap sunrise or sunset are
# included.
# - The displayed result is the highest hourly probability within the
# applicable period.
# - The result is not a separately calculated probability of receiving
# rain at any point across the whole selected period.
# - Sensor values and forecast-period changes are recalculated whenever
# the REST request updates.
#
#:########################################################################################:#
rest:
- resource_template: >-
https://api.open-meteo.com/v1/forecast?latitude={{ state_attr('zone.home', 'latitude') }}&longitude={{ state_attr('zone.home', 'longitude') }}&hourly=precipitation_probability&daily=sunrise,sunset&timezone=auto&forecast_days=2
method: GET
scan_interval: 1800
timeout: 20
sensor:
#:###################################################################################:#
# Rain Chance Today #
#:###################################################################################:#
#
# Before sunrise:
# Uses today's sunrise through sunset.
#
# During daylight:
# Uses the current time through today's sunset.
#
# At or after sunset:
# Uses the current time through midnight.
#
- name: "Open Meteo Rain Chance Today"
unique_id: open_meteo_rain_chance_today
unit_of_measurement: "%"
state_class: measurement
icon: mdi:weather-rainy
value_template: >-
{% set current_timestamp =
as_timestamp(now())
%}
{% set sunrise_timestamp =
as_timestamp(
value_json.daily.sunrise[0],
0
)
%}
{% set sunset_timestamp =
as_timestamp(
value_json.daily.sunset[0],
0
)
%}
{% set tomorrow_midnight_timestamp =
as_timestamp(
value_json.daily.time[1] ~ 'T00:00',
0
)
%}
{% if current_timestamp < sunrise_timestamp %}
{% set period_start_timestamp =
sunrise_timestamp
%}
{% set period_end_timestamp =
sunset_timestamp
%}
{% elif current_timestamp < sunset_timestamp %}
{% set period_start_timestamp =
current_timestamp
%}
{% set period_end_timestamp =
sunset_timestamp
%}
{% else %}
{% set period_start_timestamp =
current_timestamp
%}
{% set period_end_timestamp =
tomorrow_midnight_timestamp
%}
{% endif %}
{% set ns = namespace(
maximum=0,
found=false
) %}
{% for index in range(
value_json.hourly.time | count
) %}
{% set forecast_end_timestamp =
as_timestamp(
value_json.hourly.time[index],
0
)
%}
{% set probability =
value_json.hourly.precipitation_probability[index]
%}
{% if
forecast_end_timestamp > period_start_timestamp
and forecast_end_timestamp < period_end_timestamp + 3600
and probability is not none
%}
{% set ns.found = true %}
{% if probability | int(0) > ns.maximum %}
{% set ns.maximum =
probability | int(0)
%}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.maximum if ns.found else 0 }}
#:###################################################################################:#
# Rain Chance Tomorrow — Daylight #
#:###################################################################################:#
#
# Uses hourly forecast periods that overlap tomorrow's daylight
# period between sunrise and sunset.
#
- name: "Open Meteo Rain Chance Tomorrow"
unique_id: open_meteo_rain_chance_tomorrow
unit_of_measurement: "%"
state_class: measurement
icon: mdi:weather-sunset-up
value_template: >-
{% set sunrise_timestamp =
as_timestamp(
value_json.daily.sunrise[1],
0
)
%}
{% set sunset_timestamp =
as_timestamp(
value_json.daily.sunset[1],
0
)
%}
{% set ns = namespace(
maximum=0,
found=false
) %}
{% for index in range(
value_json.hourly.time | count
) %}
{% set forecast_end_timestamp =
as_timestamp(
value_json.hourly.time[index],
0
)
%}
{% set probability =
value_json.hourly.precipitation_probability[index]
%}
{% if
forecast_end_timestamp > sunrise_timestamp
and forecast_end_timestamp < sunset_timestamp + 3600
and probability is not none
%}
{% set ns.found = true %}
{% if probability | int(0) > ns.maximum %}
{% set ns.maximum =
probability | int(0)
%}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.maximum if ns.found else 0 }}
+5 -2
View File
@@ -3,6 +3,9 @@
############################################################################### ###############################################################################
# #
# Version: # Version:
# 2026-07-20 v0.3
# - Synchronized the documented Number 16 schedule with the implementation.
#
# 2026-05-09 v0.2 # 2026-05-09 v0.2
# - Refactored scheduled control to use editable schedule lists. # - Refactored scheduled control to use editable schedule lists.
# - Switched lights now use explicit ON/OFF schedule entries instead of # - Switched lights now use explicit ON/OFF schedule entries instead of
@@ -527,8 +530,8 @@ script:
# EDIT SCHEDULE HERE # EDIT SCHEDULE HERE
# Format: trigger, offset_minutes/at, target_brightness, ramp_minutes # Format: trigger, offset_minutes/at, target_brightness, ramp_minutes
# Current schedule: # Current schedule:
# - sunset -30, 80, 30 # - sunset -30, 60, 30
# - 20:00, 50, 30 # - 20:00, 50, 120
# - 01:00, 20, 120 # - 01:00, 20, 120
# - 06:00, 0, 120 # - 06:00, 0, 120
########################################################################### ###########################################################################
+66
View File
@@ -1,3 +1,28 @@
#:########################################################################################:#
# Package: Pet Feeding and Jobs Timing
# File: pet_feeding_and_jobs.yaml
# Version: v1.1.0
# Date: 2026-07-21
#
# Purpose:
# Provides legacy MQTT pet activity sensors and Jobs-derived timing sensors for
# the Lounge dog-feeding and depooping gauges.
#
# Behaviour:
# - Dog feeding reports the newest valid morning or evening completion time.
# - Dog depooping reports the weekly Jobs completion time.
# - Invalid or unrestored Jobs attributes make a timing sensor unavailable
# instead of exposing a numeric sentinel value.
#
# Version History:
# - v1.1.0 (2026-07-21): Reject invalid Jobs timing attributes and remove the
# 999999-hour fallback from the dog-feeding gauge source.
# - v1.0.0 (2026-07-21): Added Jobs-derived dog timing sensors for P010.
#:########################################################################################:#
#:######################################################################################:#
# LEGACY MQTT PET ACTIVITY AND TIMING SENSORS
#:######################################################################################:#
mqtt: mqtt:
sensor: sensor:
- unique_id: dog_fed_activity - unique_id: dog_fed_activity
@@ -24,3 +49,44 @@ mqtt:
name: "Hours Since DePooped" name: "Hours Since DePooped"
state_topic: "viewroad-tele/petfeeding/hoursSinceDePooped" state_topic: "viewroad-tele/petfeeding/hoursSinceDePooped"
unit_of_measurement: "Hours" unit_of_measurement: "Hours"
#:######################################################################################:#
# JOBS-DERIVED LOUNGE GAUGE SENSORS
#:######################################################################################:#
template:
- sensor:
- name: "Jobs Tracker Hours Since Dog Fed"
unique_id: "jobs_tracker_hours_since_dog_fed"
device_class: duration
state_class: measurement
unit_of_measurement: "h"
icon: "mdi:dog-side"
availability: >-
{% set morning = state_attr('sensor.jobs_tracker_dog_feeding_morning', 'hours_since_completed') %}
{% set evening = state_attr('sensor.jobs_tracker_dog_feeding_evening', 'hours_since_completed') %}
{{ is_number(morning) or is_number(evening) }}
state: >-
{% set morning = state_attr('sensor.jobs_tracker_dog_feeding_morning', 'hours_since_completed') %}
{% set evening = state_attr('sensor.jobs_tracker_dog_feeding_evening', 'hours_since_completed') %}
{% if is_number(morning) and is_number(evening) %}
{{ [morning | float(0), evening | float(0)] | min | round(0) }}
{% elif is_number(morning) %}
{{ morning | float(0) | round(0) }}
{% elif is_number(evening) %}
{{ evening | float(0) | round(0) }}
{% else %}
{{ none }}
{% endif %}
- name: "Jobs Tracker Hours Since Dog Depooped"
unique_id: "jobs_tracker_hours_since_dog_depooped"
device_class: duration
state_class: measurement
unit_of_measurement: "h"
icon: "mdi:emoticon-poop"
availability: >-
{% set hours = state_attr('sensor.jobs_tracker_dog_depooping', 'hours_since_completed') %}
{{ is_number(hours) }}
state: >-
{% set hours = state_attr('sensor.jobs_tracker_dog_depooping', 'hours_since_completed') %}
{{ hours | float(0) if is_number(hours) else none }}
-45
View File
@@ -1,45 +0,0 @@
mqtt:
sensor:
- unique_id: piano.lastfinish
name: "Last Piano Practice Complete"
state_topic: "viewroad-status/activityfeed/pianopractice"
- unique_id: piano.lastpracticeduration
state_topic: "viewroad-status/piano/lastPracticeDuration"
name: "Last Piano Practice Time"
unit_of_measurement: "mins"
- unique_id: piano.todayspracticeduration
state_topic: "viewroad-status/piano/todayPracticeDuration"
name: "Today's Piano Practice Time"
unit_of_measurement: "mins"
- unique_id: piano.thisweekspracticeduration
state_topic: "viewroad-status/piano/thisWeekPracticeDuration"
name: "This Week's Piano Practice Time"
unit_of_measurement: "mins"
automation:
- id: "1648705777863"
alias: Tag Started Piano Practice is Scanned
description: ""
trigger:
- platform: tag
tag_id: af3f595c-5d41-4bb6-8985-27c70b0e6217
condition: []
action:
- service: mqtt.publish
data:
topic: viewroad-commands/chores/piano_practice
payload: start
mode: single
- id: "Tag Finished Piano Practice is Scanned"
alias: Tag Finished Piano Practice is Scanned
description: ""
trigger:
- platform: tag
tag_id: 6e7a6ab5-c7e4-47f3-be8f-830241175d69
condition: []
action:
- service: mqtt.publish
data:
topic: viewroad-commands/chores/piano_practice
payload: end
mode: single
+19 -15
View File
@@ -5,7 +5,17 @@
# TITLE: # TITLE:
# Quiet Time # Quiet Time
# #
# VERSIONS: # FILE:
# quiet_time.yaml
#
# VERSION:
# V1.4 2026-07-20
#
# VERSION HISTORY:
# V1.4 2026-07-20
# - Updated documentation for the standalone Quiet Time actions package.
# - Documented the current flattened MQTT status topic.
#
# V1.3 2026-06-07 # V1.3 2026-06-07
# - Added quiet time start and stop entity action lists. # - Added quiet time start and stop entity action lists.
# - Turns the LED matrix display OFF when quiet time starts. # - Turns the LED matrix display OFF when quiet time starts.
@@ -13,7 +23,8 @@
# #
# V1.2 2026-05-21 # V1.2 2026-05-21
# - Added MQTT publishing when input_boolean.quiet_time changes. # - Added MQTT publishing when input_boolean.quiet_time changes.
# - Publishes quiet_time state to viewroad-status/homeassistant/quiet-time. # - Published quiet_time state to viewroad-status/homeassistant/quiet-time.
# This was the historical topic; the current topic is documented below.
# #
# V1.1 2026-05-21 # V1.1 2026-05-21
# - Reduced daily reset check frequency from every minute to every 30 minutes. # - Reduced daily reset check frequency from every minute to every 30 minutes.
@@ -33,20 +44,13 @@
# will automatically be turned OFF. # will automatically be turned OFF.
# - Default reset time is 07:00. # - Default reset time is 07:00.
# - The quiet_time state is published to MQTT whenever it changes. # - The quiet_time state is published to MQTT whenever it changes.
# - When quiet_time changes, editable entity lists can turn things on or off. # - The companion package handles local actions when quiet_time changes.
# #
# QUIET TIME ACTION LISTS: # COMPANION PACKAGE:
# - quiet_time_start_turn_off_entities: # - quiet_time_on_off_actions.yaml owns the editable entity action lists and
# Entities to turn OFF when quiet time starts. # other local actions that run when Quiet Time starts or ends.
# # - This package owns input_boolean.quiet_time, the daily reset behaviour,
# - quiet_time_start_turn_on_entities: # and MQTT status publishing.
# Entities to turn ON when quiet time starts.
#
# - quiet_time_stop_turn_off_entities:
# Entities to turn OFF when quiet time ends.
#
# - quiet_time_stop_turn_on_entities:
# Entities to turn ON when quiet time ends.
# #
# MQTT STATUS: # MQTT STATUS:
# - Topic: # - Topic:
+64
View File
@@ -1,3 +1,67 @@
#:########################################################################################:#
# Raspberry Pi CCTV Monitor Package #
#:########################################################################################:#
#
# TITLE:
# Raspberry Pi CCTV Monitor
#
# FILE:
# packages/raspberry_pi_monitor.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation for MQTT, availability, and controls.
#
# V1.0
# - Initial monitoring and remote-control configuration.
#
# PURPOSE:
# Monitors the cctvquad1 and cctvquad2 Raspberry Pi hosts and provides remote
# reboot and shutdown controls through MQTT.
#
# EXTERNAL DEPENDENCY:
# - Each Raspberry Pi requires an external process that publishes telemetry
# and state, consumes command topics, and performs the requested actions.
# - This package does not install or manage that external process.
#
# MQTT STATUS:
# Topic pattern:
# viewroad-status/rpis/<host>/<field>
#
# Fields:
# cpu-temp, cpu-use, ram-use, disk-use, uptime, last-seen, ipv4-address,
# reboot, and shutdown.
#
# The last-seen payload must be a timestamp that Home Assistant can parse with
# as_timestamp().
#
# MQTT COMMANDS:
# Topic pattern:
# viewroad-commands/rpis/<host>/reboot/set
# viewroad-commands/rpis/<host>/shutdown/set
#
# Commands use the default MQTT switch ON/OFF payloads, QoS 1, and are not
# retained. The external process must publish matching switch state feedback.
#
# AVAILABILITY:
# - A host is considered available when its last-seen timestamp is valid and
# no more than 180 seconds old.
# - The template uses now(), so Home Assistant periodically re-evaluates age.
#
# GROUPS:
# - group.pi_cctvquad1_on collects Quad1 telemetry and controls.
# - group.pi_cctvquad2_on collects Quad2 telemetry and controls.
#
# SAFETY:
# - Reboot and shutdown interrupt CCTV services and host availability.
# - Shutdown may require physical access to power the host back on.
# - Dashboard controls should require confirmation before sending commands.
#
#:########################################################################################:#
mqtt: mqtt:
sensor: sensor:
- unique_id: cctvquad1_cpu_temp - unique_id: cctvquad1_cpu_temp
+31 -1
View File
@@ -1,2 +1,32 @@
# enable the recorder integration (optional) #:########################################################################################:#
# Package: Recorder Configuration
# File: recorder.yaml
# Version: v1.1.0
# Date: 2026-07-23
#
# Purpose:
# Enables Home Assistant Recorder with targeted exclusions for high-frequency
# diagnostic and summary entities that do not require historical storage.
#
# Exclusions:
# - ESPHome uptime sensors.
# - Home Assistant summary v2 sensors.
# - Jobs Tracker summary sensor.
#
# Notes:
# - Excluded entities remain available for current-state use.
# - Recorder retention and database settings continue to use Home Assistant
# defaults unless configured elsewhere.
#
# Version History:
# - v1.1.0 (2026-07-23): Added targeted exclusions during P013-F010 cleanup.
# - v1.0.0 (2022-10-11): Initial Recorder package.
#:########################################################################################:#
recorder: recorder:
exclude:
entity_globs:
- sensor.esp_*_uptime*
- sensor.ha_summary_v2_*
entities:
- sensor.jobs_tracker_summary
+30
View File
@@ -1,3 +1,33 @@
#:########################################################################################:#
# Package: RobotDyn MQTT Dimmer
# File: robotdyn_dimmers.yaml
# Version: v1.1.0
# Date: 2026-02-05
#
# Purpose:
# Provides a brightness-only Home Assistant light entity for the lounge
# RobotDyn dimmer through its JSON MQTT interface.
#
# Behaviour:
# - Reads JSON state updates from the dimmer state topic.
# - Publishes power and brightness commands to the dimmer command topic.
# - Uses a separate MQTT availability topic for online/offline status.
#
# Dependency:
# - Home Assistant MQTT integration and the external RobotDyn MQTT publisher.
#
# Known Limitations:
# - P013-F027 records that command messages are retained and may replay after
# reconnecting.
# - The configured availability topic contains "availablity"; confirm whether
# that spelling matches the publisher before correcting it.
#
# Version History:
# - v1.1.0 (2026-02-05): Added the current name, default entity ID, explicit
# availability payloads, and block-style color modes.
# - v1.0.0 (2025-09-10): Initial lounge MQTT dimmer package.
#:########################################################################################:#
mqtt: mqtt:
- light: - light:
schema: json schema: json
+58 -7
View File
@@ -1,9 +1,55 @@
########################################################################################## ##########################################################################################
# MASTER BEDROOM FAN (Tasmota / Sonoff iFan02) # Room Fan Control
# Improvements: ##########################################################################################
# - Numeric speed entity (0-3) reflecting actual device state via MQTT #
# - Button to cycle speed 1,2,3,0 # TITLE:
# - Keep input_select in sync with actual FanSpeed (incl after reboots) # Room Fan Control
#
# FILE:
# packages/room_fan_control.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation for all three bedroom fans.
# - Documented the master-bedroom feedback loop and command-only controls.
#
# V1.0
# - Initial Tasmota and Sonoff iFan02 fan controls.
#
# PURPOSE:
# Controls the master-bedroom, Bedroom 2, and Bedroom 3 ceiling-fan speeds
# through Tasmota MQTT commands and Home Assistant dropdown helpers.
#
# SPEED MAPPING:
# - 0 = Off
# - 1 = Low
# - 2 = Medium
# - 3 = High
#
# MASTER BEDROOM:
# - Queries FanSpeed when Home Assistant starts.
# - Listens to Tasmota RESULT and STATE topics for device feedback.
# - Synchronizes the numeric and dropdown helpers with actual device state.
# - Uses optimistic local updates until MQTT feedback corrects the helpers.
# - Provides a button that cycles through speeds 1, 2, 3, and 0.
#
# BEDROOMS 2 AND 3:
# - Dropdown changes publish FanSpeed commands to each Tasmota device.
# - These controls are command-only and do not synchronize from feedback.
# - Their displayed dropdown state may not reflect physical or external changes.
#
# MQTT COMMAND TOPICS:
# - Master bedroom: cmnd/tasmo-ifan02-3793-bedrm1-1/FanSpeed
# - Bedroom 2: cmnd/tasmo-ifan02-7042-bedrm2/FanSpeed
# - Bedroom 3: cmnd/tasmo-ifan02-3497-bedrm3/FanSpeed
#
# DEPENDENCIES:
# - The three Tasmota devices must accept FanSpeed values from 0 through 3.
# - Master-bedroom feedback uses its RESULT and STATE MQTT topics.
#
########################################################################################## ##########################################################################################
input_select: input_select:
@@ -75,6 +121,7 @@ automation:
- id: master_bedroom_fan_query_on_ha_start - id: master_bedroom_fan_query_on_ha_start
initial_state: true initial_state: true
alias: Master Bedroom Fan - Query FanSpeed on HA Start alias: Master Bedroom Fan - Query FanSpeed on HA Start
description: "Requests the current master-bedroom FanSpeed when Home Assistant starts."
trigger: trigger:
- platform: homeassistant - platform: homeassistant
event: start event: start
@@ -90,6 +137,7 @@ automation:
- id: master_bedroom_fan_sync_from_mqtt - id: master_bedroom_fan_sync_from_mqtt
initial_state: true initial_state: true
alias: Master Bedroom Fan - Sync Helpers From MQTT FanSpeed alias: Master Bedroom Fan - Sync Helpers From MQTT FanSpeed
description: "Synchronizes the master-bedroom fan helpers from Tasmota MQTT feedback."
trigger: trigger:
- platform: mqtt - platform: mqtt
topic: stat/tasmo-ifan02-3793-bedrm1-1/RESULT topic: stat/tasmo-ifan02-3793-bedrm1-1/RESULT
@@ -122,6 +170,7 @@ automation:
- id: set_the_master_bedroom_fan - id: set_the_master_bedroom_fan
initial_state: true initial_state: true
alias: Set the Master Bedroom Fan alias: Set the Master Bedroom Fan
description: "Publishes master-bedroom fan speed changes and updates the numeric helper optimistically."
trigger: trigger:
- platform: state - platform: state
entity_id: input_select.master_bedroom_fan_set entity_id: input_select.master_bedroom_fan_set
@@ -145,11 +194,12 @@ automation:
value: "{{ desired }}" value: "{{ desired }}"
######################################################################################## ########################################################################################
# BEDROOM 2 FAN - Unchanged # BEDROOM 2 FAN - Command Only
######################################################################################## ########################################################################################
- id: set_bedroom_2_fan - id: set_bedroom_2_fan
initial_state: true initial_state: true
alias: Set Bedroom 2 Fan Speed alias: Set Bedroom 2 Fan Speed
description: "Publishes Bedroom 2 fan speed dropdown changes to Tasmota."
trigger: trigger:
entity_id: input_select.bedroom_2_fan_set entity_id: input_select.bedroom_2_fan_set
platform: state platform: state
@@ -163,11 +213,12 @@ automation:
' '
######################################################################################## ########################################################################################
# BEDROOM 3 FAN - Unchanged # BEDROOM 3 FAN - Command Only
######################################################################################## ########################################################################################
- id: set_bedroom_3_fan - id: set_bedroom_3_fan
initial_state: true initial_state: true
alias: Set Bedroom 3 Fan Speed alias: Set Bedroom 3 Fan Speed
description: "Publishes Bedroom 3 fan speed dropdown changes to Tasmota."
trigger: trigger:
entity_id: input_select.bedroom_3_fan_set entity_id: input_select.bedroom_3_fan_set
platform: state platform: state
+28 -3
View File
@@ -1,7 +1,32 @@
#:########################################################################################:#
# Package: Security and Notification Helpers
# File: security_and_notifications.yaml
# Version: v1.1.0
# Date: 2026-07-21
#
# Purpose:
# Provides the user-controlled helper that enables away-occupancy security
# behavior such as the household lighting simulation.
#
# Behaviour:
# - input_boolean.away_occupied_routine enables eligible away-mode automations.
# - The current consumer is the away-light simulation package.
#
# Dependencies:
# - packages/simulation_lights.yaml
# - packages/quiet_time.yaml owns the canonical quiet-time helper.
#
# Version History:
# - v1.1.0 (2026-07-21): Disabled the duplicate quiet-time helper after moving
# canonical ownership to the dedicated quiet-time package.
# - v1.0.0 (2024-05-27): Added quiet-time and away-security helpers.
#:########################################################################################:#
input_boolean: input_boolean:
quiet_time: # Legacy duplicate retained temporarily for verification.
name: Quiet time for no notifications # quiet_time:
icon: mdi:shield-moon # name: Quiet time for no notifications
# icon: mdi:shield-moon
away_occupied_routine: away_occupied_routine:
name: Automation for Lights etc when away name: Automation for Lights etc when away
icon: mdi:shield-lock icon: mdi:shield-lock
+51
View File
@@ -1,3 +1,52 @@
#:########################################################################################:#
# Main Bathroom Shower Detection Package #
#:########################################################################################:#
#
# TITLE:
# Main Bathroom Shower Detection
#
# FILE:
# packages/shower_controls.yaml
#
# VERSION:
# V1.1 2026-07-20
#
# VERSION HISTORY:
# V1.1 2026-07-20
# - Added structured documentation and automation descriptions.
# - Documented current indicator-only usage.
#
# V1.0
# - Initial humidity-rate shower detection.
#
# PURPOSE:
# Detects shower use from the main-bathroom humidity level and its rate of
# change, then maintains input_boolean.main_bathroom_shower as an indicator.
#
# DEPENDENCY:
# sensor.main_bathroom_environment_zth10_humidity_2 provides humidity in %.
#
# DERIVATIVE SENSOR:
# - Calculates humidity change in % per minute over a 15-minute time window.
# - Rounds the result to 0.1.
#
# DETECTION:
# - Humidity change must remain above 0.6 %/min for five minutes.
# - Current humidity must also be above 63%.
#
# CLEARING:
# - Humidity change must remain below 0.2 %/min for 15 minutes.
# - No absolute-humidity condition is required to clear the indicator.
# - The gap between activation and clearing thresholds provides hysteresis
# and prevents rapid toggling near one threshold.
#
# CURRENT USE:
# - The package only maintains input_boolean.main_bathroom_shower.
# - No other user-YAML automation, script, or package currently consumes it.
# - This package does not directly control a bathroom fan or other device.
#
#:########################################################################################:#
input_boolean: input_boolean:
main_bathroom_shower: main_bathroom_shower:
name: Main Bathroom Shower name: Main Bathroom Shower
@@ -14,6 +63,7 @@ sensor:
automation: automation:
- id: main_bathroom_shower_detected - id: main_bathroom_shower_detected
alias: Main Bathroom - Shower detected (humidity rate + min humidity) alias: Main Bathroom - Shower detected (humidity rate + min humidity)
description: "Turns on the shower indicator after a sustained humidity rise above the minimum humidity."
mode: single mode: single
trigger: trigger:
- platform: numeric_state - platform: numeric_state
@@ -31,6 +81,7 @@ automation:
- id: main_bathroom_shower_cleared - id: main_bathroom_shower_cleared
alias: Main Bathroom - Shower cleared (humidity rate) alias: Main Bathroom - Shower cleared (humidity rate)
description: "Turns off the shower indicator after the humidity change rate remains low for 15 minutes."
mode: single mode: single
trigger: trigger:
- platform: numeric_state - platform: numeric_state
+93 -3
View File
@@ -1,8 +1,39 @@
#:########################################################################################:#
# Package: Away-Light Security Simulation
# File: simulation_lights.yaml
# Version: v1.0.0
# Date: 2026-07-24
#
# Purpose:
# Simulates household activity while the away routine is active and all
# configured family members are away. Every five minutes after sunset, the
# automation selects a configured entity and operates it for a random period.
#
# Version History:
# - v1.0.0 (2026-07-24): Consolidated the simulation groups, away automation, and timed helper
# script into one feature package.
# - Preserved the existing automation ID, group keys, and script key.
# - Aligned the automation with the existing simulation_lights_all group.
#:########################################################################################:#
group: group:
simulation_lights_all: #:######################################################################################:#
name: Simulation Lights (All) # FAMILY PRESENCE
#:######################################################################################:#
family_away:
name: "All Family Members Away"
entities: entities:
- group.downstairs_flat_lights # ← now valid here - !secret person_1
- !secret person_2
- !secret person_3
#:######################################################################################:#
# SIMULATION ENTITIES
#:######################################################################################:#
simulation_lights_all:
name: "Simulation Lights (All)"
entities:
- group.downstairs_flat_lights
- switch.esp_centralstairs_top_relay_2_stair_footer_lights - switch.esp_centralstairs_top_relay_2_stair_footer_lights
- switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower - switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
- switch.esp_entrancebathrmlights_relay_1_main_lights - switch.esp_entrancebathrmlights_relay_1_main_lights
@@ -16,3 +47,62 @@ group:
- light.esp_loungepelmetleds2_lounge_pelmet_leds_a - light.esp_loungepelmetleds2_lounge_pelmet_leds_a
- light.esp_lounge6chdimmer_couch_spots_left - light.esp_lounge6chdimmer_couch_spots_left
- light.esp_lounge6chdimmer_couch_spots_right - light.esp_lounge6chdimmer_couch_spots_right
script:
#:######################################################################################:#
# RANDOM-DURATION ENTITY CONTROL
#:######################################################################################:#
light_duration:
description: "Turns an entity on for a supplied duration and then turns it off."
mode: parallel
fields:
light:
description: "Entity to operate."
example: light.bedroom
duration:
description: "How long the entity should remain on."
example: "00:25:00"
sequence:
- action: logbook.log
data:
name: "Script Notice"
message: "Light {{ light }} will be turned on for {{ duration }}"
- action: homeassistant.turn_on
target:
entity_id: "{{ light }}"
- delay: "{{ duration }}"
- action: homeassistant.turn_off
target:
entity_id: "{{ light }}"
automation:
#:######################################################################################:#
# AWAY-LIGHT SIMULATION
#:######################################################################################:#
- id: do_random_actions_when_away
alias: "Activate Random Actions for Security"
description: >
Periodically runs the timed simulation script after sunset when the away
routine is active and all configured family members are away.
mode: parallel
triggers:
- trigger: time_pattern
minutes: "/5"
conditions:
- condition: sun
after: sunset
after_offset: "-00:30:00"
- condition: state
entity_id: input_boolean.away_occupied_routine
state: "on"
- condition: state
entity_id: group.family_away
state: "not_home"
actions:
- action: script.light_duration
data:
light: "{{ state_attr('group.simulation_lights_all', 'entity_id') | random }}"
duration: "00:{{ '{:02}'.format(range(5, 29) | random | int) }}:00"
+28
View File
@@ -0,0 +1,28 @@
#:########################################################################################:#
# Package: Vehicle Announcements
# File: vehicle_announcements.yaml
# Version: v1.0.0
# Date: 2026-07-23
#
# Purpose:
# Sends vehicle arrival announcements through the View Road announcement router.
#:########################################################################################:#
automation:
- id: byd_atto3_arrival_announcement
alias: "BYD Atto 3 - Arrival Announcement"
description: >
Sends a Pushover notification to the Zorruno phone when the BYD Atto 3
Bermuda tracker arrives home.
mode: single
triggers:
- trigger: state
entity_id: device_tracker.byd_atto3_ibeacon_bur4c2a_bermuda_tracker
from: "not_home"
to: "home"
actions:
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Vehicle Notification"
long_announcement: "The BYD Atto 3 has arrived home."

Some files were not shown because too many files have changed in this diff Show More