Skip to content

Serial commands

Cyber Fidget accepts text commands over its USB serial connection. Connect the USB cable, open a serial terminal at 921600 baud, and send each command followed by a newline. Opening the port may restart the Fidget. Wait for it to start, then send version until it answers. Commands are case-insensitive; arguments may be case-sensitive. Success lines start with [cmd], and errors start with [err]. Values in angle brackets below are placeholders; square brackets mean optional arguments. A reply that lists several lines is complete at its stated final line.

A console in the browser

The website's bench page, cyberfidget.com/dev/chassis.html, is a serial console that needs no terminal program. It connects over Web Serial from a desktop Chromium-based browser (Chrome or Edge) at 921600 baud, waits for version to answer, and shows the Fidget's screen live. Type commands in Command input (Up and Down recall earlier ones), pick from the Commands list (a command with placeholders is copied into the input for you to finish), or read Device help, which lists the commands the connected Fidget's help reply names.

Commands in every build

Command and arguments Reply format
version [cmd] version=<firmware-version>
info [cmd] info.fw=..., .type=..., .built=..., .git=..., .dirty=..., .chip=..., .mac=..., .id=<12 hex digits>, .uptime_ms=..., .battery.voltage_mv=..., .battery.soc=..., .battery.crate=..., .board_rev=<major>.<minor>, .board=src=<source> hil=<0\|1> eng=<0\|1> layout=<n>; ends with [cmd] info.wake.cause=...
help [cmd] help=<commands>, [cmd] help.sync=<commands>, [cmd] help.update=<commands>; test builds add [cmd] help.test=<commands>
mark <id> [cmd] mark=<id> uptime_ms=<millis>; without an ID: [err] mark.usage=mark <id>
reboot [cmd] reboot=now, then the device restarts immediately
battery [cmd] battery.vcell_mv=<mV\|-1> soc=<percent> crate=<percent/hour>
diary [cmd] diary.stats=boot=<n> checkins=<n> on_s=<n> cycles=<n> vmin=<mv> vmax=<mv> written=<n> dropped=<n>, up to eight [cmd] diary.rec=<seq> ev=<name> t=<n> mv=<n> soc=<x.x> crate=<x.xx> lines, then [cmd] diary.done=<total_records>
diary clear [cmd] diary.clear=ok; invalid argument: [err] diary.usage=diary [clear]
menutree [cmd] menutree.* tagged menu-tree lines
screencap [cmd] screencap w=128 h=64 bpp=1 fmt=colpage len=<n> b64=<data>
screenstream off [cmd] screenstream=off
screenstream on [fps] [cmd] screenstream=on fps=<fps> interval_ms=<ms>, followed by screencap frames until stopped
upd slot [cmd] upd.slot=<label> state=<state> boot=<label> other=<label> other_state=<state> pend_img=<0\|1> unsig_ok=<0\|1>
upd allow-unsigned on\|off [cmd] upd.unsig_ok=<1\|0\|error>; invalid argument: [err] upd.usage=upd allow-unsigned on\|off

The file commands below use paths inside /apps/ or /assets/. CRC-32 is a checksum used to detect damaged bytes. fwdata and lapply require raw bytes after their command header; fread and lget return raw bytes after the header. A plain text terminal is useful for inspecting them, but file transfers need a program that handles those bytes and checksums. See the firmware's sync protocol for complete transfer and error rules.

Command and arguments Reply format
fwrite <path> <size> <crc32> [cmd] fwrite.ok=<path> size=<n> chunk=<n> crc=<hex>
fwdata <off> <len> <crc32> [cmd] fwdata.ok=off <o> len <n>
fwcommit [cmd] fwcommit.ok=<path> size=<n> crc=<hex>
fwabort [cmd] fwabort.ok
fdelete <path> [cmd] fdelete.ok=<path>
flist <dir> [cmd] flist.entry=<name> size=<n> for each entry; ends with [cmd] flist.done=<dir> entries=<n> truncated=<0\|1> max=64
fstat <path> [cmd] fstat.ok=<path> size=<n> crc=<hex>
fread <path> <off> <len> [cmd] fread.ok=<path> off=<o> len=<n> chunk=<n> crc=<hex>, then raw bytes
lget [cmd] lget.present=<0\|1> entries=<n> schema=<n> len=<n> crc=<hex>, then raw bytes when present
lapply <len> <crc32> [cmd] lapply.ok=applied <n> entries <n>
syncinfo [cmd] syncinfo.fs_total=<n> fs_used=<n> fs_free=<n>, [cmd] syncinfo.manifest=<0\|1> entries=<n> schema=<n>, [cmd] syncinfo.id=<12 hex digits>, [cmd] syncinfo.lapply=<capability>; ends with [cmd] syncinfo.fw=<firmware-version>

