Events and logs
The server pushes events as things happen: capture state, progress, the decoder list, annotations. The SDKs handle them for you, so most scripts never touch this API. Use it when you want to react to an event rather than wait for it.
Reached through workspace.events.
Events are replayed when you attach, so a client that connects late still sees the current state.
subscribe​
Calls your listener for every event, or for the kinds you name. Returns a function that unsubscribes.
- Python
- NodeJS
- Rust
- C
def on_event(message):
print(message["type"], message)
stop = workspace.events.subscribe(on_event, only=["state", "progress"])
# ...
stop()
// The second argument filters by event kind; omit it for everything.
const stop = workspace.events.subscribe(
(message) => console.log(message.type, message),
['state', 'progress'],
);
// ...
stop(); // calling the returned function unsubscribes
// Rust hands you the channel rather than taking a callback.
let mut events = workspace.events().subscribe(); // broadcast::Receiver
while let Ok(event) = events.recv().await {
println!("{}", event["type"]); // filter on the kind yourself
}
static void on_event(const char *json, void *user) {
/* Keep it quick: this runs on a thread the library owns. */
printf("%s\n", json);
}
/* NULL kinds means every event. */
ss_subscription *sub =
ss_events_subscribe(workspace, "[\"state\", \"decode_progress\"]",
on_event, NULL);
/* ... */
ss_events_unsubscribe(sub); /* before the workspace handle is freed */
The callback runs on a thread the library owns. Guard whatever it touches, and
never call ss_events_unsubscribe from inside it, since that would join the
thread it is running on. ss_last_error() is thread-local, so a failure inside
the callback is not visible from your own thread (see
The C library).
ss_event_latest and ss_event_wait need no callback at all.
The listener runs on the connection's reader thread. Blocking it stalls every other message on that connection. Queue the work and return.
latest​
The most recent event of one kind, without waiting. The SDK keeps the last of each, so this is how you read state the server pushed before you asked.
- Python
- NodeJS
- Rust
- C
config = workspace.events.latest("capture_config") # dict, or None
devices = workspace.events.latest("hw_devices")
// The SDK keeps the last event of each kind, so this needs no waiting.
const config = workspace.events.latest('capture_config');
// The SDK keeps the last event of each kind, so this needs no waiting.
let config = workspace.events().latest("capture_config"); // typed: Option<ServerMsg>
let raw = workspace.events().latest_raw("capture_config"); // raw JSON: Option<Value>
/* The library keeps the last event of each kind, so this needs no waiting. */
char *config = ss_event_latest(workspace, "capture_config"); /* NULL if none */
if (config) { printf("%s\n", config); ss_string_free(config); }
wait_for​
Blocks until an event of a kind arrives, optionally one that satisfies a
predicate. This is the building block behind capture.wait() and
decoders.wait(), and what you want for a condition they do not cover.
- Python
- NodeJS
- Rust
- C
# Wait until the trigger has fired at least 10 times.
event = workspace.events.wait_for(
"trigger_count",
predicate=lambda message: message.get("count", 0) >= 10,
timeout=60.0,
)
// Wait until the trigger has fired at least 10 times.
const event = await workspace.events.wait_for('trigger_count', {
predicate: (message) => (message.count ?? 0) >= 10,
timeout_ms: 60_000,
});
// Wait until the trigger has fired at least 10 times.
let event = workspace.events()
.wait_for("trigger_count",
|m| m["count"].as_u64().unwrap_or(0) >= 10,
Duration::from_secs(60))
.await?;
/* Returns at once if one already arrived, so waiting for "state" on an
idle workspace does not hang. There is no predicate in C: read the
JSON and wait again if it is not the one you wanted. */
char *event = ss_event_wait(workspace, "trigger_count", 60.0);
if (event) { printf("%s\n", event); ss_string_free(event); }
Errors: Timeout if nothing matching arrives in time.
Useful events​
The events a script usually cares about:
| Event | Fires when |
|---|---|
state | The capture changes state. See Capture. |
progress | Samples come in. |
pretrig_progress | The pre-trigger buffer is filling. |
trigger_count | The trigger fired again, in normal mode. |
capture_config | A capture configuration was applied — the settled rate and trigger. |
capture_refused | A capture was refused, with the reason. |
decode_progress | A decoder advanced. Throttled, but always sent at the end. |
decoders | The decoder list changed. |
annotations | Markers or measurements changed, including recomputed results. |
hw_devices | A device was plugged or unplugged. The complete list, every time. |
io_progress, io_done | A save, load or export is running or has finished. |
error | A command was refused. code is stable and meant to be matched on. |
log | The server logged a line. |
log​
The server's log for this workspace. The full history is replayed on attach, so you get the lines from before you connected too.
- Python
- NodeJS
- Rust
- C
for line in workspace.events.log:
print(line)
// The full history is replayed on attach, so this includes lines from
// before this client connected.
for (const line of workspace.events.log) {
console.log(line);
}
// The full history is replayed on attach, so this includes lines from
// before this client connected.
for line in workspace.events().log() {
println!("{line}");
}
/* A JSON array of lines, already timestamped by the server. The full
history is replayed on attach, so this includes lines from before
this client connected. */
char *log = ss_events_log(workspace);
if (log) { printf("%s\n", log); ss_string_free(log); }
When a decoder or generator script misbehaves, the reason is usually in the
server log rather than in the exception. ScriptError carries the relevant
lines with it; the log has the rest.