Skip to main content

The C library

libscanastudio is a native library with a plain C interface. It is what lets LabVIEW, MATLAB, C#, and existing C or C++ test benches drive a ScanaStudio server without speaking WebSocket themselves.

Anything you can do from Python, NodeJS or Rust, you can do from C: capture, decode, measure, drive the pattern generator, export.

The header always matches the library

scanastudio.h ships in the same archive as the binaries, and the declarations you compile against are exactly what the library exports. It is fully commented and carries extern "C" guards, so C++ can include it as is.

Getting it, and linking against it

The C library is not published to a package registry. It is one archive with prebuilt libraries for all three platforms:

ScanaStudio C API

One cross-platform archive: include/scanastudio.h, an example, and the prebuilt libraries for Windows (x86_64), Linux (x86_64) and macOS (universal). There is no per-platform download to choose from.

Download scanastudio-c-0.2.7.zip
v0.2.72026-09-12
sha256 394610b8566cd0c7f53080ddadcb96df13a58eb89b5e8bafc8b3e8e781053ef5

The C library carries its own version number and is released independently of the ScanaStudio application.

include/scanastudio.h the API, with its documentation
examples/capture.c capture on a demo device, decode, read the packets
examples/inspect.c open a .scana, describe it, seek its edges, measure
windows-x86_64/ scanastudio.dll, scanastudio.dll.lib, scanastudio.lib
linux-x86_64/ libscanastudio.so, libscanastudio.a
macos-universal/ libscanastudio.dylib, libscanastudio.a (x86_64 + arm64)

Each platform ships both a shared library and a static one. scanastudio.dll.lib is the import library that links a program against scanastudio.dll; scanastudio.lib is the static library.

cc -Iinclude prog.c -Llinux-x86_64 -lscanastudio -o prog # Linux
cc -Iinclude prog.c -Lmacos-universal -lscanastudio -o prog # macOS
cl /Iinclude prog.c windows-x86_64\scanastudio.dll.lib

At run time the shared library still has to be found: LD_LIBRARY_PATH on Linux, DYLD_LIBRARY_PATH on macOS, and next to the executable on Windows.

LD_LIBRARY_PATH=linux-x86_64 ./prog

How the API works

Handles. ss_server and ss_workspace are opaque. A workspace handle must not outlive the server it came from.

Failure. A function returning a pointer returns NULL. A function returning ss_status returns a code. One returning a number uses -1 or 0. Either way ss_last_error() holds the reason.

ss_status_Ok /* 0 */
ss_status_BadArgument
ss_status_Refused
ss_status_Disconnected
ss_status_Timeout
ss_status_Failed
ss_last_error is thread-local

It describes the calling thread's last failing call, and is valid only until that thread's next call that can fail, which clears it whether or not it fails. Read it straight after the call you are checking, and copy it if you need to keep it. The accessors, and every _free, _disconnect and _unsubscribe, leave the slot alone, so you can release handles before reading why something went wrong.

The slot is per-thread. A failure inside an event callback is recorded on the callback's thread and is invisible to yours.

Closing versus freeing. ss_workspace_close() ends the session on the server, for everybody. ss_workspace_free() only releases your handle. Call free on every handle; call close only when you mean to destroy the session.

The two shapes an answer takes

Configuration and catalogues come back as JSON

ss_server_devices, ss_server_capabilities, ss_workspace_channels, ss_workspace_device_info, ss_decoder_options, ss_decoder_config, ss_pattern_config, ss_scripts, ss_capture_state, ss_events_log and the rest return a char * the library owns. Release it with ss_string_free(). Parse it with whatever your bench already uses.

char *devices = ss_server_devices(server);
if (!devices) fail("devices");
printf("%s\n", devices);
ss_string_free(devices);

Strings you pass in are borrowed for the duration of the call only; the library never keeps your pointer.

Bulk records come back as a batch

Markers, measurements, items, packets and hex bytes carry text, so they come back as a batch. They all follow one pattern: _read, _count, _copy, _free:

ss_markers *batch = ss_markers_read(workspace);
if (!batch) fail("markers");
size_t count = ss_markers_count(batch);

