Commit Graph
314 Commits
Author SHA1 Message Date
rouggy f6cd6e999a chore: release v0.22.9 2026-08-02 06:40:10 +02:00
rouggy 296a4a55c0 refactor(cat): one DTR/RTS setting per backend, none shared
The 0.22.7/0.22.8 breakage came from a Xiegu-reported fault being fixed
inside the Yaesu and Kenwood backends. The lesson is structural: nothing
a backend does may be steered by another backend's report or another
backend's setting.

The lower-lines checkbox introduced for 0.22.9 was still one shared key
applied to both Yaesu and Kenwood — the same reflex in miniature. It is
now cat.yaesu.low_dtr_rts and cat.kenwood.low_dtr_rts, each checkbox in
its backend's own settings block, each wired only to its backend. The
shared key never shipped, so nothing needs migrating.
2026-08-01 20:35:57 +02:00
rouggy acf73c73c7 fix(panels): the wheel moved whatever slider the pointer was over
The rig-panel sliders take a non-passive wheel listener and acted on
hover, so scrolling the panel fed the gesture to whichever control sat
under the cursor — RF power included, mid-QSO. An operator scrolling to
read the bottom of the panel could change his transmit power without
touching anything.

They now require focus, i.e. a deliberate click on that slider first.
Without it the event is left alone and the panel scrolls, which is what
the operator meant. Same fix on the Flex and Yaesu panels, which share
the pattern.

This is a fault in its own right. It is NOT an explanation for the
"transmit power moves since 0.22.8" report: that release changed nothing
in the Icom path, and this behaviour is older than it.
2026-08-01 20:00:03 +02:00
rouggy 5777c119cb feat(cat): make the DTR/RTS lowering a setting
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.
2026-08-01 19:25:04 +02:00
rouggy 85bf0da006 fix(awards): the mode filter only hid rows, it did not filter the award
Selecting CW showed FT8 and SSB contacts. The filter ran client-side over
the reference list and nothing else, so a reference kept because it had
one CW contact still displayed the band cells it earned on SSB, still
counted its SSB confirmations, and opening it listed every contact
regardless of mode.

The class now narrows the log BEFORE the engine runs, so the bands, the
totals and the confirmations all describe the selected mode, and
AwardCellQSOs takes the same class. The panel cache is keyed by
"CODE|MODECLASS" — one key per award showed the previous mode's numbers
after switching.

The snapshot is cached by log revision, so this costs a matching pass and
no database read.
2026-08-01 16:10:22 +02:00
rouggy 6255a9f218 feat(details): NEW badge on a county never worked
The cluster already answers this question for a spot; the entry panel did
not answer it for the station actually being worked. A county hunter
needs to know before the QSO ends — by the time the county shows up in
the awards table the station is long gone.

qso.CountyWorked asks about one county instead of loading the whole
worked set, narrowed to the state so it stays a few dozen rows on a
remote MySQL. It compares through award.USCountyKey on both sides rather
than in SQL, because logged spellings vary ("Los Angeles" against
"LOS ANGELES, CA") and that key function is what resolves it everywhere
else.

An empty or non-US county is not "new" but unknown, and shows nothing: a
badge on a guess is worse than no badge.
2026-08-01 01:46:32 +02:00
rouggy 599621a998 fix(ui): drop the focus ring on tab panels
Radix gives the content element focus when a tab is selected, so the
focus-visible ring fired on an ordinary click and outlined the whole
lower pane — visible as an orange rule under the tab strip. A tab panel
is a container; the controls inside it carry their own focus styling.
2026-08-01 01:38:23 +02:00
rouggy 589e0b9b6a style(details): size the Info tab fields to their contents
Six equal columns gave a US county name the same width as a two-digit CQ
zone: the county truncated while the zones, the prefix and the DXCC
number sat mostly empty. The row is twelve columns now, spans set per
field.

The bearing and the two distances are read-only and never exceed
"12345 km", so they move to a flex row at a fixed width and the address —
the one field here that was never wide enough — takes the remainder.
2026-08-01 01:22:35 +02:00
rouggy c21d6cc23a fix(awards): live detection only saw the address, never the QTH
The award editor's Test tab matched DK2FJ on its OR rule "qth / exact"
and reported the two Aachen DOKs as ambiguous. The F3 panel, on the same
contact, showed nothing — it builds its own QSO for ComputeQSOAwardRefs
and that object carried address, state, county, zones and continent only.
Any award keyed on the town could not fire there, so live detection was
silently blind to a whole class of awards and the operator only saw them
after logging.

