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:
1 parent
8e4c22cfcf
commit
5a4515978f
15 files changed
+1856
-7
No files matched your search
@@ -225,6 +225,34 @@ var collectionsSchema = map[string][]fieldDef{
|
||||
// audit trail's.
|
||||
fAutodate("created", true, false),
|
||||
},
|
||||
// The home-charger scheduler: the user's own list of charging tasks, one list
|
||||
// covering every charger they own. The charger's own cloud schedule holds one
|
||||
// window per box; this holds as many tasks as they like, each naming its own
|
||||
// chargers, days and action. Run by the ticker in internal/api/chargingtasks_run.go.
|
||||
"charging_tasks": {
|
||||
fText("name", true),
|
||||
// The home_chargers rows this task acts on. Stored as a list of ids rather
|
||||
// than a relation because empty has to mean "every charger I own" — a
|
||||
// standing wish that keeps covering chargers imported later — and a
|
||||
// multi-relation would have to be rewritten on every import to say it.
|
||||
fJSON("chargers", 2000),
|
||||
fSelect("action", []string{"start", "stop", "limit", "boost"}, true),
|
||||
fNumber("amps"), // the ceiling, for the "limit" action
|
||||
// 24-hour "HH:MM", read in the IANA zone the task was written in. The
|
||||
// server's clock is not the one the user set the time by.
|
||||
fText("time", true),
|
||||
fText("zone", false),
|
||||
fJSON("days", 200), // 0=Sunday … 6=Saturday; empty means every day
|
||||
fBool("enabled"),
|
||||
// The outcome of the last firing, so a task that has been failing quietly
|
||||
// says so in the list. last_run is also the guard against firing twice in
|
||||
// the same minute.
|
||||
fText("last_run", false), // RFC3339, UTC
|
||||
fText("last_result", false),
|
||||
// Owner. Non-cascading, like a charger's.
|
||||
fRelation("owner", "users", false, false),
|
||||
fAutodate("created", true, false),
|
||||
},
|
||||
// Custom fields layered onto the built-in "users" auth collection.
|
||||
"users": {
|
||||
fText("bio", false),
|
||||
@@ -280,6 +308,7 @@ var createOrder = []string{
|
||||
"reminders",
|
||||
"control_audit",
|
||||
"home_chargers",
|
||||
"charging_tasks",
|
||||
}
|
||||
|
||||
// reconcileOrder additionally includes "users" so its custom fields (role,
|
||||
@@ -300,6 +329,7 @@ var reconcileOrder = []string{
|
||||
"reminders",
|
||||
"control_audit",
|
||||
"home_chargers",
|
||||
"charging_tasks",
|
||||
}
|
||||
|
||||
// indexes are extra SQL indexes applied at collection-create time.
|
||||
@@ -316,6 +346,9 @@ var indexes = map[string][]string{
|
||||
// A charger is looked up by its owner, and by serial when checking whether
|
||||
// the account it came from has already been imported.
|
||||
"home_chargers": {"CREATE INDEX `idx_home_chargers_owner_serial` ON `home_chargers` (`owner`, `serial`)"},
|
||||
// The runner sweeps every enabled task on every tick, and the page reads one
|
||||
// owner's; both go through these two columns.
|
||||
"charging_tasks": {"CREATE INDEX `idx_charging_tasks_owner_enabled` ON `charging_tasks` (`owner`, `enabled`)"},
|
||||
"control_audit": {
|
||||
"CREATE INDEX `idx_control_audit_serial_created` ON `control_audit` (`serial`, `created`)",
|
||||
"CREATE INDEX `idx_control_audit_user_created` ON `control_audit` (`user_id`, `created`)",
|
||||
|
||||
Reference in new issue
Block a user