Skip to main content

Device bridge

The device bridge is a small program you run on the computer your instruments are wired to. It opens one outbound WebSocket to the Orca daemon, takes commands on it, drives the instrument through its driver, and sends the answer back on the same connection.

You need it when the instruments are not on the machine running the daemon, or when you want the vendor libraries to live next to the hardware in their own process. If you are running the quickstart in simulation on one machine, skip this page. Sim drivers are built in-process and no bridge is involved.

  Lab bench PC                                Daemon host
+-----------------------------+ +--------------------------+
| shaker (serial) | | Orca runtime |
| arm (TCP) | | scheduler |
| liquid handler (USB) | | |
| | | | GET /ws/devices |
| v | | ^ |
| device bridge ----------|--- wss --->|----------+ |
+-----------------------------+ outbound +--------------------------+

The bridge always dials out. The daemon never dials in, so the bench PC needs no inbound firewall rule.

Install

The device bridge ships as the orca-client package. It is not on PyPI, so install it from a clone. Python 3.10 or newer.

git clone https://github.com/Cheshire-Labs/orca-client.git
cd orca-client
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # Linux, macOS
pip install -e .

pip also installs cheshire-drivers, which holds the driver classes and the wire protocol, from GitHub at the release this version of the bridge pins.

From here on this page calls it the device bridge.

Configure

The bridge reads one JSON file. It defaults to config.json in the working directory, and --config points it somewhere else.

{
"client_id": "lab-workstation-1",
"site": "boston",
"lab": "molbio",
"workcell": null,
"platform": {
"url": "wss://your-deployment.example.com/ws/devices",
"api_key": "${ORCA_CLIENT_API_KEY}",
"heartbeat_interval": 30.0
},
"devices": []
}
FieldWhat it is
client_idA label for this bridge, written into its own logs.
site, labHow the daemon names the connection. It registers the bridge as <site>-<lab>-client.
workcellOptional extra label. null is fine.
platform.urlThe daemon's /ws/devices endpoint. Required.
platform.api_keySent as the X-API-Key header on the handshake. Required, and must not be empty.
platform.heartbeat_intervalSeconds between heartbeats. Defaults to 30.
platform.reconnect_backoffSeconds to wait before each redial, in order. The last value repeats. Each value must be above 0. Defaults to [1, 2, 4, 8, 16, 30]. See Reconnecting.
devicesThe list described in the next section.

platform.url and platform.api_key may contain ${VAR}, which is replaced from the environment when the file is read. Keep the key out of the file that way. If the variable is not set, the bridge stops and names it. No other field is expanded.

Four fields also have an environment fallback. The bridge uses the variable only when the config file does not set the field:

VariableFalls back for
ORCA_CLIENT_URLplatform.url
ORCA_CLIENT_API_KEYplatform.api_key
ORCA_CLIENT_HEARTBEAT_INTERVALplatform.heartbeat_interval
ORCA_CLIENT_RECONNECT_BACKOFFplatform.reconnect_backoff, as comma-separated seconds: 1,2,5

--env path/to/.env loads a .env file before the config is read. Without it the bridge uses the process environment.

Which URL

The scheme is checked when the bridge loads its config. ws:// is accepted only for localhost, 127.0.0.1 and ::1. Any other host must be wss://, and the bridge refuses to start otherwise.

The framework daemon binds 127.0.0.1 and picks a free port at start, so on a single machine the URL is ws://127.0.0.1:<port>/ws/devices. Pin the port with orca start --port 8765, or read the one it chose from orca status.

Authenticating

The daemon checks the X-API-Key header at the handshake. If the check fails, it refuses the connection before the socket opens, which reaches the bridge as HTTP 403. The bridge then stops and says the platform rejected the API key. It does not redial, because the same key will be refused again. The framework daemon ships an allow-all check, because a single node trusts its own machine, so any non-empty key works there. A hosted deployment installs a real check and the key must be the one it issued.

Declare your devices

Each entry in devices is one instrument. The name is the binding key: a bridge device named shaker_1 binds to a topology Shaker("shaker_1"). Get that wrong and the daemon refuses the connection.

"devices": [
{
"type": "shaker",
"name": "shaker_1",
"driver": {
"type": "plr",
"backend": "InhecoThermoShake",
"connection": { "type": "serial", "port": "COM3", "baudrate": 9600 }
}
},
{
"type": "transporter",
"name": "arm_1",
"driver": {
"type": "plr",
"backend": "PreciseFlex",
"connection": { "type": "tcp", "host": "192.168.1.100", "tcp_port": 10100 }
}
}
]

type is the device kind, one of shaker, centrifuge, sealer, transporter, translator, liquid_handler, plate_washer, reader, delidder, storage, waste, thermocycler.

driver.type is how the instrument is driven:

