various tidyups

This commit is contained in:
zorruno
2026-08-30 00:25:42 +12:00
parent fd7aab565e
commit bce8abe48c
20 changed files with 7462 additions and 4146 deletions
+1 -1
View File
@@ -1 +1 @@
{"pid": 70, "version": 1, "ha_version": "2026.8.1", "start_ts": 1786755431.7882934} {"pid": 71, "version": 1, "ha_version": "2026.8.3", "start_ts": 1787912264.3193414}
+3750 -3750
View File
File diff suppressed because it is too large Load Diff
@@ -2,8 +2,8 @@
# PACKAGE: Appliance Monitor Processor # PACKAGE: Appliance Monitor Processor
################################################################################ ################################################################################
# #
# Version: 1.5 # Version: 1.7
# Date: 2026-07-26 # Date: 2026-08-17
# #
# Purpose: # Purpose:
# Shared appliance power monitoring processor. # Shared appliance power monitoring processor.
@@ -29,7 +29,7 @@
# operating_power_min: 12 # operating_power_min: 12
# paid_tariff_start: "00:00" # paid_tariff_start: "00:00"
# paid_tariff_end: "21:00" # paid_tariff_end: "21:00"
# paid_tariff_cost: 0.2023 # paid_tariff_cost: sensor.electricity_paid_rate (fallback 0.33235)
# offpeak_tariff_cost: 0.0 # offpeak_tariff_cost: 0.0
# max_sample_gap_seconds: 900 # max_sample_gap_seconds: 900
# min_cycle_notify_seconds: 300 # min_cycle_notify_seconds: 300
@@ -41,6 +41,14 @@
# Finished # Finished
# #
# Notes: # Notes:
# Version 1.7:
# - Use the central GST-inclusive electricity paid-rate sensor.
# - Update the fallback tariff from NZ$0.2023/kWh to NZ$0.33235/kWh.
#
# Version 1.6:
# - Preserve an explicit false power_available value instead of replacing
# it with the default true value.
#
# Version 1.5: # Version 1.5:
# - Standardized Sony TV and Pushover appliance notification titles as # - Standardized Sony TV and Pushover appliance notification titles as
# "Appliance Notification". # "Appliance Notification".
@@ -248,13 +256,21 @@ script:
am_paid_tariff_start: "{{ paid_tariff_start | default('00:00', true) | string }}" am_paid_tariff_start: "{{ paid_tariff_start | default('00:00', true) | string }}"
am_paid_tariff_end: "{{ paid_tariff_end | default('21:00', true) | string }}" am_paid_tariff_end: "{{ paid_tariff_end | default('21:00', true) | string }}"
am_paid_tariff_cost: "{{ paid_tariff_cost | default(0.2023, true) | float(0.2023) }}" am_paid_tariff_cost: >-
{{
paid_tariff_cost
| default(
states('sensor.electricity_paid_rate') | float(0.33235),
true
)
| float(0.33235)
}}
am_offpeak_tariff_cost: "{{ offpeak_tariff_cost | default(0.0, true) | float(0.0) }}" am_offpeak_tariff_cost: "{{ offpeak_tariff_cost | default(0.0, true) | float(0.0) }}"
am_max_sample_gap_seconds: "{{ max_sample_gap_seconds | default(900, true) | int(900) }}" am_max_sample_gap_seconds: "{{ max_sample_gap_seconds | default(900, true) | int(900) }}"
am_min_cycle_notify_seconds: "{{ min_cycle_notify_seconds | default(300, true) | int(300) }}" am_min_cycle_notify_seconds: "{{ min_cycle_notify_seconds | default(300, true) | int(300) }}"
am_power_available: "{{ power_available | default(true, true) | bool(true) }}" am_power_available: "{{ power_available | default(true) | bool(true) }}"
########################################################################## ##########################################################################
# ANNOUNCEMENT DEFAULTS AND PASSED VALUES # ANNOUNCEMENT DEFAULTS AND PASSED VALUES
@@ -537,7 +553,7 @@ script:
am_tariff_rate: >- am_tariff_rate: >-
{% if am_is_paid_interval | bool(false) %} {% if am_is_paid_interval | bool(false) %}
{{ am_paid_tariff_cost | float(0.2023) }} {{ am_paid_tariff_cost | float(0.33235) }}
{% else %} {% else %}
{{ am_offpeak_tariff_cost | float(0.0) }} {{ am_offpeak_tariff_cost | float(0.0) }}
{% endif %} {% endif %}
@@ -679,7 +695,7 @@ script:
{% endif %} {% endif %}
am_cycle_offpeak_savings: >- am_cycle_offpeak_savings: >-
{% set val = ((am_cycle_energy_new_kwh | float(0)) * (am_paid_tariff_cost | float(0.2023))) - (am_cycle_cost_new | float(0)) %} {% set val = ((am_cycle_energy_new_kwh | float(0)) * (am_paid_tariff_cost | float(0.33235))) - (am_cycle_cost_new | float(0)) %}
{{ ([0, val] | max) | round(4) }} {{ ([0, val] | max) | round(4) }}
am_cycle_offpeak_savings_formatted: >- am_cycle_offpeak_savings_formatted: >-
@@ -6,7 +6,7 @@
# appliances_status_monitoring.yaml # appliances_status_monitoring.yaml
# #
# VERSION: # VERSION:
# V1.5 2026-07-11 # V1.8 2026-08-15
# #
# PURPOSE: # PURPOSE:
# Appliance status monitoring package for multiple appliances. # Appliance status monitoring package for multiple appliances.
@@ -51,6 +51,11 @@
# matrix_indicator. # matrix_indicator.
# #
# VERSION HISTORY: # VERSION HISTORY:
# V1.8 2026-08-15
# - Skip appliance processing while its power source is unavailable.
# - Preserve retained cycle state and rolling samples across HA restarts and
# source reconnects instead of treating missing measurements as zero.
#
# V1.7 2026-08-12 # V1.7 2026-08-12
# - Enabled MQTT activity-feed publication for every monitored appliance. # - Enabled MQTT activity-feed publication for every monitored appliance.
# #
@@ -593,6 +598,10 @@ automation:
am_power_available: >- am_power_available: >-
{{ states(repeat.item.power_entity) not in ['unknown', 'unavailable'] }} {{ states(repeat.item.power_entity) not in ['unknown', 'unavailable'] }}
- if:
- condition: template
value_template: "{{ am_power_available | bool(false) }}"
then:
- action: script.appliance_monitor_process_sample - action: script.appliance_monitor_process_sample
data: data:
appliance_id: "{{ repeat.item.appliance_id }}" appliance_id: "{{ repeat.item.appliance_id }}"
@@ -0,0 +1,56 @@
#:########################################################################################:#
# PACKAGE: Office Lights Home Assistant Fallback Synchronization
# FILE: Lighting_2Way_Sync/office_lights_ha_fallback_sync.yaml
# VERSION: v1.0.0
# DATE: 2026-08-16
#:########################################################################################:#
#
# PURPOSE:
# Provides the Home Assistant API transport for Office two-way light control
# when MQTT is unavailable. Direct MQTT remains the HA-down transport.
#
# OPERATION:
# - Synchronizes the Office bunker and overhead control pairs in both directions.
# - Uses idempotent Home Assistant actions, so MQTT and HA may both be online
# without creating a control loop.
# - Does not reconcile state during Home Assistant startup, preventing a stale
# restored entity state from changing a physical light.
#
# DEPENDENCIES:
# - Blueprint: zorruno/two_three_way_sync.yaml
# - esp-officelights and esp-officeduallights native ESPHome API connections.
#
# VERSION HISTORY:
# - v1.0.0 (2026-08-16): Initial MQTT-down HA fallback for Office lights.
#:########################################################################################:#
automation:
#:##########################################################################:#
# BUNKER LIGHTS
#:##########################################################################:#
- id: office_bunker_lights_ha_fallback_sync
alias: "Office Bunker Lights HA Fallback Sync"
initial_state: true
use_blueprint:
path: zorruno/two_three_way_sync.yaml
input:
members:
- switch.office_office_main_lightswitch_3_relay_1_bunker_lights
- light.esp_officeduallights_light_1_right_hand_bunker_light
sync_on_start: false
startup_delay: 5
#:##########################################################################:#
# OVERHEAD LIGHTS
#:##########################################################################:#
- id: office_overhead_lights_ha_fallback_sync
alias: "Office Overhead Lights HA Fallback Sync"
initial_state: true
use_blueprint:
path: zorruno/two_three_way_sync.yaml
input:
members:
- switch.office_office_main_lightswitch_3_relay_2_overhead_lights
- light.esp_officeduallights_light_2_cool_white_overhead_lights
sync_on_start: false
startup_delay: 5
-111
View File
@@ -1,111 +0,0 @@
#:########################################################################################:#
# TP-Link Deco Guest Wi-Fi Package #
#:########################################################################################:#
#
# TITLE:
# TP-Link Deco Guest Wi-Fi
#
# FILE:
# packages/alexa_tplink_actions.yaml
#
# VERSION:
# V2.1 2026-08-04
#
# VERSION HISTORY:
# V2.1 2026-08-04
# - Added sync automation so input_boolean.wifi_guest_network tracks the
# router's actual guest Wi-Fi status via the new guest-network
# binary sensor from the integration.
#
# V2.0 2026-08-04
# - Switched from Alexa voice commands to direct Deco local API control.
# - Uses tplink_deco.set_guest_network service for reliable on/off.
#
# 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 via the Deco's local
# API, using the tplink_deco custom integration's set_guest_network service.
#
# DEPENDENCIES:
# - tplink_deco integration must be configured and connected to the Deco.
# - input_boolean.wifi_guest_network is a UI-managed helper that represents
# the requested guest-network state.
# - binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network reports
# the actual guest-network state from the router and keeps the helper in
# sync.
#
# OPERATION NOTES:
# - The helper records requested state; the sync automation below then
# reconciles it against the router's actual state so the helper tracks
# the real guest-network status.
# - If the Deco API call fails, the helper can disagree with the
# actual guest-network state.
#
#:########################################################################################:#
automation:
- alias: "Turn on the Wifi Guest Network"
description: "Enables the TP-Link Deco guest Wi-Fi network via local API."
mode: single
triggers:
- trigger: state
entity_id: input_boolean.wifi_guest_network
to: "on"
actions:
- action: tplink_deco.set_guest_network
data:
enable: true
- alias: "Turn off the Wifi Guest Network"
description: "Disables the TP-Link Deco guest Wi-Fi network via local API."
mode: single
triggers:
- trigger: state
entity_id: input_boolean.wifi_guest_network
to: "off"
actions:
- action: tplink_deco.set_guest_network
data:
enable: false
- alias: "Sync Wifi Guest Network button with actual status"
description: >
Reconciles input_boolean.wifi_guest_network against the router's actual
guest Wi-Fi state reported by the guest-network binary sensor, so the
button reflects real status (including changes made outside HA).
mode: single
triggers:
- trigger: state
entity_id: binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network
to: "on"
- trigger: state
entity_id: binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network
to: "off"
conditions:
- condition: template
value_template: >
{{ states('input_boolean.wifi_guest_network')
!= states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') }}
actions:
- choose:
- conditions:
- condition: template
value_template: >
{{ states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') == 'on' }}
sequence:
- action: input_boolean.turn_on
target:
entity_id: input_boolean.wifi_guest_network
- conditions:
- condition: template
value_template: >
{{ states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') == 'off' }}
sequence:
- action: input_boolean.turn_off
target:
entity_id: input_boolean.wifi_guest_network
+636
View File
@@ -0,0 +1,636 @@
#:########################################################################################:#
# Battery Notes Alerts Package #
#:########################################################################################:#
#
# TITLE:
# Battery Notes Alerts
#
# FILE:
# packages/battery_notes_alerts.yaml
#
# VERSION:
# V1.1.0 2026-08-26
#
# PURPOSE:
# Sends immediate Pushover alerts when a non-temporary Battery Notes device newly
# reports a low battery or becomes unavailable, plus a Thursday weekly digest.
#
# Immediate alerts are limited to one alert of each type per device per calendar day.
#
# SAFETY / PERFORMANCE DESIGN:
# - No automation-level templated variables.
# - Low battery detection uses the native Battery Notes threshold event.
# - Weekly Battery Notes checks use raise_events: false.
# - Immediate automations never scan all Home Assistant states.
# - Immediate automations never call device_entities().
# - The offline watcher does only cheap filtering on state_changed events.
# - Offline alerts are inhibited for 10 minutes after Home Assistant starts.
# - Weekly list processing is bounded to 100 returned items per category.
# - Devices with "Temporary" anywhere in the Battery Notes note are always ignored.
#
# CONTROLS:
# - input_boolean.battery_notes_alerts_master_enable
# Master enable/disable for all notifications from this package.
#
# - Battery Notes - Immediate Low Alert
# May also be independently enabled/disabled in Automations.
#
# - Battery Notes - Immediate Offline Alert
# May also be independently enabled/disabled in Automations.
#
# - Battery Notes - Weekly Digest
# May also be independently enabled/disabled in Automations.
#
# OPERATION:
# - LOW:
# Native Battery Notes threshold event only.
# Reminder-generated events are ignored.
# Maximum one low alert per device per calendar day.
#
# - OFFLINE:
# Battery Notes Battery+ entities changing from a valid state to
# unknown/unavailable.
# Suppressed for 10 minutes after Home Assistant starts.
# Maximum one offline alert per device per calendar day.
#
# - WEEKLY:
# Thursday at 19:00.
# Reports current low batteries and batteries not reporting for 2+ days.
# Battery Notes devices marked Temporary are excluded.
#
# DEPENDENCIES:
# - Battery Notes integration
# - packages/view_road_announcement_router.yaml
#
# VERSION HISTORY:
# - V1.1.0 2026-08-26: Complete redesign using native Battery Notes events/actions.
# Removes high-frequency state scanning and top-level templates.
# Adds Temporary filtering, daily per-device alert suppression,
# startup offline suppression and master enable control.
# - V1.0.2 2026-08-25: RETIRED - unsafe high-frequency architecture.
# - V1.0.1 2026-08-25: RETIRED - unsafe high-frequency architecture.
# - V1.0.0 2026-08-25: RETIRED - unsafe high-frequency architecture.
#
#:########################################################################################:#
#:########################################################################################:#
# HELPERS #
#:########################################################################################:#
input_boolean:
# Master safety switch.
#
# No "initial" value is specified intentionally:
# - First creation defaults to OFF.
# - Thereafter Home Assistant restores the previous state.
battery_notes_alerts_master_enable:
name: "Battery Notes Alerts - Master Enable"
icon: mdi:battery-alert
# Internal startup guard.
#
# This MUST start false on every Home Assistant restart.
battery_notes_offline_alerts_armed:
name: "Battery Notes Alerts - Offline Armed"
icon: mdi:shield-clock
initial: false
input_text:
# Daily de-duplication caches.
#
# Format:
# YYYY-MM-DD|xxxxxxxxxx|xxxxxxxxxx|...
#
# Only the final 10 characters of the device identifier are stored.
# A maximum of the 20 most recent device tokens is retained.
#
# No initial value is set so the cache survives Home Assistant restarts.
battery_notes_low_alert_cache:
name: "Battery Notes - Low Alert Cache"
max: 255
icon: mdi:battery-alert-variant
battery_notes_offline_alert_cache:
name: "Battery Notes - Offline Alert Cache"
max: 255
icon: mdi:battery-off-outline
#:########################################################################################:#
# AUTOMATIONS #
#:########################################################################################:#
automation:
#:######################################################################################:#
# Offline Alert Startup Guard #
#:######################################################################################:#
- id: battery_notes_arm_offline_alerts
alias: "Battery Notes - Arm Offline Alerts"
description: >
Arms Battery Notes offline notifications 10 minutes after Home Assistant starts.
This prevents normal startup/integration initialisation from being interpreted as
devices suddenly going offline.
mode: restart
triggers:
- trigger: homeassistant
event: start
actions:
- action: input_boolean.turn_off
target:
entity_id: input_boolean.battery_notes_offline_alerts_armed
- delay: "00:10:00"
- action: input_boolean.turn_on
target:
entity_id: input_boolean.battery_notes_offline_alerts_armed
#:######################################################################################:#
# Immediate Low Battery Alert #
#:######################################################################################:#
- id: battery_notes_immediate_low_alert
alias: "Battery Notes - Immediate Low Alert"
description: >
Sends one immediate Pushover alert when a non-temporary Battery Notes device
genuinely crosses into its low-battery state. Reminder-generated Battery Notes
events are ignored. A device can generate at most one low alert per calendar day.
mode: queued
max: 10
triggers:
- trigger: event
event_type: battery_notes_battery_threshold
event_data:
battery_low: true
reminder: false
conditions:
# Master control.
- condition: state
entity_id: input_boolean.battery_notes_alerts_master_enable
state: "on"
# Temporary Battery Notes devices must never generate notifications.
- condition: template
value_template: >-
{% set note = trigger.event.data.note | default('', true) | lower %}
{{ 'temporary' not in note }}
# Require some stable identifier for daily de-duplication.
- condition: template
value_template: >-
{{ (trigger.event.data.device_id | default('', true)) != ''
or (trigger.event.data.source_entity_id | default('', true)) != '' }}
actions:
# IMPORTANT:
# This de-duplication check is deliberately an ACTION condition rather than an
# automation-level condition. Because this automation is mode: queued, two events
# arriving together are serialised before checking/updating the cache.
- condition: template
value_template: >-
{% set identifier =
trigger.event.data.device_id | default('', true)
or trigger.event.data.source_entity_id | default('', true) %}
{% set token = identifier[-10:] %}
{% set today = now().date() | string %}
{% set cache = states('input_text.battery_notes_low_alert_cache') %}
{% set previous =
cache.split('|')[1:]
if cache.startswith(today ~ '|')
else [] %}
{{ token not in previous }}
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Battery Alert"
short_announcement: >-
{% set name =
trigger.event.data.device_name
| default('Unknown Battery Device', true) %}
{% set level =
trigger.event.data.battery_level
| default(-1, true)
| float(-1) %}
{% set battery_type =
trigger.event.data.battery_type_and_quantity
| default('Not recorded', true) %}
New Alert: {{ name }}, Battery Low{% if level >= 0 %}
{{ level | round(0) | int }}%{% endif %} {{ battery_type }}
long_announcement: >-
{% set name =
trigger.event.data.device_name
| default('Unknown Battery Device', true) %}
{% set level =
trigger.event.data.battery_level
| default(-1, true)
| float(-1) %}
{% set battery_type =
trigger.event.data.battery_type_and_quantity
| default('Not recorded', true) %}
New Alert: {{ name }}, Battery Low{% if level >= 0 %}
{{ level | round(0) | int }}%{% endif %} {{ battery_type }}
# Record this device only after the notification action has completed.
- action: input_text.set_value
target:
entity_id: input_text.battery_notes_low_alert_cache
data:
value: >-
{% set identifier =
trigger.event.data.device_id | default('', true)
or trigger.event.data.source_entity_id | default('', true) %}
{% set token = identifier[-10:] %}
{% set today = now().date() | string %}
{% set cache = states('input_text.battery_notes_low_alert_cache') %}
{% set previous =
cache.split('|')[1:]
if cache.startswith(today ~ '|')
else [] %}
{% set updated = (previous + [token])[-20:] %}
{{ today ~ '|' ~ updated | join('|') }}
#:######################################################################################:#
# Immediate Offline Alert #
#:######################################################################################:#
- id: battery_notes_immediate_offline_alert
alias: "Battery Notes - Immediate Offline Alert"
description: >
Sends one immediate Pushover alert when a non-temporary Battery Notes Battery+
entity genuinely changes from an available state to unknown/unavailable.
Alerts are inhibited for the first 10 minutes after Home Assistant starts.
A device can generate at most one offline alert per calendar day.
mode: queued
max: 20
triggers:
- trigger: event
event_type: state_changed
conditions:
# Keep the first checks extremely cheap because state_changed is a global event.
- condition: state
entity_id: input_boolean.battery_notes_alerts_master_enable
state: "on"
- condition: state
entity_id: input_boolean.battery_notes_offline_alerts_armed
state: "on"
# Only Battery Notes generated Battery+ entities are interesting.
# No regex and no global state lookup is required.
- condition: template
value_template: >-
{% set entity_id = trigger.event.data.entity_id | default('', true) %}
{{ (entity_id.startswith('sensor.')
and entity_id.endswith('_battery_plus'))
or
(entity_id.startswith('binary_sensor.')
and entity_id.endswith('_battery_plus_low')) }}
# Require a genuine transition from available -> unavailable.
- condition: template
value_template: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{{ old is not none
and new is not none
and old.state not in ['unknown', 'unavailable']
and new.state in ['unknown', 'unavailable'] }}
# Temporary devices are ignored.
#
# Prefer the new state's note but fall back to the old state's attributes because
# integrations do not always retain every attribute on an unavailable state.
- condition: template
value_template: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{% set note =
new.attributes.get(
'note',
old.attributes.get('note', '')
)
| default('', true)
| lower %}
{{ 'temporary' not in note }}
actions:
# As with low alerts, do this after entering the queued action sequence so two
# related Battery+ entities becoming unavailable together cannot both notify.
- condition: template
value_template: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{% set identifier =
new.attributes.get('device_id', '')
or old.attributes.get('device_id', '')
or new.entity_id %}
{% set token = identifier[-10:] %}
{% set today = now().date() | string %}
{% set cache = states('input_text.battery_notes_offline_alert_cache') %}
{% set previous =
cache.split('|')[1:]
if cache.startswith(today ~ '|')
else [] %}
{{ token not in previous }}
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Battery Alert"
short_announcement: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{% set name =
new.attributes.get('device_name')
or old.attributes.get('device_name')
or new.attributes.get('friendly_name')
or old.attributes.get('friendly_name')
or new.name %}
{% set battery_type =
new.attributes.get('battery_type_and_quantity')
or old.attributes.get('battery_type_and_quantity')
or 'Not recorded' %}
New Alert: {{ name }}, Battery Unavailable {{ battery_type }}
long_announcement: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{% set name =
new.attributes.get('device_name')
or old.attributes.get('device_name')
or new.attributes.get('friendly_name')
or old.attributes.get('friendly_name')
or new.name %}
{% set battery_type =
new.attributes.get('battery_type_and_quantity')
or old.attributes.get('battery_type_and_quantity')
or 'Not recorded' %}
New Alert: {{ name }}, Battery Unavailable {{ battery_type }}
- action: input_text.set_value
target:
entity_id: input_text.battery_notes_offline_alert_cache
data:
value: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{% set identifier =
new.attributes.get('device_id', '')
or old.attributes.get('device_id', '')
or new.entity_id %}
{% set token = identifier[-10:] %}
{% set today = now().date() | string %}
{% set cache = states('input_text.battery_notes_offline_alert_cache') %}
{% set previous =
cache.split('|')[1:]
if cache.startswith(today ~ '|')
else [] %}
{% set updated = (previous + [token])[-20:] %}
{{ today ~ '|' ~ updated | join('|') }}
#:######################################################################################:#
# Weekly Digest #
#:######################################################################################:#
- id: battery_notes_weekly_digest
alias: "Battery Notes - Weekly Digest"
description: >
Sends one Battery Notes summary every Thursday at 19:00. Reports current low
batteries and batteries which have not reported for at least two days.
Temporary Battery Notes devices are excluded.
mode: single
triggers:
- trigger: time
at: "19:00:00"
conditions:
- condition: time
weekday:
- thu
- condition: state
entity_id: input_boolean.battery_notes_alerts_master_enable
state: "on"
actions:
# IMPORTANT:
# raise_events MUST remain false.
#
# This retrieves the current low list without creating Battery Notes threshold
# reminder events.
- action: battery_notes.check_battery_low
data:
raise_events: false
response_variable: battery_low_response
# Ask Battery Notes for devices which have not reported for at least two days.
# Again, retrieve a response only; do not raise one event per device.
- action: battery_notes.check_battery_last_reported
data:
days_last_reported: 2
raise_events: false
response_variable: battery_report_response
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Weekly Battery Digest"
short_announcement: "Weekly Battery Digest"
long_announcement: >-
{# ------------------------------------------------------------------------- #}
{# Obtain response arrays. #}
{# #}
{# check_battery_battery_low is confirmed from this HA installation. #}
{# Two possible last-reported response keys are accepted defensively. #}
{# ------------------------------------------------------------------------- #}
{% set low_items =
battery_low_response.get(
'check_battery_battery_low',
battery_low_response.get('check_battery_low', [])
) %}
{% set report_items =
battery_report_response.get(
'check_battery_last_reported',
battery_report_response.get(
'check_battery_battery_last_reported',
[]
)
) %}
{# ------------------------------------------------------------------------- #}
{# Resolve the Battery Notes note. #}
{# #}
{# The low action response on this installation does not include "note", #}
{# so fall back to the attributes of Battery Notes entities. #}
{# This lookup occurs ONCE PER WEEK ONLY and is bounded below. #}
{# ------------------------------------------------------------------------- #}
{% macro battery_note(item) -%}
{% set ns =
namespace(
note=item.get('note', '') | default('', true)
) %}
{% if not ns.note %}
{% set item_device_id =
item.get('device_id', '') | default('', true) %}
{% set item_source_id =
item.get('source_entity_id', '') | default('', true) %}
{% for entity_id in integration_entities('battery_notes') %}
{% if not ns.note %}
{% set entity_device_id =
state_attr(entity_id, 'device_id')
| default('', true) %}
{% set entity_source_id =
state_attr(entity_id, 'source_entity_id')
| default('', true) %}
{% if
(item_device_id
and entity_device_id == item_device_id)
or
(item_source_id
and entity_source_id == item_source_id) %}
{% set found_note =
state_attr(entity_id, 'note')
| default('', true) %}
{% if found_note %}
{% set ns.note = found_note %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{{ ns.note }}
{%- endmacro %}
{# ------------------------------------------------------------------------- #}
{# Build bounded problem lists, excluding Temporary devices. #}
{# ------------------------------------------------------------------------- #}
{% set summary = namespace(low=[], not_reporting=[]) %}
{% for item in low_items[:100] %}
{% set note = battery_note(item) | trim | lower %}
{% if 'temporary' not in note %}
{% set name =
item.get('device_name', 'Unknown Battery Device')
| default('Unknown Battery Device', true) %}
{% set level =
item.get('battery_level', -1)
| default(-1, true)
| float(-1) %}
{% set battery_type =
item.get('battery_type_and_quantity', 'Not recorded')
| default('Not recorded', true) %}
{% if level >= 0 %}
{% set line =
name
~ ' - '
~ (level | round(0) | int)
~ '% - '
~ battery_type %}
{% else %}
{% set line =
name
~ ' - '
~ battery_type %}
{% endif %}
{% set summary.low = summary.low + [line] %}
{% endif %}
{% endfor %}
{% for item in report_items[:100] %}
{% set note = battery_note(item) | trim | lower %}
{% if 'temporary' not in note %}
{% set name =
item.get('device_name', 'Unknown Battery Device')
| default('Unknown Battery Device', true) %}
{% set days =
item.get('battery_last_reported_days', 0)
| default(0, true)
| int(0) %}
{% set last_level =
item.get('battery_last_reported_level', -1)
| default(-1, true)
| float(-1) %}
{% set battery_type =
item.get('battery_type_and_quantity', 'Not recorded')
| default('Not recorded', true) %}
{% if last_level >= 0 %}
{% set line =
name
~ ' - '
~ days
~ ' days - last '
~ (last_level | round(0) | int)
~ '% - '
~ battery_type %}
{% else %}
{% set line =
name
~ ' - '
~ days
~ ' days - '
~ battery_type %}
{% endif %}
{% set summary.not_reporting =
summary.not_reporting + [line] %}
{% endif %}
{% endfor %}
Weekly Battery Digest
{% if summary.low %}
Low Battery: {{ summary.low | count }}
{% for line in summary.low %}
• {{ line }}
{% endfor %}
{% else %}
Low Battery: 0
{% endif %}
{% if summary.not_reporting %}
Not Reporting (2+ days): {{ summary.not_reporting | count }}
{% for line in summary.not_reporting %}
• {{ line }}
{% endfor %}
{% else %}
Not Reporting (2+ days): 0
{% endif %}
{% if not summary.low and not summary.not_reporting %}
All monitored Battery Notes devices are OK.
{% endif %}
+195
View File
@@ -0,0 +1,195 @@
#:########################################################################################:#
# Battery Notes Alerts Package #
#:########################################################################################:#
#
# TITLE:
# Battery Notes Alerts
#
# FILE:
# packages/battery_notes_alerts.yaml
#
# VERSION:
# V1.0.0 2026-08-25
#
# PURPOSE:
# Sends immediate and weekly Pushover alerts for non-temporary Battery Notes devices.
# Device lists are generated from Battery Notes at runtime, so adding a note starts
# monitoring the device without maintaining a second entity list.
#
# CONTROLS:
# - Battery Notes - Immediate Alert and Battery Notes - Weekly Digest are independent
# automation entities. Each can be enabled or disabled in Home Assistant Automations.
#
# DEPENDENCIES:
# - Battery Notes integration
# - packages/view_road_announcement_router.yaml
#
# VERSION HISTORY:
# - V1.0.0 2026-08-25: Initial immediate and Thursday weekly battery alerts.
#
#:########################################################################################:#
automation:
- id: battery_notes_immediate_alert
alias: "Battery Notes - Immediate Alert"
description: >
Sends a Pushover alert when a monitored Battery Notes device newly reports
low battery or becomes unavailable. Temporary Battery Notes devices are ignored.
mode: queued
max: 10
triggers:
- trigger: event
event_type: state_changed
conditions:
- condition: template
value_template: >-
{{ trigger.event.data.entity_id is match('^(sensor|binary_sensor)\\..*_battery_plus(?:_low)?$') }}
- condition: template
value_template: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{{ old is not none
and new is not none
and old.state not in ['unknown', 'unavailable']
and old.state != new.state }}
- condition: template
value_template: >-
{% set note = trigger.event.data.new_state.attributes.note | default('', true) | lower %}
{{ trigger.event.data.new_state.attributes.device_id is defined
and 'temporary' not in note }}
- condition: template
value_template: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{% set device_id = new.attributes.device_id %}
{% set is_percentage = new.entity_id.startswith('sensor.') and new.entity_id.endswith('_battery_plus') %}
{% set companion = namespace(percentage=none) %}
{% for item in states %}
{% if item.attributes.device_id | default('') == device_id
and item.entity_id.startswith('sensor.')
and item.entity_id.endswith('_battery_plus') %}
{% set companion.percentage = item %}
{% endif %}
{% endfor %}
{% set source = companion.percentage if companion.percentage else new %}
{% set qualitative_id = source.entity_id
| replace('_battery_plus_low', '_battery_state')
| replace('_battery_plus', '_battery_state') %}
{% set qualitative = states[qualitative_id] %}
{% set qualitative_available = qualitative is not none
and qualitative.state in ['high', 'medium', 'low'] %}
{% set newly_low = (new.attributes.battery_low | default(false))
and not (old.attributes.battery_low | default(false)) %}
{% set newly_binary_low = new.entity_id.startswith('binary_sensor.')
and new.state == 'on' %}
{% set newly_unavailable = new.state in ['unknown', 'unavailable']
and source.entity_id == new.entity_id
and not qualitative_available %}
{{ newly_low or newly_binary_low or newly_unavailable }}
variables:
alert_name: >-
{% set new = trigger.event.data.new_state %}
{{ new.attributes.device_name | default(new.attributes.friendly_name, true) | default(new.name, true) }}
alert_type: >-
{% set new = trigger.event.data.new_state %}
{{ new.attributes.battery_type_and_quantity | default('Not recorded', true) }}
alert_level: >-
{% set new = trigger.event.data.new_state %}
{% set device_id = new.attributes.device_id %}
{% set level = namespace(value=none) %}
{% for item in states %}
{% if item.attributes.device_id | default('') == device_id
and item.entity_id.startswith('sensor.')
and item.entity_id.endswith('_battery_plus')
and item.state not in ['unknown', 'unavailable'] %}
{% set level.value = item.state %}
{% endif %}
{% endfor %}
{{ level.value ~ '%' if level.value is not none else '' }}
alert_line: >-
{% set new = trigger.event.data.new_state %}
{% if new.state in ['unknown', 'unavailable'] %}
New Alert: {{ alert_name }}, Battery Unavailable {{ alert_type }}
{% else %}
New Alert: {{ alert_name }}, Battery Low {{ alert_level }} {{ alert_type }}
{% endif %}
alert_summary: &battery_notes_summary >-
{% set monitored = namespace(device_ids=[], low=[], unavailable=[]) %}
{% for item in states %}
{% set note = item.attributes.note | default('', true) | lower %}
{% set device_id = item.attributes.device_id | default('') %}
{% if item.entity_id is match('^(sensor|binary_sensor)\\..*_battery_plus(?:_low)?$')
and device_id
and 'temporary' not in note
and device_id not in monitored.device_ids %}
{% set monitored.device_ids = monitored.device_ids + [device_id] %}
{% set device = namespace(percentage=none, low_sensor=none) %}
{% for candidate in states %}
{% if candidate.attributes.device_id | default('') == device_id %}
{% if candidate.entity_id.startswith('sensor.') and candidate.entity_id.endswith('_battery_plus') %}
{% set device.percentage = candidate %}
{% elif candidate.entity_id.startswith('binary_sensor.') and candidate.entity_id.endswith('_battery_plus_low') %}
{% set device.low_sensor = candidate %}
{% endif %}
{% endif %}
{% endfor %}
{% set source = device.percentage if device.percentage else device.low_sensor %}
{% set qualitative_id = source.entity_id
| replace('_battery_plus_low', '_battery_state')
| replace('_battery_plus', '_battery_state') %}
{% set qualitative = states[qualitative_id] %}
{% set qualitative_state = qualitative.state if qualitative is not none else '' %}
{% set name = source.attributes.device_name
| default(source.attributes.friendly_name, true)
| default(source.name, true) %}
{% set battery_type = source.attributes.battery_type_and_quantity | default('Not recorded', true) %}
{% set low = source.attributes.battery_low | default(false)
or (device.low_sensor is not none and device.low_sensor.state == 'on')
or qualitative_state == 'low' %}
{% set unavailable = source.state in ['unknown', 'unavailable']
and qualitative_state not in ['high', 'medium', 'low'] %}
{% if low %}
{% set level = source.state ~ '%' if device.percentage is not none and source.state not in ['unknown', 'unavailable'] else '' %}
{% set monitored.low = monitored.low + [name ~ ' (' ~ level ~ (', ' if level else '') ~ battery_type ~ ')'] %}
{% endif %}
{% if unavailable %}
{% set monitored.unavailable = monitored.unavailable + [name ~ ' (' ~ battery_type ~ ')'] %}
{% endif %}
{% endif %}
{% endfor %}
Summary: {{ monitored.device_ids | count }} Battery Notes devices, {{ monitored.unavailable | count }} unavailable, {{ monitored.low | count }} battery low.
Low Battery Devices: {{ monitored.low | join(', ') if monitored.low else 'None' }}
Unavailable Devices: {{ monitored.unavailable | join(', ') if monitored.unavailable else 'None' }}
actions:
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Battery Alert"
short_announcement: "{{ alert_line }}"
long_announcement: >-
{{ alert_line }}
{{ alert_summary }}
- id: battery_notes_weekly_digest
alias: "Battery Notes - Weekly Digest"
description: >
Sends the Battery Notes health summary to Pushover every Thursday at 19:00.
Temporary Battery Notes devices are excluded.
mode: single
triggers:
- trigger: time
at: "19:00:00"
conditions:
- condition: time
weekday:
- thu
variables:
alert_summary: *battery_notes_summary
actions:
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Weekly Battery Digest"
short_announcement: "Weekly Battery Digest"
long_announcement: "{{ alert_summary }}"
@@ -0,0 +1,197 @@
#:########################################################################################:#
# Battery Notes Alerts Package #
#:########################################################################################:#
#
# TITLE:
# Battery Notes Alerts
#
# FILE:
# packages/battery_notes_alerts.yaml
#
# VERSION:
# V1.0.1 2026-08-25
#
# PURPOSE:
# Sends immediate and weekly Pushover alerts for non-temporary Battery Notes devices.
# Device lists are generated from Battery Notes at runtime, so adding a note starts
# monitoring the device without maintaining a second entity list.
#
# CONTROLS:
# - Battery Notes - Immediate Alert and Battery Notes - Weekly Digest are independent
# automation entities. Each can be enabled or disabled in Home Assistant Automations.
#
# DEPENDENCIES:
# - Battery Notes integration
# - packages/view_road_announcement_router.yaml
#
# VERSION HISTORY:
# - V1.0.1 2026-08-25: Bound companion lookups and prevent oversized template output.
# - V1.0.0 2026-08-25: Initial immediate and Thursday weekly battery alerts.
#
#:########################################################################################:#
automation:
- id: battery_notes_immediate_alert
alias: "Battery Notes - Immediate Alert"
description: >
Sends a Pushover alert when a monitored Battery Notes device newly reports
low battery or becomes unavailable. Temporary Battery Notes devices are ignored.
mode: single
max_exceeded: silent
triggers:
- trigger: event
event_type: state_changed
conditions:
- condition: template
value_template: >-
{{ trigger.event.data.entity_id is match('^(sensor|binary_sensor)\\..*_battery_plus(?:_low)?$') }}
- condition: template
value_template: >-
{% set old = trigger.event.data.old_state %}
{% set new = trigger.event.data.new_state %}
{{ old is not none
and new is not none
and old.state not in ['unknown', 'unavailable']
and old.state != new.state }}
- condition: template
value_template: >-
{% set new = trigger.event.data.new_state %}
{% set note = new.attributes.note | default('', true) | lower %}
{{ new.attributes.device_id | default('') | bool
and 'temporary' not in note }}
- condition: template
value_template: >-
{%- set old = trigger.event.data.old_state -%}
{%- set new = trigger.event.data.new_state -%}
{%- set device_id = new.attributes.device_id -%}
{%- set companions = namespace(percentage_id='', qualitative_id='') -%}
{%- for entity_id in device_entities(device_id) -%}
{%- if entity_id.startswith('sensor.') and entity_id.endswith('_battery_plus') -%}
{%- set companions.percentage_id = entity_id -%}
{%- elif entity_id.startswith('sensor.') and entity_id.endswith('_battery_state') -%}
{%- set companions.qualitative_id = entity_id -%}
{%- endif -%}
{%- endfor -%}
{%- set source_id = companions.percentage_id or new.entity_id -%}
{%- set qualitative_available = companions.qualitative_id
and states(companions.qualitative_id) in ['high', 'medium', 'low'] -%}
{%- set newly_low = new.attributes.battery_low | default(false)
and not old.attributes.battery_low | default(false) -%}
{%- set newly_binary_low = new.entity_id.startswith('binary_sensor.')
and new.state == 'on' -%}
{%- set newly_unavailable = new.state in ['unknown', 'unavailable']
and source_id == new.entity_id
and not qualitative_available -%}
{{ newly_low or newly_binary_low or newly_unavailable }}
variables:
alert_name: >-
{% set new = trigger.event.data.new_state %}
{{ new.attributes.device_name
| default(new.attributes.friendly_name, true)
| default(new.name, true) }}
alert_type: >-
{{ trigger.event.data.new_state.attributes.battery_type_and_quantity
| default('Not recorded', true) }}
alert_level: >-
{%- set device_id = trigger.event.data.new_state.attributes.device_id -%}
{%- set level = namespace(value='') -%}
{%- for entity_id in device_entities(device_id) -%}
{%- if entity_id.startswith('sensor.')
and entity_id.endswith('_battery_plus')
and states(entity_id) not in ['unknown', 'unavailable'] -%}
{%- set level.value = states(entity_id) -%}
{%- endif -%}
{%- endfor -%}
{{ level.value ~ '%' if level.value else '' }}
alert_line: >-
{% if trigger.event.data.new_state.state in ['unknown', 'unavailable'] %}
New Alert: {{ alert_name }}, Battery Unavailable {{ alert_type }}
{% else %}
New Alert: {{ alert_name }}, Battery Low {{ alert_level }} {{ alert_type }}
{% endif %}
alert_summary: &battery_notes_summary >-
{%- set monitored = namespace(device_ids=[], low=[], unavailable=[]) -%}
{%- for item in states -%}
{%- set note = item.attributes.note | default('', true) | lower -%}
{%- set device_id = item.attributes.device_id | default('') -%}
{%- if item.entity_id is match('^(sensor|binary_sensor)\\..*_battery_plus(?:_low)?$')
and device_id
and 'temporary' not in note
and device_id not in monitored.device_ids -%}
{%- set monitored.device_ids = monitored.device_ids + [device_id] -%}
{%- endif -%}
{%- endfor -%}
{%- for device_id in monitored.device_ids -%}
{%- set device = namespace(percentage_id='', low_id='', qualitative_id='') -%}
{%- for entity_id in device_entities(device_id) -%}
{%- if entity_id.startswith('sensor.') and entity_id.endswith('_battery_plus') -%}
{%- set device.percentage_id = entity_id -%}
{%- elif entity_id.startswith('binary_sensor.') and entity_id.endswith('_battery_plus_low') -%}
{%- set device.low_id = entity_id -%}
{%- elif entity_id.startswith('sensor.') and entity_id.endswith('_battery_state') -%}
{%- set device.qualitative_id = entity_id -%}
{%- endif -%}
{%- endfor -%}
{%- set source_id = device.percentage_id or device.low_id -%}
{%- set source_state = states(source_id) -%}
{%- set qualitative_state = states(device.qualitative_id) if device.qualitative_id else '' -%}
{%- set name = state_attr(source_id, 'device_name')
or state_attr(source_id, 'friendly_name')
or source_id -%}
{%- set battery_type = state_attr(source_id, 'battery_type_and_quantity')
or 'Not recorded' -%}
{%- set low = state_attr(source_id, 'battery_low') | default(false, true)
or (device.low_id and is_state(device.low_id, 'on'))
or qualitative_state == 'low' -%}
{%- set unavailable = source_state in ['unknown', 'unavailable']
and qualitative_state not in ['high', 'medium', 'low'] -%}
{%- if low -%}
{%- set level = source_state ~ '%'
if device.percentage_id and source_state not in ['unknown', 'unavailable']
else '' -%}
{%- set monitored.low = monitored.low
+ [name ~ ' (' ~ level ~ (', ' if level else '') ~ battery_type ~ ')'] -%}
{%- endif -%}
{%- if unavailable -%}
{%- set monitored.unavailable = monitored.unavailable
+ [name ~ ' (' ~ battery_type ~ ')'] -%}
{%- endif -%}
{%- endfor -%}
Summary: {{ monitored.device_ids | count }} Battery Notes devices,
{{ monitored.unavailable | count }} unavailable,
{{ monitored.low | count }} battery low.
Low Battery Devices: {{ monitored.low | join(', ') if monitored.low else 'None' }}
Unavailable Devices: {{ monitored.unavailable | join(', ') if monitored.unavailable else 'None' }}
actions:
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Battery Alert"
short_announcement: "{{ alert_line }}"
long_announcement: >-
{{ alert_line }}
{{ alert_summary }}
- id: battery_notes_weekly_digest
alias: "Battery Notes - Weekly Digest"
description: >
Sends the Battery Notes health summary to Pushover every Thursday at 19:00.
Temporary Battery Notes devices are excluded.
mode: single
triggers:
- trigger: time
at: "19:00:00"
conditions:
- condition: time
weekday:
- thu
variables:
alert_summary: *battery_notes_summary
actions:
- action: script.view_road_announcement
data:
channels: "pushover_zorruno"
title: "Weekly Battery Digest"
short_announcement: "Weekly Battery Digest"
long_announcement: "{{ alert_summary }}"
+61 -51
View File
@@ -1,75 +1,85 @@
########################################################################################## # PACKAGE: Bedroom 3 Climate Controls
# Bedroom 3 Morning Climate
##########################################################################################
# #
# Turns on the Bedroom 3 heat pump at the selected morning time without # Purpose: Configurable automatic heat-pump schedule for Mollie's bedroom.
# changing its current HVAC mode, temperature, or other settings, then turns # Notes: The schedule runs only on enabled weekday and/or weekend day types.
# it off at 08:30. # Version: 2.0.0
# #
########################################################################################## #:########################################################################################:#
---
input_boolean: input_boolean:
bedroom_3_morning_climate_enabled: bedroom_3_morning_climate_enabled:
name: Bedroom 3 Morning Climate name: "Bedroom 3 Heat Pump Auto Control"
icon: mdi:weather-sunny icon: mdi:hvac
initial: true
input_select: bedroom_3_morning_climate_weekdays:
bedroom_3_morning_climate_time: name: "Bedroom 3 Heat Pump Weekdays"
name: Bedroom 3 Morning Climate Time icon: mdi:calendar-week
bedroom_3_morning_climate_weekends:
name: "Bedroom 3 Heat Pump Weekends"
icon: mdi:calendar-weekend
input_datetime:
bedroom_3_morning_climate_start_time:
name: "Bedroom 3 Heat Pump On Time"
icon: mdi:clock-start icon: mdi:clock-start
initial: "06:45" has_date: false
options: has_time: true
- "04:30"
- "04:45" bedroom_3_morning_climate_stop_time:
- "05:00" name: "Bedroom 3 Heat Pump Off Time"
- "05:15" icon: mdi:clock-end
- "05:30" has_date: false
- "05:45" has_time: true
- "06:00"
- "06:15" #:########################################################################################:#
- "06:30" # Automations
- "06:45" #:########################################################################################:#
- "07:00"
- "07:15"
- "07:30"
- "07:45"
- "08:00"
- "08:15"
- "08:30"
- "08:45"
- "09:00"
- "09:15"
- "09:30"
- "09:45"
- "10:00"
automation: automation:
- id: bedroom_3_morning_climate_start - id: bedroom_3_morning_climate_start
alias: Bedroom 3 Morning Climate Start alias: "Bedroom 3 Heat Pump Auto Start"
description: >- description: "Starts the heat pump at the selected time on enabled day types."
Turns on the Bedroom 3 heat pump at the selected time when enabled, then triggers:
turns it off at 08:30.
trigger:
- trigger: time_pattern - trigger: time_pattern
minutes: "/15" minutes: "/1"
condition: conditions:
- condition: state - condition: state
entity_id: input_boolean.bedroom_3_morning_climate_enabled entity_id: input_boolean.bedroom_3_morning_climate_enabled
state: "on" state: "on"
- condition: template - condition: template
value_template: >- value_template: >-
{{ now().strftime('%H:%M') == {{ now().strftime('%H:%M') ==
states('input_select.bedroom_3_morning_climate_time') }} states('input_datetime.bedroom_3_morning_climate_start_time')[:5] }}
action: - condition: template
value_template: >-
{{ (now().weekday() < 5 and
is_state('input_boolean.bedroom_3_morning_climate_weekdays', 'on')) or
(now().weekday() >= 5 and
is_state('input_boolean.bedroom_3_morning_climate_weekends', 'on')) }}
actions:
- action: climate.turn_on - action: climate.turn_on
target: target:
entity_id: climate.mollie_s_room entity_id: climate.mollie_s_room
- wait_template: >- - id: bedroom_3_morning_climate_stop
{{ now().hour > 8 or (now().hour == 8 and now().minute >= 30) }} alias: "Bedroom 3 Heat Pump Auto Stop"
description: "Stops the heat pump at the selected time on enabled day types."
triggers:
- trigger: time_pattern
minutes: "/1"
conditions:
- condition: template
value_template: >-
{{ now().strftime('%H:%M') ==
states('input_datetime.bedroom_3_morning_climate_stop_time')[:5] }}
- condition: template
value_template: >-
{{ (now().weekday() < 5 and
is_state('input_boolean.bedroom_3_morning_climate_weekdays', 'on')) or
(now().weekday() >= 5 and
is_state('input_boolean.bedroom_3_morning_climate_weekends', 'on')) }}
actions:
- action: climate.turn_off - action: climate.turn_off
target: target:
entity_id: climate.mollie_s_room entity_id: climate.mollie_s_room
+939
View File
@@ -0,0 +1,939 @@
################################################################################
# PACKAGE: Energy Cost Monitoring
################################################################################
#
# Version: 1.3.0
# Date: 2026-08-23
#
# Purpose:
# Track GST-inclusive electricity costs for the three-phase grid supply and
# every appliance configured on the Energy dashboard.
#
# Notes:
# - Charged period: 00:00 to 21:00.
# - Free period: 21:00 to 00:00.
# - Bill rates are GST-exclusive; calculated costs include 15% GST.
# - Fixed daily charges are kept separate from appliance costs.
# - Integral sensors use the left method so tariff changes are applied at the
# boundary without averaging the old and new rates.
#
# Version history:
# 1.3.0 (2026-08-24): Add Drinks Fridge and Vertical Freezer energy and
# cost monitoring.
# 1.2.0 (2026-08-23): Add Downstairs Lounge North GPO (X48PM) energy and
# cost monitoring.
# 1.1.1 (2026-08-22): Use the Zigbee X31PP plug for Main Fridge power costs.
# 1.1.0 (2026-08-17): Add sorted daily, weekly, and monthly appliance ranking
# meters and dynamic chart scales.
# 1.0.0 (2026-08-17): Initial tariff, grid, appliance, and fixed-cost tracking.
#
#:########################################################################################:#
# TARIFF HELPERS
#:########################################################################################:#
input_number:
electricity_paid_rate_ex_gst:
name: Electricity Paid Rate Ex GST
icon: mdi:cash
initial: 0.289
min: 0
max: 2
step: 0.001
mode: box
unit_of_measurement: "NZD/kWh"
electricity_daily_charge_ex_gst:
name: Electricity Daily Charge Ex GST
icon: mdi:calendar-cash
initial: 3.036
min: 0
max: 20
step: 0.001
mode: box
unit_of_measurement: "NZD/day"
#:########################################################################################:#
# TARIFF AND COST-RATE SENSORS
#:########################################################################################:#
template:
- sensor:
- name: Electricity Paid Rate
default_entity_id: sensor.electricity_paid_rate
unique_id: electricity_paid_rate
icon: mdi:cash
unit_of_measurement: "NZD/kWh"
state_class: measurement
state: >-
{{
(
states('input_number.electricity_paid_rate_ex_gst')
| float(0.289)
* 1.15
) | round(5)
}}
attributes:
gst_rate: 0.15
rate_ex_gst: >-
{{
states('input_number.electricity_paid_rate_ex_gst')
| float(0.289)
}}
- name: Electricity Current Price
default_entity_id: sensor.electricity_current_price
unique_id: electricity_current_price
icon: mdi:currency-usd
unit_of_measurement: "NZD/kWh"
state_class: measurement
state: >-
{% set minute = now().hour * 60 + now().minute %}
{{
0
if minute >= 21 * 60
else states('sensor.electricity_paid_rate') | float(0.33235)
}}
attributes:
tariff: >-
{{ "free" if now().hour >= 21 else "charged" }}
charged_period: "00:00-21:00"
free_period: "21:00-00:00"
gst_included: true
- name: Electricity Daily Charge
default_entity_id: sensor.electricity_daily_charge
unique_id: electricity_daily_charge
icon: mdi:calendar-cash
unit_of_measurement: "NZD/day"
state_class: measurement
state: >-
{{
(
states('input_number.electricity_daily_charge_ex_gst')
| float(3.036)
* 1.15
) | round(5)
}}
attributes:
gst_rate: 0.15
charge_ex_gst: >-
{{
states('input_number.electricity_daily_charge_ex_gst')
| float(3.036)
}}
- name: Electricity Grid Cost Rate
default_entity_id: sensor.electricity_grid_cost_rate
unique_id: electricity_grid_cost_rate
icon: mdi:transmission-tower
unit_of_measurement: "NZD/h"
state_class: measurement
availability: >-
{{
has_value('sensor.main_house_3_phase_power_monitor_main_power_red_active_power')
and has_value('sensor.main_house_3_phase_power_monitor_main_power_yellow_active_power')
and has_value('sensor.main_house_3_phase_power_monitor_main_power_blue_active_power')
}}
state: >-
{% set red = states('sensor.main_house_3_phase_power_monitor_main_power_red_active_power') | float(0) %}
{% set yellow = states('sensor.main_house_3_phase_power_monitor_main_power_yellow_active_power') | float(0) %}
{% set blue = states('sensor.main_house_3_phase_power_monitor_main_power_blue_active_power') | float(0) %}
{% set price = states('sensor.electricity_current_price') | float(0) %}
{{ ([red + yellow + blue, 0] | max / 1000 * price) | round(6) }}
- name: Energy Cost 16A EV Wallcharger Rate
default_entity_id: sensor.energy_cost_16a_ev_wallcharger_rate
unique_id: energy_cost_16a_ev_wallcharger_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.garage_db_control_16a_wallcharger_power') }}"
state: >-
{{
(
states('sensor.garage_db_control_16a_wallcharger_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost 32A EV Charger Rate
default_entity_id: sensor.energy_cost_32a_ev_charger_rate
unique_id: energy_cost_32a_ev_charger_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.main_house_3_phase_power_monitor_32a_wallcharger_power') }}"
state: >-
{{
(
states('sensor.main_house_3_phase_power_monitor_32a_wallcharger_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Underhouse Dehumidifier Rate
default_entity_id: sensor.energy_cost_underhouse_dehumidifier_rate
unique_id: energy_cost_underhouse_dehumidifier_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.underhouse_dehumidifier_power_plug_x10pp_power') }}"
state: >-
{{
(
states('sensor.underhouse_dehumidifier_power_plug_x10pp_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Pool Pump Rate
default_entity_id: sensor.energy_cost_pool_pump_rate
unique_id: energy_cost_pool_pump_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_poolpumppower_power') }}"
state: >-
{{
(
states('sensor.esp_poolpumppower_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Data Cupboard Left Rate
default_entity_id: sensor.energy_cost_data_cupboard_left_rate
unique_id: energy_cost_data_cupboard_left_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_datacupboardpowermon_left_power') }}"
state: >-
{{
(
states('sensor.esp_datacupboardpowermon_left_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Data Cupboard Right Rate
default_entity_id: sensor.energy_cost_data_cupboard_right_rate
unique_id: energy_cost_data_cupboard_right_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_datacupboardpowermon_right_power') }}"
state: >-
{{
(
states('sensor.esp_datacupboardpowermon_right_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Laundry Dryer Rate
default_entity_id: sensor.energy_cost_laundry_dryer_rate
unique_id: energy_cost_laundry_dryer_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_laundrydrypow_load_power') }}"
state: >-
{{
(
states('sensor.esp_laundrydrypow_load_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Laundry Washing Machine Rate
default_entity_id: sensor.energy_cost_laundry_washing_machine_rate
unique_id: energy_cost_laundry_washing_machine_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_laundrywashpow_load_power') }}"
state: >-
{{
(
states('sensor.esp_laundrywashpow_load_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Main Dishwasher Rate
default_entity_id: sensor.energy_cost_main_dishwasher_rate
unique_id: energy_cost_main_dishwasher_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_maindishwasherpower_power') }}"
state: >-
{{
(
states('sensor.esp_maindishwasherpower_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost 3D Printer Rate
default_entity_id: sensor.energy_cost_3d_printer_rate
unique_id: energy_cost_3d_printer_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_3dprinterpow_load_power') }}"
state: >-
{{
(
states('sensor.esp_3dprinterpow_load_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Downstairs Dishwasher Rate
default_entity_id: sensor.energy_cost_downstairs_dishwasher_rate
unique_id: energy_cost_downstairs_dishwasher_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.esp_downstdishwasher_power') }}"
state: >-
{{
(
states('sensor.esp_downstdishwasher_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Main Fridge Rate
default_entity_id: sensor.energy_cost_main_fridge_rate
unique_id: energy_cost_main_fridge_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.upstairs_fridge_power_x31pp_power') }}"
state: >-
{{
(
states('sensor.upstairs_fridge_power_x31pp_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Downstairs Fridge Rate
default_entity_id: sensor.energy_cost_downstairs_fridge_rate
unique_id: energy_cost_downstairs_fridge_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.downstairs_fridge_power_x41pp_power') }}"
state: >-
{{
(
states('sensor.downstairs_fridge_power_x41pp_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Drinks Fridge Rate
default_entity_id: sensor.energy_cost_drinks_fridge_rate
unique_id: energy_cost_drinks_fridge_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.drinks_fridge_power_x59pm_power') }}"
state: >-
{{
(
states('sensor.drinks_fridge_power_x59pm_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Vertical Freezer Rate
default_entity_id: sensor.energy_cost_vertical_freezer_rate
unique_id: energy_cost_vertical_freezer_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.vertical_freezer_power_x60pm_power') }}"
state: >-
{{
(
states('sensor.vertical_freezer_power_x60pm_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Arcade Cabinet 1 Rate
default_entity_id: sensor.energy_cost_arcade_cabinet_1_rate
unique_id: energy_cost_arcade_cabinet_1_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.downstairs_flat_arcade_cabinet_1_power') }}"
state: >-
{{
(
states('sensor.downstairs_flat_arcade_cabinet_1_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Energy Cost Downstairs Lounge North GPO Rate
default_entity_id: sensor.energy_cost_downstairs_lounge_north_gpo_rate
unique_id: energy_cost_downstairs_lounge_north_gpo_rate
unit_of_measurement: "NZD/h"
state_class: measurement
availability: "{{ has_value('sensor.downstairs_lounge_north_gpo_x48pm_power') }}"
state: >-
{{
(
states('sensor.downstairs_lounge_north_gpo_x48pm_power') | float(0)
/ 1000
* states('sensor.electricity_current_price') | float(0)
) | round(6)
}}
- name: Electricity Total Cost
default_entity_id: sensor.electricity_total_cost
unique_id: electricity_total_cost
icon: mdi:cash-multiple
device_class: monetary
unit_of_measurement: "NZD"
state_class: total_increasing
availability: >-
{{
has_value('sensor.electricity_grid_usage_cost')
and has_value('sensor.electricity_fixed_charges_total')
}}
state: >-
{{
(
states('sensor.electricity_grid_usage_cost') | float(0)
+ states('sensor.electricity_fixed_charges_total') | float(0)
) | round(5)
}}
attributes:
usage_cost: "{{ states('sensor.electricity_grid_usage_cost') | float(0) }}"
fixed_cost: "{{ states('sensor.electricity_fixed_charges_total') | float(0) }}"
gst_included: true
- name: Energy Cost Daily Ranking Max
default_entity_id: sensor.energy_cost_daily_ranking_max
unique_id: energy_cost_daily_ranking_max
unit_of_measurement: "NZD"
state_class: measurement
state: >-
{% set values = [
states('sensor.16a_ev_wallcharger') | float(0),
states('sensor.32a_ev_charger') | float(0),
states('sensor.underhouse_dehumidifier') | float(0),
states('sensor.pool_pump') | float(0),
states('sensor.data_cupboard_left') | float(0),
states('sensor.data_cupboard_right') | float(0),
states('sensor.laundry_dryer') | float(0),
states('sensor.washing_machine') | float(0),
states('sensor.main_dishwasher') | float(0),
states('sensor.3d_printer') | float(0),
states('sensor.downstairs_dishwasher') | float(0),
states('sensor.main_fridge') | float(0),
states('sensor.downstairs_fridge') | float(0),
states('sensor.drinks_fridge') | float(0),
states('sensor.vertical_freezer') | float(0),
states('sensor.arcade_cabinet_1') | float(0),
states('sensor.downstairs_lounge_north_gpo_x48pm') | float(0)
] %}
{{ ([values | max * 1.1, 0.01] | max) | round(5) }}
- name: Energy Cost Weekly Ranking Max
default_entity_id: sensor.energy_cost_weekly_ranking_max
unique_id: energy_cost_weekly_ranking_max
unit_of_measurement: "NZD"
state_class: measurement
state: >-
{% set values = [
states('sensor.16a_ev_wallcharger_2') | float(0),
states('sensor.32a_ev_charger_2') | float(0),
states('sensor.underhouse_dehumidifier_2') | float(0),
states('sensor.pool_pump_2') | float(0),
states('sensor.data_cupboard_left_2') | float(0),
states('sensor.data_cupboard_right_2') | float(0),
states('sensor.laundry_dryer_2') | float(0),
states('sensor.washing_machine_2') | float(0),
states('sensor.main_dishwasher_2') | float(0),
states('sensor.3d_printer_2') | float(0),
states('sensor.downstairs_dishwasher_2') | float(0),
states('sensor.main_fridge_2') | float(0),
states('sensor.downstairs_fridge_2') | float(0),
states('sensor.drinks_fridge_2') | float(0),
states('sensor.vertical_freezer_2') | float(0),
states('sensor.arcade_cabinet_1_2') | float(0),
states('sensor.downstairs_lounge_north_gpo_x48pm_2') | float(0)
] %}
{{ ([values | max * 1.1, 0.01] | max) | round(5) }}
- name: Energy Cost Monthly Ranking Max
default_entity_id: sensor.energy_cost_monthly_ranking_max
unique_id: energy_cost_monthly_ranking_max
unit_of_measurement: "NZD"
state_class: measurement
state: >-
{% set values = [
states('sensor.16a_ev_wallcharger_3') | float(0),
states('sensor.32a_ev_charger_3') | float(0),
states('sensor.underhouse_dehumidifier_3') | float(0),
states('sensor.pool_pump_3') | float(0),
states('sensor.data_cupboard_left_3') | float(0),
states('sensor.data_cupboard_right_3') | float(0),
states('sensor.laundry_dryer_3') | float(0),
states('sensor.washing_machine_3') | float(0),
states('sensor.main_dishwasher_3') | float(0),
states('sensor.3d_printer_3') | float(0),
states('sensor.downstairs_dishwasher_3') | float(0),
states('sensor.main_fridge_3') | float(0),
states('sensor.downstairs_fridge_3') | float(0),
states('sensor.drinks_fridge_3') | float(0),
states('sensor.vertical_freezer_3') | float(0),
states('sensor.arcade_cabinet_1_3') | float(0),
states('sensor.downstairs_lounge_north_gpo_x48pm_3') | float(0)
] %}
{{ ([values | max * 1.1, 0.01] | max) | round(5) }}
- triggers:
- trigger: homeassistant
event: start
- trigger: time
at: "00:00:00"
- trigger: event
event_type: event_template_reloaded
sensor:
- name: Electricity Fixed Charges Total
default_entity_id: sensor.electricity_fixed_charges_total
unique_id: electricity_fixed_charges_total
icon: mdi:calendar-cash
device_class: monetary
unit_of_measurement: "NZD"
state_class: total_increasing
state: >-
{% set rate = states('sensor.electricity_daily_charge') | float(3.4914) %}
{% set last = this.attributes.get('last_charge_date') %}
{% if last %}
{% set days = (now().date() - strptime(last, '%Y-%m-%d').date()).days %}
{{
(
this.state | float(0)
+ ([days, 0] | max) * rate
) | round(5)
}}
{% else %}
{{ rate | round(5) }}
{% endif %}
attributes:
last_charge_date: "{{ now().date() }}"
daily_charge: "{{ states('sensor.electricity_daily_charge') | float(3.4914) }}"
gst_included: true
#:########################################################################################:#
# CUMULATIVE USAGE COSTS
#:########################################################################################:#
sensor:
- platform: integration
source: sensor.electricity_grid_cost_rate
name: Electricity Grid Usage Cost
unique_id: electricity_grid_usage_cost
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_16a_ev_wallcharger_rate
name: Energy Cost 16A EV Wallcharger
unique_id: energy_cost_16a_ev_wallcharger
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_32a_ev_charger_rate
name: Energy Cost 32A EV Charger
unique_id: energy_cost_32a_ev_charger
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_underhouse_dehumidifier_rate
name: Energy Cost Underhouse Dehumidifier
unique_id: energy_cost_underhouse_dehumidifier
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_pool_pump_rate
name: Energy Cost Pool Pump
unique_id: energy_cost_pool_pump
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_data_cupboard_left_rate
name: Energy Cost Data Cupboard Left
unique_id: energy_cost_data_cupboard_left
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_data_cupboard_right_rate
name: Energy Cost Data Cupboard Right
unique_id: energy_cost_data_cupboard_right
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_laundry_dryer_rate
name: Energy Cost Laundry Dryer
unique_id: energy_cost_laundry_dryer
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_laundry_washing_machine_rate
name: Energy Cost Laundry Washing Machine
unique_id: energy_cost_laundry_washing_machine
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_main_dishwasher_rate
name: Energy Cost Main Dishwasher
unique_id: energy_cost_main_dishwasher
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_3d_printer_rate
name: Energy Cost 3D Printer
unique_id: energy_cost_3d_printer
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_downstairs_dishwasher_rate
name: Energy Cost Downstairs Dishwasher
unique_id: energy_cost_downstairs_dishwasher
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_main_fridge_rate
name: Energy Cost Main Fridge
unique_id: energy_cost_main_fridge
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_downstairs_fridge_rate
name: Energy Cost Downstairs Fridge
unique_id: energy_cost_downstairs_fridge
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_drinks_fridge_rate
name: Energy Cost Drinks Fridge
unique_id: energy_cost_drinks_fridge
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_vertical_freezer_rate
name: Energy Cost Vertical Freezer
unique_id: energy_cost_vertical_freezer
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_arcade_cabinet_1_rate
name: Energy Cost Arcade Cabinet 1
unique_id: energy_cost_arcade_cabinet_1
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
- platform: integration
source: sensor.energy_cost_downstairs_lounge_north_gpo_rate
name: Energy Cost Downstairs Lounge North GPO
unique_id: energy_cost_downstairs_lounge_north_gpo
unit_time: h
method: left
round: 5
max_sub_interval:
minutes: 5
#:########################################################################################:#
# APPLIANCE COST RANKING METERS
#:########################################################################################:#
utility_meter:
energy_cost_daily_16a_ev_wallcharger: &energy_cost_daily_meter
source: sensor.energy_cost_16a_ev_wallcharger
name: 16A EV Wallcharger
cycle: daily
periodically_resetting: false
always_available: true
energy_cost_daily_32a_ev_charger:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_32a_ev_charger
name: 32A EV Charger
energy_cost_daily_underhouse_dehumidifier:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_underhouse_dehumidifier
name: Underhouse Dehumidifier
energy_cost_daily_pool_pump:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_pool_pump
name: Pool Pump
energy_cost_daily_data_cupboard_left:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_data_cupboard_left
name: Data Cupboard Left
energy_cost_daily_data_cupboard_right:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_data_cupboard_right
name: Data Cupboard Right
energy_cost_daily_laundry_dryer:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_laundry_dryer
name: Laundry Dryer
energy_cost_daily_laundry_washing_machine:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_laundry_washing_machine
name: Washing Machine
energy_cost_daily_main_dishwasher:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_main_dishwasher
name: Main Dishwasher
energy_cost_daily_3d_printer:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_3d_printer
name: 3D Printer
energy_cost_daily_downstairs_dishwasher:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_downstairs_dishwasher
name: Downstairs Dishwasher
energy_cost_daily_main_fridge:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_main_fridge
name: Main Fridge
energy_cost_daily_downstairs_fridge:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_downstairs_fridge
name: Downstairs Fridge
energy_cost_daily_drinks_fridge:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_drinks_fridge
name: Drinks Fridge
energy_cost_daily_vertical_freezer:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_vertical_freezer
name: Vertical Freezer
energy_cost_daily_arcade_cabinet_1:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_arcade_cabinet_1
name: Arcade Cabinet 1
energy_cost_daily_downstairs_lounge_north_gpo:
<<: *energy_cost_daily_meter
source: sensor.energy_cost_downstairs_lounge_north_gpo
name: Downstairs Lounge North GPO (X48PM)
energy_cost_weekly_16a_ev_wallcharger: &energy_cost_weekly_meter
source: sensor.energy_cost_16a_ev_wallcharger
name: 16A EV Wallcharger
cycle: weekly
periodically_resetting: false
always_available: true
energy_cost_weekly_32a_ev_charger:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_32a_ev_charger
name: 32A EV Charger
energy_cost_weekly_underhouse_dehumidifier:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_underhouse_dehumidifier
name: Underhouse Dehumidifier
energy_cost_weekly_pool_pump:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_pool_pump
name: Pool Pump
energy_cost_weekly_data_cupboard_left:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_data_cupboard_left
name: Data Cupboard Left
energy_cost_weekly_data_cupboard_right:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_data_cupboard_right
name: Data Cupboard Right
energy_cost_weekly_laundry_dryer:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_laundry_dryer
name: Laundry Dryer
energy_cost_weekly_laundry_washing_machine:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_laundry_washing_machine
name: Washing Machine
energy_cost_weekly_main_dishwasher:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_main_dishwasher
name: Main Dishwasher
energy_cost_weekly_3d_printer:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_3d_printer
name: 3D Printer
energy_cost_weekly_downstairs_dishwasher:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_downstairs_dishwasher
name: Downstairs Dishwasher
energy_cost_weekly_main_fridge:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_main_fridge
name: Main Fridge
energy_cost_weekly_downstairs_fridge:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_downstairs_fridge
name: Downstairs Fridge
energy_cost_weekly_drinks_fridge:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_drinks_fridge
name: Drinks Fridge
energy_cost_weekly_vertical_freezer:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_vertical_freezer
name: Vertical Freezer
energy_cost_weekly_arcade_cabinet_1:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_arcade_cabinet_1
name: Arcade Cabinet 1
energy_cost_weekly_downstairs_lounge_north_gpo:
<<: *energy_cost_weekly_meter
source: sensor.energy_cost_downstairs_lounge_north_gpo
name: Downstairs Lounge North GPO (X48PM)
energy_cost_monthly_16a_ev_wallcharger: &energy_cost_monthly_meter
source: sensor.energy_cost_16a_ev_wallcharger
name: 16A EV Wallcharger
cycle: monthly
periodically_resetting: false
always_available: true
energy_cost_monthly_32a_ev_charger:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_32a_ev_charger
name: 32A EV Charger
energy_cost_monthly_underhouse_dehumidifier:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_underhouse_dehumidifier
name: Underhouse Dehumidifier
energy_cost_monthly_pool_pump:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_pool_pump
name: Pool Pump
energy_cost_monthly_data_cupboard_left:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_data_cupboard_left
name: Data Cupboard Left
energy_cost_monthly_data_cupboard_right:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_data_cupboard_right
name: Data Cupboard Right
energy_cost_monthly_laundry_dryer:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_laundry_dryer
name: Laundry Dryer
energy_cost_monthly_laundry_washing_machine:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_laundry_washing_machine
name: Washing Machine
energy_cost_monthly_main_dishwasher:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_main_dishwasher
name: Main Dishwasher
energy_cost_monthly_3d_printer:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_3d_printer
name: 3D Printer
energy_cost_monthly_downstairs_dishwasher:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_downstairs_dishwasher
name: Downstairs Dishwasher
energy_cost_monthly_main_fridge:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_main_fridge
name: Main Fridge
energy_cost_monthly_downstairs_fridge:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_downstairs_fridge
name: Downstairs Fridge
energy_cost_monthly_drinks_fridge:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_drinks_fridge
name: Drinks Fridge
energy_cost_monthly_vertical_freezer:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_vertical_freezer
name: Vertical Freezer
energy_cost_monthly_arcade_cabinet_1:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_arcade_cabinet_1
name: Arcade Cabinet 1
energy_cost_monthly_downstairs_lounge_north_gpo:
<<: *energy_cost_monthly_meter
source: sensor.energy_cost_downstairs_lounge_north_gpo
name: Downstairs Lounge North GPO (X48PM)
+12 -3
View File
@@ -2,6 +2,11 @@
# PACKAGE: Free Power Time Automation # # PACKAGE: Free Power Time Automation #
#:########################################################################################:# #:########################################################################################:#
# VERSION: # VERSION:
# 1.2.1 - 2026-08-17
# - Allow the configured end-time trigger to run without waiting for the
# Free Power Time Active template sensor to update at the same instant.
# The inactive-window check now applies only to startup catch-up.
#
# 1.2.0 - 2026-07-24 # 1.2.0 - 2026-07-24
# - Added startup catch-up to end_actions: when HA starts outside the # - Added startup catch-up to end_actions: when HA starts outside the
# free power window, the end actions (chargers off, dehumidifier off) # free power window, the end actions (chargers off, dehumidifier off)
@@ -321,11 +326,15 @@ automation:
triggers: triggers:
- trigger: time - trigger: time
at: input_datetime.free_power_end_time at: input_datetime.free_power_end_time
id: scheduled_end
- trigger: homeassistant - trigger: homeassistant
event: start event: start
id: startup
conditions: conditions:
- condition: state # The scheduled end must not race the template sensor's minute update.
entity_id: binary_sensor.free_power_time_active # Startup catch-up still runs only outside the free power window.
state: "off" - condition: template
value_template: >-
{{ trigger.id == 'scheduled_end' or is_state('binary_sensor.free_power_time_active', 'off') }}
actions: actions:
- action: script.free_power_end_actions - action: script.free_power_end_actions
+914
View File
@@ -0,0 +1,914 @@
#:########################################################################################:#
# PACKAGE: Fridge and Freezer Monitoring
# FILE: packages/fridge_freezer_monitoring.yaml
# VERSION: v1.10.0 2026-08-29
# - Adds Downstairs Freezer temperature, battery, door, humidity, and shared
# Downstairs Fridge/Freezer plug monitoring.
# - Uses a four-hour sustained low-draw power-loss rule for all monitored
# fridges and freezers, including the shared Drinks Fridge/Freezer plug.
# - v1.8.4 2026-08-24: Flags a Drinks Fridge power loss only after six
# continuous hours below 1 W.
# - v1.8.3 2026-08-24: Excludes normal Drinks Fridge compressor-off cycles
# from power alerts.
# - v1.8.2 2026-08-24: Widens Drinks Fridge and Vertical Freezer adaptive
# temperature ceilings to accommodate confirmed normal compressor cycles.
# - v1.8.1 2026-08-24: Excludes normal Vertical Freezer compressor-off cycles
# from power alerts and widens its learned temperature allowance.
# - v1.8.0 2026-08-24: Adds Drinks Fridge and Vertical Freezer plug monitoring.
# - Adds illuminance-based door monitoring for the Downstairs Fridge.
# - Adds Drinks Fridge door detection from its illuminance sensor.
# - Makes an open door an issue after seven minutes and delays temperature
# alerts while the door is open or cooling recovers after closure.
# - v1.5.0 2026-08-22
# - Requires five minutes of sub-1 W draw before reporting Main Kitchen Fridge power loss.
# - Moved Main Kitchen Fridge power monitoring to the Zigbee X31PP plug.
# - v1.3.0 2026-08-17: Uses the calibrated Main Kitchen Fridge temperature entity throughout.
# - Uses the calibrated Main Kitchen Fridge temperature entity throughout.
# - Added adaptive temperature, battery, humidity, and power monitoring for
# the Downstairs Fridge.
# - v1.1.0 2026-08-16: Reduced the adaptive temperature margin and issue persistence time.
#:########################################################################################:#
# PURPOSE:
# Detect missing monitoring, low batteries, loss of Main Fridge power, and
# abnormal warming, including door-aware handling where a sensor is fitted.
#:########################################################################################:#
# BEHAVIOUR:
# - Learns a continuously rolling seven-day average and 95th percentile.
# - Limits each learned threshold with an appliance-specific safety maximum.
# - Measures the temperature change rate over a smoothed 30-minute window.
# - Requires an issue condition to persist for one minute, except Main Kitchen
# Fridge low-power draw, which persists for five minutes.
# - Treats the Drinks and Downstairs Fridges as open at 10 lx or higher. An
# open door is an issue after seven minutes, while temperature alerting
# stays deferred through a 15-minute post-close recovery window.
# - Exposes one issue binary sensor per appliance and one summary sensor.
#:########################################################################################:#
# NOTES:
# - Learned thresholds use the lower of 95th percentile + 0.5 C or the fixed
# safety limit, so a cooling failure cannot gradually become normal.
# - A rise rate of at least 2 C/hour labels an over-temperature condition as
# possible open-door warming; slower warming is labelled loss of cooling.
# - Main Fridge power below 1 W is treated as loss only after five minutes to
# avoid normal compressor-off periods; unplugged or unavailable monitoring
# remains an issue after one minute.
# - Main Fridge temperature is adjusted by -0.84 C to match the reference
# thermometer.
# - Wine-fridge ESPHome telemetry is intentionally excluded.
#:########################################################################################:#
# DEPENDENCIES:
# - Seven configured fridge/freezer temperature and battery monitor pairs.
# - switch.upstairs_fridge_power_x31pp
# - sensor.upstairs_fridge_power_x31pp_power
# - switch.downstairs_fridge_power_x41pp
# - sensor.downstairs_fridge_power_x41pp_power
# - switch.drinks_fridge_power_x59pm
# - sensor.drinks_fridge_power_x59pm_power
# - switch.vertical_freezer_power_x60pm
# - sensor.vertical_freezer_power_x60pm_power
# - sensor.drinks_fridge_light_sensor_x35ls_illuminance
# - sensor.downstairs_fridge_light_sensor_x58ls_illuminance
# - sensor.downstairs_freezer_light_sensor_x61ls_illuminance
#:########################################################################################:#
sensor:
#:######################################################################################:#
# MAIN KITCHEN FRIDGE
#:######################################################################################:#
- platform: statistics
name: "Main Kitchen Fridge Temperature Average 7 Days"
unique_id: main_kitchen_fridge_temperature_average_7_days
entity_id: sensor.main_kitchen_fridge_temperature_calibrated
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Main Kitchen Fridge Temperature 95th Percentile 7 Days"
unique_id: main_kitchen_fridge_temperature_95th_percentile_7_days
entity_id: sensor.main_kitchen_fridge_temperature_calibrated
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Main Kitchen Fridge Temperature Change Rate"
unique_id: main_kitchen_fridge_temperature_change_rate
source: sensor.main_kitchen_fridge_temperature_calibrated
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
#:######################################################################################:#
# DRINKS FRIDGE
#:######################################################################################:#
- platform: statistics
name: "Drinks Fridge Temperature Average 7 Days"
unique_id: drinks_fridge_temperature_average_7_days
entity_id: sensor.drinks_fridge_environment_lcd_btt01_temperature
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Drinks Fridge Temperature 95th Percentile 7 Days"
unique_id: drinks_fridge_temperature_95th_percentile_7_days
entity_id: sensor.drinks_fridge_environment_lcd_btt01_temperature
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Drinks Fridge Temperature Change Rate"
unique_id: drinks_fridge_temperature_change_rate
source: sensor.drinks_fridge_environment_lcd_btt01_temperature
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
#:######################################################################################:#
# DOWNSTAIRS FRIDGE
#:######################################################################################:#
- platform: statistics
name: "Downstairs Fridge Temperature Average 7 Days"
unique_id: downstairs_fridge_temperature_average_7_days
entity_id: sensor.downstairs_fridge_temperature_x43ts_temperature
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Downstairs Fridge Temperature 95th Percentile 7 Days"
unique_id: downstairs_fridge_temperature_95th_percentile_7_days
entity_id: sensor.downstairs_fridge_temperature_x43ts_temperature
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Downstairs Fridge Temperature Change Rate"
unique_id: downstairs_fridge_temperature_change_rate
source: sensor.downstairs_fridge_temperature_x43ts_temperature
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
#:######################################################################################:#
# DOWNSTAIRS FREEZER
#:######################################################################################:#
- platform: statistics
name: "Downstairs Freezer Temperature Average 7 Days"
unique_id: downstairs_freezer_temperature_average_7_days
entity_id: sensor.downstairs_freezer_temperature_x62ts_temperature
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Downstairs Freezer Temperature 95th Percentile 7 Days"
unique_id: downstairs_freezer_temperature_95th_percentile_7_days
entity_id: sensor.downstairs_freezer_temperature_x62ts_temperature
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Downstairs Freezer Temperature Change Rate"
unique_id: downstairs_freezer_temperature_change_rate
source: sensor.downstairs_freezer_temperature_x62ts_temperature
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
#:######################################################################################:#
# WINE FRIDGE
#:######################################################################################:#
- platform: statistics
name: "Wine Fridge Temperature Average 7 Days"
unique_id: wine_fridge_temperature_average_7_days
entity_id: sensor.lcd_temp_humidity_btt04_temperature
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Wine Fridge Temperature 95th Percentile 7 Days"
unique_id: wine_fridge_temperature_95th_percentile_7_days
entity_id: sensor.lcd_temp_humidity_btt04_temperature
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Wine Fridge Temperature Change Rate"
unique_id: wine_fridge_temperature_change_rate
source: sensor.lcd_temp_humidity_btt04_temperature
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
#:######################################################################################:#
# DRINKS FREEZER
#:######################################################################################:#
- platform: statistics
name: "Drinks Freezer Temperature Average 7 Days"
unique_id: drinks_freezer_temperature_average_7_days
entity_id: sensor.drinks_freezer_environment_lcd_btt02_temperature
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Drinks Freezer Temperature 95th Percentile 7 Days"
unique_id: drinks_freezer_temperature_95th_percentile_7_days
entity_id: sensor.drinks_freezer_environment_lcd_btt02_temperature
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Drinks Freezer Temperature Change Rate"
unique_id: drinks_freezer_temperature_change_rate
source: sensor.drinks_freezer_environment_lcd_btt02_temperature
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
#:######################################################################################:#
# VERTICAL FREEZER
#:######################################################################################:#
- platform: statistics
name: "Vertical Freezer Temperature Average 7 Days"
unique_id: vertical_freezer_temperature_average_7_days
entity_id: sensor.lcd_temp_humidity_btt03_temperature
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Vertical Freezer Temperature 95th Percentile 7 Days"
unique_id: vertical_freezer_temperature_95th_percentile_7_days
entity_id: sensor.lcd_temp_humidity_btt03_temperature
state_characteristic: percentile
percentile: 95
max_age:
days: 7
precision: 1
- platform: derivative
name: "Vertical Freezer Temperature Change Rate"
unique_id: vertical_freezer_temperature_change_rate
source: sensor.lcd_temp_humidity_btt03_temperature
unit_time: h
time_window: "00:30:00"
max_sub_interval: "00:05:00"
round: 1
template:
#:######################################################################################:#
# DOOR DETECTION
#:######################################################################################:#
- binary_sensor:
- name: "Drinks Fridge Door Open"
unique_id: drinks_fridge_door_open
device_class: door
state: >-
{{
has_value('sensor.drinks_fridge_light_sensor_x35ls_illuminance')
and (states('sensor.drinks_fridge_light_sensor_x35ls_illuminance') | float(0)) >= 10
}}
# Retain the grace period after closure so the fridge can start cooling.
- name: "Drinks Fridge Temperature Recovery Grace"
unique_id: drinks_fridge_temperature_recovery_grace
delay_off: "00:15:00"
state: "{{ is_state('binary_sensor.drinks_fridge_door_open', 'on') }}"
- name: "Drinks Fridge Door Open Too Long"
unique_id: drinks_fridge_door_open_too_long
device_class: problem
delay_on: "00:07:00"
state: "{{ is_state('binary_sensor.drinks_fridge_door_open', 'on') }}"
- name: "Downstairs Fridge Door Open"
unique_id: downstairs_fridge_door_open
device_class: door
state: >-
{{
has_value('sensor.downstairs_fridge_light_sensor_x58ls_illuminance')
and (states('sensor.downstairs_fridge_light_sensor_x58ls_illuminance') | float(0)) >= 10
}}
# Retain the grace period after closure so the fridge can start cooling.
- name: "Downstairs Fridge Temperature Recovery Grace"
unique_id: downstairs_fridge_temperature_recovery_grace
delay_off: "00:15:00"
state: "{{ is_state('binary_sensor.downstairs_fridge_door_open', 'on') }}"
- name: "Downstairs Fridge Door Open Too Long"
unique_id: downstairs_fridge_door_open_too_long
device_class: problem
delay_on: "00:07:00"
state: "{{ is_state('binary_sensor.downstairs_fridge_door_open', 'on') }}"
- name: "Downstairs Freezer Door Open"
unique_id: downstairs_freezer_door_open
device_class: door
state: >-
{{
has_value('sensor.downstairs_freezer_light_sensor_x61ls_illuminance')
and (states('sensor.downstairs_freezer_light_sensor_x61ls_illuminance') | float(0)) >= 10
}}
# Retain the grace period after closure so the freezer can start cooling.
- name: "Downstairs Freezer Temperature Recovery Grace"
unique_id: downstairs_freezer_temperature_recovery_grace
delay_off: "00:15:00"
state: "{{ is_state('binary_sensor.downstairs_freezer_door_open', 'on') }}"
- name: "Downstairs Freezer Door Open Too Long"
unique_id: downstairs_freezer_door_open_too_long
device_class: problem
delay_on: "00:07:00"
state: "{{ is_state('binary_sensor.downstairs_freezer_door_open', 'on') }}"
#:######################################################################################:#
# APPLIANCE ISSUE SENSORS
#:######################################################################################:#
- binary_sensor:
- name: "Main Kitchen Fridge Power Draw Lost"
unique_id: main_kitchen_fridge_power_draw_lost
device_class: problem
delay_on: "04:00:00"
state: >-
{{
is_state('switch.upstairs_fridge_power_x31pp', 'on')
and has_value('sensor.upstairs_fridge_power_x31pp_power')
and (states('sensor.upstairs_fridge_power_x31pp_power') | float(0)) < 1
}}
# A compressor-off interval is normal; measure the uninterrupted idle time
# explicitly so a template reload cannot restore this as an active fault.
- name: "Drinks Fridge Power Draw Lost"
unique_id: drinks_fridge_power_draw_lost
device_class: problem
state: >-
{% set power = states('sensor.drinks_fridge_power_x59pm_power') | float(none) %}
{% set changed = states.sensor.drinks_fridge_power_x59pm_power.last_changed %}
{{
is_state('switch.drinks_fridge_power_x59pm', 'on')
and power is not none
and power < 1
and (now() - changed).total_seconds() >= 4 * 60 * 60
}}
- name: "Downstairs Fridge Power Draw Lost"
unique_id: downstairs_fridge_power_draw_lost
device_class: problem
state: >-
{% set power = states('sensor.downstairs_fridge_power_x41pp_power') | float(none) %}
{% set changed = states.sensor.downstairs_fridge_power_x41pp_power.last_changed %}
{{
is_state('switch.downstairs_fridge_power_x41pp', 'on')
and power is not none
and power < 1
and (now() - changed).total_seconds() >= 4 * 60 * 60
}}
- name: "Vertical Freezer Power Draw Lost"
unique_id: vertical_freezer_power_draw_lost
device_class: problem
state: >-
{% set power = states('sensor.vertical_freezer_power_x60pm_power') | float(none) %}
{% set changed = states.sensor.vertical_freezer_power_x60pm_power.last_changed %}
{{
is_state('switch.vertical_freezer_power_x60pm', 'on')
and power is not none
and power < 1
and (now() - changed).total_seconds() >= 4 * 60 * 60
}}
- name: "Main Kitchen Fridge Issue"
unique_id: main_kitchen_fridge_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.main_kitchen_fridge_temperature_calibrated') %}
{% set battery_missing = not has_value('sensor.unk_manufacturer_unk_model_battery') %}
{% set power_missing = not has_value('sensor.upstairs_fridge_power_x31pp_power') %}
{% set percentile = states('sensor.main_kitchen_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [10, percentile + 0.5] | min if percentile is not none else 10 %}
{{
temperature_missing
or battery_missing
or (states('sensor.unk_manufacturer_unk_model_battery') | float(-1)) <= 20
or (not temperature_missing and (states('sensor.main_kitchen_fridge_temperature_calibrated') | float(0)) > threshold)
or states('switch.upstairs_fridge_power_x31pp') != 'on'
or power_missing
or is_state('binary_sensor.main_kitchen_fridge_power_draw_lost', 'on')
}}
attributes:
appliance: "Main Kitchen Fridge"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.main_kitchen_fridge_temperature_calibrated') %}
{% set battery_missing = not has_value('sensor.unk_manufacturer_unk_model_battery') %}
{% set power_missing = not has_value('sensor.upstairs_fridge_power_x31pp_power') %}
{% set percentile = states('sensor.main_kitchen_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [10, percentile + 0.5] | min if percentile is not none else 10 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.unk_manufacturer_unk_model_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if states('switch.upstairs_fridge_power_x31pp') in ['unknown', 'unavailable'] or power_missing %}
{% set ns.items = ns.items + ['Power monitoring unavailable'] %}
{% elif not is_state('switch.upstairs_fridge_power_x31pp', 'on') %}
{% set ns.items = ns.items + ['Power switched off'] %}
{% elif is_state('binary_sensor.main_kitchen_fridge_power_draw_lost', 'on') %}
{% set ns.items = ns.items + ['Power lost - no power draw'] %}
{% endif %}
{% if not temperature_missing and (states('sensor.main_kitchen_fridge_temperature_calibrated') | float(0)) > threshold %}
{% if (states('sensor.main_kitchen_fridge_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.main_kitchen_fridge_temperature_calibrated') }}"
average_7_days: "{{ states('sensor.main_kitchen_fridge_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.main_kitchen_fridge_temperature_95th_percentile_7_days') | float(none) %}
{{ [10, percentile + 0.5] | min if percentile is not none else 10 }}
change_rate: "{{ states('sensor.main_kitchen_fridge_temperature_change_rate') }}"
battery: "{{ states('sensor.unk_manufacturer_unk_model_battery') }}"
power: "{{ states('sensor.upstairs_fridge_power_x31pp_power') }}"
- name: "Drinks Fridge Issue"
unique_id: drinks_fridge_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.drinks_fridge_environment_lcd_btt01_temperature') %}
{% set battery_missing = not has_value('sensor.drinks_fridge_environment_lcd_btt01_battery') %}
{% set power_missing = not has_value('sensor.drinks_fridge_power_x59pm_power') %}
{% set percentile = states('sensor.drinks_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [10, percentile + 3] | min if percentile is not none else 10 %}
{{
temperature_missing
or battery_missing
or (states('sensor.drinks_fridge_environment_lcd_btt01_battery') | float(-1)) <= 20
or states('switch.drinks_fridge_power_x59pm') != 'on'
or power_missing
or is_state('binary_sensor.drinks_fridge_power_draw_lost', 'on')
or is_state('binary_sensor.drinks_fridge_door_open_too_long', 'on')
or (
not temperature_missing
and not is_state('binary_sensor.drinks_fridge_temperature_recovery_grace', 'on')
and (states('sensor.drinks_fridge_environment_lcd_btt01_temperature') | float(0)) > threshold
)
}}
attributes:
appliance: "Drinks Fridge"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.drinks_fridge_environment_lcd_btt01_temperature') %}
{% set battery_missing = not has_value('sensor.drinks_fridge_environment_lcd_btt01_battery') %}
{% set power_missing = not has_value('sensor.drinks_fridge_power_x59pm_power') %}
{% set percentile = states('sensor.drinks_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [10, percentile + 3] | min if percentile is not none else 10 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.drinks_fridge_environment_lcd_btt01_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if states('switch.drinks_fridge_power_x59pm') in ['unknown', 'unavailable'] or power_missing %}
{% set ns.items = ns.items + ['Power monitoring unavailable'] %}
{% elif not is_state('switch.drinks_fridge_power_x59pm', 'on') %}
{% set ns.items = ns.items + ['Power switched off'] %}
{% elif is_state('binary_sensor.drinks_fridge_power_draw_lost', 'on') %}
{% set ns.items = ns.items + ['Power lost - no power draw for 4 hours'] %}
{% endif %}
{% if is_state('binary_sensor.drinks_fridge_door_open_too_long', 'on') %}
{% set ns.items = ns.items + ['Door open too long'] %}
{% elif not temperature_missing and is_state('binary_sensor.drinks_fridge_temperature_recovery_grace', 'on') %}
{% set ns.items = ns.items + ['Temperature alert delayed - door recently open'] %}
{% elif not temperature_missing and (states('sensor.drinks_fridge_environment_lcd_btt01_temperature') | float(0)) > threshold %}
{% if (states('sensor.drinks_fridge_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.drinks_fridge_environment_lcd_btt01_temperature') }}"
average_7_days: "{{ states('sensor.drinks_fridge_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.drinks_fridge_temperature_95th_percentile_7_days') | float(none) %}
{{ [10, percentile + 3] | min if percentile is not none else 10 }}
change_rate: "{{ states('sensor.drinks_fridge_temperature_change_rate') }}"
battery: "{{ states('sensor.drinks_fridge_environment_lcd_btt01_battery') }}"
power: "{{ states('sensor.drinks_fridge_power_x59pm_power') }}"
door_open: "{{ is_state('binary_sensor.drinks_fridge_door_open', 'on') }}"
illuminance: "{{ states('sensor.drinks_fridge_light_sensor_x35ls_illuminance') }}"
- name: "Wine Fridge Issue"
unique_id: wine_fridge_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.lcd_temp_humidity_btt04_temperature') %}
{% set battery_missing = not has_value('sensor.lcd_temp_humidity_btt04_battery') %}
{% set percentile = states('sensor.wine_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [9, percentile + 0.5] | min if percentile is not none else 9 %}
{{
temperature_missing
or battery_missing
or (states('sensor.lcd_temp_humidity_btt04_battery') | float(-1)) <= 20
or (not temperature_missing and (states('sensor.lcd_temp_humidity_btt04_temperature') | float(0)) > threshold)
}}
attributes:
appliance: "Wine Fridge"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.lcd_temp_humidity_btt04_temperature') %}
{% set battery_missing = not has_value('sensor.lcd_temp_humidity_btt04_battery') %}
{% set percentile = states('sensor.wine_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [9, percentile + 0.5] | min if percentile is not none else 9 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.lcd_temp_humidity_btt04_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if not temperature_missing and (states('sensor.lcd_temp_humidity_btt04_temperature') | float(0)) > threshold %}
{% if (states('sensor.wine_fridge_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.lcd_temp_humidity_btt04_temperature') }}"
average_7_days: "{{ states('sensor.wine_fridge_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.wine_fridge_temperature_95th_percentile_7_days') | float(none) %}
{{ [9, percentile + 0.5] | min if percentile is not none else 9 }}
change_rate: "{{ states('sensor.wine_fridge_temperature_change_rate') }}"
battery: "{{ states('sensor.lcd_temp_humidity_btt04_battery') }}"
- name: "Downstairs Fridge Issue"
unique_id: downstairs_fridge_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.downstairs_fridge_temperature_x43ts_temperature') %}
{% set battery_missing = not has_value('sensor.downstairs_fridge_temperature_x43ts_battery') %}
{% set power_missing = not has_value('sensor.downstairs_fridge_power_x41pp_power') %}
{% set percentile = states('sensor.downstairs_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [10, percentile + 0.5] | min if percentile is not none else 10 %}
{{
temperature_missing
or battery_missing
or (states('sensor.downstairs_fridge_temperature_x43ts_battery') | float(-1)) <= 20
or is_state('binary_sensor.downstairs_fridge_door_open_too_long', 'on')
or (
not temperature_missing
and not is_state('binary_sensor.downstairs_fridge_temperature_recovery_grace', 'on')
and (states('sensor.downstairs_fridge_temperature_x43ts_temperature') | float(0)) > threshold
)
or states('switch.downstairs_fridge_power_x41pp') != 'on'
or power_missing
or is_state('binary_sensor.downstairs_fridge_power_draw_lost', 'on')
}}
attributes:
appliance: "Downstairs Fridge"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.downstairs_fridge_temperature_x43ts_temperature') %}
{% set battery_missing = not has_value('sensor.downstairs_fridge_temperature_x43ts_battery') %}
{% set power_missing = not has_value('sensor.downstairs_fridge_power_x41pp_power') %}
{% set percentile = states('sensor.downstairs_fridge_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [10, percentile + 0.5] | min if percentile is not none else 10 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.downstairs_fridge_temperature_x43ts_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if states('switch.downstairs_fridge_power_x41pp') in ['unknown', 'unavailable'] or power_missing %}
{% set ns.items = ns.items + ['Power monitoring unavailable'] %}
{% elif not is_state('switch.downstairs_fridge_power_x41pp', 'on') %}
{% set ns.items = ns.items + ['Power switched off'] %}
{% elif is_state('binary_sensor.downstairs_fridge_power_draw_lost', 'on') %}
{% set ns.items = ns.items + ['Power lost - no power draw for 4 hours'] %}
{% endif %}
{% if is_state('binary_sensor.downstairs_fridge_door_open_too_long', 'on') %}
{% set ns.items = ns.items + ['Door open too long'] %}
{% elif not temperature_missing and is_state('binary_sensor.downstairs_fridge_temperature_recovery_grace', 'on') %}
{% set ns.items = ns.items + ['Temperature alert delayed - door recently open'] %}
{% elif not temperature_missing and (states('sensor.downstairs_fridge_temperature_x43ts_temperature') | float(0)) > threshold %}
{% if (states('sensor.downstairs_fridge_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.downstairs_fridge_temperature_x43ts_temperature') }}"
humidity: "{{ states('sensor.downstairs_fridge_temperature_x43ts_humidity') }}"
average_7_days: "{{ states('sensor.downstairs_fridge_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.downstairs_fridge_temperature_95th_percentile_7_days') | float(none) %}
{{ [10, percentile + 0.5] | min if percentile is not none else 10 }}
change_rate: "{{ states('sensor.downstairs_fridge_temperature_change_rate') }}"
battery: "{{ states('sensor.downstairs_fridge_temperature_x43ts_battery') }}"
power: "{{ states('sensor.downstairs_fridge_power_x41pp_power') }}"
door_open: "{{ is_state('binary_sensor.downstairs_fridge_door_open', 'on') }}"
illuminance: "{{ states('sensor.downstairs_fridge_light_sensor_x58ls_illuminance') }}"
- name: "Downstairs Freezer Issue"
unique_id: downstairs_freezer_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.downstairs_freezer_temperature_x62ts_temperature') %}
{% set battery_missing = not has_value('sensor.downstairs_freezer_temperature_x62ts_battery') %}
{% set power_missing = not has_value('sensor.downstairs_fridge_power_x41pp_power') %}
{% set percentile = states('sensor.downstairs_freezer_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [-5, percentile + 0.5] | min if percentile is not none else -5 %}
{{
temperature_missing
or battery_missing
or (states('sensor.downstairs_freezer_temperature_x62ts_battery') | float(-1)) <= 20
or is_state('binary_sensor.downstairs_freezer_door_open_too_long', 'on')
or (
not temperature_missing
and not is_state('binary_sensor.downstairs_freezer_temperature_recovery_grace', 'on')
and (states('sensor.downstairs_freezer_temperature_x62ts_temperature') | float(0)) > threshold
)
or states('switch.downstairs_fridge_power_x41pp') != 'on'
or power_missing
or is_state('binary_sensor.downstairs_fridge_power_draw_lost', 'on')
}}
attributes:
appliance: "Downstairs Freezer"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.downstairs_freezer_temperature_x62ts_temperature') %}
{% set battery_missing = not has_value('sensor.downstairs_freezer_temperature_x62ts_battery') %}
{% set power_missing = not has_value('sensor.downstairs_fridge_power_x41pp_power') %}
{% set percentile = states('sensor.downstairs_freezer_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [-5, percentile + 0.5] | min if percentile is not none else -5 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.downstairs_freezer_temperature_x62ts_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if states('switch.downstairs_fridge_power_x41pp') in ['unknown', 'unavailable'] or power_missing %}
{% set ns.items = ns.items + ['Shared power monitoring unavailable'] %}
{% elif not is_state('switch.downstairs_fridge_power_x41pp', 'on') %}
{% set ns.items = ns.items + ['Shared power switched off'] %}
{% elif is_state('binary_sensor.downstairs_fridge_power_draw_lost', 'on') %}
{% set ns.items = ns.items + ['Shared power lost - no power draw for 4 hours'] %}
{% endif %}
{% if is_state('binary_sensor.downstairs_freezer_door_open_too_long', 'on') %}
{% set ns.items = ns.items + ['Door open too long'] %}
{% elif not temperature_missing and is_state('binary_sensor.downstairs_freezer_temperature_recovery_grace', 'on') %}
{% set ns.items = ns.items + ['Temperature alert delayed - door recently open'] %}
{% elif not temperature_missing and (states('sensor.downstairs_freezer_temperature_x62ts_temperature') | float(0)) > threshold %}
{% if (states('sensor.downstairs_freezer_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.downstairs_freezer_temperature_x62ts_temperature') }}"
humidity: "{{ states('sensor.downstairs_freezer_temperature_x62ts_humidity') }}"
average_7_days: "{{ states('sensor.downstairs_freezer_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.downstairs_freezer_temperature_95th_percentile_7_days') | float(none) %}
{{ [-5, percentile + 0.5] | min if percentile is not none else -5 }}
change_rate: "{{ states('sensor.downstairs_freezer_temperature_change_rate') }}"
battery: "{{ states('sensor.downstairs_freezer_temperature_x62ts_battery') }}"
power: "{{ states('sensor.downstairs_fridge_power_x41pp_power') }}"
door_open: "{{ is_state('binary_sensor.downstairs_freezer_door_open', 'on') }}"
illuminance: "{{ states('sensor.downstairs_freezer_light_sensor_x61ls_illuminance') }}"
- name: "Drinks Freezer Issue"
unique_id: drinks_freezer_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.drinks_freezer_environment_lcd_btt02_temperature') %}
{% set battery_missing = not has_value('sensor.drinks_freezer_environment_lcd_btt02_battery') %}
{% set power_missing = not has_value('sensor.drinks_fridge_power_x59pm_power') %}
{% set percentile = states('sensor.drinks_freezer_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [-5, percentile + 0.5] | min if percentile is not none else -5 %}
{{
temperature_missing
or battery_missing
or (states('sensor.drinks_freezer_environment_lcd_btt02_battery') | float(-1)) <= 20
or states('switch.drinks_fridge_power_x59pm') != 'on'
or power_missing
or is_state('binary_sensor.drinks_fridge_power_draw_lost', 'on')
or (not temperature_missing and (states('sensor.drinks_freezer_environment_lcd_btt02_temperature') | float(0)) > threshold)
}}
attributes:
appliance: "Drinks Freezer"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.drinks_freezer_environment_lcd_btt02_temperature') %}
{% set battery_missing = not has_value('sensor.drinks_freezer_environment_lcd_btt02_battery') %}
{% set power_missing = not has_value('sensor.drinks_fridge_power_x59pm_power') %}
{% set percentile = states('sensor.drinks_freezer_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [-5, percentile + 0.5] | min if percentile is not none else -5 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.drinks_freezer_environment_lcd_btt02_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if states('switch.drinks_fridge_power_x59pm') in ['unknown', 'unavailable'] or power_missing %}
{% set ns.items = ns.items + ['Power monitoring unavailable'] %}
{% elif not is_state('switch.drinks_fridge_power_x59pm', 'on') %}
{% set ns.items = ns.items + ['Power switched off'] %}
{% elif is_state('binary_sensor.drinks_fridge_power_draw_lost', 'on') %}
{% set ns.items = ns.items + ['Power lost - no power draw for 4 hours'] %}
{% endif %}
{% if not temperature_missing and (states('sensor.drinks_freezer_environment_lcd_btt02_temperature') | float(0)) > threshold %}
{% if (states('sensor.drinks_freezer_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.drinks_freezer_environment_lcd_btt02_temperature') }}"
average_7_days: "{{ states('sensor.drinks_freezer_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.drinks_freezer_temperature_95th_percentile_7_days') | float(none) %}
{{ [-5, percentile + 0.5] | min if percentile is not none else -5 }}
change_rate: "{{ states('sensor.drinks_freezer_temperature_change_rate') }}"
battery: "{{ states('sensor.drinks_freezer_environment_lcd_btt02_battery') }}"
power: "{{ states('sensor.drinks_fridge_power_x59pm_power') }}"
- name: "Vertical Freezer Issue"
unique_id: vertical_freezer_issue
device_class: problem
delay_on: "00:01:00"
state: >-
{% set temperature_missing = not has_value('sensor.lcd_temp_humidity_btt03_temperature') %}
{% set battery_missing = not has_value('sensor.lcd_temp_humidity_btt03_battery') %}
{% set power_missing = not has_value('sensor.vertical_freezer_power_x60pm_power') %}
{% set percentile = states('sensor.vertical_freezer_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [-10, percentile + 6] | min if percentile is not none else -10 %}
{{
temperature_missing
or battery_missing
or (states('sensor.lcd_temp_humidity_btt03_battery') | float(-1)) <= 20
or states('switch.vertical_freezer_power_x60pm') != 'on'
or power_missing
or is_state('binary_sensor.vertical_freezer_power_draw_lost', 'on')
or (not temperature_missing and (states('sensor.lcd_temp_humidity_btt03_temperature') | float(0)) > threshold)
}}
attributes:
appliance: "Vertical Freezer"
issues: >-
{% set ns = namespace(items=[]) %}
{% set temperature_missing = not has_value('sensor.lcd_temp_humidity_btt03_temperature') %}
{% set battery_missing = not has_value('sensor.lcd_temp_humidity_btt03_battery') %}
{% set power_missing = not has_value('sensor.vertical_freezer_power_x60pm_power') %}
{% set percentile = states('sensor.vertical_freezer_temperature_95th_percentile_7_days') | float(none) %}
{% set threshold = [-10, percentile + 6] | min if percentile is not none else -10 %}
{% if temperature_missing or battery_missing %}
{% set ns.items = ns.items + ['Monitor offline'] %}
{% endif %}
{% if not battery_missing and (states('sensor.lcd_temp_humidity_btt03_battery') | float(-1)) <= 20 %}
{% set ns.items = ns.items + ['Low monitor battery'] %}
{% endif %}
{% if states('switch.vertical_freezer_power_x60pm') in ['unknown', 'unavailable'] or power_missing %}
{% set ns.items = ns.items + ['Power monitoring unavailable'] %}
{% elif not is_state('switch.vertical_freezer_power_x60pm', 'on') %}
{% set ns.items = ns.items + ['Power switched off'] %}
{% elif is_state('binary_sensor.vertical_freezer_power_draw_lost', 'on') %}
{% set ns.items = ns.items + ['Power lost - no power draw for 4 hours'] %}
{% endif %}
{% if not temperature_missing and (states('sensor.lcd_temp_humidity_btt03_temperature') | float(0)) > threshold %}
{% if (states('sensor.vertical_freezer_temperature_change_rate') | float(0)) >= 2 %}
{% set ns.items = ns.items + ['Warming rapidly - possible open door'] %}
{% else %}
{% set ns.items = ns.items + ['Temperature too high - possible loss of cooling'] %}
{% endif %}
{% endif %}
{{ ns.items }}
temperature: "{{ states('sensor.lcd_temp_humidity_btt03_temperature') }}"
average_7_days: "{{ states('sensor.vertical_freezer_temperature_average_7_days') }}"
alert_threshold: >-
{% set percentile = states('sensor.vertical_freezer_temperature_95th_percentile_7_days') | float(none) %}
{{ [-10, percentile + 6] | min if percentile is not none else -10 }}
change_rate: "{{ states('sensor.vertical_freezer_temperature_change_rate') }}"
battery: "{{ states('sensor.lcd_temp_humidity_btt03_battery') }}"
power: "{{ states('sensor.vertical_freezer_power_x60pm_power') }}"
#:######################################################################################:#
# SUMMARY STATUS
#:######################################################################################:#
- sensor:
- name: "Fridge Freezer Monitor Status"
unique_id: fridge_freezer_monitor_status
icon: mdi:fridge-alert-outline
state: >-
{{
'Issue'
if is_state('binary_sensor.drinks_fridge_door_open_too_long', 'on')
or is_state('binary_sensor.downstairs_fridge_door_open_too_long', 'on')
else 'Open'
if is_state('binary_sensor.drinks_fridge_door_open', 'on')
or is_state('binary_sensor.downstairs_fridge_door_open', 'on')
else 'Issue'
if expand(
'binary_sensor.main_kitchen_fridge_issue',
'binary_sensor.drinks_fridge_issue',
'binary_sensor.downstairs_fridge_issue',
'binary_sensor.wine_fridge_issue',
'binary_sensor.drinks_freezer_issue',
'binary_sensor.vertical_freezer_issue'
)
| selectattr('state', 'eq', 'on')
| list
| count > 0
else 'OK'
}}
attributes:
issue_count: >-
{{
expand(
'binary_sensor.main_kitchen_fridge_issue',
'binary_sensor.drinks_fridge_issue',
'binary_sensor.downstairs_fridge_issue',
'binary_sensor.wine_fridge_issue',
'binary_sensor.drinks_freezer_issue',
'binary_sensor.vertical_freezer_issue'
)
| selectattr('state', 'eq', 'on')
| list
| count
}}
open_count: >-
{{
[
is_state('binary_sensor.drinks_fridge_door_open', 'on'),
is_state('binary_sensor.downstairs_fridge_door_open', 'on')
]
| select
| list
| count
}}
issues: >-
{% set ns = namespace(items=[]) %}
{% set monitors = [
'binary_sensor.main_kitchen_fridge_issue',
'binary_sensor.drinks_fridge_issue',
'binary_sensor.downstairs_fridge_issue',
'binary_sensor.wine_fridge_issue',
'binary_sensor.drinks_freezer_issue',
'binary_sensor.vertical_freezer_issue'
] %}
{% for monitor in monitors if is_state(monitor, 'on') %}
{% set appliance = state_attr(monitor, 'appliance') or state_attr(monitor, 'friendly_name') %}
{% set reasons = state_attr(monitor, 'issues') or ['Issue detected'] %}
{% for reason in reasons %}
{% set ns.items = ns.items + [appliance ~ ': ' ~ reason] %}
{% endfor %}
{% endfor %}
{{ ns.items }}
+17 -4
View File
@@ -1,15 +1,16 @@
#:########################################################################################:# #:########################################################################################:#
# Package: Refrigerator Temperature MQTT Publishing # Package: Refrigerator Temperature MQTT Publishing
# File: fridge_temperatures.yaml # File: fridge_temperatures.yaml
# Version: v1.0.0 # Version: v1.1.0
# Date: 2024-09-04 # Date: 2026-08-17
# #
# Purpose: # Purpose:
# Publishes refrigerator and freezer temperature changes to household MQTT # Publishes refrigerator and freezer temperature changes to household MQTT
# status topics for use by external displays and consumers. # status topics for use by external displays and consumers.
# #
# Behaviour: # Behaviour:
# - Publishes the source sensor state whenever a valid temperature changes. # - Creates a calibrated main kitchen fridge temperature entity.
# - Publishes each valid temperature entity state whenever it changes.
# - Suppresses unknown and unavailable source states. # - Suppresses unknown and unavailable source states.
# - Covers the drinks fridge, drinks freezer, and main kitchen fridge. # - Covers the drinks fridge, drinks freezer, and main kitchen fridge.
# #
@@ -18,9 +19,21 @@
# - Home Assistant MQTT integration and broker connectivity. # - Home Assistant MQTT integration and broker connectivity.
# #
# Version History: # Version History:
# - v1.1.0 (2026-08-17): Added the -0.84 C main fridge sensor calibration.
# - v1.0.0 (2024-09-04): Initial refrigerator temperature publishing package. # - v1.0.0 (2024-09-04): Initial refrigerator temperature publishing package.
#:########################################################################################:# #:########################################################################################:#
template:
- sensor:
- name: "Main Kitchen Fridge Temperature Calibrated"
unique_id: main_kitchen_fridge_temperature_calibrated
device_class: temperature
state_class: measurement
unit_of_measurement: "°C"
availability: "{{ has_value('sensor.main_kitchen_fridge_temp_zth08') }}"
state: >-
{{ (states('sensor.main_kitchen_fridge_temp_zth08') | float - 0.84) | round(2) }}
automation: automation:
- id: publish_fridge_temps_drinks_fridge - id: publish_fridge_temps_drinks_fridge
alias: publish_fridge_temps_drinks_fridge alias: publish_fridge_temps_drinks_fridge
@@ -56,7 +69,7 @@ automation:
alias: publish_main_kitchen_fridge_temp_zth08 alias: publish_main_kitchen_fridge_temp_zth08
trigger: trigger:
- platform: state - platform: state
entity_id: sensor.main_kitchen_fridge_temp_zth08 entity_id: sensor.main_kitchen_fridge_temperature_calibrated
not_to: not_to:
- "unavailable" - "unavailable"
- "unknown" - "unknown"
-63
View File
@@ -1,63 +0,0 @@
#:########################################################################################:#
# 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
name: Node-RED Panda Up (HTTP)
resource: http://192.168.3.200:1880/healthz
method: GET
device_class: connectivity
value_template: "{{ value_json.status == 'ok' }}"
timeout: 3
scan_interval: 30
# ---- MQTT Will/Birth probe ----
mqtt:
binary_sensor:
- name: "Node-RED Panda Up (MQTT)"
unique_id: "nodered_panda_up_mqtt"
state_topic: "nodered/panda/status"
payload_on: "online"
payload_off: "offline"
device_class: connectivity
qos: 1
# Optional: avoid stale 'online' if no updates arrive
expire_after: 180
# ---- Combine them (true if either is up) ----
template:
- binary_sensor:
- name: "Node-RED Panda Up (HTTP/MQTT)"
unique_id: "nodered_panda_up_combined"
device_class: connectivity
state: >
{{ is_state('binary_sensor.node_red_panda_up_http','on')
or is_state('binary_sensor.node_red_panda_up_mqtt','on') }}
availability: >
{{ states('binary_sensor.node_red_panda_up_http') not in ['unknown','unavailable']
or states('binary_sensor.node_red_panda_up_mqtt') not in ['unknown','unavailable'] }}
+1 -1
View File
@@ -163,7 +163,7 @@ automation:
data: data:
channels: "sony_tv_lounge" channels: "sony_tv_lounge"
title: "View Road" title: "View Road"
short_announcement: "Rinnai Gas Heater" short_announcement: "Gas Fire {{ new_state_text }}"
long_announcement: "" long_announcement: ""
payload: "{{ new_state_text }}" payload: "{{ new_state_text }}"
indicator: "" indicator: ""
+10 -10
View File
@@ -54,8 +54,8 @@ automation:
max: 10 max: 10
variables: variables:
west_switch_mqtt_topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (West)" west_switch_mqtt_topic: "zigbee2mqtt/Master Bedroom Scene Switch West (X54SS)"
east_switch_mqtt_topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (East)" east_switch_mqtt_topic: "zigbee2mqtt/Master Bedroom Scene Switch East (X55SS)"
bedside_light: "switch.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb_jump_fade_up_down" bedside_light: "switch.master_bedroom_bedside_lamp_arlec_bulb_master_bedroom_bedside_lamp_arlec_bulb_jump_fade_up_down"
main_bedroom_south_lights: "switch.master_bedroom_master_bedroom_main_lights_south_relay_2_master_bedroom_south_lights" main_bedroom_south_lights: "switch.master_bedroom_master_bedroom_main_lights_south_relay_2_master_bedroom_south_lights"
@@ -64,50 +64,50 @@ automation:
triggers: triggers:
# West switch actions # West switch actions
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (West)" topic: "zigbee2mqtt/Master Bedroom Scene Switch West (X54SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "1_single" payload: "1_single"
id: west_top_left id: west_top_left
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (West)" topic: "zigbee2mqtt/Master Bedroom Scene Switch West (X54SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "2_single" payload: "2_single"
id: west_top_right id: west_top_right
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (West)" topic: "zigbee2mqtt/Master Bedroom Scene Switch West (X54SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "3_single" payload: "3_single"
id: west_bottom_left id: west_bottom_left
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (West)" topic: "zigbee2mqtt/Master Bedroom Scene Switch West (X54SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "4_single" payload: "4_single"
id: west_bottom_right id: west_bottom_right
# East switch actions # East switch actions
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (East)" topic: "zigbee2mqtt/Master Bedroom Scene Switch East (X55SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "1_single" payload: "1_single"
id: east_top_left id: east_top_left
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (East)" topic: "zigbee2mqtt/Master Bedroom Scene Switch East (X55SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "2_single" payload: "2_single"
id: east_top_right id: east_top_right
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (East)" topic: "zigbee2mqtt/Master Bedroom Scene Switch East (X55SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "3_single" payload: "3_single"
id: east_bottom_left id: east_bottom_left
- trigger: mqtt - trigger: mqtt
topic: "zigbee2mqtt/Master Bedroom 4 Button Switch (East)" topic: "zigbee2mqtt/Master Bedroom Scene Switch East (X55SS)"
value_template: "{{ value_json.action | default('') }}" value_template: "{{ value_json.action | default('') }}"
payload: "4_single" payload: "4_single"
id: east_bottom_right id: east_bottom_right
+510
View File
@@ -0,0 +1,510 @@
#:########################################################################################:#
# Package: Network Monitoring
# File: network_monitoring.yaml
# Version: v1.3.0
# Date: 2026-08-28
#
# Purpose:
# Supplies the calculated Internet, router, MQTT, and Home Assistant health
# information for general network monitoring, including the independent
# Panda and Beaver MQTT brokers, plus Node-RED Panda.
#
# Behaviour:
# - Tracks daily and seven-day Internet speed-test peaks.
# - Calculates rolling 24-hour and seven-day WAN traffic averages and peaks.
# - Measures external HTTP latency and tracks the current public IP address.
# - Monitors the Panda MQTT broker through Home Assistant's birth topic and
# Beaver's MQTT listener through a direct TCP probe.
# - Monitors Node-RED Panda through independent HTTP and MQTT signals.
# - Retains the latest outage time for each monitored network service.
#
# Dependencies:
# - packages/internet_monitoring.yaml
# - packages/edgerouter_snmp.yaml
# - MQTT integration with the default homeassistant/status birth topic.
# - Existing Zigbee2MQTT connectivity entity.
#
# Notes:
# - WAN traffic source values are kB/s and retain that unit in statistics.
# - The 24-hour and seven-day traffic values are rolling windows.
# - Home Assistant's own last outage is represented by sensor.uptime because
# Home Assistant cannot record an event while Core itself is offline.
# - Public IP data is fetched from api.ipify.org every five minutes.
#
# Version History:
# - v1.3.0 (2026-08-28): Renamed package for general network use.
# - v1.2.0 (2026-08-28): Consolidated Node-RED Panda monitoring.
# - v1.1.0 (2026-08-28): Added Beaver MQTT and HA Core health monitoring.
# - v1.0.0 (2026-08-15): Initial monitoring and outage tracking package.
#:########################################################################################:#
command_line:
#:######################################################################################:#
# EXTERNAL NETWORK INFORMATION
#:######################################################################################:#
- sensor:
name: "Flounder External IP"
unique_id: flounder_external_ip
command: >-
curl --fail --silent --show-error --max-time 10 https://api.ipify.org
command_timeout: 15
scan_interval: 300
- sensor:
name: "Flounder Internet Response Time"
unique_id: flounder_internet_response_time
command: >-
curl --fail --silent --show-error --max-time 10 --output /dev/null
--write-out "%{time_total}" https://one.one.one.one/cdn-cgi/trace
command_timeout: 15
scan_interval: 60
unit_of_measurement: "ms"
device_class: duration
state_class: measurement
value_template: "{{ ((value | float(0)) * 1000) | round(0) }}"
- binary_sensor:
name: "Flounder Beaver MQTT Broker Connected"
unique_id: flounder_beaver_mqtt_broker_connected
command: >-
timeout 5 bash -c '>/dev/tcp/172.31.6.1/1883' && echo ON || echo OFF
command_timeout: 6
scan_interval: 60
device_class: connectivity
mqtt:
#:######################################################################################:#
# MQTT BROKER CONNECTION
#:######################################################################################:#
binary_sensor:
- name: "Flounder MQTT Broker Connected"
unique_id: flounder_mqtt_broker_connected
state_topic: "homeassistant/status"
payload_on: "online"
payload_off: "offline"
device_class: connectivity
- name: "Node-RED Panda Up (MQTT)"
unique_id: nodered_panda_up_mqtt
state_topic: "nodered/panda/status"
payload_on: "online"
payload_off: "offline"
device_class: connectivity
qos: 1
expire_after: 180
binary_sensor:
#:######################################################################################:#
# NODE-RED PANDA HTTP HEALTH
#:######################################################################################:#
- platform: rest
name: "Node-RED Panda Up (HTTP)"
resource: http://192.168.3.200:1880/healthz
method: GET
device_class: connectivity
value_template: "{{ value_json.status == 'ok' }}"
timeout: 3
scan_interval: 30
input_number:
#:######################################################################################:#
# DAILY SPEED-TEST PEAKS
#:######################################################################################:#
flounder_internet_download_peak_today:
name: "Flounder Internet Download Peak Today"
min: 0
max: 10000
step: 0.01
mode: box
flounder_internet_upload_peak_today:
name: "Flounder Internet Upload Peak Today"
min: 0
max: 10000
step: 0.01
mode: box
input_datetime:
#:######################################################################################:#
# LAST EXTERNAL IP CHANGE
#:######################################################################################:#
flounder_external_ip_last_changed:
name: "Flounder External IP Last Changed"
has_date: true
has_time: true
input_text:
#:######################################################################################:#
# LAST OUTAGE TIMES
#:######################################################################################:#
flounder_internet_last_outage:
name: "Flounder Internet Last Outage"
max: 40
flounder_edgerouter_last_outage:
name: "Flounder EdgeRouter Last Outage"
max: 40
flounder_mqtt_broker_last_outage:
name: "Flounder MQTT Broker Last Outage"
max: 40
flounder_node_red_mqtt_last_outage:
name: "Flounder Node-RED MQTT Last Outage"
max: 40
flounder_zigbee2mqtt_2_last_outage:
name: "Flounder Zigbee2MQTT 2 Last Outage"
max: 40
flounder_zigbee2mqtt_3_last_outage:
name: "Flounder Zigbee2MQTT 3 Last Outage"
max: 40
template:
#:######################################################################################:#
# POPUP STATUS AND SPEED-TEST VALUES
#:######################################################################################:#
- binary_sensor:
- name: "Flounder EdgeRouter Online"
unique_id: flounder_edgerouter_online
device_class: connectivity
state: "{{ has_value('sensor.edgerouter_uptime') }}"
- name: "Flounder HA Core Online"
unique_id: flounder_ha_core_online
device_class: connectivity
state: "{{ has_value('sensor.home_assistant_core_uptime') }}"
- name: "Node-RED Panda Up (HTTP/MQTT)"
unique_id: nodered_panda_up_combined
device_class: connectivity
state: >-
{{
is_state('binary_sensor.node_red_panda_up_http', 'on')
or is_state('binary_sensor.node_red_panda_up_mqtt', 'on')
}}
availability: >-
{{
states('binary_sensor.node_red_panda_up_http')
not in ['unknown', 'unavailable']
or states('binary_sensor.node_red_panda_up_mqtt')
not in ['unknown', 'unavailable']
}}
- sensor:
- name: "Flounder Internet Download Peak Today"
unique_id: flounder_internet_download_peak_today
unit_of_measurement: "Mbit/s"
device_class: data_rate
state_class: measurement
state: >-
{{ states('input_number.flounder_internet_download_peak_today') | float(0) }}
- name: "Flounder Internet Upload Peak Today"
unique_id: flounder_internet_upload_peak_today
unit_of_measurement: "Mbit/s"
device_class: data_rate
state_class: measurement
state: >-
{{ states('input_number.flounder_internet_upload_peak_today') | float(0) }}
sensor:
#:######################################################################################:#
# INTERNET SPEED-TEST PEAKS
#:######################################################################################:#
- platform: statistics
name: "Flounder Internet Download Peak 7 Days"
unique_id: flounder_internet_download_peak_7_days
entity_id: sensor.internet_speed_test
state_characteristic: value_max
max_age:
days: 7
precision: 2
- platform: statistics
name: "Flounder Internet Upload Peak 7 Days"
unique_id: flounder_internet_upload_peak_7_days
entity_id: sensor.internet_upload_speed_test
state_characteristic: value_max
max_age:
days: 7
precision: 2
#:######################################################################################:#
# WAN DOWNLOAD TRAFFIC STATISTICS
#:######################################################################################:#
- platform: statistics
name: "Flounder WAN Download Average 24 Hours"
unique_id: flounder_wan_download_average_24_hours
entity_id: sensor.edgerouter_wan_download_rate
state_characteristic: average_step
max_age:
hours: 24
precision: 1
- platform: statistics
name: "Flounder WAN Download Peak 24 Hours"
unique_id: flounder_wan_download_peak_24_hours
entity_id: sensor.edgerouter_wan_download_rate
state_characteristic: value_max
max_age:
hours: 24
precision: 1
- platform: statistics
name: "Flounder WAN Download Average 7 Days"
unique_id: flounder_wan_download_average_7_days
entity_id: sensor.edgerouter_wan_download_rate
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Flounder WAN Download Peak 7 Days"
unique_id: flounder_wan_download_peak_7_days
entity_id: sensor.edgerouter_wan_download_rate
state_characteristic: value_max
max_age:
days: 7
precision: 1
#:######################################################################################:#
# WAN UPLOAD TRAFFIC STATISTICS
#:######################################################################################:#
- platform: statistics
name: "Flounder WAN Upload Average 24 Hours"
unique_id: flounder_wan_upload_average_24_hours
entity_id: sensor.edgerouter_wan_upload_rate
state_characteristic: average_step
max_age:
hours: 24
precision: 1
- platform: statistics
name: "Flounder WAN Upload Peak 24 Hours"
unique_id: flounder_wan_upload_peak_24_hours
entity_id: sensor.edgerouter_wan_upload_rate
state_characteristic: value_max
max_age:
hours: 24
precision: 1
- platform: statistics
name: "Flounder WAN Upload Average 7 Days"
unique_id: flounder_wan_upload_average_7_days
entity_id: sensor.edgerouter_wan_upload_rate
state_characteristic: average_step
max_age:
days: 7
precision: 1
- platform: statistics
name: "Flounder WAN Upload Peak 7 Days"
unique_id: flounder_wan_upload_peak_7_days
entity_id: sensor.edgerouter_wan_upload_rate
state_characteristic: value_max
max_age:
days: 7
precision: 1
#:######################################################################################:#
# EXTERNAL RESPONSE-TIME STATISTICS
#:######################################################################################:#
- platform: statistics
name: "Flounder Internet Response Time Average 24 Hours"
unique_id: flounder_internet_response_time_average_24_hours
entity_id: sensor.flounder_internet_response_time
state_characteristic: average_step
max_age:
hours: 24
precision: 0
- platform: statistics
name: "Flounder Internet Response Time Peak 24 Hours"
unique_id: flounder_internet_response_time_peak_24_hours
entity_id: sensor.flounder_internet_response_time
state_characteristic: value_max
max_age:
hours: 24
precision: 0
automation:
#:######################################################################################:#
# SPEED-TEST PEAK TRACKING
#:######################################################################################:#
- id: flounder_network_record_speed_test_peaks
alias: "Flounder Network: Record speed-test peaks"
triggers:
- trigger: state
entity_id: sensor.internet_speed_test
conditions: []
actions:
- delay:
seconds: 1
- condition: template
value_template: >-
{{
has_value('sensor.internet_speed_test')
and has_value('sensor.internet_upload_speed_test')
}}
- action: input_number.set_value
target:
entity_id: input_number.flounder_internet_download_peak_today
data:
value: >-
{{
[
states('sensor.internet_speed_test') | float(0),
states('input_number.flounder_internet_download_peak_today') | float(0)
]
| max
}}
- action: input_number.set_value
target:
entity_id: input_number.flounder_internet_upload_peak_today
data:
value: >-
{{
[
states('sensor.internet_upload_speed_test') | float(0),
states('input_number.flounder_internet_upload_peak_today') | float(0)
]
| max
}}
mode: queued
- id: flounder_network_reset_daily_speed_test_peaks
alias: "Flounder Network: Reset daily speed-test peaks"
triggers:
- trigger: time
at: "00:00:00"
conditions: []
actions:
- action: input_number.set_value
target:
entity_id:
- input_number.flounder_internet_download_peak_today
- input_number.flounder_internet_upload_peak_today
data:
value: 0
mode: single
#:######################################################################################:#
# PUBLIC IP CHANGE TRACKING
#:######################################################################################:#
- id: flounder_network_record_external_ip_change
alias: "Flounder Network: Record external IP change"
triggers:
- trigger: state
entity_id: sensor.flounder_external_ip
conditions:
- condition: template
value_template: >-
{{
trigger.to_state is not none
and trigger.to_state.state not in ['unknown', 'unavailable', 'none', '']
and (
trigger.from_state is none
or trigger.from_state.state != trigger.to_state.state
)
}}
actions:
- action: input_datetime.set_datetime
target:
entity_id: input_datetime.flounder_external_ip_last_changed
data:
timestamp: "{{ now().timestamp() }}"
mode: single
#:######################################################################################:#
# SERVICE OUTAGE TRACKING
#:######################################################################################:#
- id: flounder_network_record_service_outages
alias: "Flounder Network: Record service outages"
triggers:
- trigger: state
entity_id: binary_sensor.internet_connected
id: internet
- trigger: state
entity_id: binary_sensor.flounder_edgerouter_online
id: edgerouter
- trigger: state
entity_id: binary_sensor.flounder_mqtt_broker_connected
id: mqtt_broker
- trigger: state
entity_id: binary_sensor.flounder_beaver_mqtt_broker_connected
id: beaver_mqtt_broker
- trigger: state
entity_id: binary_sensor.node_red_panda_up_2
id: node_red_mqtt
- trigger: state
entity_id: binary_sensor.zigbee2mqtt_bridge_connection_state_2
id: zigbee2mqtt_2
- trigger: state
entity_id: binary_sensor.zigbee2mqtt_bridge_connection_state_3
id: zigbee2mqtt_3
conditions:
- condition: template
value_template: >-
{{
trigger.to_state is not none
and trigger.to_state.state in ['off', 'unavailable']
and (
trigger.from_state is none
or trigger.from_state.state not in ['off', 'unavailable']
)
}}
actions:
- choose:
- conditions: "{{ trigger.id == 'internet' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_internet_last_outage
data:
value: "{{ now().isoformat() }}"
- conditions: "{{ trigger.id == 'edgerouter' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_edgerouter_last_outage
data:
value: "{{ now().isoformat() }}"
- conditions: "{{ trigger.id == 'mqtt_broker' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_mqtt_broker_last_outage
data:
value: "{{ now().isoformat() }}"
- conditions: "{{ trigger.id == 'beaver_mqtt_broker' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_mqtt_broker_last_outage
data:
value: "{{ now().isoformat() }}"
- conditions: "{{ trigger.id == 'node_red_mqtt' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_node_red_mqtt_last_outage
data:
value: "{{ now().isoformat() }}"
- conditions: "{{ trigger.id == 'zigbee2mqtt_2' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_zigbee2mqtt_2_last_outage
data:
value: "{{ now().isoformat() }}"
- conditions: "{{ trigger.id == 'zigbee2mqtt_3' }}"
sequence:
- action: input_text.set_value
target:
entity_id: input_text.flounder_zigbee2mqtt_3_last_outage
data:
value: "{{ now().isoformat() }}"
mode: queued
@@ -3,6 +3,11 @@
################################################################################ ################################################################################
# #
# Version: # Version:
# - 1.2 - 2026-08-16
# - Removed the spare-button trigger after false presses caused unintended
# power toggles.
# - Retains occupancy-based backup control for Office power supplies.
#
# - 1.1 - 2026-07-25 # - 1.1 - 2026-07-25
# - Stripped light control (now handled by direct MQTT) # - Stripped light control (now handled by direct MQTT)
# - Retains power supply backup control only # - Retains power supply backup control only
@@ -20,16 +25,9 @@
# - Turns Office power supplies off after the Office mmWave occupancy sensor # - Turns Office power supplies off after the Office mmWave occupancy sensor
# has been clear for the configured delay period. # has been clear for the configured delay period.
# #
# - Provides a backup manual control path using the Office spare button.
# This is useful if the occupancy sensor is offline.
#
# Notes: # Notes:
# - This is treated as a BACKUP automation for POWER SUPPLIES ONLY. # - This is treated as a BACKUP automation for POWER SUPPLIES ONLY.
# - Light control is handled by direct ESPHome/MQTT (see device YAMLs). # - Light control is handled by direct ESPHome/MQTT (see device YAMLs).
# - The physical spare button is read using:
# binary_sensor.esp_officelights_button_3_spare
# - The spare relay is used as the manual master/latch state:
# switch.esp_officelights_relay_3_spare
# #
################################################################################ ################################################################################
@@ -58,9 +56,8 @@ automation:
description: > description: >
Backup automation for Office power supplies. Turns power on when Office Backup automation for Office power supplies. Turns power on when Office
mmWave occupancy or movement is detected. Turns power off after the mmWave occupancy or movement is detected. Turns power off after the
occupancy sensor is clear for the configured delay period. Also allows occupancy sensor is clear for the configured delay period. Light control
manual backup toggling using the Office spare button and spare relay. is handled by direct ESPHome/MQTT sync.
Light control is handled by direct ESPHome/MQTT sync.
mode: restart mode: restart
triggers: triggers:
########################################################################## ##########################################################################
@@ -79,19 +76,6 @@ automation:
to: "off" to: "off"
id: occupancy_clear id: occupancy_clear
##########################################################################
# Manual backup button press.
#
# The spare relay state is used as the master/latch:
# - If spare relay is OFF when the button is pressed, turn power ON.
# - If spare relay is ON when the button is pressed, turn power OFF.
##########################################################################
- trigger: state
entity_id: binary_sensor.esp_officelights_button_3_spare
from: "off"
to: "on"
id: backup_button_pressed
########################################################################## ##########################################################################
# Startup check. # Startup check.
# #
@@ -214,102 +198,3 @@ automation:
- action: switch.turn_off - action: switch.turn_off
target: target:
entity_id: switch.esp_officelights_relay_3_spare entity_id: switch.esp_officelights_relay_3_spare
######################################################################
# MANUAL BACKUP LOGIC:
# Button pressed while spare relay master is ON - turn power off.
######################################################################
- alias: "Backup button pressed - spare relay on, turn power off"
conditions:
- condition: trigger
id: backup_button_pressed
- condition: state
entity_id: switch.esp_officelights_relay_3_spare
state: "on"
sequence:
##############################################################
# Power off.
##############################################################
- alias: "Turn off Office right hand GPO if needed"
if:
- condition: state
entity_id: switch.esp_officedeskrightgpo_office_right_hand_gpo
state: "on"
then:
- action: switch.turn_off
target:
entity_id: switch.esp_officedeskrightgpo_office_right_hand_gpo
- alias: "Turn off Office ELV power supply if needed"
if:
- condition: state
entity_id: switch.esp_officeelvcontrol_office_elv_power_supply
state: "on"
then:
- action: switch.turn_off
target:
entity_id: switch.esp_officeelvcontrol_office_elv_power_supply
##############################################################
# Manual master/latch relay off last.
##############################################################
- alias: "Turn off Office spare relay master if needed"
if:
- condition: state
entity_id: switch.esp_officelights_relay_3_spare
state: "on"
then:
- action: switch.turn_off
target:
entity_id: switch.esp_officelights_relay_3_spare
######################################################################
# MANUAL BACKUP LOGIC:
# Button pressed while spare relay master is OFF - turn power on.
#
# No off timer is started from this manual press. If the occupancy
# sensor later changes to clear, the normal clear-delay logic will run.
######################################################################
- alias: "Backup button pressed - spare relay off, turn power on"
conditions:
- condition: trigger
id: backup_button_pressed
- condition: state
entity_id: switch.esp_officelights_relay_3_spare
state: "off"
sequence:
##############################################################
# Power supplies.
##############################################################
- alias: "Turn on Office ELV power supply if needed"
if:
- condition: state
entity_id: switch.esp_officeelvcontrol_office_elv_power_supply
state: "off"
then:
- action: switch.turn_on
target:
entity_id: switch.esp_officeelvcontrol_office_elv_power_supply
- alias: "Turn on Office right hand GPO if needed"
if:
- condition: state
entity_id: switch.esp_officedeskrightgpo_office_right_hand_gpo
state: "off"
then:
- action: switch.turn_on
target:
entity_id: switch.esp_officedeskrightgpo_office_right_hand_gpo
##############################################################
# Manual master/latch relay.
##############################################################
- alias: "Turn on Office spare relay master if needed"
if:
- condition: state
entity_id: switch.esp_officelights_relay_3_spare
state: "off"
then:
- action: switch.turn_on
target:
entity_id: switch.esp_officelights_relay_3_spare
+101
View File
@@ -0,0 +1,101 @@
#:########################################################################################:#
# TP-Link Deco Guest Wi-Fi Package #
#:########################################################################################:#
#
# TITLE:
# TP-Link Deco Guest Wi-Fi
#
# FILE:
# packages/tplink_deco_guest_wifi.yaml
#
# VERSION:
# V2.2 2026-08-24
#
# VERSION HISTORY:
# V2.2 2026-08-24
# - Replaced passive status sync with router-confirmed enforcement.
# - Repeats requests, reconciles at startup and hourly, and alerts when
# the Deco cannot confirm the requested state.
#
# V2.1 2026-08-04
# - Added the original router-status sync automation.
#
# V2.0 2026-08-04
# - Switched from Alexa voice commands to the Deco local API.
#
# PURPOSE:
# Enforces the requested TP-Link Deco guest Wi-Fi state and confirms it
# against the guest-network sensor reported by the router.
#
# DEPENDENCIES:
# - tplink_deco integration must be configured and connected to the Deco.
# - input_boolean.wifi_guest_network represents the requested guest-network
# state.
# - binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network reports
# the actual guest-network state from the router.
#
#:########################################################################################:#
automation:
- id: enforce_wifi_guest_network_state
alias: "Enforce Wifi Guest Network State"
description: >
Reconciles requested guest Wi-Fi with the Deco's reported state, repeats
the request once after 30 seconds, and alerts on an unconfirmed mismatch.
mode: restart
triggers:
- trigger: state
entity_id: input_boolean.wifi_guest_network
- trigger: state
entity_id: binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network
- trigger: homeassistant
event: start
- trigger: time_pattern
hours: "*"
minutes: "0"
conditions:
- condition: template
value_template: >
{{ states('input_boolean.wifi_guest_network') in ['on', 'off']
and states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network')
in ['on', 'off']
and states('input_boolean.wifi_guest_network')
!= states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') }}
actions:
- repeat:
count: 2
sequence:
- choose:
- conditions:
- condition: state
entity_id: input_boolean.wifi_guest_network
state: "on"
sequence:
- action: tplink_deco.set_guest_network
data:
enable: true
- conditions:
- condition: state
entity_id: input_boolean.wifi_guest_network
state: "off"
sequence:
- action: tplink_deco.set_guest_network
data:
enable: false
- wait_template: >
{{ states('input_boolean.wifi_guest_network')
== states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') }}
timeout:
seconds: 30
continue_on_timeout: true
- condition: template
value_template: >
{{ states('input_boolean.wifi_guest_network')
!= states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') }}
- action: persistent_notification.create
data:
notification_id: wifi_guest_network_state_mismatch
title: "Guest Wi-Fi state not confirmed"
message: >
The Deco still reports Guest Wi-Fi as {{ states('binary_sensor.data_cupboard_new_sundamage_12_deco_guest_network') }}
after two requests to set it {{ states('input_boolean.wifi_guest_network') }}.