Skip to main content

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
One connection, one workspace

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:

  1. A script starts a two-billion-sample capture and exits.
  2. 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.

Closing is permanent

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

There is no password

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 generates noise, not real traffic

A demo device produces pseudo-random signals, not protocol frames. It exercises the whole path: capture, decode, measure, export.