Skip to main content

Connecting and the server

The entry point of every SDK. You connect to a server, look at what it offers, and open or join a workspace.

Naming, across the four languages

Method and field names are snake_case in every SDK, NodeJS included, so the four tabs read the same. C returns structured data as JSON strings with the same field names.

Every NodeJS timeout is named timeout_ms and counted in milliseconds, where Python and Rust take seconds.

connect​

Opens the connection and checks the protocol version. The SDK refuses a server it cannot speak to.

from ikalogic_scanastudio import ScanaStudio

# Positional url, keyword-only options.
server = ScanaStudio.connect(
url="ws://127.0.0.1:4911",
timeout=30.0, # seconds to wait for any single answer
check_version=True,
)

# ScanaStudio is a context manager, and this is the idiomatic form.
with ScanaStudio.connect() as server:
...

Parameters:

  • url — where the server listens. Defaults to ws://127.0.0.1:4911.
  • timeout / timeout_ms — how long to wait for any single answer. Default 30 s.
  • check_version — refuse a server speaking a protocol this SDK does not. Leave it on.

Returns: a connected client.

Errors: VersionMismatch (Error::Version in Rust) when the server speaks another protocol; a connection error when nothing is listening. In Rust, nothing listening is Error::Socket, and a server that does not greet the client in time is Error::Timeout. In C, NULL, with the reason in ss_last_error().

Closing the connection is harmless

Closing a client leaves every workspace on the server running. Only workspace.close() ends a session. Rust has no close(): the connection ends once the ScanaStudio value and every Workspace obtained from it have been dropped.

NodeJS also offers ScanaStudio.connect_with(Impl, url, options), which takes the WebSocket class to use as its first argument. Use it in an environment where the SDK cannot find one by itself, or to pass a WebSocket implementation of your own. connect() uses the runtime's built-in WebSocket when there is one (Node.js 22 and later, Bun, browsers) and falls back to the optional ws package.

info​

Protocol version, product version, build commit and build date. Available as soon as connect returns.

print(server.info)
# ScanaStudio 6.0.23 (cd798ee), protocol 8

server.info.protocol # 8
server.info.version # "6.0.23"
server.info.commit # "cd798ee"
server.info.date # build date

Example output. Python's ServerInfo has a readable __str__; the other SDKs hand you the fields:

ScanaStudio 6.0.23 (cd798ee), protocol 8

In NodeJS the object itself prints as:

{ protocol: 8, version: "6.0.23", commit: "cd798ee", date: "2026-10-15" }

and in C it arrives as JSON:

{"commit":"cd798ee","date":"2026-10-15","protocol":8,"script_api":3,"sdk":"0.2.10","version":"6.0.23"}

Rust's ServerInfo and the C JSON also carry script_api, the version of the ScanaStudio.* API the server's script engine implements, and sdk, the SDK version the server was built alongside. That is the SDK version to install.

devices​

Every device a workspace can be opened on: connected hardware and the demo devices. The list is pushed again on every hotplug, so it is always current.

for device in server.devices():
device.key # what create() takes: "se254" or "hw:1004250000604"
device.name # "SE254 (demo)"
device.model
device.serial # None for a demo device
device.demo # bool
device.probes # probe model per port, e.g. ["P992", None]
device.booting # True while the device has not finished starting

Returns: the device list. key is the string create() takes. (open() takes the path of a .scana file, not a device.)

Fields:

  • key: hw:<serial> for hardware, for example hw:1004250000604, or the demo's own name, such as se254.
  • name: the device's description, or "SE254 (demo)" for a demo device.
  • probes: one entry per port, the probe model the port reports (P990, P991, P992, UNKNOWN) or none. The list is empty on families whose probes are fixed, which is not the same as no probe.
  • booting (Python only): the device answers but has not finished starting. A capture on it is refused until it has.

Example output. A server with no hardware plugged in still offers the demo devices, printed as key and name:

se254 SE254 (demo)
sq25 SQ25 (demo)
sq50 SQ50 (demo)
sq100 SQ100 (demo)
sq200 SQ200 (demo)
sp209 SP209 (demo)
sp209i SP209I (demo)
sp259 SP259 (demo)
sp259i SP259I (demo)
sp1018 SP1018 (demo)
sp1036 SP1036 (demo)
sp1054 SP1054 (demo)

The C tab returns the same list as JSON:

[{"demo":true,"key":"se254","model":"se254","name":"SE254 (demo)","serial":null},
{"demo":true,"key":"sp209","model":"sp209","name":"SP209 (demo)","serial":null}, ...]

Real hardware appears with "demo": false, a serial number, and a key of the form hw:<serial>.

capabilities​

What a device can do, available before you create a workspace on it. Use it to build a capture request the device will accept.

caps = server.capabilities("sp259")

