Repository navigation
Conversation
Split _run_scene so a scheduled job can run the same step executor without syncing scene IDs to the robot, and add start_job(), which refuses to interrupt a routine that is already running on the device. get_routine_runner() gives other callers the per-context instance. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit c657648)
Roborock's cloud fires server_scheduled_start jobs at nextFireTime. With the vacuum pointed at this server nothing did, so imported schedules never ran. ScheduleRunner evaluates each enabled job's cron in its own timezone and hands due jobs to the routine runner, carrying fan_power, water_box_mode and mop_mode so vac-only and vac+mop schedules behave as configured. One-shot jobs disable themselves after firing. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit b5203b2)
Start the scheduler alongside the other supervisor tasks and stop it on
shutdown. GET /user/devices/{id}/jobs now computes nextFireTime from the
cron instead of returning whatever value was captured at import time.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 14ddb5f)
schedule_store holds create/update/delete for the per-device jobs kept in the inventory, so the timer RPC handler, HTTP routes and dashboard can share one writer. Crons without their own timeZoneId now evaluate in the zone the robot is told by /location (America/New_York unless configured) rather than UTC, so a 11:00 schedule fires at 11:00 local. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 0751a7b)
Cloud-managed robots keep their schedules server-side, so a client such as LocalRock that asks the robot for its timers saw nothing and could not change or delete the schedules imported from the cloud. The topic bridge now offers each rr/m request to an interceptor, and timer requests are answered from the store (keeping a schedule's vac/mop settings on edit) instead of being forwarded to the robot. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> (cherry picked from commit 2f6f666)
POST /user/devices/{duid}/jobs creates a job (the call python-roborock
makes), and PUT/PATCH and DELETE on /jobs/{id} edit or remove one. The
GET matcher is now method-aware so a write no longer falls through to
the list route. Update and delete paths are not confirmed against the
official app; they follow the scene routes' conventions.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 5b83853)
The legacy set_timer action is ["start_clean", {}]. The object now
accepts repeat, fan_power, water_box_mode, mop_mode, mop_template_id,
segments and clean_mop, and get_timer returns them, so a client can show
and change them. Keys are merged into the job's existing settings, null
clears one, and an empty object (what current clients send) leaves the
job alone, so editing a schedule's time never resets its vac/mop choice.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit dd2d3d1)
A robot that speaks server_timer stores those schedules itself, and the official app reads them back from it. Answering get/set/upd/del_server_timer from this store instead hides the robot's real schedules from every client reading through the server and, tried once, caused real data loss: the official app deletes server_timer entries it does not recognize when its schedule screen opens. Adds a parametrized test over all four server_timer methods so the interceptor's method list can't regress into handling them again without a test catching it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The official app writes every server_timer schedule to the robot and also to
POST/PUT /user/devices/{duid}/jobs, and that record's settings carry a "name"
field holding the robot-side server_timer id. job_settings dropped it, since it
is not one of the vac/mop settings, so a client reading the schedule back
through get_timer had no way to find the robot's entry to edit or delete. It
now rides along with the settings.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A schedule's job (POST /user/devices/{duid}/jobs) carries a settings entry whose
"name" is the id of the matching timer the official app also saved on the robot.
The cloud runs such a job by sending server_scheduled_start with that entry, which
is the method name inside the job itself. The robot accepts it, runs the clean
(rooms or whole home) and records it as "Schedules" in its history; without the
entry it answers -10005 "Invalid timer", and with a bare id "Param should be name
+ fan + segment". python-roborock does not list the command, so it is sent through
a small SchedulerCommand enum.
A schedule with no robot-side id (one saved through the legacy set_timer), or one
the robot refuses, falls back to the plainer ways, since a refused command changes
nothing: a whole-home clean is one app_start carrying the schedule's fields plus
source 3, which the robot also records as "Schedules"; a room schedule is the
settings commands followed by a segment clean. Scenes and routines are untouched.
The new steps are produced only by schedules.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…occurrence claims Build on virtitnerd's schedule implementation from upstream PR Python-roborock#179, preserving its commit history and attribution. Retain current upstream fixes while adding capture-compatible payload preservation, timer-state preflight, no native-start fallback, concurrency checks and regression tests. Prepare an upstream-facing branch without personal deployment history. Record the reported S7 scheduled-start result and remaining validation.
Reconcile verified robot timer state after forwarded native writes and periodic reads, and verify native enablement for direct job API updates. Preserve schedule identity, settings, and occurrence claims; reject stale or ambiguous updates. Add coverage for concurrent edits, missed acknowledgements, one-time schedules, HTTP updates, and MQTT pass-through.
|
Thanks for building on #179 and for keeping the original commits attributed. Since this branch carries the #179 commits and is meant to merge right after them, would you open it as a PR into the #179 branch ( Nothing in the description needs to change for that. The native timer check before dispatch, no fallback to an ordinary clean after a refused start, persisted occurrence claims and the DST policy can all go in as the hardening section of #179. Two small things while it moves: the |
|
I’ve submitted a separate PR into @virtitnerd’s I’m keeping #213 open as a reference until the combined work is merged. |
Problem and result
Imported schedules need local execution while preserving their native timer links and cleaning settings. This change builds on virtitnerd's scheduler in #179 and adds native timer verification, durable occurrence claims and capture-compatible job handling.
Dependency and review order
This branch includes the original #179 commits with attribution preserved. Please coordinate with #179 rather than treating this as an independent replacement. Preferred order: merge #179, then review/merge the remaining hardening here. If #179 is squash-merged or changed, I will update this branch so its diff contains only the remaining changes.
What this adds beyond #179
#179 supplies the schedule store, job API, local scheduler and native start command. My additions are:
These include behavioral choices (at-most-once dispatch, missed-run and DST policy) for maintainer review, not claims of complete cloud parity.
Changes
Validation
I tested with a Roborock S7 (roborock.vacuum.a15, firmware 02.16.12). The local server successfully started the vacuum at its scheduled time. I used LocalRock and the OEM Roborock app for schedule control. This is a confirmed scheduled-start scenario.
Python 3.11/3.13 tests and the Docker build passed at
d5ada08d: [passing checks](https://github.com/pponce/local_roborock_server/actions/runs/40226190127).The new enablement synchronization has automated coverage for native MQTT forwarding, HTTP updates, concurrent edits, lost acknowledgements and one-time schedule handling.
Scope and companion PRs
Preferred merge order: coordinate and merge #179, then the remaining hardening in this PR. Afterward, #214 and the agreed LocalRock integration can proceed independently.
The local server now synchronizes native schedule enablement across clients: changes made through Homebridge, LocalRock, or the OEM app for example are reconciled with the saved server job after a fresh robot timer read. Background reads recover missed updates, while HTTP job updates that supply
enabledverify the native timer before saving.