Commit Graph
15 Commits
Author SHA1 Message Date
rouggy a197d124dc feat(decodes): an FT decodes tab fed by the inbound UDP link
Every FTx decode WSJT-X, JTDX or MSHV puts on the wire, grouped by T/R
period. Optional and closable, from Tools -> FT decodes; its open state is
remembered, because an operator running digital modes leaves it open for
the session rather than consulting and closing it.

The period is the point, and what separates this from the cluster list.
FT8 is a sequence of fifteen-second slots and a band is read by watching
them go by: who called CQ this slot, who answered, what I was sending while
they did. A flat list sorted by time loses exactly that, so the list is
grouped one section per period, newest first, with the operator's own
transmission shown inside the slot it went out in.

Three fields had to be carried up from the wire to make it possible:

  - the decode's OWN timestamp, which the parser read and threw away. It is
    what assigns a slot: a period's decodes arrive in one burst a second or
    two after it closes, so arrival time piles a whole period into the next
    one. Rebuilt to UTC from milliseconds-since-midnight, with the
    day-boundary case handled - a decode stamped 23:59:58 arriving at
    00:00:01 would otherwise be dated a day ahead and sit at the top of the
    list for the rest of the session.
  - the decoded line itself. The exchange is what says where a station is in
    a QSO, and no set of extracted fields reads like "R-09" does.
  - tx_message and transmitting from Status, which nothing parsed before.
    Recorded once per message rather than on every Status, which repeats it
    about once a second for the whole over.

Also picked up on the way: is_new, low_confidence, off_air, the operator's
own call and grid, and the T/R period itself - better authority on slot
length than the mode name, which says nothing about a custom period. The
Status tail is read defensively: those fields were appended over successive
schema versions and JTDX and MSHV each stop at their own point, so a short
packet is normal and keeps whatever parsed.

Status flags come from ClusterSpotStatuses, the resolver the cluster list
and band map already use, filling the same cache. One verdict per call:
"new band" in this panel and plain worked in the cluster two seconds later
would be worse than no flag at all. Clicking a call goes through the same
handler as a cluster spot, so answering a station is one gesture whether it
came off telnet or off the receiver.

Filters: CQ only, new-anything only, band, mode, continent, an SNR floor
and a free search. The band, mode and continent choices are built from what
is actually on the feed - offering 160 m to a station whose receivers are
all on 6 m is noise.

Decodes are held in the frontend and pruned to a rolling half hour: they
are a live view, not data, nothing outside the panel reads them, and a
night of FT8 on 20 m would otherwise grow a list no filter can rescue.
Arrivals are staged on a 300 ms timer so a period landing as fifty packets
costs one status lookup and one render.
2026-08-18 05:51:34 +02:00
rouggy 9f8e3c73d9 fix(udp): OpsLog was re-tuning its own rig from its own broadcasts
Reported as JTDX "Fake It" causing CAT disconnections. Fake It is not the
cause — it is what made an existing loop fire, and a regression of mine from
this morning is what closed that loop.

The station has an inbound remote-call row and an outbound N1MM RadioInfo row
on the same port, 2241, so every RadioInfo datagram OpsLog sends arrives
straight back on the loopback. The remote-call parser strips XML tags and takes
the last token as the callsign: for a RadioInfo that is
<ActiveRadioNr>1</ActiveRadioNr>, i.e. "1" — and <Freq> became a tune request
for the frequency the rig was already on.

That was harmless only while <Freq> was misread. It is in tens of Hz, the parser
assumed MHz, every echoed tune failed "out of the 11-digit CAT range", and the
loop died on the error. Teaching it the unit (331db58) closed the loop.

Fake It shifts the dial for each over rather than using split, so every
transmission changed the state twice — and each change published a RadioInfo,
which came back as a set, which changed the state again. The log shows the dial
oscillating 24915000/24915500 three times in 200 ms, then "timeout answering
IF;" and the shared link down. Every over, all morning.

