An experiment never holds an instrument. It holds its rig: one CompositeDevice with a client for each instrument's server, under a short name. In main.py, the rig is one line — each name, and the operations the experiment will call on it:

RIG = {
    "hwp": ["home_stage", "move_absolute"],  # the half-wave plate's stage
    "spec": ["acquire", "get_recent_file", "set_data_path"],  # the spectrometer
    "pm": ["measure_power"],  # the power meter
}

The scaffold builds the CompositeDevice from it; nothing else in the module names an instrument. The scaffold's example rig is one instrument called device, and three places still name it — replace each:

build_devices that use it: delete them;

instead, HWP_ADDRESS, SPEC_ADDRESS and PM_ADDRESS;

three on this one.

# setup.yaml, below benches:
devices:
  hwp:
    host_name: bench-local
  spec:
    host_name: bench-local
  pm:
    host_name: bench-local

Where each address comes from

Each name reads its server's address from _ADDRESS — HWP_ADDRESS, SPEC_ADDRESS, PM_ADDRESS:

started each instrument on its PC (see Run It from the Bench).

demo servers:

# .env
LOCAL_DATA=/Users/you/plesty-tutorial/data    # the shared data disk, as this PC sees it
HWP_ADDRESS=tcp://localhost:5552
SPEC_ADDRESS=tcp://localhost:5553
PM_ADDRESS=tcp://localhost:5551

LOCAL_DATA is the same folder the spectrometer writes into: the run's folder goes there, and every spectrum lands inside it. A ~ in it is this PC's home. Which folder a device may write in, and how a bench sets it, is in Benches, Fleets & the Share. The ports are the ones plesty-server printed. No address or path is ever in the code, so the same module runs in any lab.

The preflight

Before the first step, the rig connects to every server and checks that it answers and offers every operation RIG lists:

Setup: connecting devices
Preflight hwp @ tcp://localhost:5552: ok (SIM-APT (S/N: 55123456, firmware: 1.0.0))
Preflight spec @ tcp://localhost:5553: ok (Mock LightField Spectrometer (no hardware))
Preflight pm @ tcp://localhost:5551: ok (THORLABS,PM100D,MOCK0001,1.0.0)

A server that is not there stops the run by name before anything moves — Sub-device 'hwp' unreachable at tcp://localhost:5599 after 10.1 s — instead of at row 40. From a terminal it asks whether to try again (after you start the server) or stop. A device that the run can do without is declared with cli.optional("name"), and the operator may then skip it.

Calling an instrument

In the experiment, self.devices is the rig:

reached = self.devices.call("hwp", "move_absolute", angle)  # the angle it reached
self.devices.call("spec", "acquire")  # one spectrum, saved by the spectrometer
reading = self.devices.call("pm", "measure_power", averaging=10)  # {"power": <W>}

call(name, operation, …) is the same as calling the operation on that instrument's client, plus resilience: a server on another PC that does not answer in time is reconnected and the call tried again. Each call waits as long as the operation's declared budget — an exposure of the spectrometer waits longer than a move.

Next: Run Setup.