Skip to main content

openm454

English | 简体中文

CI

An open driver, command-line interface and MCP server for the DreamSourceLab DSCope M454 USB mixed-signal oscilloscope (4 analog channels up to 400 MSa/s, 8 digital channels, built-in 0-3 V signal generator), written so that an AI agent (Claude Code, or any MCP client) can capture and analyze real signals by itself, without the vendor GUI.

The M454 speaks a newer ("v3") USB protocol than the devices supported by the open-source DSView. The protocol was recovered from DSWave 1.2.1 by static analysis, by driving DSWave's own hardware layer through injected commands and by USB tracing; see docs/PROTOCOL.md.

Use

Requirements: Windows, Python 3.10+, the scope bound to WinUSB (it is by default), and DSWave 1.2.1, needed once to extract the FPGA bitstream (it is never redistributed). Close DSWave before using the driver: the scope accepts one USB client.

pip install openm454            # or, from a clone: pip install -e .

Point OPENM454_DSWAVE at DSWave's DSWave.exe (or its folder), or copy the DSWave folder to DSWave/ in the openm454 folder: the clone itself, or for an installed copy %LOCALAPPDATA%\openm454 (~/.local/share/openm454 elsewhere; OPENM454_HOME overrides it). That folder also holds the cached bitstream (firmware/), state (state/) and captures (captures/).

python -m openm454 info
python -m openm454 capture -c A1,D0 --vdiv 1V --offset 1.5V --timebase 200us --trig A1:rise:1.4V
python -m openm454 watch -c A1 --every 0.5s --count 10 --fields A1.freq_hz,A1.vpp_v
python -m openm454 awg set --wave sine --freq 10k --low 0V --high 2V
python -m openm454 analyze captures/<file>.npz --window=-1ms:1ms
python -m openm454 capture -c D0,D1 --rate 20M --duration 2ms --trig D0:pulse>:200us --decode "i2c:scl=D0,sda=D1"
python -m openm454 selftest

Recording modes and measured speeds

Command How it records Length Rates
capture into the 512 MiB sample memory, read out afterwards (250-265 MB/s) one record: 400M samples per channel / 512 MiB up to 400 MSa/s (1 channel)
stream every record is sent while it is acquired, at any data rate (--record-first: above 102.4 MB/s acquire it first and read it out afterwards, as DSWave does); longer than one record or --duration forever: record after record, each refreshing the previous one unlimited; between records ~0.2 s while the readout keeps up, plus the rest of the record above it (0.7 s at 250 MB/s; 2.4 s with --record-first); the last record is kept (--save-each: all) as capture: analog and digital at their own rates
log loop mode: one endless gap-free stream, kept in RAM (default; written once at the end; a RAM ring keeps the newest part of longer runs) or, with --storage disk, dumped in chunk files while running until openm454 stop or the duration; RAM budget / disk allowance one rate for all channels, 100 MHz / N for any integer N (1 kSa/s..100 MSa/s, e.g. 33.333 MSa/s); interleaved 200 MSa/s (<= 2 analog, <= 4 digital channels) and 400 MSa/s (<= 1 analog, <= 2 digital)

