-

Please Enter Your Search
search icon
Nothing found for your search
Search results from other manuals
Nothing found for your search

1 Live state

The device pushes changes to connected clients over a Server-Sent Events stream at GET /api/subscribe. Anything beyond fire-and-forget submission wants it: polling /api/status in a loop works, but you will either poll too often or find out too late that the machine has been waiting for someone for twenty minutes.

1.1 Prime first then subscribe

The stream carries changes, not state. A machine that is sitting idle sends nothing at all — no snapshot on connect, no heartbeat. So a client that only subscribes knows nothing until something happens.

Start with GET /api/init, which returns everything in one request:

curl --insecure -b cookies.txt https://kilobaser.lab.example.org/api/init
Key What it holds
status the machine: mode, current run, pending questions, consumables
processRunQueue the queue, in the order the machine will run it
processRuns recent runs
errors errors currently active
ccSettings cartridge and chip catalogue, including the compatibility matrix
settingsUser oligo yield and system information
session who you are logged in as
sensorState raw sensor presence flags

Then open the stream and apply what arrives on top.

Strictly, open the stream first and fetch /api/init immediately after, so a change landing between the two is not lost. An idle machine makes the naive order work almost always, which is exactly what makes the resulting bug so annoying to find.

1.2 The events

Event Payload How to apply it
status the whole machine status replace
processRunQueue the entire queue, in order replace
processRuns one run merge by id
errors all currently active errors replace
runs one completed run record merge by id
settingsUser settings subtree replace that subtree
clientControl interface commands, e.g. reload ignore in a headless client

The two collection events do not mean the same thing, and this is the mistake worth avoiding: processRunQueue is a complete replacement while processRuns is a single object. Merging the queue instead of replacing it produces a client whose queue slowly fills with entries the machine has already discarded, because the machine does discard them — see queue oligos.

An empty collection may arrive as null rather than []. Coerce it.

The wire format is ordinary SSE: an event: line, one or more data: lines carrying JSON, and a blank line to finish. The device never sets id: or retry:.

1.3 Applying events

The whole of it, from kb_client.py:

if name == "status":
    state["status"] = data
elif name == "processRunQueue":
    state["queue"] = data or []          # whole queue, replace
elif name == "processRuns":
    state["runs"][data["id"]] = data     # single run, merge by id
elif name == "errors":
    state["errors"] = data or []

That mirror is what the rest of an integration reads. Do not re-fetch /api/status when an event arrives; the event already carries the new state, and a device with several clients connected does not need the extra traffic.

1.4 Silence is not disconnection

There is no heartbeat, so an open stream and a dead stream look identical from the client's side. Two consequences.

A proxy will eventually close it. Anything between you and the Kilobaser that times out idle connections — a load balancer, an SSH tunnel, a corporate firewall — will drop a stream that has been quiet for an hour, and neither end will say so. Plan to reconnect.

Reconnecting loses whatever happened while you were away. The device sends no event ids, so there is nothing to resume from and no replay. After every reconnect, fetch /api/init again and rebuild from the snapshot. kb_client.subscribe() does this and emits a synthetic init event each time it connects, so downstream code cannot forget.

Reconnect on a backoff rather than immediately: a device that is rebooting will refuse connections for a while, and a tight loop against it is unhelpful to everyone.

1.5 Sessions end when the device restarts

Sessions live in the device's memory. A reboot or a software update ends all of them, and your stream stops with a 2-1-1 or 2-1-2 error rather than anything resembling a network failure. Log in again and reconnect. kb_client.py does this automatically.

A restarting device does not come back all at once, so a client that reconnects promptly meets a 503 before it gets as far as logging in:

{"error": "{\"code\": \"1-1-37\", \"message\": \"The machine is starting up. Please wait.\"}", "state": "starting"}

It carries Retry-After and a state of starting or migrating. Honor the header rather than hammering: a device that is migrating its database can take a while, and this is the one status where the device tells you exactly how long to wait.

While you are here: a restart during a run also leaves an active error, 1-2-24 Unexpected power outtage. It is not fatal, but it blocks other operations until it is acknowledged — see cancel and errors.

1.6 A browser cannot open this stream

The REST endpoints send permissive cross-origin headers, so a web page on another domain can log in and queue oligos. /api/subscribe sends no cross-origin headers at all, so an EventSource pointed at it from another origin will fail.

There is no header or parameter that changes this. If you want live updates in a web application, hold the stream in your backend and relay to your frontend however you normally would. That is the right shape for other reasons too — see overview.

1.7 Waiting for something specific

Most of what an integration wants is "block until X", and with a live mirror that is a predicate:

kb.wait_for(lambda s: not s["queue"])                          # queue drained
kb.wait_for(lambda s: s["status"]["currentProcessRunId"] == "")  # nothing running
kb.wait_for(lambda s: checkpoint(s))                            # machine wants an answer

checkpoint() is worth a closer look, because the obvious version of it is wrong:

status = state["status"]
pending = status.get("pendingAnswers") or []
mode = status["currentState"]["mode"]
if mode == "check" and pending:
    return pending

Both conditions have to hold. Pending answers can linger for a moment while the machine is still working, so testing the list on its own will have you answering a question the machine has not finished asking. Waiting for mode == "check" as well is the difference between a driver that works and one that works most of the time.

1.8 Watching it happen

python3 kb_watch.py --host kilobaser.lab.example.org --user apibot --insecure --queue

Read-only, so it is safe against a machine in the middle of a real run. Leave it in one terminal while you make calls in another — it is the fastest way to understand what the device actually broadcasts, and when.

00:14:11  init             mode=ready  cartridge=Standard Cartridge (150 bases left)  queue=0
00:17:24  processRunQueue  mode=ready  queue=6
           1. LAMP-042_RPP30-LB   insertChip -> synthesis
           2. LAMP-042_RPP30-LF   insertChip -> synthesis