Sync sessions and a busy Fidget

While a USB sync is active, the Fidget holds off its own WiFi check-ins until about 10 seconds after the last sync command. If a write arrives while the Fidget is busy (for example, in the middle of a WiFi check-in), it waits up to 5 seconds for that to finish before replying sync.busy. Commands that move data count as use, so they keep the Fidget from its 60-second idle sleep. A transfer opened with fwrite that sees no data for 60 seconds is cancelled; a later fwdata then answers fwdata.nosession, and the transfer must start again with fwrite.

Letting a Fidget install updates over WiFi (test ring)

upd allow-unsigned on lets this Fidget install update images that have not been signed by Cyber Fidget. Official releases are signed and install over WiFi without it (see Signed updates); this switch is for a small test ring that tries unsigned builds, such as a fork's releases or your own. It covers only images with no signature. An image that carries a signature this Fidget cannot verify, or one made with a key it does not know, is refused even with this switch on. Other Fidgets show "Update it from the website for now" instead. The command can only be set through the USB cable; a network request cannot turn it on. The choice stays set across restarts and updates, until a reset to factory or an erase and reinstall clears it. Send upd slot to check: unsig_ok=1 means allowed, and unsig_ok=0 means off. Send upd allow-unsigned off to turn it off, then check again with upd slot. upd slot also shows the running and next-start update slots and does not change anything.

Commands only in test builds

These commands require a firmware build with CF_TEST_CLI. A normal build replies [err] unknown command: <command> for a test-only verb. Some test commands change device state, stored settings, or power rails.