Measured on the M454 over USB 3 with every input on a 1 kHz square, every period checked for gaps (2026-11-01/02):

  • The data rate is rate x (analog channels + digital channels / 8) bytes/s.
  • stream: real time at DSWave's 100 MB/s ceiling and at 200 MB/s. At 400 MB/s the readout ran at 270-290 MB/s and the rest waited in the sample memory; the record arrived complete. 12 channels at 50 MSa/s (250 MB/s, ~2 s records): 0.74 / 0.66 s between records when sent while acquired (the default), 2.35 / 2.45 s with --record-first; records complete and continuous.
  • log: 150 MB/s (2 analog + 8 digital at 50 MSa/s) for 3 min (27 GB) and for 30 s to disk with a constant host lag; 200 MB/s (2 analog at 100 MSa/s) for 20 s without backlog; 5 MB/s for 10 min. At 250 MB/s (4 analog + 8 digital at 50 MSa/s) the readout reached ~200 MB/s and the backlog grew ~55 MB/s. A 300 MB/s burst of 3 s (2 analog + 8 digital at 100 MSa/s) read out at 266 MB/s and stayed complete.
  • log above 100 MSa/s interleaves the converters exactly as capture does (DSWave never does this in loop mode). Verified on the scope: 1 analog channel at 200 and 400 MSa/s, 2 analog at 200, 2 digital at 400, 4 digital at 200 and mixed sets. 2 digital channels at 400 MSa/s or 4 at 200 MSa/s are 100 MB/s and run without a time limit. 1 analog channel at 200 MSa/s (200 MB/s) ran 10 s without backlog.
  • The sample memory is a 512 MiB ring in loop mode: the host may fall up to ~512 MiB behind without losing data (host stalls of 10 s at 50 MB/s were absorbed). Beyond that the scope overwrites unread data without any flag and keeps streaming: from then on the data are garbage (one phase step on analog, the digital channels misaligned) until the run is restarted. Measured with the driver's backlog estimate, the break came at 508-514 MiB (three runs), so log stops at 480 MiB.
  • log --plan (no device needed) prints the verdict for a rate, the gap-free limit and recommendation (next section), how long the run can last (max_duration_s) and where the data goes: RAM by default (refused when it does not fit the budget unless --keep-last), --storage disk for ~256 MB chunk files (it warns about SSD wear: 200 MB/s is 720 GB per hour), --storage none for statistics only.
  • bench measures this PC (free RAM, decode speed, disk write speed, USB readout) and records how much RAM and disk the owner allows for recordings and the data folder (bench --ram 8GB --disk 50GB --dir <folder>, state/host.json); log takes its defaults from there. Measured here: decode 468 MB/s, USB readout 206 MB/s (4 analog + 8 digital) / 267 MB/s (2 analog + 8 digital).

Above the readout limit: how log decides

