Skip to main content

Capture

Arming, running and following an acquisition. Reached through workspace.capture.

run​

Start a capture and wait for it to finish. The one call most scripts need.

from ikalogic_scanastudio import Trigger

last = workspace.capture.run(
samples=1_000_000,
sample_rate=25_000_000,
trigger=Trigger.rising(channel=0, position=0.1),
timeout=300.0,
)
print(f"captured up to sample {last}")

Returns: the index of the last sample captured.

Errors:

  • Timeout: the capture did not start within min(timeout, 30 s) (Timeout("the capture to start")), or it was still running when timeout ran out.
  • Refused: the server declined to start the acquisition. The message is the server's reason.
  • CaptureFailed: the acquisition started, then ended in the error state. The message is the server's detail.
  • ConnectionClosed: the connection dropped while waiting.

In Rust these are Error::Timeout, Error::Refused, Error::CaptureFailed and Error::Closed. In C, ss_capture_run returns -1 and ss_last_error() holds the reason.

How run() waits. It watches for a capture state that arrives after its own start, so the state left by a previous capture is never taken for this one. Once the capture is running it waits for a terminal state. After a refusal it waits up to 5 s for the server to report that it is idle again, so that the next run() starts cleanly.

Choosing a sample rate​

A device does not sample at any rate you like: it has a fixed list, and server.capabilities(device).sample_rates is that list, descending.

A rate that is not on the list is not refused. The server picks one for you:

The rule

The server rounds down to the nearest offered rate, never up. Sampling faster than requested would fill the acquisition sooner than planned. The one exception is a request below the slowest rate the device has, which rounds up to that rate.

Measured on an SP259, whose rates are [250M, 125M, 25M, 12.5M, 6.25M, 2.5M]:

You ask forYou getWhy
250 000 000250 000 000exactly on the list
999 000 000250 000 000above the fastest → the fastest
100 000 00025 000 000rounds down past 125 M, which is faster than asked
26 000 00025 000 000rounds down
1 0002 500 000below the slowest → the slowest

That third row is the one that surprises people: 100 MS/s lands on 25 MS/s, not 125 MS/s, because 125 MS/s is faster than you asked for.

Always read the rate back. capture.sample_rate is what the device settled on, and it is always a member of capabilities().sample_rates:

workspace.capture.run(samples=1_000_000, sample_rate=100_000_000)
print(workspace.capture.sample_rate) # 25000000 on an SP259

# Or pick from the list up front, and get exactly what you chose.
caps = server.capabilities("sp259")
rate = next(r for r in caps.sample_rates if r <= 100_000_000)
workspace.capture.run(samples=1_000_000, sample_rate=rate)
Timing computed from the requested rate will be wrong

Dividing by the rate you asked for, rather than the one in capture.sample_rate, gives results that are consistently off. On the 100 MS/s example above, that is a factor of four.

start and stop​

start arms the capture and returns immediately. Use it when the script should not wait, as in Headless datalogger.

Rust and C: do not exit right after start()

In Rust and C, start(), stop() and close() (ss_capture_start, ss_capture_stop, ss_workspace_close) put the command in a send queue and return. A program that ends at once can exit before the queue is written, and the server never receives the command. Make one request that waits for an answer afterwards, such as server.workspaces() (ss_server_workspaces in C), to make sure it went out.

workspace.capture.start(
samples=2_000_000_000,
sample_rate=25_000_000,
trigger=Trigger.immediate(),
)
# ... the script can exit here; the capture keeps running on the server

workspace.capture.stop()

Options. Anything left out keeps the device's default:

OptionMeaning
samplesHow many samples to capture.
sample_rateSamples per second. The device rounds to a rate it offers; read sample_rate back to learn what it settled on.
triggerWhat arms the capture. See Triggers. Absent starts at once.
modetiming samples on the device's internal clock (the default); state samples on an external one.
state_clock_channel, state_edgeWhich channel clocks state mode, and on which edge.
logic_levelsInput thresholds in volts, per channel group.
pullsPer-channel pull: up, down or none.
probesPer-port probe model, on families with removable probes.
industrialPer-channel industrial receiver settings.
channel_thresholdsInput threshold in volts, per channel. Read only on the ports probes sets to a probe with one comparator per channel (P992), and only when the capabilities say per_channel_threshold.
ext_trig_testTRIG OUT to TRIG IN loopback test, with the two connected: TRIG OUT is held high once the device is armed, so an external rising-edge trigger fires through the link. Accepted only where the capabilities say can_ext_trig_test (the SP259 family), refused elsewhere.
operationcapture (the default), generation or mixed, on a family that replays a signal (signal_generator in its capabilities: the SQ family). See Generation rows.
generator_samplesHow many samples the generator replays, from sample 0 of the generation rows.
generator_loopReplay until the capture is stopped rather than once.
channel_ioDirection per channel: input, output or open_drain, on a family whose channels can drive (channel_io in its capabilities).
differentialOne boolean per differential pair of the device, in the order of differential_pairs in its capabilities: read that pair as one input.
timeout_msNodeJS only, for run(): how long to wait, in milliseconds. Python's run() takes timeout in seconds; Rust and C take the timeout as a separate argument.
Rust and C

