WireSkein
WireSkein reads logic-analyzer captures. It has two uses:
- Checking recorded hardware test runs. A test records what it sent, the captures, and what each step should look like on the wire (a 1 kHz square wave, an I2C write to 0x42, a UART at F_CPU / BRR).
wireskein verifychecks every capture against these expectations and reports OK / NG with measured values, as text, JSON and JUnit XML. - Decoding unknown captures.
wireskein analyzefinds which pins carry I2C, SPI, UART, RVSWD / SWIO, SWD or CAN, and decodes them. Upper layers (NMEA, Modbus, known I2C / SPI devices) are tried on top.
Status: beta (0.1.0b1). Breaking changes may still happen; see Stability.
Install
pip install --pre wireskein # or: uv add --prerelease=allow wireskein
Python 3.13 or newer. The only dependency is numpy.
Checking a test run
A test records a run with wireskein.runlog. This module uses only the standard library.
from wireskein.runlog import Recorder, square, level, only_moving
rec = Recorder("out/run1", target="x035")
with rec.section(1, "test_pwm"):
for duty in (64, 128, 0):
want = [square("PA1", 1000, duty / 255), only_moving(["PA1"])] if duty else [level("PA1", 0)]
with rec.section(2, f"duty={duty}", expect=want):
rec.command(f"PWM {duty}") # what the host sent
rec.reply(reply_line) # what the device answered
t = rec.armed() # right after arming the capture
data = read_capture() # bytes, one sample per byte, bit k = pin k
rec.capture(data, rate, ["PA1", "PA0"], t, start_us=segment_start_us)
rec.close()
Then check it:
wireskein verify out/run1 --junit out/run1/report.xml --json out/run1/report.json
OK test_pwm/duty=64 square c0001.bin
NG test_pwm/duty=128 square c0002.bin duty 0.6999 vs 0.5020
OK test_pwm/duty=0 level c0003.bin
2 ok, 1 ng, 0 unchecked (4 segments, 3 captures)
The exit code is 1 when a check fails. A check whose pins are not in the capture is unchecked (--). Unchecked results do not fail the run.
Headings and segments
Headings split the run into a tree of segments. # is a test, ## is a step, and a heading without a name (##) closes that level. Recorder.section() opens a heading and closes it when the with block ends. A capture belongs to the segment in which it was armed. Expectations are stored under the segment path, for example test_pwm/duty=64. When a name repeats under the same parent, each repetition gets an index (duty=64[0], duty=64[1]) and keeps its own expectations. A broken structure, such as a skipped level, is always reported as NG.
Checks
| Helper | Checks |
|---|---|
square(pin, freq_hz, duty, tol_freq, tol_duty, max_jitter) |
A steady square wave: frequency (relative tolerance), duty (absolute), period spread |
level(pin, value) |
The pin does not move |
starts({pin: v}) / ends({pin: v}) |
The level at the first / last sample |
only_moving([pins]) |
No other captured pin moves |
pulses(pin, count, period_s, tol) |
Number of rising edges and their period |
i2c(scl, sda, transactions, hz, tol_hz, released) |
Transactions (address, direction, bytes, ACK, complete), SCL rate, and a released bus at the end |
spi(clk, mosi, miso, cs, mode, mosi_bytes, miso_bytes, hz) |
Mode, bytes on both lines, SCK rate, and CS high at the end |
uart(pin, baud, data, tol_baud, idle, bits, parity, stop, max_errors) |
Bit rate measured from the edges, data, idle level, and framing / parity errors. baud=None only measures |
Pins and roles are given, so these checks are verification, not discovery. docs/capture-test-guide.ja.md explains how to choose capture windows and tolerances, with examples from real runs.
If a capture has the meta time_base_slipped: true (the probe knows some samples were taken late), the verdict does not change, but a failure's reason says so.
Python API
from wireskein.verify import verify, junit, dumps, lines, summary_line
report = verify("out/run1") # dict: results (with measured values), summary, log
xml = junit(report) # JUnit XML
text = dumps(report) # JSON
print(summary_line(report), *lines(report), sep="\n") # NG and unchecked lines, as the CLI prints them
For pytest, pytest-embedded-wireskein gives each test a ws_run recorder and runs verify after the test.
Decoding a capture
wireskein analyze capture.sr # sigrok .sr, or a fixture directory
wireskein analyze capture.sr --hint '{"protocols": ["i2c"]}'
wireskein analyze capture.sr --mode all --out result.json
wireskein segments capture.sr --results # a capture with marker lines on a UART
--hint restricts what is tried. It can name protocols, or give pins with their roles and baud rates. The result is still scored by the decoders' own checks.
from wireskein.analyze import load, analyze, export
cap = load("capture.sr")
res = analyze(cap, {"protocols": ["spi"]})
doc = export(res, cap)
Stability
| Part | Promise during the beta |
|---|---|
wireskein.runlog (names, arguments and meaning of Recorder and the check helpers) |
Stable. New arguments get defaults that keep the old meaning |
Run format (run.json, FORMAT = "wireskein-run/0") |
Stable. An incompatible change raises FORMAT, and verify refuses older runs with a clear error |
wireskein.verify.verify / junit, wireskein verify |
Stable. Report fields may be added |
wireskein.analyze, wireskein analyze / segments output |
May change |
wireskein._engine |
Internal |
Repository layout
src/wireskein/ the package (runlog, verify, analyze, cli, _engine, decl/ data)
tests/ pytest
research/ evaluation scripts, benchmarks and findings (not packaged)
corpus/ real and synthetic fixtures for research/
docs/ design notes (Japanese)
To run the research scripts: uv sync --group research, then uv run python research/evaluate.py --synth 200 --engine staged. See research/README.ja.md.
Development
uv run pytest
uv build
Release
Releases use GitHub Actions, the same way as pytest-embedded-arduino-cli.
- Update the
## Unreleasedsection ofCHANGELOG.md. - Run the
Releaseworkflow manually and enter the version, for example0.1.0b1. - The workflow does the rest. It updates the version in
pyproject.tomlandsrc/wireskein/__init__.py, moves the changelog entries under the new version, runs the tests, builds, commits, tagsv<version>, creates a GitHub Release, and publishes to PyPI.
PyPI publishing uses Trusted Publishing.
License
MIT
Metadata
Release files for wireskein 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wireskein-0.0.1.tar.gz | 114.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wireskein-0.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 237.0 kB
Release files / wireskein-0.0.1.tar.gz
| Download URL | wireskein-0.0.1.tar.gz |
|---|---|
| Size | 114.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c50eb4168f2d75371934ff6add448de59a1112e1bede322775cd5696b6c3d9d8
|
|
BLAKE2b-256 checksum How to use checksums |
8e0a293706687363da076ac1b5d1a247a6a1d16a029dd99c8cfe9e54938b7e6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency logRelease files / wireskein-0.0.1-py3-none-any.whl
| Download URL | wireskein-0.0.1-py3-none-any.whl |
|---|---|
| Size | 122.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
758a2aec1acabc3f869cb9526eed242e836902d32b636dee57cc978d7d3a9c5a
|
|
BLAKE2b-256 checksum How to use checksums |
b93f16024ba8cd4a440ee68f840f4cd8c51a0e219453a545c3a4313008f01149
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.
Transparency log