Skip to main content

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

modeNameProduct / UIStored?Tick typeBoot recovery
0ManualCommand onlyNoEvent (immediate)No
1Fixed timeOn or off at a local timeYesEvent (minute)Yes
2(reserved)New clients must not send
3Periodic (day start)Periodic, start within the dayYesContinuous (~1s)No
4Periodic (timed start)Periodic, start at a clock timeYesContinuous (~1s)No
5Astro sunriseSunrise ± offsetYesEvent (minute)Yes (today only)
6Astro sunsetSunset ± offsetYesEvent (minute)Yes (today only)
7Random pulsesN ON pulses in a time windowYesEvent (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.

OptionDecision
Renumber (3→2, 4→3, …)Rejected — breaks NVS and older mobile/cloud
New product feature on 2Rejected — no need
Keep reservedSelected — 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)
RoleRequirement
New mobile / Control PlaneNever write 2. Point off = mode:1 + relay_status:0
Reading UIIf twin shows 2, display as fixed-time OFF (until the device migrates)
FirmwareNormalize; do not keep canonical 2 in store

Mode details

0 — Manual (runtime)

  • Commands: manual.set / manual.clear on cmd/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.

FieldRequiredMeaning
timeYesLocal "HH:MM"
offsetNoShift
relay_statusYes in practice1 on / 0 off
daysNoMask; empty = every day
enabledYes in practiceOmit → 0 (inactive) on device write

Mobile / REST mapping

UIWire / API
Point ONmode:1, relay_status:1 (REST: fixed + action:on)
Point OFFmode:1, relay_status:0 (REST: fixed + action:off)
Range (on–off window)Two programs: ON pid + OFF pid, same days

3 — Periodic (day start)

FieldMeaning
periodic_on_minute / periodic_off_minuteBoth must be ≠ 0
periodic_cycle_total-1 unlimited; omit → 0 does not run on device
AnchorLocal minute of first eval that day

4 — Periodic (timed start)

Same fields as mode 3; anchor = time + offset.

5 / 6 — Astro

FieldMeaning
offsetAdded to sunrise/sunset (e.g. −15 min)
relay_statusState to apply
Prerequisitelocation.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.

FieldMeaning
time (+ optional offset)Window start (local)
periodic_off_minuteWindow length 1–1440 minutes (required)
periodic_on_minutePulse ON duration in minutes (required)
periodic_cycle_totalPulse count 1–16 (required)
relay_statusON-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 / actionmodeCritical fields
Weekly point, on1relay_status:1
Weekly point, off1relay_status:0
Weekly range1 + 1Two pids (ON then OFF)
Periodic, timedStart=false3on/off/cycle
Periodic, timedStart=true4+ time (offset may be 0)
Sunrise / sunset5 / 6location.set first
Random pulses7window + on duration + pulse count
Separate auto-off menuRemoved — use point OFF
Manual overridemanual.setNo mode field

REST string modes used by Control Plane / mobile API:

REST modeMQTT mode
fixed1
periodic3
periodic_timed4
sunrise5
sunset6
random7

auto_off is rejected (HTTP 400). Use fixed + action:off.


Control Plane rules

  1. Twin SoT listen: retained {mqtt_prefix}/{device_key}/state/schedule
  2. Write: {mqtt_prefix}/{device_key}/cmd/deviceprogram.set|get|remove|clear
  3. Write whitelist: 1,3,4,5,6,7 — never write 2 or 0 as a stored program
  4. program.set command_id must be unique per publish (sync-set-{rev}-{pid}-{nonce}) — not rule.id (firmware dedupe)
  5. Confirm with ack + retain; program.set does not change the relay immediately
  6. Astro requires location.set; clock sync via time.sync (UTC)
  7. Max 10 programs; same pid = upsert on device
  8. Twin sparse JSON: missing enabled on read may be treated as active by some readers — Control Plane push always sends enabled:1 for 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 / REST fixed + action:off
  • Random = pulses: window + on duration + count (on × count ≤ window)
  • Send enabled:1 on every set
  • Periodic: periodic_cycle_total: -1 (or >0)

Control Plane

  • Mode write whitelist 1,3,4,5,6,7
  • Reject auto_off create (400) / never encode 2
  • Unique command_id on every program.set
  • Range = two programs
  • Subscribe to retained state/schedule
  • Cap at 10 programs

Firmware

  • 21+OFF normalize
  • 7 pulse-in-window semantics
  • Recovery only for 1,5,6

DocScope
This pageMode SoT
Schedule MQTTTopics, REST automations, push/heal
ESP-IDF MQTT topicsTopic prefix overview