Skip to main content

Schedule MQTT & Automations

Audience: Firmware, backend, mobile, QA.

One mental model end-to-end: each automation program is one firmware scheduler slot.

LayerShape
REST / DB{ mode, time, weekdays, action, … }
MQTT{ cmd: "program.set", program: { mode: 1|3|4|5|6|7, … } }

Mode numbers: see Schedule Modes (SoT). Mode 2 is reserved — never write. Point OFF = mode:1 + relay_status:0 (REST: fixed + action:off). Create auto_off400. Max 10 programs.

1 program = 1 program.set. UI “on at 08:00 / off at 22:00” = two fixed programs.


Flow

Mobile / API → POST/PATCH/DELETE automations

DB programs (revision++)

MQTT {mqtt_prefix}/{device_key}/cmd/device
1. program.clear (command_id: sync-clear-{rev})
2. program.set × N (command_id: sync-set-{rev}-{pid}-{nonce})

Device NVS
  • REST can succeed even if MQTT publish fails (outbox retry).
  • Sync is a full replace: program.clear then all enabled programs.
  • Critical: program.set must use a unique command_id every publish. Reusing rule.id causes firmware to drop duplicates (no ack → heal/toggle broken).

Topics

DirectionTopicMessages
Cloud → device{prefix}/{device_key}/cmd/deviceprogram.clear, program.set, manual.set, time.sync, location.set, …
Device → cloud{prefix}/{device_key}/cmd/ack/deviceCommand acks
Device → cloud{prefix}/{device_key}/state/scheduleRetained program list (twin)
Device → cloud{prefix}/{device_key}/evente.g. relay_changed

MQTT wire

program.clear

{ "cmd": "program.clear", "command_id": "sync-clear-3" }

program.set

{
"cmd": "program.set",
"command_id": "sync-set-3-1-a1b2c3d4",
"program": {
"pid": 1,
"mode": 1,
"time": "18:30",
"offset": { "hour": 0, "minute": 0 },
"relay_no": 1,
"relay_status": 1,
"periodic_on_minute": 0,
"periodic_off_minute": 0,
"periodic_cycle_total": -1,
"days": "1111100",
"enabled": 1
}
}
FieldNotes
command_idUnique per set: sync-set-{revision}-{pid}-{nonce}
pidSlot among enabled programs
modeWrite whitelist: 1, 3, 4, 5, 6, 7
daysMon-first mask; API weekdays use Sun=0 … Sat=6

Random (mode 7)

FieldMeaningExample
timeWindow start"18:00"
periodic_off_minuteWindow length (minutes)180 (=21:00)
periodic_on_minutePulse ON duration10
periodic_cycle_totalPulse count (1–16)4
relay_statusON phase1

Constraint: on × count ≤ window.


Mobile automations API (moid)

MethodPathNotes
GET/moid/devices/{device_id}/automations{ revision, programs }
POST…/automations{ program }
PATCH…/automations/{rule_id}mode immutable
DELETE…/automations/{rule_id}
  • Max 10403
  • mode: "auto_off"400

REST fields for random

APIUI suggestion
timewindow_start
periodic_off_minutewindow minutes
periodic_on_minuteon_minute
periodic_cycle_totalpulse_count

Example:

{
"program": {
"enabled": true,
"channel_id": "relay_1",
"timezone": "Europe/Istanbul",
"mode": "random",
"weekdays": [0, 1, 2, 3, 4, 5, 6],
"time": "18:00",
"action": "on",
"periodic_on_minute": 10,
"periodic_off_minute": 180,
"periodic_cycle_total": 4
}
}

Reverse sync / heal

Wire mode:2 → REST fixed + action:off. Heal push always encodes OFF as MQTT mode:1. Unique command_id on every set.

CP empty?Action
Yes + device has programsBootstrap import
No + equalNo-op
No + driftKeep CP; re-push

Mistakes to avoid

Don'tDo
Wire mode:2 / REST auto_offfixed + action:off (create rejects auto_off)
command_id = rule.idsync-set-{rev}-{pid}-{nonce}
Old random = single minute / window-onlyPulse fields + on × count ≤ window
>10 programsCap at 10