fix(sat): the per-band antennas reach the satellite slices
Settings ▸ FlexRadio stores the band→antenna map keyed by the band name
in capitals — that is what the panel writes and what the entry form reads
back. applySatRadio looked its two bands up through bandForHz, which
returns the band plan's own spelling ("70cm"), so both lookups missed,
both antennas came back empty, and the early return left the pass on
whichever antenna the radio was last used on. An operator with XVTA on
2 m and XVTB on 70 cm had configured exactly the thing being ignored, and
nothing said so: the downlink ran through the wrong transverter in
silence.
The key is now computed by flexBandAntKey, which exists so the next
caller cannot make the same mistake, and a test pins the case in the
direction that broke. Both outcomes are logged — the resolved antennas,
or the bands nothing was configured for — because from the outside a
setting never made and a lookup that missed look identical.
Alongside, three things the same pass made obvious:
- Tracking shows the satellite's own azimuth, elevation, distance and
altitude beside the frequencies. The strip held where the ANTENNA was
pointing but not where the bird was, which is what says whether a pass
is worth calling on. Elevation dims below the horizon so a satellite
followed before its lever cannot be read as workable.
- Both satellite lists in the settings are sorted by name with the
numbers taken as numbers. The available column followed the order
birds.json happens to be written in and the followed column the order
of the clicks, so finding one bird among sixteen meant reading all
sixteen.
- A followed satellite that has since been renamed is resolved through
the plan's aliases. LILACSAT-2 became LO-90, and the followed list is
stored as plain text, so the bird the operator had chosen appeared as
having no elements while the same satellite sat in the available
column under its new name. Resolved in GetSatSettings rather than
satSettings, which is read at startup before the plan is loaded.
This commit is contained in:
@@ -57,7 +57,7 @@ type Tuning = {
|
||||
type Track = {
|
||||
on: boolean; name: string; transponder: string; mode: string;
|
||||
nominal_down: number; nominal_up: number; down_hz: number; up_hz: number;
|
||||
az: number; el: number; visible: boolean;
|
||||
az: number; el: number; visible: boolean; range_km: number; alt_km: number;
|
||||
radio: string; // "sat" | "downlink-only" | ""
|
||||
error: string;
|
||||
rot_on: boolean; rot_az: number; rot_el: number; rot_live: boolean; rot_az_only: boolean;
|
||||
@@ -719,7 +719,28 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) {
|
||||
first thing they hide to get the map full width. */}
|
||||
{tracking?.on && (
|
||||
<div className="flex items-center gap-2.5 rounded-md border border-border bg-card/60 px-2 py-0.5 text-xs tabular-nums">
|
||||
<span className="flex items-center gap-1" title={t('sat.down')}>
|
||||
{/* Where the bird IS, which is not where the antenna is pointing:
|
||||
these three say whether the pass is worth calling on, and the
|
||||
rotator group further along says whether the mast has caught up
|
||||
with them. Elevation goes dim below the horizon, so a satellite
|
||||
still being tracked on its way up cannot be read as workable. */}
|
||||
<span className="flex items-center gap-1.5" title={`${t('sat.tipAz')} / ${t('sat.tipEl')}`}>
|
||||
<Radar className="size-3 text-muted-foreground" />
|
||||
<span className={cn('font-medium', !tracking.visible && 'text-muted-foreground')}>
|
||||
{Math.round(tracking.az)}° / {tracking.el.toFixed(1)}°
|
||||
</span>
|
||||
</span>
|
||||
{tracking.range_km > 0 && (
|
||||
<span className="text-muted-foreground" title={t('sat.range')}>
|
||||
{Math.round(tracking.range_km).toLocaleString()} km
|
||||
</span>
|
||||
)}
|
||||
{tracking.alt_km > 0 && (
|
||||
<span className="text-muted-foreground" title={t('sat.altitude')}>
|
||||
↑{Math.round(tracking.alt_km).toLocaleString()} km
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1 border-l border-border pl-2.5" title={t('sat.down')}>
|
||||
<ArrowDown className="size-3 text-muted-foreground" />
|
||||
<span className="font-medium">{fmtHz(tracking.down_hz)}</span>
|
||||
</span>
|
||||
|
||||
@@ -1468,6 +1468,13 @@ function SatelliteElementsBlock({ autoTle, onAutoTle }: { autoTle: boolean; onAu
|
||||
// Following none means following every satellite that has both elements and a
|
||||
// frequency plan, which is the sensible thing for somebody who has not chosen
|
||||
// yet and the reason the list does not start out empty-handed.
|
||||
// byCallsign orders satellite names the way an operator reads them: alphabetical,
|
||||
// but with the number taken as a number. Plain string order files AO-123 between
|
||||
// AO-1 and AO-27, which is not where anybody looks for it.
|
||||
function byCallsign(a: { name?: string }, b: { name?: string }) {
|
||||
return String(a?.name ?? '').localeCompare(String(b?.name ?? ''), undefined, { numeric: true, sensitivity: 'base' });
|
||||
}
|
||||
|
||||
function SatelliteFollowList({ followed, onChange }: { followed: string[]; onChange: (next: string[]) => void }) {
|
||||
const { t } = useI18n();
|
||||
const [all, setAll] = useState<any[]>([]);
|
||||
@@ -1485,8 +1492,13 @@ function SatelliteFollowList({ followed, onChange }: { followed: string[]; onCha
|
||||
const needle = q.trim().toLowerCase();
|
||||
const available = all.filter((b) => !followedSet.has(String(b.name).toUpperCase())
|
||||
&& (!withPlanOnly || (b.transponders?.length ?? 0) > 0)
|
||||
&& (needle === '' || String(b.name).toLowerCase().includes(needle)));
|
||||
const chosen = followed.map((n) => byName.get(n) ?? { name: n, has_elements: false, transponders: [] });
|
||||
&& (needle === '' || String(b.name).toLowerCase().includes(needle))).sort(byCallsign);
|
||||
// Sorted, both columns: the left one came in the order the frequency file
|
||||
// happens to be written and the right one in the order the operator clicked,
|
||||
// so finding AO-91 among sixteen followed birds meant reading all sixteen.
|
||||
const chosen = followed
|
||||
.map((n) => byName.get(n) ?? { name: n, has_elements: false, transponders: [] })
|
||||
.sort(byCallsign);
|
||||
|
||||
const label = (b: any) => {
|
||||
const bits: string[] = [];
|
||||
|
||||
Reference in New Issue
Block a user