Commands vs. Statics — Quick Reference

Understanding command execution paths, static updates, timeouts, and field classification for Voodoo Cloud Display Devices.

Info

This reference explains how the Voodoo Cloud Display Device API actually behaves when it receives JSON requests on the device(s) endpoint, with a focus on commands vs. statics, timeouts, and why certain fields are accepted, ignored, or reinterpreted.

TL;DR

  • Commands drive work → time-bound → use seconds
  • Statics describe state → persistent → no timeout
  • secondsdisplaytimeout
  • Settings persist; commands do not
  • When in doubt: commands are actions, statics are context

1. Two Execution Paths: Commands vs. Statics

Every API request is classified into exactly one of these paths:

  • Command execution (dynamic, time-bound)
  • Static update (persistent background state)

Which path is taken is determined by what fields are present, not by intent.

2. Commands (Dynamic, Time-Bound)

Commands represent active work that a human is expected to complete.

What Makes a Request a Command

A request is treated as a command if either of the following is true:

  • A command field is present, or
  • Any line1–line5 field is present (the system will implicitly convert this into a flash command)

Command Behavior

  • Commands expire after seconds
  • On button press → ACK → command is cleared
  • On timeout → NACK → command is cleared
  • After clearing, the device immediately falls back to its statics

Example Command

{
  "command": "display",
  "line1": "Pack Order 12345",
  "line2": "Qty: 3",
  "color": "blue",
  "seconds": 3600
}

Tip

In this example, "line2": "Qty: 3" renders the text Qty: 3 on the second line of the display. In many workflows, it is better to use the convenience field "quantity": 3 instead, because that renders the quantity in the large box at the top left of the device.

Important Rules

  • seconds = 0 means never timeout (LED stays on until batteries fail)
  • Long-running commands are allowed but consume battery continuously
  • Commands are not persistent state and are never remembered after ACK/NACK

3. Statics (Persistent Background State)

Statics define what the device displays when no command is active.

What Makes a Request a Static Update

A request is treated as a static update when:

  • No command is present, and
  • At least one of statica–statice, locationoverride, brightness, or similar settings is present

If any static line is provided, all static lines are normalized internally to ensure consistent display behavior.

Static Behavior

  • Statics never timeout
  • They persist until explicitly changed
  • They do not generate ACKs or NACKs
  • They consume minimal battery (no LED usage)

Example Static Update

{
  "locationoverride": "PutWall 1|Cubby 34",
  "statica": "Order 34455",
  "staticb": "Waiting for Pack",
  "arrow": "up"
}

4. seconds vs. displaytimeout (Not Related)

These parameters control completely different systems.

ParameterApplies ToMeaning
secondsCommandsHow long before a command expires and NACKs
displaytimeoutStaticsHow long DeviceID/location is temporarily shown after a button press

Key Clarification

  • displaytimeout never affects LEDs
  • displaytimeout cannot cause NACKs
  • It only controls how long identifying information overlays statics

5. Settings Are Not Commands

Some fields are settings, not actions:

  • brightness
  • locationoverride
  • displaytimeout
  • statica–statice

These are remembered by the device and reused automatically.

Warning

Best Practice: Do not mix setting changes with commands in the same request.

Why:
  • Settings updates require additional radio exchanges
  • Extra exchanges add latency to commands
  • Mixed intent makes behavior harder to reason about

6. Automatic Normalization (What the Platform Helps With)

Behind the scenes, the platform:

  • Converts colors, arrows, icons, quantities, barcodes, and QR codes into device-native representations
  • Automatically assigns arrows when a location is known
  • Enforces maximum line lengths
  • Prevents invalid combinations (e.g., statics inside commands)
  • Ensures commands and statics remain logically separate

This allows integrators to send human-readable JSON while devices receive optimized instructions.

7. Recommended Pattern for Long-Lived States

❌ Anti-Pattern

  • Very large seconds values (hours/days)
  • seconds = 0 to handle pauses in work

