3355f0dda8
Treat 'unavailable'/'none'/'-'/'' remaining-time values as not-running and parse with int(0) defaults so a partially invalid sensor value degrades to 0 instead of raising. Bump manifest to 2.14.1.
1173 lines
48 KiB
YAML
1173 lines
48 KiB
YAML
# Washing Machine / Dryer Notifications Blueprint
|
|
# Monitors appliance cycles and sends notifications for start, completion,
|
|
# errors, and more. See README.md for detailed documentation.
|
|
#
|
|
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
|
|
|
|
blueprint:
|
|
name: "Custom: Washing Machine Notifications"
|
|
description: >
|
|
Sends notifications when washing starts (with mode details),
|
|
when errors occur, and when run completes.
|
|
domain: automation
|
|
|
|
# ===========================================================================
|
|
# INPUT CONFIGURATION
|
|
# ===========================================================================
|
|
input:
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Time & State Sensors
|
|
# -------------------------------------------------------------------------
|
|
# Core sensors for tracking the appliance cycle state
|
|
time_group:
|
|
name: "Time"
|
|
collapsed: false
|
|
input:
|
|
remaining_time_sensor:
|
|
name: Remaining Time Sensor
|
|
description: >
|
|
Sensor that contains remaining time in format `hh:mm:ss`.
|
|
Note: `-`, 'unknown' values mean remaining time is not available.
|
|
selector:
|
|
entity:
|
|
domain: sensor
|
|
|
|
run_state_sensor:
|
|
name: Run State Sensor
|
|
description: "Sensor that reports the run state of the device"
|
|
selector:
|
|
entity:
|
|
domain: sensor
|
|
|
|
run_state_completion_id:
|
|
name: Run State Completion ID
|
|
description: "Identifier of run state that indicates that the cycle is completed"
|
|
default: 'Цикл завершен.'
|
|
selector:
|
|
text:
|
|
|
|
notify_time_to_end:
|
|
name: Notify Time-to-End (minutes)
|
|
description: "Send notification when cycle is about to finish (minutes remaining)"
|
|
default: 10
|
|
selector:
|
|
number:
|
|
min: 1
|
|
max: 60
|
|
unit_of_measurement: minutes
|
|
mode: slider
|
|
|
|
show_estimated_end_time:
|
|
name: Show Estimated End Time
|
|
description: Include estimated completion time in start notification
|
|
default: true
|
|
selector:
|
|
boolean:
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Unload Reminder
|
|
# -------------------------------------------------------------------------
|
|
# Optional reminder to unload the appliance after the cycle completes.
|
|
# Purely time-based: no door/state tracking required.
|
|
unload_reminder_group:
|
|
name: "Unload Reminder"
|
|
collapsed: true
|
|
input:
|
|
unload_reminder_delay:
|
|
name: Unload Reminder Delay (minutes)
|
|
description: >
|
|
Send a reminder this many minutes after the cycle completes
|
|
if the appliance has not been emptied yet.
|
|
Set to 0 to disable unload reminders entirely.
|
|
default: 0
|
|
selector:
|
|
number:
|
|
min: 0
|
|
max: 180
|
|
unit_of_measurement: minutes
|
|
mode: slider
|
|
|
|
unload_reminder_repeat_count:
|
|
name: Unload Reminder Repeat Count
|
|
description: >
|
|
Total number of unload reminders to send (including the first).
|
|
Set to 1 for a single reminder.
|
|
default: 1
|
|
selector:
|
|
number:
|
|
min: 1
|
|
max: 10
|
|
mode: slider
|
|
|
|
unload_reminder_repeat_interval:
|
|
name: Unload Reminder Repeat Interval (minutes)
|
|
description: >
|
|
Time between repeated unload reminders.
|
|
Only used when Repeat Count > 1.
|
|
default: 15
|
|
selector:
|
|
number:
|
|
min: 5
|
|
max: 120
|
|
unit_of_measurement: minutes
|
|
mode: slider
|
|
|
|
unload_reminder_door_sensors:
|
|
name: Unload Reminder Door Sensors (optional)
|
|
description: >
|
|
Optional list of door / lid binary sensors. When any of them
|
|
opens after the cycle completes, pending unload reminders are
|
|
cancelled (treated as "user has unloaded").
|
|
Leave empty to keep reminders purely time-based.
|
|
default: []
|
|
selector:
|
|
entity:
|
|
domain: binary_sensor
|
|
multiple: true
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Persistent State Configuration
|
|
# -------------------------------------------------------------------------
|
|
# Stores automation state between triggers to track notification history
|
|
persistent_state:
|
|
name: "Persistent State"
|
|
collapsed: false
|
|
input:
|
|
automation_state_entity:
|
|
name: Automation state entity
|
|
description: >
|
|
`input_text` entity that stores the automation state in JSON format.
|
|
Required for all features to function properly.
|
|
Doesn't require specific initial state - values can be empty.
|
|
selector:
|
|
entity:
|
|
domain: input_text
|
|
|
|
automation_state_placeholder_key:
|
|
name: Automation state placeholder key
|
|
description: >
|
|
Overrides key for persistent storage if not empty.
|
|
By default uses the remaining time sensor entity ID.
|
|
Don't override if you don't understand the meaning.
|
|
default: ''
|
|
selector:
|
|
text:
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Notification Settings
|
|
# -------------------------------------------------------------------------
|
|
notification_group:
|
|
name: "Notification"
|
|
collapsed: false
|
|
input:
|
|
input_device_name:
|
|
name: "Device Name"
|
|
description: "Device name used in notification messages (available as `{appliance_name}` in templates)"
|
|
default: "Стиральная машина"
|
|
selector:
|
|
text:
|
|
|
|
notify_target:
|
|
name: Notification Target
|
|
description: "Notification service entity to send messages to"
|
|
selector:
|
|
entity:
|
|
domain: notify
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Message Templates (Customizable Notifications)
|
|
# -------------------------------------------------------------------------
|
|
# All messages support Jinja2 templates. Available variables depend on
|
|
# the notification type - see header comments for full list.
|
|
messages_group:
|
|
name: "Messages"
|
|
collapsed: true
|
|
input:
|
|
message_start:
|
|
name: "Start Message"
|
|
description: >
|
|
Message sent when cycle starts.
|
|
Variables: `{appliance_name}`, `{remaining}`, `{details}`, `{estimated_end}`
|
|
default: "🧺 {appliance_name}: старт. Длительность: `{remaining}`. Завершение: ~{estimated_end}.{details}"
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_completed:
|
|
name: "Completed Message"
|
|
description: >
|
|
Message sent when cycle completes.
|
|
Variables: `{appliance_name}`
|
|
default: "🟢 {appliance_name}: завершено. Не забудьте достать вещи!"
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_unload_reminder:
|
|
name: "Unload Reminder Message"
|
|
description: >
|
|
Reminder sent after the cycle completes if the appliance has not
|
|
been unloaded within the configured delay.
|
|
Variables: `{appliance_name}`, `{minutes}` (elapsed since completion)
|
|
default: "🧺 {appliance_name}: прошло {minutes} мин. — пора достать вещи!"
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_almost_done:
|
|
name: "Almost Done Message"
|
|
description: >
|
|
Message sent when cycle is about to finish.
|
|
Variables: `{appliance_name}`, `{minutes}`
|
|
default: "🟢 {appliance_name}: завершение через {minutes} минут."
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_preparation:
|
|
name: "Preparation Message"
|
|
description: >
|
|
Message sent when device enters preparation mode.
|
|
Variables: `{appliance_name}`
|
|
default: "⚙️ {appliance_name}: подготовка активирована!"
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_error:
|
|
name: "Error Message"
|
|
description: >
|
|
Message sent when an error occurs.
|
|
Variables: `{appliance_name}`, `{error}`
|
|
default: "⚠️ {appliance_name}: ошибка. Детали: {error}. Для более подробной информации обратитесь к приложению LG ThinQ."
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_tub_clean:
|
|
name: "Tub Clean Reminder Message"
|
|
description: >
|
|
Message sent when tub cleaning is needed.
|
|
Variables: `{appliance_name}`, `{tub_count}`, `{tub_threshold}`
|
|
default: "⚠️ {appliance_name}: внимание. Необходима чистка барабана. Число стирок: {tub_count}. Допустимый предел: {tub_threshold}."
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_paused:
|
|
name: "Paused Message"
|
|
description: >
|
|
Message sent when cycle is paused.
|
|
Variables: `{appliance_name}`, `{remaining}`
|
|
default: "⏸️ {appliance_name}: пауза. Осталось: {remaining}."
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_resumed:
|
|
name: "Resumed Message"
|
|
description: >
|
|
Message sent when cycle resumes.
|
|
Variables: `{appliance_name}`, `{remaining}`
|
|
default: "▶️ {appliance_name}: продолжение. Осталось: {remaining}."
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
message_energy_report:
|
|
name: "Energy Report Message"
|
|
description: >
|
|
Appended to completion message with energy stats.
|
|
Variables: `{energy_kwh}`, `{energy_cost}`
|
|
default: " Потребление: {energy_kwh} kWh."
|
|
selector:
|
|
text:
|
|
multiline: true
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Device Information Sensors
|
|
# -------------------------------------------------------------------------
|
|
info_group:
|
|
name: "Info"
|
|
collapsed: false
|
|
input:
|
|
non_running_state_ids:
|
|
name: Non Running State ID(s)
|
|
description: >
|
|
List of run state ID(s) that indicate the device is not actively
|
|
running a cycle (e.g., 'Pause', 'Standby', 'Ready')
|
|
default: []
|
|
selector:
|
|
text:
|
|
multiple: true
|
|
|
|
pause_state_ids:
|
|
name: Pause State ID(s)
|
|
description: >
|
|
List of run state ID(s) that indicate the device is paused
|
|
(e.g., 'Пауза', 'Pause'). Used for pause/resume notifications.
|
|
default: []
|
|
selector:
|
|
text:
|
|
multiple: true
|
|
|
|
preparation_state_id:
|
|
name: Preparation Mode State ID (optional)
|
|
description: >
|
|
Optional run state ID that indicates the device is preparing
|
|
(e.g., dryer heating up before starting)
|
|
default: 'Подготовка к сушке'
|
|
selector:
|
|
text:
|
|
|
|
error_message_sensor:
|
|
name: Error Message Sensor
|
|
description: "Sensor that reports error messages from the device"
|
|
selector:
|
|
entity:
|
|
domain: sensor
|
|
|
|
tub_clean_counter_sensor:
|
|
name: Tub Clean Counter Sensor (optional)
|
|
description: "Sensor that reports the number of cycles since last tub cleaning"
|
|
selector:
|
|
entity:
|
|
domain:
|
|
- sensor
|
|
- input_number
|
|
|
|
tub_clean_counter_threshold:
|
|
name: Tub Clean Counter Threshold
|
|
description: >
|
|
Number of cycles after which a tub cleaning reminder is sent.
|
|
Set to 0 to disable tub cleaning notifications.
|
|
default: 30
|
|
selector:
|
|
number:
|
|
min: 0
|
|
max: 100
|
|
mode: slider
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Startup Details (optional additional info in start notification)
|
|
# -------------------------------------------------------------------------
|
|
details_group:
|
|
name: "Details"
|
|
collapsed: false
|
|
input:
|
|
startup_info_sensors:
|
|
name: Startup Info Sensors (optional)
|
|
description: >
|
|
List of sensors with additional details to include in the
|
|
startup notification (e.g., temperature, spin speed, program)
|
|
default: []
|
|
selector:
|
|
entity:
|
|
domain:
|
|
- sensor
|
|
- binary_sensor
|
|
multiple: true
|
|
|
|
startup_info_texts:
|
|
name: Startup Info Texts
|
|
description: >
|
|
Labels for each sensor in `Startup Info Sensors` list.
|
|
Must have the same number of entries as sensors.
|
|
default: []
|
|
selector:
|
|
text:
|
|
multiple: true
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Power Monitoring
|
|
# -------------------------------------------------------------------------
|
|
power_monitoring:
|
|
name: "Power Monitoring"
|
|
collapsed: true
|
|
input:
|
|
power_sensor:
|
|
name: Power Sensor (optional)
|
|
description: >
|
|
Sensor that reports current power consumption in watts.
|
|
Used to track energy usage per cycle.
|
|
default: []
|
|
selector:
|
|
entity:
|
|
domain: sensor
|
|
device_class: power
|
|
multiple: true
|
|
|
|
energy_cost_per_kwh:
|
|
name: Energy Cost per kWh
|
|
description: >
|
|
Cost per kilowatt-hour for energy calculations.
|
|
Set to 0 to disable cost display.
|
|
default: 0
|
|
selector:
|
|
number:
|
|
min: 0
|
|
max: 100
|
|
step: 0.01
|
|
unit_of_measurement: "currency/kWh"
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Debug
|
|
# -------------------------------------------------------------------------
|
|
debug:
|
|
name: "Debug"
|
|
collapsed: true
|
|
input:
|
|
enable_debug_notifications:
|
|
name: Enable Debug Notifications
|
|
description: >
|
|
Send persistent notifications for debugging automation behavior.
|
|
Shows state changes, trigger details, and variable values.
|
|
default: false
|
|
selector:
|
|
boolean:
|
|
|
|
# =============================================================================
|
|
# AUTOMATION MODE
|
|
# =============================================================================
|
|
# Restart mode ensures new triggers interrupt any running automation
|
|
# (useful for rapid state changes)
|
|
mode: restart
|
|
|
|
# =============================================================================
|
|
# TRIGGERS
|
|
# =============================================================================
|
|
# Monitor all relevant sensors for state changes
|
|
trigger:
|
|
# Remaining time changes (tracks cycle progress)
|
|
- platform: state
|
|
entity_id: !input remaining_time_sensor
|
|
|
|
# Error message appears
|
|
- platform: state
|
|
entity_id: !input error_message_sensor
|
|
|
|
# Run state changes (start, stop, pause, etc.)
|
|
- platform: state
|
|
entity_id: !input run_state_sensor
|
|
|
|
# Tub clean counter changed
|
|
- platform: state
|
|
entity_id: !input tub_clean_counter_sensor
|
|
|
|
# Power sensor updates (for energy tracking)
|
|
# Note: Uses multiple selector, so empty list means trigger is skipped
|
|
- platform: state
|
|
entity_id: !input power_sensor
|
|
id: "power_update"
|
|
|
|
# Periodic tick for time-based checks (e.g., unload reminder)
|
|
- platform: time_pattern
|
|
minutes: "/1"
|
|
id: "reminder_tick"
|
|
|
|
# Door / lid opened after completion (cancels unload reminder)
|
|
# Note: Uses multiple selector, so empty list means trigger is skipped
|
|
- platform: state
|
|
entity_id: !input unload_reminder_door_sensors
|
|
to: 'on'
|
|
id: "door_opened"
|
|
|
|
# =============================================================================
|
|
# CONDITIONS
|
|
# =============================================================================
|
|
# No global conditions - individual cases handle their own logic
|
|
condition: []
|
|
|
|
# =============================================================================
|
|
# VARIABLES
|
|
# =============================================================================
|
|
variables:
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Persistent State Keys
|
|
# ---------------------------------------------------------------------------
|
|
# Short keys to minimize JSON storage size in input_text entity
|
|
state_notification_about_remaining_time_sent: 'nart' # "Almost done" notification sent
|
|
state_notification_about_start_sent: 'nass' # Start notification sent
|
|
state_notification_about_preparation_sent: 'naps' # Preparation notification sent
|
|
state_notification_about_pause_sent: 'napas' # Pause notification sent
|
|
state_was_paused: 'wasp' # Track if cycle was paused
|
|
state_cycle_start_time: 'cst' # Cycle start timestamp
|
|
state_energy_samples: 'esmp' # Energy sample accumulator (Wh)
|
|
state_last_sample_time: 'lst' # Last power sample timestamp
|
|
state_cycle_completion_time: 'cct' # Cycle completion timestamp (for unload reminder)
|
|
state_unload_reminder_count: 'urc' # Number of unload reminders already sent
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Input Variables
|
|
# ---------------------------------------------------------------------------
|
|
run_state_sensor: !input run_state_sensor
|
|
non_running_state_ids: !input non_running_state_ids
|
|
pause_state_ids: !input pause_state_ids
|
|
preparation_state_id: !input preparation_state_id
|
|
error_message_sensor: !input error_message_sensor
|
|
remaining_time_sensor: !input remaining_time_sensor
|
|
notify_target: !input notify_target
|
|
notify_time_to_end: !input notify_time_to_end
|
|
input_device_name: !input input_device_name
|
|
tub_clean_counter_sensor: !input tub_clean_counter_sensor
|
|
tub_clean_counter_threshold: !input tub_clean_counter_threshold
|
|
run_state_completion_id: !input run_state_completion_id
|
|
show_estimated_end_time: !input show_estimated_end_time
|
|
power_sensor: !input power_sensor
|
|
energy_cost_per_kwh: !input energy_cost_per_kwh
|
|
unload_reminder_delay: !input unload_reminder_delay
|
|
unload_reminder_repeat_count: !input unload_reminder_repeat_count
|
|
unload_reminder_repeat_interval: !input unload_reminder_repeat_interval
|
|
unload_reminder_door_sensors: !input unload_reminder_door_sensors
|
|
enable_debug_notifications: !input enable_debug_notifications
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Message Templates
|
|
# ---------------------------------------------------------------------------
|
|
message_start_template: !input message_start
|
|
message_completed_template: !input message_completed
|
|
message_unload_reminder_template: !input message_unload_reminder
|
|
message_almost_done_template: !input message_almost_done
|
|
message_preparation_template: !input message_preparation
|
|
message_error_template: !input message_error
|
|
message_tub_clean_template: !input message_tub_clean
|
|
message_paused_template: !input message_paused
|
|
message_resumed_template: !input message_resumed
|
|
message_energy_report_template: !input message_energy_report
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Computed State Values
|
|
# ---------------------------------------------------------------------------
|
|
remaining: "{{ states(remaining_time_sensor) }}"
|
|
# Note: Using 'appliance_name' instead of 'device_name' to avoid conflict
|
|
# with Home Assistant's internal DeviceExtension.device_name function
|
|
appliance_name: "{{ input_device_name }}"
|
|
run_state: "{{ states(run_state_sensor) }}"
|
|
|
|
# Determine if the appliance is actively running a cycle
|
|
# Excludes: unknown states, non-running states, preparation, and completion
|
|
is_running: >
|
|
{{ run_state not in ['unknown', 'unavailable', 'waiting', '-']
|
|
and run_state not in non_running_state_ids
|
|
and run_state != preparation_state_id
|
|
and run_state != run_state_completion_id
|
|
and (remaining | string | trim) not in ['unknown', 'unavailable', 'none', '-', ''] }}
|
|
|
|
# Parse remaining time string (hh:mm:ss) to total minutes.
|
|
# Use int(0) defaults so a partially invalid sensor value (e.g.
|
|
# 'unavailable:00:00') degrades to 0 instead of raising.
|
|
remaining_time_in_minutes: >
|
|
{% set r = remaining | string | trim %}
|
|
{% if r not in ['unknown', 'unavailable', 'none', '-', ''] %}
|
|
{% set parts = r.split(':') %}
|
|
{% if parts | length >= 2 %}
|
|
{{ parts[0] | int(0) * 60 + parts[1] | int(0) }}
|
|
{% else %}
|
|
{{ 0 }}
|
|
{% endif %}
|
|
{% else %}
|
|
{{ 0 }}
|
|
{% endif %}
|
|
|
|
# Calculate estimated end time from remaining time
|
|
estimated_end_time: >
|
|
{% set r = remaining | string | trim %}
|
|
{% if r not in ['unknown', 'unavailable', 'none', '-', ''] %}
|
|
{% set parts = r.split(':') %}
|
|
{% if parts | length >= 2 %}
|
|
{% set hours = parts[0] | int(0) %}
|
|
{% set minutes = parts[1] | int(0) %}
|
|
{% set end_time = now() + timedelta(hours=hours, minutes=minutes) %}
|
|
{{ end_time.strftime('%H:%M') }}
|
|
{% else %}
|
|
{{ 'N/A' }}
|
|
{% endif %}
|
|
{% else %}
|
|
{{ 'N/A' }}
|
|
{% endif %}
|
|
|
|
# Check if device is paused
|
|
is_paused: >
|
|
{{ run_state in pause_state_ids }}
|
|
|
|
# Get current power consumption (power_sensor is a list, use first item if available)
|
|
current_power: >
|
|
{% if power_sensor | length > 0 %}
|
|
{{ states(power_sensor[0]) | float(0) }}
|
|
{% else %}
|
|
{{ 0 }}
|
|
{% endif %}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Persistent State Management
|
|
# ---------------------------------------------------------------------------
|
|
automation_state_entity: !input automation_state_entity
|
|
|
|
# Parse global state JSON from input_text entity
|
|
automation_state_global: >
|
|
{% set text = states(automation_state_entity) | string %}
|
|
{% if text in ['unknown','unavailable','none',''] %}
|
|
{{ dict() }}
|
|
{% else %}
|
|
{{ text | from_json }}
|
|
{% endif %}
|
|
|
|
automation_state_placeholder_key: !input automation_state_placeholder_key
|
|
|
|
# Determine the key for this automation's state
|
|
automation_state_key: >
|
|
{% if automation_state_placeholder_key != '' %}
|
|
{{ automation_state_placeholder_key }}
|
|
{% else %}
|
|
{{ remaining_time_sensor }}
|
|
{% endif %}
|
|
|
|
# Get this automation's state from global state
|
|
automation_state: "{{ automation_state_global.get(automation_state_key, dict()) }}"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Debug Flag (now controlled by input)
|
|
# ---------------------------------------------------------------------------
|
|
# enable_debug_notifications is loaded from input above
|
|
|
|
# =============================================================================
|
|
# ACTIONS
|
|
# =============================================================================
|
|
action:
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DEBUG: Log current state (enabled via Debug input section)
|
|
# ---------------------------------------------------------------------------
|
|
- choose:
|
|
- conditions:
|
|
- condition: template
|
|
value_template: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "Debug - {appliance_name}"
|
|
message: >
|
|
Trigger: {{ trigger.entity_id }}
|
|
Run State: {{ run_state }}
|
|
Is Running: {{ is_running }}
|
|
Is Paused: {{ is_paused }}
|
|
Remaining: {remaining}
|
|
Remaining Minutes: {{ remaining_time_in_minutes }}
|
|
Power: {{ current_power }} W
|
|
Start Sent: {{ automation_state.get(state_notification_about_start_sent, false) }}
|
|
Time-to-End Sent: {{ automation_state.get(state_notification_about_remaining_time_sent, false) }}
|
|
Pause Sent: {{ automation_state.get(state_notification_about_pause_sent, false) }}
|
|
|
|
# ===========================================================================
|
|
# MAIN STATE MACHINE - Handle different cycle events
|
|
# ===========================================================================
|
|
- choose:
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 1: Cycle Started
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Device starts running and start notification not yet sent
|
|
# Action: Send start notification with duration and optional details
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ not automation_state.get(state_notification_about_start_sent, false)
|
|
and is_running }}
|
|
sequence:
|
|
- variables:
|
|
startup_info_sensors: !input startup_info_sensors
|
|
startup_info_texts: !input startup_info_texts
|
|
# Build sensor details string for the `{details}` placeholder
|
|
details: >
|
|
{% set ns = namespace(text = '') %}
|
|
{% for i in range(startup_info_sensors | count) %}
|
|
{% set ns.text = ns.text ~ ' ' ~ startup_info_texts[i] ~ ': [' ~ states(startup_info_sensors[i]) ~ '].' %}
|
|
{% endfor %}
|
|
{{ ns.text }}
|
|
# Get estimated end time string
|
|
est_end: "{{ estimated_end_time if show_estimated_end_time else '' }}"
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_start_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{remaining}', remaining)
|
|
| replace('{estimated_end}', est_end)
|
|
| replace('{details}', details) }}
|
|
|
|
# Send start notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# Mark start notification as sent and initialize energy tracking
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_notification_about_start_sent: true,
|
|
state_cycle_start_time: now().isoformat(),
|
|
state_energy_samples: 0,
|
|
state_last_sample_time: now().isoformat(),
|
|
state_cycle_completion_time: '',
|
|
state_unload_reminder_count: 0
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
# Debug notification for start
|
|
- choose:
|
|
- conditions: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "{appliance_name} - START"
|
|
message: >
|
|
Action: CYCLE STARTED
|
|
Time: {{ now().strftime('%H:%M:%S') }}
|
|
Duration: {remaining}
|
|
Estimated End: {{ estimated_end_time }}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 2: Cycle Completed
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Device stops running after cycle was started
|
|
# Action: Send completion notification and reset all state flags
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ not is_running
|
|
and automation_state.get(state_notification_about_start_sent, false)
|
|
and (states(run_state_sensor) in [run_state_completion_id, 'unknown', 'unavailable', '-'] or states(remaining_time_sensor) in ['unknown', 'unavailable', '-']) }}
|
|
sequence:
|
|
- variables:
|
|
# Calculate energy consumption
|
|
energy_wh: "{{ automation_state.get(state_energy_samples, 0) | float }}"
|
|
energy_kwh: "{{ (energy_wh / 1000) | round(3) }}"
|
|
energy_cost: >
|
|
{% if energy_cost_per_kwh > 0 %}
|
|
{{ (energy_kwh * energy_cost_per_kwh) | round(2) }}
|
|
{% else %}
|
|
{{ 0 }}
|
|
{% endif %}
|
|
# Build energy report string (power_sensor is a list)
|
|
energy_report: >
|
|
{% if power_sensor | length > 0 and energy_kwh > 0 %}
|
|
{% set tpl = message_energy_report_template %}
|
|
{{ tpl | replace('{energy_kwh}', energy_kwh | string)
|
|
| replace('{energy_cost}', energy_cost | string) }}
|
|
{% else %}
|
|
{{ '' }}
|
|
{% endif %}
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_completed_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
}}{{ energy_report }}
|
|
|
|
# Send completion notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# Reset all notification flags for next cycle
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set any_door_open = (unload_reminder_door_sensors | select('is_state', 'on') | list | length) > 0 %}
|
|
{% set arm_reminder = (unload_reminder_delay | int(0)) > 0 and not any_door_open %}
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_notification_about_remaining_time_sent: false,
|
|
state_notification_about_start_sent: false,
|
|
state_notification_about_preparation_sent: false,
|
|
state_notification_about_pause_sent: false,
|
|
state_was_paused: false,
|
|
state_energy_samples: 0,
|
|
state_cycle_completion_time: (now().isoformat() if arm_reminder else ''),
|
|
state_unload_reminder_count: 0
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
# Debug notification for completion
|
|
- choose:
|
|
- conditions: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "{appliance_name} - COMPLETE"
|
|
message: >
|
|
Action: CYCLE COMPLETED
|
|
Time: {{ now().strftime('%H:%M:%S') }}
|
|
Energy Used: {energy_kwh} kWh
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 3: Preparation Mode Started
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Device enters preparation state (e.g., dryer heating)
|
|
# Action: Send preparation notification
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ preparation_state_id != ''
|
|
and states(run_state_sensor) == preparation_state_id
|
|
and not automation_state.get(state_notification_about_preparation_sent, false) }}
|
|
sequence:
|
|
# Mark preparation notification as sent (before sending to prevent duplicates)
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({ state_notification_about_preparation_sent: true })) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
- variables:
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_preparation_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
}}
|
|
|
|
# Send preparation notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 4: Error Occurred
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Error message sensor changes to non-empty value
|
|
# Action: Send error notification with details
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ trigger.entity_id == error_message_sensor
|
|
and states(error_message_sensor) not in ['', 'unknown', 'unavailable', 'none', '-']
|
|
and states(error_message_sensor) | length > 0 }}
|
|
sequence:
|
|
- variables:
|
|
error: "{{ states(error_message_sensor) }}"
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_error_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{error}', error) }}
|
|
|
|
# Send error notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 5: Almost Done (Time-to-End Notification)
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Remaining time drops below threshold
|
|
# Action: Send "almost done" notification
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ not automation_state.get(state_notification_about_remaining_time_sent, false)
|
|
and is_running
|
|
and remaining_time_in_minutes <= notify_time_to_end
|
|
and remaining_time_in_minutes > 0 }}
|
|
sequence:
|
|
# Mark time-to-end notification as sent
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({ state_notification_about_remaining_time_sent: true })) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
- variables:
|
|
# Calculate minutes for the template
|
|
minutes: "{{ remaining.split(':')[1] | int(0) if ':' in (remaining | string) else 0 }}"
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_almost_done_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{minutes}', minutes | string) }}
|
|
|
|
# Send "almost done" notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 6: Tub Cleaning Reminder
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Tub clean counter exceeds threshold
|
|
# Action: Send tub cleaning reminder
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ trigger.entity_id == tub_clean_counter_sensor
|
|
and tub_clean_counter_threshold != 0
|
|
and (states(tub_clean_counter_sensor) | int(0)) > tub_clean_counter_threshold }}
|
|
sequence:
|
|
- variables:
|
|
tub_count: "{{ states(tub_clean_counter_sensor) | int }}"
|
|
tub_threshold: "{{ tub_clean_counter_threshold }}"
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_tub_clean_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{tub_count}', tub_count | string)
|
|
| replace('{tub_threshold}', tub_threshold | string) }}
|
|
|
|
# Send tub cleaning reminder
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 7: Cycle Paused
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Device enters paused state during a cycle
|
|
# Action: Send pause notification
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ pause_state_ids | length > 0
|
|
and is_paused
|
|
and automation_state.get(state_notification_about_start_sent, false)
|
|
and not automation_state.get(state_notification_about_pause_sent, false) }}
|
|
sequence:
|
|
# Mark pause notification as sent
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_notification_about_pause_sent: true,
|
|
state_was_paused: true
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
- variables:
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_paused_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{remaining}', remaining) }}
|
|
|
|
# Send pause notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# Debug notification for pause
|
|
- choose:
|
|
- conditions: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "{appliance_name} - PAUSED"
|
|
message: >
|
|
Action: CYCLE PAUSED
|
|
Time: {{ now().strftime('%H:%M:%S') }}
|
|
Remaining: {remaining}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 8: Cycle Resumed
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Device resumes after being paused
|
|
# Action: Send resume notification
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ is_running
|
|
and automation_state.get(state_was_paused, false)
|
|
and automation_state.get(state_notification_about_pause_sent, false) }}
|
|
sequence:
|
|
# Reset pause notification flag (keep was_paused for tracking)
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_notification_about_pause_sent: false
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
- variables:
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_resumed_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{remaining}', remaining) }}
|
|
|
|
# Send resume notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# Debug notification for resume
|
|
- choose:
|
|
- conditions: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "{appliance_name} - RESUMED"
|
|
message: >
|
|
Action: CYCLE RESUMED
|
|
Time: {{ now().strftime('%H:%M:%S') }}
|
|
Remaining: {remaining}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 9: Power Sample (Accumulate Energy)
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Power sensor updates during running cycle
|
|
# Action: Accumulate energy consumption
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ trigger.id == 'power_update'
|
|
and power_sensor | length > 0
|
|
and automation_state.get(state_notification_about_start_sent, false)
|
|
and is_running }}
|
|
sequence:
|
|
- variables:
|
|
last_sample: "{{ automation_state.get(state_last_sample_time, now().isoformat()) }}"
|
|
time_diff_hours: >
|
|
{% set last = last_sample | as_datetime %}
|
|
{% if last is not none %}
|
|
{{ ((now() - last).total_seconds() / 3600) | float }}
|
|
{% else %}
|
|
{{ 0 }}
|
|
{% endif %}
|
|
energy_increment: "{{ (current_power * time_diff_hours) | round(4) }}"
|
|
accumulated_energy: "{{ (automation_state.get(state_energy_samples, 0) | float + energy_increment) | round(4) }}"
|
|
|
|
# Update accumulated energy
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_energy_samples: accumulated_energy,
|
|
state_last_sample_time: now().isoformat()
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 10: Unload Reminder
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: Cycle completion timestamp is recorded and the
|
|
# configured delay (plus any repeat intervals) has elapsed without a
|
|
# new cycle starting. CASE 1 clears the timestamp on a new cycle.
|
|
# Action: Remind the user to unload the appliance.
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{%- set cct_val = automation_state.get(state_cycle_completion_time, '') -%}
|
|
{%- set count_val = automation_state.get(state_unload_reminder_count, 0) | int(0) -%}
|
|
{{ (unload_reminder_delay | int(0)) > 0
|
|
and cct_val not in ['', 'unknown', 'unavailable', 'none']
|
|
and count_val < (unload_reminder_repeat_count | int(1))
|
|
and ((now() - (cct_val | as_datetime)).total_seconds() / 60)
|
|
>= ((unload_reminder_delay | int(0)) + count_val * (unload_reminder_repeat_interval | int(0))) }}
|
|
sequence:
|
|
- variables:
|
|
cct_val: "{{ automation_state.get(state_cycle_completion_time, '') }}"
|
|
count_val: "{{ automation_state.get(state_unload_reminder_count, 0) | int(0) }}"
|
|
new_reminder_count: "{{ (count_val | int(0)) + 1 }}"
|
|
elapsed_minutes: >
|
|
{{ ((now() - (cct_val | as_datetime)).total_seconds() / 60) | round(0) | int }}
|
|
# Render the message template with available variables
|
|
message: >
|
|
{% set tpl = message_unload_reminder_template %}
|
|
{{ tpl | replace('{appliance_name}', appliance_name)
|
|
| replace('{minutes}', elapsed_minutes | string) }}
|
|
|
|
# Send unload reminder notification
|
|
- service: notify.send_message
|
|
target:
|
|
entity_id: !input notify_target
|
|
data:
|
|
message: "{{ message }}"
|
|
|
|
# Increment reminder count; clear completion time when last reminder is sent
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_unload_reminder_count: new_reminder_count | int(0),
|
|
state_cycle_completion_time: ('' if (new_reminder_count | int(0)) >= (unload_reminder_repeat_count | int(1)) else cct_val)
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
# Debug notification for unload reminder
|
|
- choose:
|
|
- conditions: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "{appliance_name} - UNLOAD REMINDER"
|
|
message: >
|
|
Action: UNLOAD REMINDER
|
|
Time: {{ now().strftime('%H:%M:%S') }}
|
|
Reminder #: {{ new_reminder_count }} of {{ unload_reminder_repeat_count }}
|
|
Elapsed: {{ elapsed_minutes }} min
|
|
|
|
# -----------------------------------------------------------------------
|
|
# CASE 11: Door Opened After Completion (cancel unload reminder)
|
|
# -----------------------------------------------------------------------
|
|
# Triggered when: A configured door / lid sensor opens while a cycle
|
|
# completion timestamp is pending. Treated as "user has unloaded".
|
|
# Action: Clear cct and urc so no further reminders fire.
|
|
- conditions:
|
|
- condition: template
|
|
value_template: >
|
|
{{ trigger.id == 'door_opened'
|
|
and automation_state.get(state_cycle_completion_time, '') not in ['', 'unknown', 'unavailable', 'none'] }}
|
|
sequence:
|
|
- service: input_text.set_value
|
|
target:
|
|
entity_id: "{{ automation_state_entity }}"
|
|
data:
|
|
value: >
|
|
{% set new_automation_state = (automation_state | combine({
|
|
state_cycle_completion_time: '',
|
|
state_unload_reminder_count: 0
|
|
})) %}
|
|
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
|
|
|
|
# Debug notification for door-cancelled reminder
|
|
- choose:
|
|
- conditions: "{{ enable_debug_notifications }}"
|
|
sequence:
|
|
- service: persistent_notification.create
|
|
data:
|
|
title: "{appliance_name} - UNLOAD DETECTED"
|
|
message: >
|
|
Action: UNLOAD REMINDER CANCELLED
|
|
Time: {{ now().strftime('%H:%M:%S') }}
|
|
Door: {{ trigger.entity_id }}
|