It now passes the entry-strip text as well: QTH, name, country, comment,
note and grid.
2026-08-01 01:15:05 +02:00
rouggy ae21ddb9d7 feat(awards): offer the ambiguous references at log time
The refusal to guess was only visible in the award editor's test tab and
in the awards table — both of them places an operator goes long after the
contact is over. It has to appear while the station is still in front of
you, so ComputeQSOAwardRefs now reports the candidates it declined to
assign and the F3 panel lists them as buttons: click one and it is
written to award_refs, replacing any sibling picked for that award.

They are never auto-added — adding one would be exactly the guess the
setting exists to refuse.

Also: the dark-theme fix for the native calendar glyph named only
input[type=date], so date-time fields kept an invisible icon. All the
temporal input types now share the rule.
2026-08-01 01:10:59 +02:00
rouggy a4623e9ea3 feat(awards): refuse an ambiguous match on one-reference awards
Two DARC DOKs are both called "Gießen" — two clubs share the town. Exact
matching finds both and both are right as far as the matcher can tell, so
it kept both and the operator got a QSO credited to a DOK that may well
have been the other one.

Picking one is not an option: it writes a reference into the log with no
evidence behind it. So a Def can now be marked one reference per QSO, and
an ambiguous match on such an award assigns none, names the candidates in
the trace and leaves the contact under missing references — where the
operator's choice now sticks (06e3437).

Off by default, and deliberately NOT a revival of the old `Multi` flag: a
field holding several references (an n-fer POTA activation, a VUCC grid
line) is several references, correctly, and stays that way.
2026-08-01 01:04:17 +02:00
rouggy d4bfd30636 fix: serial CAT keyed the radio on connect; Xiegu can key on RTS/DTR
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.
2026-07-31 22:16:03 +02:00
rouggy ed67ed7fe3 fix: replaying a QSO used the voice keyer's level and overdrove the rig
A panadapter shot settles it: answering an S5 station, the replayed recording
goes out several times wider and taller than the station being answered — in WAV
as well as MP3, so this is not the encoder.

One setting served two sources that have nothing in common. A voice-keyer
message is a microphone at speaking distance and legitimately wants 195%; a QSO
recording is a receiver's line output, which is far hotter. The same 195% put it
into the transmitter flat out, ALC pinned, noise and voice alike.

The QSO playback now has its own level, defaulting to 100%. The voice keyer
keeps the setting it had, so nobody's F-key messages change.
2026-07-31 21:32:55 +02:00
rouggy 2b66e5bc7f fix: the audio level sliders did nothing until Settings were saved
Reported again after the monitor was wired up: 300% still sounded like 10%. The
monitor fix was right but could not show, because the sliders only changed the
settings panel's own state — the value reached the audio engine when the
operator clicked Save.

A level is set by ear, by dragging. It now applies on every move, to the monitor
and to the recorder mix alike; saving still persists it. Both halves were needed
and neither works alone.
2026-07-31 20:58:40 +02:00
rouggy 3e22c4d1a3 feat: filter award references by mode class
All / CW / Phone / Digital, stacked on top of the worked/confirmed filter. The
question an operator actually has — which DXCC entities have I worked on CW but
not confirmed on CW — needs both at once; answering only one of them is what
sends people to a spreadsheet.

References carried bands but not modes, so the award computation now aggregates
mode CLASSES per reference, worked and confirmed separately. Classes, not ADIF
modes: nobody chasing CW cares whether the digital side was FT8 or RTTY, and a
list of twenty mode names would not answer the question. An unrecognised mode
counts as data rather than as nothing, so a new digital mode does not vanish
from the filter the day it appears.

Two rules the filter needs to be honest:

  - Not-confirmed means not confirmed ON THIS MODE. An entity worked on CW and
    confirmed on SSB is still a CW entity to chase, and that is exactly what the
    filter is for.
  - "Not worked" plus a mode is a contradiction — an unworked reference has no
    mode — so the mode filter stands aside there instead of emptying the list.