CaptureRequest in Rust, and the request JSON in C, have no operation, generator_samples, generator_loop, channel_io or differential field. From those two SDKs a capture is always a plain capture: generation and mixed operation on the SQ family need Python or NodeJS. An unknown field in the C request is refused.

stop ends the acquisition; whatever was already captured stays and stays readable.

wait​

Blocks until the capture reaches a terminal state.

last = workspace.capture.wait(timeout=300.0)

Returns: the index of the last sample captured. On a capture this connection ran and that has already finished, wait() returns at once. After attach() to a workspace whose capture has finished, the SDK does not yet know the capture state, and wait() runs to its timeout: check WorkspaceDesc.capturing from workspaces() instead.

Errors:

  • Timeout (Error::Timeout in Rust): the capture was still running when the time ran out. The capture is not stopped by a timeout; decide whether to stop() or keep waiting.
  • Refused: the server declined the start that preceded this wait.
  • CaptureFailed (Error::CaptureFailed): the capture started, then ended in the error state. The message is the server's detail.
  • ConnectionClosed (Error::Closed): the connection dropped.
A workspace that never captured

wait() returns when the capture is in a terminal state and has captured something. On a workspace that has never captured, it waits until a capture started by another client finishes, or until the timeout.

state and running​

workspace.capture.state # "sampling"
workspace.capture.running # True while the capture is live

The states, in the order they occur:

StateMeaning
idleNothing has been started.
preparingA start was accepted and the server is getting ready. The first running state.
configuringThe device is being set up.
purgingOld data is being cleared out.
pretriggerFilling the pre-trigger buffer, so the trigger has history behind it.
waiting_triggerArmed, waiting for the trigger condition.
samplingCapturing.
doneFinished normally.
errorFinished badly; the log says why.

idle, done and error are terminal; the rest mean the capture is running. RUNNING_STATES holds the running ones in Python, NodeJS and Rust. ss_capture_state in C returns the same strings.

last_sample, sample_rate, seconds​

last = workspace.capture.last_sample # -1 before anything is captured
rate = workspace.capture.sample_rate # what the DEVICE settled on
print(f"{workspace.capture.seconds(last):.6f} s of signal")
Always convert with the rate you got back

The device rounds the rate you asked for to one it can produce. Timing a signal with the rate you requested rather than the one in sample_rate gives results that are consistently off.

trigger_count​

How many times the trigger has fired. It only moves in normal mode, where the capture re-arms after each trigger. It goes back to 0 at every start.

workspace.capture.trigger_count

config​

The capture configuration the server actually applied: the settled sample rate, the trigger definition, the thresholds. Useful for logging what a test bench ran with.

config = workspace.capture.config # a CaptureConfigEvent
config.sample_rate, config.trigger, config.operation

The server sends the configuration when the workspace opens, so before the first capture it holds the workspace's defaults. Until it has arrived, Python and Rust return None, NodeJS undefined and C NULL.

When a capture is refused​

If the server cannot honour the request, the capture is refused rather than started, with a written reason you can show to an operator. The SDKs raise it as Refused (Error::Refused in Rust; in C the call fails and ss_last_error() holds the reason).

Each refusal also names a subject, the setting to go and change: device, trigger, probes, clock or pattern_generator. The Refused exception carries only the message. To read the subject, subscribe to the capture_refused event (see Events); in Python, events.subscribe is the only way to see that event, as it never reaches latest() or wait_for().

Common reasons:

  • the workspace was opened from a .scana and has no device attached;
  • the device is still starting up, or is being updated;
  • a start sent while another start is still being prepared (A start is already on its way);
  • a trigger the device cannot arm, including a step cell other than x, 0, 1, rise or fall;
  • ext_trig_test on a device without the loopback test;
  • a probe or clock setting the device cannot honour;
  • a pattern generator script that refuses its configuration, or a generation with nothing on its output rows.