✅ Recommended Pattern

  1. Use commands to drive active work
  2. Allow commands to timeout naturally
  3. On NACK, replace with a static message (e.g., "Waiting for Pack")
  4. Clear or update statics when work resumes

This approach:

  • Preserves battery life
  • Avoids surprise NACKs
  • Produces predictable device behavior

8. Advanced Pattern: CALL-Based Reissue

Some integrations intentionally:

  1. Let commands timeout
  2. Display a static message instructing a long button press
  3. Receive a CALL event
  4. Re-issue the command in response

This ensures explicit human intent and avoids silent retries.

Field Classification Table

A) Path Selectors (decide whether it's a Command or a Static update)

Field(s)ClassificationWhat it doesNotes / gotchas
commandCommand selectorForces the request onto the command execution pathIf present, statics are not allowed in the same request (they'll be ignored/removed).
line1–line5Implicit command selectorIf any line is present and no command, the system treats it as a command (typically flash)This is the common "why did this become a command?" surprise.

B) Command Payload Fields (meaningful when it's a command)

Field(s)ClassificationWhat it doesNotes / gotchas
secondsCommand lifetimeHow long until the command expires and can NACK0 means "never expire" → LED stays on until batteries die.
color / ColorCommand attributeLED color (normalized)Accepts variants; normalized internally.
soundCommand attributeSound behavior (normalized)Friendly values like beep/none may be translated to device-native encodings.
nonceCommand correlationCaller-provided correlation IDUseful for matching ACK/NACK/CALL responses.
locationCommand addressing helperLocation alias used to resolve device and optionally auto-add arrowWhen used in a command, it's treated as addressing/augmentation, not a persistent setting.
deviceid / deviceID / DeviceIDTargetingChooses the deviceCanonicalized internally.

C) Static Payload Fields (persistent settings + background display)

Field(s)ClassificationWhat it doesNotes / gotchas
statica–staticeStatic display linesPersistent "background" text shown when idleIf you set any static line, the platform normalizes the full set for consistent behavior.
locationoverrideStatic settingPersistent "area|location" style label shown when idleUsed for long-lived context; survives between commands.
brightnessStatic settingPersistent brightness settingDo not include in command requests (it's a setting, not an action).
displaytimeoutStatic UI settingHow long DeviceID/location is shown after button press while idleNot related to seconds; cannot cause NACKs.

D) "Convenience" Fields (interpreted differently depending on Command vs Static)

These are the sneaky ones: they're accepted in both worlds, and might or might not persist.

Field(s)ClassificationIf sent with command…If sent without command…
quantity / QuantityConvenience/decorationRendered as a command display elementBecomes a static decoration (stored and shown when idle)
barcode / BarcodeConvenience/decorationRendered as a command display elementBecomes a static decoration (stored and shown when idle)
qrcode / QRcodeConvenience/decorationRendered as a command display elementBecomes a static decoration (stored and shown when idle)
arrow / ArrowConvenience/decorationRendered as a command display elementBecomes a static decoration (stored and shown when idle)
icon / IconConvenience/decorationRendered as a command display elementBecomes a static decoration (stored and shown when idle)

Practical takeaway: these "convenience" fields behave like extra lines in a command, but like persistent statics in a static update.

E) Fields Intentionally Suppressed/Ignored in Commands

Field(s)ClassificationWhy it's suppressed
brightnessSettingSettings and actions are kept separate for predictable behavior and performance.
locationoverrideSettingCommands don't set persistent context.
statica–staticeSetting/displayStatics are not allowed inside commands.
displaytimeout (and similar)SettingCommands should not change static UI configuration.

Rule of Thumb to Remember

  • If you want an LED + timeout behavior → it's a command (command + seconds).
  • If you want something to "stick" between work → it's statics (statica…statice, locationoverride, brightness).
  • If you include line1…line5, you're in command land whether you meant to be or not.

Put Wall / Pack Wall Worked Example

Below is a Put Wall / Pack Wall worked example mapped end-to-end, using commands for short-lived actions and statics for long-lived state.

A. One-time provisioning per cubby (both sides)

