Add WW boost enable/disable toggle (persistent flag file)

In summer the WW boost interferes with car charging priority.
A new / toggle on the WW consumer card persists to
/etc/ems/ww-boost-off (presence = disabled, absence = enabled).

- config: ww_boost_disable_file flag file path
- main: wwBoostDisabled mode, wwBoostToggleHandler, runCycle skips
  computeWWBoost when disabled
- status: WWBoostDisabled in pageData, toggle button in WW card
  showing  Boost aus /  Boost ein depending on state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 16:09:33 +02:00
parent 1b57293da9
commit afbb7bf62c
4 changed files with 64 additions and 16 deletions

40
main.go
View File

@@ -115,6 +115,12 @@ func main() {
)
}
// WW boost disable flag (persistent; presence of file = boost disabled)
wwBoostDisabled := monitor.New(cfg.EMS.WWBoostDisableFile)
if wwBoostDisabled.IsActive() {
logger.Info("WW boost disabled via flag file", "flag_file", cfg.EMS.WWBoostDisableFile)
}
// Trip mode manager (goal persistence + session learning)
tripMgr := trip.New(cfg.EMS.TripGoalFile, cfg.EMS.SessionLogFile)
if g := tripMgr.ActiveGoal(); g != nil {
@@ -130,7 +136,7 @@ func main() {
// Status store (shared between HTTP handler and control loop)
wwConfigured := cfg.Viessmann.InstallationID != ""
statusStore := status.NewStore(*dryRun, wwConfigured, cfg.Strategic, monitorMode, tripMgr, carOptions)
statusStore := status.NewStore(*dryRun, wwConfigured, cfg.Strategic, monitorMode, wwBoostDisabled, tripMgr, carOptions)
// Metrics HTTP server
mux := http.NewServeMux()
@@ -144,6 +150,7 @@ func main() {
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("/ww/boost", wwBoostToggleHandler(wwBoostDisabled, logger))
mux.HandleFunc("/", statusStore.Handler())
srv := &http.Server{
@@ -172,12 +179,12 @@ func main() {
logger.Info("EMS control loop started")
// Run once immediately
runCycle(ctx, coll, eng, act, fc, m, statusStore, cfg, cfg.EMS.StateFile, logger, *dryRun, monitorMode, tripMgr)
runCycle(ctx, coll, eng, act, fc, m, statusStore, cfg, cfg.EMS.StateFile, logger, *dryRun, monitorMode, wwBoostDisabled, tripMgr)
for {
select {
case <-ticker.C:
runCycle(ctx, coll, eng, act, fc, m, statusStore, cfg, cfg.EMS.StateFile, logger, *dryRun, monitorMode, tripMgr)
runCycle(ctx, coll, eng, act, fc, m, statusStore, cfg, cfg.EMS.StateFile, logger, *dryRun, monitorMode, wwBoostDisabled, tripMgr)
case sig := <-sigCh:
logger.Info("received signal, shutting down", "signal", sig)
@@ -204,6 +211,7 @@ func runCycle(
logger *slog.Logger,
dryRun bool,
monitorMode *monitor.Mode,
wwBoostDisabled *monitor.Mode,
tripMgr *trip.Manager,
) {
now := time.Now()
@@ -251,7 +259,10 @@ func runCycle(
}
// Step 3: Run decision engine
wwBoostC := computeWWBoost(fcResult, cfg)
wwBoostC := 0.0
if !wwBoostDisabled.IsActive() {
wwBoostC = computeWWBoost(fcResult, cfg)
}
decisionStart := time.Now()
actions := eng.Decide(state, now, wwBoostC)
m.DecisionDuration.Observe(time.Since(decisionStart).Seconds())
@@ -454,6 +465,27 @@ func tripCancelHandler(tm *trip.Manager, logger *slog.Logger) http.HandlerFunc {
}
}
// wwBoostToggleHandler toggles the WW boost disable flag on POST.
func wwBoostToggleHandler(disabled *monitor.Mode, 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
}
newState := !disabled.IsActive()
if err := disabled.Set(newState); err != nil {
logger.Error("WW boost toggle failed", "error", err)
} else {
if newState {
logger.Info("WW boost disabled via web UI")
} else {
logger.Info("WW boost enabled via web UI")
}
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
// 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 {