2026-07-31 16:09:11 +02:00
rouggy e4217da010 fix: right-click menu cut off at the bottom of the window
Closes #9.

It was clamped against a GUESSED height — 230 px with the upload submenu, 110
without. The menu is far taller than that with a selection, so right-clicking on
a row near the bottom hid half the entries, delete among them.

It is measured now: placed above the cursor when it does not fit below, pinned
to the top with its own scrollbar when it fits neither way. Its height depends
on which actions the caller passes, so no constant could have stayed right.
2026-07-31 12:25:37 +02:00
rouggy eb40991482 fix: the power headings sat one column left of their boxes
The rule between the band-driven columns and the mode-driven ones was an extra
empty cell added to the body rows only. Seven cells per row against six in the
header slid every heading across: Phone stood over the TX antenna, CW over
Phone.

The rule is now drawn on the first power cell itself, in the header and the body
alike, so the two cannot drift apart again.
2026-07-31 12:00:21 +02:00
rouggy 781cf636ea fix: one table per band instead of two
Antennas and power were two tables listing the same bands, one under the other,
so configuring a band meant matching rows by eye between them.

They are now one row per band. That the two settings are stored separately and
applied on different triggers — antennas on band change, power on band or mode
change — is a backend detail, and no reason to split what is a single decision
per band for the person configuring it. A rule in the header keeps the two
groups readable.
2026-07-31 11:52:03 +02:00
rouggy 62b6fe0a3a feat: FlexRadio TX power per band and mode class
Settings → FlexRadio, beside the per-band antennas and applied the same way:
when the band or the mode changes. 1.5 kW that is fine on SSB has no business
going into an FT8 signal on the same band.

Three classes rather than one row per mode, because what decides the power is
duty cycle and what the amplifier will take of it, not the mode's name — FT8 and
RTTY punish an amplifier the same way, SSB and AM do not. The classification is
pinned by a test: a digital mode sorted as phone would deliver exactly the
1.5 kW this exists to prevent.

An empty box means "leave the power alone", never zero watts, and an unknown
mode changes nothing — setting a transmit power from a name we do not recognise
is not a good guess.

Unlike the antennas, a locked band does not skip it: the lock means "do not let
the rig drag my log entry around", not "let the amplifier keep whatever the last
mode left".
2026-07-31 11:30:46 +02:00
rouggy 7a7fa62af7 feat: choose how dates are displayed (Standard / French / US)
Settings → General, beside the language. An operator reading 2026-07-30 as the
7th of the 30th month is reading their own log wrongly, and that is a display
problem.

Storage stays ISO/ADIF, deliberately and permanently: it is what ADIF
specifies, it sorts correctly as text, and a stored format that followed a UI
preference would make the log unreadable the day the preference changed. Exports
and uploads are untouched.

Two details that decide whether it actually works:

  - The columns are rebuilt when the format changes. The formatters are captured
    inside AG-Grid's column definitions, so nothing else would notice and the
    table would keep the old format until something forced a rebuild.
  - main.tsx re-reads the choice after the portable prefs are pulled from the
    database. The module reads localStorage at import time, which is BEFORE that
    sync — so a copied data/ folder would have shown ISO until the next launch.

UTC is not part of the choice and never will be: a logbook is kept in UTC, and
showing local time would make the display disagree with the QSO's own record
twice a year.
2026-07-30 22:22:33 +02:00
rouggy 6ee4c430c7 feat: colour the QSL and upload status letters
Y green, N red, R blue — colour only, no pill or badge. These columns sit among
callsigns and dates, and a row of coloured boxes would shout louder than the
callsign it belongs to.

Twelve columns: paper QSL, LoTW, eQSL, Club Log, HRDLog, QRZ.com both ways, and
OpsLog's own card and recording flags. All four views the operator named —
recent QSOs, worked before, NET Control, QSL manager — turn out to share this
one grid, so they are all covered by construction rather than by four edits that
could drift.

Semantic tokens rather than fixed colours, so the four themes follow and the
contrast stays right on the light ones. ADIF's I (ignore) is deliberately left
in the default ink: it is neither good news nor bad, and colouring it would put
it in one camp or the other.
2026-07-30 22:12:23 +02:00
rouggy f5b74856c6 fix: the remote-delete switch sits above the service tabs
It applies to QRZ.com and Club Log both, but it was rendered after the tab
strip — so it read as a QRZ setting when the QRZ tab was open, appeared to
repeat itself on the Club Log tab, and was invisible to anyone opening a third
one.

