A measurement function in an experiment module takes physical parameters, drives devices in a sequence, and returns a structured result dict or dataclass.
Complete example: wavelength sweep
from __future__ import annotations
import time
from dataclasses import dataclass, field
from plesty.lib.device.composite_device import CompositeDevice
from plesty.lib.service import build_client
@dataclass
class WavelengthSweepResult:
wavelengths: list[int] = field(default_factory=list)
powers: list[float] = field(default_factory=list)
unit: str = "watt"
def wavelength_sweep(
instruments: CompositeDevice,
start_nm: int,
stop_nm: int,
step_nm: int = 1,
settle_ms: float = 200.0,
) -> WavelengthSweepResult:
"""Sweep laser wavelength and measure power at each step.
Args:
instruments: CompositeDevice with 'laser' and 'powermeter' attributes.
start_nm: Starting wavelength in nm.
stop_nm: Ending wavelength (inclusive) in nm.
step_nm: Wavelength step size in nm.
settle_ms: Settling time after each wavelength change in milliseconds.
Returns:
WavelengthSweepResult with wavelengths and corresponding power readings.
"""
result = WavelengthSweepResult()
wavelength = start_nm
while wavelength <= stop_nm:
instruments.laser.write("WAVELENGTH", wavelength)
time.sleep(settle_ms / 1000.0)
power = instruments.powermeter.query("POWER")
result.wavelengths.append(wavelength)
result.powers.append(float(power))
wavelength += step_nm
return result
Usage
from plesty.lib.service import build_client
from plesty.lib.device.composite_device import CompositeDevice
from plesty.scan_exp.measurements import wavelength_sweep
laser = build_client("tcp://laser-host:5555")
powermeter = build_client("tcp://pm-host:5556")
with CompositeDevice({"laser": laser, "powermeter": powermeter}) as instruments:
data = wavelength_sweep(
instruments,
start_nm=1020,
stop_nm=1080,
step_nm=2,
settle_ms=300.0,
)
print(f"Measured {len(data.wavelengths)} points")
print(f"Peak power: {max(data.powers):.3e} W at {data.wavelengths[data.powers.index(max(data.powers))]} nm")
Design principles
Return structured data, not raw strings
Use dataclasses or typed dicts for return values. This gives mypy something to check and lets downstream analyzers work with known types.
Keep measurement functions pure with respect to device state
Don't rely on the device being in a particular state at the start of a measurement — always set the state you need explicitly before reading.
Include settle time parameters
Physical instruments have settling times after setpoint changes. Make settle times explicit parameters (not hardcoded) so the caller can adjust for different hardware.
Type-annotate everything
Gate 4 (Data Layer Compliance) requires explicit return type annotations on all public methods. Gate 2 (Code Hygiene) checks mypy in strict mode.
# Wrong — missing return type
def wavelength_sweep(instruments, start_nm, stop_nm):
...
# Correct
def wavelength_sweep(
instruments: CompositeDevice,
start_nm: int,
stop_nm: int,
step_nm: int = 1,
settle_ms: float = 200.0,
) -> WavelengthSweepResult:
...