driver.typeDrives
plrA real instrument through a PyLabRobot backend, named in backend.
venusA Hamilton Venus protocol runner.
simA simulated instrument, for testing the wire with no hardware attached.

connection takes type of serial, tcp or usb, with port and baudrate for serial and host and tcp_port for TCP.

Two things you do not configure here. Teachpoints stay on the daemon: it resolves them and pushes the coordinates with each move command, so the bridge holds no local copy. And the bridge builds both a real driver and a sim driver for every device, then the daemon picks between them per run mode, so you do not switch modes in this file.

Run it

python -m orca_client --config config.json
FlagEffect
--config, -cConfig file path. Defaults to ./config.json.
--env, -eLoad this .env file before reading the config.
--verbose, -vDebug logging.
--versionPrint the version and exit.

The bridge connects, sends its device list, then waits. Devices are left uninitialized on purpose. The daemon calls initialize when it is ready, so it owns the timing.

Confirm the device connected

Ask the daemon, not the bridge:

orca device registry list

The client column says whether this bridge is reachable. The device column says whether the link is open on the driver being driven. They differ: a device you have taken by hand sits under a bridge that is still heartbeating. link names which driver answered, and modes shows which run modes the device is eligible for now that its capabilities are known.

orca device list is the shorter view of the same runtime: name, kind, busy, initialized, mode and fault, for every device the topology declares. Use it once you know the bridge is up.

Advanced

Read this when a connection is refused, or when you are working out what happens to a run while a bridge is away.

Protocol version

The bridge and the daemon both take the wire protocol from cheshire-drivers, currently version 1.3.0. The bridge stamps it on its connect message and the daemon compares it against its own before parsing anything else.

A mismatch is fatal and immediate. The daemon closes the socket with code 1002 and the reason Incompatible protocol version: expected <version>. There is no negotiation and no compatibility window, so the bridge and the daemon must come from matching builds. Upgrade one, upgrade the other.

Reconnecting

A dropped link is retried automatically. Before each redial the bridge waits the next value in platform.reconnect_backoff, and repeats the last value once it runs out. With the default that is 1, 2, 4, 8, 16, then 30 seconds between every later attempt. It keeps trying until you stop it.

The list starts again from the first value after any connection that opened, whether it closed cleanly or dropped. So a daemon restart is followed by a 1 second wait, however many restarts came before it.

The bridge stops instead of redialing when the daemon refuses it for a reason a retry cannot fix: a rejected API key (see Authenticating), a protocol version mismatch, a device claimed by another bridge, or a driver the topology contradicts (see One instrument, one bridge). The last three close the socket with code 1002, and the bridge stops on every 1002 close. The close reason names what to fix.

On reconnect the daemon assumes nothing survived. A returning bridge may have rebuilt its drivers from scratch, so the runtime forgets what it had cached about that device's bring-up, both the driver's own initialized flag and the system's bring-up record. The next explicit orca device initialize or connect then actually reaches the instrument instead of being skipped as already done.

The runtime also marks an observation gap on every piece of labware sitting at that device, because nobody was watching while the link was down, and it re-reads the deck if the device is a liquid handler. That re-read is a background task, not part of the handshake. What it does not do is move anything. Homing and bring-up stay behind their own verbs.

A command that was in flight when the link dropped leaves a device fault on the device. The workflow will not drive a faulted device. Look at the instrument, put it right, then clear the record with orca device clear-fault <name>.

One instrument, one bridge

Device names are claimed at the handshake. If a name in the connect message is already held by another live bridge, the daemon refuses the newcomer and closes it with code 1002 and a reason naming the device and its current owner.

This is deliberate and it is not a handover. Two bridges wired to one instrument is a misconfiguration: both sockets would stay open, both would believe they own the device, and commands would route to whichever registered last. Stop the incumbent bridge first, then start the replacement.

The same handshake also re-checks the advertised drivers against the topology. A bridge whose device advertises a contract the topology contradicts, because someone swapped a driver class on the bench PC, is closed with 1002 and the offending device named.

Taking a device by hand

To drive an instrument yourself while a workflow is running, take it out of the workflow's reach rather than stopping the bridge:

orca device take-control arm_1 --reason "re-teaching the hotel"
orca device release-control arm_1

A thread that tries to use a held device, or to move labware into or out of it, fails until you release it. Without this a workflow can start a move into the device between two of your own commands. The bridge stays connected throughout, which is why orca device registry list can show client true and device false at the same time.

See also

  • Devices for the device classes your workflow calls and how capabilities are decided.
  • Topology for declaring the devices the bridge binds to.
  • Transporters for arms, translators and how moves are planned.
  • Simulation hierarchy for what each run mode drives, and which ones never reach the bridge.
  • CLI: inspect for the rest of the orca device verbs.