Repository navigation
Conversation
Librador had no destructor, so ~usbCallHandler never ran: the board kept its streaming alt setting and libusb was never shut down. The next session then degraded (URB timeouts) or failed to switch transport until a physical replug. The destructor now runs teardown_connection(), which also stops the ingest thread and selects alt setting 0.
…ture CLI Adds absolute-indexed access to the 10 s scope buffers, a librador_capture_around_trigger() that scans the stream for an edge with hysteresis and returns CH1/CH2 over [trigger - pre, trigger + post) at native rate, and a labrador-capture CLI that writes it as t,CH1,CH2 CSV with a JSON sidecar.
… CaptureCommand One executable with subcommands; capture is the first. Adds --help / help, strict option parsing, usage vs runtime exit statuses, librador logging routed to stderr, and fail-fast output-path check.
ISO1 validates every frame exactly. ISO6 pairs each meta-packet checksum with a data frame approximately, so it reports about 0.6% bad and about 12% unvalidated frames that turn out to be false alarms: sample continuity showed no corruption alongside them. A bad-frame warning from the CLI should mean something, so default to ISO1 and let --transport auto|iso6|iso1 select the others.
Collaborator
|
This is a big commit, thanks! Have you thoroughly tested it? |
Author
|
I have used it a lot. I have not measured test coverage. |
Collaborator
|
Nice, seems to work on macos with bulk as the transport. |
EspoTek
added a commit
to AronRubin/Labrador
that referenced
this pull request
Oct 13, 2026
On iso6 the ingest thread scored each meta packet the moment it was processed, but assembled iso6 frames only after the whole batch (drain_iso6_queues at the end of ingest_thread_function). Whenever a block's meta URB landed in the same batch as its six data URBs, all 33 metas were matched against a checksum ring that was a full block behind: one ok, two bad, thirty unvalidated per such block. That is the ~0.6% bad / ~12% unvalidated reported on healthy iso6 streams (espotek-org#462), and the reason iso1 (which dispatches inline) never showed it. Metas now wait in pending_meta until the candidate frames around their slot (lag -3..+1) have been noted, or until the ring is about to lap them, and are scored then. iso6 frames are also drained as soon as their six slices are queued rather than at batch end, so they reach the buffers without a batch-sized lag. note_seq no longer adds 65535 to frames_dropped when the device repeats a header (negative delta). Validated with a host-only simulation that drives these functions with the firmware's lag-1 meta model: every batch-collision and meta-before-data scenario drops from 0.76% bad / 12% unvalidated to 0 / 0 with no change to frame assembly. A lost data URB (six-queue lockstep skew) now reads as ~93% bad instead of mostly unvalidated. Not yet verified on iso6 hardware: this machine is macOS, where only the bulk transport may be opened.
…auto - DeviceSession treated librador_connect() == 1 (board open but on a firmware librador will not stream from) as success, so capture armed and waited forever on an empty buffer. It now fails with the board's version against the expected one. librador.h documents the 0 / 1 / negative contract of librador_connect(). - New `labrador-cli update-firmware [--hex FILE] [--force]`: flashes the bundled labrafirm hex (assets/firmware next to the binary, as the app lays it out; cli/CMakeLists copies it for dev builds) when the board's firmware differs, launches and if needed reflashes a board stuck in bootloader mode, and exits 0 when the board ends up on the expected firmware. A Gobindar-state board is reported, not handled. - --transport defaults to auto (librador's per-platform choice: iso6 on Windows and Linux, bulk on macOS); the iso6 false-alarm fix that makes that default trustworthy is espotek-org#467. `bulk` is selectable. - Command list column sized to the longest command name.
Builds only the labrador_cli target, runs its help commands as a board-free smoke test, checks the bundled firmware hex is beside the binary, and uploads each binary with the hex. Windows is checked for no non-system DLL dependencies, macOS is a universal binary against the same static libusb the app uses.
SDL3 is configured for the whole tree and fails hard when X11 is present without Xcursor; use the same package set as the main Linux workflow.
EspoTek
force-pushed
the
feature/labrador-cli-triggered-capture
branch
from
October 13, 2026 06:04
1988117 to
078321c
Compare
Collaborator
|
Had the agent go over it and add a few fixes (including a github release!). Overall this is super nifty, though. The default transport was changed to auto because #467 fixes the iso6 issue. Thoughts before merging? |
… on macOS SDL_GetBasePath() goes through NSBundle on macOS, which trusts the path the process was invoked by: labrador-cli run via a symlink from outside Labrador.app (how it goes on PATH) looked for assets/firmware beside the link. Resolve the real executable too and try Contents/Resources from there; the error now lists every location tried.
….deb and AppImage Windows: labrador-cli.exe (x64 or x86 to match the host) installs beside labrador.exe and shares its assets\firmware; the win32 cross job uploads the 32-bit CLI alongside the GUI exe. macOS: Contents/MacOS/labrador-cli, signed with the hardened runtime before the bundle and notarized with it. .deb: /usr/lib/labrador/labrador-cli with a /usr/bin symlink on PATH. AppImage: usr/bin/labrador-cli plus an AppRun hook so '<AppImage> cli ...' (or an ARGV0 of labrador-cli) runs it. Android: untouched. Each job smoke-tests the packaged CLI (help, and update-firmware with no board, which must get as far as looking for one: proof it found the bundled hex in that layout). Static-link and deployment-floor checks now cover both binaries.
cli/README.md documents both commands with real output, the CSV and JSON formats, exit codes and where each package puts the binary; the app README links to it and the rolling release notes mention the tool per platform.
Author
|
Sorry for the radio silence. I am working too many simultaneous projects. I like the changes and the iso6 issues make more sense in light of the checksum issues you found. That will help the graphical tool remove a bunch of blips. We only want the bleeps, sweeps, and creeps that are coming from the line |
Collaborator
|
Haha, awesome! Merging this one now. |
Resolve the release-notes conflict in continuous.yml: keep the Labrador-for-Linux-<arch> asset names from espotek-org#449 and add the CLI usage sentences from this branch.
EspoTek
added a commit
to AronRubin/Labrador
that referenced
this pull request
Oct 19, 2026
On iso6 the ingest thread scored each meta packet the moment it was processed, but assembled iso6 frames only after the whole batch (drain_iso6_queues at the end of ingest_thread_function). Whenever a block's meta URB landed in the same batch as its six data URBs, all 33 metas were matched against a checksum ring that was a full block behind: one ok, two bad, thirty unvalidated per such block. That is the ~0.6% bad / ~12% unvalidated reported on healthy iso6 streams (espotek-org#462), and the reason iso1 (which dispatches inline) never showed it. Metas now wait in pending_meta until the candidate frames around their slot (lag -3..+1) have been noted, or until the ring is about to lap them, and are scored then. iso6 frames are also drained as soon as their six slices are queued rather than at batch end, so they reach the buffers without a batch-sized lag. note_seq no longer adds 65535 to frames_dropped when the device repeats a header (negative delta). Validated with a host-only simulation that drives these functions with the firmware's lag-1 meta model: every batch-collision and meta-before-data scenario drops from 0.76% bad / 12% unvalidated to 0 / 0 with no change to frame assembly. A lost data URB (six-queue lockstep skew) now reads as ~93% bad instead of mostly unvalidated. Not yet verified on iso6 hardware: this machine is macOS, where only the bulk transport may be opened.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a headless
labrador-cliwith a first command,capture, built on a newlibrador_capture_around_trigger()API. It records both scope channels at the full 375 kS/s around a trigger edge and writest,CH1,CH2CSV plus a JSON sidecar.Why
I am reverse engineering a wired RS-485 camera-head protocol with a Labrador as the logic-level scope. Each experiment is a short burst, and I need hundreds of them with host timestamps, scripted rather than clicked.
What exists today does not fit:
What changed
librador:librador_get_analog_sample_count()andlibrador_copy_analog_window()give absolute-indexed access to the existing 10 s full-rate buffers.librador_capture_around_trigger()scans the stream for an edge with hysteresis and returns CH1/CH2 over[trigger - pre, trigger + post)with a shared time origin. Errors are distinct return codes (no stream, bad request, timeout, stream restarted by a mode change).Unified_App/cli: one executable with subcommands, so more can be added later (add aCommand, register it inmain.cpp). Split intoCommand,OptionParser,DeviceSessionandCaptureCommand. It has--help, strict option parsing, separate usage and runtime exit statuses, librador logging routed to stderr, and an output-path check before it waits for a trigger.captureoptions:--out,--mode scope1|scope2,--gain,--trigger CH:EDGE:VOLTS,--hysteresis,--pre,--post,--timeout,--settle,--transport auto|iso6|iso1,--verbose.Nothing in the GUI is touched;
add_subdirectory(cli)sits next to the other librador tools and is skipped on Android.Why
--transportdefaults to ISO1ISO1 validates every frame exactly. ISO6 pairs each meta-packet checksum with a data frame approximately, so it reports about 0.6% bad and about 12% unvalidated frames on healthy runs. Per-frame continuity checks on a triangle wave found no corrupted samples alongside those flags, so they look like false alarms in the pairing, not corruption. A bad-frame warning from a CLI should mean something, hence ISO1. ISO6 and auto remain selectable. I did not change the pairing logic here; that is a separate question.
My first reason for the default was wrong. I blamed ISO6 for degrading across repeated sessions, which turned out to be the teardown leak fixed in #461. The history is squashed so the commit message carries the correct reason.
Alternatives explored
Testing
Windows, MinGW build, firmware 0x000C. Used to capture CH1 and CH2 of a 0 to 6.2 V differential pair at gain 4, triggering rising at 2.5 V with 50 ms pre-trigger, and cross-checked against an independent serial logger on the same signal; the
--helptext and rejection of bad option values were also checked. This branch carries the same code as the one I tested, plus the teardown fix and without the separate Windows libusb change (#459). Frame warnings:frames_bad_checksumandframes_droppedare counted over the whole wait, so they cannot say whether a bad frame fell inside the returned window. The header comment says so, and says that on the iso transports a bad-checksum frame stays as received (a dropped frame is filled with a repeat of the last good one).Not tested
macOS and Linux builds were not run. Only the scope buffers are used, so DAQ and logic-analyser modes are not covered.