ss_marker *markers = malloc(sizeof *markers * count);
ss_markers_copy(batch, 0, markers, count);
printf("%s at %lld\n", markers[0].label, (long long)markers[0].sample);

free(markers);
ss_markers_free(batch); /* every `label` above dies here */
Copied strings point into the batch

Every const char * inside a copied record (label, title, content, kind) points into the batch. Read it or strdup it before the matching _free.

BatchRead withRecord
ss_markersss_markers_readss_marker
ss_measuresss_measures_read, ss_measures_waitss_measure + ss_measure_value
ss_packetsss_packets_readss_packet
ss_itemsss_items_read, ss_items_read_instancess_item
ss_hexss_hex_read, ss_hex_read_instancess_hex_byte

Transitions are the exception

Transitions carry no text, so ss_data_transitions() fills an array you own directly, with no batch to free:

typedef struct { int64_t sample; uint8_t level; } ss_edge;
Two things to know about ss_data_transitions

The first record is the level in force at from, so its sample may be earlier than from. This tells you the channel's level at the start of the window.

A window holding more than capacity edges is truncated, not refused. Ask again starting from the last sample you received.

What it covers

AreaWhat you get
Serverconnect, server info, devices, capabilities, workspace list, probe a .scana, shut the server down
Workspacescreate on a device, open a .scana, attach by id, duplicate, close, device name, channels, device info, move to another device
Capturearm and return, or arm and wait, from the same request; stop, wait, state, config, trigger count, last sample, sample rate, seconds
Triggersimmediate, edge, logic, pulse, multi-step, external, and two engines in sequence
Datatransitions over a window, seek to the next or previous edge, level at a sample, baud detection, packets, packet groups, items, hex, hex search
Decoderswhat a script takes, attach, list, read and change an instance's configuration, remove, relaunch, pause, progress, wait, analog channels
Markersadd, move, remove, clear, read
Measurementsbetween two markers or two samples, change what is computed, move, remove, clear, wait, read the results
Pattern generatorwhat a script takes, attach, read and change its configuration, enable, generate on trigger, preview, resume, detach
Scriptslist, library directory, sync, the online catalogue, install, import a file or a URL, create from a template, rename, duplicate, delete, usage, direct file system access
Eventsa callback subscription, the last event of a kind, a blocking wait, the server's log
Savingsave a .scana, export CSV from any of the four sources, path, dirty, view state

Every function

Library

FunctionDoes
const char *ss_last_error(void)The reason behind this thread's last failure.
void ss_string_free(char *text)Frees a string the library returned.
uint32_t ss_protocol_version(void)The protocol version this library speaks.

Server

FunctionDoes
ss_server *ss_server_connect(const char *url)Connects. NULL means ws://127.0.0.1:4911.
ss_server *ss_server_connect_with(const char *url, double timeout_seconds, bool check_version)The same, with a handshake timeout and an optional version check.
void ss_server_disconnect(ss_server *server)Ends the connection. Workspaces keep running.
char *ss_server_info(const ss_server *)JSON: protocol, version, commit, date.
char *ss_server_devices(const ss_server *)JSON array of devices, hardware and demo.
char *ss_server_capabilities(const ss_server *, const char *device)JSON: what that device can be asked to do. Opens nothing.
char *ss_server_workspaces(const ss_server *)JSON array of the server's live workspaces.
char *ss_server_file_info(const ss_server *, const char *path)JSON: what a .scana holds, without opening it.
ss_status ss_server_shutdown(const ss_server *)Stops the server, and every other client with it.

Workspace

FunctionDoes
ss_workspace *ss_server_create(const ss_server *, const char *device)Creates a workspace on a device.
ss_workspace *ss_server_open(const ss_server *, const char *path)Opens a .scana file, and waits for it to be read.
ss_workspace *ss_server_attach(const ss_server *, uint64_t workspace_id)Joins an existing one.
uint64_t ss_workspace_duplicate(const ss_workspace *)Copies the workspace, data included; returns the new id.
void ss_workspace_free(ss_workspace *)Releases the handle.
uint64_t ss_workspace_id(const ss_workspace *)Its id.
ss_status ss_workspace_close(const ss_workspace *)Ends it on the server.
char *ss_workspace_device_name(const ss_workspace *)The device it holds, as the interface names it.
char *ss_workspace_channels(const ss_workspace *)JSON array of {name,color,ch_label}, in device order.
int32_t ss_workspace_channel(const ss_workspace *, const char *name)Channel index by name or label, -1 if none.
char *ss_workspace_device_info(const ss_workspace *)JSON: serial, model, firmware, current hardware settings.
ss_status ss_workspace_attach_device(const ss_workspace *, const char *serial)Moves the workspace onto another device.

