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:
- in
main.py, the--device-addressoption and the two lines of
build_devices that use it: delete them;
- the
DEVICE_ADDRESSentry ofMANIFEST: oneEnvVarper instrument
instead, HWP_ADDRESS, SPEC_ADDRESS and PM_ADDRESS;
setup.yaml, which says which bench runs each instrument: on one PC, all
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 — HWP_ADDRESS, SPEC_ADDRESS, PM_ADDRESS:
- From PLESTY Bench — a run started there gets them from the bench, which
started each instrument on its PC (see Run It from the Bench).
- By hand — in
.env, for servers you started yourself, like the three
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.