Above the tabs it belongs to the panel, which is what it actually is.
2026-07-30 22:01:45 +02:00
rouggy d42fdab85c feat: withdraw a deleted QSO from QRZ.com and Club Log
Opt-in, off by default: neither service can undo it, and a local delete is not
always a statement about the world — a duplicate cleared out of a contest log
was never meant to change the DX's log.

The two APIs are not alike, and the difference decides what is possible:

  - Club Log deletes by MATCH (callsign, exact timestamp, band number), so it
    works for every QSO in the log, past or future. The band table is its own
    list and is pinned by a test: a band mapped to the wrong number does not
    fail, it points the delete at a different QSO.
  - QRZ.com deletes ONLY by logid. So markExtUploaded now stores the LOGID it
    already received on every upload — it was being logged and thrown away. A
    QSO uploaded before this cannot be withdrawn from here at all, and the log
    says so by name rather than leaving the operator to assume it worked.

Both run BEFORE the local delete, because both need the QSO's own fields to
find their copy and there is nothing left to ask with once the row is gone.
Neither can block it: the operator asked for the QSO to go, and a refusing
website is not a reason to keep it.
2026-07-30 21:57:40 +02:00
rouggy 83a8708d60 fix: remember the award sort, and the grey line, across restarts
The sort choice is now written through writeUiPref and both keys are registered
as portable, so they travel with a copied data/ folder like every other UI
preference.

Registering them is the part that matters: writeUiPref stores to the DB, but
only keys on the portable list are read BACK at startup. A key written and never
restored looks exactly like one that was never saved — which is what would have
happened here.

The grey-line toggle had the same gap. It called writeUiPref from the day it was
written but was never added to the list, so it came back off on the next launch.
Found while adding the award keys beside it.
2026-07-30 21:49:25 +02:00
rouggy c0671bfe2e feat: sort award references by reference or description
Reference stays the default: that is how award lists are published and how a
paper list is read. Description answers the other question an operator actually
has — "have I worked anything in Alicante" — which reference order scatters
across eight thousand rows.

Sorted where the filtered list is built, so the grid and list views inherit it
rather than each growing its own copy that could disagree.

localeCompare rather than a byte comparison, because these lists are Spanish,
French and Italian place names: "Ávila" belongs beside "Avilés", not after
"Zaragoza". References compare numerically for the same reason in reverse —
08019 must not sort between 0801 and 081.
2026-07-30 21:47:05 +02:00
rouggy 3a87b58ba3 feat: Kenwood CAT over a network serial bridge
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.
2026-07-30 21:13:19 +02:00
rouggy f82631690a fix: grey line cut off at the edge of the world copy
The map repeats the world sideways, and zoomed out several copies are on screen
at once. The rings span −180…+180 once, so the shading ended in a hard vertical
edge wherever the next copy began: it read as a rectangle laid over the planet
rather than as night.

Each ring is now drawn again shifted a full turn either way. Three copies cover
every zoom this map reaches and cost nothing — three more polygons on a canvas
layer.

Zooming the map to fit one world would have hidden it rather than fixed it, and
would have taken away the free pan and zoom the operator has.
2026-07-30 19:35:26 +02:00
rouggy 3b58e39eec feat: grey line on the world map; fix: trace checkboxes lied about their state
Grey line — the day/night terminator and its twilight band, off by default,
redrawn every minute so a map left open all day is not silently wrong by
evening. Its own Leaflet pane below the overlays, non-interactive, so it never
hides the path or beam nor swallows a click.

The solar maths is pure and was checked against known positions before being
wired to anything: both solstices, the March equinox, the subsolar longitude at
12 UTC, and day/night at Paris, Tokyo, New York, Sydney and Reykjavik across
seasons and hemispheres. That last set is what caught the real bug: the equation
has TWO solutions per meridian and the naive branch put New York in daylight at
02:00 in December — the terminator at +63° where it belongs at −63°. A shaded
map that looks entirely plausible and is exactly mirrored. It now anchors on the
classical terminator and takes the branch nearest it, which is also what makes
the twilight band follow the terminator instead of jumping hemisphere.

