// Package scheduler enthält den Display-Zeitplan-Scheduler. // Er prüft jede Minute ob ein Screen ein- oder ausgeschaltet werden soll. package scheduler import ( "context" "log/slog" "time" "git.az-it.net/az/morz-infoboard/server/backend/internal/store" ) // DisplayCommander sendet einen Display-Befehl per MQTT. type DisplayCommander interface { SendDisplayCommand(screenSlug, action string) error } // ScreenSlugGetter lädt den Slug für eine Screen-ID. type ScreenSlugGetter interface { GetByID(ctx context.Context, id string) (*store.Screen, error) } // Run startet den Scheduler-Loop. Blockiert bis ctx abgebrochen wird. func Run(ctx context.Context, schedules *store.ScreenScheduleStore, screens ScreenSlugGetter, notifier DisplayCommander) { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() for { select { case <-ticker.C: check(ctx, schedules, screens, notifier) case <-ctx.Done(): return } } } // check prüft alle aktiven Zeitpläne und sendet ggf. Befehle. func check(ctx context.Context, schedules *store.ScreenScheduleStore, screens ScreenSlugGetter, notifier DisplayCommander) { // Uses process-local timezone — ensure TZ env var is set in the container (e.g. Europe/Berlin). now := time.Now().Format("15:04") enabled, err := schedules.ListEnabled(ctx) if err != nil { slog.Error("scheduler: list enabled schedules failed", "err", err) return } for _, sc := range enabled { screen, err := screens.GetByID(ctx, sc.ScreenID) if err != nil { slog.Warn("scheduler: screen not found", "screen_id", sc.ScreenID, "err", err) continue } var action string if sc.PowerOnTime != "" && sc.PowerOnTime == now { action = "display_on" } else if sc.PowerOffTime != "" && sc.PowerOffTime == now { action = "display_off" } if action == "" { continue } if err := notifier.SendDisplayCommand(screen.Slug, action); err != nil { slog.Error("scheduler: send command failed", "screen_id", sc.ScreenID, "action", action, "err", err) } else { slog.Info("scheduler: display command sent", "screen_id", sc.ScreenID, "slug", screen.Slug, "action", action) } } }