Decoders
A protocol decoder turns logic transitions into packets: addresses, data bytes, ACKs, frame errors. Decoders are the JavaScript files in the server's script library: the same ones ScanaStudio uses, and the same ones you can write yourself.
Reached through workspace.decoders.
Finding the available scripts​
The script library belongs to the server, not to a workspace, so it is reached through the client rather than the workspace.
The server sends its script list shortly after connect() returns, so reading
it immediately can give an empty list, and scripts.find("i2c.js") then reports
the decoder missing when it is installed.
Two reliable orders:
# 1. Create the workspace first. By then the list has arrived.
workspace = server.create("se254")
if server.scripts.find("i2c.js") is None:
...
# 2. Or wait for the list, if you must check before opening anything.
import time
deadline = time.monotonic() + 10.0
while not server.scripts.all() and time.monotonic() < deadline:
time.sleep(0.05)
decoders.add() itself is unaffected: it names the script on the server, which
knows its own library. Only the client-side listing is affected.
- Python
- NodeJS
- Rust
- C
for script in server.scripts.decoders():
script.file_name # "i2c.js" — what add() takes
script.name # "I2C"
script.version
script.author
script.description
server.scripts.find("i2c.js") # a ScriptDesc, or None
server.scripts.all() # every script, decoders and generators
server.scripts.generators() # pattern-generator scripts
server.scripts.library_dir # where they live on the server
for (const script of server.scripts.decoders()) {
script.file_name; // "i2c.js" — what add() takes
script.name; // "I2C"
script.version;
}
server.scripts.find('i2c.js'); // ScriptDesc | undefined
server.scripts.all();
server.scripts.generators();
server.scripts.library_dir;
// file_name ("i2c.js") is what add() takes; name ("I2C") is for display.
for script in server.scripts().decoders() {
println!("{} — {}", script.file_name, script.name);
}
server.scripts().find("i2c.js");
server.scripts().all();
server.scripts().generators();
server.scripts().library_dir();
/* A JSON array of every script installed. */
char *scripts = ss_scripts(server);
if (scripts) { printf("%s\n", scripts); ss_string_free(scripts); }
char *dir = ss_scripts_library_dir(server); /* on the SERVER's filesystem */
if (dir) { printf("%s\n", dir); ss_string_free(dir); }
ss_decoder_add accepts a script file name directly, so you can name i2c.js
without listing the library first.
Example output. A stock library holds 44 scripts, 42 decoders and 6 generators (a few do both):
1-wire.js — 1-Wire
ADC.js — ADC
FDX-B.js — FDX-B
IR_NEC.js — IR_NEC
LPC (Low Pin count).js — LPC (Low Pin Count)
NMEA_2000.js — NMEA_2000
...
The SDKs can also install, create, rename and delete scripts, but this documentation leaves that out (see Feature coverage). If a decoder is missing from a bench, install it in ScanaStudio once.
options​
The options a script takes. A decoder is a script, so the SDK cannot know its settings in advance; ask before you configure.
- Python
- NodeJS
- Rust
- C
for option in workspace.decoders.options("i2c.js"):
print(option)
option.id # "ch_scl" — what add() keys on
option.caption # "SCL channel" — also accepted as a key
option.type # "channel", "combo", "number"...
option.default
option.choices # for a combo
option.unit
import { describe_option } from '@ikalogic/scanastudio';
// A decoder is a script, so ask it what it takes rather than guessing.
for (const option of await workspace.decoders.options('i2c.js')) {
console.log(describe_option(option));
option.id; // "ch_scl" — what add() keys on
option.caption; // "SCL channel" — also accepted as a key
option.type; // "channel", "combo", "number"...
option.default;
}
// A decoder is a script, so ask it what it takes rather than guessing.
for option in workspace.decoders().options("i2c.js").await? {
println!("{option}"); // ScriptOption implements Display
}
/* A JSON array of the script's options — ask it rather than guessing. */
char *options = ss_decoder_options(workspace, "i2c.js");
if (options) { printf("%s\n", options); ss_string_free(options); }
Example output. i2c.js on an SE254. Each line is
id [tab]: caption (type) choices unit = default:
ch_sda: SDA Channel (ch_selector) = None
ch_scl: SCL Channel (ch_selector) = None
AUTO0: Advanced options (tab) = False
address_opt [Advanced options]: Address convention (combo) one of ['7 bit address', '8 bit address (inlcuding R/W flag)'] = '7 bit address'
address_format [Advanced options]: Address display format (combo) one of ['HEX', 'Binary', 'Decimal'] = 'HEX'
data_format [Advanced options]: Data display format (combo) one of ['HEX', 'Binary', 'Decimal', 'ASCII'] = 'HEX'
en_noise_flter [Advanced options]: Ignore high-frequency noise on data and clock lines (checkbox) = False
Only ch_scl and ch_sda have no default, so they are the two you must supply.
Everything under [Advanced options] can be left alone.
add​
Attaches a decoder instance and starts it decoding.
- Python
- NodeJS
- Rust
- C
i2c = workspace.decoders.add(
"i2c.js",
{
"ch_scl": workspace.channel("I2C SCL"),
"ch_sda": workspace.channel("I2C SDA"),
},
wait=True, # block until decoding is finished
)
i2c.instance_id
i2c.name
// Channels by name, so the same code works on any device that has them.
const i2c = await workspace.decoders.add(
'i2c.js',
{
ch_scl: workspace.channel('I2C SCL'),
ch_sda: workspace.channel('I2C SDA'),
},
{ wait: true }, // block until decoding is finished
);
use scanastudio_client::values;
// Channels by name, so the same code works on any device that has them.
let scl = workspace.channel("I2C SCL")?;
let sda = workspace.channel("I2C SDA")?;
let i2c = workspace.decoders()
.add("i2c.js", &values([("ch_scl", scl.into()), ("ch_sda", sda.into())]))
.await?;
// Rust does not take a `wait` flag; wait explicitly.
workspace.decoders().wait_for_instances(&[i2c.instance_id], Duration::from_secs(300)).await?;
int scl = ss_workspace_channel(workspace, "I2C SCL");
int sda = ss_workspace_channel(workspace, "I2C SDA");
char options[64];
snprintf(options, sizeof options, "{\"ch_scl\": %d, \"ch_sda\": %d}", scl, sda);
/* Returns the instance id, or 0 on failure. */
uint32_t i2c = ss_decoder_add(workspace, "i2c.js", options);
if (i2c == 0) fprintf(stderr, "add: %s\n", ss_last_error());
ss_decoder_wait(workspace, i2c, 300.0);
Parameters:
- the script's file name, e.g.
i2c.js; - the option values, keyed by id or caption. Anything you leave out keeps the script's own default;
wait— block until decoding finishes, rather than returning immediately.
Returns: the decoder instance.
Errors: ScriptError carries the script's own log lines when it rejects its
configuration or fails; they say what the script did not like. An option name
the script does not know raises UnknownOption, which lists the known ones.
workspace.channel("I2C SCL") keeps one test script working across every device
that has those channels. Hard-coded channel numbers break when the bench
changes.
Reading a decoder's results​
- Python
- NodeJS
- Rust
- C
for packet in i2c.packets():
print(packet.start, packet.title, packet.content)
for entry in i2c.hex_bytes(): # this decoder's bytes
print(hex(entry.byte))
page = i2c.hex(offset=0, count=5000) # a page of them
page.total
page.bytes
for await (const packet of i2c.packets()) {
console.log(packet.start, packet.title, packet.content);
}
// This decoder's bytes.
for await (const entry of i2c.hex_bytes()) {
console.log(entry.byte.toString(16));
}
// Or a page of them.
const page = await i2c.hex({ offset: 0, count: 5000 });
// Rust reads a decoder's results through DataApi, filtered by instance.
let filter = RowFilter { sources: vec![i2c.instance_id], ..Default::default() };
let packets = workspace.data().packets(&filter).await?;
// A page of this decoder's hex view.
let page = workspace.data().instance_hex(i2c.instance_id, 0, 5000).await?;
/* Filter by instance to read one decoder's packets. */
char filter[64];
snprintf(filter, sizeof filter, "{\"sources\": [%u]}", i2c);
ss_packets *batch = ss_packets_read(workspace, filter);
ss_packet first;
if (batch && ss_packets_count(batch) &&
ss_packets_copy(batch, 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(batch);
/* A page of this decoder's hex view, and its items over a window. */
ss_hex *page = ss_hex_read_instance(workspace, i2c, 0, 5000);
ss_items *items = ss_items_read_instance(workspace, i2c, 0, -1, 0);
ss_hex_free(page);
ss_items_free(items);
Packet fields are described under
Reading captured data, and both
packets() and items() accept the same filter.
Example output. The first packets of a decoded capture:
496 I2C CH4
433 START
1138 RE-START
1837 STOP
wait and progress​
Decoding runs on the server, asynchronously. progress is 0–100.
- Python
- NodeJS
- Rust
- C
i2c.wait(timeout=300.0) # this decoder
workspace.decoders.wait() # every attached decoder
i2c.progress # 0-100
await i2c.wait(300_000); // this decoder
await workspace.decoders.wait(); // every attached decoder
i2c.progress; // 0-100
workspace.decoders().wait_for_instances(&[i2c.instance_id], Duration::from_secs(300)).await?; // these
workspace.decoders().wait(Duration::from_secs(300)).await?; // all of them
workspace.decoders().progress(i2c.instance_id); // 0-100
ss_decoder_wait(workspace, i2c, 300.0); /* one instance */
ss_decoders_wait(workspace, 300.0); /* all of them */
uint8_t percent = ss_decoder_progress(workspace, i2c); /* 0-100 */
Managing instances​
- Python
- NodeJS
- Rust
- C
workspace.decoders.all() # every attached instance
workspace.decoders.get(instance_id) # one, or None
len(workspace.decoders)
i2c.update({"ch_scl": 5}) # reconfigure and restart decoding
i2c.relaunch() # decode again from scratch
i2c.pause() # stop decoding; results produced so far stay
i2c.remove() # detach it
workspace.decoders.clear() # detach every decoder
workspace.decoders.all(); // every attached instance
workspace.decoders.get(instance_id); // one, or undefined
workspace.decoders.length;
await i2c.update({ ch_scl: 5 }); // reconfigure and restart decoding
i2c.relaunch(); // decode again from scratch
i2c.pause(); // stop decoding; results produced so far stay
i2c.remove(); // detach it
workspace.decoders.clear(); // detach every decoder
workspace.decoders().all(); // every attached instance
workspace.decoders().get(instance_id); // one, or None
// Rust drives instances through DecodersApi rather than a Decoder handle.
workspace.decoders().update(i2c.instance_id, &values([("ch_scl", 5i64.into())])).await?;
workspace.decoders().relaunch(i2c.instance_id)?; // decode again from scratch
workspace.decoders().pause(i2c.instance_id)?; // stop; results so far stay
workspace.decoders().remove(i2c.instance_id)?; // detach it
char *all = ss_decoders(workspace); /* JSON: every instance */
if (all) { printf("%s\n", all); ss_string_free(all); }
char *config = ss_decoder_config(workspace, i2c); /* JSON: one instance */
if (config) { printf("%s\n", config); ss_string_free(config); }
ss_decoder_update(workspace, i2c, "{\"ch_scl\": 5}"); /* and re-decode */
ss_decoder_relaunch(workspace, i2c); /* decode again from scratch */
ss_decoder_pause(workspace, i2c); /* stop; results so far stay */
ss_decoder_remove(workspace, i2c); /* detach it */
Running a decoder on a demo device finds structure by chance, because the signal is pseudo-random rather than protocol traffic. Assert that decoding happened, not that a particular frame came out.