D = the data rate; R = how fast this PC reads the scope while it acquires. R depends on the channel set (measured here: 202 MB/s for 4 analog + 8 digital at 50 MSa/s, 264 MB/s for the same at 100 MSa/s and for 2 analog + 8 digital at 100 MSa/s, 275 MB/s for 4 analog at 100 MSa/s).

  1. R is measured, not assumed: the first log above 150 MB/s with a channel set runs a probe of up to 2 s (a probe line) and remembers R in state/host.json: the measured rate, or "kept up" (R >= D) when it reads at

    = 99 % of D. Later runs above 150 MB/s refresh it when the result is clear (still falling behind at a steady rate at the stop, or caught up at the end), and so do bench and --remeasure. Until then --plan assumes 190 MB/s and marks the plan provisional.

  2. D <= R: the host keeps up (sustained / near_limit); the run lasts as long as the storage allows.
  3. D > R: the scope gets D - R ahead per second (backlog_growth_mb_s), and it sends nothing after a stop, so the unread part is lost. A log is gap-free for at most gapfree_max_s = 480 MiB x R / (D x (D - R)), the signal the host has read when the scope is 480 MiB ahead. A shorter log runs normally.
  4. A longer log (or forever) is compared with what stream would do with the same channels and settings (stream_alternative, from stream's own planning): records of up to 400M samples per channel / 512 MiB, sent while acquired, with gaps of record x (D - R) / R + 0.18 s, at stream's rates (1-2-5 steps x interleave; it may run a domain slower, e.g. 2 analog + 8 digital at 100 MSa/s samples the digital channels at 50 MSa/s):
    • STREAM_BETTER (exit 2): stream samples every channel at least as fast, and its records are at least as long as the log's one gap-free piece (or one record holds the whole duration). The error carries the stream command, with the same vertical settings.
    • NEEDS_FORCE (exit 2): the log's piece is the longest gap-free recording possible but shorter than asked. --force records it and then stops (stop_reason ring_full, ok, with a warning); the rest of the requested time is not recorded. Both errors carry the numbers in error.plan.
  5. While running, every statistics line has backlog_mb; while the backlog grows, also backlog_growth_mb_s, ring_full_in_s and gapfree_projected_s. The result has unread_at_stop_mb (acquired, never read).

Examples here: 12 channels at 50 MSa/s (250 MB/s, R = 204): gap-free 8.88 s vs stream records of 2.03 s with 0.64 s gaps, so NEEDS_FORCE; forced, the run stopped after 8.84 s (8.30 s with R = 202 in an earlier run), every period present up to the last sample. 12 channels at 100 MSa/s (500 MB/s, R = 264): 1.13 s vs 1.02 s records with 1.09 s gaps, still NEEDS_FORCE; with a readout below ~250 MB/s stream would win. 200 MB/s read at 190 MB/s: 10 MB/s backlog growth, the guard after ~50 s, 47.8 s of gap-free signal.

The backlog is estimated as D x (time since the head) - bytes received, checked on every USB transfer, against a baseline that follows the scope / PC clock difference: it may rise by at most 200 ppm x D per second, and only while the host is caught up (estimate below 16 MiB), so the estimate stays exact over hours and a real backlog is never absorbed. A guard stop while nothing arrives at all is reported as a stalled scope (TIMEOUT_DATA); a forced log that was planned to end at the guard reports ring_full, any other guard stop HOST_TOO_SLOW.

log or stream?

Both record untriggered, but they are not the same mode of the scope. log is one endless run (loop mode); stream is a series of records, each programmed with a fixed length that the scope stops at.

log stream
gaps none inside a run one per record (refresh)
gap between two runs, 12 channels at 20 MSa/s (100 MB/s) 0.18 s 0.19 s
gap between two runs, 12 channels at 50 MSa/s (250 MB/s, above the readout limit) 0.51 s after a 1.5 s run: 0.33 s to read the run's backlog (data the scope acquires meanwhile are dropped), then 0.18 s restart 0.66-0.74 s after a ~2 s record: the rest of the record is read (sent while acquired), then the restart; 2.33-2.45 s with --record-first (the whole record read out after it was acquired)
rates one rate for every channel: 100 MHz / N for any integer N, interleaved 200 / 400 MSa/s on 1-2 channels analog and digital at their own rates (e.g. 1 analog at 400 MSa/s with 8 digital at 100 MSa/s), from DSWave's 1-2-5 x interleave table (e.g. 80 MSa/s)
above the readout limit (~200-275 MB/s) one gap-free piece of at most 480 MiB x R / (D x (D - R)) (the scope sends nothing after a stop; see above) records of up to 512 MiB / D (400M samples per channel), each sent while acquired, with gaps
below ~51 kB/s (e.g. 1 channel at 1-20 kSa/s) supported the rate is raised
protocol decoders offline on the saved file (analyze --decode) --decode on the kept record
register program DSWave's Data Acquisition mode (interleaving: this driver's extension, verified) DSWave's own program for this window (golden-tested)

The restart (~0.18 s) is the same for both; the difference is what has to be read out before it. Use log for continuous recording; stream for different analog and digital rates, decoding the record, or above the readout limit when log says STREAM_BETTER or the whole time matters more than one long gap-free piece.

python -m openm454 bench --ram 8GB --disk 50GB --dir logs
python -m openm454 log --plan -c A0,D0-D7 --rate 1M --duration 10min
python -m openm454 log -c A0,D0-D7 --rate 1M --duration 10min                   # kept in RAM, one .npz at the end
python -m openm454 log -c A0,D0-D7 --rate 1M --duration forever                 # RAM ring; end it with: python -m openm454 stop
python -m openm454 stream -c D0-D7 --rate 100M --duration 60s --every 1s

Every command prints one JSON document (watch, stream and log: one per line, then the result), whether or not --json is given: measurements, applied settings, file paths. Samples are never printed (unless --dump N asks for a decimated series); captures are saved as .npz (raw codes + scaling) and .png. python -m openm454 <command> --help lists every option. Python API: openm454.scope.Scope, openm454.capture.load_capture, Capture.decode().