Separately, from a Xiegu user: the protocol-trace checkbox does not stay ticked.
It is React state initialised to false on every mount, so a trace still running
came back unticked — the operator ticks it to "switch it on", which switches it
OFF, and the log they send contains no trace at all. Worse than a cosmetic bug:
it silently defeats the one tool asked for to diagnose their radio. Both boxes
(CAT and WinKeyer) now read their real state from the backend.
2026-07-30 18:06:12 +02:00
rouggy eb271e8f20 fix: Kenwood split read from FR/FT when the status frame omits it
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.
2026-07-30 16:50:06 +02:00
rouggy 08a932c9c1 fix: drop the per-column filters from the recent-QSO grid
They searched only the rows on screen. With the grid holding the most recent
page, filtering a column for RSGB-IOTA across a 28 000-QSO log returned nothing
— which reads as "the log does not contain it", not "it is not on this page".
That is how an import that had worked perfectly came to be reported as broken:
the tool answered a narrower question than the one being asked, and said so
only by being empty.

The advanced filter queries the whole logbook and is the honest instrument. The
headers also get back the width the filter menu icon was taking, which is the
smaller reason but a welcome one on a grid this dense.

No saved filter can be stranded by this: the grid persists widths, sort and
visibility, never a filter model.
2026-07-30 15:13:25 +02:00
rouggy d322ea9a07 feat: report UI crashes instead of showing a blank window
Two white-screen reports are open — one after logging a 10 GHz QSO, one on
starting a DVK auto-call — and neither could be investigated, for the same
reason: a render error emptied the window and left NOTHING. No line in
opslog.log, no dialog, nothing the operator could send but the words "white
screen". A fault the user cannot report is a fault that cannot be fixed.

So before hunting either trigger, the reporting path:

  - An error boundary catches a render throw, writes the error and component
    stack to the app log through the new LogUIError binding, and shows it on
    screen — selectable, with Copy and Reload.
  - Global handlers catch what a boundary cannot: throws from timers, event
    handlers and rejected promises. An auto-call loop lives entirely in
    callbacks, which is exactly where the second report came from, and those
    leave no trace at all today.

It deliberately does not try to resume: React cannot promise sane state after a
render throw, and a half-working logger would be worse than a clear stop.

This does not fix either crash. It makes the next occurrence arrive with its
cause attached, which is the step that has been missing.
2026-07-30 14:36:19 +02:00
rouggy 75c02ea2a0 feat: CI-V byte trace, for a fault the command table cannot explain
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.
2026-07-30 14:30:05 +02:00
rouggy b16cb973f2 feat: native Kenwood CAT backend
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.
2026-07-30 11:56:41 +02:00
rouggy da886de189 feat: support ERC rotator controllers through the existing GS-232A backend
An ERC (Easy Rotor Control, incl. ERC Mini) emulates Yaesu GS-232A/B, which is
exactly what the backend written for the microHAM ARCO already speaks — azimuth
out (Maaa), azimuth in (C), stop (S), which is the whole of what was asked for.
So this is two small gaps rather than a new backend:

  - Serial speed was hardcoded to 9600. An ARCO's virtual COM ignores it, but an
    ERC runs at whatever its own configuration sets, and a mismatch reads as a
    dead rotator. It is now selectable beside the COM port.
  - The entry was called "microHAM ARCO", so an ERC owner would never have found
    it. It is named after the PROTOCOL now: "GS-232A controller (microHAM ARCO,
    ERC…)".

The help text states the condition plainly in both languages, because it is the
one thing that will otherwise waste an evening: an ERC left on Hy-Gain DCU-1
speaks a different command set and simply will not answer.

Untested on hardware — I have no ERC here, and the GS-232 path itself is proven
on a real ARCO.
2026-07-30 11:07:57 +02:00
rouggy a8990ff29d fix: the status bar named the settings database, not the logbook
Spotted by the author: on SQLite the database chip showed a.dbPath — the
settings/profile database — under a tooltip reading "Local SQLite logbook".

Those became two separate files when the logbook was split out, so an operator
checking where their QSOs live, or which file to copy before a trip, was pointed
at the wrong one. MySQL was already right.

It now resolves the logbook the same way connectLogbook does: the active
profile's own file if it names one, then the default logbook.db, and the settings
db only in the case where it genuinely doubles as the logbook.

