Repository navigation
Releases: X9X0/LabLink
Release list
LabLink 2.3.2
🐛 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 afinally. 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 itsfinally. 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
🐛 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.
connectalways opened the resource again, so pressing Connect asked
libusb to claim an interface the server itself was holding; it answered
[Errno 16] Resource busyand 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 forget_readingslogged "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_jsonuses a
plainjson.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
Added
-
Rigol digital multimeter drivers (
server/equipment/rigol_multimeter.py):RigolDM3058,RigolDM3058E
andRigolDM3068built on a sharedRigolDMMBase, 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
WebSocketreadingsstream works unchanged. New sharedMultimeterDatamodel and
MultimeterFunctionenum. -
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 intests/unit/test_new_drivers.py. -
Docs:
docs/RIGOL_DMM.mdprotocol 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:
RigolDS1104for
DS1054Z/DS1074Z/DS1104Z,RigolMSO2072Afor 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.mdrigol_modern_scope.py:RigolModernScopeBase+ 13 families (DHO800…DS80000, DS1000Z-E), 93-row model table, RAW chunked waveform transfer, screenshots —docs/RIGOL_SCOPES.mdrigol_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.mdrigol_spectrum_analyzer.py:RigolSABase+ DSA800/DSA1000/RSA3000/RSA5000/RSA800/RSA6000, ASCII + REAL,32 trace parsing, markers, TG, RTSA mode —docs/RIGOL_SA.mdrigol_rf_generator.py:RigolDSGBase+ DSG800/DSG3000(B)/DSG5000 —docs/RIGOL_DSG.mdrigol_multimeter_dm858.py:RigolDM858/RigolDM858E(standard SCPI DMM tree) —docs/RIGOL_DM858.mdrigol_electronic_load.py:RigolDL3000Basemodel table,RigolDL3031A, acquisition hookrigol_vna.py:RigolVNABase+RigolRSAN(RSA3000N/5000N) andRigolDNA6000—docs/RIGOL_VNA.mdrigol_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_acquisitionend 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-modelelifchains 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 defaultLABLINK_MOCK_EQUIPMENT_TYPESnow include multimeters.
Fixed
BaseEquipment._query_binary()passeddatatype=as a keyword torun_in_executorand always raisedTypeError.BaseEquipment._determine_connection_type()reported serial resources such as
ASRL/dev/ttyUSB0::INSTRas USB because the path contains "USB"; serial is now matched first.- Device-type inference could never classify Rigol
DM30xxmodel strings as multimeters.
LabLink 2.2.1
🐛 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
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
🐛 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
🐛 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
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 indata/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 itresource_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
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 randocker composeon 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 ownlablink-update.shover SSH, takes the remote path as a field
defaulting to/opt/lablink, and leaves this machine's checkout alone. lablink-update.shcould not take a release. It only handled branches,
andgit pull origin v2.1.1is 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
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 calledsys.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 throughlablink_launch.pyw, which setssys.argv[0]to
the module nameclient.mainand hands it torunpy. The restart rebuilt
that aspython client.main, which Python reads as a file path and refuses
withcan'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-currentprints 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_headnames 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.