Workspace and devices
A workspace is one working session: a device, a capture, the decoders attached to it and its annotations. Every other API in this reference hangs off it.
- Python
- NodeJS
- Rust
- C
# Saving and exporting are on the workspace itself, not a sub-API.
workspace.save(...)
workspace.export_csv(...)
workspace.capture # capture control
workspace.data # transitions, packets, hex
workspace.decoders # protocol decoders
workspace.markers # markers
workspace.measures # measurements
workspace.pattern # pattern generator
workspace.generation # generation rows and builders (SQ family)
workspace.events # the event stream and the log
workspace.capture;
workspace.data;
workspace.decoders;
workspace.markers;
workspace.measures;
workspace.pattern;
workspace.generation;
workspace.events;
workspace.capture();
workspace.data();
workspace.decoders();
workspace.markers();
workspace.measures();
workspace.pattern();
workspace.generation();
workspace.events();
/* C has no sub-APIs: every function takes the workspace handle. */
ss_capture_run(workspace, /* ... */);
ss_data_transitions(workspace, /* ... */);
id and device_name​
- Python
- NodeJS
- Rust
- C
workspace.id # the id attach() takes
workspace.device_name # "SE254 (demo)" or "SP259 (sn 1004250000604)"
workspace.id;
workspace.device_name;
workspace.id(); // u64
workspace.device_name(); // String
uint64_t id = ss_workspace_id(workspace);
char *name = ss_workspace_device_name(workspace); /* "SE254 (demo)", "SP259 (sn ...)" */
if (name) { printf("%s\n", name); ss_string_free(name); }
channels​
The device's channels, in order. Index in this list is the channel number every other call takes.
- Python
- NodeJS
- Rust
- C
for index, channel in enumerate(workspace.channels):
print(index, channel.name, channel.ch_label, channel.color)
workspace.channels.forEach((channel, index) => {
console.log(index, channel.name, channel.ch_label, channel.color);
});
for (index, channel) in workspace.channels().into_iter().enumerate() {
println!("{index} {} {}", channel.name, channel.ch_label);
}
/* A JSON array of {name, color, ch_label}, in device order. The index into
it is the channel number every other call takes. */
char *channels = ss_workspace_channels(workspace);
if (channels) {
printf("%s\n", channels);
ss_string_free(channels);
}
Example output. The four channels of an SE254: index, signal name, probe label, colour:
ch 0 UART TX CH1 #006EF5
ch 1 UART RX CH2 #FFC139
ch 2 I2C SCL CH3 #D80000
ch 3 I2C SDA CH4 #00E700
The index is the channel number every other call takes; CH1…CH4 is what is
printed on the probe, and the colour matches the physical wire.
Fields: name (the signal name), ch_label (the label printed on the
probe), color.
channel​
Looks a channel up by name and returns its number. Matches the signal name or the probe label, case-insensitively.
- Python
- NodeJS
- Rust
- C
scl = workspace.channel("I2C SCL") # -> 2
# Raises KeyError if the device has no such channel.
const scl = workspace.channel('I2C SCL');
// Throws RangeError if the device has no such channel.
// Errors if the device has no such channel.
let scl: u16 = workspace.channel("I2C SCL")?;
int32_t scl = ss_workspace_channel(workspace, "I2C SCL"); /* -1 if there is none */
Looking channels up by name is what lets one test script run on an SE254, an
SP259 and an SP1000G without an if per device. Hard-coded channel numbers
break when the bench changes.
attach_device​
Accepts the device the server offered after a .scana was opened, so a
file-loaded workspace can capture again. It takes the device's serial number,
without the hw: prefix.
- Python
- NodeJS
- Rust
- C
workspace.attach_device("1004250000604")
workspace.attach_device('1004250000604');
workspace.attach_device("1004250000604")?;
ss_workspace_attach_device(workspace, "1004250000604");
device_info​
Everything the attached device says about itself: hardware and firmware versions, temperatures, PLL / DDR / HyperRAM health, the probes it sees.
- Python
- NodeJS
- Rust
- C
info = workspace.device_info() # timeout defaults to 60 s
for row in info["rows"]:
print(row)
// Opens the device, so it fails while a capture is running.
const info = await workspace.device_info(); // timeout_ms = 60_000
// Opens the device, so it fails while a capture is running.
let info = workspace.device_info().await?; // serde_json::Value
/* Opens the device, so it fails while a capture is running. */
char *info = ss_workspace_device_info(workspace);
if (info) {
printf("%s\n", info);
ss_string_free(info);
}
Reading device info opens the hardware, so it fails while a capture is running. Ask for it before you start, or after it is done.
Use ScanaStudio to update a device's firmware. The hardware and firmware
versions are in device_info() above.
duplicate​
Copies the workspace on the server, with its data, decoders and annotations, and returns the id of the copy. The copy has no file. This connection stays on the original; attach another connection to the new id to drive the copy.
- Python
- NodeJS
- Rust
- C
copy_id = workspace.duplicate()
with ScanaStudio.connect() as other:
copy = other.attach(copy_id)
const copy_id = await workspace.duplicate();
const other = await ScanaStudio.connect();
const copy = await other.attach(copy_id);
let copy_id: u64 = workspace.duplicate().await?;
let other = ScanaStudio::connect(DEFAULT_URL).await?;
let copy = other.attach(copy_id).await?;
uint64_t copy_id = ss_workspace_duplicate(workspace); /* 0 on failure */
ss_server *other = ss_server_connect(NULL);
ss_workspace *copy = ss_server_attach(other, copy_id);
generation​
The generation rows of the SQ family (SQ25, SQ50, SQ100, SQ200). Each channel
has a row beside its capture that holds what the next generation replays. A
capture never overwrites it. Reached through workspace.generation.
A row is filled in one of three ways: with fill, by a builder script, or by
drawing in the ScanaStudio window. A capture started with
operation="generation" or "mixed" then replays the rows of the channels set
to output in channel_io (see the
capture options).
The layout bounds what is replayed: a longer row is truncated, a shorter one
holds its last level.
Generation and mixed operation are started from Python and NodeJS only. Rust
and C can prepare the rows and builders, but their capture request has no
operation field.
Layout and fill​
- Python
- NodeJS
- Rust
- C
gen = workspace.generation
# The generator's share of the memory, and the rate the rows are timed against.
gen.set_layout(samples=100_000, sample_rate=1_000_000)
gen.fill(0, "high") # "capture" (the channel's last acquisition), "high" or "low"
state = gen.state # GenerationState
state.samples, state.sample_rate
for row in state.channels:
print(row.channel, row.last_sample) # how far each row holds content
list(gen.transitions(0)) # Edge records, as data.transitions() returns
gen.level_at(0, 10) # 1, or None while the row holds nothing
# Replay channel 0 while capturing the others.
workspace.capture.run(
samples=100_000, sample_rate=1_000_000,
operation="mixed", generator_samples=100_000,
channel_io=["output", "input", "input", "input"],
)
const gen = workspace.generation;
await gen.set_layout(100_000, 1_000_000);
await gen.fill(0, 'high'); // 'capture', 'high' or 'low'
gen.state.samples;
gen.state.channels; // [{ channel, last_sample }, ...]
for await (const edge of gen.transitions(0)) { /* ... */ }
await gen.level_at(0, 10); // 1, or null while the row holds nothing
await workspace.capture.run({
samples: 100_000, sample_rate: 1_000_000,
operation: 'mixed', generator_samples: 100_000,
channel_io: ['output', 'input', 'input', 'input'],
});
use scanastudio_client::FillSource;
let generation = workspace.generation();
generation.set_layout(100_000, 1_000_000).await?;
generation.fill(0, FillSource::High).await?;
let state = generation.state(); // GenerationState
let edges = generation.transitions(0, 0, None).await?; // None reads to the layout's extent
let level = generation.level_at(0, 10).await?; // Option<u8>
ss_generation_set_layout(workspace, 100000, 1000000);
ss_generation_fill(workspace, 0, "high"); /* "capture", "high" or "low" */
char *state = ss_generation_state(workspace); /* JSON: samples, sample_rate, channels, builders */
if (state) { printf("%s\n", state); ss_string_free(state); }
ss_edge edges[256];
ptrdiff_t n = ss_generation_transitions(workspace, 0, 0, -1, edges, 256); /* -1: to the extent */
Builders​
A builder is a script that writes generation rows, such as pwm.js. Adding one
runs it, and the rows of the channels its configuration names are rewritten
when it ends. The calls return once the run has finished; a script that fails
raises ScriptError.
- Python
- NodeJS
- Rust
- C
gen.builder_options("pwm.js") # what it takes, without attaching it
builder = gen.add_builder("pwm.js", {"channel": 2, "simple_freq_val": 10_000})
builder.instance_id, builder.name # 1, "PWM Builder on CH 3"
builder.channels # [2], the rows the last run wrote
builder.status # "building", then "built" or "error"
gen.builder_config(builder.instance_id) # current configuration
gen.update_builder(builder.instance_id, {"simple_freq_val": 20_000})
gen.rebuild_builder(builder.instance_id) # run again with the current layout
gen.builders # every builder attached
gen.remove_builder(builder.instance_id) # the rows keep their content
const builder = await gen.add_builder('pwm.js', { channel: 2, simple_freq_val: 10_000 });
await gen.builder_config(builder.instance_id);
await gen.update_builder(builder.instance_id, { simple_freq_val: 20_000 });
await gen.rebuild_builder(builder.instance_id);
gen.builders;
gen.remove_builder(builder.instance_id);
let builder = generation
.add_builder("pwm.js", &[("channel", 2i64.into()), ("simple_freq_val", 10_000.0.into())])
.await?;
generation.update_builder(builder.instance_id, &[("simple_freq_val", 20_000.0.into())]).await?;
generation.rebuild_builder(builder.instance_id).await?;
generation.remove_builder(builder.instance_id)?;
/* The builder comes back as JSON once its run has finished; NULL on failure. */
char *builder = ss_builder_add(workspace, "pwm.js",
"{\"channel\": 2, \"simple_freq_val\": 10000}");
if (!builder) fprintf(stderr, "builder: %s\n", ss_last_error());
else ss_string_free(builder);
/* ss_builder_update takes the same JSON object. ss_builder_config,
ss_builder_rebuild and ss_builder_remove take the builder's instance_id. */
Option ids and values follow the same rules as decoder options; see Decoders.
close​
Ends the workspace on the server: the capture is aborted, the decoders stop and the data is freed.
- Python
- NodeJS
- Rust
- C
workspace.close()
workspace.close(); // permanent, and it affects every attached client
workspace.close()?; // permanent, and it affects every attached client
ss_workspace_close(workspace); /* ends it on the server */
ss_workspace_free(workspace); /* releases the local handle */
There is no undo, and it affects every client attached to that workspace, including the ScanaStudio window if somebody has it open.
If you only want your script to stop watching, close the connection instead and leave the workspace running.
ss_workspace_close() ends the session on the server. ss_workspace_free()
only releases your handle. Call free on every handle; call close only when
you mean to destroy the session.