Skip to main content

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.

# 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

id and device_name​

workspace.id # the id attach() takes
workspace.device_name # "SE254 (demo)" or "SP259 (sn 1004250000604)"

channels​

The device's channels, in order. Index in this list is the channel number every other call takes.

for index, channel in enumerate(workspace.channels):
print(index, channel.name, channel.ch_label, channel.color)

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.

scl = workspace.channel("I2C SCL") # -> 2
# Raises KeyError if the device has no such channel.
Name your channels, not your numbers

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.

workspace.attach_device("1004250000604")

device_info​

Everything the attached device says about itself: hardware and firmware versions, temperatures, PLL / DDR / HyperRAM health, the probes it sees.

info = workspace.device_info() # timeout defaults to 60 s
for row in info["rows"]:
print(row)
It opens the device

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.

Firmware updates are not in the SDK

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.

copy_id = workspace.duplicate()

with ScanaStudio.connect() as other:
copy = other.attach(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​

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"],
)

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.

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

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.

workspace.close()
Closing is permanent and affects everyone

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.

C: two different things

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.