Fix compressor watts, add WW boost reset button

Compressor:
- metric changed to PromQL expression that returns actual watts:
  sensors_power_value(%) × power_value(kW) × 10
  e.g. 27% × 10 kW = 2700 W; idle = 0 W
- idle threshold raised 20→500 W (clean gap: 0W idle vs ≥2700W running)
- status page now displays real kW instead of percent

WW boost reset:
- Added 🌡️ Zurücksetzen button to WW consumer card (when configured)
- POST /ww/reset: restores WWBaseC via Viessmann API + blocks re-boost
  via engine override until midnight
- IsWW field added to consumerView for template control

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 16:00:42 +02:00
parent a537b02adb
commit 1b57293da9
3 changed files with 45 additions and 2 deletions

36
main.go
View File

@@ -143,6 +143,7 @@ func main() {
mux.HandleFunc("/monitor", monitorHandler(monitorMode, logger))
mux.HandleFunc("/trip", tripSetHandler(tripMgr, cfg, logger))
mux.HandleFunc("/trip/cancel", tripCancelHandler(tripMgr, logger))
mux.HandleFunc("/ww/reset", wwResetHandler(act, eng, cfg, logger))
mux.HandleFunc("/", statusStore.Handler())
srv := &http.Server{
@@ -453,6 +454,41 @@ func tripCancelHandler(tm *trip.Manager, logger *slog.Logger) http.HandlerFunc {
}
}
// wwResetHandler resets the WW temperature to the configured base setpoint and
// blocks further boosts for the rest of the day via an engine override.
func wwResetHandler(act *actuator.Actuator, eng *engine.Engine, cfg *config.Config, logger *slog.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// Block re-boost until midnight.
now := time.Now()
midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
lockDur := time.Until(midnight)
eng.ApplyOverride(engine.ConsumerWW, false, lockDur)
// Immediately restore base WW temperature via Viessmann API.
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
action := engine.Action{
Consumer: engine.ConsumerWW,
TurnOn: false,
TargetTempC: cfg.Strategic.WWBaseC,
Reason: "manual WW reset via web UI",
}
if err := act.Execute(ctx, []engine.Action{action}); err != nil {
logger.Error("WW reset: actuator failed", "error", err)
} else {
logger.Info("WW boost reset", "base_c", cfg.Strategic.WWBaseC, "locked_until", midnight.Format("15:04"))
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
// monitorHandler toggles monitor-only mode on POST and redirects to the status page.
func monitorHandler(mode *monitor.Mode, logger *slog.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {