Skip to content

Releases: X9X0/LabLink

LabLink 2.3.2

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 21:50

🐛 Fixed

  • After a server update the client had to be restarted before equipment
    showed as connected.
    Refreshing waited on a single-flight guard: a boolean
    set before the request and cleared in a finally. qasync destroys pending
    tasks when the event loop is disturbed -- which a server update does by
    definition, since the server goes away mid-request -- and a task destroyed
    mid-await never runs its finally. The flag stayed set for the life of the
    process, so every later refresh returned at the guard and the list could
    never update again. The guard now records when the work started rather than
    that it is happening, and a slot still held long past any plausible round
    trip is taken anyway: the worst case is one overlapping refresh instead of a
    frozen panel. Applied to the equipment list, the Control tab's list, and the
    readings request, where a destroyed task would have stopped the numbers with
    nothing on screen to say why.

LabLink 2.3.1

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 21:13

🐛 Fixed

  • A bench that was already connected could not be reconnected. After a
    server update the scope and both supplies refused to connect while the
    server was still holding all three open and serving readings from them.
    connect always opened the resource again, so pressing Connect asked
    libusb to claim an interface the server itself was holding; it answered
    [Errno 16] Resource busy and the API returned 500 -- a refusal that reads
    like broken hardware and is really the server colliding with itself. An
    instrument that is already open is returned as it stands, and an entry that
    is present but no longer connected is dropped first, since there a real
    open is wanted.
  • A websocket stream retried a command the instrument does not have. A
    scope asked for get_readings logged "Unknown command" twice a second,
    indefinitely. Every pass sends the same command, so what fails once fails
    always; the stream stops now.
  • A timestamp in a broadcast disconnected the client. send_json uses a
    plain json.dumps, which cannot encode the datetime in every readings
    payload, and the failure was raised inside the per-connection loop -- so a
    serialisation bug was read as the client hanging up and the websocket was
    dropped. The message is encoded once, before any connection is touched.

LabLink 2.3.0 — Rigol instrument support

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 20:34

Added

  • Rigol digital multimeter drivers (server/equipment/rigol_multimeter.py): RigolDM3058, RigolDM3058E
    and RigolDM3068 built on a shared RigolDMMBase, implemented from the RIGOL DM3058/DM3068 programming
    guides (RIGOL command set). Full remote control: function selection (DCV/ACV/DCI/ACI/2W+4W resistance,
    frequency, period, capacitance, continuity, diode), manual/auto range, FAST/MEDIUM/SLOW rate, DC input
    impedance, AC filter, dual display, trigger source/interval/sample count, math (REL, statistics,
    pass/fail), beeper/brightness, interface settings read-back, *TST? self test, error queue.

  • Self-identification: *IDN? parsing, USB VID/PID table entries for DM3058 (1ab1:09c4) and DM3068
    (1ab1:0c94), and multimeter-aware device-type inference in the VISA and mDNS scanners.

  • Acquisition & streaming: multimeters implement get_measurement(channel), get_readings() and
    get_measurements(), so acquisition sessions can name channels by function (DCV,RES) and the
    WebSocket readings stream works unchanged. New shared MultimeterData model and
    MultimeterFunction enum.

  • MockMultimeter (MOCK::DMM::n) with the same command surface, registered in the default mock set,
    plus four default multimeter profiles.

  • Tests: tests/hardware/test_rigol_dmm.py (scripted SCPI simulator), tests/test_mock_multimeter.py,
    and a DM3058/DM3068 case in tests/unit/test_new_drivers.py.

  • Docs: docs/RIGOL_DMM.md protocol and driver notes.

  • Rigol catalogue scrape (2026-09-15): all 421 documents on rigolna.com/support/downloads indexed (420 downloaded) in
    docs/rigol/downloads_catalog.json; 75 programming guides mined into
    docs/rigol/programming_guide_inventory.json; compatibility matrix and implementation order in
    docs/RIGOL_EQUIPMENT_CATALOG.md.

  • server/discovery/vendor_models.py: prefix-anchored Rigol model table used by the VISA and mDNS
    scanners, so DSA/RSA/DSG/DHO/MHO/DNA/DM858 models classify correctly. USB PIDs harvested from the
    user guides (DG 0x0642, DSA 0x0960, DSG 0x0992/0x099C/0x0993, DP1116A 0x0E10, M300 0x0C80) added to
    the USB hardware table.

  • Existing scope drivers now accept the whole family they already speak to: RigolDS1104 for
    DS1054Z/DS1074Z/DS1104Z, RigolMSO2072A for all MSO2000A/DS2000A models.

  • Rigol family drivers built from the scraped catalogue (each with a scripted-instrument test suite and a protocol doc in docs/):

    • rigol_power_supply.py: RigolDPBase + DP800/DP700/DP900/DP2000/DP1308A/DP1116A (21-model limits table, dialect table) — docs/RIGOL_DP.md
    • rigol_modern_scope.py: RigolModernScopeBase + 13 families (DHO800…DS80000, DS1000Z-E), 93-row model table, RAW chunked waveform transfer, screenshots — docs/RIGOL_SCOPES.md
    • rigol_function_generator.py: RigolDGBase (DG800/900/1000Z/2000/4000/5000) + RigolDGProBase (DG800/900 Pro, DG5000 Pro, DG6000), arb upload, modulation/sweep/burst, counter — docs/RIGOL_DG.md
    • rigol_spectrum_analyzer.py: RigolSABase + DSA800/DSA1000/RSA3000/RSA5000/RSA800/RSA6000, ASCII + REAL,32 trace parsing, markers, TG, RTSA mode — docs/RIGOL_SA.md
    • rigol_rf_generator.py: RigolDSGBase + DSG800/DSG3000(B)/DSG5000 — docs/RIGOL_DSG.md
    • rigol_multimeter_dm858.py: RigolDM858/RigolDM858E (standard SCPI DMM tree) — docs/RIGOL_DM858.md
    • rigol_electronic_load.py: RigolDL3000Base model table, RigolDL3031A, acquisition hook
    • rigol_vna.py: RigolVNABase + RigolRSAN (RSA3000N/5000N) and RigolDNA6000 — docs/RIGOL_VNA.md
    • rigol_daq.py: RigolM300 (scan lists, module detection, switch control) — docs/RIGOL_M300.md
  • New equipment types spectrum_analyzer (shared enum), rf_signal_generator, vector_network_analyzer, data_acquisition end to end (shared/client/discovery enums, connect dialog, discovery API, safety defaults).

  • New mocks: MockFunctionGenerator (MOCK::FGEN::n), MockSpectrumAnalyzer (MOCK::SA::n), MockRFGenerator (MOCK::RFGEN::n), MockVNA (MOCK::VNA::n), MockDAQ (MOCK::DAQ::n); all in the default mock set.

  • Shared data models FunctionGeneratorData, SpectrumData, RFGeneratorData, NetworkAnalyzerData, DataAcquisitionData.

  • equipment/manager.py: keyword-driver registry (KEYWORD_DRIVER_CLASSES / MODEL_KEYWORDS) replaces per-model elif chains for new families.

  • docs/DRIVER_AUTHORING.md: the driver contract, integration hooks and per-class command vocabularies.

  • Local manual archive: ~/Manuals/_archive/Rigol-manuals-2026-09-15.tar.gz + SHA-256 manifest.

Changed

  • Model list in the connect dialog, SUPPORTED_MANUFACTURERS, default safety limits (multimeter),
    and the default LABLINK_MOCK_EQUIPMENT_TYPES now include multimeters.

Fixed

  • BaseEquipment._query_binary() passed datatype= as a keyword to run_in_executor and always raised TypeError.
  • BaseEquipment._determine_connection_type() reported serial resources such as
    ASRL/dev/ttyUSB0::INSTR as USB because the path contains "USB"; serial is now matched first.
  • Device-type inference could never classify Rigol DM30xx model strings as multimeters.

LabLink 2.2.1

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 20:05

🐛 Fixed

  • Selecting an oscilloscope on the Control tab stopped the other instruments
    connecting.
    The tab drives power supplies, so it polls for volts and amps;
    a scope has no setpoints and the server answers 501 Not Implemented -- five
    times a second, forever. That storm saturates the event loop and starves the
    connect task, so the supplies on the same bench then fail to connect, which
    looks like an unrelated fault and is how it was found. The 2.1.4 fix stopped
    this for a missing instrument but treated every other status as transient,
    on the reasoning that a 5xx means the server is unwell and a supply should
    read through it. True of 500 and 503; not of 501, which is a permanent
    statement about the instrument. The two stay separate: a missing instrument
    is marked disconnected, while one that simply has no setpoints is left
    connected and says so, since calling a healthy scope disconnected would be a
    lie the Equipment tab repeats.

LabLink 2.2.0 — one client, several benches

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 03:49

One client, several benches. Built and tested against two Raspberry Pis with a
supply on each.

✨ Added

  • Connect to several servers at once. The client held exactly one
    connection: connecting to a second Pi replaced the first, and its
    instruments vanished from the Equipment tab until you switched back. A test
    spanning two benches meant working the dropdown. The Equipment and Control
    tabs now fan out over every connected server and merge the results into one
    list, with the bench named on each row -- and only once there is more than
    one server, since on a single-Pi bench that suffix is noise on every row.
  • Any instrument can be driven without touching the dropdown. Every
    reading, setpoint, lock and command resolves the connection from the
    selected instrument. Routing by the dropdown instead is the difference
    between setting 12 V on the supply you clicked and setting it on another
    bench.

