0.5.0 - Goodbye OptionsFlow, Hello Panel (Oh, and hi WashData Community Store)
0.5.0 - "Goodbye OptionsFlow, Hello Panel (Oh, and hi WashData Community Store)" - 2026-07-16
The biggest release in WashData's history. It retires the 180-plus-tunable options dialog in favour of a full-screen management panel, overhauls matching accuracy, rebuilds actions on native Home Assistant automations, and adds an experimental, fully-gated, NumPy-only ML subsystem that runs alongside the proven detection/matching engine. On top of that panel it also sharpens what WashData notices and how it talks to you: detection flags cycles that finish suspiciously early or draw unusual energy, energy tracking grows into a full Home Assistant Energy dashboard sensor with per-profile cost, notifications gain quiet hours and celebration milestones, the panel picks up a phase timeline and a genuine power-user Playground, and you can now simply ask your voice assistant whether the washer is done. It also opens WashData up to the community for the first time with the optional, off-by-default WashData Store: adopt program profiles and reference cycles that other owners of your exact appliance have already recorded, and share your own back, so you no longer have to teach every program from scratch.
✨ Features
-
Underrun and energy anomaly flags: Alongside the existing overrun signal, a cycle that finishes under 55% of its profile's typical duration is now flagged as an underrun (a ⚡ badge in the Cycles list), and each finished cycle's energy is compared against that profile's own history - a run more than 2.5 standard deviations off the norm is flagged energy high (🔺) or energy low (🔻). The two signals are independent (a cycle can be both an overrun and an energy spike), pure statistics, and purely visible - they never send a notification and never stop a cycle.
-
Create a profile straight from look-alike cycles: The Profiles coverage-gap banner now clusters your recent unmatched cycles by shape and duration, and when it finds a tight group (average correlation of 0.75 or better) it offers a one-click Create profile from N cycles button - turning "I keep running a program WashData doesn't know" into a labelled profile in a single step.
-
Profile warm-up guard: A brand-new profile with fewer than 5 labelled cycles is still learning its shape, so WashData no longer auto-labels against it - matches route to a quick manual confirmation instead, and the profile card shows a Still learning (n/5) badge until it has enough examples to be trusted. Existing, well-populated profiles are unaffected.
-
Shape drift detection: Profile health now watches for a profile's power shape slowly changing over its lifetime (comparing the average curve of its earliest cycles against its most recent ones). A profile whose shape has drifted apart raises a maintenance advisory in its detail view - often an early sign the appliance's behaviour has changed and the profile is worth re-recording.
-
Home Assistant Energy dashboard integration: A new
sensor.<name>_energy_totalreports the appliance's lifetime energy in kWh as a proper total-increasing energy sensor, so you can drop it straight into Home Assistant's Energy dashboard and track consumption over time. It accumulates once per completed cycle and is persisted across restarts. -
Per-profile average cost: Profile cards and the profile panel now show each program's average and total energy cost in your Home Assistant currency, computed from the price frozen onto every member cycle - so you can see at a glance that a hot cotton wash costs more than an eco run.
-
Richer finish notifications: The finish-message template gained three new placeholders:
{time_finished}(the clock time the cycle ended),{vs_typical}(a plain "12% longer than usual" comparison against the profile's median, empty when unknown), and{cycle_count}(your appliance's lifetime completed-cycle count). See NOTIFICATIONS.md. -
Peak-rate tip on start: If you have configured a price per kWh and set a peak-rate threshold, WashData appends a short informational line to the start notification when a cycle begins while the current price is at or above that threshold (default "Running at peak rate ({price}/kWh)."). It is purely advisory - WashData never schedules or delays your appliance.
-
Quiet hours: Set a do-not-disturb window (start and end hour, windows crossing midnight supported) under Notifications. Finish, clean-laundry, pre-completion, and milestone notifications that would fire inside the window are held and delivered the moment it ends; live-progress ticks and start notifications are never delayed.
-
Cycle milestones: WashData can now celebrate the appliance's lifetime achievements - a one-off notification fires when the completed-cycle count crosses a milestone (default 50, 100, 500, and 1000), with a customizable message (
{device}/{cycle_count}). Respects quiet hours; no new notification type or entity. -
iOS Live Activity enrichment: Live-progress notifications sent to
mobile_app_*targets now carry the extra fields an iOS Live Activity needs - a program subtitle, a structuredcontent_state(state, progress, ETA, program, device), and start/end lifecycle markers - so a companion-app Live Activity can show a live washing-machine widget on the lock screen. Android and other targets are unaffected. -
Live phase timeline: The Status progress bar now sits above a color-coded phase timeline - each phase of the matched program rendered as its own segment with a live cursor, so you can see at a glance whether the machine is washing, rinsing, or spinning. Falls back to the plain bar when the matched profile has no phases defined.
-
Duration sparkline on profile cards: Each profile card shows a mini sparkline of its recent cycle durations (last ~10 runs), a quick visual cue for whether a program's run time is steady or drifting. Hidden until a profile has at least three cycles.
-
Cycles list: pagination, undo, and bulk relabel: Long histories now paginate with a Load more button instead of loading everything at once. Deleting a cycle or a profile shows a 10-second Undo toast and only commits the delete once the window closes. The multi-select toolbar gained a Bulk relabel action to reassign many cycles to one profile at once.
-
Settings change history: Every settings save is now recorded (which field, its old and new value, and when). Changed fields show a small dot with a "Changed from X to Y on {date}" tooltip, and a Settings history table lists the recent changes - so you can see what you tuned and when, and remember what a value used to be.
-
Maintenance log and service reminders: A new Maintenance sub-tab (under Advanced) lets you log real-world upkeep - descaling, filter cleaning, drum cleaning, bearing service, or a free-form note - as a dated timeline. Set per-event reminder intervals (defaults: descale every 30 cycles, filter clean every 50, drum clean every 100) and WashData surfaces a
maintenance_duelist on the state sensor and a panel banner once you are due. Logging a relevant maintenance event also suppresses the matching drift/health advisory for 30 days, so fixing the appliance quiets the nag. No new notification type. -
First-run onboarding: A fresh device with no cycles and no profiles now shows a friendly onboarding card on the Status tab - "just run your appliance normally; after 3 cycles matching begins" - with an N/3 progress meter and a Skip link. Once three cycles are in, it prompts you to name your first program, turning the empty-panel cold start into a guided path.
-
Basic / Advanced settings: A Basic | Advanced toggle now sits atop the Settings tab. Basic shows roughly the dozen highest-impact fields for a clean first pass; Advanced shows everything. It is purely a visibility filter (hidden fields keep their values) and the choice is remembered per account.
-
Playground tab (power users): A new top-level Playground tab for people who want to see exactly what the engine is doing - all driven by the real detection, matching and estimation code, not an approximation. It is one unified workbench: a single interactive power graph is always the centrepiece, and a shared detection/matching settings panel and a bottom "Across your cycles" drawer both feed it. Pick any stored cycle (matched by auto-detect by default, so you see what the matcher itself would pick) and press Run (or Cancel) to replay it exactly as the integration would - the real detector state band, phase bands, and event pins whose heads sit above the graph on stems down to their real time (detected → match committed → notification points → finished); hover a pin for a plain-language explanation of what that event did. A side rail of alerts (overrun, did-not-finish, unmatched, ambiguous, …) now also flags how the cycle ended - when it stopped only because power stayed low for the off-delay timeout (rather than smart end-prediction), or would have run indefinitely - so you can see whether an auto-detected cycle actually ends on its own. The simulated match now mirrors the live integration faithfully: it reports the committed program (persistence-gated, held) rather than the raw per-interval top candidate, so mid-cycle profile flips that never happened live are absent from the simulation; and the analysis panel separates Match confidence (the real matcher score, same as the readout strip) from Envelope fit (how the shape sits inside the saved envelope — a different lens), making the two independent numbers easy to read without confusion. A live readout strip shows model-estimated time-left, progress, match confidence and current phase. The graph is fully interactive: hover to read time-from-start / time-to-end / power at the cursor, scroll to zoom, drag to pan, double-click to reset. Drag the start/stop threshold lines, or edit any setting, and the simulation re-runs. Every knob in the settings panel is a real, user-settable option (all detection knobs are fully wired; the matching group exposes the two duration-ratio gates you can configure), and Save to settings copies everything you edited straight into the device's live settings in one click - no retyping. The drawer holds two lenses that drive the same graph in place (no back-and-forth): Test on history replays your recent cycles into a per-cycle results table with a before/after diff (which cycles became correct, which regressed, whose end-timing changed) - click any row to load that cycle into the graph above; and Optimize finds the value of any setting that best meets an objective (match accuracy, end-timing accuracy, false-end rate, duration off-target, ambiguity) as a 1D curve, or sweeps two settings together as a 2D heatmap, then applies the best value in one click. Both run as background tasks (see below) with a live progress bar, ETA and a working Cancel, so the panel stays responsive and keeps loading other data instead of freezing while a batch runs. The graph still overlays the profile's power envelope and the DTW alignment with the full Stage-2 / DTW / Stage-4 score breakdown, so you can see why a cycle matched (or didn't).
-
Background tasks you can actually see: long operations — the Playground's Test-on-history and Optimize, plus Process history (reprocess) and on-device ML training — now run as tracked background tasks on the server instead of being tied to the open tab. A header activity pill shows each running task (device and what it is doing), and — crucially — a task survives a backgrounded tab or a dropped connection: reopen the panel and it re-attaches to the running task and still shows its state and, when finished, its result. The Playground's batch runs (Test-on-history and Optimize) are chunked, so their pill shows a live progress bar and ETA and a Cancel that stops the run promptly; Process history reports phase-by-phase progress (matching → golden → suggestions → training → health) and cancelling it skips the remaining ML-training step; ML training runs to completion in one pass (no interim percentage). Test-on-history and Optimize also keep a Recent runs list so you can reopen a past run's results without recomputing them. No more work churning invisibly with no way to see it or get the result back.
-
Ask Home Assistant if it's done: WashData now answers natural-language questions through Assist (voice or text) - "is my washer done?", "how long until the dryer finishes?" - with a live, plain-language reply ("still running, about 20 minutes left" / "finished 5 minutes ago" / "not running"), disambiguating by appliance name when you have more than one. Because Home Assistant does not let a custom integration inject sentences into the built-in voice agent at runtime, you wire the trigger phrases with a small
custom_sentencespack (one line to copy; see NOTIFICATIONS.md); the intent itself works immediately from automations and the Assist pipeline. -
Per-cycle energy cost: The Cycles list has a new Cost column. Cost is frozen onto each cycle when it finishes (energy × the price per kWh in effect at that moment), so later price changes never rewrite historical costs. Shown in your Home Assistant currency; set a static price or a price-entity under Settings to populate it.
-
ML end-detection guard (experimental, opt-in): When ML models are enabled for a device, the cycle-end model acts as an anti-premature-stop guard - if it judges a low-power lull to be a pause rather than the true end, it defers the normal completion and keeps the cycle running until the model agrees the cycle has really finished. It is one-directional (it can only delay a finish, never end a cycle early), bounded (a wrong model adds at most 30 minutes, never hangs a cycle), only engages once the matched profile is confident enough to trust, and never touches the force-stop / smart-termination / manual-stop paths. Prefers a device's on-device-trained end model, falls back to the shipped baseline. Off by default; the proven power/energy end detection is unchanged when disabled.
-
Terminal-drop fast finalize (experimental, opt-in): The counterpart to the end-guard. Normally a cycle whose power drops to zero is held open for several minutes (up to 8 for washing machines, an hour for dishwashers) before it is finalized, because that quiet period could be a legitimate soak / drying pause - the integration can't tell a pulled plug from a soak just from "0 watts". When ML models are enabled, WashData now learns, from the device's own completed cycles, the earliest point at which that appliance has ever legitimately gone quiet. A hard power cliff-to-zero that happens earlier than that is treated as a genuine stop (plug pulled, program cancelled) and the cycle is finalized in ~1–2 minutes instead of waiting out the full soak window. Like the end-guard it is strictly one-directional (it can only ever shorten the wait, never end a normal cycle early), needs a few completed cycles before it will act, ignores interrupted cycles when learning the baseline, and is completely inert when the ML opt-in is off. As a safeguard it also defers on unfamiliar cycles: if the running cycle is drawing power unlike anything the appliance has produced before (so it might be a brand-new program you're running for the first time, whose normal behaviour just isn't learned yet), the fast finalize stands down and the proven slow detection takes over. The cycle is still recorded as Interrupted - this only makes the integration react to it sooner.
-
Full-Screen Management Panel: A new WashData entry in the Home Assistant sidebar opens a dedicated panel with Overview, Cycles, Profiles, Settings, ML Training, and Advanced tabs, backed by a new WebSocket API. This is where you manage WashData now: the integration's Configure dialog is reduced to onboarding essentials only (device type, power sensor, minimum power, and an optional first profile), and everything else - detection tuning, profiles, phases, notifications, diagnostics, export/import, access control - moved into the panel. The layout is mobile and touch friendly (responsive grids, larger touch targets, portrait support) and auto-busts its browser cache on every update.
-
Overview dashboard: The panel's landing tab. Live state, power, progress, and time remaining, with a device bar across the top for switching between appliances, and attention cards that surface pending tuning suggestions and cycle-review items and jump straight to them. The Manual Recording widget lives here too - hit Start Recording before you run a new program to capture a clean reference cycle. A labelled live power chart, served from the integration so it survives a browser refresh (and the in-progress cycle trace survives a restart), with a cursor-following readout showing time from start, time to end, power, and envelope range. When a profile is matched its full expected curve is overlaid in faint orange; an optional Raw socket overlay draws the configured power sensor's recorder history for the running cycle side by side with the integration's processed and sampled trace. An optional Live Match Debug card shows confidence, match ambiguity, and the top candidate profiles.
-
Program selector on Overview: Choose the running program, or return to auto-detect, directly from the dashboard. The selection is tagged (auto-detected) or (manually selected) and drives the same manual-program mechanism as the program-select entity, so the panel, the entity, and automations stay in sync.
-
Interactive Cycles tab (#272, formerly History): A compact, history-sorted list. Click a cycle to inspect its full power curve and label, delete, trim, or split it. The per-cycle inspect view exposes the complete power trace for every stored cycle - directly addressing requests to retrieve a cycle's power profile for use with external tools (e.g. Tibber pricing windows). Trimming uses draggable handles with a seconds-from-start or clock-time input toggle. Splitting auto-detects idle gaps or lets you place split points on the graph, assigning a profile per segment. A selection mode adds multi-cycle Compare, Merge, and bulk delete, and retroactive auto-label moved into the toolbar. Compare overlays the selected cycles on one graph - each cycle has a show/hide toggle (colour-matched to its trace), and a second set of toggles overlays any learned profile's expected envelope, so you can eyeball how several runs line up against each other and against a program.
-
Profile control panel: Open any profile for statistics (cycle count, durations, average and total energy, consistency, last run), its average envelope graph, an interactive phase-range editor with draggable boundaries over the average curve, history cleanup (every member cycle overlaid; hover to identify a curve and click or tick it to delete outliers), and rename / rebuild / delete. Phase-catalog management (adding, editing, and removing the phase labels themselves) also lives in the Profiles tab.
-
Profile phases now drive estimation, not just labels: The phase ranges you draw on a profile (Pre-Rinse, Wash, Rinse, Soak, Spin, Dry, ...) are no longer a purely cosmetic overlay - they are wired into the phase-aware progress and time-remaining estimator. The current-phase readout and the remaining-time estimate are derived by indexing your phase ranges with the (ML-blended) progress fraction, so both stay correct when a cycle runs longer or shorter than usual. One phase definition powers the editor, the live phase readout, and the estimate.
-
Inline tuning suggestions: Suggested values appear beside the relevant settings fields with a one-click Use and an Apply all banner. Each setting has a hover explainer with a small diagram drawn in the browser, and the Settings layout was reworked to be roomier and grouped.
-
Advanced tab (#281): One home for the power-user and admin surfaces, split into sub-tabs. My Preferences holds per-account display options (default landing tab, cycle date format, whether to show the expected-curve overlay and the Live Match Debug card). Diagnostics shows storage stats plus maintenance actions - Process History (re-run matching and ML retraining across all stored cycles after a batch of reviews), Clear Debug Traces, and Wipe All Data - and config export / import (paste JSON or load a file; import also accepts a Home Assistant diagnostics download). Panel Settings lets an admin set the default landing tab for everyone and hide tabs from non-admins. Access Control is the per-user RBAC screen (below). (Phase-catalog management moved to the Profiles tab, and learning-feedback resolution is handled from the Overview attention cards and the per-cycle Review view.)
-
Role-Based Access Control: Not everyone in the house needs to change settings. RBAC lets an administrator hand a partner or a roommate simple read access - so they can check whether the machine is done without being able to touch its configuration - while keeping edit or full access for themselves. Set a per-device level (none, read, edit, or full) per Home Assistant user, with a per-user default, from Advanced → Access Control. Access is enforced on every WebSocket command on the server, not only hidden in the UI: read users get a mostly view-only panel with hidden devices removed (they may still pick the running program, a live-only action), edit users operate normally, and destructive or export actions require full access. Administrators always have full access. Access control is off by default, so existing single-user setups are unaffected.
-
Panel language override: Under Advanced → My Preferences, a Panel language dropdown forces the WashData panel into a specific language for your account only. If a translation is incomplete or reads awkwardly, switch it to English and the panel re-renders in English immediately - no Home Assistant restart, no effect on other users, and no effect on Home Assistant's own language (only the WashData panel is affected).
-
Logs: A live view of the recent WashData log stream, read from an in-memory ring buffer, with export. Filter by level, device, and component (e.g. show only the Playground or the matcher) and search the messages — all applied instantly client-side. It opens as a slide-in drawer that sits side-by-side with the rest of the panel instead of navigating away, so you can read logs and click through the panel at the same time; drag its edge to resize, and the width and open/closed state persist across refreshes. The drawer auto-refreshes while open.
-
Higher matching accuracy: The profile-matching pipeline was re-tuned end to end - the correlation/MAE similarity split, the duration and energy agreement terms, and the DTW refinement stage, which gained a derivative-DTW (DDTW) "ensemble" mode. In leave-one-out evaluation on real-world data this lifted top-1 program identification substantially (from about 62% with DTW disabled into the low 70s, with further gains for grouped near-duplicate programs). The MAE term is now expressed relative to each cycle's peak power, so the same proportional error scores equally on low- and high-power appliances. All scoring constants remain centralised in
const.py. -
Profile groups (#288, work in progress): Group near-duplicate profiles (e.g. the same program at different temperatures, or lower-spin variants that run longer) so the matcher treats them as one candidate and then picks the best-fitting member. Cohesion is measured with a duration-tolerant comparison of peak-normalised envelopes, so only genuinely similar profiles are collapsed; loose groups are left as individual profiles. The panel suggests near-duplicate clusters and provides a group control panel showing the aggregate envelope with each member overlaid, plus its cohesion. Safeguards keep an over-generic group from overpowering a genuinely better single profile (an ambiguous group win falls back to the uncertain/feedback path, and the chosen member must fit reasonably on its own). This is aimed at amplitude variants - profiles whose power shape is identical but peak amplitude differs (different temperature, different spin speed). Grouping, the clustering suggestions, and member selection are new and still maturing; expect them to get sharper in the coming releases.
-
Cycle Timers with auto-pause (#294): Configure one or more timers that fire at specific minutes into a cycle - e.g. "5 minutes in: add bleach softener" or "120 minutes in: resume the soak". Each timer sends a notification to your configured services at the set offset; enabling Auto-pause on a timer also pauses the cycle automatically at that point and delivers an interactive notification with a Resume Cycle action button. The cycle can be resumed from the panel, via the
ha_washdata.resume_cycleservice, or directly from the notification. All time metrics (elapsed time, time remaining, and progress) exclude time spent paused so the estimates stay accurate across a multi-hour soak. -
Notifications built on native automations (#287): The old bespoke "notification actions" editor never had a home in the new panel - a custom in-house action builder isn't something the panel could sensibly surface or edit - so notifications are now driven by ordinary Home Assistant automations instead. Settings → Notifications → Automations lists the automations that already reference this device (deep-linking to the automation editor) and offers a New Automation split button with two ready-made templates: one prefilled with a cycle started trigger and one with a cycle finished trigger for this device. WashData fires
ha_washdata_cycle_started/ha_washdata_cycle_endedbus events and exposes entities, so any side effect is built exactly like any other automation (see the Notifications & Automations guide). Notify-service and people targets are compact entity-pill pickers with a domain-filtered add box and one-click removal. Any custom actions carried over from an older setup keep firing and are not dropped; they will be resurfaced so you can migrate them into automations (or remove them). This change also resolves the'str' object has no attribute 'async_render_to_info'error that complex actions withchoose/if/repeatblocks could throw, since notification actions now run through Home Assistant's standardScriptengine. -
Experimental ML subsystem + dedicated ML Training tab (opt-in, work in progress): A new NumPy-only ML subsystem, off by default and gated behind feature flags, runs alongside the proven detection and matching code and never replaces it. It ships embedded baseline models (cycle-end detection, curve quality, live-match commit), and all ML management is consolidated in a dedicated ML Training tab: a plain-language status card (personalized vs built-in, with a data-readiness bar), two independent toggles (apply models during a live cycle / let WashData retrain), a manual "Train now", a "what WashData has learned" list with a fit chip per capability, and program-matching fine-tuning (with revert). Per-cycle ML health / review stays inline in the Cycles tab and ML-calibrated tuning suggestions appear beside the classic ones. Optional on-device retraining only promotes a model when it beats the shipped baseline on held-out data. When the feature is off, the UI and logic stay inert. This whole subsystem is an early foundation - enable it if you want to help it learn, and expect its accuracy to improve substantially over subsequent releases.
-
On-device matcher tuning: When on-device training runs, WashData also tunes how much the matcher weighs curve shape versus duration and energy agreement, and how much its DTW ensemble leans on the derivative (DDTW) component - using the same held-out promotion discipline as the models. Device-specific weights are adopted only when they beat the shipped defaults on held-out cycles, and only bounded scoring weights can change (never structural matching behaviour). A Matching Tuning card in the ML Training tab shows the defaults, the in-use weights, when they were tuned, and a Revert to defaults button.
-
Richer cycle and profile review: The cycle-relabel view can overlay any of the device's profiles for quick visual comparison, per-cycle ML quality / golden review labels feed back into on-device training, and multi-select on the Cycles page uses a reliable per-row checkbox.
-
Remaining-time prediction that learns your appliance (experimental, opt-in): A new on-device regression model predicts how far through a cycle you are from the power curve's shape rather than assuming every run takes the profile's median duration, so time-remaining stays accurate when a program runs longer or shorter than usual. It blends gently into the existing phase-aware estimate (never wholly overriding it) and is inert until on-device training promotes a model that beats the naive estimate on your own cycles. There is no shipped baseline, so behaviour is unchanged until then.
-
Projected energy & cost for the running cycle: The progress sensor now exposes
projected_energy_kwhandprojected_costattributes for the in-progress cycle, derived from energy consumed so far and the (ML-blended) progress, using the same price you configured for completed-cycle cost. Present only once a reliable estimate exists; no new entity. -
Profile recommendations: The Profiles tab shows a Recommendations panel with actionable, plain-language advice derived from each profile's health and drift - e.g. "this profile's cycles are running progressively longer, consider re-recording" or "low fit score, review its cycles." Pure statistics, surfaced in the existing tab (never a notification).
-
Anti-wrinkle mode now available for washing machines (#296): The anti-crease / anti-wrinkle state was previously gated to dryers and washer-dryers only. Washing machines with a post-cycle tumble mode (e.g. Miele "Knitterschutz") exhibit the same pattern - a low baseline draw with occasional short motor bursts until the door is opened - so the feature is now enabled for washing machines too. Enable Anti-Wrinkle Shield in Settings; the detection logic and the
STATE_ANTI_WRINKLEstate are identical to the dryer implementation. Real-world setup note (tested on Miele washers and dryers with a constant ~2.5–3.2 W anti-crease baseline): set Stop Threshold above the anti-crease baseline (e.g. 4 W), use a shorter Off Delay (~150 s, less than the burst interval), and lower End Energy Threshold to ~0.25 Wh so the energy gate doesn't absorb the constant baseline draw. The Anti-Wrinkle Exit Power threshold (default 0.8 W) must sit below the anti-crease baseline (it marks the true-off state, not the baseline itself); the default value is correct for most machines and should not be raised above the baseline. -
New device types: "Other (Advanced)" and "Threshold Device" (#228): Two catch-all types for appliances that aren't one of the named categories. Threshold Device (#228) is for truly uncategorised appliances where you only need simple threshold-based cycle detection: it does no profile matching or learning, and ships deliberately generic detection defaults that you tune yourself to your appliance. The new
generic"Other (Advanced)" type is instead for predictable appliances that do have a reproducible power signature but don't fit a named category (washing machine, dryer, dishwasher, air fryer, bread maker, pump): it ships neutral defaults and takes part fully in profile matching, learning, and the ML features. (What used to be labelled "Other (Advanced)" - internal keyother- is now the Threshold Device; the internal key is unchanged, so existing entries keep working exactly as before.) -
Live panel - no more manual refresh: The management panel now refreshes automatically whenever Home Assistant entity states change (via
set hass()) as well as on a 5-second background poll, so the dashboard, cycles, and profiles stay current without any manual page refresh. The Refresh Interval setting has been removed from Panel Settings. The panel also subscribes toha_washdata_cycle_startedandha_washdata_cycle_endedevents, so cycle completions appear within seconds. -
Power-based Off detection (#284, opt-in): A new Power Off Threshold (with a Power Off Delay) under Settings, in Detection, lets WashData return a finished machine to Off when it is actually switched off rather than after a fixed timer. Once a cycle has finished, if power stays below the threshold for the delay, the state returns to Off and the Clean overlay clears, giving "laundry is waiting until I switch the machine off" automations a real off event to trigger on, especially on machines without a door sensor. Set the threshold above the appliance's true switched-off floor and below its finished-but-on standby draw (it is ignored unless it sits below the Stop Threshold). It is evaluated only in the terminal state, so a mid-cycle soak can never be mistaken for off, and it is disabled by default (threshold 0), leaving existing behaviour unchanged. When enabled, the machine stays in Finished/Clean until it is genuinely powered off (the progress-reset timer no longer forces Off); a machine whose standby never drops below the threshold stays Finished by design, so choose the value with that in mind.
-
HA restart gap detection: When Home Assistant restarts mid-cycle, the integration now records the dark period (from the last saved timestamp to restoration time) as a restart gap on the cycle. Restart gaps are surfaced in two places: a blue ↻ badge in the Cycles list (hover for the gap duration), and a shaded region on the power trace in the cycle detail view with a plain-language summary below the graph. Matching always uses only real sensor readings; no synthetic fill is injected into the power trace, so matching accuracy is unaffected. The metadata is available to automations and external tools via the
restart_gapsfield on the cycle record. -
Cycle anomaly signals (work in progress): WashData now flags irregularities without ever nagging you. A running cycle that materially exceeds its matched profile's typical duration gets a soft overrun signal (a
cycle_anomaly/overrun_ratioattribute on the state sensor and a ⏱ badge in the Cycles list). Separately, each finished cycle's power trace is scanned for transient artifacts - a pause (e.g. a door opened mid-cycle), or a sustained out-of-band dip or spike - which are shaded on the cycle graph and listed in its Inspect view with a ⚠ badge in the Cycles list. These are purely visible signals: they never send a notification and never end or force-stop a cycle (the existing safety timeouts are unchanged). Anomaly detection is new and deliberately conservative; it will get more accurate as the underlying heuristics and models mature. -
Revert on-device models to baseline: The ML Training tab gained a Revert models to baseline button that discards all on-device-trained models and falls back to the shipped baselines (training may re-promote them later), mirroring the existing matcher-tuning revert.
-
Forward-looking reference power curve (#304): While a cycle is running and matched,
sensor.<name>_programnow carries areference_profileattribute - a compact[[offset_s, watts], ...]curve of the matched program's expected power over time (plusduration_sandcycle_count). Home energy managers can slice it from the live progress position to anticipate later load (for example a heating spike still to come) and make a smarter "ride out a dip vs. throttle another load" decision than a scalar time-remaining allows. It is read-only exposure of data WashData already learns - no detection or matching behaviour changes. The curve is downsampled to stay under ~1 KB, is present only once a real profile is matched (absent while detecting or unmatched, so consumers can tell the difference), and is excluded from the recorder database since it is a live forecast with no historical value. -
WashData Store — community catalog (opt-in): A new optional online feature set (disabled by default, enabled under Settings → Store) connects your integration to the WashData Store, a community-maintained catalog of appliances, programs, and reference power cycles. The store is entirely opt-in: zero data leaves your Home Assistant unless you explicitly share something. Key capabilities:
- Browse and adopt: Declare your appliance's brand and model in Settings (with brand and model dropdowns fed from the live catalog). Once declared, WashData can download community-contributed reference cycles for your device and import them as imported profiles — giving you a head start on program recognition without recording every wash yourself.
- Share a device package: A share tree in the Settings tab lets you select which profiles and their reference cycles to upload. Settings (tuned detection thresholds) and phase maps can optionally be included per profile. Shared packages are uploaded as pending contributions and become searchable by other users once the community confirms them.
- Import from a share link: Paste a share link from another user to import their device package directly. Adoption brings in brand, model, profiles, and optionally tuned settings and phase maps — one click to bootstrap a new device with community data.
- Sign in with GitHub: The store requires a GitHub account for contributions (community voting, uploading). Browsing and downloading are always anonymous; your GitHub identity is used only for uploads and for the community confirmation vote.
- Community quality control: Uploaded cycles are community-voted. Once a device or cycle accumulates enough confirmations from distinct users (default threshold: 5), it is auto-promoted to approved. Admins can adjust the threshold and approve or remove content through a dedicated admin panel.
- Identity cleared on opt-out: Disabling online features clears the stored store account from WashData's data, so no identity is silently retained after opting out.
🛠️ Improvements
-
Setting conflict validation with coherent cascade: The Settings panel detects and highlights incompatible setting combinations inline, before saving. When two settings conflict (e.g. Start Threshold below Stop Threshold), a red error row appears under each affected field with a clickable Use X button. Clicking it now cascades automatically: if fixing Start Threshold means Stop Threshold must also drop, and Stop Threshold means Min Power must also drop, all three are adjusted in one click — fields in the current section update their DOM inputs; fields in other sections are updated via
this._optsand tracked in_cascadePendingso the next Save includes them. Clicking a tuning suggestion's Use button also triggers the same cascade immediately, so staging one suggestion auto-fixes any downstream settings that need to move with it. Saving is blocked while any conflict is unresolved. Conflicts in the saved settings are surfaced in the Overview tab (red attention card, ⚠ tab-label indicator, red section-pill dot). The backendreconcile_suggestions()is now direction-aware and cascade-creative: it determines which setting is the intended anchor (e.g. a lowered Start Threshold) and cascade-creates consistent entries for dependent settings (Stop Threshold, Min Power) so the full suggestion set is jointly valid before it's ever shown. A fixpoint loop (up to 8 iterations) ensures multi-step chains converge. A new Revert changes button sits next to Save and Refresh: it snapshots the settings state before each save and can restore it in one click, even after saving — disabled until the first save in the session. Conflict rules are now device-type-aware: the anti-wrinkle constraints (exit power vs stop threshold, max power vs start threshold) only fire for washing machines, dryers, and washer-dryer combos; the pump-stuck-duration constraint only fires for pump devices. This prevents spurious conflict errors on dishwashers and other appliances where those features don't exist. Conflict-suggestion coherence: when a conflict fires and a pending tuning suggestion for the affected setting would resolve it, the panel shows "Stage the pending suggestion (X) below to fix this" in place of the generic Use X fix button — directing the user to the already-computed, data-backed value rather than a mechanical minimum. Fully localised across all 35 supported panel languages. -
Suggestion quality gates: The suggestion engine now applies two gates before surfacing a recommendation. (1) Minimum significant delta: a suggestion is only stored when it represents a real change — at least 8% relative change or a key-category absolute minimum (0.3 W for power settings, 5 s for time settings, 0.02 for ratios and confidence scores). Noise changes like 0.67 → 0.68 are silently discarded before they can appear. (2) Post-apply cooldown: after the user applies suggestions, no new suggestions are surfaced for the next 3 completed cycles. This prevents the engine from immediately re-suggesting a slightly-different value based on a single new cycle, and stops the conflict-resolution / suggestion-engine feedback loop where applying a conflict-mandated value (e.g. raise Stop Threshold to 2.0) could cause the suggestion engine to immediately suggest lowering it again.
-
Clearer suggestion labels with per-setting impact explanations: When the Settings panel shows a tuning suggestion, the generic "Classic" and "ML" labels are replaced with Observed (what your appliance's data statistically shows) and Calibrated (what would improve detection accuracy based on past outcomes). When both engines agree within 5%, they collapse into a single WashData recommends recommendation — no ambiguity. When they diverge, a one-line tradeoff note appears below the two values explaining exactly what choosing each will do for that specific setting. For example, for Stop Threshold: "💡 Ends cycle faster — may close during a pause · 🤖 Waits longer to end — fewer premature completions". Impact explanations cover Stop Threshold, Start Threshold, Off Delay, Min Off Gap, Auto-Label Confidence, Profile Match Threshold, Watchdog Interval, No-Update Timeout, Learning Confidence, Min Power, Duration Tolerance, End Energy Threshold, End Repeat Count, and Running Dead Zone. Fully localised across all 35 supported panel languages.
-
ML suggestions surfaced in overview: ML-calibrated setting recommendations appear beside the yellow suggestion dot and the attention card in the Overview tab, the same way classic statistical suggestions do.
-
Built-in phases fully editable: All phases in the per-profile phase editor - including the default/built-in ranges - can be edited. They remain un-deletable (only custom phases can be deleted), and built-in phases display a "Built-in" tag with a note that changes override the default.
-
Settings switches & tooltips: Boolean settings render as switches instead of checkboxes (matching the label styling of every other setting), tooltips are positioned correctly, every setting now has an explanatory tooltip, and the Triggers & Door section is split into clearer subcategories (External End Trigger / Door & Pause / Unload Reminder).
-
Live suggestion state: Accepting a tuning suggestion immediately updates the category indicator and the "N suggestions" count in the panel (reverted on refresh if not saved).
-
Training status: The training indicator is per-device (starting a run on one appliance does not affect the display for others) and reflects the real backend state after a page refresh.
-
Profile health, drift, and coverage insights (pure statistics, no ML): the Profiles tab surfaces per-profile fit/consistency health, duration/energy drift trends, and a coverage-gap hint when many recent cycles go unmatched (with a create-profile shortcut). These also power the new Recommendations panel.
-
Envelope conformance as a second safety check: at cycle end WashData measures how much of the trace falls within the matched profile's power band; a poorly-conforming cycle downgrades a high-confidence auto-label to a review request (complementing the ML quality gate), catching matches that fit in shape but not in power level.
-
live_matchmodel retrains on-device: compact per-cycle match-ranking snapshots accumulate during normal matching and become the training set for the live-match commit model, so it can personalize to your appliance like the other on-device models. -
Train nowand status show every model: the ML Training tab lists each promoted on-device model with when it was trained and its held-out metric (including the new remaining-time regressor). -
Conflict badge on all device cards: The device selector bar shows a red
⚠ Nbadge next to any device that has N unresolved setting conflicts, not only the currently selected one. Conflict counts are derived from the same rules used in the Settings tab and applied to each device's options already present in the WebSocket payload, so no extra round-trip is needed. -
Conflict banner and jump-to-section in Settings: When any setting conflict is active, a red banner appears at the top of the Settings tab stating the conflict count and offering a Go to first button that switches directly to the first section containing a conflicted field. This makes it easy to locate and fix a conflict in a section that is not currently visible without manually clicking through each section tab. New translation keys:
conflict.settings_banner,conflict.settings_banner_btn.
🐛 Fixes & refinements
-
Playground and background-task commands are available without a manual restart: WebSocket handlers re-register on every setup call so all commands work immediately after a plain integration reload. The panel's restart prompt self-clears once the commands are live, and a regression test now fails if any registered handler is missing its decorator.
-
Panel updates are push-driven: state changes arrive via Home Assistant's entity-state push plus cycle/task event subscriptions, with a coalesced refresh and a slow ~20s safety heartbeat. No busy-polling or unnecessary background chatter.
-
Logs view captures activity at the configured log level: at Home Assistant's default INFO level, cycle lifecycle events appear without any extra setup. An empty view distinguishes "nothing logged yet" from "filtered out." Raise the integration's log level to capture more detail.
-
Back-to-back cycles no longer clobber each other: when one cycle's end-of-cycle processing overlapped the start of the very next cycle, the cleanup could reset the newly-started cycle back to Off mid-run. Cycle-end processing now detects that a new cycle has begun and leaves its live state untouched.
-
No more phantom "cycle finished" alerts from noise: a very short, near-zero-energy power blip is now fully suppressed (the way dishwasher pump-outs already were) instead of being stored as a cycle and firing a finish notification.
-
Interrupted cycles survive a restart with their trace intact: a cycle resurrected after a Home Assistant restart mid-run no longer loses its pre-restart power trace (a timestamp-format mismatch had silently dropped it), and a stale verified-pause flag can no longer carry into the next cycle.
-
Accessibility: keyboard focus rings on all controls,
prefers-reduced-motionsupport, screen-reader roles on toasts and dialogs, and text labels on every chart canvas. Status/health colour tints now render correctly against custom themes. -
On-device ML training: the quality model trains on real per-cycle anomaly-flag counts, and held-out evaluation groups rows by source cycle to prevent same-cycle data leakage — a personalised model is promoted only when it genuinely generalises to unseen data.
-
Panel language and date-format preferences are saved per user: the Panel language and Cycle date display choices under My Preferences are persisted and restored per Home Assistant user account.
-
Auto-pause timer sends the Resume notification only after the pause has taken effect: if a pause cannot apply (wrong state, or a switch that fails to cut power), no Resume notification is sent, so there is never a stuck interactive notification with a non-functional button.
-
Tuned settings survive a reconfigure: the config dialog now consistently reads your current (panel-edited) options first, so re-running setup can no longer fall back to stale defaults for device type, power sensor, or minimum power.
-
Security hardening: destructive WebSocket actions (wipe history, import/export config, reprocess history, clear debug data, trigger ML training) now require an administrator, and configuration export/import is restricted to Home Assistant's allowed paths and refuses to overwrite an existing file. Fired cycle-ended events are also trimmed to stay safely under Home Assistant's event-size limit. When per-user access control is enabled, the background-task commands (watch progress, cancel a task, read a finished result) now resolve each task's owning device and check the caller's access to it, so a restricted user can no longer read or cancel tasks on a device they aren't authorized for by omitting the device id; and the integration-wide community-store preferences now require an administrator, matching the connect / online-toggle actions.
-
Warm-up guard is consistently enforced: a still-learning profile (under 5 labelled cycles) always routes to a manual confirmation regardless of confidence-threshold configuration; the confidence shown is always the real match score.
-
Broad stability pass: an extensive automated code review across the whole 0.5.0 branch tightened dozens of edge cases in cycle detection, profile matching, notifications, and the ML subsystem (input validation, restart handling, and background-task safety), with no change to the tuned matching behaviour.
-
Cycle milestone notifications start from your existing history: the lifetime completed-cycle counter is seeded from your stored cycle history on first run, so an established appliance does not announce the 50 / 100 / 500 milestones it has already passed.
-
Community Store sharing uploads correct cycle stats: when sharing a program, imported and recorded cycles upload their stored duration, energy, and signature. Re-adopting a device package reapplies phase ranges even when there are no new cycles to import.
-
A configured price of exactly 0 is handled correctly: the running cycle's projected cost shows 0.00 (a free or zero-cost tariff) rather than blank.
-
Clearer configuration errors: the options dialog now shows a localized message when the minimum power threshold is not greater than 0, and a failed Assign profile service call now reports the actual reason instead of a generic "cycle not found."
-
Playground Optimize sweep progress is accurate: the progress bar and percentage correctly track the run from start to finish.
-
Serbian (Latin) card labels corrected: the WashData Lovelace card's Serbian (Latin) locale was showing Cyrillic, machine-translated text; it is now proper Serbian in Latin script.
-
Dishwasher Smart Termination uses a fixed 300-second confirmation window: the window is independent of
off_delay, so a large tuned or suggestedoff_delaydoes not delay cycle finalization. A matched dishwasher ends at its real cycle end and the finish notification arrives on time. (Smart Termination is still gated on reaching the expected duration, so the shorter window cannot end a cycle early.) -
off_delaysuggestions are no longer inflated by the drying phase: the classic ("Observed") and ML ("Calibrated") off-delay recommendations counted a resumed intra-cycle pause whenever any activity followed a quiet stretch, so a dishwasher's terminal pump-out or vent tick (a few dozen watts, a sample or two) right before the cycle ends turned the whole ~35-min drying tail into a giant "pause", pushing the suggestedoff_delayup toward 2000s. A low stretch now only counts as a real pause when the appliance resumes into sustained activity, so the recommendation reflects genuine mid-cycle pauses. -
Process History reconciles cycle duration data: cycles whose stored
durationandend_timedo not agree with their power trace are corrected so all three values are consistent. Healthy cycles — including dishwashers with a legitimate near-zero drying tail — are left untouched. -
Smart Termination could split one wash into several cycles when two similar programs exist (#288): When a device has a short program ("Quick 40°C", ~46 min) and a longer program whose first half looks almost identical ("Normal 40°C", ~88 min), the matcher would lock onto the shorter one during the opening minutes of a Normal wash - because the traces really are the same up to that point. If the machine then went quiet during a mid-cycle soak, Smart Termination could fire at the short program's expected duration and close the cycle early, leaving the remaining 30+ minutes to be re-detected as one or more extra cycles. WashData now recognises this "prefix ambiguity" - a plausible longer program that an ongoing soak could still be inside - and in that case blocks Smart Termination, falling back to the power-based timeout so a Normal wash is no longer chopped into pieces. For a genuine Quick cycle this only delays completion by a few minutes.
-
CleanState Never Shown for Washing Machines (#282, #283): A completed cycle ends inSTATE_FINISHED, butcheck_state()only surfacedSTATE_CLEANforSTATE_OFF, so with a Door Sensor configured theCleanstate was unreachable andsensor.<name>_stateshowedFinished.check_state()now reportsCleanfor finished cycles too. Reported and fixed by @Olen. -
Signal and other strict-schema notify platforms rejected extra keys (#286): Notification payloads previously included mobile-app-specific fields (
tag,timeout,channel,priority,actions,sticky) for every configured notify target, causingvoluptuous.error.MultipleInvalid: extra keys not allowed @ data[tag]on platforms such as Signal Messenger and Pushover whose schemas do not accept those keys. These fields are now added to the payload only when the target service starts withmobile_app_; all other services receive the basemessage/titlepayload only. -
Notify entities silently dropped notifications (#297): When a modern Home Assistant notification entity (e.g.
notify.iphone_*) was configured as a notification target, notifications were never sent. The dispatch path correctly detected the entity, then fell through to a legacyhass.services.async_call("notify", "iphone_*", ...)call that silently fails; modern notify entities are not registered as legacy services and must be called vianotify.send_messagewithentity_idinstead. Fixed: the dispatch now callsnotify.send_messagefor any target that resolves to a notify-domain entity in the HA state machine. -
Notify Group entities missing from the notification target picker (#290): Notify groups created through the Home Assistant UI (e.g.
notify.group_home) are registered as standard HA entities, not as legacynotify.*services, so they were absent from WashData's notification target autocomplete list. The Settings panel entity picker now merges both legacy service names (fromhass.services.notify) and entity-registry notify entries (fromhass.states), so all notify targets (legacy services, mobile app entities, and UI-created notify groups) appear in the suggestion list. -
Entities named in the server's language, not the viewer's (#279): WashData resolved its entity names in the Home Assistant server (system) language, so a user whose own profile was set to English could still see the entities named in whatever language the server was configured in. Entity names now resolve in the user's language context instead of the server context, so they follow the language of whoever is viewing them.
-
Dishwasher eco cycle truncated: tail not captured (#291): On eco programs with a long passive-drying tail and a small power bump at the very end, the cycle was being closed before the tail completed, producing a shortened cycle record with missing end data. The dishwasher passive-drying deferral in
_should_defer_finishnow keeps the cycle inSTATE_ENDINGuntil a confirmed end-spike (the pump-out / final heat burst) is seen, or the 30-minute safety bound releases it, so the full cycle including the tail is captured in the recorded cycle. -
Dishwasher: short cycles overran ~30-44 min and could be mislabelled: A dishwasher cycle that ran a few minutes shorter than its profile's average, and whose terminal pump-out landed just before the drop into
ENDING(so no in-ENDINGend-spike ever armed), could not satisfy Smart Termination's 99%-of-expected gate and sat in the pump-out wait until the fallback timeout - finishing ~30-44 min late. Worse, as the trailing idle tail grew, the live match drifted from the correct program to a longer near-duplicate (e.g. "65°" being overtaken by "50°") and the cycle was stored under the wrong label (or unmatched). Two coordinated fixes: (1) the pump-out wait now also releases once the cycle has reached its expected duration and stayed sustained-quiet for 10 min afterwards (gated on reaching expected, so a genuinely-late pump-out after a long passive dry is still caught by the end-spike arm); (2) live re-matching is frozen once the dishwasher is inENDINGand sustained-quiet, so the completed active-phase match is not corrupted by the growing idle tail. Verified on a 31-cycle real dishwasher export: before → 2 cycles finished by timeout at ~180 min, unmatched/mislabelled; after → all 31 detected as one cycle each, matched to the correct profile, and smart-terminated (median overrun 0 min, max +13.5 min). No behavioural change for non-dishwasher devices, unmatched cycles, or cycles whose end-spike already arms normally. -
Settings documentation inaccuracies corrected (#285): Three inaccuracies in the integration's documented behaviour were fixed: (1)
progress_reset_delaydefault is 1800 s (30 min), not 2.5 min or 5 min as stated in older docs; when the timer fires it also resets the detector toSTATE_OFF, not only the progress display; the Settings tooltip now reflects both side effects. (2)anti_wrinkle_exit_powerwas undocumented: it is the true-off exit threshold for the anti-wrinkle detector (must sit below the anti-crease baseline, not above it; the 0.8 W default is correct for most machines) and now has a full tooltip in Settings. (3)sensor.<name>_statepossible values now correctly enumerateIdle,Detecting…,Running,Paused,Ending,Finished,Clean,Anti-Wrinkle, andOffin the documentation. -
Settings values are preserved across section switches and saves: pending edits in one section are retained when switching to another. Cross-section conflict checks use the live in-progress form state, so off-screen fields never silently block a save, and every Save includes all in-progress edits.
-
Manual recording is available as a device action in the automation editor (#305): Record Cycle Start and Record Cycle Stop ship as button entities alongside Force End / Pause / Resume, so recording appears in the device-action picker exactly where you look for it. Record Cycle Start is available only while no recording is in progress; Record Cycle Stop only while one is active. (The underlying
record_start/record_stopservices are unchanged; captured recordings are turned into labelled profiles from the panel.) -
User pause stays active until you press Resume Cycle (#306): Pressing Pause Cycle holds the appliance in Paused by user until you explicitly resume, regardless of how long the pause lasts. Three paths are hardened: (1) the periodic profile matcher skips its envelope-alignment heuristic entirely while a user pause is active (on a dishwasher, without this guard the 1-hour minimum off-gap timeout would eventually finalize the cycle and flip the state to Interrupted); (2) pausing during the cycle detecting phase is respected — the low-power false-start check skips its abort when the user has set a pause; (3) after an HA restart the pause lock is re-asserted on restore, so a paused cycle does not finalize on the first timeout after restarting.
-
Community Store: no misleading zero-energy uploads: Sharing a cycle that has no computed energy figure (older cycles, some recordings) no longer uploads
0 Whas if it were real - the energy statistic is now omitted when unknown, so it cannot drag the store's per-program average energy down. -
Disabling online features clears the stored credential: Turning the community-store online switch off is now a full opt-out - the stored refresh token is dropped from disk (previously it lingered until an explicit Disconnect), and re-enabling simply reconnects. Logging into a different store account now fully replaces the previous one instead of merging, so no field from a prior account (e.g. a stale user id) can persist across an account switch.
-
end_repeat_countsuggestion no longer over-counts brief blips: The end-confirmation-count recommendation applied the same too-loose "resumed pause" test that the off-delay heuristic had, so a terminal pump-out or drying tick after a quiet stretch could be counted as a mid-cycle pause and push the suggested confirmation count higher than needed. It now requires the appliance to resume into sustained activity (the same shared rule already used for off-delay), and a sensor dropout mid-cycle is no longer mistaken for a merged "mid-cycle restart" when selecting clean cycles for tuning - a valid cycle that briefly lost its plug still contributes to learning. -
Localized background-task and suggestion text: The header activity pill now shows the current sub-step of a Process-History run (matching → golden → suggestions → training → health) in your language rather than a generic English label, and the "excluded N mis-detected cycles" note on tuning suggestions is fully localized (each exclusion reason translated) instead of always appearing in English.
-
Event-loop responsiveness and store reliability: The opt-in per-cycle ML quality score and the community-share trace downsampling now run in an executor thread instead of on the event loop, so neither can stall Home Assistant on a long trace; whole-device community downloads bound their per-profile fetch concurrency to avoid tripping the store's rate limiter on appliances with many profiles; and the profile-group cohesion cache is guarded against concurrent access from executor threads.
-
Release validation (hassfest) fixed: the voice-assistant intent responses were stored under a top-level
intentkey that Home Assistant'shassfestvalidator rejects (extra keys not allowed), which was failing release validation. They now live in a self-servedtranslations/intent/{lang}.jsondirectory (the same pattern the panel translations use) and are loaded directly, so all 35 languages stay localized andhassfestpasses. -
Notification & upload hardening: milestone lists now accept genuine positive integers only (a stray
50.5ortruecan no longer fire an unconfigured milestone); the panel's HTML escaper also escapes single quotes, closing an attribute-injection edge for values containing an apostrophe; and community uploads validate their inputs instead of raising, honouring the "never raise" contract. -
Playground fidelity: the what-if simulator now matches the live notification contract exactly (start notifications are never held by quiet hours; milestone notifications are), and the Optimize → end-timing accuracy objective now scores each cycle's detected end against that cycle's own recorded length rather than the program median, so a legitimately long or short cycle is judged fairly.
🛠️ Developer
-
Type-safe WebSocket API contract + generated reference: A new
ws_schema.pyis the single source of truth for the panel's WebSocket surface - a request registry (WS_COMMANDS) plus a typed response (TypedDict) for every one of the ~70 commands. A debug-only response validator runs whenHA_WASHDATA_WS_CONTRACT=1(zero overhead otherwise), a sync test fails if a command is added or removed without updating the contract, anddevtools/generate_ws_types.pyregenerates both the TypeScript types (www/ws-types.d.ts) and an auto-generated command reference atdocs/WS_API.md. -
New modules:
playground.py(headless detector/matcher/estimator replay behind the Playground tab),intents.py(theHaWashdataStatusconversation intent;conversationadded tomanifest.jsondependencies), andprogress.py+notification_rules.py- the single source of truth for the cycle progress/remaining/phase/energy math and the notification decision predicates.manager.pynow calls those pure modules (thin wrappers), and the Playground'sSimRunnercalls the same functions, so the panel's what-if replay is byte-for-byte what the live integration computes; the former client-side detection copy in the panel was deleted. Behavior of the extraction is locked by a golden before/after snapshot plus the progress/notification test suites. New WebSocket commands (run_playground_simulation,run_playground_cycle_detail,run_playground_history,run_playground_sweep,get_dtw_debug) require a full Home Assistant restart to register. -
Panel localisation reworked: All panel UI strings now resolve through the panel's
_t()system, sourced from the per-language files intranslations/panel/. The integration serves that directory directly at/ha_washdata/panel-translations/{lang}.jsonand the panel fetches only the user's language plus theenfallback on demand, so the browser downloads roughly 90-180 KB instead of a single ~3.6 MB all-languages bundle (which, with no cache-buster, was re-downloaded on every panel open). Per-language files are cached per release and busted on upgrade. This removes the client-side bundle (www/panel-translations.json), itsbuild_panel_translations.pygenerator, and the CI freshness check - there is no build step to run after editing panel translations. Scope covers section titles, sub-group headers, field labels and tooltip/doc strings, artifact detail messages, button labels, dynamic chart labels, and the new-profile input label. The shipped language set was trimmed to the languages that can be kept accurate and maintained (see Removed); a revised translation workflow is in progress and will ship in a follow-up release. The config-flow translation files were audited to match the reduced onboarding flow (obsolete multi-step-flow keys removed, keys added for the reconfigure and simplified setup steps). -
New WebSocket command surface (
ws_api.py) backing the panel (settings, profiles, cycles, phases, recording, feedback, diagnostics, suggestions, curve and envelope data, merge / split / trim, logs, panel config, and RBAC), plus commands for profile groups, ML shadow-comparison / per-cycle review, ML training status / trigger, and matcher-tuning revert. Every async handler is registered with@websocket_api.async_response, and all commands pass through a single server-side RBAC guard. -
ProfileStore.get_storage_stats()no longer runs blocking file I/O or serialises the full dataset on the event loop, which could stall the diagnostics view. -
manifest.jsondeclares the panel's prerequisites:frontend,websocket_api, andrecorderare added asafter_dependencies(the recorder is optional and used only for the raw-socket overlay). -
New NumPy-only
ml/package: embedded standardized-logistic baselines with golden parity fixtures,feature_extraction.py,engine.resolve_scorer(the single bridge that prefers an on-device trained spec over the embedded baseline),trainer.py(NumPy logistic training, byte-compatible with the embeddedscore()),training_task.py(on-device orchestration with held-out promotion), andmatching_tuner.py(executor-safe leave-one-out scoring-weight tuning). Nothing beyond NumPy ships at runtime. -
Storage bumped to v8 with a deterministic, non-destructive migration. New store keys:
profile_groups,matching_config, per-cycleml_review, on-deviceml_model_versions, and a "golden" flag on recorded cycles. The migration tags historical manually-recorded cycles as golden references on upgrade so hand-recorded cycles made before the golden flag existed are recognised (kept through cleanup, seed matching, count as clean for training). Recent recordings are matched by the recorder'smeta.source == "recorder"/meta.original_samplesmarkers; the v8 step also recovers older recordings that predate those markers (theirmetais empty) by their structural signature - a completed cycle that the manual recorder built directly and so never stampedmax_power/termination_reasonon, whereas every auto-detected cycle always carriesmax_power. Idempotent, runs once. -
Suggestion engine reworked:
select_clean_cycles()filters mis-detected cycles before analysis,reconcile_suggestions()enforces cross-parameter invariants, and an ML-calibratedMLSuggestionEngineis available behind its flag. The Diagnostics Process History action now reprocesses cycles, backfills, refreshes suggestions, retrains (if enabled) and recomputes per-cycle health in one pass. -
New services:
pause_cycle,resume_cycle, and (behind the ML-training flag)trigger_ml_training. -
A headless panel render smoke test (
devtools/panel_smoke.js) now runs before pytest inrun_tests.sh, catching load-time (TDZ) and template errors that a syntax check alone cannot. -
ML subsystem grew a regression head:
trainer.py/engine.py/training_task.pynow handle astandardized_linearhead (fit_ridge,build_regression_spec,predict_*,resolve_regressor) alongside the logistic classifiers, used by the remaining-time model; the on-devicelive_matchandremaining_timecapabilities train from accumulated data with the same held-out promotion discipline.ProfileStoregained match-ranking-history storage and the pure-statisticscompute_profile_trends/suggest_coverage_gaps/compute_envelope_conformance/compute_profile_advisorieshelpers, surfaced viaws_get_profiles. -
Terminal-state to Off transition consolidated (#284): The two previously independent paths that returned a finished cycle to Off (the manager's wall-clock state-expiry timer and a duplicate hardcoded 30-minute auto-expire inside the detector's
process_reading) are now a single owner,manager._reset_terminal_to_off, driven from_handle_state_expiry. This removes a split-brain window where the detector could flip to Off while the manager's progress/Clean bookkeeping still lingered, and is the foundation for the opt-in power-based Off detection.ANTI_WRINKLE -> Offkeeps its own idle/timeout logic. -
Config-entry schema bumped to 3.6: a deterministic, non-destructive migration remaps any entry still on a removed device type to
other, preserving all tuned options. -
Dead-code and duplicate cleanup (no behaviour change): removed ~900+ lines of unused code - the config-flow-era server-side SVG/chart generators and markdown-table helpers (the panel renders charts client-side), assorted orphaned methods/constants, an orphaned feedback-detail WebSocket endpoint, and the unused
apply_suggestionsoption. Consolidated duplicated logic behind single helpers: one gap-aware trapezoidal energy integrator (signal_processing.integrate_wh, used by both persistence paths), a sharedprofile_expectations, a single ambiguity-margin helper, and one power-trace decompression path. Verified byte-identical on the real-data replay suite.
🗑 Removed
-
Coffee Machine, Electric Vehicle, Heat Pump, and Oven device types have been removed. They were deprecated because each fails one of WashData's appliance fit tests (a user-selected discrete program, a reproducible power signature, a clean return to OFF), so profile matching and time-remaining estimation produced noise rather than signal. Existing config entries on any of these types are automatically migrated to Threshold Device on load, with all stored options and learned profiles preserved - WashData keeps working, it just stops layering appliance-specific defaults it never had good values for. Their device-type-specific defaults, phase catalogs, translations, and the associated deprecation UI (the "(deprecated)" picker labels and the "hide deprecated device types" panel option) were all removed.
-
Unmaintainable language packs removed (60 → 35 languages). Rather than ship translations that could not be kept accurate, the language set was cut back to 35 maintained languages. If your language is no longer bundled, WashData falls back to English; the per-account Panel language override lets you pick English (or another shipped language) explicitly. A revised translation workflow is in the works and will restore broader coverage in a follow-up release.
-
WashData-initiated persistent notifications removed: WashData no longer posts unsolicited Home Assistant persistent notifications for its own learning/tuning activity. Three notification paths were removed: the "tuning suggestions ready" notification, the cycle-verification feedback notification (previously gated by the
suppress_feedback_notificationsoption), and the ghost-cycle auto-tune notification (the "detected ghost cycles / suggested min_power change" message). All of these signals are now surfaced exclusively through the panel: tuning suggestions and the auto-tune min_power suggestion appear in the Settings suggestions banner / per-field pill widgets, and cycles needing verification appear in the Cycles review queue. Thesuppress_feedback_notificationsoption has been removed (it is stripped from existing config entries on migration, no data lost), along with its panel setting and thesuggestions_ready_notification_*,feedback_notification_*, andauto_tune_*translation keys instrings.json,translations/en.json, and all other supported language files. Your configured start/finish/timer notification channels are unaffected.
⚠️ Notes & Prerequisites
- After updating, perform a full Home Assistant restart (not just an integration reload) so the new WebSocket commands register, then hard-refresh the panel.
- This release sets a minimum Home Assistant version of 2026.5.0.
- The raw-socket overlay needs the Recorder to be retaining history for the configured power sensor; it degrades gracefully if the recorder is unavailable.
- If your device was set to Coffee Machine, Electric Vehicle, Heat Pump, or Oven, it is migrated to Threshold Device automatically (your tuned settings and profiles are kept). Since Threshold Device ships intentionally generic defaults, review the detection thresholds under Settings for that device.