Protocol decoders (--decode SPEC, repeatable, on capture, watch, stream and analyze): uart:rx=D4,baud=115200|auto,bits=8,parity=none,stop=1,invert=0, spi:clk=..,mosi=..,miso=..,cs=..,mode=0..3, i2c:scl=..,sda=.., can:rx=..,bitrate=500k|auto,invert=0. They report every frame with trigger-relative times and its errors (parity/framing, partial words, NACKs, CRC/stuff/form errors), and remove the comparator chatter of slow edges. Output format: docs/schemas/decode.json. Decoding needs about 10 samples per bit or clock (I2C: 9 per SCL period, no errors; 5-6 per period still decode with well under 1 % errors). They have been checked against real buses (SPI up to 18 MHz, RS232, CAN up to 1 Mbit/s, I2C up to 400 kHz) generated by a microcontroller, including deep captures and decoded streams.

CLI reference

pip install openm454 installs the openm454 command (python -m openm454 ... is the same) and the openm454-mcp server. Global options, before the subcommand: --max-wall T (watchdog: stop and release the device after T or the budget computed from the command, whichever is longer; it never shortens the computed budget), --verbose (wire log on stderr), --json (accepted, output is always JSON). python -m openm454 <command> --help lists every option.

Command Purpose Main options
info device, firmware, calibration, limits
capture one triggered (or untriggered) record: measurements, .png, .npz see below
watch repeated captures, one JSON line each (monitoring) the capture options, --every 0.5s, --count N (default 20 when neither --count nor --for is given), --for T, --fields A1.freq_hz,..., --stop-when EXPR, --save-last
stream untriggered recording sent while it is acquired, live statistics the capture options, --every 1s, --save-each, --record-first
log gap-free endless recording (loop mode), one rate for all channels -c, --rate 1M, --duration 10s|forever, --storage ram|disk|none, --keep-last, --chunk, --ram, --plan, --force, --remeasure, --out, --tag, --awg, the vertical options
bench this PC's recording limits (free RAM, decode and disk speed, USB readout); stores the RAM / disk allowance --ram 8GB, --disk 50GB, --dir, --disk-test-mb, --no-device
awg set continuous waveform on the MCX output (0..3 V) --wave sine|square|triangle|noise, --freq 1k, --low/--high or --vpp/--dc, --duty 50%, --phase
awg sweep frequency sweep, repeating --start, --end (required), --time 100ms, --log, --wave, --high, --low, --duty
awg burst N periods of the carrier every period --cycles, --period 10ms, --freq, --wave, --high, --low, --duty
awg mod AM / FM / FSK of the carrier --kind am|fm|fsk, --mod-wave, --mod-freq, --depth, --deviation, --hop, --freq, --wave, --high, --low, --duty
awg dc steady DC level (0..3 V, 6.4 mV steps) --level (required)
awg off, awg status park the output at 0 V / show the last setting
plot render a saved .npz to PNG (no device) file, --window, --channels, --out
analyze measure a saved .npz (no device) file, --window, --channels, --edges N, --dump N, --decode SPEC
selftest verify the scope (Self-test) --quick, --no-awg, --fixture file|standard
release make sure the device is idle and released
recover reset and reload the FPGA of a hung scope, like a power-up (a scope that does not answer register reads still needs a replug)
stop ask a running watch / stream / log (e.g. in the background) to stop cleanly
debug maintenance: regs-read, regs-write (same safety policy), plan (print the register program for capture options without touching the device)