Also moves the 4O3A mark beside its menu label instead of the far edge of the
row, as asked.
2026-07-29 23:53:18 +02:00
rouggy e3b810b4ff feat: draw the actual 4O3A mark in the sidebar, not a text badge
The wordmark I put there was not what the author expected: the brand is "4", a
green waveform of stacked lenses, then "3A".

Only the waveform is SVG. The digits stay HTML text so they keep the sidebar's
own typeface at any theme or zoom — an SVG <text> would pick its own font and
drift from the menu around it. Seven ellipses, tallest in the middle, match the
shape of the mark.

Still drawn rather than fetched: an image off the web has no verifiable
provenance, and this stays one small component to swap if the official SVG is
preferred.
2026-07-29 23:37:57 +02:00
rouggy 337392bb6d feat: mark the 4O3A panels in the settings sidebar
Antenna Genius and Tuner Genius now carry a small 4O3A wordmark, so the two
panels that drive that hardware are recognisable without reading the labels.

Drawn inline rather than shipped as an image file, following the language
picker's flags: it scales with the row, follows the theme through currentColor,
and adds no binary asset to the repository.

It is a plain wordmark, not the manufacturer's logo artwork — I did not pull an
image off the web, since I cannot verify the provenance or licence of what I
would find, and redistributing a company's mark inside the product is the
author's call. The component is one function and takes the official SVG in its
place if that is preferred.

Not applied to the Amplifier panel: it covers both the 4O3A PowerGenius XL and
the SPE Expert range, so a single vendor mark there would be wrong.
2026-07-29 23:28:27 +02:00
rouggy 67a03c3ddc feat: a level control for the voice keyer, and name every CAT backend in its PTT list
The messages went to the radio exactly as recorded. Nothing in OpsLog could
raise them, so a microphone captured quietly drove the rig quietly and the only
remedies were the radio's own USB input menu or the Windows mixer — which is
where the operator was heading. There is now a level from 10 to 400 %, applied
with clamping (a wrap would turn loud speech into noise on the air), and Play
previews at that same level so the adjustment is made against what will actually
be transmitted.

The PTT method list also named only OmniRig, Flex, Icom and TCI, so choosing a
native Yaesu left "CAT" with no backend beside it — which reads as "there is no
CAT PTT for my radio" and sends the operator to RTS on a COM port that has
nothing to do with the rig. Same list-needing-every-member shape as three
earlier bugs in this feature. The TestPTT log line had the same rot: it said
"CAT via OmniRig" whatever backend was running.
2026-07-29 14:29:47 +02:00
rouggy 8da3a27803 fix: the PWR bar stayed at full scale after transmitting
Receiving cleared the two bar percentages but not the values the bars now
actually draw from: the watts and the SWR ratio, added when the meters were
corrected. So the power bar sat pinned across the panel with the rig plainly
receiving — the dash in the label said one thing and the bar another.

Every transmit value is cleared now, and the peak-hold state with them: a peak
left in the holder would have carried the last transmission's reading into the
start of the next one, which is worse, being wrong while it looks live.

The panel also draws both bars from zero unless the rig reports transmitting, so
a value that has not been refreshed yet cannot show as output.
2026-07-29 14:20:58 +02:00
rouggy 8e491544dd fix: power is RM5, not RM4 — the CW keying was the experiment
An FM carrier could not answer it: constant by definition, so nothing to
correlate. CW at 100 W did, because the keying itself varies the output:

  key down: RM4=25 RM5=207 RM6=13
  key up:   RM4=25 RM5=0   RM6=0

RM5 follows the RF envelope exactly — it IS the power meter. RM4 sits near 25
whether the key is down or up, so it is not measuring output at all, and reading
it as power is what showed 8 W on a 100 W transmission. The operator's hunch was
right and my first reading of the ramp was wrong: what I took for a needle
rising was RM4 drifting, not tracking.

Watts are now derived from that meter (207 = 100 W, measured) instead of the
power SETTING scaled by a percentage — the setting says what was asked for, the
meter says what left the radio.

