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 ofbuild_devicesthat use it: delete them; - the
DEVICE_ADDRESSentry ofMANIFEST: oneEnvVarper instrument instead,HWP_ADDRESS,SPEC_ADDRESSandPM_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 <NAME>_ADDRESS — 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.