diff --git a/app_sat_track.go b/app_sat_track.go index c0cfced..dbb6970 100644 --- a/app_sat_track.go +++ b/app_sat_track.go @@ -338,22 +338,28 @@ func (a *App) satTrackStep(t *satTracker) { down, up := sh.DownHz, sh.UpHz t.mu.Lock() - t.status = SatTrackStatus{ - On: true, Name: b.Name, Transponder: tp.Label, Mode: tp.Mode, - NominalDown: nominal, NominalUp: nomUp, - DownHz: down, UpHz: up, - Az: pos.Az, El: pos.El, Visible: visible, - Radio: t.status.Radio, Error: t.status.Error, - } + // Rebuilt from the old one, not from nothing. + // + // The rotator fields are written by readRotator, which runs at most every + // three seconds — a controller query binds a socket and waits. Building a + // fresh status here dropped them on every OTHER tick, so the antenna + // readout appeared for one second in three and vanished again, which reads + // as a rotator that keeps disconnecting. + st := t.status + st.On, st.Name, st.Transponder, st.Mode = true, b.Name, tp.Label, tp.Mode + st.NominalDown, st.NominalUp = nominal, nomUp + st.DownHz, st.UpHz = down, up + st.Az, st.El, st.Visible = pos.Az, pos.El, visible + t.status = st t.mu.Unlock() t.pointRotator(pos, b.Geostationary) t.readRotator() t.mu.Lock() - st := t.status + out := t.status t.mu.Unlock() - a.emitSatTrack(st) + a.emitSatTrack(out) // Only send what has actually moved. The step is the smallest change worth a // command: on SSB a listener hears twenty hertz, on an FM channel nothing diff --git a/changelog.json b/changelog.json index 329ade6..5a2d27f 100644 --- a/changelog.json +++ b/changelog.json @@ -1,4 +1,18 @@ [ + { + "version": "0.27.22", + "date": "", + "en": [ + "The antenna readout no longer flickers in and out during a pass. The rotator is asked where it is every three seconds, but the tracking status was rebuilt from scratch every second and dropped the answer in between — so the antenna appeared for one second in three, which reads as a rotator that keeps disconnecting.", + "While tracking, the two frequencies and the antenna bearing sit beside the Tracking button. During a pass an operator watches the radio and the antenna, not a column on the far side of the window — and that column is the first thing hidden to get the map full width. The compass spins while the antenna is still slewing: a mast takes tens of seconds to cross a pass, and the difference between \"on its way\" and \"stuck\" is the whole reason to look at it.", + "Satellite frequencies are shown to a hundred hertz instead of one. The Doppler moves about sixty hertz a second on 70 cm, so the last two digits changed every tick and the display was a blur of numbers nobody could read and nobody needed. The radio still gets the whole figure — this is only how much of it is worth putting in front of you. The shift beside it now reads \"+9.7 kHz\" rather than \"+9741 Hz\"." + ], + "fr": [ + "L’affichage de l’antenne ne clignote plus pendant un passage. Le rotor est interrogé toutes les trois secondes, mais l’état du suivi était reconstruit de zéro chaque seconde et perdait la réponse entre-temps — l’antenne apparaissait donc une seconde sur trois, ce qui se lit comme un rotor qui se déconnecte sans arrêt.", + "Pendant le suivi, les deux fréquences et le cap de l’antenne sont affichés à côté du bouton Tracking. Pendant un passage, on regarde la radio et l’antenne, pas une colonne à l’autre bout de la fenêtre — et c’est la première chose qu’on masque pour avoir la carte en pleine largeur. La boussole tourne tant que l’antenne est en mouvement : un pylône met des dizaines de secondes à traverser un passage, et distinguer « en route » de « bloqué » est toute la raison de la regarder.", + "Les fréquences satellite sont affichées à la centaine de hertz au lieu du hertz. Le Doppler se déplace d’environ soixante hertz par seconde en 70 cm : les deux derniers chiffres changeaient à chaque tick et l’affichage était une bouillie de chiffres illisible et inutile. La radio reçoit toujours la valeur complète — il ne s’agit que de ce qui vaut la peine d’être mis sous vos yeux. Le décalage à côté indique désormais « +9,7 kHz » plutôt que « +9741 Hz »." + ] + }, { "version": "0.27.21", "date": "", diff --git a/frontend/src/components/SatellitePanel.tsx b/frontend/src/components/SatellitePanel.tsx index a70a177..524e9ed 100644 --- a/frontend/src/components/SatellitePanel.tsx +++ b/frontend/src/components/SatellitePanel.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; -import { Satellite as SatIcon, Radio, ArrowUp, ArrowDown, PanelRightClose, PanelRightOpen, Radar } from 'lucide-react'; +import { Satellite as SatIcon, Radio, ArrowUp, ArrowDown, PanelRightClose, PanelRightOpen, Radar, Compass } from 'lucide-react'; import { GetSatelliteBirds, GetSatellitePositions, GetSatellitePasses, GetSatelliteTuning, GetSatelliteGroundTrack, GetSatelliteTLEInfo, GetSatelliteNextPass, GetSatelliteSkyTrack, @@ -74,11 +74,28 @@ const SIDE_SHOWN_KEY = 'opslog.satSideShown'; const SIDE_W_DEFAULT = 336, SIDE_W_MIN = 240, SIDE_W_MAX = 720; const SKY_SHOWN_KEY = 'opslog.satSkyShown'; +// Four decimals — a hundred hertz, which is what a linear transponder is +// actually tuned to. +// +// It used to be six, and the last two digits changed every tick: the Doppler +// moves about sixty hertz a second on 70 cm, so the display was a blur of +// numbers nobody could read and nobody needed. The RADIO still gets the whole +// figure — the correction is computed and sent to the hertz — this is only how +// much of it is worth putting in front of an operator. The shift beside it, in +// kilohertz, is where the fine movement shows. const fmtHz = (hz: number) => { if (!hz) return '—'; - // Six decimals: a linear transponder is tuned to the hundred hertz, and the - // Doppler correction moves the last three digits every second. - return (hz / 1e6).toFixed(6).replace(/(\d)(?=(\d{3})+\.)/g, '$1 '); + return (hz / 1e6).toFixed(4).replace(/(\d)(?=(\d{3})+\.)/g, '$1 '); +}; + +// The Doppler shift, as an operator would say it: hertz while it is small +// enough to say in hertz, kilohertz once it is not. "+9741 Hz" is four digits +// of precision on a number that is only ever read as "about ten kilohertz". +const fmtShift = (hz: number) => { + const sign = hz > 0 ? '+' : '−'; + const a = Math.abs(hz); + if (a < 1000) return `${sign}${Math.round(a)} Hz`; + return `${sign}${(a / 1000).toFixed(1)} kHz`; }; const fmtDeg = (d: number) => `${d.toFixed(1)}°`; const fmtKm = (km: number) => `${Math.round(km).toLocaleString()} km`; @@ -619,6 +636,14 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) { // The pass, as a countdown and a bar. Both derived here from two timestamps, // so they move every second without asking Go anything. + // Is the antenna still on its way? The rotator is asked where it is every + // three seconds and a mast takes tens of seconds to cross a pass, so a + // difference between where it is and where the satellite is means it is + // moving — which is exactly what a number alone cannot show, and the + // difference between "on its way" and "stuck" is the whole reason to look. + const antennaMoving = !!tracking?.rot_on && !!tracking.rot_live && + Math.abs(((tracking.az - tracking.rot_az + 540) % 360) - 180) > 3; + const aosMs = pass?.has_pass ? Date.parse(pass.aos) : 0; const losMs = pass?.has_pass ? Date.parse(pass.los) : 0; const inPass = !!pass?.has_pass && now >= aosMs && now < losMs; @@ -687,6 +712,40 @@ export function SatellitePanel({ myGrid }: { myGrid: string }) { {tracking?.on && tracking.radio === 'downlink-only' && ( {t('sat.downlinkOnly')} )} + + {/* What the station is actually doing, beside the button that started + it. During a pass an operator watches the radio and the antenna, not + a column on the far side of the window — and that column is the + first thing they hide to get the map full width. */} + {tracking?.on && ( +
+ + + {fmtHz(tracking.down_hz)} + + {!!tracking.up_hz && ( + + + {fmtHz(tracking.up_hz)} + + )} + {tracking.rot_on && ( + + {/* The needle spins while the antenna is slewing. A rotator + takes tens of seconds to cross a pass, and the difference + between "on its way" and "stuck" is the whole reason to look + at it — a number alone cannot show movement. */} + + + {Math.round(tracking.rot_az)}° + {!tracking.rot_az_only && ` / ${Math.round(tracking.rot_el)}°`} + + + )} +
+ )}
{/* Elements are maintenance, so only their AGE is here — and only when it has become a reason the panel might be wrong. */} @@ -1031,9 +1090,7 @@ function FreqRow({ label, hz, nominal }: { label: string; hz: number; nominal: n {label} {fmtHz(hz)} {!!shift && ( - - {shift > 0 ? '+' : '−'}{Math.abs(Math.round(shift))} Hz - + {fmtShift(shift)} )}
);