-
-
Notifications
You must be signed in to change notification settings - Fork 41
Notifications and Events
WashData can tell you about a cycle in two ways. You can use either, or both. This guide explains what each does, how the notifications behave over the life of a cycle, and how to build your own automations on top.
TL;DR
- Want a ready-made push to your phone? Fill in the per-event notification targets.
- Want full control (custom sound/channel, conditions, TTS, only certain events, lights, ...)? Build a normal Home Assistant automation triggered by WashData's cycle events. The panel's Notifications → Automations section finds and creates them for you. (This replaces the old built-in "custom actions" editor.)
All of these are configured in the WashData panel (sidebar) under Settings -> Notifications. The Home Assistant Configure dialog now only holds the core setup fields (device type, power sensor, minimum power); everything else - including notifications - lives in the panel.
For every notification it produces, WashData runs the per-event notification targets
(below), and independently emits bus events (notify_fire_events, default on) that
your own automations can trigger on. Use whichever suits you; they are independent.
Three separate target lists, one per kind of notification:
| Option | Sends on |
|---|---|
Cycle Start - Notification Targets (notify_start_services) |
cycle start |
Cycle Finish - Notification Targets (notify_finish_services) |
cycle finished, the pre-end reminder, and the laundry-waiting nag |
Live Progress - Notification Targets (notify_live_services) |
recurring live progress updates (mobile companion app only) |
Key behaviours:
- Leave a list empty to skip that kind entirely. This is how you send "only when finished": put a target in the finish list and leave start and live empty.
- The finish list also receives the pre-end reminder and the laundry-waiting nag, because those are "your cycle is (almost) done" messages.
- The live list is mobile-only: non-
mobile_apptargets are skipped for live updates, because progress bars and countdown timers are a companion-app feature. - Each list accepts both notify services (e.g.
notify.mobile_app_pixel) and notify entities (e.g. those exposed bytelegram_bot). Notify entities are delivered through the universalnotify.send_messageaction automatically.
An empty list therefore disables that kind of notification entirely: WashData does not dispatch it and does not fall back to a persistent notification for it. (The sidebar bell is used only for interactive/system prompts such as the pause-timer control, not as a fallback for an empty per-event list.)
For anything beyond a plain push - a custom sound or Android channel, TTS, conditions,
only certain events, driving lights, and so on - build a normal Home Assistant
automation triggered by the events WashData fires on the bus. This does everything
the old built-in "custom actions" editor did, natively, so you get the full automation
editor, conditions, and templating. Keep Fire Automation Events (notify_fire_events,
default on) enabled so the events are emitted.
Find and create them from the panel. Open WashData → Settings → Notifications → Automations. It lists the automations that already reference this device (each deep-links to the automation editor) and gives you a New Automation button: a blank one, or one prefilled with a cycle started or cycle finished trigger for this device.
How to configure them - using WashData's variables. Inside an automation you read
WashData's values from the trigger's event data. WashData fires
ha_washdata_cycle_started and ha_washdata_cycle_ended (see
Events reference for the full payloads). Example - notify with the
program, duration and cost when a cycle finishes:
automation:
- alias: "Notify when the washer finishes"
trigger:
- platform: event
event_type: ha_washdata_cycle_ended
# Optional: pin to one device. Omit event_data to match any WashData device.
event_data:
entry_id: <your device's entry_id>
action:
- service: notify.mobile_app_pixel
data:
title: "{{ trigger.event.data.device_name }} finished"
message: >-
{{ trigger.event.data.program }} took
{{ (trigger.event.data.duration / 60) | round(0) }} min, used
{{ trigger.event.data.cycle_data.energy_wh | round(0) }} Wh
({{ trigger.event.data.cycle_data.cost }} in energy).
data:
channel: Laundry DoneThe variables you can template against come from trigger.event.data:
| In an automation | Meaning |
|---|---|
{{ trigger.event.data.device_name }} |
The device title you configured |
{{ trigger.event.data.program }} |
Matched profile name ("unknown" if none matched) |
{{ trigger.event.data.duration }} |
Cycle length in seconds (divide by 60 for minutes) |
{{ trigger.event.data.start_time }} / .end_time
|
ISO timestamps (ended event) |
{{ trigger.event.data.cycle_data.energy_wh }} |
Energy used, watt-hours (ended event) |
{{ trigger.event.data.cycle_data.cost }} |
Energy cost, your HA currency (ended event, if a price is set) |
{{ trigger.event.data.cycle_data.max_power }} / .status
|
Peak watts / completed|interrupted|force_stopped
|
These
{{ trigger.event.data.* }}templates are for automations. The short{device}/{duration}message placeholders below are a different mechanism - they only substitute inside the built-in message templates in the Notifications settings, not inside automations.
Migrating from the old custom actions. If you configured actions in a previous version, they keep firing, and the Automations section shows a legacy custom actions notice with a one-click Convert to automation (it creates an automation prefilled with both cycle triggers plus your original action steps) and a Remove button. Any
{device}/{duration}-style placeholders inside those actions are not templated in an automation - after converting, replace them with the{{ trigger.event.data.* }}forms shown above.
When you configure push targets, the start / live / reminder / finished notifications
form a single thread: they share one per-device tag, so each new one replaces
the previous on your phone instead of stacking up.
Cycle start -> Live progress (recurring, replaces start)
-> Reminder (once, X min before end, replaces live, makes a sound)
-> Finished (replaces live in place)
- The finished notification replaces the live progress card in place.
- The laundry-waiting nag ("still inside") is a separate card with its own tag, because it can fire up to an hour after the finished alert.
- With no push target configured, the whole thread collapses into a single persistent-notification card (it no longer accumulates one card per cycle).
| Option | Key | What it does |
|---|---|---|
| Delay Until People Home | notify_people |
People entities used for presence gating. |
| Delay Until Someone Home | notify_only_when_home |
Hold notifications until a listed person is home; the latest live update is kept and the rest coalesced. |
| Fire Automation Events | notify_fire_events |
Emit bus events for automations (default on). |
| Cycle Start Targets | notify_start_services |
Where to send the start notification. |
| Cycle Finish Targets | notify_finish_services |
Where to send finished + reminder + laundry nag. |
| Live Progress Targets | notify_live_services |
Mobile targets for recurring progress updates. |
| Pre-completion (minutes before end) | notify_before_end_minutes |
Minutes before the estimated end to fire the one-time reminder. 0 disables it. |
| Live Update Interval (seconds) | notify_live_interval_seconds |
How often live updates are sent (min 30s). |
| Live Update Overrun Allowance (%) | notify_live_overrun_percent |
Extra live updates allowed for cycles that run long. |
| Chronometer Countdown Timer | notify_live_chronometer |
Each live update carries a ticking countdown to the estimated finish (Android only). |
| Auto-dismiss After (seconds) | notify_timeout_seconds |
Auto-dismiss notifications after N seconds. 0 = never (default). |
| Notification Channel (status/live/reminder) | notify_channel |
Android channel for start/live/reminder. Empty = app default. |
| Finished Notification Channel | notify_finish_channel |
Distinct Android channel for finished + reminder + laundry nag. Empty = reuse the channel above. |
| Notification Title | notify_title |
Title template. |
| Notification Icon | notify_icon |
Icon (e.g. mdi:washing-machine). Empty = none. |
| Start / Finish / Live Update / Reminder Message | notify_*_message |
Message templates (see placeholders). |
| Energy Price - Entity / Static | energy_price_* |
Enables the {cost} placeholder in the finish message. |
| Quiet Hours Start / End (hour) |
notify_quiet_start_hour / notify_quiet_end_hour
|
Do-not-disturb window (0-23; unset or equal = off). Finish-type notifications are held and delivered when it ends. See Quiet hours. |
| Peak-Rate Threshold / Message |
peak_rate_threshold / peak_rate_message
|
Append an advisory tip to the start notification when the current price is at or above the threshold. See Peak-rate tip on start. |
| Cycle Milestones / Message |
notify_milestones / notify_milestone_message
|
Celebrate lifetime cycle-count milestones. See Cycle milestones. |
A notification channel's sound and importance are set by the Android companion app the first time that channel name is used, and cannot be changed from the payload later. WashData only sets the channel name. So: create the channel by sending one notification to it, then adjust its sound/importance in the companion app's notification settings. The reminder is routed to the finished channel (while still sharing the lifecycle tag) so it stays audible even if your status channel is silenced.
| Placeholder | Available in |
|---|---|
{device} |
all messages and the title |
{program} |
start, live, reminder |
{minutes} |
live, reminder |
{duration} |
finish, laundry nag |
{energy_kwh}, {cost}
|
finish (cost needs an energy price configured) |
{time_finished} |
finish (the clock time, HH:MM, the cycle ended) |
{vs_typical} |
finish (e.g. 12% longer than usual vs the profile median; empty when unknown) |
{cycle_count} |
finish (the appliance's lifetime completed-cycle count) |
{price} |
the peak-rate start tip (see Peak-rate tip on start) |
Notification text is composed by WashData in Python, so any wording it generates (the default start/finish/timer message templates and the peak-rate start tip) follows the server/instance language set under Settings > System > General - not each individual user's profile language. This is a Home Assistant platform limitation: integrations are not told which user a notification is for, so there is no per-user language to translate into. (Config and options dialogs are translated per user, because the frontend resolves those.)
You have two ways to get notifications in the language you want:
-
Edit the templates.
notify_titleand thenotify_*_messageoptions are free text - write them in any language; the{device},{program},{minutes},{duration},{energy_kwh}, and{cost}placeholders work regardless of language. -
Use the automation events (recommended for multi-user / multi-language setups). The
bus events below carry language-neutral data (device name, program, durations,
timestamps), so you can build the notification text yourself in your automation - in
whatever language each recipient prefers - and send it with your own
notifyaction.
When sending to a mobile_app target, WashData forwards these keys in the
notification data (useful if you replicate the behaviour in your own action):
- Common:
tag,timeout,channel,priority - Live only:
progress,progress_max,live_update,alert_once,cycle_seconds,time_remaining_seconds,minutes_left,live_updates_sent,live_updates_cap, and (with the chronometer on)chronometer,when,countdown.
For mobile_app_* live targets only, WashData adds the fields an iOS Live Activity
needs so a companion-app widget can track the cycle on the lock screen:
-
subtitle- the matched program name. -
content_state- a dict{state, progress_pct, eta_timestamp, program, device}for the Live Activity to render. -
activity-start/endlifecycle markers so the Activity begins with the cycle and is dismissed when it finishes.
These keys are only attached to mobile_app_* targets; Android and other notify platforms
are unaffected (they never receive them, so strict-schema platforms are not broken).
Set a do-not-disturb window with Quiet Hours Start / End (notify_quiet_start_hour /
notify_quiet_end_hour), both hours 0-23. The feature is off when either is unset or
the two are equal, and windows that cross midnight are supported (e.g. start 22, end
7).
- Held inside the window: the finished, clean-laundry nag, pre-completion / reminder, and milestone notifications. They are queued and delivered the moment the window ends.
- Never delayed: live-progress ticks and the start notification, so a cycle you kick off at night still confirms it started.
notify_milestones is a list of lifetime completed-cycle counts (default [50, 100, 500, 1000]). When the appliance's lifetime count crosses one of them, a single celebration
notification fires using notify_milestone_message (default "{device} has completed
{cycle_count} cycles!"). It is a one-off per milestone, respects quiet hours, and adds no
new notification type or entity. Placeholders: {device}, {cycle_count}.
When an energy price is configured and peak_rate_threshold is set (and positive), a
cycle that starts while the current price is at or above the threshold gets a one-line
tip appended to the start notification, from peak_rate_message (default "Running at peak
rate ({price}/kWh)."). It is purely informational - WashData never schedules, delays, or
controls the appliance. Placeholders: {device}, {price}.
WashData fires these events on the Home Assistant event bus, suitable as automation
triggers (platform: event). All are gated by the Fire Automation Events toggle
(default on); disable it to suppress them globally without touching automations.
Fired immediately when a cycle is detected (state machine first leaves OFF). The
profile match has typically not run yet, so program is usually "detecting..." or
"unknown"; use ha_washdata_cycle_ended if you need the resolved profile name.
event_type: ha_washdata_cycle_started
data:
entry_id: 01KR1WGJYHJBTT5MEGS0VRXC4D # config-entry id, stable across restarts
device_name: Waschmaschine # the integration title you configured
device_type: washing_machine # washing_machine | dryer | washer_dryer | dishwasher | air_fryer | bread_maker | pump | generic | other
program: "detecting..." # may resolve to a profile name later in the cycle
start_time: "2026-05-09T07:43:08.626640+02:00"Fired once the cycle has fully terminated and been written to history. Carries the full
cycle record minus the raw power trace (power_data, debug_data, power_trace are
stripped to stay under Home Assistant's 32 KB event payload limit; fetch them via the
diagnostics download if you need the samples).
event_type: ha_washdata_cycle_ended
data:
entry_id: 01KR1WGJYHJBTT5MEGS0VRXC4D
device_name: WashingMachine
program: "40C / 1200rpm / cotton" # resolved profile name, or "unknown" if no match
duration: 13784.144562 # seconds (float)
start_time: "2026-05-09T07:43:08.626640+02:00"
end_time: "2026-05-09T11:32:52.771202+02:00"
cycle_data:
id: db87b5b60b2e # 12-char hex, stable cycle identifier
start_time: "2026-05-09T07:43:08.626640+02:00"
end_time: "2026-05-09T11:32:52.771202+02:00"
duration: 13784.144562 # seconds (float)
max_power: 2063 # watts (peak observed in the cycle)
energy_wh: 1564.04 # integrated energy over the cycle
cost: 0.42 # energy cost frozen at completion (your HA currency); absent if no price is configured
energy_price: 0.27 # price per kWh used to compute `cost` (absent if no price is configured)
status: completed # completed | interrupted | force_stopped
termination_reason: timeout # timeout | smart | force_stopped | user | terminal_drop
profile_name: null # null when no profile was matched
sampling_interval: 43 # mean seconds between power readings
device_type: washing_machine
signature: # feature vector used for matching
duration: 13784.1
total_energy: 1564.04
max_power: 2063
event_density: 0
time_to_first_high: 1416.1
high_phase_ratio: 0.149
p05: 17 # 5th-percentile power (W)
p25: 38
p50: 91
p75: 203
p95: 2024.8(Pump device type only.) Fired once when an active pump cycle exceeds the configured stuck-pump duration. Useful for sump-pit, condensate, or borehole-pump alarms.
event_type: ha_washdata_pump_stuck
data:
entry_id: 01KR1WGJYHJBTT5MEGS0VRXC4D
device: Sumppumpe # the integration title (note: legacy key name)
elapsed_seconds: 1830 # how long the pump had been running
threshold_seconds: 1800 # the configured stuck-pump thresholdWashData registers a Home Assistant conversation intent, HaWashdataStatus, so you can
ask your voice or text assistant about an appliance and get a live, plain-language answer
derived from the manager/sensor state:
- "Is my washer done?" → "still running, about 20 minutes left" / "finished 5 minutes ago" / "not running"
- "How long until the dryer finishes?" - an optional appliance name disambiguates when more than one device is configured.
The intent handler is registered automatically and works immediately from automations,
the intent_script integration, developer tools, and the Assist pipeline. What Home
Assistant does not allow a custom integration to do is inject trigger sentences into the
built-in conversation agent at runtime, so you teach Assist which phrases map to the intent
with a config-directory sentence pack. A ready-to-use English pack ships at
docs/custom_sentences/en/ha_washdata.yaml - copy it to
<config>/custom_sentences/en/ha_washdata.yaml and restart Home Assistant. The minimal
shape (one file per language) is:
language: en
intents:
HaWashdataStatus:
data:
- sentences:
- "is my {name} done"
- "is the {name} finished"
- "how long until the {name} finishes"
- "how long is left on the {name}"
- sentences:
- "is the laundry done"
- "how long until it finishes"
lists:
name:
wildcard: trueThe {name} slot is optional; without it the assistant answers for the single (or first)
configured device. Prefer templated responses? Declare the same intent_type
(HaWashdataStatus) via the intent_script integration instead.
Beyond the entity states listed in the README, a few attributes are handy when building dashboards and automations:
-
sensor.<name>_stateexposessub_state(finer-grained state),current_program_guess, andsamples_recorded; pump devices also exposepump_stuck. -
sensor.<name>_programexposes the matched profile's phase ranges/catalog when a real profile is matched (used by phase-aware cards). Once matched it also exposesreference_profile, the matched program's expected power-over-time shape:reference_profile: points: # up to ~50 samples, absolute seconds from cycle start - [0, 12.0] # [offset_s, watts] - [72, 2105.3] - [3600, 4.0] duration_s: 3600.0 # profile's typical total duration cycle_count: 12 # cycles behind the learned average
This is a forward-looking load shape for energy-management automations (e.g. peak shaving): slice
pointsfrom the live position - which you already know fromsensor.<name>_cycle_progress(or elapsed vs.duration_s) - to see the expected remaining draw, such as a heating spike still to come, rather than only a scalar ETA. It appears only while a real profile is matched - it is absent while the state sensor still readsdetecting…or the cycle is unmatched, so an automation can distinguish "no forecast yet" from "forecast available." The curve is a live signal and is intentionally not stored in the recorder (it has no historical value); read it from the current state, not from history.
For the full cycle record (energy, peak power, termination reason, feature signature),
trigger on ha_washdata_cycle_ended and read cycle_data as shown above.
User Docs
Reference
Development
Repository