Capture

FunctionDoes
ss_status ss_capture_start(const ss_workspace *, const char *request_json)Arms from a whole StartCapture payload, and returns at once.
int64_t ss_capture_run(const ss_workspace *, const char *request_json, double timeout_seconds)The same request, armed and waited for in one call. Returns the last sample, -1 on failure.
int64_t ss_capture_wait(const ss_workspace *, double timeout_seconds)Waits for the acquisition; returns the last sample, -1 on failure.
ss_status ss_capture_stop(const ss_workspace *)Stops it. What was captured stays.
char *ss_capture_state(const ss_workspace *)"idle", "armed", "running", "done"
bool ss_capture_running(const ss_workspace *)Whether the device is still filling, waiting or triggering.
char *ss_capture_config(const ss_workspace *)JSON: the configuration the device settled on.
uint32_t ss_capture_trigger_count(const ss_workspace *)How many times the trigger fired, in normal mode.
int64_t ss_capture_last_sample(const ss_workspace *)Last sample captured, -1 before anything was.
uint64_t ss_capture_sample_rate(const ss_workspace *)The rate the device settled on.
double ss_capture_seconds(const ss_workspace *, int64_t sample)A sample index as seconds from the start.
-1 means two things, and ss_last_error() separates them

ss_capture_wait and ss_capture_run return the last sample index, or -1 on failure. Seeking functions answer -1 when there is nothing more to find. To tell the two apart, read ss_last_error() immediately after the call: it is cleared by every function that can fail, so a reason there belongs to the call you just made, and NULL means the call succeeded and the -1 is real.

Read it before calling anything else, exactly as with errno: the next call that can fail clears the slot, whether or not that call fails.

int64_t at = ss_data_next_edge(workspace, 0, from);
if (at < 0) {
const char *why = ss_last_error(); /* before any other call */
if (why) fprintf(stderr, "seek: %s\n", why);
else printf("no edge left on this channel\n");
}

Triggers

Each of these writes the trigger object that ss_capture_start takes. They are the C equivalent of the Trigger helpers in Python, NodeJS and Rust. All of them return a char * to free with ss_string_free.

FunctionDoes
char *ss_trigger_immediate(void)No trigger: sampling starts as soon as the device is armed.
char *ss_trigger_edge(uint16_t channel, const char *edge, double position, const char *mode)"rising" or "falling" on one channel.
char *ss_trigger_logic(const uint16_t *channels, size_t channel_count, double position, const char *mode)Any logic change on these channels.
char *ss_trigger_pulse(uint16_t channel, const char *polarity, double min_width, double max_width, double position, const char *mode)A pulse, widths in seconds; a negative one leaves that end open.
char *ss_trigger_external(const char *edge, const char *impedance, double position, const char *mode)The device's external trigger input.
char *ss_trigger_steps(const char *steps_json, double position, const char *mode)A multi-step pattern.
char *ss_trigger_sequence(const char *first_json, const char *second_json, const char *order)Two engines: "a_or_b", "a_then_b", "b_then_a", "a_and_b".

position is the share of the acquisition kept before the trigger, 0.0 to 0.9. mode is "single" or "normal"; NULL means "single".

Data

