All house-specific values (IPs, coordinates, Viessmann credentials, kWp)
replaced with <...> placeholders. Ready to fill in once hardware is known.
Notable differences from house/lutz template:
- installation_id left empty (disables WW boost until credentials obtained)
- base_load_w set to 400W (adjust after measuring)
- forecast lat/lon/declination/azimuth/kwp all require local values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds heating_min_ambient_c (default 15°C) to season config. When outdoor
temperature is at or above this threshold, SG-Ready is suppressed even if
the calendar month is within the heating season. Prevents unnecessary heat
pump boost activation on warm spring/autumn days.
Logic: heating active = in_heating_month AND ambient < threshold
Zero value (unset) disables the temperature gate (calendar-only, old behaviour).
New test: TestHeatingPeriodAmbientSuppression covers warm-day suppression,
cold-day pass-through, and summer month independence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Section title changed to "Verbraucher — Priorität ↓" and each consumer
row now shows a circled number (① ② ③ ④) so it's immediately clear
which consumer has precedence for turn-on and reverse order for shutdown.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update apiBase: api.viessmann.com → api.viessmann-climatesolutions.com
(Viessmann renamed their IoT API host; old domain is NXDOMAIN)
- Extend --test-viessmann context to 120s
- Add 30s propagation wait before read-back in both Stage 3 and Restore:
cloud accepts writes immediately but gateway polls on its own schedule,
so GET returns the last confirmed device value with ~30s lag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds GetDHWTemperature() to the Viessmann client (GET on the DHW feature
endpoint) and a --test-viessmann CLI flag that runs three stages without
starting the EMS control loop:
Stage 1: read current DHW setpoint (confirms auth + endpoint path)
Stage 2: write same value back, read-back to confirm round-trip
Stage 3: write +1°C, read-back to confirm, restore original
The +1°C delta is below the heat pump's 5°C hysteresis so the compressor
will not fire, but the change is visible in the Viessmann app for visual
confirmation before trusting the WW boost logic in production.
Usage: ./ems --config /etc/ems/ems-config.yaml --test-viessmann
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Override duration (web UI):
- Dropdown on Ein button: 30min / 1h / 2h / 4h
- Engine notified immediately via ApplyOverride() — no waiting for
next SyncHardwareState cycle
- Aus button always uses 1h lockout (keeps consumer off for 1h)
Hard-stop thresholds that cancel active overrides:
- SOC emergency brake: now also clears ManualOverride flag so EMS
resumes full control after the safety shutdown
- override_max_import_w (default 800W): if grid import exceeds this
while an override is active, override is cancelled immediately
(no hysteresis delay — protection is instant)
Config: ems.override_max_import_w (0 = disabled)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kettle/dishwasher spikes are seconds-long against hourly forecast
averages — negligible. Correct value is the steady-state draw.
Effect: surplus window today widens from 11:00-13:00 to 10:00-17:00.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses hourly PV forecast data (watts per hour from forecast.solar) to
find the first contiguous block of hours where:
PV production - base_load_w >= min_surplus_w
Shows a blue advice card on the status page:
"Auto einstecken bis HH:MM Uhr"
"Erwartetes Überschuss-Fenster: HH:MM – HH:MM Uhr"
Card is hidden when:
- No surplus window found (weak forecast day)
- Window has already started or passed
- A wallbox is currently active (car already charging)
Config: forecast.base_load_w (1000W), forecast.min_surplus_w (1800W)
Timestamp parsing handles both "HH:MM:SS" and "HH:MM" key formats.
Today: surplus window 11:00–13:00 (24.3 kWh forecast).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
forecast.solar updates estimates as the day progresses, so a single
morning fetch can be stale by afternoon. New behaviour:
- Re-fetch every 4h within 07:00–19:00 window (3 fetches/day)
- Outside the window always serve the cached value
- Free tier limit is 12 req/day — 3 fetches is well within budget
- First startup outside window still fetches if cache is empty
Config: forecast.fetch_interval, fetch_window_start, fetch_window_end
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Status page:
- Per-phase grid power (L1/L2/L3) with export/import direction
- Shelly PM live power badge on active wallbox rows
- Compressor power shown below consumer list
- SyncDeviceStates() method on Store for PM power readback
Engine:
- Fix wallbox mutex: re-read Active state after P3 so same-cycle
WallboxA activation correctly blocks WallboxB in P4
- Fix RecoverState: set ActivatedAt to zero so recovered consumers
are immediately eligible for shutdown decisions
- Add 3 new unit tests: mutual exclusion, car-not-charging,
compressor-idle early release
Makefile:
- make install no longer overwrites /etc/ems/ems-config.yaml if it
already exists (prevents password loss on updates)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>