Skip to content

v0.3.0

Latest

Choose a tag to compare

@github-actions github-actions released this 11 Sep 08:44
· 1 commit to master since this release
v0.3.0
816a5f1

gflex v0.3.0 fixes the defects found by two full review passes over the whole tree. They were in exit-code classification, the safety interlocks, firmware fetch and flash, the image decoder, PDO evaluation and the USB transport. Some existing failures now exit with a different code, and several commands, info --all among them, can now fail where they used to exit 0. The README promises stable exit codes, so this is 0.3.0 rather than 0.2.1.

Changes that can affect scripts

  • --port / GFLEX_PORT: a path that exists but is not a character device (a regular file, directory, FIFO, socket or block device) is refused with exit 2 before anything is opened. The opened descriptor is checked again, and a regular file found there is refused the same way. v0.2.0 opened a regular file or FIFO read-write and wrote MIDI frames into it, and exited 3 for a directory or socket. Point it at a /dev/snd/midiC*D* node, which gflex devices lists.
  • info --all fails if all nine optional reads fail, or if the device disappears while they are being sent. The exit code follows the cause: 5 for timeouts, 3 for an unplug. v0.2.0 printed nine blank fields and exited 0, which a script could not tell apart from a unit that simply declines those reads. Plain info is unaffected.
  • Refused before a device is opened, when the arguments alone settle it:
    • voltage set with a negative value, a value outside 3300-48000 mV, or one that overflows 16 bits, now exits 7.
    • vlimit set with an inverted or out-of-envelope pair, when both --low and --high are given, now exits 7.
    • firmware fetch --raw without -o now exits 2.
    • With no device attached these used to exit 3. With a busy or unreadable device the two setting commands used to exit 4 or 6.
  • raw:
    • A frame over the 64-byte MIDI receive limit exits 2 before --dry-run and before any device is opened. v0.2.0 exited 1 after claiming the device, and under --dry-run printed the frame and exited 0. --no-ack still sends it, with a warning that the device will drop it.
    • A frame with the write flag clear that still carries a payload now needs confirmation. For example, 04 12 2e e0 is the likeliest typo of the voltage write 04 92 2e e0. Without a TTY and without --yes it exits 7. The PDO chunk read 03 11 kk still passes silently. Set the 0x80 flag if a write was meant.
    • A 0x prefix now marks a byte boundary, so 0x4 exits 2. v0.2.0 borrowed a nibble from the next token, so raw 0x0 0x4 0x92 0x2E 0xE0 was parsed as the 12000 mV write 04 92 2e e0. Unprefixed input parses as before.
  • --dry-run of the setting commands now lists the reads each command sends around its write. For voltage set, vlimit set and calibrate adc the write is no longer frames[0] in the JSON, so select it by "write": true or cmd.
  • Firmware exit codes:
    • A failed download from the vendor service now always exits 1; one that timed out used to exit 5. The message names the endpoint and --fetch-timeout. Under flash --fetch it now reads "fetching the image for serial ...".
    • A permission failure reading the image or writing -o exits 1, not 6, and no longer prints the install-udev hint.
    • A failure to open the bootloader interface now exits 6 for a permission failure, 4 for an interface another process holds, and 1 for a unit still running its application. All three used to exit 3. Under --recover, the application-mode case now fails at once instead of after the 8 s retry window.
  • Firmware refusals:
    • A vendor reply that is not JSON is refused with exit 1 and a sanitised excerpt. v0.2.0 treated it as a raw image with no CRC, split into 512-byte pages, and flash --fetch --force would flash it. Use firmware fetch --raw -o FILE to inspect such a reply.
    • An image that gives pg_id on some pages but not others is refused.
    • A named FIFO with no writer at open time is refused with exit 1, where v0.2.0 hung. Start the writer first, or use <(...), /dev/stdin or a regular file.
  • Firmware behaviour:
    • firmware fetch --dry-run is now honoured: it prints the serial read and exits 0 without opening the device, contacting the vendor or writing -o. v0.2.0 ignored the flag and sent the serial to the vendor.
    • firmware fetch -o saves pages as hex instead of base64. gflex reads both formats. Re-fetch any v0.2.0 save made at a page size divisible by 3; the vendor's current 320-byte pages and the 512-byte default are unaffected.
    • After a flash, the reconnected unit's serial is checked before the settings are replayed. If a different VFLEX answers, the command exits 1 with "the settings were NOT replayed". If the serial read fails, it only warns.
    • A --force flash of an image without a CRC that fails after the final command no longer reports that the update SUCCEEDED. The message now reads "written and started unverified".
  • Interrupts:
    • The first Ctrl-C prints a notice that the device is being released.
    • The final line is now gflex: interrupted: <error>, so match it as a prefix. The exit code is still 1.
    • A second Ctrl-C now kills the process at once, with shell status 130 (outside the exit-code table). On --transport usb that skips the driver rebind.
  • Other exit and text changes:
    • monitor now prints the frames already decoded when --for expires or Ctrl-C lands, and exits non-zero if a transport error was already waiting.
    • An install-udev interrupted during udevadm exits 1 and prints the manual reload command.
    • On --transport usb, exit 4 now says the USB interface is claimed by another process and no longer prints the ALSA hint.
    • An unacknowledged write's error text contains "write sent but not acknowledged".
    • calibrate adc marks each term (written) or (unchanged) in its human output, and tolerance set marks what it wrote as written.

