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.
- Python
- NodeJS
- Rust
- C
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}")
import { Trigger } from '@ikalogic/scanastudio';
// Arm on a rising edge on channel 0, keeping 10% of history before it.
const last = await workspace.capture.run({
samples: 1_000_000,
sample_rate: 25_000_000,
trigger: Trigger.rising(0, { position: 0.1 }),
timeout_ms: 300_000,
});
use scanastudio_client::{CaptureRequest, Trigger, TriggerExt};
use std::time::Duration;
// Arm on a rising edge on channel 0, keeping 10% of history before it.
let last = workspace.capture().run(
&CaptureRequest::new(1_000_000, 25_000_000).trigger(Trigger::rising(0).at(0.1)),
Duration::from_secs(300),
).await?;
/* The request, then how long to wait for it. */
char *trigger = ss_trigger_edge(0, "rising", 0.1, NULL);
char request[192];
snprintf(request, sizeof request,
"{\"samples\": 1000000, \"sample_rate\": 25000000, \"trigger\": %s}",
trigger);
ss_string_free(trigger);
/* Returns the last sample index, or -1 on failure (not an ss_status). */
int64_t last = ss_capture_run(workspace, request, 300.0);
if (last < 0) fprintf(stderr, "capture: %s\n", ss_last_error());
Without a trigger field the capture begins as soon as the device is armed:
ss_capture_run(workspace, "{\"samples\": 1000000, \"sample_rate\": 25000000}", 300.0);
Every trigger Triggers describes
is built with an ss_trigger_* function and dropped into the same request.
Returns: the index of the last sample captured.
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 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 for | You get | Why |
|---|---|---|
| 250 000 000 | 250 000 000 | exactly on the list |
| 999 000 000 | 250 000 000 | above the fastest → the fastest |
| 100 000 000 | 25 000 000 | rounds down past 125 M, which is faster than asked |
| 26 000 000 | 25 000 000 | rounds down |
| 1 000 | 2 500 000 | below 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:
- Python
- NodeJS
- Rust
- C
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)
await workspace.capture.run({ samples: 1_000_000, sample_rate: 100_000_000 });
console.log(workspace.capture.sample_rate); // 25000000 on an SP259
// Or pick from the list up front, and get exactly what you chose.
const caps = await server.capabilities('sp259');
const rate = caps.sample_rates.find((r) => r <= 100_000_000);
await workspace.capture.run({ samples: 1_000_000, sample_rate: rate });
workspace.capture()
.run(&CaptureRequest::new(1_000_000, 100_000_000), Duration::from_secs(300))
.await?;
println!("{}", workspace.capture().sample_rate()); // 25000000 on an SP259
ss_capture_run(workspace, "{\"samples\": 1000000, \"sample_rate\": 100000000}", 300.0);
printf("%llu\n", (unsigned long long)ss_capture_sample_rate(workspace)); /* 25000000 */
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.
- Python
- NodeJS
- Rust
- C
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()
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();
workspace.capture().start(&CaptureRequest::new(2_000_000_000, 25_000_000))?;
// ... the program can exit here; the capture keeps running on the server
workspace.capture().stop()?;
/* The whole StartCapture payload, as JSON. ss_capture_start returns at once. */
ss_capture_start(workspace,
"{\"samples\": 2000000000, \"sample_rate\": 25000000,"
" \"trigger\": {\"trig_a\": {\"kind\": \"none\"}}}");
/* ... the program can exit here; the capture keeps running on the server */
ss_capture_stop(workspace);
Both take the same request. ss_capture_run arms and waits in one call.
ss_capture_start returns as soon as the device is armed; call
ss_capture_wait later, or nothing at all if the program means to exit. The
ss_trigger_* builders write the trigger object for either one.
An unknown field in the request is refused, not ignored, so a misspelt name is an error.
Options. Anything left out keeps the device's default:
| Option | Meaning |
|---|---|
samples | How many samples to capture. |
sample_rate | Samples per second. The device rounds to a rate it offers; read sample_rate back to learn what it settled on. |
trigger | What arms the capture. See Triggers. Absent starts at once. |
mode | timing samples on the device's internal clock (the default); state samples on an external one. |
state_clock_channel, state_edge | Which channel clocks state mode, and on which edge. |
logic_levels | Input thresholds in volts, per channel group. |
pulls | Per-channel pull: up, down or none. |
probes | Per-port probe model, on families with removable probes. |
industrial | Per-channel industrial receiver settings. |
stop ends the acquisition; whatever was already captured stays and stays
readable.
wait​
Blocks until the capture reaches a terminal state.
- Python
- NodeJS
- Rust
- C
last = workspace.capture.wait(timeout=300.0)
// Blocks until the capture reaches a terminal state.
const last = await workspace.capture.wait(300_000);
// Blocks until the capture reaches a terminal state.
let last = workspace.capture().wait(Duration::from_secs(300)).await?;
/* What blocks after ss_capture_start. ss_capture_run has already waited.
Both return the last sample index. */
int64_t last = ss_capture_wait(workspace, 300.0);
if (last < 0) fprintf(stderr, "wait: %s\n", ss_last_error());
Errors: a timeout is raised as Timeout (Error::Timeout in Rust). The
capture is not stopped by a timeout; decide whether to stop() or keep
waiting.
wait() on a finished capture times outwait() waits for the capture to reach a terminal state; it does not return
early if it is already in one. Attaching to a workspace that has already
finished and calling wait() blocks until the timeout. Check running, or
capturing in the workspaces() listing, before waiting on a session you did
not start yourself.
state and running​
- Python
- NodeJS
- Rust
- C
workspace.capture.state # "sampling"
workspace.capture.running # True while the capture is live
workspace.capture.state; // "sampling"
workspace.capture.running; // true while the capture is live
workspace.capture().state(); // String, e.g. "sampling"
workspace.capture().running(); // true while the capture is live
char *state = ss_capture_state(workspace); /* "sampling" */
if (state) { printf("%s\n", state); ss_string_free(state); }
ss_capture_running(workspace); /* true while it is live */
The states, in the order they occur:
| State | Meaning |
|---|---|
idle | Nothing has been started. |
configuring | The device is being set up. |
purging | Old data is being cleared out. |
pretrigger | Filling the pre-trigger buffer, so the trigger has history behind it. |
waiting_trigger | Armed, waiting for the trigger condition. |
sampling | Capturing. |
done | Finished normally. |
error | Finished badly; the log says why. |
idle, done and error are terminal; the rest mean the capture is running.
last_sample, sample_rate, seconds​
- Python
- NodeJS
- Rust
- C
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")
const last = workspace.capture.last_sample; // -1 before anything is captured
const rate = workspace.capture.sample_rate; // what the DEVICE settled on
workspace.capture.seconds(last);
let last = workspace.capture().last_sample(); // -1 before anything is captured
let rate = workspace.capture().sample_rate(); // what the DEVICE settled on
workspace.capture().seconds(last); // sample index -> seconds
int64_t last = ss_capture_last_sample(workspace);
uint64_t rate = ss_capture_sample_rate(workspace); /* what the DEVICE settled on */
double seconds = ss_capture_seconds(workspace, last);
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.
- Python
- NodeJS
- Rust
- C
workspace.capture.trigger_count
workspace.capture.trigger_count; // only moves in "normal" mode
workspace.capture().trigger_count(); // u32, only moves in "normal" mode
uint32_t seen = ss_capture_trigger_count(workspace); /* only moves in "normal" */
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.
- Python
- NodeJS
- Rust
- C
config = workspace.capture.config # None before the first capture
// The configuration the server actually applied; null before the first capture.
const config = workspace.capture.config;
// The configuration the server actually applied; None before the first capture.
let config = workspace.capture().config(); // Option<ServerMsg>
/* JSON, and NULL before the first capture. */
char *config = ss_capture_config(workspace);
if (config) { printf("%s\n", config); ss_string_free(config); }
If the device cannot honour the configuration, the capture is refused rather
than started, with a reason you can show to an operator. The SDKs raise it as
Refused (Error::Refused in Rust). A generator script that produces nothing
also refuses the capture this way.