A slice has an rxant and a txant, and each pair belongs to that slice's
own band. Only two of the four were being set — the downlink's receive
antenna and the uplink's transmit one — so the downlink slice was left
with an empty txant. It never keys, so nothing was wrong on the air, but
the slice was half-configured: move transmit focus to it and the radio
uses whatever antenna it happened to be left on. Reported with XVTA on
2 m and XVTB on 70 cm, where the 70 cm slice showed one antenna and the
2 m slice showed two.
SatAntennas now takes all four, and the late-slice replay in
adoptSatSlice hands the uplink its own pair instead of putting the
transmit antenna on both of its ports.
Two things.
The sky plot and the position share the left column now, and the button
that opens it says so the way the right one does — it was a radar icon,
left over from when it toggled a plot inside the readout column, and the
gesture is the same one on both sides. The plot and the numbers are the
same answer at two precisions: azimuth and elevation drawn, then written
out to the digit. Having them at opposite ends of the window meant
reading a bearing off one side and finding it on the other.
And the band-drift warning, reported by W4TE. It compared the decoder's
announced band against RigState.Band, which is the TRANSMIT band — so
with slice A on 20 m running its own WSJT-X, slice B on 40 m, and
transmit focus on B, the 20 m decoder was told the rig was on 40 m while
the slice it listens to had been on 20 m throughout. The panel's own
comment had accepted this as a line that setup could read past; it is
worse than that, because the warning names a band and asserts something
false about the radio.
RigState now carries RxBands: every band the rig has a receiver on. One
entry on a single-VFO rig, one per slice on a Flex, the transmit band
always included so it cannot come back empty while the rig is on a
frequency. The warning fires only when the decoder announces a band
NOTHING on the radio is on, which is what it was always for and what a
lost CAT link actually looks like.
The map drew one direction of every path on it: what this receiver
decoded. The reverse — which stations are reporting our own
transmissions — is the half an operator cannot see from their own
radio, and on FT8 it is the half that decides whether calling is worth
the cycle.
It is the narrowest slice of the PSK Reporter feed there is. The v2
topic is
pskr/filter/v2/<band>/<mode>/<tx call>/<rx call>/…
so putting the operator's callsign in the TRANSMIT level makes the
broker send nothing else. For scale, from internal/pskr's own measured
numbers: four bands unfiltered is 83 messages a second, filtered on the
receiver's square 0.2 to 1.2 a second — one callsign in the transmit
level is a handful per FT8 cycle however open the band is. Both grids
are in the payload, so the arc is arithmetic and there is no lookup.
internal/pskrme, with its own connection, for the same reason
internal/pskrtgt has its own: the three want slices of the feed that
cannot be filtered out of one another. It also means this keeps working
with the band-opening watch off — hanging it off that feed's lifecycle
would have made it fail silently for anyone not chasing openings.
Nothing is persisted. One entry per STATION inside a fifteen-minute
window, carrying its freshest report: PSK Reporter's uploaders batch,
many every five minutes, so a tighter window would show a fraction of
who actually heard the last few calls. Stop clears the window, or
switching the layer back on would redraw who heard us before it was on.
On the map the layer is dashed and single-coloured. Solid is what we
decoded, dashed is somebody decoding us; colour alone could not carry
that distinction next to fourteen band colours. The receivers are rings
rather than filled dots for the same reason. Per profile, since the
callsign IS the subscription — a switch resubscribes rather than going
on reporting who hears the previous station.
Every FM bird's uplink, downlink and tone compared against N9KT's Live
FM Satellites table, and the image list beside it. Two corrections and
ten additions:
- AO-123 (ASRTU-1) had no CTCSS. Its 145.850 uplink wants 67.0 Hz, and
without the tone the transponder simply does not answer.
- SO-50 keeps 67.0 Hz, which is the tone that keys it, but its label now
says 74.4 Hz arms the ten-minute timer first. That is a one-off
two-second carrier, not a tone to key every transmission with, so it
belongs where it will be read rather than in a field the radio would
apply throughout.
- Eight SSTV satellites added as receive-only entries, no uplink field
at all, the way the ISS SSTV row was already written: HC1PX
(GALAPAGOS-UTE-SWSU), RS18S (SakhaCube-Cholbon), RS27S (UTMN-2),
RS38S (VIZARD-meteo), RS40S (UmKA-1), RS57S (Monitor-4), RS58S
(Monitor-3), RS83S (Lobachevsky). SSTV downlinks also added to
SONATE-2 (145.880, Martin M1) and QMR-KWT-2 (436.950, Robot 36), and
the ISS row now names its mode.
The NORAD numbers were read out of the element feed by name, not
guessed: five of the eight are in it and carry an exact catalogue
number, and the three that are not (HC1PX, RS18S, RS83S) are joined by
name and alias instead, so they read as having no elements until a feed
carries them rather than pointing an antenna somewhere arbitrary.
AO-91 was left alone. The table shows no tone in its uplink cell but
Fox-1B does require 67.0 Hz, so ours stands and the table is the
incomplete one.
The FTx button is gone. It was the wrong grain in both directions: the
Digital class already sat a contest RTTY square next to an FT8 one, and
FTx then sat FT8 next to FT4, when what this map answers is where ONE
mode has been heard. The four classes stay as buttons and a dropdown
beside them offers a single mode.
Its contents come from the log, not from a list in the code. That is
what settles FT2: it is not a registered ADIF mode yet, so a hardcoded
list meant either leaving out the operators already using it or shipping
a mode that does not officially exist. A query does neither, and needs
no change here the day it is registered. The mode offered is the SUBMODE
where there is one, because ADIF files PSK63 under PSK and "PSK" is not
the name anybody is looking for.
The band list is the station's own, unioned with anything worked outside
it so nothing in the log is unreachable, ordered by frequency through
the band plan's index — sorting the names puts 10m between 1.25m and
12m. The satellite list is drawn from SAT_NAME on the squares
themselves and the control is absent altogether on a terrestrial log: a
VHF square worked through AO-91 and one worked line-of-sight are not the
same achievement, and until now nothing separated them.
The mode dropdown shares the scope with the class buttons rather than
narrowing on top of them — mode is one question, and two controls both
answering it is how a map ends up showing PHONE ∩ FT8 and nothing else.
A stored FTX preference is read as Digital, so it cannot leave the map
filtered by something no control shows as selected.
"la lilacsat je le vois en DATA ???!" — and the LO-90 fix could not have reached
him. The satellite file is copied out on the first run and was the operator's
from then on, so the merge could only ADD birds, never repair one. LilacSat-2
went out with an APRS digipeater and no FM transponder, and that mistake had
become his data, permanently.
The merge now distinguishes three cases, and the middle one is the whole point:
- a satellite he does not have is added;
- one he has, UNCHANGED from the plan he was given, is replaced — he never
edited it, so it is not his to keep: it is our data and ours was wrong;
- one he EDITED is left exactly alone, and named in the log. A frequency
somebody corrected by hand outranks anything shipped; they were on the air
and we were not.
"Unchanged" is decided against a baseline — satellites.shipped.json, the plan
this station was last handed — so the comparison is with what THEY were given
rather than with whatever ships today. Their edits survive every future release,
not just the next one.
The first run after this has no baseline, and there an edit of theirs and a
mistake of ours are indistinguishable. The shipped plan wins, once, with the
whole file copied to satellites.json.bak first and every replacement named. The
safe-looking alternative was the wrong one: standing down would have written a
baseline recording their entry as "edited" and frozen a known-wrong frequency
for the life of the install.
Verified against his own file: LILACSAT-2 becomes LO-90 with the FM transponder
first, and the twelve curated entries that were missing their catalog numbers
get them — which also closes the NORAD gap left open when the exact join went in.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Shipped with nothing but an APRS digipeater on 144.390, which is not what
anybody works that satellite on. The FM transponder — 144.350 up, 437.200 down —
was dropped by the generator, correctly by its own rule: SatNOGS marks that
transmitter inactive.
And SatNOGS is not wrong. LilacSat-2's transponder is switched on to an
announced schedule rather than left running, and from a database a scheduled
transponder looks exactly like a dead one. The judgement belongs to whoever
reads the diff, not to the filter.
So the transponder goes back in, labelled "(scheduled)" the way PO-101 already
is, and the satellite takes its OSCAR name, LO-90, with LILACSAT-2 and CAS-3H as
aliases so the element feeds still meet it.
The generator now reports what it refused on that ground and kept nothing else
for — one line per satellite, phrased as a question. Reviving every inactive
transponder would fill the list with birds that answer nothing; saying nothing
is how this one shipped wrong. There is no third option that a filter can decide
on its own.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Calibrate was in the protocol notes at the top of the package ('V') and nowhere
else. It drives every element to its end stop so the controller re-learns where
zero is, and it is the cure for the one SteppIR fault an operator cannot reason
about: an antenna that tunes to the wrong length. The controller counts steps
from a remembered position, so once that memory is wrong — a power cut in
mid-move, elements pushed by hand, a motor that slipped — every frequency after
it is wrong by the same amount, silently.
It asks before it runs. Minutes, every element travelling its full length, and
the antenna unusable until it finishes: right answer to a mistuned beam, wrong
answer to a stray click during a contest.
It sits beside Retract as a two-column row rather than a second full-width
button. The docked widget is narrow and already tall, and the pair reads as what
it is — the two commands that move every element at once.
Retract (Home) was already there and worked on a SteppIR; it now carries a
tooltip saying what it is for, because "retract elements" does not tell an
operator that this is the storage position and that the next tune brings them
back out unaided.
An Ultrabeam controller has no calibration command, and the adapter says so
instead of pretending — the antenna interface is shared, and a button that
silently does nothing is worse than one that explains itself.
Untested on hardware: the byte is from SteppIR's own Transceiver Interface note,
which is the same source the rest of this package was cross-checked against.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Reported with two screenshots: both slices in USB on an inverting transponder,
slice B sitting at exactly 435.100000, and the red TX badge on the 2 m DOWNLINK.
The log named the cause in one line:
flex: satellite armed (rx slice 0, tx slice -1)
Creating a slice is asynchronous — "slice create" is answered later with the
index — and SetSatellite returned without waiting. Everything downstream then
ran against an uplink of -1 and silently did nothing: no antenna, no CTCSS tone,
no sideband, never tuned, and never sent "tx=1". So the radio went on
transmitting on the downlink, which is the one failure here that puts a signal
where it must not go, and the frequency and mode on screen were simply the ones
the slice had been created with.
Three fixes, because the ordering can fail in more than one way:
- Arming waits for both indices before reporting the pair armed, and says so
plainly when the radio does not produce them.
- The uplink is adopted from the SLICE STATUS as well as from the create
reply. The status needs no sequence-number correlation: if satellite mode is
armed, the uplink is unknown, and a slice is in use that is not the
downlink, that is it — the radio is saying so.
- What the uplink is owed is remembered — its mode, its antenna, its tone —
and given to it when it appears. Those three are sent ONCE; only the
frequency is re-sent every tick, so a slice that arrived late kept nothing.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
An operator with a 450° mast watched the Genius stop at 359 and had to press
"clockwise" by hand to get through north. Half of that was ours: GoTo clamped
every target to 360 before sending it, although the wire command carries three
digits and always could have said 370.
So the clamp goes to 450, the rotator-range setting is offered for the Rotator
Genius like the other backends it applies to, and when a bearing can be reached
two ways the nearer one is taken — 010° sent as 370° when the antenna is already
at 350°, which is the entire point of having an overlap.
THE GENIUS DECIDES WHAT IS REACHABLE. It reports the limits it is configured
with, and they are the truth about what is bolted to the tower. This particular
station's box says "5 to 4" — the factory 360° range — and would refuse 370,
turning a working command into a rejected one. So the overlap is used only when
the Genius itself says it has one, and when OpsLog is set to 450 while the Genius
is not, the log says so once a minute and names the dialog to change: the setting
lives in the Genius's own Rotator Configuration, and nothing here can reach past
its limits.
The |h reply always carried those limits and they were being skipped over.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
An operator with a WinKey2 USB had to run K1EL's WKdemo and close it again
before OpsLog could talk to the keyer at all. That workaround is the diagnosis:
closing another program does something to the keyer that opening the port does
not, and whatever state it was stuck in survives a failed connect.
So the second handshake attempt now does what closing WKdemo does, in an order
that survives each step failing:
- Host Close, in case the keyer is still in host mode from a session that
ended without one — a crash, a cable pulled, a machine switched off. It has
been waiting ever since for a host that went away.
- Admin Reset, which returns it to its power-up state. A parser stuck
part-way through a command whose parameters will never arrive cannot be
talked out of it any other way.
- A DTR pulse, which is what closing a program actually does to the line. On
a WKUSB and on every Arduino-based clone, DTR runs to the processor's reset:
it is a power-on reset in all but name.
RTS is left alone throughout — on a serial WinKeyer it is the negative rail the
RS-232 swing comes from, and driving it starves the chip.
A keyer that answers the echo and then refuses to open is the same leftover
session seen from the other side, so that case sends Host Close and asks again
rather than reporting a keyer that demonstrably just spoke to us as absent.
And a port already known to need the slow path gets the wake-up on the FIRST
attempt from then on: making the operator sit through a failure to earn it again
doubles the connect time for no new information.
The handshake bytes are already logged on every connect, so the next report of
this shape says where it stopped.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Three things the tracker was leaving to chance on a FlexRadio, all reported from
a real pass.
ANTENNAS. Settings ▸ FlexRadio holds a per-band RX/TX antenna map, and it was
applied in exactly one place: the entry form, on a band change, to the active
slice. A pass never goes through that path — the tracker arms two slices itself.
So both were left on whatever the radio last used, and a station with
transverters (XVTA on 2 m, XVTB on 70 cm) heard nothing at all, having
configured precisely the thing being ignored. The two slices are on two
different bands, so they cannot share one setting: the downlink takes the
receive antenna for ITS band, the uplink the transmit antenna for its. Per
slice, not through sendSlice, which addresses whichever slice is active — during
a pass that is the downlink, so the uplink would never have been set.
SIDEBAND. satMode forced USB above 30 MHz on both sides. An inverting
transponder turns the passband over, so lower sideband up comes back as upper
sideband down: FO-29, RS-44 and AO-73 were being worked with the operator's own
audio going through upside down. The tracker now decides both sidebands from the
transponder's inverting flag and passes them separately; a bare "SSB" still
means USB, so nothing else changes.
CTCSS. Nothing set it, on any bird. The frequency plan has carried the tone all
along — 67.0 on SO-50 and AO-91, 141.3 on PO-101 — and an FM repeater does not
answer without it, which is indistinguishable from a satellite that is not
there. It goes on the uplink slice, value before mode so the radio cannot
transmit the previous tone in the gap between two commands.
Written against the SmartSDR slice API and UNTESTED on hardware.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Reported as "the Doppler moves the frequency enormously", and it did: a 2 m
downlink was being shifted two megahertz across a pass instead of three
kilohertz, in the wrong direction.
The propagator library reports a range rate that is not one. Measured against
the range it is meant to be the derivative of, on this station's own cached
elements:
PO-101 library -1457.3 km/s measured +6.227 km/s
ISS library +2036.8 km/s measured -5.522 km/s
Wrong by a factor of some 250 and of the wrong sign, so the correction both
overshot and pushed the operator away from the station they could hear. Nothing
else was affected — the elevation and the passes come from the look angle, which
is right — which is why this survived: the satellite was in the correct place on
the map while the radio was told to go megahertz away from it.
So OpsLog computes it itself, as the difference between two ranges a second
apart. That cannot be wrong in either magnitude or sign: it differentiates the
very number the panel displays. Two extra propagations per call, which is
microseconds.
Two tests pin it, and both fail against the old behaviour: a range rate faster
than orbital velocity is a units mistake, and the Doppler on the two bands
satellites are worked on has a textbook size — about ±3.5 kHz on 2 m, ±10 kHz on
70 cm.
cmd/satdiag is the throwaway that found it, kept because the next report of this
shape ("the frequency moves oddly", "that pass is not real") is answered by the
same three numbers: which elements the satellite resolved to, the range rate
reported against the range rate measured, and the Doppler each transponder gets.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
25 satellites, typed by hand and never revisited. Eight of them were the
first-generation Tevel constellation, which re-entered in 2024; four more had
come down too; and the nine Tevel-2 satellites that replaced them, the Chinese
space station, AO-27, AO-123 and twenty others were simply absent. So: 44
satellites now, and a generator instead of a memory.
cmd/satgen joins three public sources on the NORAD catalog number — Celestrak's
amateur group and PE0SAT's mirror for which birds OpsLog can actually get
elements for, and SatNOGS DB for the transmitters. It is a one-shot tool, run by
hand, the same arrangement as cmd/cntygen, and it is deliberately conservative:
- It never destroys a curated entry. The hand-written plans hold things
SatNOGS does not reliably carry — a CTCSS tone, the QO-100 passband as
operators describe it — so an existing bird keeps its data and only gains
its catalog number.
- It prunes on the re-entry date, which is a fact SatNOGS publishes rather
than a judgement about which of the missing satellites are missing for good.
- It refuses a digital uplink that does not say what it is. A GMSK uplink is a
command channel far more often than a digipeater, and shipping the wrong one
invites somebody to transmit on a control frequency. An analog uplink with
both ends is a contact by construction, which is what catches the repeaters
that describe themselves only as "Mode V/U FM".
- Its output is deterministic. One satellite can hold two catalog entries —
GreenCube is 53106 in one feed and 53109 in the other — and iterating a map
picked a different one each run.
A bird now carries its NORAD number, and that is how its elements are found.
Names were the only join before, and they are written differently by every party
involved: "TIANYAN 01" and "TO-108" are one satellite that had never once met,
so TO-108 tracked nothing at all.
And the plan now reaches a station that has already run OpsLog. The editable
copy was written on the first launch and was the operator's list for ever after,
so a release adding nine satellites reached nobody who had opened the tab. It is
merged on each load instead: a satellite they already have is untouched, edits
and corrections included, and only the ones they have never seen are added.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The satellite page configured its own EasyComm or PstRotator link while five
other backends were configured in the rotator list. An operator with one az/el
mast therefore described it twice, and could describe it differently the second
time — a station that works on HF and not on a pass, for no reason visible
anywhere on screen.
Now every interface lives in Settings ▸ Rotator, once, and the satellite page
stores only a KEY into that list plus the tracking policy that is genuinely its
own (minimum elevation, step, park). The key and not the index: deleting the
first rotor must not silently point the tracker at a different mast.
migrateSatRotator() turns an existing satellite link into a real entry in the
list, selects it, and clears the old keys so it cannot run twice.
Which rotors have an elevation axis is now a question with one answer, in Go:
rotatorTypes plus rotorHasElevation, exposed to the panel by GetRotatorTypes.
The dropdown, the labels, each backend's default port and default baud all come
from there, so TypeScript no longer keeps a second copy of the same knowledge to
drift out of step. Three cases do not follow from the type alone and are treated
as such: PstRotator forwards elevation to a mast that may not have any, so the
operator says; a SPID's dialect decides (Rot1Prog has no elevation in its reply
format); and an ARCO and an ERC-M speak the same GS-232 while only one of them
lifts.
Each interface carries an Az / Az+El badge beside it. The satellite rotor
dropdown LISTS the azimuth-only ones, disabled, rather than hiding them: an
operator who owns one rotator and does not see it concludes OpsLog cannot find
it, where a greyed row saying "azimuth only" teaches the actual thing.
ERC-M by DF9GR is new — the az/el interface for a Yaesu G-5500. It emulates
GS-232, so internal/rotator/gs232 grew the elevation half: W for a two-axis
move, C2 to read both, falling back to C+B for the firmware that answers C2 with
the azimuth alone. That fallback is the point of the parser tests: reading such
a reply as "elevation zero" would put the antenna on the horizon, which is the
one wrong answer that looks plausible.
EasyComm II is promoted to an ordinary rotator interface, so it can also turn
the antenna from the compass and from a spot click.
The ERC-M is UNTESTED on hardware. Its Test button reads BOTH axes rather than
just the azimuth, so a controller wired for azimuth alone says so there instead
of during a pass.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Measured rather than guessed: the whole repository was cross-compiled for
linux/amd64 and the gaps closed one by one. There were fewer than expected.
Flex and TCI were never Windows-specific — they carried //go:build windows by
inheritance and import nothing but net and gorilla/websocket. Untagged, no code
change. The two backends a Linux operator is most likely to own were already
portable.
Audio was 560 lines, not 2287: only devices.go and engine.go touch WASAPI, while
manager.go, recorder.go, wav.go and mp3.go were pure Go wearing the tag by
association. The whole platform surface is seven functions, now implemented a
second time on PulseAudio through github.com/jfreymuth/pulse — pure Go over the
server socket, so the no-cgo rule survives, and PipeWire answers the same
protocol. The fixed 16 kHz mono format and the server-side resampling mirror
what AUTOCONVERTPCM does on Windows, for the same reason.
OmniRig is the only real loss, and its backend still EXISTS off Windows rather
than being compiled out of app.go: a settings database is portable, so an
operator moving a profile across keeps "omnirig" saved and must be told to pick
a native backend instead of meeting a nil one.
The parts where Linux is not Windows, and where a compile-only stub would have
been a silent bug:
- data dir: still beside the binary, but ~/.local/share/OpsLog/data when that
folder belongs to the system — decided by trying the write, because /opt and
/usr/local are writable on some stations and not others.
- single instance: an flock, not a pid file. The kernel drops it however the
process dies, so a crash leaves nothing to delete by hand. This is the guard
that stops two instances fighting over the rig frequency.
- update: simpler here. Unix renames over a running binary, so the deferred
swap the Windows path needs a detached helper for is unreachable.
- tasklist/taskkill become /proc and SIGTERM; the boot log moves out of /tmp,
which is wiped exactly when the evidence is wanted.
- serial ports sorted naturally: /dev/ttyUSB10 was landing between USB1 and
USB2, the same trap COM10 fell into.
release.ps1 now cross-builds and vets for linux before it builds the exe, and
refuses the release if that fails — a port rots one unguarded x/sys/windows call
at a time.
Nothing has been executed on Linux yet: Wails needs webkit2gtk and cgo there, so
the binary must be built on Linux. scripts/linux-setup.sh checks the machine and
does it; BUILDING-LINUX.md is the manual version.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Reported on an IC-7610 shared through OmniRig: clicking a CW spot put
the radio in CW-R every time, and the only way out was to edit the
OmniRig rig file by hand.
OmniRig's model has two CW modes, PM_CW_U and PM_CW_L, and nothing in it
says which one an .ini calls plain CW. Icom rig files disagree with each
other — on some PM_CW_U is CI-V mode 0x03 (CW), on others 0x07 (CW-R) —
so asking for "CW" is a question with two right answers and OpsLog was
only ever giving one of them.
Settings ▸ CAT ▸ OmniRig now carries the answer for THIS rig, and it is
pushed to a radio that is already connected rather than waiting for a
reconnect: which bit a mode maps to is not worth dropping the link, and
with it WSJT-X's rigctl session. Same shape as the Yaesu RTTY sideband,
for the same reason.
Nothing changes for anyone whose rig file already agrees with us.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Reported from a station whose two WSJT-X rows had been dead for weeks:
both were ticked multicast with 127.0.0.1 in the group box, and both
failed the join on every interface with
setsockopt: l'adresse demandée n'est pas valide dans son contexte
which names nothing the operator typed and does not say what is wrong
with it. A multicast group runs 224.0.0.0 to 239.255.255.255; 127.0.0.1
is loopback unicast, and it is an understandable thing to type — it is
the address every other field in every other program wants.
The address is now checked before the join. When it is not a multicast
one the row listens on unicast instead, which is what such an address
means, and the log says why it is not multicast. The row works, and the
reason is in a sentence rather than in a kernel error code.
Three things reported together from the tab.
The shipped frequency plan went from eleven satellites to twenty-five:
the eight Tevel FM cubesats, EO-88, AO-109, CAS-4A and 4B, TO-108,
GreenCube's single-frequency digipeater, and QO-100's wideband
transponder beside its narrowband one. It remains a starting point in a
file the operator can correct — a transponder gets switched and no
release should be needed to follow it — and the picker still lists every
bird in the element set when the "with a plan" filter is unticked.
The readout column drags to any width between 240 and 720 pixels,
double-clicks back to its default, and folds away entirely. How much map
against how much detail is the operator's call: watching a footprint
cross an ocean and working a pass want opposite things.
And the locator is no longer asked for twice. Passes are predicted from
the station locator, which is set once in Station information; the field
here was only ever for an antenna at another site, so it says so and sits
folded away. Nobody should have to wonder which of two locators is in
use.
Also: "Driven by" is two columns wide. "OpsLog (EasyComm II)" did not fit
in a third of the row, and a truncated choice is a choice that cannot be
read.
Reported from a station running MSHV, WSJT-X and JTDX together: click a
call in MSHV and the entry field filled, emptied, refilled — once a
second — with the map zooming in and out to match.
Two causes, both about reading one program's statement as another's.
The clear was tracked per LISTENER. Several decoders commonly share one
multicast group, so an idle WSJT-X reporting no DX Call — which is simply
true, and which it repeats every second — was read as MSHV abandoning the
station it was calling. "The operator cleared the DX Call" is a statement
about one program, never about a socket, so it is now tracked per
program, and a clear carries the id of whoever made it.
And nothing arbitrated between them. The program that announces a station
now holds the entry field, and the others cannot touch it until it lets
go: it clears its own call, it stops sending (closed), or the QSO is
logged. That is the operator's own suggestion, and it is the right one —
between overs there is no way to tell "I have nothing" from "I am not the
one you are working" except by remembering who was.
Refusing another program's callsign is logged once per focus, not once a
second: an operator whose second decoder "stopped filling the call" needs
something to read.
It handles azimuth and elevation, and a great many stations already run
it in front of a controller OpsLog has never heard of. For those,
OpsLog talking to the controller itself would be a second program
fighting PstRotator over the same cable — so it hands over the bearing
instead, and lets PstRotator turn the mast.
Both kinds sit behind one small interface, chosen in Settings. Neither is
more correct than the other: the right one is whichever the station
already has working.
The 450° overlap is deliberately NOT applied on the PstRotator path.
PstRotator knows which machine is on the other end and does its own; two
programs each deciding to go the long way round is exactly how an antenna
unwinds in the middle of a pass.
Position queries are asked at most every three seconds rather than on
every tick. A PstRotator query binds a socket and waits up to a second
and a half, and many setups answer nothing at all — so one silence is
enough and it stops asking, reporting the commanded position instead and
saying that is what it is.
EasyComm is what satellite rotator controllers agreed on, so a box that
works with SatPC32, Gpredict or Hamlib works here. Serial or TCP, and its
own settings rather than the HF rotator's: an az/el pair is a different
machine on a different port, and an operator who has both must not have
to choose.
A great many EasyComm controllers — the Arduino trackers above all —
accept commands and never say a word back. That is legal and common, so a
silent controller is not treated as a broken one: it is still driven, and
the last commanded position is reported in its place, marked as commanded
rather than read. A stuck rotator must not be able to hide behind an
order it never carried out, which is why the panel shows the antenna's
position beside the satellite's.
The 450° overlap is the reason a satellite rotator is worth having, so it
is used: a pass crossing north continues past 360 instead of unwinding
three quarters of a turn with the antenna sweeping the ground. Below the
configured elevation the mast is left alone — the numbers are right all
the way round the orbit, but a rotator that chases a satellite through
the far side of the earth spends the night turning, and a mast has a
finite number of turns in it.
The hard part of satellite tuning is not the arithmetic, it is deciding
who owns the dial. A tracker that forces both frequencies fights the
operator every time they turn the knob to follow a station across a
linear transponder; one that never touches the receiver leaves them
chasing a signal that slides nine kilohertz across a 70 cm pass.
So the operator owns the receiver and the tracker follows them. Every
second it asks the radio where the receiver actually is. Where it put it,
nothing has changed. Further than a dial-turn's tolerance, and the
operator has chosen a station: what they landed on is converted back into
a nominal frequency, and the transmitter is derived from that. Which is
the division of labour on a linear bird — the operator listens, the radio
does the sums.
Three ways to reach the radio, because a satellite pair is a shape of
operating rather than a manufacturer's feature. An IC-9700 or IC-9100 is
asked for its OWN satellite mode: it pairs main and sub, gives full
duplex, and keeps the dials linked the way its designers meant, which is
always better than an imitation built out of split. A Flex gets two
slices, A the downlink and B the uplink, created when missing, because
"slice B does not exist" is not something to make an operator fix at the
start of a ten-minute pass. Everything else gets the downlink, and is
told so — half the job announced beats half the job hidden.
What goes in the log is the NOMINAL pair. Two stations working each other
through a transponder read different numbers off their dials at the same
instant; the only figure they can both agree on is the transponder's own.
FREQ is the uplink and FREQ_RX the downlink — the one place a satellite
QSO differs from every other kind, and the reason FREQ alone cannot
describe one.
What internal/sat could not know: where the antenna is, which birds the
operator cares about, and where the files live.
Startup reads the cached elements and the frequency plan from disk and
nothing else — one file and a few hundred parses, so the tab is full the
moment it is opened, on a shack PC with no internet as much as on one
with. Fetching is the slow, optional half and never blocks a launch; it
happens on its own only when the set is stale and the operator asked for
it.
Elements pasted in by hand go in their own file. The feed cache is
replaced wholesale on every refresh, so a freshly launched satellite —
whose elements circulate on a mailing list days before any feed carries
it, which is exactly the week everybody wants to hear it — would
otherwise be wiped by the first automatic update.
The list joins both halves and shows what is missing on either side. A
bird with elements and no plan is one the operator can still track; a
bird with a plan and no elements is the visible symptom of an element set
that is too old. Dropping either turns a fixable configuration problem
into a satellite that "does not exist".
GetSatelliteTuning is the working answer, and everything that will later
drive a radio is built on top of it rather than beside it, so the display
and the rig can never disagree. It keeps the operator's frequency
nominal and applies Doppler only on the way out: on a linear pass the
station being answered stays put on the dial while both radios chase the
shift. A geostationary bird is corrected by nothing at all.
Two things the tracker cannot work without, both kept apart from the
orbital maths on purpose.
The elements come from Celestrak's amateur group, with PE0SAT as the
fallback for the hour when Celestrak is rate-limiting a hundred trackers
at once. A malformed satellite is skipped rather than fatal — a feed of
two hundred birds with one bad checksum must still give the operator the
other hundred and ninety-nine — and the count is returned so the app can
say so. The cache is plain TLE text in the data directory, written
beside and renamed, and only replaced once a feed has produced usable
elements: a captive portal must not take away the set the station
already had. Loading it first is what makes the satellite tab full on a
shack PC with no internet.
The frequency plan is separate because it changes for different reasons:
elements every few days from a feed, a transponder when the satellite is
commanded into another mode. The shipped list is a starting point, copied
to the data directory on first use and read from there afterwards, so an
operator can correct a frequency without waiting for a release and keep
the correction across updates — and a file they have broken is reported,
not overwritten.
UplinkFor is the part that matters on the air. On an inverting linear
transponder, tuning up the downlink means going down the uplink; get it
backwards and you transmit at the far end of the passband from the
station you can hear, which is the classic first evening on a linear
bird.
Names are matched on letters and digits alone. Celestrak says
"RADFXSAT (FOX-1B)" where every operator says AO-91, and nobody spells
Es'hail the same way twice.
The foundation of the satellite branch, and nothing above it yet: where a
satellite is (SGP4 from akhenakh/sgp4, Apache-2.0 and pure Go, so the
no-cgo rule holds), where it will be (passes with an elevation floor,
because a three-degree scrape is a line in a table that will never be a
QSO), and what its motion does to a frequency.
The Doppler pair is the part worth being careful about: the two
corrections go in OPPOSITE directions. The downlink arrives shifted and
we tune to meet it; the uplink must LEAVE shifted the other way to land
on the transponder's nominal input. A test pins the signs and the size —
7 km/s on 2 m is about 3.4 kHz.
Elements keep their raw lines beside the parsed form: that is what the
cache stores and what an operator pastes by hand for a bird no feed
carries yet, which is exactly when everyone wants to hear it.
Ticking "RTTY on USB" changed nothing: the flag is deliberately absent
from catLinkSig — none of these preferences is worth dropping the CAT
link, and with it WSJT-X's rigctl session, to apply — so saving the
settings left the link alone and the running client kept its old answer
until the next launch.
Preferences that the link does not depend on are now pushed to the
connected rig when the settings are saved, through the Yaesu escape on
the manager. SetRTTYUpper joins the controller interface for that: it is
a preference rather than a command, but it has to be reachable on a rig
that is already talking.
His log settles the 28.074 case: "SetCATFrequency 28.074 MHz" and the
state still reads mode=USB — nothing sent a mode, so the rig simply
stayed where it was. A spot click has always carried one; a frequency
typed by hand carried none. It now uses the same table and the same
tolerance as a spot (±3 kHz of a known FT8/FT4/JS8 frequency), and only
towards the digital modes: tuning AWAY from one leaves the mode alone,
because there the frequency says nothing about what the operator means.
RTTY on Yaesu is a choice the log cannot make: ADIF records "RTTY" and
the rig has MD06 (RTTY-L) and MD09 (RTTY-U). The older lower sideband
stays the default and a station whose FSK controller wants the upper one
says so once in Settings → CAT.
An FTDX3000 rejects every FA command: the FTDX10 family writes a
frequency in nine digits and everything before it — FTDX3000, FTDX5000,
FTDX1200, FT-2000, FT-950, FT-450 — writes eight, answering the longer
form with "?;". The rig would not follow and nothing said why.
The width is LEARNED from the rig's own replies rather than tabulated: it
announces the format in every answer to FA;, so it comes from the radio
in front of the operator instead of from a model list that is always one
release behind — and a Yaesu this backend has never heard of is right on
the first read. Nine until the first reply lands, which is what the
modern rigs use and what this backend was written against.
The five older ID codes are named too, so the console says FTDX3000
rather than a bare number.
The shortened transmit gag was written for an Ultrabeam that is now polled
four times a second while it moves. The SteppIR was still on two seconds
and reported nothing at all until its own poll came round, so the gap
between the 900 ms grace and the first poll that would have seen the
movement was a hole in "block TX while the elements travel" — the
transmitter released in the middle of a move.
It now reports a commanded move at once (bounded, so an antenna that
never answers cannot latch the inhibit on), on a COPY of the cached
status so the flag cannot leak into what the poll goroutine owns, and
follows the motors with its poll rate exactly as the Ultrabeam does.
The motorised antenna gagged the transmitter for a second or two after it
had finished moving. Three delays were stacked: the antenna polled every
two seconds, the gag held for three after the command whatever the
antenna said, and the widget refreshed every three. The antenna is now
asked four times a second WHILE IT MOVES — an idle one has nothing to say
and stays at two seconds — the gag only bridges the command itself
(900 ms), and the widget follows at half a second while moving.
WSJT-X highlighting:
- A watch-list station already worked on this band and mode is no longer
painted as one to call. The list is a statement of intent, not of what
is left to do, and its pink outranked every other verdict including the
log's, so a worked station stayed pink for the session with nothing to
tell it from one still needed.
- The four colours are the operator's to choose (Settings → UDP). Only
the background: the text colour is derived by luma, so a dark blue
cannot come back as black-on-black in somebody else's window. Changing
one clears the installed highlights, or the de-duplication would keep
showing yesterday's colour until a callsign changed verdict.
The conninfo that authorises the RX stream goes out during the login,
before the audio socket exists — it has to, since it is what authorises
it. So the rig is told to send audio to :50003 while nothing is bound
there, gets an ICMP port-unreachable back, backs off, and the audio turns
up only when its own retry timer comes round: twenty to thirty seconds by
the operator's watch, ninety in one log.
It is sent once more the moment the port is listening, which is the same
message the session already carries — RS-BA1 repeats it too. The audio
dial moved above the pumps, because after ctrlPump is running the
control-stream auth state belongs to it.
"I turned the sound back on and nothing comes out of the speakers", with
a log that reports success at every step: the network audio stream up,
664-byte packets arriving, the monitor started. The render goroutine's
error was thrown away — a device unplugged, renamed by Windows or unable
to open at 16 kHz fails exactly there, silently.
It is logged now, with the device id. Two once-only lines either side of
it say whether the decoded audio is reaching a running monitor or
arriving with nobody listening, which separates a device fault from the
speakers simply being switched off.
The guard I added for services with no credentials demanded one, and
nobody has ever set it: OpsLog carries its own Club Log APPLICATION key
(clublogAppAPIKey), so the account is an email, a password and the
logbook callsign. An operator whose live upload had been working for
months was told the service was not configured the moment he sent QSOs by
hand after an import.
Written in app.go, the rules drifted from the uploaders on the first try.
They now live in internal/extsvc beside the Upload* functions that
enforce them, each case mirroring that function's own guard — which also
caught Cloudlog, where the station profile is required and the check did
not ask for it. The message names the fields actually missing rather than
listing everything the service takes.
Audio off, and the IC-7760 still answers at connect and then never again:
twenty-eight commands sent, not one reply, packets still arriving on the
stream. A count says how much went unanswered and never which command
went out last — which is the one thing that can identify a frame this rig
does not tolerate, on a radio nobody here has.
The transport now keeps the command headers of the last eight frames and
prints them with the silence report.
The RX audio stream is experimental and shares the rig's session with
CI-V. The shape in an operator's log is unmistakable: audio packets
arriving by the hundred while not one CI-V reply comes back, the watchdog
tearing the session down, twenty seconds' pause, and the whole thing
again — read from outside as "the Icom keeps disconnecting", with
nothing pointing at the switch that would end it.
The log now says it, and names the setting. icomAudio counts what it has
delivered so the line distinguishes "audio is enabled" from "audio is
arriving", which is the half that makes it a suspect.
From an operator's log: an IC-7760 in standby, and OpsLog dialling and
dropping every forty seconds for as long as it was left there — control
link up, login OK, token renewed, and not one CI-V answer.
lastGoodAt bounds "the link answers but no CI-V comes back". It belongs
to a session and was never cleared when a new one opened, so a rig that
went to standby half an hour ago handed every fresh session a
half-hour-old last good read: past the grace before the first command was
even sent. Torn down at once, redialled twenty seconds later, torn down
again. Cleared on connect, the rule reads as it was written — silent
since connect is a rig in standby, and the session is kept so it can be
woken.
The console it is woken from was missing too. It appeared only once the
live CAT state said "icom", which a sleeping rig never says, so the ON
button was absent at the one moment it exists for. It now follows the
CONFIGURED radio — which also had to start following a radio switched
from the status bar, instead of waiting for a trip through Settings and a
Save that changed nothing.
An exported file was full of blank gaps: a record ran down a dozen lines
and the next appeared to start in the middle of the page. ADDRESS is a
multi-line field by the standard and callbooks and other loggers fill it
that way — "Kabul", four blank lines, "Afghanistan" — and the writer
copied the value out as it stood. The files were always valid, since ADIF
counts bytes; they were unreadable, and so was anything that quoted them.
Line breaks inside a value are now joined with a comma and tabs become
spaces, which is how an address reads on one line anyway. The length
prefix is computed after the flattening, so the record stays exact, and
every path through the writer gets it: the file exports, the uploads and
the record forwarded over UDP.
The changelog's 0.27.15 block also takes back the FT-map hover fix, which
landed after the 0.27.14 release commit and was sitting in that block.
The rest between two series was two minutes — four overs on FT8, by
which time the DX has worked four other callers and half the time has
gone. It is now counted in the station's OWN overs and is one by default:
seven calls, one over listened through, and it goes again if the station
is still there. The deadline lands mid-cycle on purpose, so "sit out one
over" means one over rather than depending on a millisecond of clock
skew.
A period the target was DECODED in is never counted as a miss. A period
is judged more than once — decodes arrive in a burst and stragglers
follow — and a later judgement holds a partial view of it, not evidence
of absence: D2ACE answered in the very period the counter then read as a
miss.
The readout moved out of the Auto button and beside it. "Auto D2ACE 1/7
·1/3" read as one number, and the control changed width every period.
The callsign is now the biggest thing on the row, the calls and the
missed periods each carry a label, the miss count appears only once there
is one, and a station being waited for gets its own amber chip.
The mouse wheel steps the RST fields — one S-unit on an RST or RS, one
decibel on a digital report, up for a better one — in the entry strip and
in the QSO editor. The dropdown beside them lists the reports worth
having to hand, not all 41 decibels, so the wheel works on the value
rather than walking the list. The listener is native and non-passive:
React attaches onWheel passively, where preventDefault does nothing and
the panel scrolls away under the field being adjusted.
PSK Reporter, whole-band scope: clicking a decode reset the report count
to zero. The window there is the BAND's — every FTx report on it,
filtered by target only when the analysis is drawn — and it was emptied
on every target change, throwing away an hour of evidence at the moment
the operator asked the question it answers. The narrow scope still clears
it: there the window is one station's, and keeping it would answer the
new question with the old station's evidence.
ZD8GB — watch-listed, six streams a period, two of them RR73 — was
refused as "parked" for the rest of the session. Parking answers "it
will not answer, stop wasting the evening on it", which is a fair verdict
about a station the LOG picked out and the wrong one about a station the
OPERATOR named: a DXpedition running a pileup takes more than two series
of calls to get through to, which is exactly why it is on the list. The
rest between series still applies, so it cannot monopolise the
transmitter — it simply never becomes ineligible.
Send to (right-click) now refuses a service with no credentials and says
which are missing. The upload runs on its own goroutine and reports into
the QSL Manager's console, which is not open when the command came from
the QSO list, so an upload to an unconfigured service looked exactly like
one that worked. The toast is also raised only once the backend has
accepted the request, and Cloudlog / Wavelog and HamQTH name themselves
in it.
Three field reports.
Icom CI-V address 00 could not be kept: zero was read as "not
configured" in all three places that validate it, so every save put the
rig back to the IC-7610's 0x98 — and the model dropdown followed, since
it is derived from the address rather than stored. Picking "Other
(custom address)" also had no effect of its own: the list re-derived
itself and snapped back to whatever rig matched. It now stays chosen.
Cloudlog/Wavelog showed "17m/17m" on ordinary FT8 contacts (OE6CLD).
Every QSO is stamped with a receive side equal to the transmit side, and
the uploaded record carried it; Wavelog draws band/band_rx whenever both
are there. In ADIF an absent BAND_RX means "same as transmit", so the
uploaded record now writes the receive side only when it differs. The
copy forwarded to another logger over UDP keeps writing it in full —
that is why it was stamped in the first place (Log4OM reads BAND_RX) —
through its own ForwardRecordADIF.
"S/F" in a spot comment joins superfox / sfox / F-H as FT8.
The watch list was a tab, and an operator working FT8 lives on the decodes
one: a station they had asked to be told about turned up on a screen they
were not looking at. The same answer is now docked in the widget strip,
above the tabs, reduced to what is worth acting on — on the air and still
needed, one row per band and mode, with the cluster's own NEW DXCC /
NEW BAND / NEW SLOT badge and a click that tunes. Off by default. The
"active and needed" answer costs a debounced query per visible slot, so it
is written once (lib/watchlistSpots) and the tab uses it too.
Auto-call:
- It answers a new prefix, county, state, square or park. Those markers
are orthogonal to the entity, they ranked as nothing-needed, and the
engine sat through a never-worked WPX prefix calling CQ. New rung at
the foot of the ladder, gated by the chase switches the badges use —
which meant making those switches portable, since the backend cannot
read localStorage.
- It calls THROUGH a pileup. Giving up the moment the DX answered
somebody else is precisely how a queue is not worked; the call and miss
counters already bound the effort, and a station in mid-exchange is
still never chosen as a new target.
The PSK Reporter panel now follows the station auto-call is waiting for:
the analysis takes a history query and a period or two to fill, so
starting it when the DX comes free is starting it too late.
Callbook lookup: a compound callsign with a page of its OWN keeps that
page's location. QRZ files HP/WE9G under exactly that form, with the
Panama square the station is operating from, and the rule that drops a
home address from a portable call was throwing it away. The record's own
country tells an operation's page from a home page.
Changelog: entries may open with [NEW], drawn as a pill in the What's new
dialog — a release is mostly fixes and the two or three genuinely new
things should not have to be found by reading all of it.
Six faults from an evening on 60 m, all in the same family: the engine
judging a station by what the log wants from it and forgetting what is
already under way.
- An exchange was abandoned mid-QSO. The reply lands in the same period
the ladder is re-read, and that period was judged before the reply was
taken into account, so a better-ranked caller took the slot from a
station that had just come back to us. The answer is settled first now,
and our own report counts as being inside the exchange too — which also
protects a QSO the operator started by hand.
- A station just picked started with misses against it. Its transmit slot
was unknown until a second decode, and with the parity unknown every
period counted, including the one spent transmitting to it.
- The freed slot after "it is working somebody else" was thrown away: the
period's decodes are in hand, so the next station is picked from them
rather than fifteen seconds later. Never mid-over.
- Auto-call is never armed from a stored setting — not at launch, not on
a profile switch. It is the one feature that puts the station on the
air by itself and OpsLog starts with Windows.
- It says what it is waiting for: a wanted station in a QSO with somebody
else now shows beside the Auto button instead of looking idle.
- Switching profile left the previous logbook's verdicts on screen. The
worked-index, chase-new and the frontend's cached verdicts are dropped
when the logbook changes.
FT decodes: distance column, a message addressed to you set whole in
green (the station you are calling keeps a tint — most of what it sends
goes to other people), badge order L / Wkd / WL, list cleared when the
RIG changes band.
Rotor: new world-map compass from EC1KD's design, with the Ultrabeam boom
and second lobe restored and the compact form preserved; the classic dial
is kept and Settings → Rotator chooses between them. Stop no longer
flickers on a rotor standing still — movement was inferred from a degree,
less than the jitter a controller reports at rest.
Measured against DXHunter on the same station at the same second: 18
decodes here against 27 there.
- The window was five minutes. PSK Reporter's uploaders batch their
reports, most of them every five, so a five-minute window catches
about one upload cycle per station. Ten, as DXHunter has always had
behind a label that says four.
- The history query ran once per target and only on the narrow feed.
It now runs in both scopes and again every five minutes while a
station is watched, which is the cadence the uploaders keep.
- It asked only what the target RECEIVED. Both directions now, so
"who is hearing him" starts full too.
- The suggested call offset looked for a run of empty slots and said
nothing when there was none — exactly the case it exists for: a
hundred decodes across a 2800 Hz passband leave no gap. Failing a
gap it names the quietest slot, ties to the higher offset, never
above the ceiling.
Chase new: the receiver squares now follow the radius that was asked
for (it was one fixed ring whatever the setting said, so raising it
bought nothing), and the panel says what the feed is doing rather than
leaving an empty list to speak for itself. A change of hunt empties it:
its rows are verdicts reached under the old rule and nothing re-judged
them. Its own band selection, because what a station CAN work and what
is worth watching tonight are different questions.
Six faults, all found on the air this evening and all in the same
feature. The decision trace added here is what found the first one:
one line per period, saying what was on the air and why each station
was refused.
- "worked" was read from the ENTITY's status, which means the country
is in the log on this band and mode. On 10 m, where most countries
are, twenty decodes out of twenty-one were refused as worked — the
watched DXpedition among them. The entity decides what is NEEDED;
the callsign's own slot decides whether calling it is a duplicate.
candidateOf() is split out and tested because one line was wrong for
weeks and nothing could catch it.
- A multi-answer line was read only up to its first message. MSHV
answers two stations in one transmission ("YV5ALI RR73; F4BPO
<HK0/PY8WW> -08") and the second half was a report to us: the engine
saw a station working somebody else and dropped the target at the
moment the DX was answering. The decodes panel already read every
segment.
- The slot after a QSO belongs to our own 73. Handing straight on to
the next station took it, switching the DX call mid-sequence, and
the frame that closes the contact never went out whole.
- A station CALLING US is answered whether or not the log wants
anything from it. It was refused for having nothing to gain, so a
QSO would end, two stations would call, and both were ignored.
- Callability was tested when a station was CHOSEN and never again
while it was held: one picked on its CQ that then answered another
caller went on being called for the whole seven attempts.
- A watched callsign now outranks every station that is not on the
list. Lifted one rung at a time it sat at the bottom with nothing
needed from it, and was never reached on a busy band — the opposite
of what putting it on the list means.
Halt is now a verdict: the station is set aside for the session rather
than released, and the chase list does not override it. The attempts
cap lets the over finish instead of cutting the call that counted it,
and a rest is a rest. Only what the decodes list is SHOWING can be
called — the panel publishes the callsigns it shows, so there is one
definition of "shown" and not two.
Reported from a real shack: JTDX decoding FT4 came through as Q65. A
Decode carries a one-character marker rather than a mode name, and the
forks do not agree on that one — so the character alone cannot answer,
and its wrong answer reached everything behind it: new mode, new slot,
the mode filter, all computed against a mode nobody was using.
The sending program's own Status settles it. It comes from that program,
names the mode in full, and is re-sent whenever it changes, so it knows
what is being decoded in a way one character never can. Only the
ambiguous marker consults it: the ones both forks agree on keep
answering from the table, Status or no Status, and with no Status at all
':' still reads as Q65 — WSJT-X's meaning, the older and commoner.
Reported on an FTDX101MP: the slider sprang back to 100 W. The console
was not wrong about the rig — yaesuMaxPower already answered 200 for
that model, and the slider was drawn to it. The SET path carried its own
hard-coded 100, so asking for 200 W sent PC100, the radio obeyed, and
the next poll read back what had actually been set.
One ceiling now, the one the console draws to, so the two cannot say
different things. Pinned with the model list, including the unknown-rig
case: crediting a radio with power it does not have would be commands it
NAKs, so silence still means 100.
HAMLOG.online no longer issues API keys, and its upload API takes
nothing else. An operator without a key cannot obtain one, so the
auto-upload switch, the on-close sweep and the 'Send to' entry were all
arming something that could only fail — silently, once per QSO.
Closed at the source rather than hidden in the UI: the upload returns a
sentinel that says why, the manager stops routing to it and says so once
a session, and the manual path refuses with the same words. The settings
page states it plainly instead of showing a switch that does nothing.
Nothing else goes. Their confirmations arrive as an ADIF FILE and never
needed a key, so that import stays; the sent/received state already in
operators' logs stays readable, filterable and bulk-editable; and the
upload itself is kept whole as uploadHamlogLive, still covered by its
request-shape tests, against the day keys come back.