The bars also hold their peak with a gentle decay. In CW the meters genuinely
read zero between elements, so following the raw value made them flash to nothing
several times a second; a needle has inertia, and this only ever holds a value
the radio really reported.
2026-07-29 13:56:59 +02:00
rouggy bdda7ad07a fix: read the real SWR on the Yaesu, and show the ratio
A second measurement at a known mismatch settled both the index and the scale.
At SWR 1.1: RM6=0. At SWR 1.5: RM6=52, while RM4 kept tracking the power.

52/255 = 0.204, which is the reflection coefficient of a 1.5 SWR to three
decimals. So the raw value is rho scaled to 255, and the ratio is
(1+rho)/(1-rho) — physics, not a curve fitted through two points, which is why
2.0 and 3.0 fall out of it correctly without ever having been measured.

The panel now shows that ratio, the number the operator reads on the rig, rather
than a percentage of meter travel — and a dash while receiving, since a stale SWR
from the last transmission reads as a live one.

Both measurements are recorded in the code and in a test, so the mapping is
evidence rather than a table borrowed from another model — which is exactly how
it came to read 81 in the first place.
2026-07-29 13:49:09 +02:00
rouggy 48d92ff547 fix: CW speed drove the wrong keyer; survey the meters to find the SWR one
Speed: with DTR/RTS line keying the PC does the timing, so the Yaesu console's
slider — which sets the rig's internal keyer — changed nothing audible and looked
broken. Both entry points now go through one handler that drives the engine
actually sending, and additionally sets the rig's own keyer whenever a Yaesu is
on CAT, so the radio's front panel shows the same figure.

SWR: an operator reads 80 on the bar with a real SWR of 1.1. That is the shape of
reading the WRONG METER — ALC, say — not of a scaling error, and which RM index
carries which meter is not consistent across the family. Rather than guess again
and move the wrong number somewhere else, the first transmission now logs RM1
through RM6 raw, once. The log will say which index is which on this radio, and
the mapping can then be corrected as a fact.
2026-07-29 13:32:29 +02:00
rouggy 0190255762 fix: the Yaesu keyer asked for a COM port it does not use
Selecting the Yaesu keyer fell through to the WinKeyer branch of the settings,
which offers a serial port picker and a Connect button. So the operator went
hunting for the right port — trying the standard COM and the enhanced one in turn
— and neither connected, because this keyer uses NEITHER: it keys over the CAT
link already configured in Settings → CAT.

It now has its own branch, like the Flex one: a speed field, the warning when the
CAT backend is not a Yaesu, and a line saying explicitly that keying rides the
CAT link. Speed changes go to the rig's keyer instead of a WinKeyer that is not
there.
2026-07-29 12:49:19 +02:00
rouggy 08f3e54afb feat: CW through the Yaesu keyer — fifth CW engine
The radio has a keyer and a command to feed it (KY), so an FTDX10 needs no
WinKeyer and no second cable, exactly as the Icom CI-V and Flex CWX engines
already do. The rig keys with its own timing, which is why the spacing is right
where a PC keying a line through USB latency drifts.

Text is filtered to what the keyer can actually send: an unsupported byte does
not produce an error on a Yaesu, it can abort the whole buffer, so the rest of a
macro would vanish silently. It is then fed in 24-character pieces, waiting for
room between them — the rig DROPS what does not fit, again with no error, so a
contest CQ would lose its tail.

STOP is the honest gap. Yaesu documents no buffer-clear, so it drops the
transmitter (TX0) instead: nothing queued reaches the air, which is what Escape
means to an operator. It deliberately does NOT send "KY0;" — a plausible-looking
clear that the rig would read as the CHARACTER zero and transmit.

A test caught a real one on the way: tabs and newlines were dropped as
"unsupported", gluing the words either side together, so a macro written on two
lines went out as CQCQ. Whitespace now becomes a word gap before filtering.

Settings warn when the Yaesu keyer is selected without the Yaesu CAT backend —
otherwise it simply never keys, with nothing on screen saying why.

Send path follows the CAT reference and how Hamlib drives these rigs; NOT yet
verified on the air.
2026-07-29 12:42:40 +02:00
rouggy 520d17810c fix: the Yaesu panel showed the TRANSMIT frequency as the main VFO under split
Confirmed on the FTDX10: receiving on 14.018.98 and pressing SPLIT put 14.019.98
in the big display with "TX 14.019.98 (0 kHz)" under it. The radio was right —
RX 14.018.98, TX 14.019.98, up 1 kHz on CW — the panel was not.