caps.sample_rates # [250000000, 125000000, 25000000, ...] descending
caps.sample_depths # acquisition depths, in samples
caps.logic_levels # discrete thresholds (V); empty if custom_threshold applies
caps.custom_threshold # a min/max/step range, or None
caps.logic_level_groups # ['CH1 to CH3', 'CH4 to CH6', ...]
caps.probes # physical probes in channel order; empty if not block-probed
caps.state_clock_channels # channels the external clock can be read on; empty = no state mode
caps.channel_pulls # switchable pull-up/pull-down
caps.pattern_generator # a pattern generator script can drive the channels
caps.signal_generator # the channels can replay generation rows (SQ family)
caps.channel_io # what a channel's direction can be set to, or None
caps.differential_pairs # channel pairs readable as one differential input
caps.per_channel_threshold # one threshold per channel through channel_thresholds (P992)
caps.pre_trig_limit # what it can hold as pre-trigger, in SAMPLES, or None
caps.trigger_engines # 2 gives A, B and a sequence; 1 gives a single event
caps.trig_timing # the trigger timer: reachable precision on widths
caps.can_ext_trig_in, caps.can_ext_trig_out
caps.can_ext_trig_test # the TRIG OUT -> TRIG IN loopback test is accepted
caps.default_state_edge # "rising", "falling" or "dual"
caps.industrial_inputs # empty on a non-industrial version
caps.error # None, or why the device is unknown

Parameters: device — the same vocabulary create() takes: a demo key such as se254, or hw:<serial>.

Fields:

FieldMeaning
sample_ratesTiming-mode rates in Hz, descending.
sample_depthsAcquisition depths offered, in samples.
logic_levelsDiscrete logic levels in volts; empty when custom_threshold applies.
custom_thresholdA min/max/step range with presets, for an adjustable threshold.
logic_level_groupsThe channel groups that share one logic level.
per_channel_thresholdA probe with one comparator per channel (P992) takes one threshold per channel through channel_thresholds. False while the device firmware cannot read them.
probesPhysical probes in channel order; empty when the device is not block-probed.
state_clock_channelsChannels the state-mode clock can be read on. Empty means no state mode.
default_state_edgerising, falling or dual.
channel_pullsSwitchable pull-up or pull-down on every channel.
channel_ioWhat a channel's direction can be set to (open_drain allowed, pulls, input impedance, series resistor), or none on a family whose channels only read.
differential_pairsPairs of channels (first, second) that can be read as one differential input.
industrial_inputsIndustrial bus receivers (channel, bus, termination); empty on a non-industrial version.
pattern_generatorA pattern generator script can drive the channels (SP1000G).
signal_generatorThe channels can replay the generation rows, through the capture's operation (SQ family).
trigger_engines2 gives events A and B plus a sequence; 1 gives a single event.
trig_timingThe trigger timer (base_clock_hz, delta_time_bits, clk_div_bits, min_clock_hz, max_steps): what precision a pulse width or step time can reach.
pre_trig_limitHow much pre-trigger the device can hold, in samples (kind is buffer or ring), or none when unknown.
can_ext_trig_in, can_ext_trig_outThe external trigger input and output exist.
can_ext_trig_testThe capture's ext_trig_test is accepted.
errorSet when the server does not know the device.

Errors: none for an unknown device. The answer comes back with error set, for example unknown device: nope, and the other fields are then meaningless. Check error before using the rest.

It does not open the device

The capabilities come from the server's own tables, so a device that is not plugged in is still described, and the call never fails because somebody else is capturing on that device.

Example output. Four devices:

--- se254 ---
sample_rates: [250000000, 125000000, 25000000, 12500000, 6250000, 2500000] (6)
logic_levels: [1.8, 2.5, 3.3, 5.0]
logic_level_groups: ['CH1 to CH4']
state_clock_channels: [] channel_pulls: False pattern_generator: False
trigger_engines: 2 ext_in: False ext_out: False

--- sq25 ---
sample_rates: [25000000, 10000000, 5000000, 1000000, 500000, 250000, ...] (9)
logic_levels: [3.3]
logic_level_groups: ['CH1 to CH4']
state_clock_channels: [] channel_pulls: False signal_generator: True
channel_io: open_drain=False pulls=True input_impedance_ohm=100000 series_ohm=270
trigger_engines: 1 ext_in: False ext_out: False

--- sp259 ---
sample_rates: [250000000, 125000000, 25000000, 12500000, 6250000, 2500000] (6)
logic_level_groups: ['CH1 to CH3', 'CH4 to CH6', 'CH7 to CH9']
state_clock_channels: [8] channel_pulls: False pattern_generator: False
trigger_engines: 2 ext_in: True ext_out: True

--- sp1018 ---
sample_rates: [1000000000, 500000000, 250000000, 125000000, 50000000, ...] (8)
logic_levels: [] custom_threshold: min=0.0 max=3.0 step=0.025
probes: ['Probe A', 'Probe B']
state_clock_channels: [8, 17] channel_pulls: True pattern_generator: True
trigger_engines: 2 ext_in: True ext_out: True

Things to note: only the SP1018G reports pattern_generator: True, and only the SQ25 reports signal_generator: True with a channel_io description; the SE254 lists no state_clock_channels, so it has no state mode; and the SP1018G has no discrete logic_levels because it takes a custom_threshold anywhere from 0 to 3 V instead.

