So the tester can exercise everything in one session instead of one
control per build: RF and mic gain, squelch, preamp, attenuator, NB, NR,
AGC, filter width, antenna, RIT/XIT with a clear, and the keyer speed.
The analogue scales differ between an Elecraft and a Kenwood — RF gain
000-250, mic 000-060, squelch 000-029 against 0-255 — so each is mapped
through its own full scale and shown as a percentage. Using one rig's
range on the other silently halves or doubles every setting.
Antenna only appears when the radio answers AN: a K3 without the internal
ATU has one socket, and a switch that goes nowhere is worse than none.
The Elecraft backend already existed; what was missing was somewhere to
operate the radio from. This adds the panel, on the Kenwood-dialect
client the K3 already speaks, in a tab of its own beside the Yaesu and
Icom consoles.
Scope is the six controls asked for and nothing else. Every extra command
added without a radio to test it against is a control that may or may not
do what its label says, and a K3 exposes dozens.
No K3 was available while writing this, so the two halves are treated
differently. The setters are the commands the reference documents
unambiguously and whose effect is visible and reversible (PC, AG,
TX/RX). The meters are the opposite: their scaling differs by model and
firmware, so the raw answers are logged next to the power setting, the
panel says the scaling is provisional, and an unmeasured SWR shows as
'—' rather than as a perfect 1.0 — a good match on an antenna nobody
measured is the reading that costs a radio.
ATU tune sends SWT20 (the K3 front-panel tap) and logs exactly what it
sent, so a wrong mapping names itself instead of leaving an operator
guessing which button OpsLog pressed.
Working a DXpedition split means guessing where it listens. The useful
information is not the callsign the DX answered but the FREQUENCY that
station was calling on, and a CW skimmer already marks it: SDC posts each
decoded report to the panadapter as a spot.
Those spots reach OpsLog through the radio's spot feed. On a marker, the
TRANSMIT slice moves there plus a signed offset; the receive slice never
moves, because losing the DX is worse than missing a call. The marker
text is a setting -- it is chosen in SDC by the operator, so any constant
here would be wrong for whoever chose otherwise -- and the switch is a
button beside SPLIT, since it is turned on when a DXpedition appears and
off when it is worked.
Moves are throttled and ignore a marker landing where the slice already
is: a busy pile-up produces several reports a second and the slice would
otherwise never be anywhere long enough to call.
The spot feed is now subscribed to unconditionally. It was tied to
OpsLog's own spot overlay, so an operator running SDC with the overlay
off received nothing -- the reason no foreign spot was ever logged. The
connect-time 'spot clear' stays behind the overlay flag: it wipes every
spot on the radio, a skimmer's included.
Chasing a split pileup with a CW skimmer means acting on the report the
DX just sent: SDC posts what it decodes as panadapter spots, so the 5NN
is already on the radio and OpsLog already subscribes to 'spot all'.
What is not known is the shape of those spots -- which field carries the
report, whether it is the callsign or the comment, what source the
skimmer names itself. Guessing produces a feature that never fires, so
this logs the first 60 foreign spots of a session verbatim and does
nothing else. Bounded because a skimmer posts hundreds an hour.
HAMLOG.online is a seventh external service: one API key, one ADIF record per
QSO, immediate / delayed / on-close like the rest. They publish no API
documentation, so the protocol is read from THEIR OWN client — the HAMLOG Agent
(github.com/hamlogonline/Agent), which is the authoritative source short of
asking them:
POST https://hamlog.online/api/agent/
{"ADIFADD": {"APIKEY": k, "ADIFDATA": record}} → {"STATUS":"OK"}
{"KEYSTATUS": {"APIKEY": k}} → {"STATUS":"OK","CALLSIGN":…}
Success is STATUS == OK, not "no ERROR field": their failure carries ERROR and
no STATUS, and reading an unknown reply — a proxy page, a maintenance notice —
as an acceptance is how a contact goes missing without anyone noticing.
KEYSTATUS buys something no other service here offers: the key can be checked
BEFORE the first QSO, and the answer names the account. A key pasted from
another callsign is caught in the settings panel rather than through a week of
silent refusals.
Their confirmations are also an award source now, ticked like LoTW rather than
expressed through "custom". It reads the ADIF extras, not a column: the standard
names a field for hamlog.EU and none for hamlog.ONLINE, and borrowing the other
site's field would write a falsehood into every exported log. Three plausible
key names from their own export are accepted too, so nobody has to rename a
column by hand after an export.
Yaesu gains antenna selection (AN), remembered per band — the antenna picked on
a band comes back with it, with no table to fill in anywhere. Rigs with one
socket never answer AN and never show the row; the log says which case it is.
And a serial port that is refused now names its likely holder. OmniRig stays
resident once activated and keeps the port of the rig configured in it, so a
native backend never gets it — "Serial port busy" alone accused nobody, and an
FTDX10 spent a morning being blamed for it.
An Icom reached over the LAN streams its receive audio through the Icom
protocol, and Windows sees no sound card for it at all — the audio settings
could only offer the PC's own microphone, so "From radio" had nothing right to
point at and the QSO recorder had nothing to record. The recorder now accepts a
PUSHED source: the decoded stream goes to the speakers and to the recorder
alike, with no virtual cable to set up. Which source it uses follows the CAT
backend, and it is restarted only when that answer changes, so an ordinary
settings save never cuts a recording in half.
OmniRig no longer sends SetSimplexMode to a Yaesu when tuning. It is a silent
no-op on some — an FT-891 logged OK on every spot click while FreqA never
moved — and actively harmful on others. From an FT-2000 log, one QSY:
Vfo="AB"(0x80) Split=0x10000 (off) the operator's state
Vfo="BA"(0x100) Split=0x8000 (ON) after SetSimplexMode
The rig-agnostic "receive and transmit HERE, simplex" call turned split on and
moved reception to VFO B. OpsLog then displayed B — reading the radio correctly,
after having moved it itself. Icom is untouched: there the call is the
authoritative one and the direct write is unreliable.
Also:
- the NEW county badge shows in the entry form itself, inside the field,
where the operator is deciding whether to call.
- the basemap buttons clear the zoom controls; Light sat a few pixels from
the minus button and was being clicked by mistake.
- French cluster status: DÉJÀ CTC reads DÉJÀ QSO.
Panadapter spots are now worth reading. The comment carries the spotter, the
entity and the status in DXHunter's own shape — "CQ up 2 [F4BPO] [Franz Josef
Land] [New Slot]" — which needed two things nothing documents: SmartSDR splits
its command line on SPACES, so the words ran together until every space became
non-breaking; and it truncates past ~60 characters, so the cluster's own words
are trimmed first and the three brackets always survive. RBN column padding is
collapsed on the way in, or a preserved run of spaces opened a gap wide enough
to push the rest off screen.
"Already worked" means the CALLSIGN is in the log, not the entity: saying it of
a station never contacted was simply wrong. Each status can also be kept off the
panadapter entirely, and the WSJT-X decode spots obey the same switches — the
palette governs the panadapter, not one of the two things that feed it.
And the radio is no longer hammered: a spot whose frequency, colour and comment
are unchanged is not removed and redrawn. A busy skimmer feed re-spots the same
station every few seconds; one two-minute session sent 2128 adds, 88 of them for
a single callsign, and the display did not move a pixel for any of them.
CI-V, from an IC-7850 that kept killing JTDX: a reply the rig sent to another
controller on the same bus is no longer taken for ours, and a set_ptt, set_freq
or set_mode whose acknowledgement goes missing is verified by reading the rig
back instead of being reported as a failure. WSJT-X and JTDX answer a failed
command with a Rig Control Error and drop the link mid-over — 98 keyings, 6 lost
acknowledgements, 2 dropped connections in one session. The check waits 700 ms,
not the poll's 150: the rig has just failed to answer twice because it was
retuning, and a short probe would fail for the same reason.
Auto-call is withdrawn — it duplicated DXHunter, which already answers decodes,
and two programs deciding that from one shack key over each other. The library
is kept whole and dormant; a guard in App.tsx makes sure a stored preference
cannot key a transmitter whose switch no longer exists.
Also:
- the log rotates while running, not only at startup: the CI-V trace left on
wrote 416 MB and nothing would have stopped it before the disk did. Closing
it now releases the crash file too — the runtime keeps its own duplicate.
- the interface zoom announces itself, with a badge, a click back to 100% and
a View menu; Ctrl+wheel and Ctrl+0 always worked and nothing said so.
- no more elastic bounce, and no swipe-to-navigate out of the app.
- Edit QSO: your own TX power and the contacted station's extended locator
were saved and written back with no box to set them.
- FT decodes: continents are a multiple choice; a compound MSHV message that
answers two stations in one line is recognised as addressed to you.
Three faults an operator's log finally made visible, plus the interface
work that came out of the same session.
Reliability:
- UDP events were dropped on backpressure without a word. A period hands
over twenty-odd decodes at once, and one slow write to the radio was
enough to fill the queue — so a decode simply never appeared, and the
only detector was the operator comparing the panel with JTDX. The drop
is now counted and logged, panadapter spots went to their own goroutine
so the radio can no longer hold the decode stream up, and the queue is
deep enough for a full period.
- The CAT manager waited for its poll loop with a bare <-done. A loop
wedged in a serial read then blocked every later restart inside Start,
before it could even try to connect: the rig stayed dead, no line was
written anywhere, and only killing the process recovered it. The wait
is bounded at ten seconds and says what it abandoned and why the next
connect may fail.
- Shutdown had no logging at all, so a hang left nothing to go on and a
process the operator had to kill — which then blocked the restart after
an update. Every step is logged, and a watchdog forces the exit if one
of them never returns.
Auto-call:
- A QSO in progress is now held by OpsLog itself rather than inferred
from the sender's Status. The moment WSJT-X/JTDX dropped the DX call or
the Enable-Tx flag between overs, the exchange looked finished and the
next CQ was answered, interleaving two and then three QSOs on one
slice. Released on log, on halt, on taking over, and by a watchdog.
Cluster console:
- Replies to a command were buried under the spot flood; a Replies
toggle hides the DX spots, which the list above already shows.
- Twelve named command buttons beside the input, configured in
Settings -> Cluster; a button with no command is not drawn.
- Following the tail is now an explicit switch, and sending a command
re-arms it. It used to measure "am I at the bottom" AFTER committing
the new lines, so a ten-line reply looked like the operator had
scrolled up and was never followed — the one case it exists for.
Awards:
- An award can name NO field. The matching controls disappear with it
and only hand-assigned references count, which is the only thing that
can feed a reference like WWBOTA. A test pins that nothing else is
scanned.
- WWBOTA added to the catalogue with its 31 342 references.
Elsewhere: the rotor widget's Stop button acknowledges the press like
the direction presets already did, and the docked band map can be
switched to fit-to-band from its own header.
Extends to frequency and mode what PTT already had. A missing FB is not a
missing command: the rig acts on the frame as it decodes it, and what
expires is our wait for the answer, on a bus shared with the rig's own
transceive updates. JTDX in "Split: Fake It" moves the dial and the mode
immediately before every key-down, so those acks queue behind each other.
Losing one is fatal to the client rather than merely untidy: rigctld answers
RPRT -9, JTDX reads that as losing rig control and tears the connection down
mid-over. An operator's log shows three set_freq failures and one set_mode,
each followed within 300 ms by a fresh rigctld client -- and shows the PTT
resend rescuing an over that would otherwise have ended there.
Opt-in per caller rather than folded into exec: only a command that says
"be in this state" can be repeated safely, and a relative one must never
come through here.
The acknowledgement loss itself is still unexplained. Every failure in that
log is preceded by a state read reporting SSB on a rig in DATA, which points
at CI-V frame desync rather than a slow rig, and needs a trace to pin down.
"PTT via CAT does nothing on TCI." What OpsLog sends is right — trx:0,true; is
the documented command and the same one the reference clients send — so the
command was going out and the radio was discarding it.
ExpertSDR announces transmit permission with TX_ENABLE: on connect, and again
whenever the band changes, "in case transmitter permission was changed" (§4.3).
While it is false the radio simply IGNORES trx. OpsLog never read that command,
so there was nothing in the log, nothing on screen, and a dead key.
Now the permission is tracked and SetPTT refuses out loud, naming where to look:
the frequency must be inside a transmit band and TX enabled in ExpertSDR. The
refusal travels the path that already exists — Manager.SetPTT to pttKey, which
logs it and hands it to the UI.
Silence is not a "no". A radio that never mentions TX_ENABLE — an older
ExpertSDR, or another program speaking TCI — is not treated as refusing: we key
and let it decide. Permission is also forgotten on connect, so a refusal
remembered from a band since left cannot block PTT until a restart.
This may not be the operator's own cause, and that is the other half of the
change: if it is not, the log now settles it in one line. cat: TCI: → trx:0,true;
present means the command left OpsLog and the radio ignored it for a reason it
has not told us; absent means the fault is on this side.
Spot mirroring drew the same station two or three times, a few hertz apart.
Not a loop in the spot pipeline — one cluster line produces exactly one
SendSpot. It is that a popular DX station IS spotted two or three times, by
different operators inside the same minute, and no two of them agree on the
frequency to better than a few tens of hertz. This backend assumed ExpertSDR
replaced a spot bearing a callsign it already held, and said so in a comment;
it keys on the frequency too, so each of those became its own marker and stayed.
The FlexRadio backend has always done this properly (spot remove before spot
add, "one live spot per call"). TCI now does the same: the frequency drawn for
each call is remembered, a re-spot within 500 Hz sends nothing at all, and one
further away deletes the old marker before drawing the new one — so a station
that really moves still moves, instead of collecting markers.
SPOT_DELETE was not in the protocol document this backend was written from.
Confirmed before use against ars-ka0s/eesdr-tci, an independent TCI library that
lists SPOT with 5 arguments, SPOT_DELETE with 1 and SPOT_CLEAR with 0 — the
other two matching exactly what already works here, which is what makes the
third trustworthy.
The memory is cleared where Connect sends spot_clear. Left standing, it would
suppress the next spot for every remembered call as "already drawn" onto a
panorama the radio had just emptied — spots would quietly stop appearing after
a reconnect until each station changed frequency.
Five tests on the decision, which is split out from SendSpot so it can be
checked without a radio.
WSJT-X "Fake It" shifts the dial for the duration of each over and puts it
back afterwards, but the restore is conditional: it reads the frequency back
and only moves the dial if it disagrees with where it believes the radio
should be.
That read lands inside the window where this backend stops polling on
purpose. A Kenwood answers "?;" to IF; while it is transmitting, and treating
that as a fault used to drop the shared CAT link entirely, so the cached
state answers instead. SetFrequency wrote the command to the rig without
touching that cache — so mid-over the cache still described the pre-over
dial, WSJT-X read its own receive frequency back, concluded there was nothing
to restore, and the radio stayed on the transmit frequency. Every later over
started from there.
Reported from a session where the dial stuck at 7075500 after a full FT8
over while a bare TUNE, which never sets a frequency, worked fine.
Only simplex updates the cache. Under split, FreqHz is the transmit frequency
while the write lands on whichever VFO the operator is on, and guessing which
side moved would put a wrong number in front of the operator — a stale one
survives until the next poll.
The test reproduces the reported sequence and fails without the fix with the
same frequency the log shows.
set_split_vfo and set_split_freq both answered RPRT 0 and did nothing. WSJT-X
and JTDX in "Split Operating: Rig" send exactly that pair, believed both, and
transmitted on the RECEIVE frequency — on a pileup, straight onto the DX, while
showing the operator precisely what they had asked for. A lie that leaves no
trace in any log is the worst kind of bug this program can have.
The two commands are honoured as a PAIR. Arming alone does nothing on the radio,
because WSJT-X sends the frequency second and split armed on whatever the
transmit VFO happened to hold is worse than no split at all: it transmits
somewhere the operator never chose. The request is remembered and set_split_freq
does the work.
Kenwood gains SetSplit — FB to place the dial, then FR0/FT1 to arm, in that
order for the same reason. It writes what State() already knows how to read.
Everything else REFUSES, and that is the feature, not a shortfall. Only Flex and
Icom could even toggle split before, neither could set the transmit frequency,
and Yaesu, TCI and OmniRig have nothing at all. A refusal WSJT-X can report —
and act on, by falling back to Fake It — is worth far more than a success it has
no way to check.
Both paths are pinned: split reaching the rig as one armed call with the right
frequency, and a backend that cannot do it producing an error rather than RPRT 0.
IF reports the VFO "in use". In split that is the RECEIVE VFO while receiving
and the TRANSMIT VFO while transmitting — the backend took it as the receive VFO
in both cases. Everything therefore read correctly until the PTT closed and then
reversed, which is exactly how it was reported from a TS-590 on USB.
The IF frame has carried the transmit bit all along; parseKenwoodIF already
decoded it into f.TX. It was simply never consulted here.
This is not only a display fault. FreqHz is what a QSO is LOGGED on, so a
contact worked in split went into the log on the DX's frequency instead of the
operator's — wrong in the log, wrong in every ADIF exported from it, and
invisible until someone checked a QSO by hand.
The emulated rig in the test package can now be keyed, so the case is pinned
from both sides: verified failing without the fix (tx and rx exchanged) and
passing with it.
CW over CAT never worked on a Kenwood. The KY implementation was written against
Elecraft's, which accepts a string of any length, so "KY OH5CX;" went out and a
TS-590SG answered "?;". OpsLog read that as "this radio refuses CW over CAT" and
told the operator to fit a serial keyer — advice that was wrong, and expensive.
The TS-590 manual is explicit: P2 has a fixed length of 24, blanks are filled
with spaces, and those spaces are not keyed. So the fix costs nothing on air; it
is simply the shape the command has. Elecraft stays variable-length, where
padding would key the trailing spaces as word gaps.
The semicolon is also gone from the allowed CW characters. It TERMINATES a CAT
frame, the manual forbids it in P2, and one in a macro would have closed the
command early and left the rest of the message to be read as commands.
Found from a log and a manual page, not from a rig: nobody here owns a Kenwood.
What is proven is the frame shape; that a TS-590SG then keys it still needs the
operator to confirm.
A Kenwood answers "?;" while it is busy, and the TS-590SG does it for a moment
after RX; — which is exactly when the poll resumes. One rejected IF; then tore
down the whole CAT link.
The code already knew this. Its own comment says a "?;" to IF; is "a transient
busy, NOT unsupported" and that latching it off "would read as lost the rig".
That reasoning was applied to the unsupported-command map and stopped there: the
call still returned an error, the poll loop still turned it into connected=false,
and the link still dropped.
What it cost was not cosmetic. WSJT-X keys through shared CAT, so it lost the
rig mid-sequence; Hamlib then sent "F 9223372036854775808.000000" - an
uninitialised 2^63 - which OpsLog refused and logged. The operator sees a
frightening frequency error whose actual cause is three lines earlier.
A "?;" is now sentinel-wrapped so the poll can tell "the rig declined" from "the
serial link is gone", and three consecutive ones are ridden out on the last
known state before the link is called dead. Under a second of tolerance: enough
for the rig to finish, far too short to hide an unplugged cable. A serial fault
is untouched and still drops immediately.
Tested against the emulated rig already in this package, with a hook that
answers "?;" on demand: the link survives inside the grace and still reports the
last good frequency, the count resets on recovery so a later busy spell gets the
full allowance, and a rig refusing forever is still reported as broken.
MD6 is FSK on a Kenwood and DATA on a K3/K4. kenwoodModeToADIF decoded the
digit unconditionally as RTTY, so OpsLog contradicted the mode it had just
set: SetMode writes MD6 for a digital mode on an Elecraft, then ReadState
parsed the IF frame back as RTTY.
A K3 running FT8 therefore showed RTTY in the status bar, logged its QSOs on
RTTY, and — through the shared CAT server — told WSJT-X/JTDX the rig sat in
RTTY while they had just asked for a data mode. Found in a K3 operator's log:
every cat:state line read mode=RTTY on 21.074 FT8, two lines after OpsLog's
own "MD6;".
The digit now resolves to the configured digital mode whenever MD6 means DATA
on this rig — the Elecraft backend, and the "DATA A - MD6" data-mode option
that exists for it. A plain Kenwood still reads MD6 as RTTY.
The Elecraft handling was buried in the Kenwood "data mode" option, so users
couldn't find it. Add "Elecraft K3/K4" as its own entry in the CAT backend
selector. It reuses the Kenwood-dialect transport and the Kenwood USB/network
settings (the K3 emulates the Kenwood command set — a separate transport would be
a near-total duplicate), with an `elecraft` flag on the client that forces
digital modes to DATA A (MD6+DT0) — no data-mode dropdown needed. RigState still
reports Backend "kenwood" so the CW-over-CAT keyer capability keeps working.
Integrates the fix/jtdx-kenwood-tx work onto main: the Kenwood/Elecraft backend's
"data" data-mode path now sends MD6 then DT0 so a K3/K4 lands in DATA A — the
audio sub-mode FT8 uses. MD6 alone could leave the rig in an FSK/PSK sub-mode
where it keyed but the rear sound-card audio never modulated ("transmits but
nothing comes out"). The data-mode option is relabelled "DATA A — MD6+DT0
(Elecraft K3/K4)". A shared Kenwood/Elecraft backend is kept (the K3 emulates the
Kenwood dialect); the Elecraft specifics live behind the data-mode seam rather
than a 7000-line duplicate backend.
A K3 (and other Kenwood-dialect rigs) answers "?;" to an IF; status poll while
it is transmitting. ask() latched that as "IF unsupported" and ReadState
returned an error, so the Manager dropped the whole CAT link mid-transmit — the
log shows connected=false / "IF rejected" the instant JTDX sent TX;. That tore
down the shared rigctld CAT the digital-mode software keys through.
Two fixes: (1) track PTT (SetPTT) and skip the wire poll while it's held —
ReadState hands back the cached last state, capped at 30 s so a missed
SetPTT(false) can't freeze it; (2) never latch IF/ID as unsupported — those are
universal on Kenwood/Elecraft, so a "?;" to them is a transient busy (mid-TX),
not a missing command.
No single Kenwood mode digit fits every rig for a data mode: a K3/K4 wants
DATA (MD6), a TS-590SG/TS-990S data mode is a USB modifier set on the rig,
and MD6 on a plain Kenwood is FSK/RTTY. So the operator now chooses in
Settings → CAT: USB (default), DATA (MD6), or leave the rig's mode unchanged.
Only soundcard/data modes (FT8, PSK, JT…) follow this; RTTY/FSK stays its own
native mode, and voice/CW are untouched (isKenwoodDataMode + test).
The frontend engine loader had no "kenwood" case, so a saved engine of
"kenwood" fell through to the WinKeyer default — the Kenwood/Elecraft
CW-over-CAT engine never ran even though the setting showed it selected.
Also clarify the Kenwood data-mode comment: there is no single mode digit
right for both a K3 (DATA=MD6) and a TS-590SG (MD6=FSK), so data stays on
USB as the safe common base; a rig-specific DATA mode is a follow-up.
- PGXL: remote AUTH password; direct-link meter fallback (VITA-first) with
250 ms poll + peak-hold; fan mode uses bare "fanmode=" (was ignored).
- TCI: spot colour sent as decimal ARGB (ExpertSDR dropped the hex string);
spot click handled via CLICKED_ON_SPOT / RX_CLICKED_ON_SPOT to fill the entry.
- FlexRadio: clicking an OpsLog spot on the panadapter now applies the mode too.
- Filter presets: the trash icon deletes again (Radix pointer-down intercept).
- PTT hotkey: keys over CAT when a backend is active (was hijacked by a stale
Audio-tab RTS/DTR serial setting); AltGr keys allowed; presses logged.
- CW macro <LOGQSO>: logs after the preceding CW is sent, not on the first letter.
Settings -> Rotator is now a list like the amplifiers: add/remove rotors,
mix PstRotator / Rotator Genius / ARCO, and a Rotator Genius can drive both
its ports (one entry = two rotors). The compass gains a rotor selector, and
a per-rotor motorized-antenna flag so the boom/pattern paths show only for
the rotor carrying the Ultrabeam/SteppIR.
Also in this batch: a keyboard PTT hotkey (hold-to-talk or toggle, reusing
the audio PTT method with a CAT fallback), and TCI spot push to the
panadapter with click-to-fill of the callsign.
Both defaults break someone. 0.22.7 lowered the lines on Yaesu and
Kenwood and silenced a TS-990 whose interface needs RTS raised to
transmit; 0.22.8 stopped lowering them and an FT8 station reported its
output power wandering, cured by closing OpsLog — Windows raises both on
open and the interface reads one as PTT.
So it is now a checkbox on those two backends, off by default: that is
how they behaved before any of this. Xiegu keeps lowering them
unconditionally (its own report, its own explicit keying setting), and
the CI-V backend keeps the behaviour it has always had.
Reported by one in the field. Kenwood's documentation gives 024 for that
radio; both are mapped rather than betting on which is right, and the log
no longer reads "Kenwood (022)".
d4bfd30 fixed a Xiegu G90 behind a DE-19 that keyed on connect, and I
applied the same line-lowering to Kenwood and Yaesu with no evidence that
either needed it. It silenced them: many USB-serial interfaces will not
transmit with RTS low — hardware flow control, or an output stage the
line enables. A TS-990 on COM3 opened cleanly and answered nothing, which
reached us as "CAT stopped working after the update".
Xiegu keeps the behaviour: that is where the fault was reported, and that
backend now has an explicit setting for which line keys the rig. The CI-V
backend keeps its own, which predates all of this.
Kenwood also distinguishes a silent port from one sending data that never
answers, and quotes what arrived — "the rig is not answering" sent an
operator checking the power switch on a radio whose frames were visibly
on the wire.
A TS-480 connected, answered ID, then never reported a state:
discarding " 000000000010000000;" while waiting for IF
connected on COM3 @ 115200 baud
cat:state → connected=false freq=0
A headless frame tail can only come from bytes that were thrown away.
ask() buffered into a local slice, so everything left after the matched
frame — including the head of a frame still arriving — went with it, and
the link could sit one answer behind its questions. The buffer now lives
on the backend, and Connect drains whatever the rig said before AI0 took
effect rather than inheriting it.
Also: the cat:state diagnostic prints RigState.Error. It read
"connected=false freq=0" and said nothing about why, so every report of a
CAT failure arrived without the one fact that explains it — the reason
only ever reached a tooltip.
New work moves to 0.22.8; 0.22.7 is released.
Reported on a Xiegu G90. With the blue programming cable — three wires, no
modem lines — CAT works perfectly. Behind a DE-19, which carries audio, CAT and
PTT, the radio went into transmit the moment OpsLog connected and stayed there.
Windows raises DTR and RTS when a serial port is opened, and the DE-19 reads
them as PTT; Xiegu's own documentation asks for both low. The CI-V backend has
dropped them since it was written, for the same reason on Icom rigs with USB
SEND mapped to a line. Xiegu, Yaesu and Kenwood did not. They do now.
The second half of the report: WSJT-X through OpsLog's rigctld decoded fine and
never transmitted. Nothing was broken in that chain — set_ptt reaches the
backend, which sends the CI-V PTT command, which a G90 ignores. That is why
Xiegu keys on a hardware line instead. The backend can now do that: Settings →
CAT → Xiegu → how the rig is keyed (CI-V / RTS / DTR).
Untested here — no G90 in reach. The operator who reported it offered to try.
SmartSDR moves its active flag onto the TX slice as soon as the transmit
frequency is touched, and OpsLog followed it. But the operator is listening to
the DX on the other slice: the S-meter, audio level, filter and DSP they are
adjusting all belong there, so following the transmitter handed them the
controls of a receiver they were not using.
In split the receive slice now wins. Simplex is unchanged, and two same-band
slices in different classes (SSB alongside FT8) are still not a split.
A slice clicked IN OPSLOG is remembered and overrides both the split rule and
the radio's focus — that is the operator speaking. Activating a slice on the
radio or in SmartSDR deliberately does not move OpsLog. The pin is dropped when
that slice stops being in use, so a closed receiver cannot strand the display.
The panel now highlights the slice OpsLog is working with rather than the radio's
focused one; reporting the radio's flag would light up one slice while every
control acted on another.
The same ASCII stream over a socket instead of a wire: ser2net, an
Ethernet-serial adapter, a Raspberry Pi at the radio. One TCP transport
presented as a serial port, so the backend keeps a single code path.
Explicitly NOT the radio's own RJ45. A TS-890 or TS-990 speaks Kenwood's
KNS/ARCP there — session, authentication, a different protocol — and that needs
one of those radios in hand to write honestly. The setting's help text says so,
because an operator who plugs in their TS-890's Ethernet port and types its
address deserves to learn that from the interface rather than from silence.
Two details that decide whether this is usable or maddening:
- A read deadline expiring is reported as "no data yet", not as an error. It
is exactly what a serial read timeout means to the caller; as an error it
would make ask() abandon a rig that is merely thinking.
- The log line names what was connected to. "connected on @ 0 baud" after a
network connect sends someone hunting a serial fault that does not exist.
The host wins over the COM port when both are filled: it is the more deliberate
setting, and silently preferring the wire leaves someone staring at an address
they typed and a radio that never answers.
Reported from a Flex in Kenwood CAT mode: frequency read perfectly, split never
appeared. Not every rig speaking this dialect fills IF's split bit.
Rather than guess which column that firmware populates, ask the question that
DEFINES split — is the transmit VFO a different VFO from the receive one — which
is exactly what FR and FT answer, and the same rule the Yaesu backend settled on
after several wrong turns. FR is also trusted over IF for which VFO is in use:
they are asked in the same breath, and a rig vague about the split bit may be
just as vague about the VFO field.
Cost is bounded: a rig that rejects FR/FT answers "?;" once and is never asked
again, so this is two short commands per poll only where it works. Both paths
are tested against the emulator — split found through FR/FT with IF silent, and
IF-reported split still working on a rig that refuses FR/FT without re-asking.
Also extends the CAT wire trace to the Kenwood backend, ASCII quoted so an empty
reply is visible as such: "" and ";" look identical unquoted, and telling them
apart is the whole question when a rig half-supports a command. If this fix is
not the whole story on real hardware, the trace is what will say so.
Nobody here owns a Kenwood, and a backend that has never completed a single
exchange is a guess however carefully it was written.
But this repository already contains the other half of the conversation:
internal/catemu ANSWERS this dialect, pretending to be a TS-2000 so an ACOM
amplifier follows OpsLog. The test's responder replies exactly as catemu does —
same FA/FB widths, same 38-character IF layout, same ID019 — so the two halves
are checked against each other instead of against my reading of a manual.
Connect adds a dialPort seam for this; nothing else changed in the backend.
Covered: model identification, simplex on A, everything following the VFO in
use when on B, tuning writing to that VFO rather than blindly to FA, split with
TX and RX the right way round (ADIF FREQ is the TRANSMIT frequency — swapping
them writes the wrong frequency into every logged QSO), the mode digit, and a
silent port reported as not answering rather than as a connected radio. The last
three are each a fault that actually reached a user through the Yaesu backend.
What this does NOT prove: that a real TS-590 answers on the same timings, or
fills every IF field the way its documentation says. That still needs a radio.
Reported on an IC-9100: the MOX button does not transmit, it sets split. The
source sends the PTT command (0x1C 0x00) and nowhere sends the split one (0x0F),
so the discrepancy is between what OpsLog sends and what the rig acts on — and
that link has already been shown, in the same operator's log, to lose frame sync.
Rather than guess from the command table, this adds what settled the WinKeyer
bug in one line: the actual bytes. Settings → CAT → Log the CI-V protocol writes
every frame in both directions as hex. RX is traced BEFORE framing, since the
bytes as they arrived are what reveals a lost boundary — a decoded view would
hide exactly the fault being hunted.
Session-only, like the keyer trace: it is a diagnostic, and a log full of hex
helps nobody who forgot it was on.
I am not claiming a cause yet. The last time I inferred one from a protocol
document rather than from evidence, I inverted a digit and broke the case that
worked.
From F4JND's log: a rig sitting on 145.550 MHz FM produced status lines reading
16445550000, 8364550000, 5817408364 and 12640 Hz between good readings — the last
of those even resolved to a 6 cm band.
BCDToFreq treated the nibbles 0x0A..0x0F as the digits 10..15, so a byte stream
that had lost frame sync still decoded into a number. The same log shows why sync
was lost: PTT round trips timing out and a scope stream the rig kept rejecting
(0x27), both competing with the poll on one serial line.
Non-decimal nibbles are now refused. Each caller keeps its last good value rather
than publishing noise: the frequency reader returns an error, the sub-VFO reader
returns not-ok, and the scope keeps its previous edges. Garbage that LOOKS like a
frequency is worse than a refused frame — it reaches the display, the band slots
and eventually the log, and it is the operator who then has to disprove it.
The test builds its fixture with FreqToBCD rather than by hand, because my
hand-written one was byte-reversed and the test caught it.
This does NOT explain the white screen that was also reported, and I have not
claimed it does: it removes one source of absurd values reaching the UI, which is
worth doing on its own evidence.
A sixth CAT backend, talking to a TS-590/890/990/2000 over its serial port with
no OmniRig in between — frequency, mode, VFO, split and PTT. Elecraft K3/K4 and
the "Kenwood" setting on other radios speak the same dialect.
The protocol was not researched from scratch: internal/catemu already ANSWERS
these commands, pretending to be a TS-2000 so an ACOM amplifier follows OpsLog.
The IF frame layout here is read from the same format string catemu emits, so
the two halves of the repository agree by construction — and the test caught my
own fixture being one character short, which is exactly the error that layout
invites.
Design notes worth keeping:
- IF; is the poll. One frame carries frequency, TX state, mode, VFO and split,
so simplex operation costs a single round trip; the other VFO is only asked
for when split is actually on.
- FA/FB take ELEVEN digits here where Yaesu uses nine. That is the likeliest
place to copy the Yaesu backend and be wrong by a factor of a hundred, so it
has its own test.
- Every lesson the Yaesu backend learned the hard way is built in from the
start: replies matched to the command that asked, "?;" remembered so a poll
stops paying a timeout for an unsupported command, the serial handle closed
before reopening, and a rig that answers nothing reported as absent rather
than "connected".
Untested on hardware — I have no Kenwood here. The frame parsing is covered by
tests against catemu's own format.
Reported, and reasoned out from the half that already worked: a spot click now
tunes the right VFO, but it set the mode with MD0 — main — whatever VFO the
operator was on. Working from SUB, the spot changed the mode of the VFO NOT in
use and left the one in use as it was. The author inferred the sub half from
seeing main change, and was right.
The read had the same fault, so the console displayed main's mode while showing
the sub VFO's frequency.
Mode now addresses the receiver in use — MD0 for main, MD1 for sub — in the poll
read, in SetMode, and in the console's raw-mode setter. A rig without a sub
receiver never sees MD1: curVFO only becomes B where the rig reports its receive
VFO in the first place.
Reported on an FTDX101: with RX and TX moved together to the sub VFO, OpsLog
showed split — and took the MAIN frequency as the transmit one, which would log
the wrong frequency.
ST is a bare flag. It says "split" without saying which VFO transmits, and this
rig raises it whenever the transmit VFO is the sub one, whether or not the
operator is also listening there. FT names the transmit VFO, so where the receive
VFO is known as well (FR), split is derived from the pair: they differ or they do
not. That is a fact about the rig's state rather than a flag whose meaning varies
by model.
The fix is therefore in the probe ORDER, not in the reading: FT is asked first
when FR answered, and ST stays the fallback for rigs that have neither. A test
pins the order and both halves of the trap — that FT gets this case right, and
that ST alone gets it wrong.
The seven minutes of reconnects in the operator's log turned out to be the radio
switched off — no defect. But the log said otherwise:
20:14:42 yaesu: ID query failed (timeout) — continuing…
20:14:44 yaesu: connected on COM7 @ 38400 baud, model="FTDX101D"
Opening the port was treated as connecting, and the model name was left over from
the previous session, so a powered-down rig produced a line claiming it was
connected as a named model. That is what sent me looking for a software fault.
Connect now tracks whether ANY probe answered. If none did, it clears the stale
model, says so in terms an operator can act on — is it powered on, is the CAT
rate right — and returns an error so the reconnect loop keeps trying rather than
believing it succeeded.
Connect assigned a fresh handle to y.port without closing the one it already
held. It is called again on every reconnect, so a rig that fails to answer leaks
one handle per attempt — every few seconds, indefinitely.
Windows opens a serial port EXCLUSIVELY, which makes this visible as "yaesu: open
COM7 @ 9600 baud: Serial port busy" in an operator's log: OpsLog was competing
with itself for the port.
This is a real defect found while reading the reconnect loop, but I want to be
straight about its limits: it does NOT explain the seven minutes of "ID query
failed — timeout" in the same log, where the port opened and the rig stayed
silent. That one is still open, and the operator alternates between two separate
radios (a TCI SDR and the Yaesu) rather than sharing one port, so a conflict
between backends is not the explanation either.
Reported both ways round, which is what settles it. With RX and TX on SUB
everything worked; with them on MAIN the frequency froze and a spot click tuned
the sub VFO. The log shows the rig answering FR01 in the MAIN case.
So on an FTDX101 the state is the FIRST digit — 0 = main — and the second is a
separate parameter. Yesterday I changed this to the LAST digit, which read FR01
as SUB and inverted the whole thing. The fault that change was meant to fix ("SUB
shows MAIN") came from reading VS instead of FR, and probing FR had already fixed
it on its own; the digit change was an unnecessary guess layered on a real fix.
Nothing else needed changing for the operator's two questions: the display and
SetFrequency already follow curVFO, so a spot now tunes whichever VFO holds RX
and TX, and the main frequency is read when on main. A test states that in those
terms — active VFO and which command a spot writes — rather than only as a byte,
because the byte is what I got wrong while believing the logic was right.
The log named the cause in one line: yaesu: receive VFO is read through FR
(answered "FR01;"). An FTDX10 answers "FR0;" — one digit — so reading the digit
straight after the prefix worked there and took the 0 of "01" on an FTDX101.
The rig was on SUB and OpsLog said MAIN, which is the reported "Sub shows the main
frequency". It also explains the second half: split is "TX VFO differs from RX
VFO", so a receive VFO that alternated between right and wrong made the split flag
flip between consecutive polls and swap freq and rx in the log.
The state is now taken from the LAST digit before the terminator, which is correct
on both forms, and the same reading applies to ST/FT/VS rather than leaving the
next two-digit rig to be found in the field.
On the operator's question of whether freq should simply be the active VFO: no —
freq is the TRANSMIT frequency by ADIF convention (FREQ is the QSO frequency,
FREQ_RX the receive one, set only when split). In simplex they are the same, so it
does read as the active VFO; in split it must stay the transmit VFO or the log
records the wrong frequency. What the operator saw was this bug, not that rule.
The operator's description separates the two cases precisely, and that is what
identifies the fault. RX alone on SUB displayed VFO B correctly; RX AND TX on
SUB — entirely on the sub receiver — displayed VFO A.
VS was being read as "which VFO is in use". On an FTDX101 it does not answer that
question: with both RX and TX on sub it still reported the main VFO. FR is the
command that selects the RECEIVE VFO, and it is now asked first, VS remaining the
fallback for models that lack it.
Split falls out of the same correction: split means the TRANSMIT VFO differs from
the RECEIVE one, so a wrong receive VFO made the comparison wrong too — which is
the second half of what was reported. A test covers all four RX/TX combinations,
including the one that is NOT split (both on sub) and reads as split if you take
the transmit VFO alone.
Reported on an FTDX101: the panel showed split ON with the radio OFF, and the
reverse — while the same model behaved correctly for another operator, and an
FTDX10 was fine throughout. That pattern is the clue: one operator was on MAIN,
the other on SUB.
The two commands say different things and were read alike:
ST is a split FLAG — ST1 means split, whatever VFO is in use.
FT names the TX VFO — FT0 = transmit on A, FT1 = transmit on B.
Split is on when the rig transmits on a DIFFERENT VFO from the one it listens to.
Reading FT1 as "split" is therefore correct only on VFO A, and exactly inverted
on SUB. It is now compared with the current VFO.
Writing had the identical fault, and it was worse: sending FT1 for "split on"
while the operator listened on SUB CLEARED the split it was asked to set. Both
directions are pinned by tests over each VFO state.