Two guards, because one was not enough to be sure: a RadioInfo payload is never
a remote-call request, and a callsign has a letter in it — so the next program
to broadcast its state on that port cannot drive the rig either. Reload also
names an inbound and an outbound row sharing a port, which is the arrangement
that permitted this and which no settings panel shows.
2026-08-16 12:25:41 +02:00
rouggy 331db58705 fix(antenna,udp): name the reason tracking is off; infer the remote-tune unit
The SteppIR report was not a SteppIR fault. In the log the antenna starts,
answers every poll, and sits at 21050 kHz while the rig works 21074 — and the
status line only prints when the frame CHANGES, so a link that is alive and
parked looks identical to one that died. The one line that explained it said
"ultrabeam: follow loop stopped", which covers two quite different situations
and, at startup where nothing was running, reads as a fault. Tracking was simply
switched off. It now says which of the two it is, names the antenna type, and
states the consequence — that the antenna will not follow the rig.

Same log, a second and unrelated fault: every launch failed a tune request from
DXHunter with "frequency 2107400000000 out of the 11-digit CAT range".
<FREQ> was read as MHz, but DXHunter had echoed back the value OpsLog itself
published in the N1MM RadioInfo broadcast, whose <Freq> is in tens of Hz. Both
units come from the same peer, so the unit is now inferred: try MHz, kHz, Hz,
tens of Hz, and keep the first that lands on an amateur band — anything a
station is asked to tune to is in one. Hz before tens of Hz because their only
overlap, 40 m against 4 m, is far more likely to be 40 m. Nothing plausible
tunes nothing and says so, rather than sending the rig to a wrong band.
2026-08-16 00:27:53 +02:00
rouggy 6c2c1f106a feat(udp): custom outbound messages — a trigger, a template, UDP or a URL
The hard-coded emitters each speak one published format at one fixed moment.
This is the escape hatch, and it exists mainly for antenna switches: they are
driven by a URL, and the band change is the trigger they want.

Four triggers, each carrying its own field set: band change (the RADIO's band,
not the entry form — a switch follows the rig, not what is being typed), QSO
logged, rotator command and callsign lookup. The panel prints the fields the
selected trigger can fill, which is the point of the whole thing: a placeholder
the trigger does not carry renders as nothing, and without the list an operator
writes {freq} on a band change and has no way to learn why the switch never
moved.

Three things the panel cannot show, handled here:

  - Escaping. A URL needs every value percent-encoded; a UDP payload must not
    be touched. The first portable callsign, F4BPO/P, puts a path separator in
    the middle of a query string otherwise — it works on the bench and fails on
    the air.
  - Blocking. An HTTP call to a switch that is unplugged would otherwise sit
    for the operating system's timeout, on the path of a band change. It runs
    in the background with a 3 s limit.
  - Silence. A switch answering 404 after a firmware update fails exactly like
    success looks from here, so the status code is logged, throttled per row.

The rotator trigger hooks the COMMAND rather than the SP/LP buttons, so the
compass and a spot click fire it too, and the path ("SP"/"LP") is passed
through because an azimuth alone cannot say which was taken — 137° is short
path to one station and long to another.

Credentials in a URL are stored as typed. That is the operator's call, on the
grounds that this is LAN gear, and the hint in the panel says so.
2026-08-15 01:39:16 +02:00
rouggy c5c9d355ec fix(logging): name the antenna disagreement, and confirm an outbound send
Two silences from one operator's log.

The SteppIR is not blocked — it is being overruled. Every tune is commanded,
acknowledged on the next poll with the frequency asked for, then replaced on
the poll after by a different one: 21075 asked, 21075 confirmed, 21050
reported, again and again, with the operator tuning once more each time. Two
minutes of that reads as a dead link. It now says what it is, once, when the
motors have stopped and the reported frequency is more than 10 kHz from the
commanded one — a controller quantising to its own grid is not a fault, one
sitting somewhere else entirely is.

UDP outbound had no positive signal at all: only failures were logged. When
an operator sets up an ADIF message to a second logger and nothing arrives,
"OpsLog never sent it" and "the other program did not take it" looked exactly
the same from here — and the first is the common case, because a row created
as an inbound ADIF listener instead of an outbound ADIF message matches
nothing and emits nothing. Successful sends are logged with their destination
and service, and a QSO logged with no outbound row says so once a session.
2026-08-14 17:37:12 +02:00
rouggy 2cd1274975 fix(logging): say why the antenna stopped and what the ADIF port is receiving
Two problems from one operator's log, both of them the log's fault.