🐛 Fixed

  • Tokens were stored under fixed keys rather than per server, so a second
    login overwrote the first and the next start offered one server's token to
    the other. Single-server use hides this completely. Tokens are now keyed by
    host and port, and an existing unkeyed pair is adopted by the first server
    that asks, so upgrading does not read as a logout.
  • Equipment ids are only unique within a server. Two Pis can mint the
    same one -- both test Pis report a 1902B, and the rows are identical apart
    from the bench. Selection and lookups now use a composite key, so the right
    instrument is selected rather than whichever was listed first.
  • One unreachable server no longer costs the others. The fan-out is
    concurrent, so three switched-off servers cost one connect timeout rather
    than three; a server that does not answer contributes no rows and a
    tooltip rather than a modal dialog; and a refresh will not start while the
    previous one is still out, since a fan-out can outlast the five-second tab
    refresh.

📝 Changed

  • Disconnecting a server disconnects that server, rather than whichever
    connection happened to be active.

LabLink 2.1.5

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 02:52

🐛 Fixed

  • The Disconnect button did nothing, and had done since v1.3.0. That
    release made the handler a coroutine without marking it as a Qt slot, so
    clicking built a coroutine object and discarded it -- no request, no dialog,
    no error, not even a log line. Nothing on screen distinguishes a slot that
    silently never ran from one that ran and had nothing to do, which is how it
    survived four minor releases. An audit of every signal connection in the
    client found this was the only one, and that audit is now a test.

📝 Changed

  • The equipment details read as a table. The grid gave neither column a
    stretch, so Qt split the spare width evenly and every value began at the
    halfway mark, a hand's width from the caption naming it. The captions now
    take their natural width and the values sit beside them.

LabLink 2.1.4

Choose a tag to compare

@X9X0 X9X0 released this 15 Sep 02:35

🐛 Fixed

  • A client could not connect to an instrument after the server updated.
    The server issues a new equipment id each time it opens an instrument, so
    restarting it -- which every update does -- invalidates the id the client is
    still holding. The readings loop logged the resulting 404 and carried on,
    asking for a dead id ten times a second. That storm is not just noise in the
    log: it saturates the event loop and starves the connect task, so the
    reconnect that would have cleared it appears to hang instead. The symptom is
    an updated server, a healthy supply, and no way to reach it. A 404 now stops
    the readings, drops the cached "connected" status that would otherwise
    restart the timer on the next tick, and refreshes the equipment list.
    Timeouts and server faults still retry, since one slow serial reply should
    not stop a supply reading.

LabLink v2.1.3 - Bench Work

Choose a tag to compare

@X9X0 X9X0 released this 14 Sep 23:44

Bench work. Everything here came from running the client against a real
Raspberry Pi with a B&K Precision 1685B and 9205B attached, and most of it
would not have been found any other way.

✨ Added

  • The bench survives a restart. The equipment list lived only in memory, so
    every container restart emptied it -- and an upgrade restarts containers. The
    server now records what it has identified in data/equipment_inventory.json
    on the docker volume, and lists those alongside the instruments it currently
    holds open. Identity is remembered; connections are not, so nothing opens a
    port at boot and an instrument someone else is using is listed rather than
    seized. This matters most for a 1685B, which does not answer *IDN? at all:
    rediscovery means inferring it from a USB serial bridge, and two bridges look
    alike.
  • Min/Max tracking on the Control tab, which is how a transient the live
    number skips past gets noticed. It prints to the resolution the instrument
    actually reports.
  • Auto Range for the analog and graph modes. A 5 A supply sitting at 0.3 A
    uses a sixteenth of the dial, where a 10 mA change moves the needle about a
    pixel. The scale grows to fit what has been seen and holds there rather than
    falling back as the reading drops, which made a steady supply look like a
    climbing one.
  • The update warns what it will interrupt, naming the instruments that are
    connected and will be dropped while the server restarts. An output left
    enabled stays enabled with nothing watching it.
  • The SSH host is offered from the connection, with the SSH user remembered
    per server once an update has worked.

🐛 Fixed

  • Selecting equipment silently dropped the reading rate to 1 Hz while the
    control still read 10 Hz. It restarted the timer at a hardcoded interval; a
    display that disagrees with the behaviour is worse than either being wrong.
    The rate is also remembered between sessions now.
  • The version and branch pickers could not see anything new. Both listed
    only what the clone already had, so a release or a commit pushed five minutes
    earlier never appeared however many times Refresh was pressed. They fetch on
    the button now -- and only on the button: fetching on the five-second
    refresh, which is what the first attempt did, hitched the window every few
    seconds.
  • Connecting to an instrument failed with 'Equipment' object has no attribute 'resource_string'. The client model calls it resource_name;
    the code asked for the server's spelling. It had been hidden behind a 404
    storm that kept the connect task from ever being entered.
  • The Control tab polled instruments the server had not opened, 404ing at
    the reading rate.
  • A background refresh raised modal error dialogs from a five-second timer,
    interrupting the operator to report something unactionable -- and during an
    update not even a fault, since the server is down because we asked it to be.