Safety

  • The protocol has no NACK, so a write that was fully sent but never answered may still have been applied. voltage set, vlimit set, calibrate adc and authlock set now warn about this and name the get command to check with. voltage set also reads once and, if that read answers, appends the voltage the device now reports. Exit codes are unchanged.
  • voltage set, current set and vlimit set add a read_back boolean to their --json output. current set and vlimit set also warn when the read-back differs from the value written.
  • calibrate adc writes only the terms given on the command line. An omitted term is filled from its own read, so --offset N no longer fails just because the offset read failed; only the scale read it needs can fail it.
  • After a flash, an unreadable voltage window is read up to three times and then left as the flash left it, listed under not_restored. v0.2.0 reset it to 3300-48000 mV, which could widen a 5 V ceiling to 48 V after one dropped frame. A narrow window that fails the vendor's erased-window test is still widened, but that is now announced with a paste-ready gflex vlimit set command to restore it. The flash prompt now says settings are reset to factory defaults.
  • Printed repair commands now carry units. The concrete one, --low 3300mV --high 48000mV, can be pasted as-is, and the placeholders read <n>mV and <n>mA. A bare number is read as volts or amps, so the old --low 3300 --high 48000 was refused.

Firmware

  • A Ctrl-C between the jump and CMD_BOOTLOAD_END now says the unit is still in the bootloader and can be re-flashed, and prints the resume command. So does a serial mismatch found after the jump, and a CRC mismatch prints it as before. Other failures in that window say the unit is still re-flashable without printing the command.
  • A failure to deliver CMD_BOOTLOAD_END, other than the device leaving the bus, is now reported as a failure that leaves the unit probably still in the bootloader and re-flashable, instead of as success.
  • Resume and retry commands carry every flag that decides what gets flashed: --page-size, --crc, --force, --ack-mode, and a non-default --ws-url or --fetch-timeout. They also carry a non-default --transport.
  • On a rawmidi port matched only by name, or taken as the only port on the system, the bootloader jump is reported as unconfirmed, and the post-flash wait uses USB presence instead of timing out after a good update.
  • A vendor-class interface that declares the MIDIStreaming subclass, next to a second vendor-class interface, is now treated as a running application, as the audio-class MIDIStreaming interface already was. The bootloader interface picker prefers a vendor interface that is not MIDIStreaming.
  • Image decoding is bounded by the largest image the page geometry can address, and the bound is applied while the image is decoded rather than after. A 140 MB flat JSON array of byte values used to cost 3.39 GB of memory. A pipe used together with --page-size no longer loses up to its first 4 KiB. --crc warns when it replaces a different CRC the image declares.
  • Vendor fetch:
    • A Ctrl-C during the handshake takes effect at once.
    • A Connection header split over several lines is now accepted.
    • Text the server controls in error messages is reduced to printable ASCII.

