Schedule Modes
Audience: Control Plane / backend, mobile, firmware, QA.
This page is the source of truth for scheduler mode numbers on the wire. Topic and message details: Schedule MQTT.
Wire rule: Numbers are never renumbered. Mode 2 stays empty (reserved / legacy).
Limit: Firmware SCHEDULER_MAX_PROGRAMS = 10. Control Plane enforces the same cap.
Quick table
mode | Name | Product / UI | Stored? | Tick type | Boot recovery |
|---|---|---|---|---|---|
0 | Manual | Command only | No | Event (immediate) | No |
1 | Fixed time | On or off at a local time | Yes | Event (minute) | Yes |
2 | (reserved) | New clients must not send | — | — | — |
3 | Periodic (day start) | Periodic, start within the day | Yes | Continuous (~1s) | No |
4 | Periodic (timed start) | Periodic, start at a clock time | Yes | Continuous (~1s) | No |
5 | Astro sunrise | Sunrise ± offset | Yes | Event (minute) | Yes (today only) |
6 | Astro sunset | Sunset ± offset | Yes | Event (minute) | Yes (today only) |
7 | Random pulses | N ON pulses in a time window | Yes | Event (minute) | No |
Priority when several programs match: lower mode wins; if equal, higher pid wins.
Mode 2 (reserved)
No new feature is assigned to slot 2.
| Option | Decision |
|---|---|
| Renumber (3→2, 4→3, …) | Rejected — breaks NVS and older mobile/cloud |
| New product feature on 2 | Rejected — no need |
| Keep reserved | Selected — stable wire, clear meaning, legacy-safe |
Legacy rule (device)
Older auto-off programs may still arrive as mode: 2 or sit in NVS:
mode 2 → mode 1 + relay_status 0 (parse / NVS sanitize / set)
| Role | Requirement |
|---|---|
| New mobile / Control Plane | Never write 2. Point off = mode:1 + relay_status:0 |
| Reading UI | If twin shows 2, display as fixed-time OFF (until the device migrates) |
| Firmware | Normalize; do not keep canonical 2 in store |
Mode details
0 — Manual (runtime)
- Commands:
manual.set/manual.clearoncmd/device - Not stored in the program list
- Highest priority on that tick (mode 0)
1 — Fixed time (ON or OFF)
One “clock time” mode. There is no separate auto-off mode.
| Field | Required | Meaning |
|---|---|---|
time | Yes | Local "HH:MM" |
offset | No | Shift |
relay_status | Yes in practice | 1 on / 0 off |
days | No | Mask; empty = every day |
enabled | Yes in practice | Omit → 0 (inactive) on device write |
Mobile / REST mapping
| UI | Wire / API |
|---|---|
| Point ON | mode:1, relay_status:1 (REST: fixed + action:on) |
| Point OFF | mode:1, relay_status:0 (REST: fixed + action:off) |
| Range (on–off window) | Two programs: ON pid + OFF pid, same days |
3 — Periodic (day start)
| Field | Meaning |
|---|---|
periodic_on_minute / periodic_off_minute | Both must be ≠ 0 |
periodic_cycle_total | -1 unlimited; omit → 0 does not run on device |
| Anchor | Local minute of first eval that day |
4 — Periodic (timed start)
Same fields as mode 3; anchor = time + offset.
5 / 6 — Astro
| Field | Meaning |
|---|---|
offset | Added to sunrise/sunset (e.g. −15 min) |
relay_status | State to apply |
| Prerequisite | location.set (latitude, longitude, local_offset) |
time is unused for the astro trigger (may still appear in JSON).
7 — Random pulses in a window
N ON pulses inside a local time window; each pulse stays ON for X minutes; off between pulses.
| Field | Meaning |
|---|---|
time (+ optional offset) | Window start (local) |
periodic_off_minute | Window length 1–1440 minutes (required) |
periodic_on_minute | Pulse ON duration in minutes (required) |
periodic_cycle_total | Pulse count 1–16 (required) |
relay_status | ON-phase state (typically 1) |
Rule: periodic_on_minute × periodic_cycle_total ≤ periodic_off_minute.
UI field aliases (same wire): window_start, window end/minutes, pulse_count, on_minute.
Example — 18:00–21:00, 4 pulses × 10 minutes ON:
{
"cmd": "program.set",
"program": {
"pid": 5,
"mode": 7,
"time": "18:00",
"relay_status": 1,
"periodic_on_minute": 10,
"periodic_off_minute": 180,
"periodic_cycle_total": 4,
"days": "1111111",
"enabled": 1
}
}
Mobile / REST → mode map
| Mobile kind / action | mode | Critical fields |
|---|---|---|
| Weekly point, on | 1 | relay_status:1 |
| Weekly point, off | 1 | relay_status:0 |
| Weekly range | 1 + 1 | Two pids (ON then OFF) |
| Periodic, timedStart=false | 3 | on/off/cycle |
| Periodic, timedStart=true | 4 | + time (offset may be 0) |
| Sunrise / sunset | 5 / 6 | location.set first |
| Random pulses | 7 | window + on duration + pulse count |
| — | Removed — use point OFF | |
| Manual override | manual.set | No mode field |
REST string modes used by Control Plane / mobile API:
REST mode | MQTT mode |
|---|---|
fixed | 1 |
periodic | 3 |
periodic_timed | 4 |
sunrise | 5 |
sunset | 6 |
random | 7 |
auto_off is rejected (HTTP 400). Use fixed + action:off.
Control Plane rules
- Twin SoT listen: retained
{mqtt_prefix}/{device_key}/state/schedule - Write:
{mqtt_prefix}/{device_key}/cmd/device→program.set|get|remove|clear - Write whitelist:
1,3,4,5,6,7— never write2or0as a stored program program.setcommand_idmust be unique per publish (sync-set-{rev}-{pid}-{nonce}) — notrule.id(firmware dedupe)- Confirm with ack + retain;
program.setdoes not change the relay immediately - Astro requires
location.set; clock sync viatime.sync(UTC) - Max 10 programs; same
pid= upsert on device - Twin sparse JSON: missing
enabledon read may be treated as active by some readers — Control Plane push always sendsenabled:1for enabled programs
Firmware constants
SCHED_MODE_MANUAL = 0
SCHED_MODE_SCHEDULE = 1
/* 2 reserved — legacy AUTO_OFF → SCHEDULE+OFF */
SCHED_MODE_PERIODIC_DIRECT = 3
SCHED_MODE_PERIODIC_TIME = 4
SCHED_MODE_ASTRO_SUNRISE = 5
SCHED_MODE_ASTRO_SUNSET = 6
SCHED_MODE_RANDOM = 7
Team checklist
Mobile
- No separate auto-off screen
- OFF =
mode:1/ RESTfixed+action:off - Random = pulses: window + on duration + count (
on × count ≤ window) - Send
enabled:1on every set - Periodic:
periodic_cycle_total: -1(or>0)
Control Plane
- Mode write whitelist
1,3,4,5,6,7 - Reject
auto_offcreate (400) / never encode2 - Unique
command_idon everyprogram.set - Range = two programs
- Subscribe to retained
state/schedule - Cap at 10 programs
Firmware
-
2→1+OFF normalize -
7pulse-in-window semantics - Recovery only for
1,5,6
Related
| Doc | Scope |
|---|---|
| This page | Mode SoT |
| Schedule MQTT | Topics, REST automations, push/heal |
| ESP-IDF MQTT topics | Topic prefix overview |