📝 Changed

  • The analog display is a panel meter now, after a Simpson Model 29: a
    shallow arc with the pivot low in the case, which spreads the scale over the
    full width and gives far more travel per unit than a round dial in the same
    box. It fills the window like the other two modes, where it used to sit at
    its minimum with room to spare. The case follows the theme -- grey bezel in
    both, cream card and black pointer in light, black card and green pointer in
    dark -- with the graduations and lettering printed in the same ink as the
    pointer.
  • The update log fills the window instead of staying ten lines tall with a
    third of the screen empty beneath it.

LabLink v2.1.2 - A Deployed Server Can Update Itself

Choose a tag to compare

@X9X0 X9X0 released this 14 Sep 20:37

A deployed LabLink can now update itself. Until this release a Pi could not:
/opt/lablink was never a git checkout, so lablink-update.sh -- which
already did exactly the right thing -- had nothing to fetch into, and the
client's "Update Remote Server" button never touched the remote at all.

🐛 Fixed

  • "Update Remote Server" updated no remote. It checked the ref out in the
    local clone, then ran docker compose on the remote in a directory named
    by the local git root -- so it moved the laptop's code and told the Pi to
    cd C:/LabLinkTest, which fails on the first command. It now drives the
    remote's own lablink-update.sh over SSH, takes the remote path as a field
    defaulting to /opt/lablink, and leaves this machine's checkout alone.
  • lablink-update.sh could not take a release. It only handled branches,
    and git pull origin v2.1.1 is not a thing; it now pulls only when HEAD is
    on a branch. It also prompted "Rebuild anyway?" with no way to answer over
    SSH, hanging the run forever, and it rebuilt stale code when the checkout
    failed while reporting success. It stops now.

📝 Changed

  • The image builder clones instead of unpacking a tarball, and installs
    git, which it never did. An image built the old way produced a server that
    could only ever be reinstalled.
  • The deploy wizard clones from GitHub by default. The deployed server
    gets a checkout and can update itself, and far less crosses the wire since
    the remote pulls directly. Sending the working tree is still available for
    testing uncommitted changes, now labelled as producing a server that cannot
    update itself rather than leaving that a surprise.
  • SSH is expected to work without a password. The update runs with output
    captured, so a password prompt could never be answered; it now fails
    immediately and says so instead of blocking.

LabLink v2.1.1 - The Self-Update, Finished

Choose a tag to compare

@X9X0 X9X0 released this 14 Sep 19:36

The client self-update, finished. 2.1.0 shipped the pieces; running one on a
real install found the rest, and this is the first version whose update button
takes you somewhere and brings you back.

🐛 Fixed

  • The update button closed the client and left it closed. It marked the
    update and called sys.exit(0), on the theory that "the launcher should
    detect the flag" -- nothing watches for it, so the update sat unapplied
    until somebody started the client again by hand, while the dialog promised
    the application would restart. It restarts now.
  • The restart itself could not work from a shortcut. The Start Menu and
    desktop entries go through lablink_launch.pyw, which sets sys.argv[0] to
    the module name client.main and hands it to runpy. The restart rebuilt
    that as python client.main, which Python reads as a file path and refuses
    with can't open file. A path is now run as a path and a module as
    -m <module>, from the checkout directory so it resolves wherever the
    shortcut started from.
  • A server update moved the client's own code. Both server updates call
    checkout_git_ref, which operates on this clone -- the one the client runs
    from -- so updating a server changed the client too, and an older version
    downgraded the running client as a side effect. The remote path did it as
    readily as the local one: updating the Pi over SSH checked a ref out on the
    laptop first. Both now check the direction, warn with the commit count, and
    default to No.
  • A tag checkout left the UI unable to say what was running.
    git branch --show-current prints nothing on a detached HEAD, so the status
    bar and the branch picker went blank at the moment the question mattered
    most -- right after an update moved the code. describe_head names the
    branch, or the tag, or the commit.

📝 Changed

  • The update button says something the instant it is pressed. Checking the
    update direction fetches first, which is seconds on a slow link, and until
    it returned the button looked ignored -- so it got pressed again.
  • The update dialog describes what you see. It said the application would
    close and reopen twice. It does restart twice, but the process in between
    applies the checkout and re-execs before it builds a window, so what a user
    sees is one close and one reopen. A test now pins that the update stays
    ahead of the window, because the alternative is the old UI flashing up
    mid-update.