Skip to content

zoompilotjetlinkwiki

Control protocol

How the apps and the web page talk to the server. There is no socket: each runs the server in its own process and uses ServerController (in JetlinkKit/Sources/JetlinkServer) directly.

Who How
Mac and iPhone apps Swift calls: handle(_:) runs a ControlCommand, events delivers each ControlEvent
Android app Each command as a JSON object through JNI (Native.command), a JSON reply back; the screens draw a snapshot built from the events
Web page (Jetson, PC) Relays events to browsers as Server-Sent Events; a signed-in page sends some commands as JSON over HTTP

It has no version number: every caller is built with the server it talks to. The JSON below is what ControlEvent.jsonLine() writes and the page streams: one object per line, snake_case keys, sorted. An optional field with no value is left out, and a reader takes a missing key as null; the examples below show such fields as <value>|null.

The web page

jetlink-server --status-port P --web-auth FILE (the installed service uses 5600 and /etc/jetlink/web-auth.json) serves a page to any browser on the network. With the password file, everything but the page and the sign-in needs the sign-in cookie; without it the page is read-only and the /api/ controls answer 403.

Path What
GET / the page
GET /api/session {"auth": "none"\|"required", "signed_in"}; renews a cookie over a day old
POST /api/login {"password"}: sets the cookie, or 401; 429 with retry_after while held up
POST /api/logout clears the cookie on this browser
POST /api/logout-all a new signing key: every other browser signs in again
POST /api/password {"current", "new"}: a new password and key, a new cookie for this browser
GET /events Server-Sent Events: on connect the latest hello, server, link, engine, inventory, catalog, download and benchmark, the last two minutes of stats, then host and one hw a second while a page is open
GET /logs the server's last 300 log lines, as text
POST /api/command one of the commands below: status, catalog, download, cancel_download, prepare, forget, inventory, benchmark, cancel_benchmark; the reply's ok false is a 409
GET /api/system settings: the installer's answers that apply here, in its words; the version, keep-awake, the last run
POST /api/settings {"power": "always"\|"switched", "comma_poweroff": "yes"\|"no", "desktop": "on"\|"off", "autostart": "yes"\|"no"}, only those that apply: runs jetlink setup --set ...
POST /api/action {"action": "restart"\|"reboot"\|"poweroff"\|"update"\|"check_update"\|"keep_awake", "seconds"}
GET /api/task the settings or update run: kind, state (running, done, failed, lost), exit, its last lines
  • Cookie: jetlink_session, HttpOnly, SameSite=Strict, 400 days. It is an expiry, a nonce and an HMAC-SHA256 under the file's key, so the server keeps nothing and a restart signs nobody out.
  • Password file: root's, 0600: PBKDF2-HMAC-SHA256 (210,000 rounds) of the password, its salt, and the 32-byte signing key. The server reads it again when it changes. jetlink-server web-password --file F [--generate] writes one.
  • Guessing: five tries an address, then a wait that doubles up to five minutes; two password checks a second at most from everyone together, one at a time.
  • Other sites: a POST needs Content-Type: application/json, the header X-Jetlink: 1 (no other site can send it without a preflight, which is never answered) and, when the browser sends one, an Origin naming this host.
  • Driving: while the comma is connected and frames came in the last 10 s, prepare, download, forget, benchmark, settings, updates, restarts, reboots and power-off answer 409.
  • Runs: settings and updates run the installer as the transient unit jetlink-web-task (systemd-run, nice 10), outside the server's own unit, since they restart it. Its output and exit status are in /run/jetlink-web/. Keep-awake is jetlink caffeinate -t as jetlink-web-awake, so it outlasts a restart too; Check for updates is jetlink update --check.
curl -c jar -H 'X-Jetlink: 1' -H 'Content-Type: application/json' \
  -d '{"password": "<password>"}' http://<name>.local:5600/api/login
curl -b jar -N http://<name>.local:5600/events

host (hostname, board, OS, kernel, GPU) and hw (CPU, memory, GPU, temperatures, power, fan, disk) come from the Linux host; other hosts leave them out.

Events

{"event": "<name>", "t": <unix time, float seconds>, ...}. Placeholders in angle brackets.

{"event":"server","t":0,"state":"serving","detail":"","backend":"trt",
 "runtime_version":"10.16.2.10","device":"Orin-sm87"}
// state: "serving" | "stopping". backend, runtime_version and device are what
// the hello to the comma carries.

{"event":"link","t":0,"state":"connected","detail":"","peer":"usb","medium":"usb3","client":"modeld"}
// state: "waiting" | "connected" | "disconnected". medium, when connected:
// "usb3" | "usb2" | "usb1" | "usb" (speed unknown) | "tcp", from the comma's
// hello (a phone's cable is TCP over USB). client: the hello's name, "modeld"
// while driving or "provision" fetching the model; absent before a hello.
// Transitions only.