Do this once when installing / mapping devices.

Set location + baseline display behavior (statics)
{
  "locationoverride": "PutWall 1 | Cubby 34",
  "displaytimeout": 15,
  "brightness": 100
}

What it means: Device always knows its putwall/cubby identity when idle. When someone taps the button while idle, it will briefly show DeviceID/location for 15s, then revert.

B. Put Side workflow (fast action)

Goal: Light the Put Side device briefly while the sorter drops units.

Create a "PUT NOW" command (short timeout)
{
  "command": "flash",
  "line1": "PUT: Order 1959690",
  "line2": "Qty: 2",
  "color": "blue",
  "seconds": 60,
  "nonce": "1959690"
}

What happens on the device:

  • LED turns on (blue) and shows the message.
  • Worker drops items and taps the button.

On button press → ACK → command clears:

  • Device immediately falls back to its statics (location / any static lines).

If nobody taps in 60 seconds → NACK → command clears:

  • This is fine on Put Side because it's supposed to be immediate. Your app can log NACK as "put not acknowledged" and potentially re-queue.

Info

Put Side best practice: keep seconds short (30–120s) so NACKs are meaningful.

C. Pack Side workflow (may sit for hours)

Goal: If items are sitting in cubby waiting for packing, the device should keep showing state without draining batteries or generating NACK noise.

When a cubby becomes 'ready for pack', set a PACK HOLD static
{
  "locationoverride": "PutWall 1 | Cubby 34",
  "statica": "READY TO PACK",
  "staticb": "Order 1959690",
  "staticc": "Units: 2",
  "arrow": "up"
}

What happens:

  • No LED necessarily.
  • This message stays forever (until changed).
  • No timeout. No ACK/NACK. Minimal battery impact.

D. Trigger the packer at the moment packing should happen

When packing actually begins (or when you want to actively draw attention), use a command on Pack Side.

Start packing command (short-to-medium seconds)
{
  "command": "display",
  "line1": "PACK NOW",
  "line2": "Order 1959690",
  "line3": "Units: 2",
  "color": "green",
  "seconds": 900,
  "nonce": "1959690-pack"
}

Behavior:

  • LED on (green) up to 15 minutes.
  • If packer taps → ACK → command clears → device returns to the PACK HOLD statics (unless you clear them).

E. What to do on ACK (packing completed)

When you get ACK for the pack command, you typically want to change the idle state.

Option 1: Clear the statics (go back to just location/DeviceID behavior)
{
  "statica": "",
  "staticb": "",
  "staticc": "",
  "staticd": "",
  "statice": ""
}
Option 2: Replace with "DONE" statics for a while
{
  "statica": "PACK COMPLETE",
  "staticb": "Order 1959690",
  "staticc": "Good to ship"
}

F. What to do on NACK (packing didn't happen in time)

This is where Pack Side differs from Put Side. If the pack command times out (NACK), don't keep reissuing a long-running command. Instead:

On NACK, fall back to a static that expresses waiting state
{
  "statica": "WAITING FOR PACK",
  "staticb": "Order 1959690",
  "staticc": "Units: 2"
}

Now:

  • No more NACK spam.
  • No battery drain from a stuck LED.
  • The device still conveys exactly what's sitting there.

G. Optional advanced pattern: CALL-based reissue (human intent)

If you want the packer to explicitly say "I'm here, re-light it":

On NACK, show instructions as statics
{
  "statica": "HOLD BUTTON 4s",
  "staticb": "to re-light pack",
  "staticc": "Order 1959690",
  "unsolicitedNonce": "reissueID-122298393"
}

When worker holds the button:

  • Device sends CALL with the unsolicitedNonce you specify
  • Your system responds by reissuing the pack command

This is elegant because it avoids blind retries.

Warning

The "why not just seconds=0 on Pack Side?" answer:

You can do it, but it turns Pack Side into "leave the LED on for hours/days", which is usually unnecessary battery drain and makes timeout signals meaningless. Statics carry the long-lived state better.