New internal/monitor package provides a thread-safe, file-backed toggle.
Flag file presence (/etc/ems/monitor-only) = monitor-only active, survives
reboots. Deletion = resume normal operation.
Behaviour when active:
- EMS continues polling Prometheus, running the decision engine, and
updating the status page every 2 minutes — full visibility maintained
- All actuator calls suppressed (Shelly switches + Viessmann WW writes)
- Suppressed actions logged as [MONITOR-ONLY] for audit trail
- Startup warns if flag file is already present
Web UI:
- Amber banner "⏸ Monitor-Only — Keine Schaltvorgänge" with inline
"▶ Automatik" resume button when active
- Small unobtrusive "⏸ Monitor-Only" button at page bottom when inactive
- POST /monitor endpoint toggles state and redirects back to status page
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>
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>