capture, watch and stream share these options:

  • channels / vertical: -c A0-A3,D0-D7 (also analog, digital), --vdiv 1V, --offset 0V, --coupling dc|ac, --bw20 A1,A2, --invert A2, --probe 10x|A2=10x, --threshold 1.4V. Per-channel values are written A0=500mV,A2=2V.
  • timing: --timebase (window = 10 divisions), --duration, --samples, --rate.
  • trigger: --trig SRC:EDGE[:LEVEL] (e.g. A1:rise:1.4V, D0:fall, D0:pulse>:200us), --trig-mode auto|normal|force (default auto), --trig-pos 50%, --auto-after 0.3s, --timeout 5s (normal-mode wait), --holdoff, --sensitivity 0..15 (default 5).
  • output: --measure basic|full|none, --edges N, --dump N, --window t0:t1, --no-plot, --no-save, --out, --tag, --awg 'sine,10k,0V,2V[,duty%]' (set the generator first), --decode SPEC (repeatable), --decode-limit (default 50 frames).

log takes the vertical options too; its timing is --rate and --duration (see the recording sections above). A value that starts with - needs the = form on Python 3.10-3.12 (--window=-1ms:1ms, --offset=-1.5V; per channel A1=-1.5V is fine as it is); the MCP server does this itself. Exit codes: 0 ok, 1 other (a failed self-test, an internal error, interrupted), 2 bad arguments, 3 scope not found, 4 busy, 5 no trigger, 6 device / protocol / watchdog failure, 7 refused by the safety rules (see Errors).

Self-test

python -m openm454 selftest checks the scope itself. The M454 has no internal test signal, calibration output or loopback path: its only signal source is the generator output, and the vendor's own auto-calibration even wants the inputs floating. So the checks fall in two groups.

Wiring-free checks run on any wiring. They only read the inputs and never touch the generator (about 4-6 s):

  • V01 device info
  • V12 a forced capture of all 12 channels (A0-A3, D0-D7) at 100 MSa/s
  • V13 the top rates and fold modes: 1 analog channel at 400 MSa/s, 2 analog at 200 MSa/s, 2 digital at 400 MSa/s, 4 digital at 200 MSa/s
  • V11 capture latency

Signal checks (V00, V02-V10: calibration square, V/div, sample-rate consistency, digital channels and threshold, trigger position, digital trigger, no-trigger timeout, generator loopback) need the standard fixture below and are reported as skipped, with the reason, when the wiring is not declared as that fixture. Their tolerances are, for instance, 1 kHz +-2 %, top 2.8 V +-12 %, base 0 V +-0.15 V, duty 50 +-3 %.

Input Standard fixture
A0 the scope's own signal-generator output
A1 (1x probe), A2 (10x probe), A3 (1x probe) an external ~2.8 V (0 to ~2.8 V), 1 kHz, 50 % duty square wave
D0, D2, D5, D7 the same square wave
D1, D3, D4, D6 left open

Declare the wiring once, in state/fixture.json of the data folder, or for a single run with --fixture standard:

python -c "from openm454 import fixture; fixture.save('standard', 'standard')"
python -m openm454 selftest                      # uses the recorded wiring
python -m openm454 selftest --fixture standard   # the fixture is wired right now (this run only)

