feat(psu): switch a Modbus RTU bench supply from OpsLog

The manufacturer's document arrived, so this is no longer guesswork: 9600 8N1,
function codes 03 and 06 only, and a register map with the output on/off at
0x0001, the measurements at 0x0010…0x0013 and the set points at 0x0030/0x0031.

ONE REGISTER IS WRITTEN — 0x0001, the output. The map also exposes the voltage
and current set points and the three protection trip levels as writable, and
none of them belong to a logbook: a wrong value there is 30 V where a radio
expected 13.8, or a trip level lifted on a supply feeding an amplifier. They are
read and displayed instead, next to the measured values, which is also how an
operator sees at a glance that the supply is on and the radio is drawing nothing.

The wire layer is tested where it can be. CRC-16/MODBUS is pinned against its
published check value — the CRC of "123456789" is 0x4B37 — which fixes the
polynomial, the initial value, the reflection and the absence of a final xor all
at once; the rest of the protocol is checked frame by frame against the manual,
including the byte order of the CRC, an exception reply told apart from a broken
line, and a reply from another slave on the bus refused. The write echo must
match the value sent: it is the only confirmation the output really switched,
and accepting the frame without it is how a radio ends up dark behind a green
light.

Framing follows the manual's own rules: 3.5 character times of silence between
frames (4 ms at 9600), and a frame is over when the line falls quiet — Modbus
RTU has no terminator, and a serial read that times out returns (0, nil) here,
so the reader is built on a deadline and a quiet-time rather than on an error
that never comes.

Untested against hardware — nobody here has the supply.
This commit is contained in:
2026-08-16 13:15:55 +02:00
parent 9f8e3c73d9
commit 8683a450a7
12 changed files with 1038 additions and 4 deletions
+16
View File
@@ -918,6 +918,14 @@ export function GetPSKReporterStatus() {
return window['go']['main']['App']['GetPSKReporterStatus']();
}
export function GetPSUSettings() {
return window['go']['main']['App']['GetPSUSettings']();
}
export function GetPSUStatus() {
return window['go']['main']['App']['GetPSUStatus']();
}
export function GetPendingQSOs() {
return window['go']['main']['App']['GetPendingQSOs']();
}
@@ -1830,6 +1838,10 @@ export function SavePOTAToken(arg1) {
return window['go']['main']['App']['SavePOTAToken'](arg1);
}
export function SavePSUSettings(arg1) {
return window['go']['main']['App']['SavePSUSettings'](arg1);
}
export function SaveProfile(arg1) {
return window['go']['main']['App']['SaveProfile'](arg1);
}
@@ -1990,6 +2002,10 @@ export function SetOpsLogQSLReceived(arg1, arg2) {
return window['go']['main']['App']['SetOpsLogQSLReceived'](arg1, arg2);
}
export function SetPSUOutput(arg1) {
return window['go']['main']['App']['SetPSUOutput'](arg1);
}
export function SetPassphrase(arg1) {
return window['go']['main']['App']['SetPassphrase'](arg1);
}