FunctionDoes
ptrdiff_t ss_data_transitions(const ss_workspace *, uint16_t channel, int64_t from, int64_t to, ss_edge *out, size_t capacity)Fills out with edges over [from, to). to of -1 reads to the end.
int32_t ss_data_level_at(const ss_workspace *, uint16_t channel, int64_t sample)The level held at a sample, or -1 if unknown.
int64_t ss_data_next_edge(const ss_workspace *, uint16_t channel, int64_t after)The next transition, or -1. One round trip per edge.
int64_t ss_data_previous_edge(const ss_workspace *, uint16_t channel, int64_t before)The last transition before a sample, or -1.
ss_status ss_data_detect_baud(const ss_workspace *, uint16_t channel, double *baud, double *nearest)The bit rate measured, and the nearest standard one. Both are 0 when there are too few transitions to measure.
ss_packets *ss_packets_read(const ss_workspace *, const char *filter_json)The decoded packets a RowFilter keeps; NULL for everything.
size_t ss_packets_count(const ss_packets *)How many the batch holds.
ptrdiff_t ss_packets_copy(const ss_packets *, size_t first, ss_packet *out, size_t capacity)Copies records out.
void ss_packets_free(ss_packets *)Frees the batch, and every string copied from it.
char *ss_data_packet_groups(const ss_workspace *, const char *filter_json)JSON: the Packet View's {instance_id,start,children} groups.
ss_items *ss_items_read(const ss_workspace *, const char *filter_json, const char *hidden_json)Every decoder's items, merged and sorted by start sample.
ss_items *ss_items_read_instance(const ss_workspace *, uint32_t instance_id, int64_t from, int64_t to, int64_t min_length)One decoder's items over a window.
size_t ss_items_count(const ss_items *)How many the batch holds.
ptrdiff_t ss_items_copy(const ss_items *, size_t first, ss_item *out, size_t capacity)Copies records out.
void ss_items_free(ss_items *)Frees the batch.
ss_hex *ss_hex_read(const ss_workspace *, const char *hidden_json)Every decoder's hex bytes, merged.
ss_hex *ss_hex_read_instance(const ss_workspace *, uint32_t instance_id, uint32_t offset, uint32_t count)A page of one decoder's hex store.
size_t ss_hex_count(const ss_hex *)Bytes in this page.
uint32_t ss_hex_total(const ss_hex *)Bytes in the whole store, which is what to page through.
uint32_t ss_hex_generation(const ss_hex *)A different one means the store was cleared.
ptrdiff_t ss_hex_copy(const ss_hex *, size_t first, ss_hex_byte *out, size_t capacity)Copies bytes out.
void ss_hex_free(ss_hex *)Frees the page.
int64_t ss_data_find_hex(const ss_workspace *, const uint8_t *pattern, size_t pattern_len, uint64_t from, bool backwards, const char *hidden_json)Searches the merged Hex View, -1 if not found.

Decoders

FunctionDoes
char *ss_decoder_options(const ss_workspace *, const char *script)JSON array: what the script takes.
uint32_t ss_decoder_add(const ss_workspace *, const char *script, const char *options_json)Attaches a decoder; returns the instance id, 0 on failure.
char *ss_decoders(const ss_workspace *)JSON array of the instances attached.
char *ss_decoder_config(const ss_workspace *, uint32_t instance_id)JSON: one instance's settings.
ss_status ss_decoder_update(const ss_workspace *, uint32_t instance_id, const char *options_json)Changes them, and re-decodes.
ss_status ss_decoder_remove(const ss_workspace *, uint32_t instance_id)Detaches it.
ss_status ss_decoder_relaunch(const ss_workspace *, uint32_t instance_id)Runs it again from the start.
ss_status ss_decoder_pause(const ss_workspace *, uint32_t instance_id)Stops it where it is.
uint8_t ss_decoder_progress(const ss_workspace *, uint32_t instance_id)0 to 100.
ss_status ss_decoder_wait(const ss_workspace *, uint32_t instance_id, double timeout_seconds)Waits for one to finish.
ss_status ss_decoders_wait(const ss_workspace *, double timeout_seconds)Waits for all of them.
char *ss_decoder_vacs(const ss_workspace *, uint32_t instance_id)JSON: the virtual analog channels it produced.

Markers

FunctionDoes
uint32_t ss_marker_add(const ss_workspace *, int64_t sample)Places one; returns its id, 0 on failure.
ss_status ss_marker_move(const ss_workspace *, uint32_t marker_id, int64_t sample)Moves it.
ss_status ss_marker_remove(const ss_workspace *, uint32_t marker_id)Removes it.
ss_status ss_markers_clear(const ss_workspace *)Removes every marker.
ss_markers *ss_markers_read(const ss_workspace *)Reads them all.
size_t ss_markers_count(const ss_markers *)How many.
ptrdiff_t ss_markers_copy(const ss_markers *, size_t first, ss_marker *out, size_t capacity)Copies records out.
void ss_markers_free(ss_markers *)Frees the batch, and every label copied from it.