Command and arguments Reply format
apps [cmd] apps.<index>=<name> for each compiled app
app [cmd] app.index=<index>, .name=<name>, .uptime_ms=<millis>
launch <name\|index> or launch <blob-id> [cmd] launch.ok=<index> or [cmd] launch.ok=blob path=<path>
soak <app> or soak off [cmd] soak=<app> or [cmd] soak=off
net [cmd] net.mode=<mode> and fields for that mode
heapstat [cmd] heapstat.free_int=<B> min_free_int=<B> largest_int=<B>
btstat [cmd] btstat.controller=<state> bluedroid=<state> wifi_mode=<n> free_int=<B> min_free_int=<B> largest_int=<B>
tlsprobe [url] [cmd] tlsprobe.started=1, then [cmd] tlsprobe.ok=<0\|1> state=<done\|failed\|timeout> err=<code> join_ms=<n> tls_ms=<n> get_ms=<n> http=<status> bytes=<n> heap_free_min=<B> largest_min=<B> heap_min_before=<B> heap_min_boot=<B> stack_size=<B> stack_hw=<B> url=<url>; start failures use [cmd] tlsprobe.error=<reason>
tlsalloc <psram\|internal> [cmd] tlsalloc.ok=<mode> psram_free=<B>
mic [cmd] mic.heap_free=... diagnostic lines, ending with [cmd] mic.released=1
wifi <ssid>\|<pass> [cmd] wifi.saved=<ssid>
wasmstat [cmd] wasmstat.* runtime fields
btn <index> press\|release\|tap [cmd] btn.press=<index>, [cmd] btn.release=<index>, or [cmd] btn.tap=<index> release_in_ms=120 followed by a release
sleep [cmd] sleep=requested
prompt <n> [timeout_ms] [cmd] prompt.open=<n> timeout_ms=<ms>, later [cmd] prompt.result=<index\|none>; errors: [err] prompt.usage=..., .busy=1, or .refused=not-menu
status [cmd] status.bar=<line\|->, .badge=<0\|1>, .glyph=<state> age=<label\|-> checkin_age_s=<n\|-> cached=<0\|1>, zero or more .item=<kind> pri=<0-2> sticky=<0\|1> attn=<0\|1> late=<0\|1> cached=<0\|1> text=<text>; ends with .count=<n>
status post <kind> [late] [cached] [text] [cmd] status.post=<kind> badge=<0\|1> or [err] status.post.refused=<kind>
status popup <kind> [late] [cached] [text] [cmd] status.popup=<kind> open=1, later .popup.result=<accept\|ignore> kind=<kind>; outside the menu .popup=<kind> open=0 routed=bar
status clear [kind] [cmd] status.clear=all or .clear=<kind> removed=<n>
status checkin <never\|seconds_ago> [cached] [cmd] status.checkin=never glyph=never or .checkin=<seconds_ago> cached=<0\|1> glyph=<state>
rail aux on\|off [cmd] rail.aux=<on\|off>
rail oled off\|on [cmd] rail.oled=<off\|on> note=<note>; after off, restart to restore the shared display and sensor bus
gauge hibrt force\|auto [cmd] gauge.hibrt=<force\|auto> hibernating=<0\|1>
gauge alert-min <V> [cmd] gauge.alert_min_v=<readback-volts>
uvlo simulate <mV> [cmd] uvlo.simulate=<mV> plausible=<0\|1> sleep_verdict=<shutdown\|resleep> runtime_verdict=<shutdown\|ok> sleep_threshold_mv=<mV> runtime_threshold_mv=<mV> debounce_ms=<ms>
cloud base <url> [cmd] cloud.base=<ok\|error>
cloud token <credential> [cmd] cloud.token=<ok\|error>; the credential is never echoed
cloud autoapply on\|off [cmd] cloud.autoapply=<ok\|error>
cloud check Later: [cmd] cloud.result=<ok\|none\|error> err=<code> applied=<batch\|-> offered=<fw\|-> next_ms=<n> heap_min=<B>
cloud interval <hours> [cmd] cloud.interval=<ok\|error>
cloud due <hours> [cmd] cloud.due=<ok\|error>
cloud guard <ms> [cmd] cloud.guard=<ok\|error>
cloud press [cmd] cloud.press=<ok\|error>
cloud ssid absent\|saved [cmd] cloud.ssid=<absent\|saved\|error>
cloud bootcheck on\|off [cmd] cloud.bootcheck=<on\|off\|error>; sets Check at start-up
cloud btafterwifi allow\|block [cmd] cloud.btafterwifi=<allow\|block\|error>
awake [cmd] awake.mode=<mode> stop=<stop> listen=<0\|1> worker=<0\|1> polls=<n> deliveries=<n> failures=<n> connected=<0\|1> ... with timers and memory fields
awake set off\|stay\|dev [until\|idle] [cmd] awake.set=<mode> stop=<stop>; applies an Awake & dev mode choice
awake idle\|safety <s>, awake battery low\|real, awake poll [cmd] awake.idle_ms=<ms>, .safety_ms=<ms>, .battery=<low\|real>, .poll=now; other bench verbs are listed by [cmd] awake.usage=...
heapmap [cmd] heapmap.task=<name> stack=<address> lines, [cmd] heapmap.largest=<B> free=<B>, a raw memory dump, then [cmd] heapmap.done=1
upd [cmd] upd.key=<key> type=<str\|u8\|u32\|i32> value=<value> per key; ends with [cmd] upd.done=<count>
upd offer <version> [source] [cmd] upd.offer=open version=<v> source=<source> or .offer=suppressed reason=<skipped\|not-newer\|invalid\|website-shown> version=<v>; errors use [err] upd.offer=<busy\|not-menu\|refused>. On a Fidget without a second update slot, it opens the website-update instruction instead; website-shown means that instruction was already shown for this version
upd install <version> [cmd] upd.install=restarting version=<v> or .install=refused reason=<no-update-slot\|unsigned\|version\|storage>
upd fault <name> [cmd] upd.fault=<name\|error>
upd seen-clear [cmd] upd.seen_clear=<count\|error>
reset factory confirm [cmd] reset.factory=start, then [reset] factory=done just before the Fidget restarts; or [cmd] reset.factory=refused reason=<Finish update first\|Update is starting\|radio-busy\|app-busy>. Runs the same erase as Settings > Reset to factory (see Update or reset your Fidget); use a bench Fidget
upd verify-test [cmd] upd.verify_test=<ok\|fail>; checks the built-in test signature against its test data, and that a changed digest is refused
link start [cmd] link.code=<code>, then [cmd] link.state=<state> lines; busy: [cmd] link.state=error reason=busy
link ok\|no\|clear\|keep [cmd] link.answer=<ok\|no\|clear\|keep>
link unlink [cmd] link.state=unlinked or .state=error reason=<code>
link status [cmd] link.status=linked:<yes\|no> account:<label\|-> fingerprint:<ok\|mismatch> previous:<yes\|no>
link forget [cmd] link.forget=<ok\|error>

For the precise limits and side effects of a test command, consult the firmware's SerialCli documentation before using it. In particular, upd fault and rail oled off can deliberately interrupt normal operation.