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 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 when the value is dropped.

info​

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

print(server.info)
# ScanaStudio 6.0.13 (a0db302), protocol 8

server.info.protocol # 8
server.info.version # "6.0.13"
server.info.commit # "a0db302"
server.info.date # build date

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

ScanaStudio 6.0.13 (1a909ee), protocol 8

In NodeJS the object itself prints as:

{ protocol: 8, version: "6.0.13", commit: "1a909ee", date: "2026-09-02" }

and in C it arrives as JSON:

{"commit":"1a909ee","date":"2026-09-02","protocol":8,"version":"6.0.13"}

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():
print(device) # "se254 — SE254 (demo)"
device.key # what open() takes: "se254" or "hw:SP259-000123"
device.name # "SE254"
device.model
device.serial # None for a demo device
device.demo # bool
device.probes # probe models, per group

Returns: the device list. key is the string open() takes.

Example output. A server with no hardware plugged in still offers the demo devices, printed here through Python's Device.__str__:

se254 — SE254 (demo) (demo)
sp209 — SP209 (demo) (demo)
sp209i — SP209I (demo) (demo)
sp259 — SP259 (demo) (demo)
sp259i — SP259I (demo) (demo)
sp1018 — SP1018 (demo) (demo)
sp1036 — SP1036 (demo) (demo)
sp1054 — SP1054 (demo) (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 # the channels can generate as well as capture
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.can_ext_trig_in, caps.can_ext_trig_out
caps.industrial_inputs # empty on a non-industrial version

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

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. Three devices:

--- se254 ---
sample_rates: [250000000, 125000000, 25000000, 12500000, 6250000, 2500000] (6)
logic_levels: [1.2, 1.5, 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

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

Three things to note: only the SP1018G reports pattern_generator: True; 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:SP259-000123") # 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.

One connection, one workspace

Opening a second workspace on the same client detaches it from the first. The first workspace keeps running on the server; you have only stopped watching it. Use a second connection 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
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)

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.
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.

The current workspace​

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

Closing​

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