Compare commits
125 Commits
d003115d82
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fe6feee250 | |||
| fce8c7b0bc | |||
| f46b29e6ab | |||
| 32df6ce407 | |||
| 8fdcb128a3 | |||
| b9e9c98f7e | |||
| 6dc9b56ff7 | |||
| 4e2107f7f7 | |||
| 66bee0ccf7 | |||
| 5b675e1d62 | |||
| a76dbb6b16 | |||
| e20e36cdb5 | |||
| a2fb86aa93 | |||
| c6e8d7721c | |||
| 3ff67b883b | |||
| 2e645793f0 | |||
| 4ff90ea60a | |||
| dcd43a18c1 | |||
| 2b6b91f829 | |||
| d8bcf05bf6 | |||
| afcbf1a94b | |||
| 027af09e40 | |||
| 90a6469161 | |||
| bffcda38d9 | |||
| cb95ceacea | |||
| 941334ecfd | |||
| a8b0990000 | |||
| 809f9ada66 | |||
| 14d65a8cf1 | |||
| 9afee6e638 | |||
| fd2fe422d0 | |||
| f07cd6d0c4 | |||
| ea604b8a30 | |||
| 241a4954bd | |||
| b2876e937a | |||
| 0dd442f34a | |||
| 67a8ed7436 | |||
| f92ffa3eab | |||
| b893249959 | |||
| c658792f5d | |||
| ca60f73b9d | |||
| 5747968e96 | |||
| 3938515564 | |||
| 526fd23567 | |||
| c8e9992c32 | |||
| 5e731f791e | |||
| 4bc8c308a6 | |||
| dcf28a3c8f | |||
| 9bcc9b9b4d | |||
| a95036e0c5 | |||
| 347e20f552 | |||
| ec195d33ce | |||
| d0df25acf3 | |||
| 1dbe748e19 | |||
| 3dcaa1b06b | |||
| d324ec568b | |||
| 97669cb664 | |||
| a81f56a261 | |||
| 3cc5a757bc | |||
| 0794008667 | |||
| cafc856a3c | |||
| 38281df1dd | |||
| c197172608 | |||
| 034c87317f | |||
| 824fd3fac6 | |||
| 41836a3dee | |||
| 45f1f0540a | |||
| c374e32ded | |||
| 3dc72898c9 | |||
| 324cefef4c | |||
| 07048708a9 | |||
| 355b3166f7 | |||
| 8b31b780b0 | |||
| ee08838c1f | |||
| aa4b632916 | |||
| 5be9125eec | |||
| 5f540de6af | |||
| 95835550de | |||
| 20a40b2b3f | |||
| cf857f322f | |||
| 497cf553ad | |||
| caa4d906c4 | |||
| b8681f7741 | |||
| 5403d27f10 | |||
| 54f4da6c18 | |||
| 19ba5a0c8c | |||
| 6ae711d922 | |||
| e90b6b2b8c | |||
| 24bb66893a | |||
| 905a16236b | |||
| cc720cfd00 | |||
| 34a84d0d0c | |||
| 5855dbb5a9 | |||
| 2a8c9584d7 | |||
| bb97b2ac12 | |||
| 0ca6fefd08 | |||
| ea7eedff93 | |||
| 79054490d0 | |||
| d27ba8bd14 | |||
| 63209b5364 | |||
| 1b9552e328 | |||
| ae566427c2 | |||
| f63ff4bfe2 | |||
| 7bd3e3a0c2 | |||
| 06a482b43f | |||
| e013bab5c9 | |||
| 4fab9755ec | |||
| 769cb34eb6 | |||
| ce7a456d1f | |||
| e586ff8f28 | |||
| f571919352 | |||
| 2e9a37a9b9 | |||
| d45474036c | |||
| 5c5256caa9 | |||
| 2a5725b16d | |||
| 706844e6e8 | |||
| 2b2b8edc37 | |||
| c11728dba6 | |||
| 0377bf0b08 | |||
| 893ed9dcfc | |||
| 1d00da6514 | |||
| 1aa5985c2c | |||
| 97efa38777 | |||
| cd39558b1f | |||
| 5b7a9d39d2 |
+1
-1
@@ -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}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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 -%}
|
||||
@@ -39,6 +39,3 @@ zha:
|
||||
salus_provider: true
|
||||
inovelli_provider: true
|
||||
thirdreality_provider: true
|
||||
|
||||
device_tracker:
|
||||
- platform: bluetooth_le_tracker
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+265
-78
@@ -6,79 +6,119 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-3dprinterpow.yaml
|
||||
#:########################################################################################:#
|
||||
# VERSIONS:
|
||||
# V1.1 2026-02-25 Updated yaml to zorruno layout V1.1
|
||||
# V1.0 2025-06-29 Initial Version
|
||||
# V1.3 2026-07-12
|
||||
# - 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:
|
||||
# Sonoff POW R1 (ESP8266)
|
||||
# https://devices.esphome.io/devices/Sonoff-POW-R1
|
||||
#:########################################################################################:#
|
||||
# OPERATION NOTES:
|
||||
# - Inline power monitor (HLW8012) plus relay control for the 3D printer power feed
|
||||
# - Local MQTT control (ON/OFF) without needing Home Assistant
|
||||
# - Publishes current relay state on boot (burst) and on relay changes
|
||||
# - "Running" binary sensor is derived from measured power > 4 W (with delayed_off)
|
||||
# - Inline power monitor using the HLW8012.
|
||||
# - Reports current, voltage, instantaneous 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.
|
||||
# - 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:
|
||||
# - Command topic (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}
|
||||
# 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:
|
||||
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE
|
||||
# - Device remains controllable via MQTT local command topic
|
||||
# - Status publishes to MQTT still occur (relay state burst + changes)
|
||||
# - HA entities are unavailable until HA returns
|
||||
# b) MQTT OFFLINE (but WiFi/Network and HA API ONLINE)
|
||||
# - 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
|
||||
# - Device remains controllable through the MQTT local command topic.
|
||||
# - Relay status changes continue to publish through MQTT.
|
||||
# - Energy integration and maximum power monitoring continue locally.
|
||||
# - Home Assistant entities remain unavailable until HA returns.
|
||||
#
|
||||
# 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
|
||||
# - No HA API and no MQTT control available
|
||||
# - Local button still toggles the relay (and status LED follows)
|
||||
# - Accurate time is NOT needed (SNTP not needed)
|
||||
# - No Home Assistant API or MQTT control is available.
|
||||
# - The local physical button continues to toggle the relay.
|
||||
# - 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
|
||||
# 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:
|
||||
# Device Naming
|
||||
device_name: "esp-3dprinterpow"
|
||||
friendly_name: "3D Printer Power"
|
||||
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.
|
||||
description_comment: "3D Printer inline monitor and switch override using a Sonoff POW R1. (Layout V1.1)"
|
||||
device_area: "Underhouse"
|
||||
|
||||
# Project Naming
|
||||
project_name: "Sonoff Technologies.POW R1" # Project Details
|
||||
project_version: "v1.1" # Project V denotes release of yaml file, allowing checking of deployed vs latest version
|
||||
project_name: "Sonoff Technologies.POW R1"
|
||||
project_version: "v1.3"
|
||||
|
||||
# Passwords & Secrets
|
||||
# Passwords and Secrets
|
||||
api_key: !secret esp-api_key
|
||||
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_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
|
||||
# Don't forget to switch it back when changed.
|
||||
# If changing IP addresses, update the current IP address here.
|
||||
# Switch it back once the address change is complete.
|
||||
current_ip_address: ${static_ip_address}
|
||||
|
||||
# MQTT LOCAL Controls
|
||||
# MQTT Local Controls
|
||||
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_status_topic: "${mqtt_status_main_topic}/${mqtt_local_device_name}" # Topic we will use to view status 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}"
|
||||
|
||||
# Device Settings
|
||||
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
||||
update_interval: "60s" # update time for for general sensors etc
|
||||
entity_prefix: "Load" # Simple device name where we want to prefix a sensor or switch, eg "Load" Current.
|
||||
log_level: "INFO"
|
||||
update_interval: "60s"
|
||||
entity_prefix: "Load"
|
||||
|
||||
#:########################################################################################:#
|
||||
# PACKAGES: Included Common Packages
|
||||
@@ -126,14 +166,29 @@ packages:
|
||||
esphome:
|
||||
name: "${device_name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${description_comment}" # Appears on the esphome page in HA
|
||||
comment: "${description_comment}"
|
||||
area: "${device_area}"
|
||||
|
||||
# Restrict PlatformIO to one simultaneous compiler process to reduce host RAM use.
|
||||
compile_process_limit: 1
|
||||
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
|
||||
on_boot:
|
||||
priority: -10
|
||||
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
|
||||
|
||||
#:########################################################################################:#
|
||||
@@ -142,112 +197,239 @@ esphome:
|
||||
#:########################################################################################:#
|
||||
esp8266:
|
||||
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
|
||||
# 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)
|
||||
level: "${log_level}"
|
||||
|
||||
# Set to 0 if the UART is required for another component.
|
||||
#baud_rate: 0
|
||||
|
||||
#esp8266_store_log_strings_in_flash: false
|
||||
#tx_buffer_size: 64
|
||||
|
||||
#:########################################################################################:#
|
||||
# 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:
|
||||
on_message:
|
||||
# Switch control from MQTT
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "ON"
|
||||
then:
|
||||
- switch.turn_on: relay
|
||||
- light.turn_on: led
|
||||
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "OFF"
|
||||
then:
|
||||
- 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:
|
||||
# https://esphome.io/components/binary_sensor/
|
||||
#:########################################################################################:#
|
||||
binary_sensor:
|
||||
# Physical Sonoff button.
|
||||
- platform: gpio
|
||||
name: "Button"
|
||||
|
||||
pin:
|
||||
number: GPIO0
|
||||
mode: INPUT_PULLUP
|
||||
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
|
||||
name: "${entity_prefix} Running"
|
||||
|
||||
filters:
|
||||
- delayed_off: 15s
|
||||
|
||||
lambda: |-
|
||||
if (isnan(id(power).state)) {
|
||||
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/
|
||||
#:########################################################################################:#
|
||||
sensor:
|
||||
- platform: hlw8012
|
||||
sel_pin: 5
|
||||
cf_pin: 14
|
||||
cf1_pin: 13
|
||||
sel_pin: GPIO5
|
||||
cf_pin: GPIO14
|
||||
cf1_pin: GPIO13
|
||||
update_interval: 2s
|
||||
|
||||
current:
|
||||
name: "${entity_prefix} Current"
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
unit_of_measurement: "A"
|
||||
accuracy_decimals: 2
|
||||
|
||||
voltage:
|
||||
name: "${entity_prefix} Voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
accuracy_decimals: 1
|
||||
|
||||
power:
|
||||
name: "${entity_prefix} 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:
|
||||
# https://esphome.io/components/switch/
|
||||
#:########################################################################################:#
|
||||
switch:
|
||||
- platform: template
|
||||
name: "Relay"
|
||||
optimistic: true
|
||||
id: virtual_button
|
||||
turn_on_action:
|
||||
- switch.turn_on: relay
|
||||
- light.turn_on: led
|
||||
turn_off_action:
|
||||
- switch.turn_off: relay
|
||||
- light.turn_off: led
|
||||
|
||||
# The actual GPIO relay is exposed directly to Home Assistant.
|
||||
# This is the single authoritative relay state for:
|
||||
# - Home Assistant API control;
|
||||
# - MQTT control;
|
||||
# - physical button control;
|
||||
# - relay state restoration;
|
||||
# - MQTT status publishing;
|
||||
# - status LED synchronisation.
|
||||
- platform: gpio
|
||||
name: "Relay"
|
||||
id: relay
|
||||
pin: GPIO12
|
||||
restore_mode: RESTORE_DEFAULT_ON
|
||||
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
|
||||
on_turn_on:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "ON"
|
||||
retain: false
|
||||
then:
|
||||
- light.turn_on: led
|
||||
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "ON"
|
||||
retain: false
|
||||
|
||||
on_turn_off:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "OFF"
|
||||
retain: false
|
||||
then:
|
||||
- light.turn_off: led
|
||||
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "OFF"
|
||||
retain: false
|
||||
|
||||
#:########################################################################################:#
|
||||
# OUTPUT COMPONENT:
|
||||
@@ -256,6 +438,7 @@ switch:
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: pow_blue_led
|
||||
|
||||
pin:
|
||||
number: GPIO15
|
||||
inverted: true
|
||||
@@ -267,8 +450,8 @@ output:
|
||||
light:
|
||||
- platform: monochromatic
|
||||
name: "Status LED"
|
||||
output: pow_blue_led
|
||||
id: led
|
||||
output: pow_blue_led
|
||||
|
||||
#:########################################################################################:#
|
||||
# SCRIPT COMPONENT:
|
||||
@@ -277,13 +460,17 @@ light:
|
||||
script:
|
||||
- id: publish_state_burst
|
||||
mode: restart
|
||||
|
||||
then:
|
||||
- repeat:
|
||||
count: 5
|
||||
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: !lambda 'return id(relay).state ? "ON" : "OFF";'
|
||||
payload: !lambda |-
|
||||
return id(relay).state ? "ON" : "OFF";
|
||||
retain: false
|
||||
qos: 1
|
||||
|
||||
- delay: 5s
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-bedrm1arlecbulb.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.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)
|
||||
@@ -42,7 +43,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed).
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -56,7 +56,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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)
|
||||
# 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)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 0;'
|
||||
lambda: "return id(restart_mode) == 0;"
|
||||
then:
|
||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||
- lambda: "id(ramp_switch_target_on) = true;"
|
||||
- script.execute: ramp_on_script
|
||||
|
||||
# Mode 1: Restore Brightness (deferred; forces Warm endpoint)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 1;'
|
||||
lambda: "return id(restart_mode) == 1;"
|
||||
then:
|
||||
- script.execute: deferred_restore_brightness
|
||||
|
||||
# Mode 2: Remain Off
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 2;'
|
||||
lambda: "return id(restart_mode) == 2;"
|
||||
then:
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: bulb_light
|
||||
transition_length: 0s
|
||||
|
||||
# Boot complete: allow OFF handlers again
|
||||
- lambda: 'id(booting) = false;'
|
||||
- lambda: "id(booting) = false;"
|
||||
|
||||
#:########################################################################################:#
|
||||
# ESP PLATFORM AND FRAMEWORK
|
||||
@@ -301,12 +301,12 @@ logger:
|
||||
#:########################################################################################:#
|
||||
mqtt:
|
||||
on_message:
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
then:
|
||||
- switch.turn_on: bulb_ramp_switch
|
||||
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
then:
|
||||
- switch.turn_off: bulb_ramp_switch
|
||||
@@ -536,7 +536,7 @@ light:
|
||||
|
||||
on_turn_on:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -546,20 +546,20 @@ light:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
on_turn_off:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(booting);'
|
||||
lambda: "return !id(booting);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
retain: true
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- script.stop: max_on_watchdog
|
||||
- lambda: |-
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
@@ -667,9 +667,9 @@ number:
|
||||
set_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return x <= 0.0f;'
|
||||
lambda: "return x <= 0.0f;"
|
||||
then:
|
||||
- lambda: 'id(suppress_slider_sync) = true;'
|
||||
- lambda: "id(suppress_slider_sync) = true;"
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- light.turn_off:
|
||||
@@ -681,7 +681,7 @@ number:
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
id(last_set_pos) = 0;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
- lambda: |-
|
||||
id(suppress_slider_sync) = true;
|
||||
@@ -713,7 +713,7 @@ number:
|
||||
if (out_pct > maxp) out_pct = maxp;
|
||||
id(last_brightness_pct) = out_pct;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
|
||||
- platform: template
|
||||
id: cfg_max_on_hours
|
||||
@@ -736,12 +736,12 @@ number:
|
||||
id(cfg_max_on_hours).publish_state((float) hrs);
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(bulb_light).current_values.is_on();'
|
||||
lambda: "return id(bulb_light).current_values.is_on();"
|
||||
then:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -753,7 +753,7 @@ script:
|
||||
- id: ramp_on_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
|
||||
- if:
|
||||
@@ -766,13 +766,13 @@ script:
|
||||
- light.turn_on:
|
||||
id: bulb_light
|
||||
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
|
||||
|
||||
- light.turn_on:
|
||||
id: bulb_light
|
||||
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 |-
|
||||
const auto &cv = id(bulb_light).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -795,7 +795,7 @@ script:
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(bulb_light).current_values;
|
||||
@@ -807,7 +807,7 @@ script:
|
||||
- id: ramp_to_target_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
|
||||
- if:
|
||||
@@ -820,7 +820,7 @@ script:
|
||||
- light.turn_on:
|
||||
id: bulb_light
|
||||
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
|
||||
|
||||
- light.turn_on:
|
||||
@@ -858,7 +858,7 @@ script:
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(bulb_light).current_values;
|
||||
@@ -878,7 +878,7 @@ script:
|
||||
- light.turn_on:
|
||||
id: bulb_light
|
||||
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 |-
|
||||
const auto &cv = id(bulb_light).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -895,7 +895,7 @@ script:
|
||||
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
|
||||
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:
|
||||
id: bulb_light
|
||||
transition_length: 150ms
|
||||
@@ -913,12 +913,12 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);'
|
||||
- delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(bulb_light).current_values.is_on();'
|
||||
lambda: "return id(bulb_light).current_values.is_on();"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = false;
|
||||
@@ -942,17 +942,17 @@ script:
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||
lambda: "return id(restore_target_pct) > 0.5f;"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = true;
|
||||
id(suppress_slider_sync) = true;
|
||||
- script.stop: ramp_off_script
|
||||
- script.execute: ramp_to_target_script
|
||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- delay: !lambda "return (uint32_t) id(last_ramp_ms);"
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: bulb_light
|
||||
transition_length: 0s
|
||||
@@ -1013,7 +1013,7 @@ script:
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(bulb_light).current_values.is_on();'
|
||||
lambda: "return id(bulb_light).current_values.is_on();"
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: bulb_light
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-breakfastbarleds.yaml
|
||||
#:########################################################################################:#
|
||||
# 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
|
||||
# V1.0 2025-08-17 First setup (and replacement of Tasmota)
|
||||
#:########################################################################################:#
|
||||
@@ -47,10 +48,10 @@
|
||||
#:########################################################################################:#
|
||||
# MQTT COMMANDS:
|
||||
# Local MQTT light control:
|
||||
# - ${mqtt_local_command_topic}/light/set -> ON,OFF
|
||||
# - ${mqtt_local_command_topic} -> ON,OFF
|
||||
#
|
||||
# Local MQTT light status:
|
||||
# - ${mqtt_local_status_topic}/light/state -> ON,OFF
|
||||
# - ${mqtt_local_status_topic} -> ON,OFF
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE
|
||||
@@ -80,7 +81,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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)
|
||||
api_key: !secret esp-api_key
|
||||
@@ -130,8 +131,7 @@ packages:
|
||||
local_current_ip_address: "${current_ip_address}"
|
||||
|
||||
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||
common_api: !include
|
||||
#file: common/api_common.yaml
|
||||
common_api: !include #file: common/api_common.yaml
|
||||
file: common/api_common_noencryption.yaml
|
||||
vars:
|
||||
local_api_key: "${api_key}"
|
||||
@@ -178,32 +178,32 @@ esphome:
|
||||
# Mode 0: Fade up to full (obeys fade settings; no on/off flicker)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 0;'
|
||||
lambda: "return id(restart_mode) == 0;"
|
||||
then:
|
||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||
- lambda: "id(ramp_switch_target_on) = true;"
|
||||
- script.execute: ramp_on_script
|
||||
|
||||
# Mode 1: Restore Brightness (deferred to avoid races)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 1;'
|
||||
lambda: "return id(restart_mode) == 1;"
|
||||
then:
|
||||
- script.execute: deferred_restore_brightness
|
||||
|
||||
# Mode 2: Remain Off (no blip; stays off)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 2;'
|
||||
lambda: "return id(restart_mode) == 2;"
|
||||
then:
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: mosfet_leds
|
||||
transition_length: 0s
|
||||
|
||||
# Boot complete: allow OFF handlers again
|
||||
- lambda: 'id(booting) = false;'
|
||||
- lambda: "id(booting) = false;"
|
||||
|
||||
#:########################################################################################:#
|
||||
# ESP PLATFORM AND FRAMEWORK:
|
||||
@@ -345,13 +345,13 @@ status_led:
|
||||
mqtt:
|
||||
on_message:
|
||||
# Light control to ramp up
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
then:
|
||||
- switch.turn_on: mosfet_ramp_switch
|
||||
|
||||
# Light control to ramp down
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
then:
|
||||
- switch.turn_off: mosfet_ramp_switch
|
||||
@@ -482,7 +482,7 @@ binary_sensor:
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(ramp_switch_target_on);'
|
||||
lambda: "return id(ramp_switch_target_on);"
|
||||
then:
|
||||
# Target is currently ON -> press should go OFF (start ramp-down)
|
||||
- lambda: |-
|
||||
@@ -584,7 +584,7 @@ light:
|
||||
on_turn_on:
|
||||
- output.turn_on: green_led_out
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -595,7 +595,7 @@ light:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -605,13 +605,13 @@ light:
|
||||
- output.turn_off: green_led_out
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(booting);'
|
||||
lambda: "return !id(booting);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
retain: true
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- script.stop: max_on_watchdog
|
||||
- lambda: |-
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
@@ -699,9 +699,9 @@ number:
|
||||
set_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return x <= 0.0f;'
|
||||
lambda: "return x <= 0.0f;"
|
||||
then:
|
||||
- lambda: 'id(suppress_slider_sync) = true;'
|
||||
- lambda: "id(suppress_slider_sync) = true;"
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- light.turn_off:
|
||||
@@ -713,7 +713,7 @@ number:
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
id(last_set_pos) = 0;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
- lambda: |-
|
||||
id(suppress_slider_sync) = true;
|
||||
@@ -744,7 +744,7 @@ number:
|
||||
if (out_pct > maxp) out_pct = maxp;
|
||||
id(last_brightness_pct) = out_pct;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
|
||||
- platform: template
|
||||
id: cfg_max_on_hours
|
||||
@@ -767,12 +767,12 @@ number:
|
||||
id(cfg_max_on_hours).publish_state((float) hrs);
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mosfet_leds).current_values.is_on();'
|
||||
lambda: "return id(mosfet_leds).current_values.is_on();"
|
||||
then:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -785,7 +785,7 @@ script:
|
||||
- id: ramp_on_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
|
||||
# Ensure we start at at least the floor without a visible "pop".
|
||||
@@ -798,13 +798,13 @@ script:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: 80ms
|
||||
|
||||
# Ramp from current (>= floor) to cap over a fraction of ramp_up_ms.
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
|
||||
transition_length: !lambda |-
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -825,7 +825,7 @@ script:
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
@@ -838,7 +838,7 @@ script:
|
||||
- id: ramp_to_target_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
|
||||
# Ensure we start at the floor cleanly.
|
||||
@@ -851,7 +851,7 @@ script:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: 80ms
|
||||
|
||||
# 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);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
@@ -904,7 +904,7 @@ script:
|
||||
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: !lambda |-
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -918,7 +918,7 @@ script:
|
||||
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
|
||||
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:
|
||||
id: mosfet_leds
|
||||
transition_length: 150ms
|
||||
@@ -935,12 +935,12 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);'
|
||||
- delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mosfet_leds).current_values.is_on();'
|
||||
lambda: "return id(mosfet_leds).current_values.is_on();"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = false;
|
||||
@@ -967,18 +967,18 @@ script:
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||
lambda: "return id(restore_target_pct) > 0.5f;"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = true;
|
||||
id(suppress_slider_sync) = true;
|
||||
- script.stop: ramp_off_script
|
||||
- script.execute: ramp_to_target_script
|
||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- delay: !lambda "return (uint32_t) id(last_ramp_ms);"
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
# Exact last was 0 -> remain OFF
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: mosfet_leds
|
||||
transition_length: 0s
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-centralstairs-bottom.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.7 2025-09-24 Upload to this device
|
||||
# V3.5 2025-07-24 YAML tidyups
|
||||
@@ -83,7 +84,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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_command_main_topic: !secret mqtt_command_main_topic
|
||||
@@ -184,19 +185,19 @@ mqtt:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(Relay_1).state;'
|
||||
lambda: "return id(Relay_1).state;"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_1}"
|
||||
payload: "${mqtt_command_ON}"
|
||||
qos: 0
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
else:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_1}"
|
||||
payload: "${mqtt_command_OFF}"
|
||||
qos: 0
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
|
||||
on_message:
|
||||
# Relay 1: only turn ON if currently OFF
|
||||
@@ -241,6 +242,26 @@ mqtt:
|
||||
then:
|
||||
- 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:
|
||||
# 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
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(Relay_2).state;'
|
||||
lambda: "return id(Relay_2).state;"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_command_topic_2}"
|
||||
@@ -332,12 +353,14 @@ switch:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_1}"
|
||||
payload: "${mqtt_command_ON}"
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
on_turn_off:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_1}"
|
||||
payload: "${mqtt_command_OFF}"
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
|
||||
- platform: gpio
|
||||
name: "Relay 2: ${switch_2_name}"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-centralstairs-top.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.7 2025-09-24 Upload to this device
|
||||
# V3.5 2025-07-24 YAML tidyups
|
||||
@@ -83,7 +84,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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_command_main_topic: !secret mqtt_command_main_topic
|
||||
@@ -184,19 +185,19 @@ mqtt:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(Relay_2).state;'
|
||||
lambda: "return id(Relay_2).state;"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_2}"
|
||||
payload: "${mqtt_command_ON}"
|
||||
qos: 0
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
else:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_2}"
|
||||
payload: "${mqtt_command_OFF}"
|
||||
qos: 0
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
|
||||
on_message:
|
||||
# Relay 1: only turn ON if currently OFF
|
||||
@@ -241,6 +242,26 @@ mqtt:
|
||||
then:
|
||||
- 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:
|
||||
# https://esphome.io/components/status_led.html
|
||||
@@ -286,7 +307,7 @@ binary_sensor:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(Relay_1).state;'
|
||||
lambda: "return id(Relay_1).state;"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_command_topic_1}"
|
||||
@@ -338,9 +359,11 @@ switch:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_2}"
|
||||
payload: "${mqtt_command_ON}"
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
on_turn_off:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic_2}"
|
||||
payload: "${mqtt_command_OFF}"
|
||||
retain: false
|
||||
qos: 1
|
||||
retain: true
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstairskitchleds.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.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),
|
||||
@@ -60,12 +61,12 @@
|
||||
#:########################################################################################:#
|
||||
# MQTT COMMANDS:
|
||||
# - 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_OFF}" -> fade down (switch OFF)
|
||||
#
|
||||
# - Status topic:
|
||||
# ${mqtt_local_status_topic}/light/state
|
||||
# ${mqtt_local_status_topic}
|
||||
# Payload "${mqtt_local_device_command_ON}" / "${mqtt_local_device_command_OFF}"
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
@@ -81,7 +82,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed)
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -95,7 +95,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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)
|
||||
api_key: !secret esp-api_key
|
||||
@@ -145,8 +145,7 @@ packages:
|
||||
local_current_ip_address: "${current_ip_address}"
|
||||
|
||||
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||
common_api: !include
|
||||
#file: common/api_common.yaml
|
||||
common_api: !include #file: common/api_common.yaml
|
||||
file: common/api_common_noencryption.yaml
|
||||
vars:
|
||||
local_api_key: "${api_key}"
|
||||
@@ -193,32 +192,32 @@ esphome:
|
||||
# Mode 0: Fade up to full (obeys fade settings; no on/off flicker)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 0;'
|
||||
lambda: "return id(restart_mode) == 0;"
|
||||
then:
|
||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||
- lambda: "id(ramp_switch_target_on) = true;"
|
||||
- script.execute: ramp_on_script
|
||||
|
||||
# Mode 1: Restore Brightness (deferred to avoid races)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 1;'
|
||||
lambda: "return id(restart_mode) == 1;"
|
||||
then:
|
||||
- script.execute: deferred_restore_brightness
|
||||
|
||||
# Mode 2: Remain Off (no blip; stays off)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 2;'
|
||||
lambda: "return id(restart_mode) == 2;"
|
||||
then:
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: mosfet_leds
|
||||
transition_length: 0s
|
||||
|
||||
# Boot complete: allow OFF handlers again
|
||||
- lambda: 'id(booting) = false;'
|
||||
- lambda: "id(booting) = false;"
|
||||
|
||||
#:########################################################################################:#
|
||||
# ESP PLATFORM AND FRAMEWORK
|
||||
@@ -358,13 +357,13 @@ status_led:
|
||||
mqtt:
|
||||
on_message:
|
||||
# Light control to ramp up
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
then:
|
||||
- switch.turn_on: mosfet_ramp_switch
|
||||
|
||||
# Light control to ramp down
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
then:
|
||||
- switch.turn_off: mosfet_ramp_switch
|
||||
@@ -492,7 +491,7 @@ binary_sensor:
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(ramp_switch_target_on);'
|
||||
lambda: "return id(ramp_switch_target_on);"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = false;
|
||||
@@ -589,7 +588,7 @@ light:
|
||||
on_turn_on:
|
||||
- output.turn_on: green_led_out
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -599,7 +598,7 @@ light:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -607,13 +606,13 @@ light:
|
||||
- output.turn_off: green_led_out
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(booting);'
|
||||
lambda: "return !id(booting);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
retain: true
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- script.stop: max_on_watchdog
|
||||
- lambda: |-
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
@@ -700,9 +699,9 @@ number:
|
||||
set_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return x <= 0.0f;'
|
||||
lambda: "return x <= 0.0f;"
|
||||
then:
|
||||
- lambda: 'id(suppress_slider_sync) = true;'
|
||||
- lambda: "id(suppress_slider_sync) = true;"
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- light.turn_off:
|
||||
@@ -714,7 +713,7 @@ number:
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
id(last_set_pos) = 0;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
- lambda: |-
|
||||
id(suppress_slider_sync) = true;
|
||||
@@ -745,7 +744,7 @@ number:
|
||||
if (out_pct > maxp) out_pct = maxp;
|
||||
id(last_brightness_pct) = out_pct;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
|
||||
- platform: template
|
||||
id: cfg_max_on_hours
|
||||
@@ -768,12 +767,12 @@ number:
|
||||
id(cfg_max_on_hours).publish_state((float) hrs);
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mosfet_leds).current_values.is_on();'
|
||||
lambda: "return id(mosfet_leds).current_values.is_on();"
|
||||
then:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -785,7 +784,7 @@ script:
|
||||
- id: ramp_on_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
- if:
|
||||
condition:
|
||||
@@ -796,11 +795,11 @@ script:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: 80ms
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
|
||||
transition_length: !lambda |-
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -820,7 +819,7 @@ script:
|
||||
if (frac > 1.0f) frac = 1.0f;
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
@@ -832,7 +831,7 @@ script:
|
||||
- id: ramp_to_target_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
- if:
|
||||
condition:
|
||||
@@ -843,7 +842,7 @@ script:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: 80ms
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
@@ -873,7 +872,7 @@ script:
|
||||
if (frac > 1.0f) frac = 1.0f;
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
@@ -891,7 +890,7 @@ script:
|
||||
- script.stop: ramp_on_script
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: !lambda |-
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -904,7 +903,7 @@ script:
|
||||
if (frac > 1.0f) frac = 1.0f;
|
||||
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
|
||||
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:
|
||||
id: mosfet_leds
|
||||
transition_length: 150ms
|
||||
@@ -921,12 +920,12 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);'
|
||||
- delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mosfet_leds).current_values.is_on();'
|
||||
lambda: "return id(mosfet_leds).current_values.is_on();"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = false;
|
||||
@@ -950,17 +949,17 @@ script:
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||
lambda: "return id(restore_target_pct) > 0.5f;"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = true;
|
||||
id(suppress_slider_sync) = true;
|
||||
- script.stop: ramp_off_script
|
||||
- script.execute: ramp_to_target_script
|
||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- delay: !lambda "return (uint32_t) id(last_ramp_ms);"
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: mosfet_leds
|
||||
transition_length: 0s
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstkitchlights.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.0 2025-06-05 YAML tidyups
|
||||
# v1.0 2025-03-28 Initial Version
|
||||
@@ -21,6 +22,13 @@
|
||||
# - Relay 1: Dining Light
|
||||
# - Relay 2: Kitchen Light
|
||||
# - 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:
|
||||
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE
|
||||
@@ -36,7 +44,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed)
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -50,7 +57,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Zemismart Technologies.KS-811 Triple"
|
||||
project_version: "v2.1"
|
||||
project_version: "v2.2"
|
||||
|
||||
# Passwords & Secrets
|
||||
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.
|
||||
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
|
||||
log_level: "INFO" # NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||
update_interval: "60s" # update time for general sensors etc
|
||||
@@ -195,6 +209,14 @@ switch:
|
||||
name: "Relay 2: ${switch_2_name}"
|
||||
pin: GPIO12
|
||||
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
|
||||
name: "Relay 3: ${switch_3_name}"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstloungeentry.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.6 2025-07-24 YAML tidyups
|
||||
#:########################################################################################:#
|
||||
@@ -45,7 +46,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed)
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -59,7 +59,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Zemismart Technologies.KS-811 Single"
|
||||
project_version: "v3.7"
|
||||
project_version: "v3.8"
|
||||
|
||||
# Passwords & Secrets
|
||||
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_device_name: "downst-lounge-main-lights"
|
||||
mqtt_remote_device_command_topic: "${mqtt_command_main_topic}/${mqtt_remote_device_name}/light1/set"
|
||||
mqtt_remote_status_topic: "${mqtt_status_main_topic}/${mqtt_remote_device_name}/light1/state"
|
||||
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}"
|
||||
mqtt_remote_device_command_on: "On"
|
||||
mqtt_remote_device_command_off: "Off"
|
||||
|
||||
@@ -170,7 +170,7 @@ mqtt:
|
||||
- delay: 50ms
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(Relay_1).state;'
|
||||
lambda: "return !id(Relay_1).state;"
|
||||
then:
|
||||
- switch.turn_on: Relay_1
|
||||
|
||||
@@ -180,7 +180,7 @@ mqtt:
|
||||
- delay: 50ms
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(Relay_1).state;'
|
||||
lambda: "return id(Relay_1).state;"
|
||||
then:
|
||||
- switch.turn_off: Relay_1
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-downstloungemain.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.5 2025-07-28 YAML tidyups
|
||||
#:########################################################################################:#
|
||||
@@ -23,12 +24,12 @@
|
||||
#:########################################################################################:#
|
||||
# MQTT COMMANDS:
|
||||
# - Local command topic (Main Lights):
|
||||
# ${mqtt_local_command_topic}/light1/set
|
||||
# ${mqtt_local_command_topic}
|
||||
# Payload "On" -> Relay 1 ON
|
||||
# Payload "Off" -> Relay 1 OFF
|
||||
#
|
||||
# - Local status topic (Main Lights):
|
||||
# ${mqtt_local_status_topic}/light1/state
|
||||
# ${mqtt_local_status_topic}
|
||||
# Payload "On" / "Off" published when Relay 1 changes state
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
@@ -45,7 +46,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed).
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -59,7 +59,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Zemismart Technologies.KS-811 Triple"
|
||||
project_version: "v3.6"
|
||||
project_version: "v3.7"
|
||||
|
||||
# Passwords & Secrets
|
||||
api_key: !secret esp-api_key
|
||||
@@ -156,11 +156,11 @@ logger:
|
||||
mqtt:
|
||||
on_message:
|
||||
# Main Lights control (Relay 1)
|
||||
- topic: "${mqtt_local_command_topic}/light1/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "On"
|
||||
then:
|
||||
- switch.turn_on: relay_1
|
||||
- topic: "${mqtt_local_command_topic}/light1/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "Off"
|
||||
then:
|
||||
- switch.turn_off: relay_1
|
||||
@@ -222,11 +222,11 @@ switch:
|
||||
id: relay_1
|
||||
on_turn_on:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light1/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "On"
|
||||
on_turn_off:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light1/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "Off"
|
||||
|
||||
# Relay 2 (GPIO12)
|
||||
|
||||
@@ -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);
|
||||
@@ -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
@@ -6,6 +6,9 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-laundrydrypow.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.0 2025-05-28 Initial Version
|
||||
#:########################################################################################:#
|
||||
@@ -20,7 +23,16 @@
|
||||
# - Reference: https://devices.esphome.io/devices/Sonoff-POW-R1
|
||||
#:########################################################################################:#
|
||||
# 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
|
||||
# - Local button toggles the virtual relay switch
|
||||
# - On boot, the virtual relay is turned ON
|
||||
@@ -31,15 +43,18 @@
|
||||
# a) Home Assistant offline (network and MQTT online):
|
||||
# - Device continues running normally
|
||||
# - 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):
|
||||
# - Device continues running locally
|
||||
# - Local button, relay control, LED control, and power monitoring still work
|
||||
# - Energy integration and maximum power monitoring continue locally
|
||||
#
|
||||
# c) Entire WiFi/Network offline:
|
||||
# - Device continues running locally
|
||||
# - 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
|
||||
#:########################################################################################:#
|
||||
|
||||
@@ -57,7 +72,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Sonoff Technologies.POW R1"
|
||||
project_version: "v1.1"
|
||||
project_version: "v1.2"
|
||||
|
||||
# Entity Naming
|
||||
entity_prefix: "Load"
|
||||
@@ -121,9 +136,14 @@ esphome:
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${description_comment}"
|
||||
area: "${device_area}"
|
||||
|
||||
# Limit parallel compiler processes to reduce host RAM usage.
|
||||
compile_process_limit: 1
|
||||
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
|
||||
on_boot:
|
||||
priority: 200
|
||||
then:
|
||||
@@ -157,6 +177,28 @@ logger:
|
||||
# number: GPIO2
|
||||
# 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:
|
||||
# https://esphome.io/components/binary_sensor/
|
||||
@@ -178,7 +220,7 @@ binary_sensor:
|
||||
lambda: |-
|
||||
if (isnan(id(power).state)) {
|
||||
return {};
|
||||
} else if (id(power).state > 4) {
|
||||
} else if (id(power).state > 4.0f) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -194,13 +236,44 @@ sensor:
|
||||
cf_pin: GPIO14
|
||||
cf1_pin: GPIO13
|
||||
update_interval: 2s
|
||||
|
||||
current:
|
||||
name: "${entity_prefix} Current"
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
unit_of_measurement: "A"
|
||||
accuracy_decimals: 2
|
||||
|
||||
voltage:
|
||||
name: "${entity_prefix} Voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
accuracy_decimals: 1
|
||||
|
||||
power:
|
||||
name: "${entity_prefix} 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:
|
||||
# - above: 4.0
|
||||
# then:
|
||||
@@ -209,6 +282,64 @@ sensor:
|
||||
# then:
|
||||
# - 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:
|
||||
# https://esphome.io/components/switch/
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-laundrywashpow.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.0 2025-03-28 Initial Version
|
||||
#:########################################################################################:#
|
||||
@@ -20,7 +23,18 @@
|
||||
# - Reference: https://devices.esphome.io/devices/Sonoff-POW-R1
|
||||
#:########################################################################################:#
|
||||
# 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
|
||||
# - Local button toggles the virtual relay switch
|
||||
# - On boot, the virtual relay is turned ON
|
||||
@@ -31,15 +45,18 @@
|
||||
# a) Home Assistant offline (network and MQTT online):
|
||||
# - Device continues running normally
|
||||
# - 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):
|
||||
# - Device continues running locally
|
||||
# - Local button, relay control, LED control, and power monitoring still work
|
||||
# - Energy integration and maximum power monitoring continue locally
|
||||
#
|
||||
# c) Entire WiFi/Network offline:
|
||||
# - Device continues running locally
|
||||
# - 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
|
||||
#:########################################################################################:#
|
||||
|
||||
@@ -57,7 +74,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Sonoff Technologies.POW R1"
|
||||
project_version: "v1.1"
|
||||
project_version: "v1.2"
|
||||
|
||||
# Entity Naming
|
||||
entity_prefix: "Load"
|
||||
@@ -121,9 +138,14 @@ esphome:
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${description_comment}"
|
||||
area: "${device_area}"
|
||||
|
||||
# Limit parallel compiler processes to reduce host RAM usage.
|
||||
compile_process_limit: 1
|
||||
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
|
||||
on_boot:
|
||||
priority: 200
|
||||
then:
|
||||
@@ -157,6 +179,28 @@ logger:
|
||||
# number: GPIO2
|
||||
# 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:
|
||||
# https://esphome.io/components/binary_sensor/
|
||||
@@ -178,7 +222,7 @@ binary_sensor:
|
||||
lambda: |-
|
||||
if (isnan(id(power).state)) {
|
||||
return {};
|
||||
} else if (id(power).state > 4) {
|
||||
} else if (id(power).state > 4.0f) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -194,13 +238,44 @@ sensor:
|
||||
cf_pin: GPIO14
|
||||
cf1_pin: GPIO13
|
||||
update_interval: 2s
|
||||
|
||||
current:
|
||||
name: "${entity_prefix} Current"
|
||||
device_class: current
|
||||
state_class: measurement
|
||||
unit_of_measurement: "A"
|
||||
accuracy_decimals: 2
|
||||
|
||||
voltage:
|
||||
name: "${entity_prefix} Voltage"
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
unit_of_measurement: "V"
|
||||
accuracy_decimals: 1
|
||||
|
||||
power:
|
||||
name: "${entity_prefix} 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:
|
||||
# - above: 4.0
|
||||
# then:
|
||||
@@ -209,6 +284,64 @@ sensor:
|
||||
# then:
|
||||
# - 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:
|
||||
# https://esphome.io/components/switch/
|
||||
|
||||
@@ -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
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungebookshelfleds.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.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)
|
||||
@@ -48,10 +49,10 @@
|
||||
# - Maximum ON time before automatic fade-down (1-48 h, 0 = no limit)
|
||||
#
|
||||
# MQTT COMMANDS:
|
||||
# - ${mqtt_local_command_topic}/light/set = ON
|
||||
# - ${mqtt_local_command_topic}/light/set = OFF
|
||||
# - ${mqtt_local_command_topic} = ON
|
||||
# - ${mqtt_local_command_topic} = OFF
|
||||
# - State publishes to:
|
||||
# - ${mqtt_local_status_topic}/light/state
|
||||
# - ${mqtt_local_status_topic}
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
# a) Home Assistant offline (network and MQTT online):
|
||||
@@ -84,7 +85,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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)
|
||||
api_key: !secret esp-api_key # If using HA API
|
||||
@@ -102,19 +103,19 @@ substitutions:
|
||||
mqtt_local_device_command_OFF: "OFF"
|
||||
|
||||
# Device Specific Settings
|
||||
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
||||
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
|
||||
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
|
||||
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
|
||||
log_level: "NONE" # Define logging level: NONE, ERROR, WARN, INFO, DEBUG (default), VERBOSE, VERY_VERBOSE
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
# Device Specific GPIO (so we can easily update for other devices)
|
||||
device_status_led: GPIO2
|
||||
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_fading_led: GPIO13 # if no LED, 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
|
||||
|
||||
#:########################################################################################:#
|
||||
# PACKAGES:
|
||||
@@ -132,8 +133,7 @@ packages:
|
||||
local_ota_pass: "${ota_pass}"
|
||||
|
||||
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||
common_api: !include
|
||||
#file: common/api_common.yaml
|
||||
common_api: !include #file: common/api_common.yaml
|
||||
file: common/api_common_noencryption.yaml
|
||||
vars:
|
||||
local_api_key: "${api_key}"
|
||||
@@ -245,12 +245,12 @@ globals:
|
||||
- id: min_brightness_pct
|
||||
type: int
|
||||
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)
|
||||
- id: max_brightness_pct
|
||||
type: int
|
||||
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
|
||||
- id: max_on_hours
|
||||
type: int
|
||||
@@ -260,23 +260,23 @@ globals:
|
||||
- id: led_gamma_f
|
||||
type: float
|
||||
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)
|
||||
- id: ramp_up_ms # fade-in when turned ON
|
||||
- id: ramp_up_ms # fade-in when turned ON
|
||||
type: int
|
||||
restore_value: true
|
||||
initial_value: "5000" # 5 s
|
||||
initial_value: "5000" # 5 s
|
||||
# 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
|
||||
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)
|
||||
- id: restart_mode
|
||||
type: int
|
||||
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.
|
||||
# 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.
|
||||
#:########################################################################################:#
|
||||
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)
|
||||
|
||||
#:########################################################################################:#
|
||||
@@ -351,12 +351,12 @@ status_led:
|
||||
mqtt:
|
||||
on_message:
|
||||
# Light control to ramp up
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
then:
|
||||
- switch.turn_on: mosfet_ramp_switch
|
||||
# Light control to ramp down
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
then:
|
||||
- switch.turn_off: mosfet_ramp_switch
|
||||
@@ -570,7 +570,7 @@ output:
|
||||
frequency: ${pwm_frequency}
|
||||
|
||||
- platform: gpio
|
||||
id: green_led_out # Green LED
|
||||
id: green_led_out # Green LED
|
||||
pin:
|
||||
number: ${device_fading_led}
|
||||
inverted: false
|
||||
@@ -593,7 +593,7 @@ light:
|
||||
on_turn_on:
|
||||
- output.turn_on: green_led_out
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -617,7 +617,7 @@ light:
|
||||
lambda: "return !id(booting);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
retain: true
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
@@ -957,7 +957,7 @@ script:
|
||||
- id: deferred_restore_brightness
|
||||
mode: restart
|
||||
then:
|
||||
- delay: 5s # let preferences/globals load and light settle
|
||||
- delay: 5s # let preferences/globals load and light settle
|
||||
- lambda: |-
|
||||
// Use ONLY the exact last saved brightness.
|
||||
float target = id(last_brightness_pct);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-loungecabinetleds.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.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)
|
||||
@@ -50,10 +51,10 @@
|
||||
# - Maximum ON time before automatic fade-down (1-48 h, 0 = no limit)
|
||||
#
|
||||
# MQTT COMMANDS:
|
||||
# - ${mqtt_local_command_topic}/light/set = ON
|
||||
# - ${mqtt_local_command_topic}/light/set = OFF
|
||||
# - ${mqtt_local_command_topic} = ON
|
||||
# - ${mqtt_local_command_topic} = OFF
|
||||
# - State publishes to:
|
||||
# - ${mqtt_local_status_topic}/light/state
|
||||
# - ${mqtt_local_status_topic}
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
# a) Home Assistant offline (network and MQTT online)
|
||||
@@ -80,7 +81,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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)
|
||||
api_key: !secret esp-api_key # If using HA API
|
||||
@@ -130,8 +131,7 @@ packages:
|
||||
local_current_ip_address: "${current_ip_address}"
|
||||
|
||||
#### HOME ASSISTANT API (choose encryption or no encryption options) ####
|
||||
common_api: !include
|
||||
#file: common/api_common.yaml
|
||||
common_api: !include #file: common/api_common.yaml
|
||||
file: common/api_common_noencryption.yaml
|
||||
vars:
|
||||
local_api_key: "${api_key}"
|
||||
@@ -181,32 +181,32 @@ esphome:
|
||||
# Mode 0: Fade up to full (obeys fade settings; no on/off flicker)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 0;'
|
||||
lambda: "return id(restart_mode) == 0;"
|
||||
then:
|
||||
- lambda: 'id(ramp_switch_target_on) = true;'
|
||||
- lambda: "id(ramp_switch_target_on) = true;"
|
||||
- script.execute: ramp_on_script
|
||||
|
||||
# Mode 1: Restore Brightness (deferred to avoid races)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 1;'
|
||||
lambda: "return id(restart_mode) == 1;"
|
||||
then:
|
||||
- script.execute: deferred_restore_brightness
|
||||
|
||||
# Mode 2: Remain Off (no blip; stays off)
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restart_mode) == 2;'
|
||||
lambda: "return id(restart_mode) == 2;"
|
||||
then:
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: mosfet_leds
|
||||
transition_length: 0s
|
||||
|
||||
# Boot complete: allow OFF handlers again
|
||||
- lambda: 'id(booting) = false;'
|
||||
- lambda: "id(booting) = false;"
|
||||
|
||||
# NEW: force indicator OFF after boot logic
|
||||
# (Removed: output.turn_off: green_led_out)
|
||||
@@ -365,13 +365,13 @@ status_led:
|
||||
mqtt:
|
||||
on_message:
|
||||
# Light control to ramp up
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
then:
|
||||
- switch.turn_on: mosfet_ramp_switch
|
||||
|
||||
# Light control to ramp down
|
||||
- topic: "${mqtt_local_command_topic}/light/set"
|
||||
- topic: "${mqtt_local_command_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
then:
|
||||
- switch.turn_off: mosfet_ramp_switch
|
||||
@@ -503,7 +503,7 @@ binary_sensor:
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(ramp_switch_target_on);'
|
||||
lambda: "return id(ramp_switch_target_on);"
|
||||
then:
|
||||
# Target is currently ON -> press should go OFF (start ramp-down)
|
||||
- lambda: |-
|
||||
@@ -608,7 +608,7 @@ light:
|
||||
on_turn_on:
|
||||
- output.turn_on: green_led_out
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_ON}"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -619,7 +619,7 @@ light:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -629,13 +629,13 @@ light:
|
||||
- output.turn_off: green_led_out
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(booting);'
|
||||
lambda: "return !id(booting);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic}/light/state"
|
||||
topic: "${mqtt_local_status_topic}"
|
||||
payload: "${mqtt_local_device_command_OFF}"
|
||||
retain: true
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- script.stop: max_on_watchdog
|
||||
- lambda: |-
|
||||
id(last_brightness_pct) = 0.0f;
|
||||
@@ -724,10 +724,10 @@ number:
|
||||
set_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return x <= 0.0f;'
|
||||
lambda: "return x <= 0.0f;"
|
||||
then:
|
||||
# 0 means OFF
|
||||
- lambda: 'id(suppress_slider_sync) = true;'
|
||||
- lambda: "id(suppress_slider_sync) = true;"
|
||||
- script.stop: ramp_on_script
|
||||
- script.stop: ramp_off_script
|
||||
- light.turn_off:
|
||||
@@ -739,7 +739,7 @@ number:
|
||||
id(last_brightness_pct) = 0.0f; // persist OFF immediately
|
||||
id(last_set_pos) = 0;
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
# Map 1..100 - [min..max] and set ON
|
||||
- lambda: |-
|
||||
@@ -771,7 +771,7 @@ number:
|
||||
if (out_pct > maxp) out_pct = maxp;
|
||||
id(last_brightness_pct) = out_pct; // persist exact target now
|
||||
- delay: 400ms
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
|
||||
- platform: template
|
||||
id: cfg_max_on_hours
|
||||
@@ -794,12 +794,12 @@ number:
|
||||
id(cfg_max_on_hours).publish_state((float) hrs);
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mosfet_leds).current_values.is_on();'
|
||||
lambda: "return id(mosfet_leds).current_values.is_on();"
|
||||
then:
|
||||
- script.stop: max_on_watchdog
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- script.execute: max_on_watchdog
|
||||
|
||||
@@ -813,7 +813,7 @@ script:
|
||||
- id: ramp_on_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
# Ensure we start at at least the floor without a visible "pop".
|
||||
- if:
|
||||
@@ -825,12 +825,12 @@ script:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: 80ms
|
||||
# Ramp from current (>= floor) to cap over a fraction of ramp_up_ms.
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(max_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(max_brightness_pct) / 100.0f;"
|
||||
transition_length: !lambda |-
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -850,7 +850,7 @@ script:
|
||||
if (frac > 1.0f) frac = 1.0f;
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
@@ -863,7 +863,7 @@ script:
|
||||
- id: ramp_to_target_script
|
||||
mode: restart
|
||||
then:
|
||||
- lambda: 'id(is_ramping) = true;'
|
||||
- lambda: "id(is_ramping) = true;"
|
||||
- script.stop: ramp_off_script
|
||||
# Ensure we start at the floor cleanly.
|
||||
- if:
|
||||
@@ -875,7 +875,7 @@ script:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: 80ms
|
||||
# Ramp from current (>= floor) to restore_target_pct over a fraction of ramp_up_ms.
|
||||
- light.turn_on:
|
||||
@@ -906,7 +906,7 @@ script:
|
||||
if (frac > 1.0f) frac = 1.0f;
|
||||
id(last_ramp_ms) = (int) (id(ramp_up_ms) * frac);
|
||||
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: |-
|
||||
id(is_ramping) = false;
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
@@ -925,7 +925,7 @@ script:
|
||||
- script.stop: ramp_on_script
|
||||
- light.turn_on:
|
||||
id: mosfet_leds
|
||||
brightness: !lambda 'return id(min_brightness_pct) / 100.0f;'
|
||||
brightness: !lambda "return id(min_brightness_pct) / 100.0f;"
|
||||
transition_length: !lambda |-
|
||||
const auto &cv = id(mosfet_leds).current_values;
|
||||
const float floor = id(min_brightness_pct) / 100.0f;
|
||||
@@ -938,7 +938,7 @@ script:
|
||||
if (frac > 1.0f) frac = 1.0f;
|
||||
id(last_ramp_ms) = (int) (id(ramp_down_ms) * frac);
|
||||
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:
|
||||
id: mosfet_leds
|
||||
transition_length: 150ms
|
||||
@@ -955,12 +955,12 @@ script:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(max_on_hours) > 0;'
|
||||
lambda: "return id(max_on_hours) > 0;"
|
||||
then:
|
||||
- delay: !lambda 'return (uint32_t) (id(max_on_hours) * 3600000UL);'
|
||||
- delay: !lambda "return (uint32_t) (id(max_on_hours) * 3600000UL);"
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(mosfet_leds).current_values.is_on();'
|
||||
lambda: "return id(mosfet_leds).current_values.is_on();"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = false;
|
||||
@@ -987,18 +987,18 @@ script:
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(restore_target_pct) > 0.5f;'
|
||||
lambda: "return id(restore_target_pct) > 0.5f;"
|
||||
then:
|
||||
- lambda: |-
|
||||
id(ramp_switch_target_on) = true; // reflect our intent
|
||||
id(suppress_slider_sync) = true;
|
||||
- script.stop: ramp_off_script
|
||||
- script.execute: ramp_to_target_script
|
||||
- delay: !lambda 'return (uint32_t) id(last_ramp_ms);'
|
||||
- lambda: 'id(suppress_slider_sync) = false;'
|
||||
- delay: !lambda "return (uint32_t) id(last_ramp_ms);"
|
||||
- lambda: "id(suppress_slider_sync) = false;"
|
||||
else:
|
||||
# Exact last was 0 -> remain OFF
|
||||
- lambda: 'id(ramp_switch_target_on) = false;'
|
||||
- lambda: "id(ramp_switch_target_on) = false;"
|
||||
- light.turn_off:
|
||||
id: mosfet_leds
|
||||
transition_length: 0s
|
||||
|
||||
@@ -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)"
|
||||
@@ -6,6 +6,11 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-mainpowermonitor.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.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.
|
||||
@@ -21,6 +26,11 @@
|
||||
# OPERATION NOTES:
|
||||
# - Power Monitor using 3x PZEM-004T V3 (Modbus) for 3-phase monitoring
|
||||
# - 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 binary sensor to indicate when the EV charger is operating (threshold configurable)
|
||||
#
|
||||
@@ -60,7 +70,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Wemos.D1Mini PZEM-004T V3 3-Phase Power Monitor"
|
||||
project_version: "v1.3"
|
||||
project_version: "v1.4"
|
||||
|
||||
# Passwords
|
||||
api_key: !secret esp-api_key
|
||||
@@ -202,6 +212,10 @@ esphome:
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${description_comment}"
|
||||
area: "${device_area}"
|
||||
|
||||
# Restrict PlatformIO to one simultaneous compiler process to reduce host RAM use.
|
||||
compile_process_limit: 1
|
||||
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
@@ -467,11 +481,16 @@ sensor:
|
||||
power:
|
||||
name: "${pzem_red_p_name}"
|
||||
id: pzem_red_power_w
|
||||
unit_of_measurement: "W"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
accuracy_decimals: 0
|
||||
energy:
|
||||
name: "${pzem_red_e_name}"
|
||||
id: pzem_red_energy_kwh
|
||||
unit_of_measurement: "kWh"
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
@@ -537,11 +556,16 @@ sensor:
|
||||
power:
|
||||
name: "${pzem_yellow_p_name}"
|
||||
id: pzem_yellow_power_w
|
||||
unit_of_measurement: "W"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
accuracy_decimals: 0
|
||||
energy:
|
||||
name: "${pzem_yellow_e_name}"
|
||||
id: pzem_yellow_energy_kwh
|
||||
unit_of_measurement: "kWh"
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
@@ -607,11 +631,16 @@ sensor:
|
||||
power:
|
||||
name: "${pzem_blue_p_name}"
|
||||
id: pzem_blue_power_w
|
||||
unit_of_measurement: "W"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
accuracy_decimals: 0
|
||||
energy:
|
||||
name: "${pzem_blue_e_name}"
|
||||
id: pzem_blue_energy_kwh
|
||||
unit_of_measurement: "kWh"
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
@@ -665,6 +694,8 @@ sensor:
|
||||
name: "${mains_total_p_name}"
|
||||
id: mains_total_active_power_w
|
||||
unit_of_measurement: "W"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
accuracy_decimals: 0
|
||||
update_interval: ${pzem_update_interval}
|
||||
lambda: |-
|
||||
@@ -711,6 +742,8 @@ sensor:
|
||||
name: "${mains_total_e_name}"
|
||||
id: mains_total_active_energy_kwh
|
||||
unit_of_measurement: "kWh"
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
accuracy_decimals: 3
|
||||
update_interval: ${pzem_update_interval}
|
||||
lambda: |-
|
||||
@@ -957,6 +990,8 @@ sensor:
|
||||
name: "${ev_power_name}"
|
||||
id: ev_power_avg_w
|
||||
unit_of_measurement: "W"
|
||||
device_class: power
|
||||
state_class: measurement
|
||||
accuracy_decimals: 0
|
||||
update_interval: "${ev_power_avg_sample_interval}"
|
||||
lambda: |-
|
||||
@@ -966,6 +1001,8 @@ sensor:
|
||||
name: "${ev_total_energy_name}"
|
||||
id: ev_total_energy_kwh
|
||||
unit_of_measurement: "kWh"
|
||||
device_class: energy
|
||||
state_class: total_increasing
|
||||
accuracy_decimals: 3
|
||||
update_interval: 10s
|
||||
lambda: |-
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-masterbdrmsouth.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.
|
||||
# Relay 1 on both switches = North Lights virtual/Tasmota control.
|
||||
# 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.
|
||||
#
|
||||
# - North Lights retained status/sync topic:
|
||||
# ${mqtt_local_status_topic1}/state
|
||||
# ${mqtt_local_status_topic1}
|
||||
# Payload "On" / "Off" mirrors the actual North Lights state.
|
||||
#
|
||||
# - South Lights local command topic:
|
||||
@@ -65,7 +66,7 @@
|
||||
# NOTE: TOGGLE is intentionally not handled here to avoid ambiguous multi-device toggles.
|
||||
#
|
||||
# - South Lights retained status/sync topic:
|
||||
# ${mqtt_local_status_topic2}/state
|
||||
# ${mqtt_local_status_topic2}
|
||||
# Payload "On" / "Off" mirrors the actual South Lights state.
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
@@ -87,7 +88,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed).
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -101,7 +101,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Zemismart Technologies.KS-811 Triple"
|
||||
project_version: "v1.3"
|
||||
project_version: "v1.4"
|
||||
|
||||
# Passwords & Secrets
|
||||
api_key: !secret esp-api_key
|
||||
@@ -218,22 +218,22 @@ globals:
|
||||
- id: north_update_from_sync
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
- id: south_update_from_sync
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
- id: north_lights_state
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
- id: south_lights_state
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
#:########################################################################################:#
|
||||
# MQTT: Device-specific MQTT command + retained status/sync triggers
|
||||
@@ -275,7 +275,7 @@ mqtt:
|
||||
}
|
||||
|
||||
# Retained North status is a secondary mirror/sync path.
|
||||
- topic: "${mqtt_local_status_topic1}/state"
|
||||
- topic: "${mqtt_local_status_topic1}"
|
||||
then:
|
||||
- lambda: |-
|
||||
if (x == "On" || x == "ON" || x == "on") {
|
||||
@@ -291,7 +291,7 @@ mqtt:
|
||||
# retained status after the physical Relay 2 changes, and this switch mirrors
|
||||
# that status onto its local Relay 2.
|
||||
# -------------------------------------------------------------------------
|
||||
- topic: "${mqtt_local_status_topic2}/state"
|
||||
- topic: "${mqtt_local_status_topic2}"
|
||||
then:
|
||||
- lambda: |-
|
||||
if (x == "On" || x == "ON" || x == "on") {
|
||||
@@ -410,7 +410,7 @@ script:
|
||||
id(north_update_from_sync) = true;
|
||||
- switch.turn_on: relay_1
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic1}/state"
|
||||
topic: "${mqtt_local_status_topic1}"
|
||||
payload: "On"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -424,7 +424,7 @@ script:
|
||||
id(north_update_from_sync) = true;
|
||||
- switch.turn_off: relay_1
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic1}/state"
|
||||
topic: "${mqtt_local_status_topic1}"
|
||||
payload: "Off"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-masterbdrmwest.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.
|
||||
# Relay 1 on both switches = North Lights virtual/Tasmota control.
|
||||
# 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.
|
||||
#
|
||||
# - North Lights retained status/sync topic:
|
||||
# ${mqtt_local_status_topic1}/state
|
||||
# ${mqtt_local_status_topic1}
|
||||
# Payload "On" / "Off" mirrors the actual North Lights state.
|
||||
#
|
||||
# - South Lights local command topic:
|
||||
@@ -65,7 +66,7 @@
|
||||
# NOTE: TOGGLE is intentionally not handled here to avoid ambiguous multi-device toggles.
|
||||
#
|
||||
# - South Lights retained status/sync topic:
|
||||
# ${mqtt_local_status_topic2}/state
|
||||
# ${mqtt_local_status_topic2}
|
||||
# Payload "On" / "Off" published when Relay 2 changes state.
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
@@ -88,7 +89,6 @@
|
||||
# - Accurate time is NOT needed (SNTP not needed).
|
||||
#:########################################################################################:#
|
||||
|
||||
|
||||
#:########################################################################################:#
|
||||
# SUBSTITUTIONS: Specific device variable substitutions
|
||||
# If NOT using a secrets file, just replace these with the passwords etc (in quotes)
|
||||
@@ -102,7 +102,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
project_name: "Zemismart Technologies.KS-811 Triple"
|
||||
project_version: "v1.3"
|
||||
project_version: "v1.4"
|
||||
|
||||
# Passwords & Secrets
|
||||
api_key: !secret esp-api_key
|
||||
@@ -217,22 +217,22 @@ globals:
|
||||
- id: north_update_from_sync
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
- id: south_update_from_sync
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
- id: north_lights_state
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
- id: south_lights_state
|
||||
type: bool
|
||||
restore_value: false
|
||||
initial_value: 'false'
|
||||
initial_value: "false"
|
||||
|
||||
#:########################################################################################:#
|
||||
# MQTT: Device-specific MQTT command + retained status/sync triggers
|
||||
@@ -274,7 +274,7 @@ mqtt:
|
||||
}
|
||||
|
||||
# Retained North status is a secondary mirror/sync path.
|
||||
- topic: "${mqtt_local_status_topic1}/state"
|
||||
- topic: "${mqtt_local_status_topic1}"
|
||||
then:
|
||||
- lambda: |-
|
||||
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.
|
||||
- topic: "${mqtt_local_status_topic2}/state"
|
||||
- topic: "${mqtt_local_status_topic2}"
|
||||
then:
|
||||
- lambda: |-
|
||||
if (x == "On" || x == "ON" || x == "on") {
|
||||
@@ -415,7 +415,7 @@ script:
|
||||
id(north_update_from_sync) = true;
|
||||
- switch.turn_on: relay_1
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic1}/state"
|
||||
topic: "${mqtt_local_status_topic1}"
|
||||
payload: "On"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -429,7 +429,7 @@ script:
|
||||
id(north_update_from_sync) = true;
|
||||
- switch.turn_off: relay_1
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic1}/state"
|
||||
topic: "${mqtt_local_status_topic1}"
|
||||
payload: "Off"
|
||||
retain: true
|
||||
- lambda: |-
|
||||
@@ -554,10 +554,10 @@ switch:
|
||||
}
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(south_update_from_sync);'
|
||||
lambda: "return !id(south_update_from_sync);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic2}/state"
|
||||
topic: "${mqtt_local_status_topic2}"
|
||||
payload: "On"
|
||||
retain: true
|
||||
on_turn_off:
|
||||
@@ -568,10 +568,10 @@ switch:
|
||||
}
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return !id(south_update_from_sync);'
|
||||
lambda: "return !id(south_update_from_sync);"
|
||||
then:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_local_status_topic2}/state"
|
||||
topic: "${mqtt_local_status_topic2}"
|
||||
payload: "Off"
|
||||
retain: true
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -6,6 +6,7 @@
|
||||
# https://home.fox.co.nz/gitea/zorruno/zorruno-homeassistant/src/branch/master/esphome/esp-officeusbhubpower.yaml
|
||||
#:########################################################################################:#
|
||||
# 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.0 2025-06-14 Initial Version
|
||||
#:########################################################################################:#
|
||||
@@ -22,10 +23,10 @@
|
||||
#:########################################################################################:#
|
||||
# MQTT COMMANDS:
|
||||
# Command (set relay):
|
||||
# - ${mqtt_command_topic}/relay1/set -> ON, OFF
|
||||
# - ${mqtt_command_topic} -> ON, OFF
|
||||
#
|
||||
# Status (published from template sensor):
|
||||
# - ${mqtt_status_topic}/relay1/state -> ON, OFF
|
||||
# - ${mqtt_status_topic} -> ON, OFF
|
||||
#:########################################################################################:#
|
||||
# OFFLINE NOTES:
|
||||
# a) Home Assistant OFFLINE, but Network and MQTT ONLINE
|
||||
@@ -54,7 +55,7 @@ substitutions:
|
||||
|
||||
# Project Naming
|
||||
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)
|
||||
api_key: !secret esp-api_key
|
||||
@@ -201,12 +202,12 @@ binary_sensor:
|
||||
return id(relay1).state;
|
||||
on_press:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic}/relay1/state"
|
||||
topic: "${mqtt_status_topic}"
|
||||
payload: "ON"
|
||||
retain: false
|
||||
on_release:
|
||||
- mqtt.publish:
|
||||
topic: "${mqtt_status_topic}/relay1/state"
|
||||
topic: "${mqtt_status_topic}"
|
||||
payload: "OFF"
|
||||
retain: false
|
||||
|
||||
@@ -227,12 +228,12 @@ status_led:
|
||||
#:########################################################################################:#
|
||||
mqtt:
|
||||
on_message:
|
||||
- topic: "${mqtt_command_topic}/relay1/set"
|
||||
- topic: "${mqtt_command_topic}"
|
||||
payload: "ON"
|
||||
then:
|
||||
- switch.turn_on: relay1
|
||||
|
||||
- topic: "${mqtt_command_topic}/relay1/set"
|
||||
- topic: "${mqtt_command_topic}"
|
||||
payload: "OFF"
|
||||
then:
|
||||
- switch.turn_off: relay1
|
||||
File diff suppressed because it is too large
Load Diff
@@ -191,7 +191,7 @@ globals:
|
||||
- id: restore_mode
|
||||
type: int
|
||||
restore_value: yes
|
||||
initial_value: "2"
|
||||
initial_value: "0"
|
||||
|
||||
#:########################################################################################:#
|
||||
# SELECT COMPONENT:
|
||||
|
||||
@@ -21,11 +21,11 @@ substitutions:
|
||||
#friendly_name: "Main Dishwashers Power"
|
||||
friendly_name: "Spare1 Athomv1"
|
||||
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_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
|
||||
api_key: !secret esp-api_key # unfortunately you can't use substitutions inside secrets names
|
||||
@@ -38,7 +38,7 @@ substitutions:
|
||||
|
||||
# Device Settings
|
||||
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
|
||||
@@ -90,18 +90,13 @@ esphome:
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 200
|
||||
then:
|
||||
- switch.turn_on: "relay"
|
||||
|
||||
##########################################################################################
|
||||
# ESP Platform and Framework
|
||||
# https://esphome.io/components/esp32.html
|
||||
##########################################################################################
|
||||
esp8266:
|
||||
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:
|
||||
flash_write_interval: 5min
|
||||
@@ -117,7 +112,7 @@ mdns:
|
||||
# https://esphome.io/components/logger.html
|
||||
##########################################################################################
|
||||
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)
|
||||
#esp8266_store_log_strings_in_flash: false
|
||||
#tx_buffer_size: 64
|
||||
@@ -149,8 +144,8 @@ binary_sensor:
|
||||
- min_length: 20ms
|
||||
max_length: 500ms
|
||||
then:
|
||||
- switch.toggle:
|
||||
id: relay
|
||||
- switch.toggle:
|
||||
id: relay
|
||||
|
||||
- platform: template
|
||||
name: "Relay Status"
|
||||
@@ -166,7 +161,6 @@ switch:
|
||||
name: "Power Output"
|
||||
pin: GPIO14
|
||||
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
|
||||
icon: "${relay_icon}"
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,6 +0,0 @@
|
||||
family_away:
|
||||
name: All Family Members Away
|
||||
entities:
|
||||
- !secret person_1
|
||||
- !secret person_2
|
||||
- !secret person_3
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -4,17 +4,60 @@
|
||||
# PACKAGE: 16A EV Charger Control
|
||||
###############################################################################
|
||||
#
|
||||
# FILE:
|
||||
# packages/16a_ev_charging_controls.yaml
|
||||
#
|
||||
# VERSION:
|
||||
# V1.2 2026-07-20
|
||||
# - Added structured mode, timing, and restart behavior documentation.
|
||||
#
|
||||
# V1.1 2026-05-15
|
||||
# - Reworked 1hr topup logic to use a hard deadline and watchdog.
|
||||
# - Removed dependency on timer.finished event for relay shutoff.
|
||||
# - 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.
|
||||
#
|
||||
# V1.0 2026-05-11
|
||||
# - 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:
|
||||
# Car display name:
|
||||
# - Leaf
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# PACKAGE: Appliance Monitor Processor
|
||||
################################################################################
|
||||
#
|
||||
# Version: 1.2
|
||||
# Date: 2026-06-09
|
||||
# Version: 1.3
|
||||
# Date: 2026-07-11
|
||||
#
|
||||
# Purpose:
|
||||
# Shared appliance power monitoring processor.
|
||||
@@ -20,6 +20,7 @@
|
||||
# - One MQTT sensor per appliance.
|
||||
# - Timestamp-based energy and cost calculation.
|
||||
# - Rolling average state detection.
|
||||
# - Completion announcements through script.view_road_announcement.
|
||||
#
|
||||
# Default values:
|
||||
# debug_flow: false
|
||||
@@ -40,6 +41,16 @@
|
||||
# Finished
|
||||
#
|
||||
# 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:
|
||||
# - Added min_cycle_notify_seconds.
|
||||
# - Finished cycles shorter than this are still recorded, but do not send
|
||||
@@ -53,17 +64,39 @@
|
||||
#
|
||||
################################################################################
|
||||
# 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
|
||||
# method there.
|
||||
################################################################################
|
||||
#
|
||||
# The finishing script calls:
|
||||
#
|
||||
# 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:
|
||||
# am_announcement_channels
|
||||
# am_announcement_title
|
||||
# am_short_announcement
|
||||
# am_long_announcement
|
||||
# am_matrix_indicator
|
||||
# announcement_enabled
|
||||
# announcement_channels
|
||||
# title
|
||||
# short_announcement
|
||||
# long_announcement
|
||||
# announcement_payload
|
||||
# payload
|
||||
# matrix_indicator
|
||||
# mqtt_feed
|
||||
#
|
||||
################################################################################
|
||||
|
||||
@@ -127,6 +160,59 @@ script:
|
||||
step: 1
|
||||
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:
|
||||
##########################################################################
|
||||
# CORE DEFAULTS AND PASSED VALUES
|
||||
@@ -156,10 +242,36 @@ script:
|
||||
##########################################################################
|
||||
# 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:
|
||||
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: >-
|
||||
{{ 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: >-
|
||||
{{ short_announcement | default((am_appliance_action | title) ~ ' complete', true) | string }}
|
||||
@@ -170,7 +282,20 @@ script:
|
||||
am_announcement_title: >-
|
||||
{{ 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
|
||||
@@ -622,6 +747,12 @@ script:
|
||||
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_finished | bool(false)
|
||||
@@ -631,6 +762,8 @@ script:
|
||||
am_cycle_notification_suppressed_reason: >-
|
||||
{% if am_cycle_notification_suppressed | bool(false) %}
|
||||
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 %}
|
||||
|
||||
##########################################################################
|
||||
@@ -673,6 +806,10 @@ script:
|
||||
|
||||
min_cycle_notify_seconds=am_min_cycle_notify_seconds | int(300),
|
||||
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_reason=am_cycle_notification_suppressed_reason | trim,
|
||||
|
||||
@@ -740,18 +877,45 @@ script:
|
||||
threshold. No announcement was sent.
|
||||
|
||||
##########################################################################
|
||||
# FINISHED ANNOUNCEMENT
|
||||
##########################################################################
|
||||
# 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.
|
||||
# ANNOUNCEMENT DISABLED LOG
|
||||
##########################################################################
|
||||
|
||||
- if:
|
||||
- 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:
|
||||
- action: script.view_road_announcement
|
||||
data:
|
||||
@@ -759,5 +923,6 @@ script:
|
||||
title: "{{ am_announcement_title }}"
|
||||
short_announcement: "{{ am_short_announcement }}"
|
||||
long_announcement: "{{ am_long_announcement }}"
|
||||
payload: ""
|
||||
payload: "{{ am_announcement_payload }}"
|
||||
indicator: "{{ am_matrix_indicator }}"
|
||||
mqtt_feed: "{{ am_mqtt_feed }}"
|
||||
|
||||
@@ -1,49 +1,85 @@
|
||||
################################################################################
|
||||
# PACKAGE: Appliances Status Monitoring
|
||||
################################################################################
|
||||
#:########################################################################################:#
|
||||
# Appliances Status Monitoring Package #
|
||||
#:########################################################################################:#
|
||||
#
|
||||
# Version: 1.4
|
||||
# Date: 2026-06-09
|
||||
# FILE:
|
||||
# appliances_status_monitoring.yaml
|
||||
#
|
||||
# Purpose:
|
||||
# VERSION:
|
||||
# V1.5 2026-07-11
|
||||
#
|
||||
# PURPOSE:
|
||||
# Appliance status monitoring package for multiple appliances.
|
||||
#
|
||||
# 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.
|
||||
# 3. Calls script.appliance_monitor_process_sample.
|
||||
# 4. Passes per-appliance completion announcement settings to the processor.
|
||||
#
|
||||
# Processor package required:
|
||||
# appliance_monitor_processor.yaml
|
||||
#
|
||||
# Announcement router required:
|
||||
# view_road_announcement_router.yaml
|
||||
# REQUIRED PACKAGES:
|
||||
# appliance_monitor_processor.yaml V1.3 or newer
|
||||
# view_road_announcement_router.yaml V1.8 or newer
|
||||
#
|
||||
# MQTT:
|
||||
# MQTT is used only as retained state storage for appliance monitor sensors.
|
||||
# Sensor entities are created using MQTT discovery.
|
||||
# MQTT is used as retained state storage for appliance monitor sensors.
|
||||
# Sensor entities are created using MQTT Discovery.
|
||||
#
|
||||
# Adding another appliance:
|
||||
# Add one entry to the am_appliances list.
|
||||
# ADDING AN APPLIANCE:
|
||||
# Add one entry to the am_appliances registry below.
|
||||
#
|
||||
# Version 1.4:
|
||||
# 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.
|
||||
# ANNOUNCEMENT ROUTER CHANNELS:
|
||||
# The following channel IDs are accepted by the current announcement router:
|
||||
#
|
||||
# Version 1.3:
|
||||
# Removed the is_number gate before calling the processor. The processor is
|
||||
# now always called once per appliance per sample, and power is safely converted
|
||||
# to 0 if the source sensor is unavailable or non-numeric.
|
||||
# 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.
|
||||
# - 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:
|
||||
- id: 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.
|
||||
alias: "Appliances Status Monitoring - Process Samples"
|
||||
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
|
||||
max: 20
|
||||
|
||||
@@ -57,17 +93,17 @@ automation:
|
||||
id: periodic_sample
|
||||
|
||||
variables:
|
||||
##########################################################################
|
||||
# MQTT LOCAL CONTROLS
|
||||
##########################################################################
|
||||
#:####################################################################################:#
|
||||
# MQTT LOCAL CONTROLS #
|
||||
#:####################################################################################:#
|
||||
|
||||
mqtt_device_name: "appliance_monitor"
|
||||
mqtt_local_status_topic: !secret mqtt_status_main_topic
|
||||
mqtt_discovery_prefix: "homeassistant"
|
||||
|
||||
##########################################################################
|
||||
# DEFAULTS
|
||||
##########################################################################
|
||||
#:####################################################################################:#
|
||||
# PROCESSOR DEFAULTS #
|
||||
#:####################################################################################:#
|
||||
|
||||
am_default_debug_flow: false
|
||||
am_default_resolution: 2
|
||||
@@ -75,91 +111,184 @@ automation:
|
||||
am_default_operating_power_min: 12
|
||||
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_payload: ""
|
||||
|
||||
# Optional appliance-specific overrides available:
|
||||
# resolution: 4
|
||||
# The number of samples for the average, with one sample per minute.
|
||||
#:####################################################################################:#
|
||||
# OPTIONAL PER-APPLIANCE OVERRIDES #
|
||||
#:####################################################################################:#
|
||||
#
|
||||
# standby_power: 3
|
||||
# Below this value, the appliance will show "Off".
|
||||
# Between this value and operating_power_min, it will show "Standby".
|
||||
# debug_flow: true
|
||||
# Writes state-processing details to the Home Assistant logbook.
|
||||
#
|
||||
# operating_power_min: 20
|
||||
# The minimum average power needed for the appliance to show "Operating".
|
||||
# resolution: 4
|
||||
# Number of one-minute samples used for the rolling average.
|
||||
#
|
||||
# min_cycle_notify_seconds: 600
|
||||
# Finished cycles shorter than this are recorded, but no notification
|
||||
# is sent. Default is 300 seconds.
|
||||
# standby_power: 3
|
||||
# Below this value, the appliance shows Off.
|
||||
# Between standby_power and operating_power_min, it shows Standby.
|
||||
#
|
||||
# announcement_channels: "pushover_zorruno"
|
||||
# Announcement channels for this appliance.
|
||||
# operating_power_min: 20
|
||||
# Minimum rolling-average power required for Operating.
|
||||
#
|
||||
# long_announcement: "The device has finished. Please empty it when convenient."
|
||||
# Override the long announcement.
|
||||
# min_cycle_notify_seconds: 600
|
||||
# Minimum cycle duration before a completion announcement is allowed.
|
||||
#
|
||||
# min_cycle_notify_seconds: 600
|
||||
# Minimum time for an appliance to be operating before a completion announcement generated.
|
||||
# announcement_enabled: false
|
||||
# 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:
|
||||
#:##################################################################################:#
|
||||
# Washing Machine #
|
||||
#:##################################################################################:#
|
||||
|
||||
- appliance_id: washing_machine
|
||||
appliance_name: "Washing Machine"
|
||||
appliance_action: "washing"
|
||||
power_entity: "sensor.esp_laundrywashpow_load_power"
|
||||
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
|
||||
standby_power: 3
|
||||
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_name: "Dryer"
|
||||
appliance_action: "drying"
|
||||
power_entity: "sensor.esp_laundrydrypow_load_power"
|
||||
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
|
||||
standby_power: 3
|
||||
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_name: "Main Dishwasher"
|
||||
appliance_action: "dishwashing"
|
||||
power_entity: "sensor.esp_maindishwasherpower_power"
|
||||
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
|
||||
standby_power: 3
|
||||
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_name: "Downstairs Dishwasher"
|
||||
appliance_action: "dishwashing"
|
||||
power_entity: "sensor.esp_downstdishwasher_power"
|
||||
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
|
||||
standby_power: 3
|
||||
operating_power_min: 30
|
||||
|
||||
announcement_enabled: true
|
||||
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_name: "EV Charger (32A)"
|
||||
@@ -167,88 +296,134 @@ automation:
|
||||
power_entity: "sensor.main_house_3_phase_power_monitor_32a_wallcharger_power"
|
||||
monitor_sensor: "sensor.appliance_monitor_ev_charger_32a_monitor"
|
||||
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
|
||||
standby_power: 3
|
||||
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_name: "EV Charger (16A)"
|
||||
appliance_action: "charging"
|
||||
power_entity: "sensor.main_house_3_phase_power_monitor_32a_wallcharger_power"
|
||||
monitor_sensor: "sensor.garage_db_control_16a_wallcharger_power"
|
||||
power_entity: "sensor.garage_db_control_16a_wallcharger_power"
|
||||
monitor_sensor: "sensor.appliance_monitor_ev_charger_16a_monitor"
|
||||
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
|
||||
standby_power: 3
|
||||
operating_power_min: 100
|
||||
|
||||
announcement_enabled: true
|
||||
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_name: "Ryobi Charger Left"
|
||||
appliance_action: "charging"
|
||||
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"
|
||||
short_announcement: "/s/sRyobi Charging Left"
|
||||
# long_announcement: # Created by the processor
|
||||
matrix_indicator: "ryo1,1,15,10,6,5,1,0"
|
||||
|
||||
resolution: 4
|
||||
standby_power: 1
|
||||
operating_power_min: 20
|
||||
|
||||
announcement_enabled: true
|
||||
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_name: "Ryobi Charger Right"
|
||||
appliance_action: "charging"
|
||||
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"
|
||||
short_announcement: "/s/sRyobi Charging Right"
|
||||
# long_announcement: # Created by the processor
|
||||
matrix_indicator: "ryo2,1,15,10,6,5,1,0"
|
||||
|
||||
resolution: 4
|
||||
standby_power: 1
|
||||
operating_power_min: 20
|
||||
|
||||
announcement_enabled: true
|
||||
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_name: "Pool Pump"
|
||||
appliance_action: "pumping"
|
||||
power_entity: "sensor.esp_poolpumppower_power"
|
||||
#monitor_sensor: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
|
||||
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
|
||||
standby_power: 5
|
||||
operating_power_min: 20
|
||||
|
||||
announcement_enabled: true
|
||||
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_name: "3D Printer"
|
||||
appliance_action: "printing"
|
||||
power_entity: "sensor.esp_3dprinterpow_load_power"
|
||||
#monitor_sensor: "sensor.ryobi_charger_right_power_monitor_x16pp_power"
|
||||
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
|
||||
standby_power: 2
|
||||
operating_power_min: 20
|
||||
|
||||
announcement_enabled: true
|
||||
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:
|
||||
##########################################################################
|
||||
# PUBLISH / REFRESH MQTT DISCOVERY CONFIG
|
||||
##########################################################################
|
||||
#:####################################################################################:#
|
||||
# PUBLISH / REFRESH MQTT DISCOVERY CONFIG #
|
||||
#:####################################################################################:#
|
||||
|
||||
- repeat:
|
||||
for_each: "{{ am_appliances }}"
|
||||
@@ -285,19 +460,18 @@ automation:
|
||||
identifiers=[mqtt_device_name],
|
||||
name='Appliance Monitor',
|
||||
manufacturer='View Road Home Assistant',
|
||||
model='Shared appliance power monitor'
|
||||
model='Shared appliance power monitor',
|
||||
sw_version='1.5'
|
||||
)
|
||||
) | to_json
|
||||
}}
|
||||
|
||||
##########################################################################
|
||||
# PROCESS SAMPLES
|
||||
##########################################################################
|
||||
#:####################################################################################:#
|
||||
# PROCESS SAMPLES #
|
||||
#:####################################################################################:#
|
||||
#
|
||||
# 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:
|
||||
for_each: "{{ am_appliances }}"
|
||||
@@ -309,36 +483,97 @@ automation:
|
||||
am_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: >-
|
||||
{{ 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: >-
|
||||
{{ repeat.item.announcement_channels | default(am_default_announcement_channels, true) }}
|
||||
{{
|
||||
repeat.item.announcement_channels
|
||||
| default(am_default_announcement_channels, true)
|
||||
}}
|
||||
|
||||
am_debug_flow: >-
|
||||
{{ repeat.item.debug_flow | default(am_default_debug_flow, true) }}
|
||||
am_announcement_payload: >-
|
||||
{{
|
||||
repeat.item.announcement_payload
|
||||
| default(am_default_announcement_payload, true)
|
||||
}}
|
||||
|
||||
am_resolution: >-
|
||||
{{ repeat.item.resolution | default(am_default_resolution, true) }}
|
||||
am_mqtt_feed: >-
|
||||
{{
|
||||
repeat.item.mqtt_feed
|
||||
| default(repeat.item.appliance_id, true)
|
||||
}}
|
||||
|
||||
am_standby_power: >-
|
||||
{{ repeat.item.standby_power | default(am_default_standby_power, true) }}
|
||||
am_short_announcement: >-
|
||||
{{
|
||||
repeat.item.short_announcement
|
||||
| default(
|
||||
(repeat.item.appliance_action | title) ~ ' complete',
|
||||
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) }}
|
||||
am_matrix_indicator: >-
|
||||
{{ repeat.item.matrix_indicator | default('', true) }}
|
||||
|
||||
am_long_announcement: >-
|
||||
{{ 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
|
||||
data:
|
||||
appliance_id: "{{ repeat.item.appliance_id }}"
|
||||
@@ -355,8 +590,11 @@ automation:
|
||||
operating_power_min: "{{ am_operating_power_min }}"
|
||||
min_cycle_notify_seconds: "{{ am_min_cycle_notify_seconds }}"
|
||||
|
||||
announcement_enabled: "{{ am_announcement_enabled }}"
|
||||
announcement_channels: "{{ am_announcement_channels }}"
|
||||
short_announcement: "{{ repeat.item.short_announcement }}"
|
||||
long_announcement: "{{ am_long_announcement }}"
|
||||
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:
|
||||
# Jobs Tracker Processor
|
||||
@@ -9,20 +9,122 @@
|
||||
# jobs_tracker_processor.yaml
|
||||
#
|
||||
# VERSION:
|
||||
# V1.3.1 2026-06-20
|
||||
# V1.6 2026-07-10
|
||||
#
|
||||
# CHANGELOG:
|
||||
# V1.3.1:
|
||||
# PURPOSE:
|
||||
# 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
|
||||
# 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
|
||||
# due time.
|
||||
# - Added "Not Due" state.
|
||||
# - Added Not Due state.
|
||||
# - Completion is recalculated from retained last_completed_ts after restart.
|
||||
#
|
||||
################################################################################
|
||||
#:########################################################################################:#
|
||||
|
||||
script:
|
||||
jobs_tracker_update_job:
|
||||
@@ -59,6 +161,12 @@ script:
|
||||
mark_complete:
|
||||
description: "Set true when a human has completed the job."
|
||||
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:
|
||||
- variables:
|
||||
@@ -73,6 +181,17 @@ script:
|
||||
now_ts: "{{ as_timestamp(now()) }}"
|
||||
today_midnight_ts: "{{ as_timestamp(today_at('00:00')) }}"
|
||||
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:
|
||||
current_period_start_ts: >-
|
||||
@@ -98,6 +217,28 @@ script:
|
||||
{{ (today_midnight_ts | float(0)) + 86400 }}
|
||||
{% 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:
|
||||
current_due_ts: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
@@ -110,6 +251,19 @@ script:
|
||||
{{ (current_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||
{% 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: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
{{ (next_period_start_ts | float(0)) + (due_seconds | int) }}
|
||||
@@ -133,6 +287,60 @@ script:
|
||||
120
|
||||
{% 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:
|
||||
stored_last_completed_ts: >-
|
||||
{% set v = state_attr(state_entity, 'last_completed_ts') %}
|
||||
@@ -141,20 +349,52 @@ script:
|
||||
last_completed_ts: >-
|
||||
{{ now_ts if mark_complete_bool else stored_last_completed_ts }}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Completion and timing state #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
complete_bool: >-
|
||||
{{ (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: >-
|
||||
{% if repeat_type_safe == 'daily' %}
|
||||
{{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
|
||||
{% else %}
|
||||
true
|
||||
{% endif %}
|
||||
{{ (now_ts | float(0)) >= (current_due_ts | float(0)) }}
|
||||
|
||||
overdue_started_bool: >-
|
||||
{{ (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: >-
|
||||
{% if (now_ts | float(0)) <= (current_due_ts | float(0)) %}
|
||||
0
|
||||
@@ -169,10 +409,16 @@ script:
|
||||
{{ (((now_ts | float(0)) - (current_due_ts | float(0))) / 3600) | round(0) | int }}
|
||||
{% endif %}
|
||||
|
||||
#:####################################################################################:#
|
||||
# Display state #
|
||||
#:####################################################################################:#
|
||||
|
||||
- variables:
|
||||
job_status: >-
|
||||
{% if complete_bool | bool %}
|
||||
Complete
|
||||
{% elif missed_bool | bool %}
|
||||
Missed
|
||||
{% elif not (due_started_bool | bool) %}
|
||||
Not Due
|
||||
{% elif overdue_started_bool | bool %}
|
||||
@@ -265,52 +511,64 @@ script:
|
||||
{{ ts | timestamp_custom('%Y-%m-%d @ %H:%M', true) }}
|
||||
{% 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
|
||||
data:
|
||||
topic: "{{ topic_safe }}"
|
||||
retain: true
|
||||
qos: 1
|
||||
payload: >-
|
||||
{{
|
||||
{
|
||||
"job_id": job_id,
|
||||
"job_name": job_name,
|
||||
"repeat": repeat_type_safe,
|
||||
"due_time": due_time_safe,
|
||||
"due_weekday": due_weekday_safe | int,
|
||||
"due_day": due_day_safe | int,
|
||||
|
||||
"state": job_status | trim,
|
||||
"complete": complete_bool | bool,
|
||||
|
||||
"last_completed_ts": last_completed_ts | float(0),
|
||||
"last_completed_time": last_completed_time,
|
||||
"last_completed_human": last_completed_human,
|
||||
|
||||
"completed_for_period_ts": current_period_start_ts | float(0) if complete_bool | bool else 0,
|
||||
"completed_for_due_ts": current_period_start_ts | float(0) if complete_bool | bool else 0,
|
||||
|
||||
"current_period_start_ts": current_period_start_ts | float(0),
|
||||
"next_period_start_ts": next_period_start_ts | float(0),
|
||||
|
||||
"current_due_ts": current_due_ts | float(0),
|
||||
"next_due_ts": next_due_ts | float(0),
|
||||
"display_due_ts": display_due_ts | float(0),
|
||||
"next_due_human": next_due_human,
|
||||
|
||||
"due_started": due_started_bool | bool,
|
||||
"overdue_started": overdue_started_bool | bool,
|
||||
|
||||
"minutes_since_required": minutes_since_required | int(0),
|
||||
"hours_since_required": hours_since_required | int(0),
|
||||
|
||||
"overdue_after_minutes": overdue_after_minutes | int(0),
|
||||
"overdue_minutes": overdue_minutes | int(0),
|
||||
"overdue_hours": overdue_hours | int(0),
|
||||
|
||||
"hours_since_completed": hours_since_completed,
|
||||
|
||||
"last_updated_local": (now_ts | float(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true),
|
||||
"mqtt_topic": topic_safe
|
||||
} | tojson
|
||||
}}
|
||||
{
|
||||
"job_id": {{ job_id | tojson }},
|
||||
"job_name": {{ job_name | tojson }},
|
||||
"repeat": {{ repeat_type_safe | tojson }},
|
||||
"due_time": {{ due_time_safe | tojson }},
|
||||
"due_weekday": {{ due_weekday_safe | int }},
|
||||
"due_day": {{ due_day_safe | int }},
|
||||
"state": {{ job_status | trim | tojson }},
|
||||
"complete": {{ complete_bool | bool | tojson }},
|
||||
"last_completed_ts": {{ last_completed_ts | float(0) }},
|
||||
"last_completed_time": {{ last_completed_time | tojson }},
|
||||
"last_completed_human": {{ last_completed_human | tojson }},
|
||||
"previous_period_start_ts": {{ previous_period_start_ts | float(0) }},
|
||||
"previous_due_ts": {{ previous_due_ts | float(0) }},
|
||||
"previous_completed": {{ previous_completed_bool | bool | tojson }},
|
||||
"previous_missed": {{ previous_missed_bool | bool | tojson }},
|
||||
"previous_missed_active": {{ previous_missed_active_bool | bool | tojson }},
|
||||
"missed": {{ missed_bool | bool | tojson }},
|
||||
"completion_window_start": {{ completion_window_start_safe | tojson }},
|
||||
"completion_window_end": {{ completion_window_end_safe | tojson }},
|
||||
"has_completion_window": {{ has_completion_window | bool | tojson }},
|
||||
"completion_window_missed": {{ completion_window_missed_bool | bool | tojson }},
|
||||
"current_completion_window_start_ts": {{ current_completion_window_start_ts | float(0) }},
|
||||
"current_completion_window_end_ts": {{ current_completion_window_end_ts | float(0) }},
|
||||
"completed_for_period_ts": {{ completed_for_period_ts_safe | float(0) }},
|
||||
"completed_for_due_ts": {{ completed_for_period_ts_safe | float(0) }},
|
||||
"current_period_start_ts": {{ current_period_start_ts | float(0) }},
|
||||
"next_period_start_ts": {{ next_period_start_ts | float(0) }},
|
||||
"current_due_ts": {{ current_due_ts | float(0) }},
|
||||
"next_due_ts": {{ next_due_ts | float(0) }},
|
||||
"display_due_ts": {{ display_due_ts | float(0) }},
|
||||
"next_due_human": {{ next_due_human | tojson }},
|
||||
"due_started": {{ due_started_bool | bool | tojson }},
|
||||
"overdue_started": {{ overdue_started_bool | bool | tojson }},
|
||||
"minutes_since_required": {{ minutes_since_required | int(0) }},
|
||||
"hours_since_required": {{ hours_since_required | int(0) }},
|
||||
"overdue_after_minutes": {{ overdue_after_minutes | int(0) }},
|
||||
"overdue_minutes": {{ overdue_minutes | int(0) }},
|
||||
"overdue_hours": {{ overdue_hours | int(0) }},
|
||||
"hours_since_completed": {{ hours_since_completed | tojson }},
|
||||
"last_updated_local": {{ ((now_ts | float(0)) | timestamp_custom('%Y-%m-%d %H:%M:%S', true)) | 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:
|
||||
# Stair Lights Top
|
||||
- id: stair_lights_top_2way_sync
|
||||
@@ -9,7 +33,6 @@ automation:
|
||||
members:
|
||||
- switch.esp_centralstairs_top_relay_1_main_stair_lights
|
||||
- switch.esp_centralstairs_bottom_relay_1_main_stair_lights_lower
|
||||
- switch.esp_loungemiddleswitch_relay_3_downlights_north
|
||||
sync_on_start: true
|
||||
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:
|
||||
# Two lights and two switches for these corridor lights
|
||||
- id: garage_entry_outside_2way_sync
|
||||
|
||||
@@ -6,12 +6,16 @@
|
||||
# Pool Light Operation
|
||||
#
|
||||
# FILE:
|
||||
# packages/pool_light_boost_switch.yaml
|
||||
# packages/Pool_Controls/pool_light_operation.yaml
|
||||
#
|
||||
# VERSION:
|
||||
# V1.1 2026-06-29
|
||||
# V1.2 2026-07-20
|
||||
#
|
||||
# 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
|
||||
# - Tidied layout, comments, naming and automation IDs.
|
||||
# - Added structured package header and section comments.
|
||||
@@ -38,8 +42,15 @@
|
||||
# Operation topic:
|
||||
# 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:
|
||||
# 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
|
||||
target:
|
||||
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:
|
||||
customize:
|
||||
input_boolean.poolpump_mode_switch:
|
||||
icon: mdi:pump
|
||||
|
||||
#:########################################################################################:#
|
||||
# Input Booleans #
|
||||
#:########################################################################################:#
|
||||
|
||||
input_boolean:
|
||||
poolpump_mode_switch:
|
||||
name: "Pool Pump Operation"
|
||||
@@ -14,6 +72,10 @@ input_boolean:
|
||||
name: "Send TIMER at Midnight"
|
||||
initial: false
|
||||
|
||||
#:########################################################################################:#
|
||||
# Automations #
|
||||
#:########################################################################################:#
|
||||
|
||||
automation:
|
||||
- alias: "Pool Pump → send BOOST when user turns UI switch ON"
|
||||
trigger:
|
||||
@@ -90,6 +152,3 @@ automation:
|
||||
- service: input_boolean.turn_off
|
||||
data:
|
||||
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
|
||||
#
|
||||
# FILE:
|
||||
# pool_timer_selection.yaml
|
||||
# packages/Pool_Controls/pool_timers_selections.yaml
|
||||
#
|
||||
# VERSION:
|
||||
# V1.0 2026-06-29
|
||||
# V1.1 2026-07-20
|
||||
#
|
||||
# VERSION HISTORY:
|
||||
# V1.1 2026-07-20
|
||||
# - Corrected the documented package filename.
|
||||
# - Documented command selectors versus ESPHome timer feedback.
|
||||
#
|
||||
# V1.0 2026-06-29
|
||||
# - Initial package.
|
||||
# - Adds Home Assistant input_select helpers for Pool Pump and Pool Light timer times.
|
||||
@@ -36,6 +40,16 @@
|
||||
#
|
||||
# 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:
|
||||
# The current ESPHome timer logic only handles same-day timer windows:
|
||||
# current_mins >= on_time AND current_mins < off_time
|
||||
|
||||
@@ -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:
|
||||
- alias: "Turn on the Wifi Guest Network"
|
||||
description: "Asks the linked TP-Link Alexa skill to enable the guest Wi-Fi network."
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
@@ -14,6 +61,7 @@ automation:
|
||||
media_content_id: ask t.p.link to enable 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
|
||||
triggers:
|
||||
- trigger: state
|
||||
@@ -28,6 +76,7 @@ automation:
|
||||
media_content_id: ask t.p.link to disable guest network
|
||||
|
||||
- alias: "Switch guest network off after time"
|
||||
description: "Turns off the guest-network helper after it remains on for two hours."
|
||||
mode: single
|
||||
triggers:
|
||||
- trigger: state
|
||||
|
||||
@@ -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:
|
||||
- platform: anniversary
|
||||
name: events
|
||||
|
||||
@@ -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:
|
||||
sensor:
|
||||
- unique_id: washing_machine_finished
|
||||
@@ -13,6 +39,10 @@ mqtt:
|
||||
name: "Dishwasher Finished"
|
||||
state_topic: "viewroad-status/activityfeed/Dishwasher_complete"
|
||||
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
|
||||
name: "Leaf Done"
|
||||
state_topic: "viewroad-status/activityfeed/Leaf_Charger_complete"
|
||||
|
||||
@@ -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:
|
||||
# 1.2 - 2026-05-12
|
||||
# - Added fast Zigbee2MQTT FP1-style event triggers:
|
||||
# sensor.underhouse_mmwave_sensor_x23ms_presence_event = enter
|
||||
# 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.
|
||||
# Purpose:
|
||||
# Replaces the Node-RED automation for the underhouse entrance lights. Uses
|
||||
# Zigbee2MQTT mmWave event triggers (enter/approach/presence) to turn lights
|
||||
# on instantly, then off after 400 seconds of sustained clear presence.
|
||||
#
|
||||
# Notes:
|
||||
# - The UI may show the binary sensor as "Detected", but the YAML state is
|
||||
# normally "on".
|
||||
# - The presence_event sensor can react earlier than the binary presence sensor.
|
||||
# - 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.
|
||||
# Behaviour:
|
||||
# - Fast Zigbee2MQTT FP1-style event triggers (enter, approach) turn lights on.
|
||||
# - Binary presence sensor serves as a backup trigger and clearance condition.
|
||||
# - Lights turn off 400 seconds after presence clears.
|
||||
#
|
||||
################################################################################
|
||||
# Version History:
|
||||
# - v1.2.0 (2026-05-12): Added fast Zigbee2MQTT FP1-style event triggers and
|
||||
# converted header to standard package format.
|
||||
#
|
||||
#:########################################################################################:#
|
||||
|
||||
automation:
|
||||
- id: underhouse_entrance_auto_lights
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
#:########################################################################################:#
|
||||
# TITLE: UNDERHOUSE STORAGE LIGHTS AUTO LIGHTS PACKAGE
|
||||
#:########################################################################################:#
|
||||
# DESCRIPTION:
|
||||
# Package: Underhouse Storage Lights
|
||||
# File: auto_lights_underhouse_storage.yaml
|
||||
# Version: v1.1.0
|
||||
# Date: 2026-06-14
|
||||
#
|
||||
# Purpose:
|
||||
# Replaces the Node-RED automation for the underhouse storage lights.
|
||||
# Doors, PIR sensors, a remote switch, and local relay changes can start or
|
||||
# extend the storage lighting timer.
|
||||
#
|
||||
# VERSION:
|
||||
# V1.1 2026-06-14
|
||||
# - Door-open trigger now watches each real door contact individually.
|
||||
# - This means any door changing from closed to open turns the lights on
|
||||
# immediately, even if another door is already open.
|
||||
# - All doors closed now turns the lights off immediately.
|
||||
# - PIR occupancy can still start/restart the motion timer after that.
|
||||
# Behaviour:
|
||||
# - Individual door-open triggers turn lights on immediately.
|
||||
# - All doors closed turns lights off immediately.
|
||||
# - PIR occupancy can start/restart the motion timer.
|
||||
# - Remote switch press starts or extends the light timer.
|
||||
#
|
||||
# V1.0 2026-05-12
|
||||
# - Initial Home Assistant package version.
|
||||
#
|
||||
# EDIT NOTES:
|
||||
# - 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
|
||||
# Version History:
|
||||
# - v1.1.0 (2026-06-14): Door-open trigger now watches each real door contact
|
||||
# individually; all doors closed turns lights off immediately.
|
||||
# - v1.0.0 (2026-05-12): Initial Home Assistant package version.
|
||||
#:########################################################################################:#
|
||||
|
||||
binary_sensor:
|
||||
@@ -112,8 +108,7 @@ automation:
|
||||
entity_id:
|
||||
- binary_sensor.underhouse_stealth_door_x07rs_contact
|
||||
- binary_sensor.underhouse_north_door_door
|
||||
# FUTURE DOOR: replace this with the actual third underhouse storage door contact.
|
||||
#- binary_sensor.underhouse_future_door_contact
|
||||
- binary_sensor.under_house_underhouse_east_stealth_door_door
|
||||
from: "off"
|
||||
to: "on"
|
||||
|
||||
@@ -213,8 +208,7 @@ automation:
|
||||
entity_id:
|
||||
- binary_sensor.underhouse_stealth_door_x07rs_contact
|
||||
- binary_sensor.underhouse_north_door_door
|
||||
# FUTURE DOOR: replace this with the actual third underhouse storage door contact.
|
||||
#- binary_sensor.underhouse_future_door_contact
|
||||
- binary_sensor.under_house_underhouse_east_stealth_door_door
|
||||
from: "on"
|
||||
to: "off"
|
||||
action:
|
||||
|
||||
@@ -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
|
||||
@@ -1,8 +1,8 @@
|
||||
###############################################################################
|
||||
# Bedroom 1 Ikea Air Filter Package
|
||||
# File: /config/packages/bedroom_1_ikea_air_filter.yaml
|
||||
# Version: 1.1
|
||||
# Date: 2026-05-08
|
||||
# Version: 1.2
|
||||
# Date: 2026-07-20
|
||||
#
|
||||
# Purpose:
|
||||
# - Creates a dropdown helper called "Bedroom 1 Ikea Fan".
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# Behaviour notes:
|
||||
# - 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.
|
||||
# - There is no 10-minute re-check, so the fan will not ramp back up just
|
||||
# because the bedside lamp was switched off.
|
||||
@@ -80,12 +80,16 @@ automation:
|
||||
entity_id: input_boolean.quiet_time
|
||||
to: "on"
|
||||
|
||||
# Bedside lamp only forces low when it turns on.
|
||||
# Turning the lamp off does not change the fan speed.
|
||||
# The lamp state catches direct light control.
|
||||
- trigger: state
|
||||
entity_id: light.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb
|
||||
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.
|
||||
- trigger: time
|
||||
at:
|
||||
@@ -104,9 +108,10 @@ automation:
|
||||
- variables:
|
||||
trigger_entity: "{{ trigger.entity_id | default('') }}"
|
||||
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: >-
|
||||
{{
|
||||
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.state == 'on'
|
||||
}}
|
||||
|
||||
@@ -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:
|
||||
bedroom_3_led_actions:
|
||||
icon: mdi:led-strip
|
||||
|
||||
@@ -67,6 +67,12 @@ automation:
|
||||
|
||||
variables:
|
||||
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
|
||||
delay_seconds: 4
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#:########################################################################################:#
|
||||
# Package: BYD Charging Schedule At Home
|
||||
# File: byd_charging_schedule_athome.yaml
|
||||
# Version: v1.0.1
|
||||
# Date: 2026-06-19
|
||||
# Version: v1.1.0
|
||||
# Date: 2026-07-23
|
||||
#
|
||||
# Purpose:
|
||||
# Enables the BYD Atto 3 charging schedule when the car arrives home.
|
||||
@@ -11,11 +11,12 @@
|
||||
#
|
||||
# Behaviour:
|
||||
# - Confirmed arrival home:
|
||||
# Turns ON switch.atto_3_schedule_enabled,
|
||||
# but only if the car had previously been confirmed away.
|
||||
# Sets the schedule to 9:00 PM through midnight with charge-to-full disabled,
|
||||
# enables it, and retries cloud reconciliation once per minute.
|
||||
#
|
||||
# - 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:
|
||||
# If the car is already home and the user or another HA automation turns
|
||||
@@ -35,24 +36,35 @@ input_boolean:
|
||||
|
||||
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
|
||||
alias: "BYD Atto 3 Charging Schedule - Arrival Enable"
|
||||
description: >
|
||||
Turns the BYD charging schedule ON when the car arrives home, but only if
|
||||
it was previously confirmed away. This prevents brief away/home glitches
|
||||
from undoing a manual OFF while the car is actually still at home.
|
||||
Reconciles the BYD charging schedule to 9:00 PM through midnight and turns
|
||||
it ON when the car arrives home after a confirmed away period. Rechecks the
|
||||
cloud-confirmed state once per minute and retries failed updates.
|
||||
mode: single
|
||||
variables: &byd_charging_schedule_vars
|
||||
tracker_entity: device_tracker.byd_atto3_ibeacon_bur4c2a_bermuda_tracker
|
||||
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".
|
||||
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.
|
||||
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.
|
||||
ignored_tracker_states:
|
||||
@@ -69,7 +81,7 @@ automation:
|
||||
{{ trigger.to_state is not none
|
||||
and trigger.from_state is not none
|
||||
and trigger.to_state.state == home_state
|
||||
and trigger.from_state.state != home_state }}
|
||||
and trigger.from_state.state == 'not_home' }}
|
||||
|
||||
actions:
|
||||
- choose:
|
||||
@@ -78,23 +90,72 @@ automation:
|
||||
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
|
||||
state: "on"
|
||||
sequence:
|
||||
- action: switch.turn_on
|
||||
- action: input_boolean.turn_off
|
||||
target:
|
||||
entity_id: "{{ schedule_switch_entity }}"
|
||||
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
|
||||
|
||||
- action: input_boolean.turn_off
|
||||
target:
|
||||
entity_id: input_boolean.byd_atto3_charging_schedule_confirmed_away
|
||||
- repeat:
|
||||
count: "{{ schedule_verify_attempts | int(6) }}"
|
||||
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
|
||||
alias: "BYD Atto 3 Charging Schedule - Departure Disable After Timeout"
|
||||
description: >
|
||||
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
|
||||
marks the car as confirmed away.
|
||||
is still away, marks it confirmed away, turns the BYD charging schedule OFF,
|
||||
and retries failed cloud updates once per minute.
|
||||
mode: restart
|
||||
variables: *byd_charging_schedule_vars
|
||||
|
||||
@@ -111,7 +172,7 @@ automation:
|
||||
|
||||
actions:
|
||||
- delay:
|
||||
minutes: "{{ away_confirm_minutes | int(15) }}"
|
||||
minutes: "{{ away_confirm_minutes | int(10) }}"
|
||||
|
||||
- condition: template
|
||||
value_template: >-
|
||||
@@ -119,14 +180,28 @@ automation:
|
||||
{{ current_state != home_state
|
||||
and current_state not in ignored_tracker_states }}
|
||||
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: "{{ schedule_switch_entity }}"
|
||||
|
||||
- action: input_boolean.turn_on
|
||||
target:
|
||||
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
|
||||
#:######################################################################################:#
|
||||
@@ -134,8 +209,8 @@ automation:
|
||||
alias: "BYD Atto 3 Charging Schedule - Startup Away Check"
|
||||
description: >
|
||||
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
|
||||
confirmed away so that the next confirmed arrival can enable the schedule.
|
||||
beyond the configured timeout, mark it confirmed away and reconcile the
|
||||
charging schedule OFF so the next confirmed arrival can enable it.
|
||||
mode: single
|
||||
variables: *byd_charging_schedule_vars
|
||||
|
||||
@@ -153,7 +228,7 @@ automation:
|
||||
and current_state not in ignored_tracker_states }}
|
||||
|
||||
- delay:
|
||||
minutes: "{{ away_confirm_minutes | int(15) }}"
|
||||
minutes: "{{ away_confirm_minutes | int(10) }}"
|
||||
|
||||
- condition: template
|
||||
value_template: >-
|
||||
@@ -161,10 +236,24 @@ automation:
|
||||
{{ current_state != home_state
|
||||
and current_state not in ignored_tracker_states }}
|
||||
|
||||
- action: switch.turn_off
|
||||
target:
|
||||
entity_id: "{{ schedule_switch_entity }}"
|
||||
|
||||
- action: input_boolean.turn_on
|
||||
target:
|
||||
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 }}"
|
||||
|
||||
@@ -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:
|
||||
binary_sensor:
|
||||
- unique_id: front_door_movement_camera_corner
|
||||
name: "Front Door Movement"
|
||||
state_topic: "viewroad-status/cctv/front_door_motion"
|
||||
payload_on: "1"
|
||||
payload_off: "0"
|
||||
icon: mdi:exit-run
|
||||
|
||||
@@ -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:
|
||||
moon_phase_percent_overlay:
|
||||
name: Moon phase percent overlay
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
################################################################################
|
||||
# PACKAGE: Downstairs Lights Bulk Activation
|
||||
# FILE: packages/downstairs_lights_bulk_activation.yaml
|
||||
# FILE: packages/downstairs_bulk_lights_on_from_stairs.yaml
|
||||
#
|
||||
# Version: 1.0
|
||||
# Date: 2026-04-26
|
||||
# Version: 1.1
|
||||
# Date: 2026-07-20
|
||||
#
|
||||
# Notes:
|
||||
# - Uses the virtual switch:
|
||||
@@ -16,16 +16,16 @@
|
||||
# Turns OFF the listed downstairs lights, but only if each light is currently ON.
|
||||
#
|
||||
# - No action is taken unless:
|
||||
# input_boolean.downstairs_flat_occupied is ON.
|
||||
# input_boolean.downstairs_flat_occupied is OFF.
|
||||
################################################################################
|
||||
|
||||
automation:
|
||||
- id: downstairs_lights_bulk_activation
|
||||
alias: "Downstairs Lights Bulk Activation"
|
||||
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
|
||||
flat occupied input_boolean is ON.
|
||||
flat is not occupied.
|
||||
|
||||
mode: single
|
||||
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
@@ -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:
|
||||
switch:
|
||||
- unique_id: mollies.internet
|
||||
|
||||
@@ -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:
|
||||
- id: publish_fridge_temps_drinks_fridge
|
||||
alias: publish_fridge_temps_drinks_fridge
|
||||
|
||||
@@ -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"
|
||||
@@ -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:
|
||||
# sensor.ha_summary_v2_...
|
||||
# FILE:
|
||||
# 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:
|
||||
|
||||
@@ -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 ----
|
||||
binary_sensor:
|
||||
- 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.
|
||||
#
|
||||
##########################################################################################
|
||||
|
||||
##########################################################################################
|
||||
|
||||
@@ -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:
|
||||
- sensor:
|
||||
- name: "Internet Download Traffic"
|
||||
unique_id: internet_downtraffic
|
||||
unit_of_measurement: "kB/s"
|
||||
state_class: measurement
|
||||
state: >
|
||||
{{ state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Received)') | float(0) }}
|
||||
availability: "{{ has_value('sensor.edgerouter_wan_download_rate') }}"
|
||||
state: "{{ states('sensor.edgerouter_wan_download_rate') | float(0) }}"
|
||||
|
||||
- name: "Internet Upload Traffic"
|
||||
unique_id: internet_uptraffic
|
||||
unit_of_measurement: "kB/s"
|
||||
state_class: measurement
|
||||
state: >
|
||||
{{ state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Sent)') | float(0) }}
|
||||
availability: "{{ has_value('sensor.edgerouter_wan_upload_rate') }}"
|
||||
state: "{{ states('sensor.edgerouter_wan_upload_rate') | float(0) }}"
|
||||
|
||||
- name: "Internet Total Traffic"
|
||||
unique_id: internet_totaltraffic
|
||||
unit_of_measurement: "kB/s"
|
||||
state_class: measurement
|
||||
state: >
|
||||
availability: >-
|
||||
{{
|
||||
(state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Received)') | float(0))
|
||||
+ (state_attr('binary_sensor.edgeos_interface_pppoe0', 'KBytes/ps (Sent)') | float(0))
|
||||
has_value('sensor.edgerouter_wan_download_rate')
|
||||
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))
|
||||
}}
|
||||
|
||||
@@ -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:
|
||||
sensor:
|
||||
- unique_id: kettle.temp
|
||||
|
||||
@@ -126,7 +126,7 @@ script:
|
||||
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "on"
|
||||
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "on" # non dimmable pair
|
||||
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
|
||||
switch.lounge_cupboard_dual_usb_xus09_l2: "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_rafter_buttons: 40
|
||||
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
|
||||
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "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_rafter_buttons: 30
|
||||
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
|
||||
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "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_rafter_buttons: 0
|
||||
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
|
||||
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off"
|
||||
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_rafter_buttons: 0
|
||||
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
|
||||
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off"
|
||||
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_lounge_rafter_buttons: 15
|
||||
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.tasmo_h801_loungeled1_6180_a: 25
|
||||
#light.tasmo_h801_loungeled1_6180_b: 25
|
||||
@@ -270,7 +270,7 @@ script:
|
||||
#switch.esp_loungemiddleswitch_relay_3_downlights_north: "off"
|
||||
switch.lounge_nspanel_touchscreen_lounge_stair_downlights: "off" # non dimmable pair
|
||||
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
|
||||
switch.lounge_cupboard_dual_usb_xus09_l2: "off"
|
||||
switch.esp_loungesouthleftswitch_relay_1_wall_washer_left: "off"
|
||||
|
||||
@@ -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:
|
||||
- sensor:
|
||||
- name: "Devices with low battery"
|
||||
@@ -9,7 +55,7 @@ template:
|
||||
{% 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 %}
|
||||
{% if st | int(-1) != -1 and (st | int(0)) <= 20 %}
|
||||
{% set ns.c = ns.c + 1 %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -32,21 +78,11 @@ template:
|
||||
mdi:battery-alert
|
||||
{% endif %}
|
||||
state: >
|
||||
{% set ns = namespace(names=[]) %}
|
||||
{% for s in states.sensor %}
|
||||
{% 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 %}
|
||||
{% set count = states('sensor.devices_with_low_battery') | int(0) %}
|
||||
{% if count == 0 %}
|
||||
None
|
||||
{% else %}
|
||||
{{ ns.names | join(', ') }}
|
||||
{{ count }} devices
|
||||
{% endif %}
|
||||
attributes:
|
||||
count: "{{ states('sensor.devices_with_low_battery') | int(0) }}"
|
||||
@@ -56,7 +92,7 @@ template:
|
||||
{% 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 %}
|
||||
{% if st | int(-1) != -1 and (st | int(0)) <= 20 %}
|
||||
{% set ns.e = ns.e + [s.entity_id] %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -69,10 +105,29 @@ template:
|
||||
{% 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 %}
|
||||
{% if st | int(-1) != -1 and (st | int(0)) <= 20 %}
|
||||
{% set ns.n = ns.n + [s.name] %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ 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 }}
|
||||
|
||||
@@ -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:
|
||||
- id: "1669966515993"
|
||||
alias: Turn off Lounge TV
|
||||
description: ""
|
||||
description: "Turns off the lounge TV when the MQTT OFF command is received."
|
||||
trigger:
|
||||
- platform: mqtt
|
||||
topic: viewroad-commands/maintv/action
|
||||
@@ -16,6 +61,7 @@ automation:
|
||||
|
||||
- id: tv_amp_follow_remote_state
|
||||
alias: Lounge surround amp follows TV remote (5s debounce)
|
||||
description: "Follows the stable lounge TV remote state after a five-second debounce."
|
||||
mode: restart
|
||||
trigger:
|
||||
- platform: state
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#media_player:
|
||||
# MPD Media Player on Lounge Touchscreen
|
||||
# - platform: mpd
|
||||
# host: !secret lounge_mpd_player
|
||||
# scan_interval: 1
|
||||
@@ -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"
|
||||
@@ -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:
|
||||
- platform: niwa_tides
|
||||
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
|
||||
@@ -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:
|
||||
# sensor:
|
||||
# - unique_id: office_media_button_1
|
||||
@@ -6,10 +52,14 @@
|
||||
# unit_of_measurement: "Press"
|
||||
# value_template: "{{ value_json.ZbReceived.Office_Media_Button.Power }}"
|
||||
|
||||
#:########################################################################################:#
|
||||
# Active ZHA Automations #
|
||||
#:########################################################################################:#
|
||||
|
||||
automation:
|
||||
- id: 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:
|
||||
- platform: event
|
||||
event_type: zha_event
|
||||
@@ -26,7 +76,7 @@ automation:
|
||||
mode: single
|
||||
- id: 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:
|
||||
- platform: event
|
||||
event_type: zha_event
|
||||
@@ -43,7 +93,7 @@ automation:
|
||||
mode: single
|
||||
- id: 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:
|
||||
- platform: event
|
||||
event_type: zha_event
|
||||
@@ -62,6 +112,8 @@ automation:
|
||||
target:
|
||||
device_id: abb6b5a6b4e4925dcb3a77ea2c293eaa
|
||||
mode: single
|
||||
|
||||
# Historical disabled MQTT and entity-state alternatives.
|
||||
# - id: "1629271273958"
|
||||
# alias: Pause office TV on MQTT
|
||||
# description: ""
|
||||
@@ -90,6 +142,7 @@ automation:
|
||||
# seek_position: 30
|
||||
# mode: single
|
||||
|
||||
# Historical disabled entity-state automation block.
|
||||
#automation:
|
||||
# - id: "1629271273952"
|
||||
# unique_id: pause_office_tv_on_media_button
|
||||
|
||||
@@ -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 }}
|
||||
@@ -3,6 +3,9 @@
|
||||
###############################################################################
|
||||
#
|
||||
# Version:
|
||||
# 2026-07-20 v0.3
|
||||
# - Synchronized the documented Number 16 schedule with the implementation.
|
||||
#
|
||||
# 2026-05-09 v0.2
|
||||
# - Refactored scheduled control to use editable schedule lists.
|
||||
# - Switched lights now use explicit ON/OFF schedule entries instead of
|
||||
@@ -527,8 +530,8 @@ script:
|
||||
# EDIT SCHEDULE HERE
|
||||
# Format: trigger, offset_minutes/at, target_brightness, ramp_minutes
|
||||
# Current schedule:
|
||||
# - sunset -30, 80, 30
|
||||
# - 20:00, 50, 30
|
||||
# - sunset -30, 60, 30
|
||||
# - 20:00, 50, 120
|
||||
# - 01:00, 20, 120
|
||||
# - 06:00, 0, 120
|
||||
###########################################################################
|
||||
|
||||
@@ -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:
|
||||
sensor:
|
||||
- unique_id: dog_fed_activity
|
||||
@@ -24,3 +49,44 @@ mqtt:
|
||||
name: "Hours Since DePooped"
|
||||
state_topic: "viewroad-tele/petfeeding/hoursSinceDePooped"
|
||||
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 }}
|
||||
|
||||
@@ -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
@@ -5,7 +5,17 @@
|
||||
# TITLE:
|
||||
# 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
|
||||
# - Added quiet time start and stop entity action lists.
|
||||
# - Turns the LED matrix display OFF when quiet time starts.
|
||||
@@ -13,7 +23,8 @@
|
||||
#
|
||||
# V1.2 2026-05-21
|
||||
# - 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
|
||||
# - Reduced daily reset check frequency from every minute to every 30 minutes.
|
||||
@@ -33,20 +44,13 @@
|
||||
# will automatically be turned OFF.
|
||||
# - Default reset time is 07:00.
|
||||
# - 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:
|
||||
# - quiet_time_start_turn_off_entities:
|
||||
# Entities to turn OFF when quiet time starts.
|
||||
#
|
||||
# - quiet_time_start_turn_on_entities:
|
||||
# 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.
|
||||
# COMPANION PACKAGE:
|
||||
# - quiet_time_on_off_actions.yaml owns the editable entity action lists and
|
||||
# other local actions that run when Quiet Time starts or ends.
|
||||
# - This package owns input_boolean.quiet_time, the daily reset behaviour,
|
||||
# and MQTT status publishing.
|
||||
#
|
||||
# MQTT STATUS:
|
||||
# - Topic:
|
||||
|
||||
@@ -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:
|
||||
sensor:
|
||||
- unique_id: cctvquad1_cpu_temp
|
||||
|
||||
+31
-1
@@ -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:
|
||||
exclude:
|
||||
entity_globs:
|
||||
- sensor.esp_*_uptime*
|
||||
- sensor.ha_summary_v2_*
|
||||
entities:
|
||||
- sensor.jobs_tracker_summary
|
||||
|
||||
@@ -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:
|
||||
- light:
|
||||
schema: json
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
##########################################################################################
|
||||
# MASTER BEDROOM FAN (Tasmota / Sonoff iFan02)
|
||||
# Improvements:
|
||||
# - Numeric speed entity (0-3) reflecting actual device state via MQTT
|
||||
# - Button to cycle speed 1,2,3,0
|
||||
# - Keep input_select in sync with actual FanSpeed (incl after reboots)
|
||||
# Room Fan Control
|
||||
##########################################################################################
|
||||
#
|
||||
# TITLE:
|
||||
# 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:
|
||||
@@ -75,6 +121,7 @@ automation:
|
||||
- id: master_bedroom_fan_query_on_ha_start
|
||||
initial_state: true
|
||||
alias: Master Bedroom Fan - Query FanSpeed on HA Start
|
||||
description: "Requests the current master-bedroom FanSpeed when Home Assistant starts."
|
||||
trigger:
|
||||
- platform: homeassistant
|
||||
event: start
|
||||
@@ -90,6 +137,7 @@ automation:
|
||||
- id: master_bedroom_fan_sync_from_mqtt
|
||||
initial_state: true
|
||||
alias: Master Bedroom Fan - Sync Helpers From MQTT FanSpeed
|
||||
description: "Synchronizes the master-bedroom fan helpers from Tasmota MQTT feedback."
|
||||
trigger:
|
||||
- platform: mqtt
|
||||
topic: stat/tasmo-ifan02-3793-bedrm1-1/RESULT
|
||||
@@ -122,6 +170,7 @@ automation:
|
||||
- id: set_the_master_bedroom_fan
|
||||
initial_state: true
|
||||
alias: Set the Master Bedroom Fan
|
||||
description: "Publishes master-bedroom fan speed changes and updates the numeric helper optimistically."
|
||||
trigger:
|
||||
- platform: state
|
||||
entity_id: input_select.master_bedroom_fan_set
|
||||
@@ -145,11 +194,12 @@ automation:
|
||||
value: "{{ desired }}"
|
||||
|
||||
########################################################################################
|
||||
# BEDROOM 2 FAN - Unchanged
|
||||
# BEDROOM 2 FAN - Command Only
|
||||
########################################################################################
|
||||
- id: set_bedroom_2_fan
|
||||
initial_state: true
|
||||
alias: Set Bedroom 2 Fan Speed
|
||||
description: "Publishes Bedroom 2 fan speed dropdown changes to Tasmota."
|
||||
trigger:
|
||||
entity_id: input_select.bedroom_2_fan_set
|
||||
platform: state
|
||||
@@ -163,11 +213,12 @@ automation:
|
||||
'
|
||||
|
||||
########################################################################################
|
||||
# BEDROOM 3 FAN - Unchanged
|
||||
# BEDROOM 3 FAN - Command Only
|
||||
########################################################################################
|
||||
- id: set_bedroom_3_fan
|
||||
initial_state: true
|
||||
alias: Set Bedroom 3 Fan Speed
|
||||
description: "Publishes Bedroom 3 fan speed dropdown changes to Tasmota."
|
||||
trigger:
|
||||
entity_id: input_select.bedroom_3_fan_set
|
||||
platform: state
|
||||
|
||||
@@ -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:
|
||||
quiet_time:
|
||||
name: Quiet time for no notifications
|
||||
icon: mdi:shield-moon
|
||||
# Legacy duplicate retained temporarily for verification.
|
||||
# quiet_time:
|
||||
# name: Quiet time for no notifications
|
||||
# icon: mdi:shield-moon
|
||||
away_occupied_routine:
|
||||
name: Automation for Lights etc when away
|
||||
icon: mdi:shield-lock
|
||||
|
||||
@@ -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:
|
||||
main_bathroom_shower:
|
||||
name: Main Bathroom Shower
|
||||
@@ -14,6 +63,7 @@ sensor:
|
||||
automation:
|
||||
- id: main_bathroom_shower_detected
|
||||
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
|
||||
trigger:
|
||||
- platform: numeric_state
|
||||
@@ -31,6 +81,7 @@ automation:
|
||||
|
||||
- id: main_bathroom_shower_cleared
|
||||
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
|
||||
trigger:
|
||||
- platform: numeric_state
|
||||
|
||||
@@ -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:
|
||||
simulation_lights_all:
|
||||
name: Simulation Lights (All)
|
||||
#:######################################################################################:#
|
||||
# FAMILY PRESENCE
|
||||
#:######################################################################################:#
|
||||
family_away:
|
||||
name: "All Family Members Away"
|
||||
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_bottom_relay_1_main_stair_lights_lower
|
||||
- switch.esp_entrancebathrmlights_relay_1_main_lights
|
||||
@@ -16,3 +47,62 @@ group:
|
||||
- light.esp_loungepelmetleds2_lounge_pelmet_leds_a
|
||||
- light.esp_lounge6chdimmer_couch_spots_left
|
||||
- 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"
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user