Measurements

FunctionDoes
char *ss_measure_kinds(void)JSON array: the kinds this server understands.
uint32_t ss_measure_add(const ss_workspace *, uint32_t marker_a, uint32_t marker_b, uint16_t channel, const char *kinds_json, uint8_t time_precision, uint8_t freq_precision)Measures between two markers.
uint32_t ss_measure_between(const ss_workspace *, int64_t start, int64_t end, uint16_t channel, const char *kinds_json, uint8_t time_precision, uint8_t freq_precision)Places both markers and measures between them.
ss_status ss_measure_set_kinds(const ss_workspace *, uint32_t measure_id, const char *kinds_json, uint8_t time_precision, uint8_t freq_precision)Changes what it computes.
ss_status ss_measure_move(const ss_workspace *, uint32_t measure_id, int64_t sample_a, int64_t sample_b, int32_t channel)Moves the whole measurement. -1 keeps the channel.
ss_status ss_measure_remove(const ss_workspace *, uint32_t measure_id)Removes it; its markers stay.
ss_status ss_measures_clear(const ss_workspace *)Removes every measurement.
ss_measures *ss_measures_read(const ss_workspace *)Reads them as they stand.
ss_measures *ss_measures_wait(const ss_workspace *, double timeout_seconds)Waits until nothing is computing, then reads.
size_t ss_measures_count(const ss_measures *)How many.
ptrdiff_t ss_measures_copy(const ss_measures *, size_t first, ss_measure *out, size_t capacity)Copies the measurements out.
ptrdiff_t ss_measure_values_copy(const ss_measures *, size_t index, size_t first, ss_measure_value *out, size_t capacity)Copies one measurement's results out.
void ss_measures_free(ss_measures *)Frees the batch.

Results are in SI units (seconds, hertz, counts). time_precision and freq_precision say what was asked for; the value handed back is unrounded, and formatting it is the caller's job.

Pattern generator

FunctionDoes
char *ss_pattern_options(const ss_workspace *, const char *script)JSON array: what the generator script takes.
char *ss_pattern_add(const ss_workspace *, const char *script, const char *options_json)Attaches it.
char *ss_pattern_config(const ss_workspace *)JSON: its current settings.
ss_status ss_pattern_update(const ss_workspace *, const char *options_json)Changes them.
char *ss_pattern_current(const ss_workspace *)JSON: the generator attached, if any.
ss_status ss_pattern_remove(const ss_workspace *)Detaches it.
ss_status ss_pattern_set_enabled(const ss_workspace *, bool enabled)Arms or disarms generation.
ss_status ss_pattern_set_generate_on_trigger(const ss_workspace *, bool enabled)Whether it fires with the capture's trigger.
bool ss_pattern_generate_on_trigger(const ss_workspace *)Reads that back.
ss_status ss_pattern_preview(const ss_workspace *, double timeout_seconds)Builds the waveform without driving the pins, and waits.
ss_status ss_pattern_resume(const ss_workspace *)Resumes a paused generator.

Saving and exporting

FunctionDoes
ss_status ss_workspace_save(const ss_workspace *, const char *path, double timeout_seconds)Writes a .scana, and waits for it to land.
ss_status ss_workspace_export_csv(const ss_workspace *, const char *path, const char *options_json, double timeout_seconds)Writes CSV from any of the four sources.
char *ss_workspace_path(const ss_workspace *)Where it saves, or NULL if it never has.
bool ss_workspace_dirty(const ss_workspace *)Whether anything changed since the last save.
ss_status ss_workspace_set_ui_state(const ss_workspace *, const char *state, bool mark_dirty)Stores a client's own view state, opaquely.
char *ss_workspace_ui_state(const ss_workspace *)The view state the workspace was opened with, or NULL.