fixture.save(analog='standard', digital='standard', note=None) writes {"analog": ..., "digital": ..., "since": ...}; "standard" for a group of leads means the standard fixture (any other value means something else is connected and the signal checks on those leads are skipped). A missing file means unknown wiring, so a fresh install runs only the wiring-free checks; so does a file that cannot be read (the result's note says why; a UTF-8 byte-order mark is fine). Update the file whenever the wiring changes.

The generator is used only with the analog fixture and without --no-awg: V09 parks it at 0 V for the no-trigger timeout, and V10 drives a 2 Vpp sine and a 0-3 V square into A0, then turns it off (also when one of its captures fails). --no-awg (and the MCP default) leaves the generator untouched; V10 is then left out of the results, and V09 is skipped when the last awg setting left the generator running (run awg off first to include it). --quick runs fewer repetitions (about half the time). The result lists every check with measured and expected; the exit code is 1 when a gating check failed.

MCP server

openm454-mcp (or python -m openm454.mcp_server) serves the scope over the Model Context Protocol (stdio, no extra dependencies; it is installed with pip install openm454):

claude mcp add openm454 -- openm454-mcp

If pip's Scripts folder is not on PATH, give the full path to openm454-mcp. Other clients: {"mcpServers": {"openm454": {"command": "openm454-mcp"}}}. Each tool call runs one CLI command in a child process, so the scope is opened and released per call, a hang never blocks the server, and every rule of the CLI applies.

11 tools:

Tool Runs Key arguments (defaults)
status info none. Connected? model, USB speed, firmware / FPGA state, last generator setting
capture capture channels (A0-A3,D0-D7), timebase / duration / rate / samples, trigger (SRC:EDGE[:LEVEL]), trig_mode (normal with a trigger, force without), timeout_s (5), trig_pos, holdoff, decode, edges, window, save (true), plot (true), the vertical options (vdiv, offset, probe, coupling, bw20, threshold). Per channel frequency, Vpp, min/max/mean, duty, rise/fall (digital: state, frequency, duty, edges), trigger result, decoded frames, file paths. A trigger that does not fire gives TIMEOUT_NO_TRIGGER
analyze analyze file (required), channels, window, edges, decode; measurements of a saved .npz
plot plot file (required), channels, window, out; returns the .png path
awg awg set / dc / off / status action (required: set, dc, off, status); wave, freq, high, low (or vpp, dc), duty, phase; level for dc. The output keeps running after the call
log_plan log --plan rate (required), channels, duration, storage, keep_last, force; no device needed
record_start log or stream in the background mode (required: log or stream), channels, rate, duration, every, storage (log: ram / disk / none; stream: only none, nothing saved), keep_last and force (log only), decode (stream only); returns a job id. One recording at a time, the scope is busy meanwhile
record_status none (reads the job's output) job (the latest), lines (3); latest status lines, and the result once done
record_stop stop job, wait_s (120); stops cleanly and returns the result
release release none; frees a scope left running by a crashed program
selftest selftest quick (true), fixture (file; or standard), awg (false), detail, max_wall_s (1260)

Results are summaries; on capture, analyze and selftest, detail: "full" returns the CLI's whole document. The samples stay in the .npz. Every device call has a hard time limit, after which an error (TIMEOUT_WATCHDOG) is returned and the CLI's own watchdog stops the scope: capture and selftest take max_wall_s (default for capture: 180 s plus the trigger timeout and the record length; for selftest: 1260 s, above the CLI's own 1200 s watchdog); status, awg and release are limited to 60 s.

selftest runs selftest --quick (--quick dropped when quick is false), with --no-awg unless awg is true and --fixture standard when fixture is standard, so by default it never touches the generator and only runs the signal checks if the wiring is declared. Pass fixture: "standard" only after the user has wired and confirmed the standard fixture, and awg: true only when the generator output goes to A0 and nothing else. The summary has passed_checks, failed_checks (measured vs expected), skipped_checks (grouped by reason) and counts that match those lists (checks not run after a device failure count as skipped), plus fixture, note and seconds. A failed run returns the error SELFTEST_FAILED, whose hint tells a failed signal check (usually the wiring) from a failed wiring-free one (the scope or its USB link); a device failure returns that device error (with stopped_at).

CLI commands with no MCP tool:

Command Why
watch prints one JSON line per capture over many captures (20 by default, or until --count / --for / --stop-when / openm454 stop), which does not fit one request and one reply; use record_start with mode: "stream", or call capture repeatedly
bench maintenance: measures this PC's USB readout speed, which log uses
recover resets and reloads the FPGA of a hung scope; kept a deliberate CLI step
debug raw register access, deliberately not exposed to agents
stop record_stop uses it internally
awg sweep, burst, mod the awg tool covers set, dc, off, status only

An agent that has a shell can use the CLI for all of these.

Errors

Every error has a code, a message and a hint, so the next step is clear:

Code Exit Meaning
NOT_FOUND 3 the scope is not connected (USB 2A0E:0080 not seen)
BUSY 4 DSWave or another openm454 process holds the scope
TIMEOUT_NO_TRIGGER 5 nothing triggered within --timeout (--trig-mode normal)
TRIGGER_OUT_OF_RANGE 2 the trigger level is outside the channel's visible range (the message gives it)
INVALID_ARG 2 an argument is wrong or impossible for this channel set
STREAM_BETTER, NEEDS_FORCE 2 log longer than its gap-free limit (see above)
SELFTEST_FAILED 1 MCP selftest only (the CLI exits 1 and lists the checks): a check failed; failed_checks has measured vs expected (a failed signal check usually means the wiring differs from the standard fixture, a failed wiring-free check points at the scope or its USB link)
UNSAFE_OPERATION 7 refused by the safety rules
TIMEOUT_WATCHDOG, TIMEOUT_DATA, USB_ERROR, PROTOCOL_ERROR, HOST_TOO_SLOW 6 the device or the host failed during a run; data up to then is kept

Layout

  • openm454/ driver: USB transport with a safety whitelist, device bring-up (FPGA, security handshake, calibration), register compiler (mso.py, reproduces DSWave's register programs exactly), stream decoder (packet.py), acquisition engine, loop-mode logger (logger.py), measurements, plots, AWG, CLI, self-test, protocol decoders (decode.py), synthetic bus traffic for tests (protosim.py) and the MCP server (mcp_server.py).
  • tests/ offline tests: golden register and AWG programs, the stream decoder, the acquisition engine against a simulated device, measurements, protocol decoders on synthetic traffic, the MCP server, safety rules (python -m pytest tests).
  • docs/PROTOCOL.md the recovered USB protocol.
  • .github/workflows/: CI (the offline tests on Windows and Linux, Python 3.10-3.14, with OPENM454_NO_USB=1, which makes the driver refuse to open USB; a check that tracked files carry no local paths; a package build) on pushes to main and on pull requests; a tag vX.Y.Z equal to openm454.__version__ tests, builds and publishes to PyPI (trusted publishing) and creates a GitHub release.

What is NOT here

The vendor application, its embedded FPGA bitstream and firmware images, decompiled vendor code, raw USB traces (they contain the device's security key), the reverse-engineering workspace and the vendor datasheet are kept out of this repository.

Safety

The driver never sends the firmware-upgrade request (bRequest 0xB3), never writes the device EEPROM, and configures the FPGA only when the device reports it unconfigured or when openm454 recover resets a hung scope (always from the verified bitstream, as the first write of the session).

License

GPL-3.0 (see LICENSE). DSView (GPLv3) by DreamSourceLab was used as a protocol reference. DSWave and DSCope are trademarks of DreamSourceLab; this project is not affiliated with DreamSourceLab.

Metadata

Release files for openm454 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openm454 0.1.1
File Size Uploaded Provenance Actions
openm454-0.1.1.tar.gz 239.6 kB With provenance Details for openm454-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for openm454 0.1.1
File Interpreter ABI Platform Actions
openm454-0.1.1-py3-none-any.whl Python 3 none any Details for openm454-0.1.1-py3-none-any.whl

Total release size: 417.5 kB

Release files / openm454-0.1.1.tar.gz

Download URL openm454-0.1.1.tar.gz
Size 239.6 kB
Tags Source
SHA-256 checksum
How to use checksums
cbcdf7ab8fb2036b5412db04540fbf5769c49ac7aceaecfcf992d36e54213cd9
BLAKE2b-256 checksum
How to use checksums
8a9861656eda61aa0078f5f268d66ebdfbb0da6504dc18584a85cd07eb2de184
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 7, 2026.

Transparency log

Release files / openm454-0.1.1-py3-none-any.whl

Download URL openm454-0.1.1-py3-none-any.whl
Size 177.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c3cc21f3d3435e96c073917db04203309d46beef777a863fb1b824d90b3d1622
BLAKE2b-256 checksum
How to use checksums
4d141e4c6396dce7c439fd94b61b43e13524fce21ab4fcba42ba2588088c22a6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 7, 2026.

Transparency log

Security for openm454 0.1.1

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.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for openm454-0.1.1.tar.gz

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 Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page