Scheduling
Timers, daily and cron schedules, sunrise and sunset, and the time-zone rules they follow.
| You want | Use | Notes |
|---|---|---|
| run something in N seconds | runIn(seconds, handler, data?, options?) | re-arming the same handler replaces its pending timer |
| run once at an instant | runOnce(isoString, handler) | silently ignored if the instant is past |
| every day at a time | runDaily('HH:MM', handler) | home time zone |
| a cron expression | schedule('0 22 * * 1-5', handler) | 5 fields, optional leading seconds field; home time zone |
| at the next sunrise / sunset | scheduleAtSunrise(handler, offsetMinutes?) / scheduleAtSunset(...) | one-shot; re-arm in the handler |
| cancel | unschedule(handler?) | no argument cancels everything this instance scheduled |
All of them are persisted by the hub and re-armed after a reboot; a timer that came due while the hub was off fires once on boot. Handlers are top-level named functions, as everywhere else. Signatures and examples are on the Scheduling reference.
Inactivity timers
Because runIn replaces the pending timer for the same handler, the classic "off after no motion
for N minutes" needs no bookkeeping:
function onMotionCleared() { runIn(offAfterMinutes * 60, turnOff); } // each clear pushes the deadline out
function onMotion() { unschedule(turnOff); } // motion resumed: cancelPass { overwrite: false } to stack timers instead, and data to tell them apart:
runIn(300, release, { token: evt.entityId }, { overwrite: false });
function release(data) { console.log('releasing', data.token); }Daily sun-anchored behaviour
scheduleAtSunrise / scheduleAtSunset arm one firing at the next occurrence (plus an offset
in minutes; negative is before). For something that should happen every evening, re-arm as the last
step of the handler, and again on systemStart in case a reboot dropped the pending one:
function initialize() {
subscribeToLocationEvent('systemStart', armSunset);
armSunset();
}
function armSunset() { scheduleAtSunset(onSunset, -30); }
function onSunset() {
porch.turnOn();
armSunset();
}To test whether it is currently dark, instead of firing at the boundary, use getSunTimes()
and compare with the home's clock (below). available: false means the hub has no location yet.
Time zones
Cron and daily schedules fire in the home's time zone (getHomeTimeZone(), taken from the hub's
address; UTC until one is set). The isolate's own Date runs on the hub process clock, usually
UTC, so never use getHours(), getDay() or toLocaleString() for wall-clock logic:
const now = formatDateParts(new Date(), getHomeTimeZone()); // { year, month, day, hour, minute, second, weekday }
const minutes = Number(now.hour) * 60 + Number(now.minute);
const sun = getSunTimes();
if (sun.available) {
const set = formatDateParts(new Date(sun.sunsetIso), getHomeTimeZone());
const sunsetMinutes = Number(set.hour) * 60 + Number(set.minute);
if (minutes >= sunsetMinutes) { /* after sunset today */ }
}Date.now() and getTime() are zone-independent and fine for durations.
Cron format
Standard five fields: minute hour day-of-month month day-of-week, evaluated in the home's zone.
A sixth leading field for seconds is accepted. Examples: 0 7 * * 1-5 (07:00 weekdays),
*/15 * * * * (every 15 minutes), 0 0 1 * * (midnight on the 1st).
Cleaning up
Call unschedule() in updated before re-initialising, so a changed time does not leave the old
schedule firing. unschedule(handler) is precise enough to cancel one pending runIn without
touching daily schedules.