Skip to content
This is the development version of the documentation. It may change before the next release. See 0.14.x for the latest release.

Client

Client is the object that sends commands and reads state.

let mut client = Client::open(&geometry, &option).await?;
client.send(Clear).await?;
client.send(Synchronize).await?;
Parameter Type Description
geometry &Geometry Physical layout of the devices
option &TransportOption Connection settings. See TransportOption
let num_devices = client.num_devices();
let now = client.device_time_now()?;
let firmware = client.read_firmware_version().await?;
client.send(Nop).await?;
client.send_streaming(Nop).await?;
let state = tokio::time::timeout(Duration::from_millis(200), client.read_state()).await??;
let fpga_state = state.replies().iter().map(|r| r.fpga_state).collect::<Vec<_>>();
Method async Returns Description
num_devices() — usize Number of connected devices
geometry() — &Geometry Physical layout of the devices given at open time
read_firmware_version() ✓ Result<Vec<FirmwareVersion>, Error> CPU/FPGA firmware version of each device
device_time_now() — Result<SysTime, Error> Current time of the devices (see SysTime)
send(cmd) ✓ Result<(), Error> Send a command, confirming the response frame by frame
send_streaming(cmd) ✓ Result<(), Error> Send a command without waiting for each response, then confirm the responses of every frame (see streaming)
read_state() ✓ Result<Response, Error> Send one liveness check to every device and return their replies (see State Monitoring)

With heartbeat_interval in TransportOption, the Client keeps sending liveness checks at that interval (see Failsafe). By default it sends none.

There is no function that stops the devices. The socket closes, and the liveness checks stop, when the Client is dropped in Rust, on Dispose() in C#, and on leaving the with block in Python. Nothing is sent to the devices. The devices keep their last output after the client is closed, so stopping the output is the user’s responsibility.

send waits for the response frame by frame (stop-and-wait). send_streaming keeps sending without waiting and confirms the responses from the oldest frame. Use it to send commands of many frames, such as Modulation or an STM, quickly.

client
.send_streaming(Modulation::new(SamplingConfig::FREQ_4K, &modulation))
.await?;

read_state gathers the replies of every device into a Response.

Response Description
replies() DeviceReplys in device-number order
device_status() State of each device built from the flags of its reply (see State Monitoring)
check() Return the error of the lowest-numbered device that replied with an error
Field of DeviceReply Description
device Number of the device that replied
msg_id Which liveness check the reply answers
status Error code (0 = success)
flags State flags of the device
unit_id ID of the device
sys_time Device time [ns] (0 = not carried)
telemetry Telemetry
fpga_state FpgaState
data Value of a read command

FPGA state flags

Method Type Description
is_thermal_asserted() bool Whether the thermal sensor is over the threshold
current_mod_bank() ModulationBank Current Modulation bank
current_pattern_bank() PatternBank Current Pattern bank (shared by Pattern and STM)
is_pattern_mode() bool Whether a Pattern is being output
is_pattern_stopped() bool Whether Pattern / STM playback has stopped
is_mod_stopped() bool Whether Modulation playback has stopped
is_transition_pending() bool Whether a bank switch is waiting to take effect

CPU diagnostic counters

Field Type Description
fifo_drop u32 Frames dropped because the receive FIFO was full (a dropped frame gets no reply)
exec_late u32 Frames whose processing started 1 µs or more after their processing time
sync_resync u8 Number of hardware resynchronizations of the sync input (reset to 0 by Synchronize)
cpu_watchdog u8 Number of times the CPU watchdog fired (kept until the FPGA is reconfigured)
boot_failure u8 Number of steps that failed at boot (not cleared by Clear)

each takes a closure that receives a device and assigns a different command per device.

client
.send(each(|device| {
Some(if device.idx() % 2 == 0 {
Pattern::new(&left, Intensity::MAX)
} else {
Pattern::new(&right, Intensity::MAX)
})
}))
.await?;

In Rust, the return values of the closure must all be the same type. To mix commands of different types, unify them into a BoxedCommand with .boxed().

client
.send(each(|device| {
Some(if device.idx() % 2 == 0 {
Box::new(Pattern::new(&left, Intensity::MAX)) as Box<dyn Operation>
} else {
Box::new(SetSilencer::default())
})
}))
.await?;