Skip to main content

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 on attach

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.

def on_event(message):
print(message["type"], message)

stop = workspace.events.subscribe(on_event, only=["state", "progress"])
# ...
stop()
Do not block the listener

In Python the listener runs on the connection's reader thread: blocking it stalls every other message on that connection. In NodeJS everything runs on one thread, so long synchronous work in a listener delays every other callback and promise. In both cases, queue the work and return.

A listener that raises is logged (to the ikalogic_scanastudio logger in Python, to console.error in NodeJS) and delivery to the other listeners goes on.

A Rust receiver that falls behind is told it lagged and skips what it missed. latest() always holds the last event of each kind, so a slow reader loses intermediate events but not the current state.

The answer to a request this connection made, such as measure_added or pattern_duration_result, goes to the call that is waiting for it and is not passed to listeners.

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.

config = workspace.events.latest("capture_config") # dict, or None
devices = workspace.events.latest("hw_devices")

wait_for​

Blocks until the last event of a kind satisfies a predicate, or until one arrives when you give no predicate. This is the building block behind capture.wait() and decoders.wait(), and what you want for a condition they do not cover.

wait_for checks the cached last event first. If an event received earlier already matches, it returns at once without waiting for a new one. To wait for something that has not happened yet, write the predicate so that the current value does not satisfy it, for example a count above the one you read with latest().

# 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,
)
Some events never reach latest() or wait_for()

capture_refused, log and log_history are not kept in the cache. The SDK turns a refusal into a Refused error from capture.wait() and run(), and folds the log lines into events.log. latest("capture_refused") is therefore always empty and wait_for("capture_refused") always times out. To react to them as events, use subscribe.

Errors: Timeout if nothing matching arrives in time. ConnectionClosed (Error::Closed in Rust, NULL with ss_last_error() in C) if the link drops while waiting: every pending wait fails at once.

Useful events​

The events a script usually cares about:

EventFires when
stateThe capture changes state. See Capture.
progressSamples come in.
pretrig_progressThe pre-trigger buffer is filling.
trigger_countThe trigger count changed: it fired again in normal mode, or went back to 0 at the start of a capture. Sent only on change; the value at attach time is capture_config.trigger_count.
capture_configA capture configuration was applied: the settled rate, trigger, thresholds and the trigger count.
capture_refusedThe server refused to start a capture. reason is a sentence to show as is; subject says what it is about: device, trigger, probes, clock or pattern_generator. Reaches listeners only, see above.
decode_progressA decoder advanced. Throttled, but always sent at the end. Carries progress, items, done, and the totals hex_bytes, packets and mem_bytes produced so far.
decodersThe decoder list changed.
annotationsMarkers or measurements changed, including recomputed results.
hw_devicesA device was plugged or unplugged. The complete list, every time.
device_attachedA device was attached to a workspace opened from a file. Carries device, device_name and the new channels.
io_progress, io_doneA save, load or export is running or has finished. io_done carries op, path and error when it failed. Every client receives it; the connection that asked also gets a copy with its request id.
busyA command was refused because an export holds the workspace. reason is a sentence; id is the refused request's. The SDK turns it into a Busy error for that call.
file_stateThe workspace's path or dirty flag changed.
ui_stateThe stored view state, sent on attach and when a file is opened.
pattern_generatorThe pattern generator changed: the script attached, generate_on_trigger, and generating, which stays true while the device may still be generating.
pattern_previewA preview started, finished or failed. logs holds the script's messages.
generationThe generation rows or their builders changed: the layout (samples, sample_rate), one entry per channel, and the builders.
add_builder_resultThe answer to generation.add_builder() or update_builder(): the builder attached, or error when its configuration was refused. The call returns it.
pattern_duration_resultThe answer to pattern.duration(): seconds, endless, over_limit. The call returns it.
script_file_access_requestA script without file system access tried to open a path of its own. Carries file_name and path.
errorA command was refused. code is stable and meant to be matched on.
logThe server logged a line. Reaches listeners only.
log_historyThe whole log, sent on attach. It replaces events.log. Reaches listeners only.

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.

for line in workspace.events.log:
print(line)
The log is where a failing script explains itself

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.