-
Notifications
You must be signed in to change notification settings - Fork 0
Timers
"Turn this unit off in 45 minutes." Switch the air conditioning on for the evening and have the server switch it off again — so it happens whether or not the phone is still in the house, charged, or in the country.
In the Android app it is the hourglass beside the power switch. Over HTTP it is
/api/timers. New in 3.2.0 — a client can check for it without guessing at
versions, since GET /api/version lists a sleep_timer feature flag.
Programs are standing intentions that keep applying: a schedule fires every Tuesday, a curve drives a setpoint all day. A timer is the opposite — it exists to happen once and then stop existing.
Expressing one as a schedule entry would mean teaching every client that reads
programs about a mode where a schedule is not really a schedule, and would leave
a spent one sitting in the list looking like it will fire again. So timers are
their own thing: timers.json, their own runner, their own endpoints.
# in 45 minutes, turn the living room off
curl -X POST http://server:8420/api/timers \
-H "X-API-Key: $KEY" -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"unit_ids": ["153931628470980"], "minutes": 45}'
curl .../api/timers # what is pending
curl -X DELETE .../api/timers/<id>
curl .../api/timers/status # runner health, for diagnosticsFull reference: REST API.
You ask for minutes, never for a time of day. The server computes the moment
from its own clock. That is deliberate: the alternative puts the burden of knowing
the server's timezone on every client, when the phone may be in another zone and
its clock may be wrong — a failure this project has already been bitten by. Every
response carries seconds_remaining, computed server-side, so a client counts
down from that rather than from its own idea of the time.
settings defaults to turning the unit off, but it is a full control payload, so
the same mechanism can mean "switch to eco in an hour" without a second feature.
- A new timer replaces that unit's existing one. Asking for "off in 30" when one is already pending means you changed your mind, not that you want two competing promises about the same unit.
-
A timer that fires deletes itself.
timers.jsonis empty most of the time; a file full of past entries means the runner is not running. - A timer whose unit is unreachable still deletes itself. Retrying every tick would eventually turn a unit off hours after the moment anyone asked about.
- An overdue timer fires late rather than being skipped. If the server was asleep or rebooting when it came due, "off, late" is the safe direction to be wrong in — the alternative leaves a unit running all night. It is logged.
-
Times are the server's local clock, like everything else scheduled here. In
a container that means setting
TZ.
The log says dispatched, and that word is chosen carefully: msmart's apply()
does not raise when a unit is unreachable — it logs the network error and returns
— so a success from the shared control path is not proof the unit heard anything.
This is true of every control command, including a button press; it just matters
more for something unattended.
The runner checks the state that comes back and warns when the unit reports itself offline:
timer 4f2a dispatched to unit 153931628470980, but the unit is offline
-- it may not have received it
| Variable | Default | Meaning |
|---|---|---|
AC_TIMERS |
<config dir>/timers.json |
where pending timers live |
AC_TIMER_TICK |
15 |
seconds between due-checks |
The tick is finer than the scheduler's 30s on purpose: a schedule only has to hit the right minute, while a timer is a promise about a moment. It is also a separate asyncio task, so a disk error while a timer deletes itself cannot stop schedules and curves from running.
Breeze Core · Breeze for Android · Packages · AGPL-3.0
Start here
Install it
Use it
Reference
Run it safely
Develop and port