764 lines
31 KiB
YAML
764 lines
31 KiB
YAML
################################################################################
|
|
# PACKAGE: Appliance Monitor Processor
|
|
################################################################################
|
|
#
|
|
# Version: 1.2
|
|
# Date: 2026-06-09
|
|
#
|
|
# Purpose:
|
|
# Shared appliance power monitoring processor.
|
|
#
|
|
# This script is called by appliances_status_monitoring.yaml. It reads the
|
|
# previous appliance monitor state from the appliance monitor MQTT sensor,
|
|
# calculates the next state and cycle totals, publishes retained JSON state
|
|
# back to MQTT, and calls the View Road announcement router when a cycle
|
|
# finishes.
|
|
#
|
|
# Design:
|
|
# - One shared processing script.
|
|
# - MQTT retained JSON state per appliance.
|
|
# - One MQTT sensor per appliance.
|
|
# - Timestamp-based energy and cost calculation.
|
|
# - Rolling average state detection.
|
|
#
|
|
# Default values:
|
|
# debug_flow: false
|
|
# resolution: 7
|
|
# standby_power: 2
|
|
# operating_power_min: 12
|
|
# paid_tariff_start: "00:00"
|
|
# paid_tariff_end: "21:00"
|
|
# paid_tariff_cost: 0.2023
|
|
# offpeak_tariff_cost: 0.0
|
|
# max_sample_gap_seconds: 900
|
|
# min_cycle_notify_seconds: 300
|
|
#
|
|
# States:
|
|
# Off
|
|
# Standby
|
|
# Operating
|
|
# Finished
|
|
#
|
|
# Notes:
|
|
# Version 1.2:
|
|
# - Added min_cycle_notify_seconds.
|
|
# - Finished cycles shorter than this are still recorded, but do not send
|
|
# notifications.
|
|
# - Added MQTT attributes showing whether notification was allowed or
|
|
# suppressed.
|
|
#
|
|
# Version 1.1:
|
|
# - Accepts lower-case retained states from previous package versions, such
|
|
# as standby, operating, and finished.
|
|
#
|
|
################################################################################
|
|
# FINISHED ANNOUNCEMENT
|
|
# The finishing script calls the package view_road_announcement_router.yaml.
|
|
# If you want to use your own announcement system, it will occur at the end
|
|
# at am_cycle_notify_allowed being true, so change to your own announcement
|
|
# method there.
|
|
#
|
|
# Announcements can use:
|
|
# am_announcement_channels
|
|
# am_announcement_title
|
|
# am_short_announcement
|
|
# am_long_announcement
|
|
# am_matrix_indicator
|
|
#
|
|
################################################################################
|
|
|
|
script:
|
|
appliance_monitor_process_sample:
|
|
alias: Appliance Monitor - Process Sample
|
|
icon: mdi:meter-electric
|
|
mode: queued
|
|
max: 50
|
|
|
|
fields:
|
|
appliance_id:
|
|
name: Appliance ID
|
|
description: Short unique ID, for example washing_machine.
|
|
selector:
|
|
text:
|
|
|
|
appliance_name:
|
|
name: Appliance Name
|
|
description: Human readable appliance name.
|
|
selector:
|
|
text:
|
|
|
|
appliance_action:
|
|
name: Appliance Action
|
|
description: Human readable action, for example washing.
|
|
selector:
|
|
text:
|
|
|
|
power_w:
|
|
name: Power W
|
|
description: Current instantaneous appliance power in watts.
|
|
selector:
|
|
number:
|
|
min: 0
|
|
max: 10000
|
|
step: 0.1
|
|
unit_of_measurement: W
|
|
|
|
monitor_sensor:
|
|
name: Monitor Sensor
|
|
description: MQTT monitor sensor used to read previous state.
|
|
selector:
|
|
entity:
|
|
domain: sensor
|
|
|
|
state_topic:
|
|
name: MQTT State Topic
|
|
description: Retained MQTT topic used to publish monitor state JSON.
|
|
selector:
|
|
text:
|
|
|
|
min_cycle_notify_seconds:
|
|
name: Minimum Cycle Notify Seconds
|
|
description: Finished cycles shorter than this are recorded but do not send notifications.
|
|
default: 300
|
|
selector:
|
|
number:
|
|
min: 0
|
|
max: 86400
|
|
step: 1
|
|
unit_of_measurement: s
|
|
|
|
sequence:
|
|
##########################################################################
|
|
# CORE DEFAULTS AND PASSED VALUES
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_appliance_id: "{{ appliance_id | default('appliance', true) | string }}"
|
|
am_appliance_name: "{{ appliance_name | default('Appliance', true) | string }}"
|
|
am_appliance_action: "{{ appliance_action | default('running', true) | string }}"
|
|
am_power_w: "{{ power_w | default(0, true) | float(0) }}"
|
|
am_monitor_sensor: "{{ monitor_sensor | default('', true) | string }}"
|
|
am_state_topic: "{{ state_topic | default('', true) | string }}"
|
|
|
|
am_debug_flow: "{{ debug_flow | default(false, true) | bool(false) }}"
|
|
am_resolution: "{{ resolution | default(7, true) | int(7) }}"
|
|
am_standby_power: "{{ standby_power | default(2, true) | float(2) }}"
|
|
am_operating_power_min: "{{ operating_power_min | default(12, true) | float(12) }}"
|
|
|
|
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_cost: "{{ paid_tariff_cost | default(0.2023, true) | float(0.2023) }}"
|
|
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_min_cycle_notify_seconds: "{{ min_cycle_notify_seconds | default(300, true) | int(300) }}"
|
|
|
|
##########################################################################
|
|
# ANNOUNCEMENT DEFAULTS AND PASSED VALUES
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_announcement_channels: >-
|
|
{{ announcement_channels | default('pushover_zorruno, sony_tv_lounge, lounge_google_home_voice, led_matrix_1', true) | string }}
|
|
|
|
am_short_announcement: >-
|
|
{{ short_announcement | default((am_appliance_action | title) ~ ' complete', true) | string }}
|
|
|
|
am_long_announcement_override: >-
|
|
{{ long_announcement | default('', true) | string }}
|
|
|
|
am_announcement_title: >-
|
|
{{ title | default(am_appliance_name ~ ' Notification', true) | string }}
|
|
|
|
am_matrix_indicator: "{{ matrix_indicator | default('', true) | string }}"
|
|
|
|
##########################################################################
|
|
# PREVIOUS STATE FROM MQTT MONITOR SENSOR ATTRIBUTES
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_now_ts: "{{ as_timestamp(now()) }}"
|
|
|
|
am_previous_state: >-
|
|
{%- set s = states(am_monitor_sensor) | string | lower | trim -%}
|
|
{{ {'operating': 'Operating', 'finished': 'Finished', 'standby': 'Standby', 'off': 'Off'}.get(s, 'Off') }}
|
|
|
|
am_last_power_w: "{{ state_attr(am_monitor_sensor, 'last_power_w') | float(am_power_w) }}"
|
|
am_last_sample_ts: "{{ state_attr(am_monitor_sensor, 'last_sample_ts') | float(0) }}"
|
|
|
|
am_existing_cycle_start_ts: "{{ state_attr(am_monitor_sensor, 'cycle_start_ts') | float(0) }}"
|
|
am_existing_cycle_stop_ts: "{{ state_attr(am_monitor_sensor, 'cycle_stop_ts') | float(0) }}"
|
|
|
|
am_existing_cycle_energy_kwh: "{{ state_attr(am_monitor_sensor, 'cycle_energy_kwh') | float(0) }}"
|
|
am_existing_cycle_paid_energy_kwh: "{{ state_attr(am_monitor_sensor, 'cycle_paid_energy_kwh') | float(0) }}"
|
|
am_existing_cycle_free_energy_kwh: "{{ state_attr(am_monitor_sensor, 'cycle_free_energy_kwh') | float(0) }}"
|
|
am_existing_cycle_cost: "{{ state_attr(am_monitor_sensor, 'cycle_cost') | float(0) }}"
|
|
|
|
am_previous_recent_samples_json: >-
|
|
{% set raw = state_attr(am_monitor_sensor, 'recent_samples_json') | string | trim %}
|
|
{{ raw if raw.startswith('[') else '[]' }}
|
|
|
|
##########################################################################
|
|
# ELAPSED TIME
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_elapsed_sec_raw: >-
|
|
{% if am_last_sample_ts | float(0) > 0 %}
|
|
{{ (am_now_ts | float(0)) - (am_last_sample_ts | float(0)) }}
|
|
{% else %}
|
|
0
|
|
{% endif %}
|
|
|
|
am_effective_elapsed_sec: >-
|
|
{% set elapsed = am_elapsed_sec_raw | float(0) %}
|
|
{% if elapsed <= 0 %}
|
|
0
|
|
{% elif elapsed > (am_max_sample_gap_seconds | int(900)) %}
|
|
{{ am_max_sample_gap_seconds | int(900) }}
|
|
{% else %}
|
|
{{ elapsed }}
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# ROLLING AVERAGE
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_previous_recent_samples_list: >-
|
|
{% set raw_json = state_attr(am_monitor_sensor, 'recent_samples_json') %}
|
|
{% set raw_list = state_attr(am_monitor_sensor, 'recent_samples') %}
|
|
{% if raw_json is string %}
|
|
{{ raw_json | from_json(default=[]) }}
|
|
{% elif raw_json is iterable and raw_json is not string %}
|
|
{{ raw_json | list }}
|
|
{% elif raw_list is iterable and raw_list is not string %}
|
|
{{ raw_list | list }}
|
|
{% else %}
|
|
[]
|
|
{% endif %}
|
|
|
|
am_recent_samples_list: >-
|
|
{% set old = am_previous_recent_samples_list %}
|
|
{% set ns = namespace(items=[am_power_w | float(0)]) %}
|
|
{% for item in old %}
|
|
{% if loop.index < (am_resolution | int(7)) %}
|
|
{% set ns.items = ns.items + [item | float(0)] %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{{ ns.items }}
|
|
|
|
am_recent_samples_json: >-
|
|
{{ am_recent_samples_list | to_json }}
|
|
|
|
am_average_power_w: >-
|
|
{% set samples = am_recent_samples_list %}
|
|
{% if samples | count > 0 %}
|
|
{{ ((samples | sum) / (samples | count)) | round(3) }}
|
|
{% else %}
|
|
0
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# STATE MACHINE
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_new_state: >-
|
|
{% set avg = am_average_power_w | float(0) %}
|
|
{% set standby = am_standby_power | float(2) %}
|
|
{% set operate = am_operating_power_min | float(12) %}
|
|
{% set prev = am_previous_state | string %}
|
|
{% if prev == 'Operating' and avg < operate %}
|
|
Finished
|
|
{% elif avg < standby %}
|
|
Off
|
|
{% elif avg >= operate %}
|
|
Operating
|
|
{% elif avg >= standby and prev in ['Off', 'Finished'] %}
|
|
Standby
|
|
{% else %}
|
|
{{ prev }}
|
|
{% endif %}
|
|
|
|
am_state_changed: "{{ (am_new_state | string) != (am_previous_state | string) }}"
|
|
am_cycle_started: "{{ am_new_state == 'Operating' and am_previous_state != 'Operating' }}"
|
|
am_cycle_finished: "{{ am_new_state == 'Finished' and am_previous_state == 'Operating' }}"
|
|
|
|
##########################################################################
|
|
# ACCUMULATION WINDOW
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_accumulation_seconds: >-
|
|
{% set elapsed = am_effective_elapsed_sec | float(0) %}
|
|
{% if elapsed <= 0 %}
|
|
0
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Operating' %}
|
|
{{ elapsed }}
|
|
{% elif am_previous_state != 'Operating' and am_new_state == 'Operating' %}
|
|
{{ elapsed / 2 }}
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Finished' %}
|
|
{{ elapsed / 2 }}
|
|
{% else %}
|
|
0
|
|
{% endif %}
|
|
|
|
am_accumulation_average_power_w: >-
|
|
{% set last = am_last_power_w | float(0) %}
|
|
{% set current = am_power_w | float(0) %}
|
|
{% set operate = am_operating_power_min | float(12) %}
|
|
{% if am_previous_state == 'Operating' and am_new_state == 'Operating' %}
|
|
{{ ((last + current) / 2) | round(3) }}
|
|
{% elif am_previous_state != 'Operating' and am_new_state == 'Operating' %}
|
|
{{ ((operate + current) / 2) | round(3) }}
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Finished' %}
|
|
{{ ((last + operate) / 2) | round(3) }}
|
|
{% else %}
|
|
0
|
|
{% endif %}
|
|
|
|
am_accumulation_start_ts: >-
|
|
{% set elapsed = am_effective_elapsed_sec | float(0) %}
|
|
{% set accum = am_accumulation_seconds | float(0) %}
|
|
{% if accum <= 0 %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Operating' %}
|
|
{{ (am_now_ts | float(0)) - elapsed }}
|
|
{% elif am_previous_state != 'Operating' and am_new_state == 'Operating' %}
|
|
{{ (am_now_ts | float(0)) - accum }}
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Finished' %}
|
|
{{ (am_now_ts | float(0)) - elapsed }}
|
|
{% else %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% endif %}
|
|
|
|
am_accumulation_stop_ts: >-
|
|
{% set elapsed = am_effective_elapsed_sec | float(0) %}
|
|
{% set accum = am_accumulation_seconds | float(0) %}
|
|
{% if accum <= 0 %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Operating' %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% elif am_previous_state != 'Operating' and am_new_state == 'Operating' %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% elif am_previous_state == 'Operating' and am_new_state == 'Finished' %}
|
|
{{ (am_now_ts | float(0)) - elapsed + accum }}
|
|
{% else %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% endif %}
|
|
|
|
am_interval_mid_ts: >-
|
|
{% set start = am_accumulation_start_ts | float(0) %}
|
|
{% set accum = am_accumulation_seconds | float(0) %}
|
|
{% if accum > 0 %}
|
|
{{ start + (accum / 2) }}
|
|
{% else %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# TARIFF CALCULATION
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_paid_start_minutes: >-
|
|
{% set p = (am_paid_tariff_start | string).split(':') %}
|
|
{% set hh = p[0] if p | count > 0 else 0 %}
|
|
{% set mm = p[1] if p | count > 1 else 0 %}
|
|
{{ (hh | int(0)) * 60 + (mm | int(0)) }}
|
|
|
|
am_paid_end_minutes: >-
|
|
{% set p = (am_paid_tariff_end | string).split(':') %}
|
|
{% set hh = p[0] if p | count > 0 else 0 %}
|
|
{% set mm = p[1] if p | count > 1 else 0 %}
|
|
{{ (hh | int(0)) * 60 + (mm | int(0)) }}
|
|
|
|
am_interval_mid_minutes: >-
|
|
{% set h = am_interval_mid_ts | float(0) | timestamp_custom('%H', true) | int(0) %}
|
|
{% set m = am_interval_mid_ts | float(0) | timestamp_custom('%M', true) | int(0) %}
|
|
{{ h * 60 + m }}
|
|
|
|
am_is_paid_interval: >-
|
|
{% set start = am_paid_start_minutes | int(0) %}
|
|
{% set end = am_paid_end_minutes | int(0) %}
|
|
{% set mid = am_interval_mid_minutes | int(0) %}
|
|
{% if start == end %}
|
|
true
|
|
{% elif start < end %}
|
|
{{ mid >= start and mid < end }}
|
|
{% else %}
|
|
{{ mid >= start or mid < end }}
|
|
{% endif %}
|
|
|
|
am_tariff_rate: >-
|
|
{% if am_is_paid_interval | bool(false) %}
|
|
{{ am_paid_tariff_cost | float(0.2023) }}
|
|
{% else %}
|
|
{{ am_offpeak_tariff_cost | float(0.0) }}
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# INTERVAL ENERGY AND COST
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_interval_energy_kwh: >-
|
|
{{
|
|
(
|
|
(am_accumulation_average_power_w | float(0))
|
|
* (am_accumulation_seconds | float(0))
|
|
/ 3600000
|
|
) | round(6)
|
|
}}
|
|
|
|
am_interval_cost: >-
|
|
{{ ((am_interval_energy_kwh | float(0)) * (am_tariff_rate | float(0))) | round(6) }}
|
|
|
|
am_should_accumulate: "{{ (am_accumulation_seconds | float(0)) > 0 }}"
|
|
|
|
##########################################################################
|
|
# CYCLE TOTAL BASES
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_cycle_energy_base_kwh: >-
|
|
{% if am_cycle_started | bool(false) %}
|
|
0
|
|
{% else %}
|
|
{{ am_existing_cycle_energy_kwh | float(0) }}
|
|
{% endif %}
|
|
|
|
am_cycle_paid_energy_base_kwh: >-
|
|
{% if am_cycle_started | bool(false) %}
|
|
0
|
|
{% else %}
|
|
{{ am_existing_cycle_paid_energy_kwh | float(0) }}
|
|
{% endif %}
|
|
|
|
am_cycle_free_energy_base_kwh: >-
|
|
{% if am_cycle_started | bool(false) %}
|
|
0
|
|
{% else %}
|
|
{{ am_existing_cycle_free_energy_kwh | float(0) }}
|
|
{% endif %}
|
|
|
|
am_cycle_cost_base: >-
|
|
{% if am_cycle_started | bool(false) %}
|
|
0
|
|
{% else %}
|
|
{{ am_existing_cycle_cost | float(0) }}
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# CYCLE TOTALS
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_cycle_energy_new_kwh: >-
|
|
{% set add_kwh = am_interval_energy_kwh | float(0) if am_should_accumulate | bool(false) else 0 %}
|
|
{{ ((am_cycle_energy_base_kwh | float(0)) + add_kwh) | round(6) }}
|
|
|
|
am_cycle_cost_new: >-
|
|
{% set add_cost = am_interval_cost | float(0) if am_should_accumulate | bool(false) else 0 %}
|
|
{{ ((am_cycle_cost_base | float(0)) + add_cost) | round(6) }}
|
|
|
|
am_cycle_paid_energy_new_kwh: >-
|
|
{% set add_kwh = am_interval_energy_kwh | float(0) if (am_should_accumulate | bool(false) and am_is_paid_interval | bool(false)) else 0 %}
|
|
{{ ((am_cycle_paid_energy_base_kwh | float(0)) + add_kwh) | round(6) }}
|
|
|
|
am_cycle_free_energy_new_kwh: >-
|
|
{% set add_kwh = am_interval_energy_kwh | float(0) if (am_should_accumulate | bool(false) and not (am_is_paid_interval | bool(false))) else 0 %}
|
|
{{ ((am_cycle_free_energy_base_kwh | float(0)) + add_kwh) | round(6) }}
|
|
|
|
##########################################################################
|
|
# CYCLE TIMES
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_cycle_start_ts_new: >-
|
|
{% if am_cycle_started | bool(false) %}
|
|
{{ am_accumulation_start_ts | float(0) }}
|
|
{% elif am_existing_cycle_start_ts | float(0) > 0 %}
|
|
{{ am_existing_cycle_start_ts | float(0) }}
|
|
{% else %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% endif %}
|
|
|
|
am_cycle_stop_ts_new: >-
|
|
{% if am_cycle_finished | bool(false) %}
|
|
{{ am_accumulation_stop_ts | float(0) }}
|
|
{% elif am_cycle_started | bool(false) %}
|
|
{{ am_accumulation_start_ts | float(0) }}
|
|
{% elif am_existing_cycle_stop_ts | float(0) > 0 %}
|
|
{{ am_existing_cycle_stop_ts | float(0) }}
|
|
{% else %}
|
|
{{ am_now_ts | float(0) }}
|
|
{% endif %}
|
|
|
|
am_cycle_duration_seconds_new: >-
|
|
{% set start = am_cycle_start_ts_new | float(0) %}
|
|
{% if am_new_state == 'Operating' %}
|
|
{% set stop = am_now_ts | float(0) %}
|
|
{% else %}
|
|
{% set stop = am_cycle_stop_ts_new | float(0) %}
|
|
{% endif %}
|
|
{% if start > 0 and stop > start %}
|
|
{{ (stop - start) | int(0) }}
|
|
{% else %}
|
|
0
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# FORMATTED VALUES
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_cycle_energy_formatted: >-
|
|
{% set kwh = am_cycle_energy_new_kwh | float(0) %}
|
|
{% if kwh >= 1 %}
|
|
{{ '%.1f' | format(kwh) }}kWh
|
|
{% elif kwh < 0.001 %}
|
|
<1Wh
|
|
{% else %}
|
|
{{ '%.1f' | format(kwh * 1000) }}Wh
|
|
{% endif %}
|
|
|
|
am_cycle_cost_formatted: >-
|
|
{% set val = am_cycle_cost_new | float(0) %}
|
|
{% if val < 0.01 %}
|
|
<1c
|
|
{% elif val < 1 %}
|
|
{{ (val * 100) | round(0) | int(0) }}c
|
|
{% else %}
|
|
${{ '%.2f' | format(val) }}
|
|
{% endif %}
|
|
|
|
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)) %}
|
|
{{ ([0, val] | max) | round(4) }}
|
|
|
|
am_cycle_offpeak_savings_formatted: >-
|
|
{% set val = am_cycle_offpeak_savings | float(0) %}
|
|
{% if val < 0.01 %}
|
|
<1c
|
|
{% elif val < 1 %}
|
|
{{ (val * 100) | round(0) | int(0) }}c
|
|
{% else %}
|
|
${{ '%.2f' | format(val) }}
|
|
{% endif %}
|
|
|
|
am_cycle_duration_formatted: >-
|
|
{% set d = am_cycle_duration_seconds_new | int(0) %}
|
|
{% set h = (d // 3600) | int(0) %}
|
|
{% set m = ((d % 3600) // 60) | int(0) %}
|
|
{% if h < 1 %}
|
|
{{ '%02dmin' | format(m) }}
|
|
{% else %}
|
|
{{ '%02dh %02dmin' | format(h, m) }}
|
|
{% endif %}
|
|
|
|
am_cycle_start_time_formatted: >-
|
|
{{ am_cycle_start_ts_new | float(0) | timestamp_custom('%H:%M', true) }}
|
|
|
|
am_cycle_stop_time_formatted: >-
|
|
{{ am_cycle_stop_ts_new | float(0) | timestamp_custom('%H:%M', true) }}
|
|
|
|
am_cycle_start_local: >-
|
|
{{ am_cycle_start_ts_new | float(0) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}
|
|
|
|
am_cycle_stop_local: >-
|
|
{{ am_cycle_stop_ts_new | float(0) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}
|
|
|
|
am_last_sample_local: >-
|
|
{{ am_now_ts | float(0) | timestamp_custom('%Y-%m-%d %H:%M:%S', true) }}
|
|
|
|
##########################################################################
|
|
# SUMMARY STRINGS
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_summary_short: >-
|
|
Your {{ am_appliance_action }} is complete.
|
|
|
|
am_summary: >-
|
|
Your {{ am_appliance_action }} is complete.
|
|
It started at {{ am_cycle_start_time_formatted | trim }},
|
|
finished at {{ am_cycle_stop_time_formatted | trim }},
|
|
and used {{ am_cycle_energy_formatted | trim }},
|
|
taking {{ am_cycle_duration_formatted | trim }}
|
|
at a cost of {{ am_cycle_cost_formatted | trim }}
|
|
(Off-peak savings of {{ am_cycle_offpeak_savings_formatted | trim }}).
|
|
|
|
am_last_summary_new: >-
|
|
{% if am_cycle_started | bool(false) %}
|
|
Cycle running
|
|
{% elif am_cycle_finished | bool(false) %}
|
|
{{ am_summary }}
|
|
{% else %}
|
|
{{ state_attr(am_monitor_sensor, 'last_summary') | default('No cycle recorded', true) }}
|
|
{% endif %}
|
|
|
|
am_long_announcement: >-
|
|
{% if am_long_announcement_override | trim | length > 0 %}
|
|
{{ am_long_announcement_override }}
|
|
{% else %}
|
|
{{ am_summary }}
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# NOTIFICATION DECISION
|
|
##########################################################################
|
|
#
|
|
# Very short cycles are still recorded, but no announcement is sent.
|
|
# Default minimum is 300 seconds / 5 minutes.
|
|
#
|
|
##########################################################################
|
|
|
|
- variables:
|
|
am_cycle_notify_allowed: >-
|
|
{{
|
|
am_cycle_finished | bool(false)
|
|
and (am_cycle_duration_seconds_new | int(0)) >= (am_min_cycle_notify_seconds | int(300))
|
|
}}
|
|
|
|
am_cycle_notification_suppressed: >-
|
|
{{
|
|
am_cycle_finished | bool(false)
|
|
and not (am_cycle_notify_allowed | bool(false))
|
|
}}
|
|
|
|
am_cycle_notification_suppressed_reason: >-
|
|
{% if am_cycle_notification_suppressed | bool(false) %}
|
|
Cycle was shorter than {{ am_min_cycle_notify_seconds | int(300) }} seconds
|
|
{% endif %}
|
|
|
|
##########################################################################
|
|
# PUBLISH RETAINED MQTT STATE
|
|
##########################################################################
|
|
|
|
- action: mqtt.publish
|
|
data:
|
|
topic: "{{ am_state_topic }}"
|
|
retain: true
|
|
qos: 0
|
|
payload: >-
|
|
{{
|
|
dict(
|
|
appliance_id=am_appliance_id,
|
|
appliance_name=am_appliance_name,
|
|
appliance_action=am_appliance_action,
|
|
|
|
state=am_new_state,
|
|
previous_state=am_previous_state,
|
|
state_changed=am_state_changed | bool(false),
|
|
|
|
power_w=am_power_w | float(0),
|
|
last_power_w=am_power_w | float(0),
|
|
previous_power_w=am_last_power_w | float(0),
|
|
average_power_w=am_average_power_w | float(0),
|
|
recent_samples=am_recent_samples_list,
|
|
recent_samples_json=am_recent_samples_json,
|
|
|
|
last_sample_ts=am_now_ts | float(0),
|
|
last_sample_local=am_last_sample_local,
|
|
|
|
cycle_start_ts=am_cycle_start_ts_new | float(0),
|
|
cycle_stop_ts=am_cycle_stop_ts_new | float(0),
|
|
cycle_start_local=am_cycle_start_local,
|
|
cycle_stop_local=am_cycle_stop_local,
|
|
|
|
cycle_duration_seconds=am_cycle_duration_seconds_new | int(0),
|
|
cycle_duration_formatted=am_cycle_duration_formatted | trim,
|
|
|
|
min_cycle_notify_seconds=am_min_cycle_notify_seconds | int(300),
|
|
cycle_notify_allowed=am_cycle_notify_allowed | bool(false),
|
|
cycle_notification_suppressed=am_cycle_notification_suppressed | bool(false),
|
|
cycle_notification_suppressed_reason=am_cycle_notification_suppressed_reason | trim,
|
|
|
|
cycle_energy_kwh=am_cycle_energy_new_kwh | float(0),
|
|
cycle_paid_energy_kwh=am_cycle_paid_energy_new_kwh | float(0),
|
|
cycle_free_energy_kwh=am_cycle_free_energy_new_kwh | float(0),
|
|
cycle_energy_formatted=am_cycle_energy_formatted | trim,
|
|
|
|
cycle_cost=am_cycle_cost_new | float(0),
|
|
cycle_cost_formatted=am_cycle_cost_formatted | trim,
|
|
|
|
cycle_offpeak_savings=am_cycle_offpeak_savings | float(0),
|
|
cycle_offpeak_savings_formatted=am_cycle_offpeak_savings_formatted | trim,
|
|
|
|
paid_tariff_start=am_paid_tariff_start,
|
|
paid_tariff_end=am_paid_tariff_end,
|
|
paid_tariff_cost=am_paid_tariff_cost | float(0),
|
|
offpeak_tariff_cost=am_offpeak_tariff_cost | float(0),
|
|
|
|
accumulation_seconds=am_accumulation_seconds | float(0),
|
|
accumulation_average_power_w=am_accumulation_average_power_w | float(0),
|
|
interval_energy_kwh=am_interval_energy_kwh | float(0),
|
|
interval_cost=am_interval_cost | float(0),
|
|
is_paid_interval=am_is_paid_interval | bool(false),
|
|
|
|
summary_short=am_summary_short,
|
|
summary=am_summary,
|
|
last_summary=am_last_summary_new
|
|
) | to_json
|
|
}}
|
|
|
|
##########################################################################
|
|
# DEBUG LOGBOOK ENTRY
|
|
##########################################################################
|
|
|
|
- if:
|
|
- condition: template
|
|
value_template: "{{ am_debug_flow | bool(false) }}"
|
|
then:
|
|
- action: logbook.log
|
|
data:
|
|
name: "Appliance Monitor"
|
|
message: >-
|
|
{{ am_appliance_name }}: {{ am_previous_state }} -> {{ am_new_state }},
|
|
power {{ am_power_w | round(1) }}W,
|
|
average {{ am_average_power_w | round(1) }}W,
|
|
cycle {{ am_cycle_energy_new_kwh | round(3) }}kWh,
|
|
cost {{ am_cycle_cost_new | round(4) }}
|
|
|
|
##########################################################################
|
|
# SHORT CYCLE SUPPRESSION LOG
|
|
##########################################################################
|
|
|
|
- if:
|
|
- condition: template
|
|
value_template: "{{ am_cycle_notification_suppressed | bool(false) }}"
|
|
then:
|
|
- action: logbook.log
|
|
data:
|
|
name: "Appliance Monitor"
|
|
message: >-
|
|
{{ am_appliance_name }} finished after
|
|
{{ am_cycle_duration_formatted | trim }}, below the
|
|
{{ am_min_cycle_notify_seconds | int(300) }} second notification
|
|
threshold. No announcement was sent.
|
|
|
|
##########################################################################
|
|
# FINISHED ANNOUNCEMENT
|
|
##########################################################################
|
|
# This script is in additional package view_road_announcement_router.yaml.
|
|
# If you want to use your own announcement system, it will occur here
|
|
# at am_cycle_notify_allowed being true.
|
|
#
|
|
# Change to your own announcement method here if needed.
|
|
##########################################################################
|
|
|
|
- if:
|
|
- condition: template
|
|
value_template: "{{ am_cycle_notify_allowed | bool(false) }}"
|
|
then:
|
|
- action: script.view_road_announcement
|
|
data:
|
|
channels: "{{ am_announcement_channels }}"
|
|
title: "{{ am_announcement_title }}"
|
|
short_announcement: "{{ am_short_announcement }}"
|
|
long_announcement: "{{ am_long_announcement }}"
|
|
payload: ""
|
|
indicator: "{{ am_matrix_indicator }}"
|