Add advanced features to Washing Machine blueprint
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s

- Estimated end time in start notification (e.g., "Завершение: ~14:30")
- Pause/Resume notifications when cycle is paused or resumed
- Power consumption monitoring with energy tracking per cycle
- Energy report appended to completion notification
- Debug notifications toggle for troubleshooting
This commit is contained in:
2026-01-25 04:51:58 +03:00
parent f6679f73e3
commit affa7cd0f9

View File

@@ -5,40 +5,53 @@
# notifications for various events throughout the wash/dry cycle. # notifications for various events throughout the wash/dry cycle.
# #
# Features: # Features:
# - Start notification with cycle duration and mode details # - Start notification with cycle duration, estimated end time, and mode details
# - Completion notification (reminder to unload clothes) # - Completion notification (reminder to unload clothes) with energy report
# - "Almost done" notification (configurable minutes before end) # - "Almost done" notification (configurable minutes before end)
# - Pause/Resume notifications (detect when cycle is paused or resumed)
# - Error message notifications # - Error message notifications
# - Preparation mode notification (e.g., for dryer prep) # - Preparation mode notification (e.g., for dryer prep)
# - Tub/drum cleaning reminder based on wash counter # - Tub/drum cleaning reminder based on wash counter
# - Power consumption monitoring with energy tracking per cycle
# - Fully customizable notification messages (i18n support) # - Fully customizable notification messages (i18n support)
# - Debug notifications for troubleshooting
# #
# State Machine: # State Machine:
# The automation tracks the appliance through these states: # The automation tracks the appliance through these states:
# 1. IDLE: Waiting for cycle to start # 1. IDLE: Waiting for cycle to start
# 2. RUNNING: Cycle in progress (start notification sent) # 2. RUNNING: Cycle in progress (start notification sent)
# 3. FINISHING: Near completion (time-to-end notification sent) # 3. PAUSED: Cycle temporarily paused (pause notification sent)
# 4. COMPLETED: Cycle done (completion notification sent, state reset) # 4. FINISHING: Near completion (time-to-end notification sent)
# 5. COMPLETED: Cycle done (completion notification sent, state reset)
# #
# Persistent State Keys: # Persistent State Keys:
# - nass: Notification About Start Sent # - nass: Notification About Start Sent
# - nart: Notification About Remaining Time Sent # - nart: Notification About Remaining Time Sent
# - naps: Notification About Preparation Sent # - naps: Notification About Preparation Sent
# - napas: Notification About Pause Sent
# - wasp: Was Paused flag
# - cst: Cycle Start Time (ISO timestamp)
# - esmp: Energy Samples accumulator (Wh)
# - lst: Last Sample Time (ISO timestamp)
# #
# Message Template Variables: # Message Template Variables:
# All message templates support Jinja2 templating with these variables: # All message templates support Jinja2 templating with these variables:
# - {{ device_name }} - Device name (e.g., "Washing Machine") # - {{ device_name }} - Device name (e.g., "Washing Machine")
# - {{ remaining }} - Remaining time as string (e.g., "01:30:00") # - {{ remaining }} - Remaining time as string (e.g., "01:30:00")
# - {{ minutes }} - Remaining minutes as number (e.g., 90) # - {{ estimated_end }} - Estimated completion time (e.g., "14:30")
# - {{ error }} - Error message text (only in error notification) # - {{ minutes }} - Remaining minutes as number (e.g., 90)
# - {{ tub_count }} - Tub clean counter value (only in tub clean notification) # - {{ error }} - Error message text (only in error notification)
# - {{ tub_threshold }}- Tub clean threshold (only in tub clean notification) # - {{ tub_count }} - Tub clean counter value (only in tub clean notification)
# - {{ details }} - Startup details from sensors (only in start notification) # - {{ tub_threshold }} - Tub clean threshold (only in tub clean notification)
# - {{ details }} - Startup details from sensors (only in start notification)
# - {{ energy_kwh }} - Energy consumed in kWh (only in completion notification)
# - {{ energy_cost }} - Estimated cost (only in completion notification)
# #
# Requirements: # Requirements:
# - Sensors for: remaining time, run state, error messages # - Sensors for: remaining time, run state, error messages
# - input_text entity for persistent state storage # - input_text entity for persistent state storage
# - Notification service entity # - Notification service entity
# - (Optional) Power sensor for energy tracking
# #
# Note: Default messages are in Russian for LG ThinQ integration. # Note: Default messages are in Russian for LG ThinQ integration.
# Customize messages in the "Messages" section for your language. # Customize messages in the "Messages" section for your language.
@@ -100,6 +113,13 @@ blueprint:
unit_of_measurement: minutes unit_of_measurement: minutes
mode: slider mode: slider
show_estimated_end_time:
name: Show Estimated End Time
description: Include estimated completion time in start notification
default: true
selector:
boolean:
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Persistent State Configuration # Persistent State Configuration
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -162,8 +182,8 @@ blueprint:
name: "Start Message" name: "Start Message"
description: > description: >
Message sent when cycle starts. Message sent when cycle starts.
Variables: {{ device_name }}, {{ remaining }}, {{ details }} Variables: {{ device_name }}, {{ remaining }}, {{ details }}, {{ estimated_end }}
default: "🧺 {{ device_name }}: старт. Длительность: `{{ remaining }}`.{{ details }}" default: "🧺 {{ device_name }}: старт. Длительность: `{{ remaining }}`. Завершение: ~{{ estimated_end }}.{{ details }}"
selector: selector:
text: text:
multiline: true multiline: true
@@ -218,6 +238,36 @@ blueprint:
text: text:
multiline: true multiline: true
message_paused:
name: "Paused Message"
description: >
Message sent when cycle is paused.
Variables: {{ device_name }}, {{ remaining }}
default: "⏸️ {{ device_name }}: пауза. Осталось: {{ remaining }}."
selector:
text:
multiline: true
message_resumed:
name: "Resumed Message"
description: >
Message sent when cycle resumes.
Variables: {{ device_name }}, {{ remaining }}
default: "▶️ {{ device_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 # Device Information Sensors
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -235,6 +285,16 @@ blueprint:
text: text:
multiple: true 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: preparation_state_id:
name: Preparation Mode State ID (optional) name: Preparation Mode State ID (optional)
description: > description: >
@@ -302,6 +362,53 @@ blueprint:
text: text:
multiple: true 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: null
selector:
entity:
domain: sensor
device_class: power
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 # AUTOMATION MODE
# ============================================================================= # =============================================================================
@@ -330,6 +437,11 @@ trigger:
- platform: state - platform: state
entity_id: !input tub_clean_counter_sensor entity_id: !input tub_clean_counter_sensor
# Power sensor updates (for energy tracking)
- platform: state
entity_id: !input power_sensor
id: "power_update"
# ============================================================================= # =============================================================================
# CONDITIONS # CONDITIONS
# ============================================================================= # =============================================================================
@@ -348,12 +460,18 @@ variables:
state_notification_about_remaining_time_sent: 'nart' # "Almost done" notification sent state_notification_about_remaining_time_sent: 'nart' # "Almost done" notification sent
state_notification_about_start_sent: 'nass' # Start notification sent state_notification_about_start_sent: 'nass' # Start notification sent
state_notification_about_preparation_sent: 'naps' # Preparation 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
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Input Variables # Input Variables
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
run_state_sensor: !input run_state_sensor run_state_sensor: !input run_state_sensor
non_running_state_ids: !input non_running_state_ids non_running_state_ids: !input non_running_state_ids
pause_state_ids: !input pause_state_ids
preparation_state_id: !input preparation_state_id preparation_state_id: !input preparation_state_id
error_message_sensor: !input error_message_sensor error_message_sensor: !input error_message_sensor
remaining_time_sensor: !input remaining_time_sensor remaining_time_sensor: !input remaining_time_sensor
@@ -363,6 +481,10 @@ variables:
tub_clean_counter_sensor: !input tub_clean_counter_sensor tub_clean_counter_sensor: !input tub_clean_counter_sensor
tub_clean_counter_threshold: !input tub_clean_counter_threshold tub_clean_counter_threshold: !input tub_clean_counter_threshold
run_state_completion_id: !input run_state_completion_id 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
enable_debug_notifications: !input enable_debug_notifications
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Message Templates # Message Templates
@@ -373,6 +495,9 @@ variables:
message_preparation_template: !input message_preparation message_preparation_template: !input message_preparation
message_error_template: !input message_error message_error_template: !input message_error
message_tub_clean_template: !input message_tub_clean 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 # Computed State Values
@@ -404,6 +529,34 @@ variables:
{{ 0 }} {{ 0 }}
{% endif %} {% endif %}
# Calculate estimated end time from remaining time
estimated_end_time: >
{% if remaining not in ['unknown', 'unavailable', '-'] %}
{% set parts = remaining.split(':') %}
{% if parts | length >= 2 %}
{% set hours = parts[0] | int %}
{% set minutes = parts[1] | int %}
{% 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
current_power: >
{% if power_sensor is not none %}
{{ states(power_sensor) | float(0) }}
{% else %}
{{ 0 }}
{% endif %}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Persistent State Management # Persistent State Management
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -432,9 +585,9 @@ variables:
automation_state: "{{ automation_state_global.get(automation_state_key, dict()) }}" automation_state: "{{ automation_state_global.get(automation_state_key, dict()) }}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Debug Flag # Debug Flag (now controlled by input)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
is_debug: false # enable_debug_notifications is loaded from input above
# ============================================================================= # =============================================================================
# ACTIONS # ACTIONS
@@ -442,22 +595,27 @@ variables:
action: action:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# DEBUG: Log current state (enable by setting is_debug: true) # DEBUG: Log current state (enabled via Debug input section)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ is_debug }}" value_template: "{{ enable_debug_notifications }}"
sequence: sequence:
- service: persistent_notification.create - service: persistent_notification.create
data: data:
title: "Debug Info - Washing Machine" title: "Debug - {{ device_name }}"
message: > message: >
run_state = {{ run_state }}, Trigger: {{ trigger.entity_id }}
non_running_state_ids = {{ non_running_state_ids }}, Run State: {{ run_state }}
remaining_time = {{ states(remaining_time_sensor) }}, Is Running: {{ is_running }}
is_running = {{ is_running }}, Is Paused: {{ is_paused }}
start_notification_sent = {{ automation_state.get(state_notification_about_start_sent, false) }} 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 # MAIN STATE MACHINE - Handle different cycle events
@@ -485,11 +643,14 @@ action:
{% set ns.text = ns.text ~ ' ' ~ startup_info_texts[i] ~ ': [' ~ states(startup_info_sensors[i]) ~ '].' %} {% set ns.text = ns.text ~ ' ' ~ startup_info_texts[i] ~ ': [' ~ states(startup_info_sensors[i]) ~ '].' %}
{% endfor %} {% endfor %}
{{ ns.text }} {{ 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 # Render the message template with available variables
message: > message: >
{% set tpl = message_start_template %} {% set tpl = message_start_template %}
{{ tpl | replace('{{ device_name }}', device_name) {{ tpl | replace('{{ device_name }}', device_name)
| replace('{{ remaining }}', remaining) | replace('{{ remaining }}', remaining)
| replace('{{ estimated_end }}', est_end)
| replace('{{ details }}', details) }} | replace('{{ details }}', details) }}
# Send start notification # Send start notification
@@ -499,15 +660,33 @@ action:
data: data:
message: "{{ message }}" message: "{{ message }}"
# Mark start notification as sent # Mark start notification as sent and initialize energy tracking
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: "{{ automation_state_entity }}" entity_id: "{{ automation_state_entity }}"
data: data:
value: > value: >
{% set new_automation_state = (automation_state | combine({ state_notification_about_start_sent: true })) %} {% 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()
})) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} {{ 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: "{{ device_name }} - START"
message: >
Action: CYCLE STARTED
Time: {{ now().strftime('%H:%M:%S') }}
Duration: {{ remaining }}
Estimated End: {{ estimated_end_time }}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# CASE 2: Cycle Completed # CASE 2: Cycle Completed
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -521,10 +700,28 @@ action:
and (states(run_state_sensor) in [run_state_completion_id, 'unknown', '-'] or states(remaining_time_sensor) in ['unknown', '-']) }} and (states(run_state_sensor) in [run_state_completion_id, 'unknown', '-'] or states(remaining_time_sensor) in ['unknown', '-']) }}
sequence: sequence:
- variables: - 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
energy_report: >
{% if power_sensor is not none 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 # Render the message template with available variables
message: > message: >
{% set tpl = message_completed_template %} {% set tpl = message_completed_template %}
{{ tpl | replace('{{ device_name }}', device_name) }} {{ tpl | replace('{{ device_name }}', device_name) }}{{ energy_report }}
# Send completion notification # Send completion notification
- service: notify.send_message - service: notify.send_message
@@ -542,10 +739,25 @@ action:
{% set new_automation_state = (automation_state | combine({ {% set new_automation_state = (automation_state | combine({
state_notification_about_remaining_time_sent: false, state_notification_about_remaining_time_sent: false,
state_notification_about_start_sent: false, state_notification_about_start_sent: false,
state_notification_about_preparation_sent: false state_notification_about_preparation_sent: false,
state_notification_about_pause_sent: false,
state_was_paused: false,
state_energy_samples: 0
})) %} })) %}
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} {{ 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: "{{ device_name }} - COMPLETE"
message: >
Action: CYCLE COMPLETED
Time: {{ now().strftime('%H:%M:%S') }}
Energy Used: {{ energy_kwh }} kWh
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# CASE 3: Preparation Mode Started # CASE 3: Preparation Mode Started
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -673,3 +885,138 @@ action:
entity_id: !input notify_target entity_id: !input notify_target
data: data:
message: "{{ message }}" 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:
message: >
{% set tpl = message_paused_template %}
{{ tpl | replace('{{ device_name }}', device_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: "{{ device_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:
message: >
{% set tpl = message_resumed_template %}
{{ tpl | replace('{{ device_name }}', device_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: "{{ device_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 is not none
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 }}