One schedule for every charger, and a clock on the server to keep it

The charger's own cloud schedule is one window inside one box: charge
between these hours, every day, and that is the whole vocabulary. A third
tab on Charging holds a list instead — each line an action, a time, the
days it repeats on and the chargers it acts on — and one list covers the
whole account rather than each charger hiding its own.

The clock is the server's. A schedule that only fires while a tab is open
is a reminder, so a ticker sweeps every enabled task and fires whichever
minute has come. It sends by handing a synthesised request to the same
control endpoint the page's buttons use, so a scheduled command goes
through the same cascade, ownership gate, rate limit and audit trail —
what the owner cannot press by hand, the scheduler cannot send for them.

A task names its chargers, or names none, which means all of them and
keeps meaning that for a charger imported next year. Times are stored as
a wall clock plus the zone they were written in, so 23:00 stays 23:00
wherever the server sits. One action per task: a charging window is the
two tasks that open and close it, which is how it is read back, edited
and switched off.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-09-03 22:47:30 +02:00
co-authored by Claude Opus 5
parent 8e4c22cfcf
commit 5a4515978f
15 changed files with 1856 additions and 7 deletions
+4
View File
@@ -73,6 +73,10 @@ func main() {
// endpoints answer 503 until the read succeeds.
_ = srv.StartPlugins()
// The home-charger scheduler's clock. A schedule that only fires while a
// browser tab is open is a reminder, not a schedule, so it is watched here.
srv.StartScheduler()
httpServer := &http.Server{
Addr: cfg.Addr,
Handler: srv.Handler(),