feat: add unload reminder to Washing Machine

- Send a reminder N minutes after cycle completion, with optional repeats
- Auto-cancel reminders when a new cycle starts or a door/lid sensor opens
- Gate the completion timestamp on configured door sensors being closed
- Track completion time (cct) and reminder count (urc) in persistent state
This commit is contained in:
2026-05-27 13:09:55 +03:00
parent 34cf5b1f7a
commit ad6f30ce3c
2 changed files with 212 additions and 3 deletions
+16 -1
View File
@@ -7,6 +7,7 @@ This blueprint monitors washing machine or dryer appliances and sends notificati
- Start notification with cycle duration, estimated end time, and mode details
- Completion notification (reminder to unload clothes) with energy report
- "Almost done" notification (configurable minutes before end)
- Unload reminder (configurable delay, optional repeats) — auto-cancelled when a new cycle starts or a configured door/lid sensor opens
- Pause/Resume notifications (detect when cycle is paused or resumed)
- Error message notifications
- Preparation mode notification (e.g., for dryer prep)
@@ -39,6 +40,8 @@ The automation tracks the appliance through these states:
| `cst` | Cycle Start Time (ISO timestamp) |
| `esmp` | Energy Samples accumulator (Wh) |
| `lst` | Last Sample Time (ISO timestamp) |
| `cct` | Cycle Completion Time (ISO timestamp, drives unload reminder) |
| `urc` | Unload Reminder Count (number of reminders already sent) |
## Message Template Variables
@@ -49,7 +52,7 @@ All message templates support these placeholder variables (use single braces):
| `{appliance_name}` | Device name (e.g., "Washing Machine") |
| `{remaining}` | Remaining time as string (e.g., "01:30:00") |
| `{estimated_end}` | Estimated completion time (e.g., "14:30") |
| `{minutes}` | Remaining minutes as number (e.g., 90) |
| `{minutes}` | Remaining minutes (almost-done) or elapsed minutes since completion (unload reminder) |
| `{error}` | Error message text (only in error notification) |
| `{tub_count}` | Tub clean counter value (only in tub clean notification) |
| `{tub_threshold}` | Tub clean threshold (only in tub clean notification) |
@@ -64,6 +67,18 @@ All message templates support these placeholder variables (use single braces):
- Notification service entity
- (Optional) Power sensor for energy tracking
## Unload Reminder
If `Unload Reminder Delay` is greater than 0, the automation:
1. Records the completion timestamp (`cct`) when the cycle finishes — but **only if** every door sensor in `Unload Reminder Door Sensors` is currently closed (or the list is empty).
2. Sends a reminder N minutes later (where N = delay), then optionally repeats every `Unload Reminder Repeat Interval` minutes up to `Unload Reminder Repeat Count` total.
3. Cancels all pending reminders when:
- A new cycle starts (start handler clears `cct` and `urc`), **or**
- Any of the configured door / lid sensors transitions to `on` (treated as "user has unloaded").
The check runs once per minute via a `time_pattern` trigger. The door sensor list is optional — leave it empty to keep reminders purely time-based.
## Note
Default messages are in Russian for LG ThinQ integration. Customize messages in the "Messages" section for your language.
+196 -2
View File
@@ -65,6 +65,67 @@ blueprint:
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
# -------------------------------------------------------------------------
@@ -143,6 +204,17 @@ blueprint:
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: >
@@ -389,6 +461,18 @@ trigger:
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
# =============================================================================
@@ -412,6 +496,8 @@ variables:
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
@@ -431,6 +517,10 @@ variables:
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
# ---------------------------------------------------------------------------
@@ -438,6 +528,7 @@ variables:
# ---------------------------------------------------------------------------
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
@@ -619,7 +710,9 @@ action:
state_notification_about_start_sent: true,
state_cycle_start_time: now().isoformat(),
state_energy_samples: 0,
state_last_sample_time: now().isoformat()
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 }}
@@ -686,13 +779,17 @@ action:
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_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 }}
@@ -973,3 +1070,100 @@ action:
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 }}