RigState follows the ADIF convention where freq_hz is the TRANSMIT frequency, so
under split it is the OTHER VFO. Taking it as the main display showed the
operator the frequency they transmit on, and then an offset of that frequency
against itself: zero.

The header now reads the listening frequency (freq_rx_hz when split, freq_hz
otherwise) and the TX line shows the real transmit frequency and offset.
2026-07-29 12:19:49 +02:00
rouggy c9218310ae feat: the Yaesu console follows the mode — CW gets its own controls
Microphone gain and VOX are meaningless in CW: the rig ignores both, so showing
them is showing dead controls. They are hidden, and a CW card takes their place
with keyer speed (KS), break-in (BI) and ZIN (ZI), the zero-in that retunes so
the station being received lands on the operator's own pitch.

ZIN is a one-shot with no state, so it is a plain button rather than a chip that
would look latched, and no settings read-back follows — the frequency change
arrives through the normal poll like any other.

The keyer values are read on the slow beat whatever the mode, so the card is
already populated the instant the operator switches to CW instead of filling in a
poll cycle later.

Which controls show is decided by the RIG's mode, not the logged one: the logged
mode can be a digital sub-mode the radio knows nothing about.
2026-07-29 12:07:28 +02:00
rouggy 8cc6997eec feat: colour the Yaesu S-meter like a real one — green, amber past S9, red at +20
The bar was one colour for its whole travel, so nothing distinguished a signal
that is merely readable from one that belongs in the log as 59+20.

Green to S9, amber through the S9+ range, red from +20 dB. The thresholds are
computed from the SAME S9 point the label uses rather than hard-coded
percentages: the S9 position is still a hypothesis on this rig, and when it is
corrected the colours have to move with it or the meter would say 59+20 in
amber.
2026-07-29 11:59:58 +02:00
rouggy 6d309cada1 fix: SPLIT placed the transmitter on a stale VFO; share the meter widget
Reported on the FTDX10: listening on 14.244 with VFO B still holding 18.115 from
an earlier session, pressing SPLIT threw the transmitter onto another band. The
button only flipped the rig's split flag, and the other VFO is stale by nature —
the only transmit frequency that makes sense is one derived from where the
operator is listening NOW. SPLIT therefore places the TX VFO too: up 1 kHz on CW
and the data modes, up 5 kHz on phone, the offsets operators actually call. The
+1k / +5k buttons remain for anything else, and a test pins the mapping.

The panel also drew its own flat meters while the Flex and Icom consoles use the
shared LED-segment MeterBar. Two instrument styles in one application is just
inconsistency — it now uses the shared component, and the local one is gone.

And the three consoles are named alike: "Flex Console", "Icom Console", "Yaesu
Console", in the tabs and in the Main-view pane list, in both languages.
2026-07-29 11:55:28 +02:00
rouggy 7153768579 feat: Yaesu panel — sideband on re-click, split readout, one-touch up 1/up 5
Three corrections from the operator's second pass.

The sideband gesture was wrong: I used double-click, which hides the action.
Clicking a button that is ALREADY active now flips its sideband — CW-U → CW-L →
CW-U. One button, one finger, nothing to discover.

A lit SPLIT chip does not tell an operator anything useful: it says split is on,
not where they transmit. The header now shows the TX frequency and the offset in
kHz whenever split is active.

And the offset that matters is set in one action: up 1 kHz on CW, up 5 kHz on
phone. Doing it by hand means swapping VFOs, retuning and swapping back — exactly
the fumbling a panel exists to remove. Both are offered rather than picked from
the mode, because which one is idiomatic is the operator's call, and the button
turns split on at the same time.

The offset is measured from the RECEIVE frequency and written to the VFO we are
not listening on, so it stays correct when the operator works on VFO B, where the
roles are mirrored.
2026-07-29 11:46:54 +02:00
rouggy df4155108f fix: centre the Yaesu panel like the Icom and Flex ones
Capping the width without mx-auto pinned the console to the left edge with an
empty window beside it. It now uses the exact wrapper the other two panels use —
h-full min-h-0 overflow-auto bg-background, then max-w-5xl mx-auto p-3 — rather
than a second layout of my own invention.
2026-07-29 11:42:42 +02:00