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?;client = await Client.open(geometry, TransportOption())await client.send(Clear())await client.send(Synchronize())var client = await Client.OpenAsync(layout, new TransportOption());await client.SendAsync(new Clear());await client.SendAsync(new Synchronize());| 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<_>>();num_devices = client.num_devices()now = client.device_time_now()
firmware = await client.read_firmware_version()
await client.send(Nop())await client.send_streaming(Nop())
state = await asyncio.wait_for(client.read_state(), timeout=0.2)fpga_state = [r.fpga_state for r in state.replies]var numDevices = client.NumDevices;var now = client.DeviceTimeNow();
var firmware = await client.ReadFirmwareVersionAsync();
await client.SendAsync(new Nop());await client.SendStreamingAsync(new Nop());
using var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));var state = await client.ReadStateAsync(timeout.Token);var fpgaState = state.Replies.Select(r => r.FpgaState).ToArray();| 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.
streaming
Section titled “streaming”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?;await client.send_streaming(Modulation(SamplingConfig.FREQ_4K, data))await client.SendStreamingAsync(new Modulation(SamplingConfig.Freq4k, modulation));Response and DeviceReply
Section titled “Response and DeviceReply”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 |
FpgaState
Section titled “FpgaState”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 |
Telemetry
Section titled “Telemetry”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?;await client.send(each(lambda device: Pattern(left if device.idx() % 2 == 0 else right, Intensity.MAX)))await client.SendAsync(Command.Each(device => device.Idx % 2 == 0 ? new Pattern(left, Intensity.Max) : new Pattern(right, Intensity.Max)));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?;