{"event":"engine","t":0,"state":"building","sha256":"<sha256>","detail":"",
 "stage":"build","frac":0.42,"msg":"","load_only":false}
// state: "none" | "building" | "loading" | "ready" | "failed". Every state
// change, and progress at most 4 times a second.

{"event":"stats","t":0,"frames":20,"fps":19.9,"slow":0,"window_s":1.0,
 "total_ms":{"mean":17.0,"p99":17.4,"max":18.1},"served_ms":{"mean":17.4,"p99":17.9,"max":18.6},
 "gpu_ms":{"mean":15.7},"stages_ms":{"queue":0.5,"gpu":15.7,"other":0.8,"send":0.4}}
// Once a second while frames arrive. total_ms: arrival to reply ready;
// served_ms adds the send; stages_ms are means that add up to served_ms.mean.
// slow: frames whose total was over 60 ms.

{"event":"inventory","t":0,"loaded":"<sha256>|null","last_loaded":"<sha256>|null",
 "models":[{"sha256":"<sha256>","bytes":765953504,"path":"<cache>/models/<sha16>.onnx",
            "name":"Cinque Terre V3 Model","ref":"<ref>|null"}],
 "artifacts":[{"sha256":"<sha256>","key":"<sha16>.ort1.29.0.coreml-Apple_M1_Pro",
               "path":"<cache>/engines/<sha16>.ort1.29.0.coreml-Apple_M1_Pro.ortcache",
               "bytes":2300000000,"backend":"ort","runtime_version":"1.29.0","device":"coreml-Apple_M1_Pro",
               "built_at":"<ISO 8601>|null","build_seconds":8.2,"checkpoint":"<id>|null","current":true}],
 "disk":{"models_bytes":765953504,"engines_bytes":2300000000,"free_bytes":120000000000}}
// A Mac's. current: this server would load that artifact. After builds,
// loads, downloads, imports, forget, and the inventory command.

{"event":"catalog","t":0,"fetched_at":1757440000.0,"url":"<catalog url>",
 "default_ref":"<ref>","error":"<why>|null",
 "models":[{"name":"Cinque Terre V3 Model","short_name":"CTV3M","ref":"<ref>",
            "build_time":"<ISO 8601>","index":13,"sha256":"<sha256>|null","bytes":765953504}]}
// Newest first. sha256 and bytes are null until that ref's pointer is
// resolved. error is set when a refresh failed and this is the cached list.

{"event":"download","t":0,"sha256":"<sha256>","ref":"<ref>|null","state":"progress",
 "frac":0.42,"bytes":321000000,"total":765953504,"rate_bps":41000000.0,"detail":"","source":"<url>"}
// state: "started" | "progress" (at most 4 a second) | "done" | "failed" | "cancelled".

{"event":"import","t":0,"path":"<file>","state":"hashing","frac":0.3,"sha256":"<sha256>|null","detail":""}
// state: "hashing" | "copying" | "done" | "failed".

{"event":"benchmark","t":0,"state":"running","elapsed":12.0,"total":60.0,"frames":240,
 "frame":{...},"report":{...}|null,"detail":""}
// state: "running" | "done" | "cancelled" | "failed"; report when done.

{"event":"shutdown_request","t":0,"reason":"<why>"}
// The comma asked a phone or Mac to power off; the apps refuse and say so.

{"event":"hello","t":0,"version":"0.7.0"}
// The web page's first event: the server's version, as
// `jetlink-server --version` prints it.

Commands

{"cmd": "<name>", ...arguments}, answered by one reply: {"ok": true, ...extras} or {"ok": false, "error": "<sentence>"}.

cmd arguments reply extras behaviour
status re-sends server, link, engine, inventory, catalog
catalog refresh (default false) queued: true fetches the catalog if asked, or if the cache is missing or over an hour old, resolves missing pointers, then emits catalog
download ref or sha256 sha256 queues a download (one at a time, in order); download events follow
cancel_download sha256 cancels it and removes the .part
import path queued: true hashes and copies the file in; import events, then inventory
prepare sha256, frame_skip (default 4) state; sha256 when downloading builds if needed and loads it. With no model file or engine on disk it downloads first and replies state: "downloading"
unload releases the loaded engine
forget sha256, artifacts (default true), model (default false) unloads it if loaded, deletes its engines and/or download, then inventory
inventory emits inventory
benchmark seconds (default 60, up to 3600) queued: true runs the loaded model at the comma's pace with no comma; benchmark events. Refused while a comma is connected
cancel_benchmark stops it
shutdown emits server with stopping; the app stops the server

From the Jetlink documentation.

This is experimental software. You drive the car, follow the law, and carry all the risk. Read the safety page first.

zoom-zoom-zoom · by Zeph Leggett