Which rates you may ask for

sample_rates lists what the device offers. A capture asking for anything else is rounded down to a member of it rather than refused. See Choosing a sample rate.

create​

Creates a workspace on a device and attaches this connection to it.

workspace = server.create("se254") # a demo device
workspace = server.create("hw:1004250000604") # real hardware
workspace = server.create("se254", timeout=60.0)

Parameters:

  • device — a key from devices(): a demo key such as se254, or hw:<serial>.
  • timeout / timeout_ms — how long to wait for the workspace. Default 60 s.

Returns: a Workspace.

Errors: a device key the server does not know is not reported as such: the call waits and ends in Timeout (Error::Timeout in Rust; NULL in C) after the timeout, 60 s by default. Take the key from devices().

One connection, one workspace

create(), open() and attach() all move the connection to the new workspace. The previous workspace keeps running on the server.

An older Workspace object keeps its own id, so its close() still closes the right workspace. Its sub-APIs (capture, data, decoders and the others) do not: they act on the workspace the connection is now attached to, and they read a state cache shared with it, which can be stale. Use one connection per workspace to drive two at once.

workspaces​

Lists every workspace the server holds, including ones this client never created and ones no client is watching. This is the discovery call that makes detaching and rejoining possible.

for session in server.workspaces():
session.workspace_id # what attach() takes
session.device_name
session.state # "sampling", "done", ...
session.last_sample
session.sample_rate
session.decoders # how many decoders are attached
session.clients # how many clients are watching (informational)
session.capturing # an acquisition is running
session.device # the device key, reusable with create()
session.requested_samples
session.file_path # the .scana it was opened from or saved to, or None
clients is informational

It says how many connections are watching a workspace, and nothing more. The server never closes a workspace because nobody is watching it: a session with zero clients keeps capturing.

Example output. Five sessions on one server, none of them being watched. 0 client(s) is normal:

192139071722857 SE254 (demo) done 1000000 samples 0 client(s)
192139071722858 SE254 (demo) done 1000000 samples 0 client(s)
192139071722860 SE254 (demo) done 1000000 samples 0 client(s)
192139071722862 SE254 (demo) done 1000000 samples 0 client(s)
192139071722863 SE254 (demo) sampling 1225000000 samples 0 client(s)

attach​

Joins a workspace already running on the server, by its id. The server replays its full state, so you see the capture configuration, the decoders, the markers and the log as if you had been there all along.

sessions = server.workspaces()
workspace = server.attach(sessions[0].workspace_id)

Parameters:

  • workspace_id: an id from workspaces().
  • timeout / timeout_ms: default 60 s.

Errors: an id that is not on the server, such as a workspace already closed, ends in Timeout after the timeout rather than in an immediate error.

Python: the capture state right after attach

In Python, capture.state and capture.running read idle right after attach() to a workspace whose capture is running, until the server sends the next state change. To know whether a session is capturing, read capturing in workspaces().

See Headless datalogger for the full pattern.

open​

Opens a .scana file as a new workspace: the captured data, its decoders and its annotations, with no device involved.

workspace = server.open("/data/run-42.scana")
The server resolves the path

The path is opened by the server, on the server's machine and with the server's permissions, not by your script. Against a remote server, a local path means nothing. The same is true of save() and export_csv().

Parameters:

  • path — the .scana to open, as the server sees it.
  • timeout / timeout_ms — default 600 s; loading a large capture is slow. Rust has no timeout argument and always waits up to 600 s.

Errors: a file the server cannot read (missing, not a .scana, no permission) raises IoError (Error::Io in Rust) at once, with the server's reason. A load that is still running when the timeout runs out raises Timeout.

It returns once the capture is loaded

open() waits until the file is loaded. When it returns, last_sample describes the recording and the transitions are readable. See Opening a .scana.

file_info​

Reads the manifest of a .scana without opening it: the device it was recorded on, the channel count, the sample rate and the length. It needs no workspace, so a script can decide what to open before creating anything.

info = server.file_info("/data/run-42.scana")
info["device"], info["device_name"], info["channels"]
info["sample_rate"], info["requested_samples"]
info["error"] # None, or why the file could not be read

As with open(), the server resolves the path. A file it cannot read does not raise: the answer carries error and empty fields.

Example output. A one-million-sample SE254 capture:

{'device': 'se254', 'device_name': 'SE254 (demo)', 'channels': 4,
'sample_rate': 25000000, 'requested_samples': 1000000, 'error': None}

The current workspace​

server.workspace # the Workspace this connection is attached to, or None

Closing​

server.close() # ends the connection; workspaces keep running

shutdown​

Stops the server itself, and with it every workspace it holds. Captures are aborted, data is freed and devices are released, for every client, including the ScanaStudio window. Any client may send it.

server.shutdown()
Not the way to end a script

A test bench almost never wants this. To end a script, close the connection; to end one session, close its workspace.

scripts​

server.scripts (server.scripts() in Rust) manages the decoder and generator scripts installed on the server. See Script library.