|
| 1 | +--- |
| 2 | +description: HA WashData development workflow - project scope, architecture, and guardrails for all development sessions |
| 3 | +--- |
| 4 | + |
| 5 | +# HA WashData Development Guide |
| 6 | + |
| 7 | +## Project Overview |
| 8 | + |
| 9 | +**Purpose**: Home Assistant custom integration monitoring washing machines, dryers, dishwashers, and coffee machines via smart sockets (power readings). Uses NumPy-powered shape correlation matching to detect cycle programs and estimate completion times. |
| 10 | + |
| 11 | +**Repository**: `/root/ha_washdata` |
| 12 | +**Version**: 0.4.0 (as of Feb 2026) |
| 13 | +**Status**: Available in HACS Default Repository, 1000+ installations, 500+ GitHub stars |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Non-Negotiable Constraints |
| 18 | + |
| 19 | +// turbo-all |
| 20 | + |
| 21 | +### 1. Dependency Policy |
| 22 | +- **ONLY NumPy allowed** - No SciPy, scikit-learn, or other ML libraries |
| 23 | +- Must be in `manifest.json` requirements field |
| 24 | +- No external API calls - 100% local |
| 25 | +- **Async I/O Mandatory**: All heavy matching (DTW, NumPy) MUST run in executor (`await hass.async_add_executor_job`). NEVER block the event loop. |
| 26 | + |
| 27 | +### 2. dt-Aware Computations |
| 28 | +- All time/energy calculations MUST use timestamps (not sample counts) |
| 29 | +- Use `dt_util.now()` for timezone-aware datetimes |
| 30 | +- Energy integration: `Σ P * dt` with explicit gap handling |
| 31 | + |
| 32 | +### 3. UI Text Handling |
| 33 | +- **NO inline strings in Python** - Use `strings.json` and `translations/en.json` |
| 34 | +- Config/Options flow labels must be translation keys |
| 35 | + |
| 36 | +### 4. Options Flow Pattern |
| 37 | +- Advanced tuning in **OptionsFlowHandler** (not ConfigFlow only) |
| 38 | +- Store tunables in `entry.options`, identity in `entry.data` |
| 39 | +- Use `async_update_entry` for modifications |
| 40 | + |
| 41 | +### 5. Migration Safety |
| 42 | +- Use config entry versioning (VERSION/MINOR_VERSION) |
| 43 | +- Implement `async_migrate_entry` in `__init__.py` |
| 44 | +- Migration must be **deterministic and idempotent** |
| 45 | +- Never drop user data during migration |
| 46 | + |
| 47 | +### 6. Event Data Limits |
| 48 | +- Home Assistant limits event data to **32KB** |
| 49 | +- Exclude `power_data`, `debug_data`, `power_trace` from events |
| 50 | + |
| 51 | +--- |
| 52 | + |
| 53 | +## Architecture Overview |
| 54 | + |
| 55 | +``` |
| 56 | +┌─────────────────────────────────────────────────────────────┐ |
| 57 | +│ Home Assistant Integration │ |
| 58 | +├─────────────────────────────────────────────────────────────┤ |
| 59 | +│ ┌──────────────────────────────────────────────────────┐ │ |
| 60 | +│ │ WashDataManager (manager.py ~110KB) │ │ |
| 61 | +│ │ • Power sensor event handling │ │ |
| 62 | +│ │ • Progress tracking & idle-based reset │ │ |
| 63 | +│ │ • Feedback requests & notifications │ │ |
| 64 | +│ │ • Watchdog timer for stuck cycles │ │ |
| 65 | +│ │ • External end trigger support │ │ |
| 66 | +│ └──────────────────────────────────────────────────────┘ │ |
| 67 | +│ ↓ ↓ │ |
| 68 | +│ ┌──────────────────┐ ┌──────────────────────────┐ │ |
| 69 | +│ │ CycleDetector │ │ LearningManager │ │ |
| 70 | +│ │ (cycle_detector) │ │ (learning.py) │ │ |
| 71 | +│ │ │ │ │ │ |
| 72 | +│ │ • State machine: │ │ • User feedback tracking │ │ |
| 73 | +│ │ OFF→STARTING→ │ │ • Profile learning │ │ |
| 74 | +│ │ RUNNING↔PAUSED │ │ • 80/20 weighting │ │ |
| 75 | +│ │ →ENDING→OFF │ │ │ │ |
| 76 | +│ └──────────────────┘ └──────────────────────────┘ │ |
| 77 | +│ ↓ ↓ │ |
| 78 | +│ ┌──────────────────────────────────────────────────────┐ │ |
| 79 | +│ │ ProfileStore (profile_store.py ~108KB) │ │ |
| 80 | +│ │ │ │ |
| 81 | +│ │ • Multi-stage matching pipeline: │ │ |
| 82 | +│ │ Stage 1: Fast Reject (duration/energy/signature) │ │ |
| 83 | +│ │ Stage 2: Core Similarity (MAE+Correlation+Peak) │ │ |
| 84 | +│ │ Stage 3: DTW-Lite tie-break (Sakoe-Chiba band) │ │ |
| 85 | +│ │ • Cycle compression & storage (v2 schema) │ │ |
| 86 | +│ │ • Profile CRUD operations │ │ |
| 87 | +│ └──────────────────────────────────────────────────────┘ │ |
| 88 | +└─────────────────────────────────────────────────────────────┘ |
| 89 | +``` |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +## Key Files Reference |
| 94 | + |
| 95 | +| File | Purpose | Size | |
| 96 | +|------|---------|------| |
| 97 | +| `manager.py` | Main orchestrator, power event handling, progress tracking | ~110KB | |
| 98 | +| `profile_store.py` | Storage v2, compression, NumPy matching pipeline | ~108KB | |
| 99 | +| `config_flow.py` | Configuration wizard, options flow, all UI steps | ~105KB | |
| 100 | +| `cycle_detector.py` | State machine (OFF→STARTING→RUNNING↔PAUSED→ENDING→OFF) | ~36KB | |
| 101 | +| `learning.py` | User feedback processing, profile duration learning | ~23KB | |
| 102 | +| `__init__.py` | Entry point, setup, migration logic | ~25KB | |
| 103 | +| `sensor.py` | All sensor entity definitions | ~15KB | |
| 104 | +| `analysis.py` | Feature extraction and analysis utilities | ~15KB | |
| 105 | +| `const.py` | All constants, config keys, defaults | ~10KB | |
| 106 | +| `signal_processing.py` | dt-aware integration, resampling, smoothing | ~8KB | |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +## Cycle Detection Logic |
| 111 | + |
| 112 | +### State Machine |
| 113 | +``` |
| 114 | +OFF → STARTING → RUNNING ↔ PAUSED → ENDING → OFF |
| 115 | +``` |
| 116 | + |
| 117 | +### Key Thresholds (device-type aware) |
| 118 | +- `start_threshold_w` / `stop_threshold_w`: Hysteresis for clean transitions |
| 119 | +- `start_energy_threshold`: Wh required to confirm start (reject spikes) |
| 120 | +- `end_energy_threshold`: Max Wh during off_delay to confirm end |
| 121 | +- `off_delay`: Seconds below threshold before completing |
| 122 | +- `min_off_gap`: Minimum OFF time before new cycle can start |
| 123 | +- `running_dead_zone`: Ignore power dips in first N seconds after start |
| 124 | +- `end_repeat_count`: Consecutive low readings before ending |
| 125 | + |
| 126 | +### Status Classification |
| 127 | +- ✓ `completed`: Natural finish after off_delay |
| 128 | +- ✓ `force_stopped`: Watchdog finalized while in low-power wait |
| 129 | +- ✗ `interrupted`: Abnormal early end (very short or abrupt drop) |
| 130 | +- ⚠ `resumed`: Restored after HA restart |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## Profile Matching Pipeline |
| 135 | + |
| 136 | +### Stage 1: Fast Reject |
| 137 | +- Duration ratio filter (configurable min/max ratios) |
| 138 | +- Energy delta check (>50% = reject) |
| 139 | +- Signature mismatch (event density, time-to-first-high) |
| 140 | + |
| 141 | +### Stage 2: Core Similarity |
| 142 | +- **MAE (40%)**: Mean absolute error, robust scaled |
| 143 | +- **Correlation (40%)**: NumPy corrcoef shape matching |
| 144 | +- **Peak Power (20%)**: Max power amplitude comparison |
| 145 | +- Confidence boost (+20%) if correlation > 0.85 |
| 146 | + |
| 147 | +### Stage 3: DTW-Lite (tie-breaker only) |
| 148 | +- Sakoe-Chiba band constraint (O(T*band) complexity) |
| 149 | +- Only runs when margin < ambiguity threshold |
| 150 | +- Normalized series (z-score) before comparison |
| 151 | + |
| 152 | +### Key Matching Parameters |
| 153 | +- `profile_match_threshold`: Minimum score to accept match (default: 0.4) |
| 154 | +- `profile_unmatch_threshold`: Score below which to reject mid-cycle (default: 0.35) |
| 155 | +- `profile_duration_tolerance`: Duration band for matching (default: 0.25 = ±25%) |
| 156 | +- `profile_match_interval`: Seconds between match attempts (default: 300) |
| 157 | + |
| 158 | +--- |
| 159 | + |
| 160 | +## Testing Workflow |
| 161 | + |
| 162 | +### Run Tests |
| 163 | +```bash |
| 164 | +# All tests |
| 165 | +pytest tests/ -v |
| 166 | + |
| 167 | +# Specific test files |
| 168 | +pytest tests/test_cycle_detector.py -v |
| 169 | +pytest tests/test_profile_store.py -v |
| 170 | +pytest tests/test_manager.py -v |
| 171 | +pytest tests/test_real_data.py -v |
| 172 | + |
| 173 | +# Syntax check |
| 174 | +python3 -m py_compile custom_components/ha_washdata/*.py |
| 175 | +``` |
| 176 | + |
| 177 | +### Mock Socket Testing |
| 178 | +```bash |
| 179 | +cd /root/ha_washdata |
| 180 | +python3 devtools/mqtt_mock_socket.py --speedup 720 --default LONG --variability 0.15 |
| 181 | + |
| 182 | +# Fault injection |
| 183 | +mosquitto_pub -t homeassistant/mock_washer_power/cmd -m 'LONG_DROPOUT' |
| 184 | +mosquitto_pub -t homeassistant/mock_washer_power/cmd -m 'MEDIUM_GLITCH' |
| 185 | +``` |
| 186 | + |
| 187 | +--- |
| 188 | + |
| 189 | +## Development Checklist |
| 190 | + |
| 191 | +Before any PR: |
| 192 | +1. [ ] `python3 -m py_compile custom_components/ha_washdata/*.py` passes |
| 193 | +2. [ ] `pytest tests/ -v` all green |
| 194 | +3. [ ] No SciPy or disallowed imports |
| 195 | +4. [ ] UI strings in `strings.json` / `translations/en.json` |
| 196 | +5. [ ] dt-aware calculations (timestamps, not sample counts) |
| 197 | +6. [ ] Event data < 32KB (exclude power_data from events) |
| 198 | +7. [ ] Migration is idempotent if schema changed |
| 199 | +8. [ ] Deprecated code removed (not kept alongside new) |
| 200 | +9. [ ] All removed settings also removed from localization files |
| 201 | + |
| 202 | +--- |
| 203 | + |
| 204 | +## Removed/Deprecated Settings (Do Not Use) |
| 205 | + |
| 206 | +These settings have been removed from the codebase and should NOT be re-added: |
| 207 | +- `auto_merge_gap_seconds` - Never used in actual logic |
| 208 | +- `auto_merge_lookback_hours` - Never used in actual logic |
| 209 | +- Legacy slider-based inputs (replaced with text boxes) |
| 210 | +- Sample-count based detection (replaced with dt-aware accumulators) |
| 211 | + |
| 212 | +--- |
| 213 | + |
| 214 | +## Documentation References |
| 215 | + |
| 216 | +- `README.md`: User guide, installation, basic usage, screenshots |
| 217 | +- `SETTINGS_VISUALIZED.md`: Visual explainers for all 20+ parameters |
| 218 | +- `IMPLEMENTATION.md`: Architecture, features, key classes |
| 219 | +- `TESTING.md`: Mock socket guide, test procedures, debugging |
| 220 | +- `CHANGELOG.md`: Release history (current: 0.4.0) |
0 commit comments