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]>
The map answers "where is the satellite over the earth". This answers
"where do I look", which during a pass is the question that matters.
The projection is the one every tracker uses and every operator already
reads: the centre is the zenith, the rim is the horizon, north is up. So
the radius is (90 − elevation), not the elevation — a bird overhead is a
dot in the middle, and a pass that hugs the rim is one that never rises.
Whether it comes over the roof or along the treeline is something no
amount of azimuth and elevation digits conveys, and one glance settles.
The whole pass is drawn: a dashed track with arrowheads for the direction
of travel, a hollow circle where it rises, a filled one where it sets,
and a cross where the satellite is now — green above the horizon, grey
below, because the numbers are still right down there and nothing can be
worked through the earth.
The track is fetched once a minute, not once a second: the SHAPE of a
pass does not change while it happens. Only the marker moves, and that
rides on the tuning poll that was already running.
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.
Settings ▸ Lists ▸ Satellites is gone. It was a text box an operator
typed their birds into by hand, and it had nothing to do with the
satellites the tracker knew: the same station kept two lists of the same
satellites and they drifted apart. The SAT_NAME box on the entry form now
offers the followed set — or every satellite with a frequency plan when
none is followed — merged with whatever that old list still holds, read
and never written, because what it holds is somebody's past work.
And the satellite section moved out of Hardware, which it never was.
Which birds you chase, where your antenna stands and how old your
elements are is operating; the rotator inside it is the only hardware
there, and one block does not make a section a device.
Two things belong in different places, and they were in one.
Settings → Satellites now holds the setup: which satellites to follow —
the same two-column shape as the awards, for the same reason, since a
feed carries two hundred birds and an operator works six — and the
orbital elements, their age, the fetch, and pasting your own. Following
none still means every satellite with both elements and a frequency plan,
so somebody who has not chosen yet is not handed an empty tab.
The panel keeps only what a pass needs. A countdown to AOS, or to LOS
once it is up, because that is the number that decides whether you sit
down; a bar for where in the pass you are, since mid-pass the useful
question is not the clock but whether you are past the peak; rise, peak
and set with compass directions, because "rises SW" is a direction to
look in and 213° is arithmetic. Distance, altitude and footprint. And
approaching or receding, which is the sign of the whole Doppler
correction and the only thing that explains why the frequencies are
moving the way they are.
The countdowns run in the browser from two timestamps. Predicting a pass
steps the orbit across a day thirty seconds at a time, which is not
something to do once a second for a clock the page can keep itself.
Two things reported together on the new tab.
The locator: the station's is on the ACTIVE PROFILE, not in a settings
key. keyStationMyGrid is a legacy key that EnsureDefault migrated into
the profile long ago and nothing writes any more, so reading it told an
operator with a perfectly good locator on screen that he had not set one
— and refused every pass prediction on the strength of it.
The map: Leaflet stacks its panes and controls up to z-index 1000, which
without a stacking context of their own float over the whole application
— Preferences opened behind the map, its Save and Close buttons under it.
The other three maps in OpsLog each carry `isolate` for exactly this
reason; this one was missing it.
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.