options_json is an ExportOptions: source is "samples", "raw", "packets" or "hex"; channels applies to "samples" and instances to the other three; from_marker and to_marker narrow it to a range.

ss_workspace_export_csv(workspace, "/data/samples.csv",
"{\"source\": \"samples\", \"channels\": [0, 1]}", 600.0);
ss_workspace_export_csv(workspace, "/data/packets.csv",
"{\"source\": \"packets\", \"separator\": \";\"}", 600.0);
The server resolves the path, and freezes the workspace

ss_workspace_save, ss_workspace_export_csv and ss_server_open act on the server's filesystem, not yours. While an export runs the workspace is frozen: anything that would change its data is refused until it finishes.

Scripts

FunctionDoes
char *ss_scripts(const ss_server *)JSON array of every script installed.
char *ss_scripts_library_dir(const ss_server *)Where the server keeps the library, on its own filesystem.
ss_status ss_scripts_sync(const ss_server *, double timeout_seconds)Syncs against the online catalogue, and waits.
char *ss_scripts_online(const ss_server *, bool refresh)JSON: the online catalogue.
char *ss_scripts_install(const ss_server *, const char *urls_json, bool overwrite_modified)Installs from the catalogue.
char *ss_script_import_file(const ss_server *, const char *file_name, const char *source)Imports a script from its source text.
char *ss_script_import_url(const ss_server *, const char *url)Imports one from a URL.
char *ss_script_create(const ss_server *, const char *file_name, const char *template_json)Creates one from a template.
char *ss_script_rename(const ss_server *, const char *file_name, const char *new_file_name)Renames it.
char *ss_script_duplicate(const ss_server *, const char *file_name, const char *new_file_name)Copies it.
char *ss_scripts_delete(const ss_server *, const char *file_names_json)Deletes a JSON array of them.
char *ss_script_set_file_access(const ss_server *, const char *file_name, bool enabled)⚠️ Lets it open paths of its own, not only the files chosen in its GUI.
char *ss_scripts_usage(const ss_server *)JSON: which workspaces use which script.

Events

FunctionDoes
ss_subscription *ss_events_subscribe(const ss_workspace *, const char *kinds_json, ss_event_fn on_event, void *user)A callback per event. NULL kinds means all of them.
void ss_events_unsubscribe(ss_subscription *sub)Ends it, and waits for its thread to stop.
char *ss_event_latest(const ss_workspace *, const char *kind)The last message of this type, as JSON.
char *ss_event_wait(const ss_workspace *, const char *kind, double timeout_seconds)Waits for one. Returns at once if one already arrived.
char *ss_events_log(const ss_workspace *)JSON array: the server's journal for this workspace.
static void on_event(const char *json, void *user) { /* keep it quick */ }

ss_subscription *sub = ss_events_subscribe(workspace, "[\"state\"]", on_event, NULL);
/* ... */
ss_events_unsubscribe(sub); /* before freeing the workspace handle */
The callback runs on the library's thread

The callback runs on a thread the library owns. Keep it quick, guard whatever it touches, and never call ss_events_unsubscribe from inside it, since that would join the thread it is running on. ss_last_error() is thread-local, so a failure inside the callback is not visible from your own thread.

A subscription that falls behind skips events rather than blocking the connection. ss_event_latest() always holds the last event of each kind, so a slow reader may miss intermediate events but always sees the current state.

ss_capture_wait, ss_decoder_wait, ss_decoders_wait, ss_measures_wait and ss_pattern_preview block with a timeout of their own; for anything else, subscribe.

A complete program

Capture on a demo device, read the transitions, decode I²C and read its packets. This is examples/capture.c from the archive:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "scanastudio.h"

static void fail(const char *what) {
const char *reason = ss_last_error();
fprintf(stderr, "%s: %s\n", what, reason ? reason : "no reason given");
exit(1);
}