PDO and scan

  • A power-limited SPR PPS object (USB-PD bit 27) is marked [power limited; source budget ~N W] in pdo dump and scan. It appears as pps_power_limited and pps_budget_w in --json.
  • Verdicts cap such a range at the inferred budget divided by the voltage, so an answer that relied on the advertised current can now be NO.
  • Computed currents round down to the 10 mA step instead of to the nearest step.
  • An SPR verdict that falls short now moves up to an EPR AVS range that covers the voltage.
  • A refusal at 15 V or above now cites a recorded EPR cable failure, as the epr_cable_fail_recorded caveat.
  • devices marks ports matched only by name as yes (name only), with identified_by in --json.

Robustness

  • A device that disappears between commands (ENODEV, ENXIO or ESHUTDOWN on a send), or a send into a port already closed, now stops retries at once.
  • A permission-denied error opening the rawmidi node is retried for up to 1 s, which covers a reconnect that races udev applying the ACL.
  • --transport usb:
    • A transfer submitted while the interface is being released is refused, which stops a MIDI read re-claiming it in the ordinary case.
    • A rebind refused with EBUSY is retried once, after handing back any claim that still slipped through.
    • monitor warns when the driver was not rebound.
    • A transfer with an out-of-range byte count returns an error instead of panicking.
  • install-udev triggers only USB devices with vendor 0x37bf.

Hardware verification

  • Smoke test on 2026-09-11, against a third unit (serial 86383db3, APP.05.00.00, mfg date 004apr26):
    • info --all answered all nine optional reads.
    • pdo dump downloaded all 12 chunks.
    • firmware fetch downloaded the vendor image: 165 pages of 320 bytes, crc 0x30. firmware flash --dry-run on the saved file reported the same.
    • voltage get read 5000 mV.
  • Not run on hardware for this release:
    • An actual flash. The last one was on 2026-08-21, on unit 58b4f621, with a pre-v0.2.0 revision.
    • Every write path: voltage, current and vlimit set, calibrate, led, tolerance and authlock. These changed in this release and are verified by tests only, as is everything else not listed above.
  • Decoder comparison: the saved image was decoded by five decoders, and all gave byte-identical output (52,800 bytes, sha256 2f63574e…0c85, every per-page hash equal):
    • this release;
    • v0.2.0;
    • 1df7d03, the revision used for the 2026-08-21 flash;
    • two decoders written separately from SPEC §10.3.
  • Raw vendor reply: a reply for the same serial was also kept in the vendor's {pg_id, chunks} form. This release, v0.2.0, 1df7d03 and a decoder written separately from SPEC §10.3 all gave byte-identical output on it (52,800 bytes, sha256 edcc8ecb…f8cf9).
  • Limits of that comparison:
    • The saved file is gflex's own fetch -o output, with hex pages. It exercises only the hex-page path.
    • The raw reply states its pages and chunk keys in order, so it does not test reordering. That was tested only on documents built from the saved file's bytes, with pages shuffled and chunk keys scrambled. All decoders agreed on the byte-array form. Negative controls confirm that a decoder ignoring pg_id or chunk-key order would have been caught. One spec decoder refuses hex-string chunks by design; that is a difference in what it accepts, not a disagreement about content.
    • Four downloads for the same serial, two of them one second apart, contain entirely different bytes. No two share a page or a 40-byte chunk, all look like random data (about 8.0 bits per byte), and all carry the same version, geometry and declared CRC. The service returns a fresh payload on every request, so two correct downloads never compare equal and there is no fixed image to check one against; SPEC.md §10.3 records the details.
    • gflex computes no CRC. The crc 0x30 in --dry-run only echoes the byte the image declares, and nothing ties these bytes to a CRC reported by a device.

Static Linux binaries. They are cgo-free and depend on nothing at runtime -- no shared
libraries, no Go toolchain, no browser.

gpg --verify SHA256SUMS.asc SHA256SUMS &&
sha256sum --ignore-missing -c SHA256SUMS &&
install -m 0755 gflex-v0.3.0-linux-amd64 ~/.local/bin/gflex

Chained on purpose: a bad signature or a failed checksum stops that paste before install
runs. SHA256SUMS.asc is signed by hand once this workflow has published, because the
signing key never goes near CI; the README's "Verifying a download" says where to get that
key and which fingerprint it has to be.

The USB transport and every firmware operation need a udev rule; run gflex install-udev
or see the README's Install section.

What has been verified on real hardware and what has not is set out in the README's
"Status and limitations", and the protocol itself in SPEC.md.

Full Changelog: v0.2.0...v0.3.0