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 - Start notification with cycle duration, estimated end time, and mode details
- Completion notification (reminder to unload clothes) with energy report - Completion notification (reminder to unload clothes) with energy report
- "Almost done" notification (configurable minutes before end) - "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) - 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)
@@ -39,6 +40,8 @@ The automation tracks the appliance through these states:
| `cst` | Cycle Start Time (ISO timestamp) | | `cst` | Cycle Start Time (ISO timestamp) |
| `esmp` | Energy Samples accumulator (Wh) | | `esmp` | Energy Samples accumulator (Wh) |
| `lst` | Last Sample Time (ISO timestamp) | | `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 ## 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") | | `{appliance_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") |
| `{estimated_end}` | Estimated completion time (e.g., "14:30") | | `{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) | | `{error}` | Error message text (only in error notification) |
| `{tub_count}` | Tub clean counter value (only in tub clean notification) | | `{tub_count}` | Tub clean counter value (only in tub clean notification) |
| `{tub_threshold}` | Tub clean threshold (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 - Notification service entity
- (Optional) Power sensor for energy tracking - (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 ## Note
Default messages are in Russian for LG ThinQ integration. Customize messages in the "Messages" section for your language. 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: selector:
boolean: 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 # Persistent State Configuration
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@@ -143,6 +204,17 @@ blueprint:
text: text:
multiline: true 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: message_almost_done:
name: "Almost Done Message" name: "Almost Done Message"
description: > description: >
@@ -389,6 +461,18 @@ trigger:
entity_id: !input power_sensor entity_id: !input power_sensor
id: "power_update" 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 # CONDITIONS
# ============================================================================= # =============================================================================
@@ -412,6 +496,8 @@ variables:
state_cycle_start_time: 'cst' # Cycle start timestamp state_cycle_start_time: 'cst' # Cycle start timestamp
state_energy_samples: 'esmp' # Energy sample accumulator (Wh) state_energy_samples: 'esmp' # Energy sample accumulator (Wh)
state_last_sample_time: 'lst' # Last power sample timestamp 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 # Input Variables
@@ -431,6 +517,10 @@ variables:
show_estimated_end_time: !input show_estimated_end_time show_estimated_end_time: !input show_estimated_end_time
power_sensor: !input power_sensor power_sensor: !input power_sensor
energy_cost_per_kwh: !input energy_cost_per_kwh 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 enable_debug_notifications: !input enable_debug_notifications
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -438,6 +528,7 @@ variables:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
message_start_template: !input message_start message_start_template: !input message_start
message_completed_template: !input message_completed message_completed_template: !input message_completed
message_unload_reminder_template: !input message_unload_reminder
message_almost_done_template: !input message_almost_done message_almost_done_template: !input message_almost_done
message_preparation_template: !input message_preparation message_preparation_template: !input message_preparation
message_error_template: !input message_error message_error_template: !input message_error
@@ -619,7 +710,9 @@ action:
state_notification_about_start_sent: true, state_notification_about_start_sent: true,
state_cycle_start_time: now().isoformat(), state_cycle_start_time: now().isoformat(),
state_energy_samples: 0, 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 }} {{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
@@ -686,13 +779,17 @@ action:
entity_id: "{{ automation_state_entity }}" entity_id: "{{ automation_state_entity }}"
data: data:
value: > 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({ {% 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_notification_about_pause_sent: false,
state_was_paused: 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 }} {{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }}
@@ -973,3 +1070,100 @@ action:
state_last_sample_time: now().isoformat() 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 }}
# -----------------------------------------------------------------------
# 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 }}