int main(void) {
printf("protocol %u\n", ss_protocol_version());

ss_server *server = ss_server_connect(NULL);
if (!server) fail("connect");

char *info = ss_server_info(server);
if (info) { printf("server %s\n", info); ss_string_free(info); }

ss_workspace *workspace = ss_server_create(server, "se254");
if (!workspace) fail("create");

const char *request = "{\"samples\": 200000, \"sample_rate\": 25000000}";
if (ss_capture_run(workspace, request, 120.0) < 0) fail("capture");
printf("captured %lld samples at %llu S/s\n",
(long long)ss_capture_last_sample(workspace),
(unsigned long long)ss_capture_sample_rate(workspace));

enum { ROOM = 4096 };
ss_edge *edges = malloc(sizeof(ss_edge) * ROOM);
if (!edges) return 1;
long written = (long)ss_data_transitions(workspace, 0, 0, -1, edges, ROOM);
if (written < 0) fail("transitions");

printf("channel 0 holds %ld transitions\n", written);
for (long i = 0; i < written && i < 5; i += 1) {
printf(" %lld -> %u\n", (long long)edges[i].sample, edges[i].level);
}
free(edges);

int scl = ss_workspace_channel(workspace, "I2C SCL");
int sda = ss_workspace_channel(workspace, "I2C SDA");
if (scl >= 0 && sda >= 0) {
char options[64];
snprintf(options, sizeof options, "{\"ch_scl\": %d, \"ch_sda\": %d}", scl, sda);
unsigned instance = ss_decoder_add(workspace, "i2c.js", options);
if (instance) {
if (ss_decoder_wait(workspace, instance, 300.0) != ss_status_Ok) fail("decode");

char filter[64];
snprintf(filter, sizeof filter, "{\"sources\": [%u]}", instance);
ss_packets *packets = ss_packets_read(workspace, filter);
if (packets) {
size_t count = ss_packets_count(packets);
printf("i2c produced %zu packets\n", count);
ss_packet first;
if (count && ss_packets_copy(packets, 0, &first, 1) == 1) {
/* The strings belong to the batch: read them before freeing it. */
printf(" %s: %s\n", first.title, first.content);
}
ss_packets_free(packets);
}
} else {
printf("i2c.js is not installed; skipping the decode\n");
}
}

ss_workspace_close(workspace);
ss_workspace_free(workspace);
ss_server_disconnect(server);
return 0;
}

Reading a file instead of capturing

examples/inspect.c does no acquisition: it opens a .scana, prints what it was recorded on, walks its edges one round trip at a time, and measures across it.

ss_server *server = ss_server_connect(NULL);
if (!server) fail("connect");

/* What is in the file, before deciding to open it. */
show("file", ss_server_file_info(server, path));

ss_workspace *ws = ss_server_open(server, path);
if (!ws) fail("open");

show("device", ss_workspace_device_name(ws));
show("channels", ss_workspace_channels(ws));
show("hardware", ss_workspace_device_info(ws));

long long last = ss_capture_last_sample(ws);

/* Walk channel 0 edge by edge, without holding the capture in memory. */
long long at = ss_data_next_edge(ws, 0, -1);
for (int seen = 0; at >= 0 && seen < 10; seen += 1) {
printf(" edge at %lld (%.9f s)\n", at, ss_capture_seconds(ws, at));
at = ss_data_next_edge(ws, 0, at);
}

/* Measure across the whole capture, between two markers of our own. */
unsigned a = ss_marker_add(ws, 0);
unsigned b = ss_marker_add(ws, last);
unsigned measure =
ss_measure_add(ws, a, b, 0, "[\"time\", \"frequency\", \"edges\"]", 3, 3);

ss_measures *measures = ss_measures_wait(ws, 60.0);
if (!measures) fail("measures");
for (size_t i = 0; i < ss_measures_count(measures); i += 1) {
ss_measure row;
if (ss_measures_copy(measures, i, &row, 1) != 1) break;
if (row.id != measure) continue;

ss_measure_value *values = malloc(sizeof *values * row.value_count);
ptrdiff_t got = ss_measure_values_copy(measures, i, 0, values, row.value_count);
for (ptrdiff_t v = 0; v < got; v += 1) {
printf(" %s = %g\n", values[v].kind, values[v].value);
}
free(values);
}
/* Every string read above belonged to the batch, so it is freed last. */
ss_measures_free(measures);
ss_server_open waits for the file to be read

It returns once the capture is loaded, so ss_capture_last_sample and ss_data_transitions describe the recording immediately after. See Opening a .scana.