Skip to main content

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.

Read the script list after creating a 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.

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

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
...
Managing the script library

server.scripts also syncs, installs, imports, creates, renames, duplicates and deletes scripts. Those calls belong to the server, and are described in Server.

options​

The options a script takes. A decoder is a script, so the SDK cannot know its settings in advance; ask before you configure.

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 # "ch_selector", "combo", "checkbox", "engineering_input"...
option.default
option.choices # the entries of a combo, empty otherwise
option.unit # "Hz", "%"... empty when there is none
option.tab # the tab it sits under in the dialog, empty at the top

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.

The type is the kind of control the script declares in its dialog, and it decides what value an option takes:

TypeValue you pass
ch_selectorA channel index, e.g. from workspace.channel("I2C SCL").
comboThe entry's text (case-insensitive) or its index in choices.
checkbox, tabTrue / False.
engineering_inputA number in the base unit: 1e6 for 1 MHz.
baud_selector, hidden_intA whole number.
text_inputA string.

In Rust the type is the kind field of ScriptOption.

add​

Attaches a decoder instance and starts it decoding.

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

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 when the script rejects its configuration. Its logs hold the script's own log lines only when the script failed while drawing its dialog. A refusal from add or update comes with empty logs, and the message says what was refused.
  • UnknownOption for an option name the script does not know. It lists the known ones. In Python it is a subclass of KeyError.
  • Invalid values are refused before anything is sent: None for a channel raises TypeError; a non-numeric value, an unknown combo entry or a combo index out of range raises ValueError in Python and RangeError in NodeJS. These do not derive from ScanaStudioError.
Pass channels by name

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​

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 item in i2c.items(): # this decoder's rows of the Raw View
print(item.start, item.content)

i2c.raw_items(start=0, end=100_000) # items with every form of their text
i2c.memory(offset=0, count=5000) # its Memory View, a MemPage
i2c.vacs() # the virtual analog channels it declared
i2c.options() # its current configuration

Packet, item, hex and Memory View fields are described under Reading captured data. Both packets() and items() accept the same filter.

CallReturns
packets()This decoder's Packet View rows.
items()This decoder's Raw View rows (content is the longest form).
raw_items(start, end, min_length)Its items over a window, with contents: every form the script gave, longest first.
hex(offset, count)A page of its hex store. hex_bytes() iterates over all of it.
memory(offset, count)A page of its Memory View: what the script read out of the target, at the target's addresses.
vacs()The virtual analog channels it declared: vac_id, label, unit, color, size, and min/max over the whole curve.
options()Its current configuration, in the same form as decoders.options(script), ready to edit and pass to update.

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.

i2c.wait(timeout=300.0) # this decoder
workspace.decoders.wait() # every attached decoder
i2c.progress # 0-100

Managing instances​

workspace.decoders.all() # every attached instance
workspace.decoders.get(instance_id) # one, or None
len(workspace.decoders)
for decoder in workspace.decoders: # same as all()
decoder.instance_id, decoder.file_name, decoder.name # 1, "i2c.js", "I2C"

workspace.decoders.remove(i2c) # by Decoder or by instance id

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
A demo device decodes noise

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.