Command sent. Did the device actually execute it?
How to design device control that distinguishes acceptance from motion, recovers the result after a lost response, and shows what is known after a restart.

An operator sets a target position of 40 mm. The panel says “Done”, but the drive carriage is halfway there. What did the system actually confirm? The server may have accepted the request, the broker may have forwarded the message, and the controller may have queued the job. Each stage tells us something different.
We will follow a fictional command, C42, for a linear drive. First we will lose the server response, then see what happens after a controller restart. The values illustrate the application contract. Choosing values for a particular drive requires measurements and trials.
What the panel can confirm
The panel sends POST /devices/D1/commands with ID C42, a 40 mm target, a start deadline, and expected control generation 7. In this contract, the API stores the command and its dispatch job in one transaction. After the transaction commits, it returns 202 Accepted with the address /devices/D1/commands/C42, where the panel can read the status. HTTP 202 signals acceptance for processing. The durable write is a promise made by this API contract. RFC 9110, § 15.3.3
Scroll horizontally if needed.
| State | What the system can establish |
|---|---|
Accepted · accepted |
The API durably stored C42 and its dispatch job. |
Executing · executing |
The controller reported that it began executing C42. |
Confirmed · confirmed |
Evidence tied to C42 met the position, quality, and freshness criteria. |
Expired · expired |
The controller or the authoritative queue established that the start deadline passed before execution began. |
Failed · failed |
The controller reported a conclusive execution failure. The carriage may have moved part of the way. |
Unknown effect · unknown |
The available evidence leaves the outcome of C42 unresolved. |
The panel shows the command target, last known command state, and last measured position with the age of the reading separately. Historical confirmation of C42 remains true even if the carriage later changes position. When the connection is lost, the panel can mark the cached status as stale. The state unknown represents an outcome the system has been unable to resolve, for example after a restart during movement.
How a command moves through the system
The diagram begins after C42 is accepted. Authorisation failures and conflicts during creation occur earlier. The transition from unknown to confirmed represents recovered evidence from the same attempt. The transition to failed represents recovery of a conclusive failure record. Time passing without data leaves the physical outcome unresolved.
The API stores each command’s state separately and assigns the next revision after accepting a permitted transition. The panel can then ignore an older revision of C42, and an event for C42 leaves C43 alone. The controller’s event counter may reset on restart. The API compares these counters within a single boot session and checks that the event belongs to the correct attempt. A late “Executing” event cannot roll back “Confirmed”.
Recovering the result without a second movement
The panel loses its connection after C42 is stored and before receiving 202. Meanwhile, the controller receives the job, moves the carriage, and returns evidence that it reached the target. When the panel reconnects, it retries the same POST with C42. The API finds the record and returns 200 OK with the current state, confirmed. An earlier retry would return the state known at that point. If the panel already knows the C42 address, it can read the status with GET.
The panel issues one ID for each operator intent. Two requests with the same ID and the same parameters refer to the same resource. Retrying C42 with a 60 mm target returns a conflict. Define the scope of the key, for example by account and device, and check current permissions when reading a stored result. AWS Builders’ Library
There are two separate retry boundaries.
- Panel and API. A unique key and an atomic write of the command and dispatch job prevent duplicate jobs, even with concurrent requests. A dispatch job stored in the same database is an example of a transactional outbox. AWS Prescriptive Guidance
- Dispatch to the controller. A worker can send C42, lose its connection before marking the job as sent, and send it again. The controller recognises the ID in durable storage and atomically claims the start of execution. MQTT QoS 2 concerns message delivery within the protocol, and HTTP method idempotency concerns the intended effect on the server. The device execution contract determines how many times the mechanism moves. MQTT 5.0, § 4.3 · RFC 9110, § 9.2.2
ID retention has to cover permitted retries, queueing, and reconnection windows. When the retry budget runs out, the panel shows the last known state. Automatically issuing a fresh ID would create another intent. Reading the existing C42 can remain possible after the start deadline, subject to retention and current permissions.
What changes when the drive restarts
The controller records the start of execution before signalling the drive. The write and physical movement are separate actions. If power fails between those actions or during movement, the same record can remain after a restart whether the carriage stayed still, moved partway, or reached the target without a stored result. The system moves to unknown and does not restart C42 automatically.
The controller can report a result preserved from the previous boot session if the evidence ties it to C42 and supports the checks that applied to that attempt. A new 40 mm reading after restart is a separate observation. It shows the current position and leaves the history of C42 unresolved on its own. This also applies to an absolute target. Before moving again, establish the current zero point, changes to the mechanism, and the order of jobs. For the relative command “move by 40 mm”, repeating the action may add another 40 mm.
What reading is enough to confirm the position?
The example measures the carriage. The motor position can look correct even when the mechanism has play or a coupling has failed. The controller accepts positions from 39.5 to 40.5 mm and sample ages from 0 to 200 ms, with both ranges inclusive. It calculates sample age on the monotonic clock of that boot session. The sample must come from the start of execution or later, pass the quality check, and be tied to device D1, command C42, generation 8, and the boot session in which the attempt ran.
Scroll horizontally if needed.
| Sample at the controller’s evaluation | Decision in the example |
|---|---|
| 40.3 mm, 80 ms old, valid quality and matching attempt | Confirms that the position criterion was met. |
| 39.5 mm or 40.5 mm, at most 200 ms old, all other conditions met | Confirms. Both endpoints are included. |
| 40.3 mm, 201 ms old, or a sample from before execution started | Insufficient to confirm C42. |
| 40.3 mm, 80 ms old, from another boot session without a link to C42 | Insufficient to confirm C42. |
The controller reports when it evaluated the sample and how old the sample was at the time. The API can later show the historical result without subtracting the controller’s clock from its own. A single sample establishes the position at the moment of measurement. The carriage may already have been at the target before the command. To establish movement, use readings taken before the attempt and during or after it. To establish that the carriage held its position, specify the required time within tolerance and any applicable speed limit.
Handling a late command and a second operator
The C42 deadline limits when motion can start. The API checks it on acceptance, and the controller checks again before execution. Once now >= expiresAt, starting is forbidden. MQTT 5.0 Message Expiry Interval bounds the message lifetime before the broker forwards it. A delivered message can still wait in the controller’s queue. If the controller cannot reliably evaluate the deadline, this contract prevents it from starting. The start deadline, the time limit for movement in progress, and confirmation of a stop are separate requirements. MQTT 5.0, § 3.3.2.3.3
Two operators see generation 7. The API atomically accepts C42 and advances the generation to 8. A second request with the old generation receives a conflict. Retrying the same C42 bypasses this check because it reads an existing resource. The API checks permissions and the retry key first, then evaluates the deadline and expected generation for a new command.
The example permits one unresolved job per device. A new intent with the current generation waits until C42 is resolved or receives a busy response. The controller preserves duplicate protection after restart. A late copy of C42 still refers to the same attempt. Queueing and stopping behaviour belong in a separate device specification. Machine safety functions require a separate design.
Testing the contract before deployment
Inject failures at each boundary. For each test, observe the panel, the controller’s durable journal, and the effect on the device.
Scroll horizontally if needed.
| Test | Expected result |
|---|---|
202 lost after C42 was stored |
Retrying the POST returns the same resource. A single dispatch job exists. |
| Duplicate after dispatch or restart | The controller recognises C42. The start count does not increase. |
| Same key with a 60 mm target | Parameter conflict, with no new job. |
| Two concurrent intents using generation 7 | One is accepted. The second operator refreshes the state. |
| Deadline passes while queued or the controller cannot evaluate it reliably | No start. Reporting expiry requires conclusive information. |
| Power failure between storing the start and storing the result | Outcome unknown until reconciliation. Restart does not replay C42. |
| Stale, invalid, or incorrectly associated reading | C42 is unconfirmed. |
| Operator loses access to the device | Reading the stored C42 result is denied too. |
To reconstruct each test, record the device and command IDs, generation, actor, event version, boot session ID, acceptance and start, feedback, and outcome. Record the authorisation decision without copying access tokens. When accepting the integration, check that after each interruption the panel shows exactly what the system can establish about C42.
Sources
- IETF RFC 9110, June 2022, HTTP method idempotency. § 9.2.2
- IETF RFC 9110, June 2022, semantics of status 202. § 15.3.3
- OASIS MQTT 5.0, March 2019, message lifetime and QoS levels. Specification
- AWS Builders’ Library, caller intent identifiers and retry handling. Making retries safe with idempotent APIs
- AWS Prescriptive Guidance, consistent writes of an operation and dispatch job, and duplicate messages. Transactional outbox pattern
Sources checked on 10 November 2026. The example models an application contract. Storage durability, behaviour after power failure, and motion criteria require trials on the target device.