Core concepts
A few ideas explain most of the SDK's design. They are the same in Python, NodeJS, Rust and C, so this page is language-neutral.
Server, client, workspace
ScanaStudio is split in two. The server owns the USB devices, the captured data, the decoders and the scripts. The client is whatever talks to it: the ScanaStudio window, your script, a CLI.
A workspace is one working session: a device, a capture, the decoders
attached to it, its markers and measurements. One tab in the ScanaStudio window
is one workspace, and so is one create() call from your code. A server holds
as many as you like.
your script ──┐
├── WebSocket ──> server ──> workspace ──> device
ScanaStudio ──┘ └─> workspace ──> device
An SDK connection is attached to one workspace at a time, and every command targets that one. To work with two workspaces at once, open two connections.
The server owns the workspace
This changes how you write scripts. Disconnecting destroys nothing. Close your script, lose the network, reboot your laptop: the workspace stays on the server, the capture keeps running, and the data stays reachable.
A workspace stays until something closes it. There is no timeout, so a trigger can stay armed for hours.
This is a supported way to work:
- A script starts a two-billion-sample capture and exits.
- Hours later, another script lists the server's workspaces, attaches to that one by id, stops it and reads the results.
workspaces() is the discovery call that makes it possible, and attach() is
how you rejoin. See Headless datalogger.
workspace.close() aborts the capture, stops the decoders and frees the data,
for every client attached to it, including the ScanaStudio window if
somebody has that workspace open. Closing your connection is harmless; closing
the workspace is not.
Push small notifications, pull bulk data
The server pushes events: capture state, progress, the decoder list, the marker list. Events are replayed when you attach, so a client that connects late still sees the current state.
Bulk data (transitions, packets, hex bytes) is pulled when you ask for it.
That is why transitions() is an iterator rather than a callback: nothing is
sent until you read, and a slow reader never slows down the acquisition.
Everything is a sample index
Positions are sample numbers, not seconds. A capture at 25 MS/s puts sample 25 000 000 at one second. Convert with the capture's own rate:
seconds = workspace.capture.seconds(sample)
Use the sample rate the device actually settled on, capture.sample_rate,
rather than the one you asked for. They differ whenever the device cannot hit
the requested rate exactly.
last_sample is the index of the last sample captured, and it is -1 before
anything has been captured.
Transitions, not samples
The server never stores one value per sample. It stores transitions: the sample index at which a channel changed, and the level it changed to. A signal that sits idle for a second costs nothing.
Reading transitions therefore gives you edges, not a waveform:
sample level
0 1 <- the level in force at the start of the window
1041 0
1083 1
Decoders are scripts, and their options are discovered at runtime
A protocol decoder is a JavaScript file in the server's script library
(i2c.js, uart.js, spi.js): the same files ScanaStudio itself uses, and the
same ones you can write yourself with the
scripting API.
Because a decoder is a script, the SDK cannot know its settings in advance. Ask:
workspace.decoders.options("i2c.js") -> the list of settings, with defaults
workspace.decoders.add("i2c.js", {...}) -> an instance, decoding
Options are passed by id or by caption, and anything you leave out keeps the
script's own default. Channels are passed as channel numbers, which is what
workspace.channel("I2C SCL") gives you. Look them up by name and the same
script works on any device that has those channels.
Adding a decoder starts it immediately and asynchronously; wait() blocks until
it has finished.
Protocol version
Client and server check their protocol version on connect. A mismatch raises an error rather than half-working; update whichever side is older. The protocol version changes only on a breaking change, so it is independent of the product version.
The server has no authentication
The protocol has no authentication of any kind. A server listening on anything but loopback hands your instruments to whoever can reach the port: they can start and stop captures, read your data, and shut the server down.
The default is 127.0.0.1:4911 for that reason. If you open it to a network,
put it behind an SSH tunnel, a VPN, or a firewall rule that admits only the
machines you trust. See
Remote and shared instruments.
Demo devices
Every server offers demo devices (se254 is the one used throughout this
documentation) that behave like real hardware with nothing attached. They let
you develop a script without an analyser, and run a CI job without wiring one
into the build machine.
A demo device produces pseudo-random signals, not protocol frames. It exercises the whole path: capture, decode, measure, export.