SteppIR. The last line about it is "status query failed, reconnecting: Port
has been closed" and then nothing — for the rest of the session. The poll loop
does retry every two seconds, but a failed open was a bare `continue`: no line,
ever. And startUltrabeam had three silent ways out — settings unreadable, the
antenna turned off, no port or host configured — so a cleared field and a lost
adapter produced exactly the same evidence, which is none. Both now say what
happened, with the port or host named. Reopen failures are reported three
times and then throttled (a port that is gone stays gone), and the recovery
says how many attempts it took.

FLDIGI. Four hundred "ADIF payload ignored" lines and nothing else legible.
The cause is in the same log: two inbound listeners on the SAME multicast
group and port, 239.255.0.1:2237 — one WSJT (MSHV), one ADIF (FLDIGI). Every
WSJT-X packet is delivered to both, and an FT8 cycle is dozens of decodes
every fifteen seconds; the bursts in the log are exactly 15 s apart. The ADIF
listener now checks for the WSJT-X magic and says so once, with what to do
about it, instead of rejecting each packet in writing. Anything else is
described a few times with its payload and then goes quiet.
2026-08-14 16:24:06 +02:00
rouggy 9b2115be8f feat(cluster): grids from the UDP decodes, plus spotter continent and LoTW
Backend groundwork; the columns and filters that consume it come next.

GRIDS. A CQ is the one WSJT-X message that carries a locator, and wsjtSender was
throwing that token away. It is now returned, validated as a real field+square,
and remembered per callsign. This is the ONLY grid source available: a DX-cluster
line carries the spotter's grid at best and never the DX's, and a per-callsign
QRZ lookup under an RBN firehose is not a trade worth making. So grids are known
for the stations this receiver decoded - which is exactly the FT8/FT4 watering
hole an operator is looking at while grid chasing.

RR73 is why the grid is validated rather than pattern-matched. R is inside A-R
and 73 inside 00-99, so a sign-off satisfies the Maidenhead shape exactly and
would have planted a grid that does not exist into the index, silently.

NEW GRID keys on "GRID|MODE" with the mode put through the same normMode as
everything else, so the "group digital modes" option decides whether a grid
worked on FT8 is still new on FT4 - one rule, no branch. Grids are truncated to
four characters: a log holds a mix of JN36 and JN36QU, and without that the same
square is new forever, once per subsquare.

On cost, which was the condition: one more DISTINCT scan when the status
snapshot is rebuilt, then map lookups per spot. The same shape as the county and
POTA sets it sits beside, and the snapshot exists precisely so a spot batch never
touches the logbook.

Spotter continent and the LoTW flag come from tables already in memory - the
DXCC prefix table and ARRL's user list - so they cost a lookup each. The spotter
continent answers a different question from the DX's: whether anyone near you is
hearing this at all.
2026-08-10 16:52:36 +02:00
rouggy a0f7f2abf0 feat(udp): show the packet behind a parse error, and stop repeating it
"WSJT parse error: bad magic 0x3132372e" named neither the sender nor the
payload, so there was nothing to act on — even though those four bytes are
ASCII "127.", i.e. some program broadcasting an address on a port expecting
WSJT-X binary.

The line now carries the remote address, the size, a printable preview and a
hex dump of the first 96 bytes. Text senders are readable at a glance; a
genuinely binary payload still shows its bytes.

And it stops after five. The reported case wrote that line about 150 times a
second: a permanently misconfigured port would fill the 10 MB rotating log with
one repeated sentence and bury every other piece of evidence — the log's whole
purpose. The fifth line names the two things worth checking, the sender and the
service type.

N1MM's parse error goes through the same path; it had no packet detail either.
2026-08-09 07:59:51 +02:00
rouggy 9846354bf9 chore: release v0.23.2 2026-08-02 23:51:46 +02:00
rouggy f6cd6e999a chore: release v0.22.9 2026-08-02 06:40:10 +02:00
rouggy a036120871 feat: remote_call UDP accepts <FREQ>/<MODE> from DXHunter and tunes the active CAT backend 2026-07-29 17:49:29 +02:00
rouggy 28b6f04ea4 feat: When a call is cleared from MSHV/WSJTx/JTDX it also clears the call field of OpsLog 2026-07-16 11:52:42 +02:00
rouggy 6c39204301 chore: release v0.19.4 2026-07-10 17:16:32 +02:00
rouggy 5c004f5e2f feat: upload to external services clublog qrz 2026-05-28 22:52:50 +02:00
rouggy e82e30dd02 up 2026-05-28 21:32:46 +02:00