diff --git a/app.go b/app.go index 1033aaa..d790b0a 100644 --- a/app.go +++ b/app.go @@ -17486,7 +17486,7 @@ func linkHeading(l rotorLink) (az, el float64, hasEl bool, raw string, err error func linkGoTo(l rotorLink, az, el int) error { switch l.Type { case "rotgenius": - return rotgenius.New(l.Host, l.Port).GoTo(l.Num, az) + return rotgeniusGoTo(l, az) case "arco": return arcoClient(l).GoTo(az) case "erc": @@ -22517,3 +22517,77 @@ func (a *App) IcomConsolePTT(on bool) error { } return a.cat.SetPTT(on) } + +// rotgeniusGoTo picks which way round to reach a bearing on a mast with an +// overlap. +// +// A 450° rotator can be at 010° twice over: once at 10 and once at 370, and +// only the second reaches it without unwinding the cable back through north. +// OpsLog used to clamp every target to 360, so an operator with such a mast +// watched the Rotator Genius stop dead at 359 and had to press "clockwise" by +// hand to get through north. +// +// THE GENIUS DECIDES WHAT IS REACHABLE, not us and not a setting. It reports the +// limits it is configured with, and they are the truth about what is bolted to +// the tower: a box configured "5 to 4" — the factory 360° range — refuses a +// target of 370, and asking anyway turns a working command into a rejected one. +// So the overlap is used only when the Genius itself says it has one, and when +// it does not, the operator is told, because the setting to change is in the +// Genius and not here. +// +// Which of the two forms is right then depends on where the antenna IS, so the +// heading is read first and the nearer one wins. That is the point of an +// overlap: a beam at 350° heading for 010° should cross north, not travel the +// other 340 degrees. +func rotgeniusGoTo(l rotorLink, az int) error { + c := rotgenius.New(l.Host, l.Port) + a := ((az % 360) + 360) % 360 + if l.MaxAz <= 360 { + return c.GoTo(l.Num, a) + } + st, _, err := c.Heading(l.Num) + if err != nil || !st.Connected { + // No reading to compare against. The plain bearing is always reachable; + // the overlap is an optimisation, not a requirement. + return c.GoTo(l.Num, a) + } + if st.LimitCW <= 360 { + // OpsLog is set to 450 and the Genius is not. Said once per move rather + // than silently doing the wrong thing — the fix is in the Genius's own + // rotator configuration, and nothing here can reach past its limits. + rotgeniusRangeWarn(l, st) + return c.GoTo(l.Num, a) + } + target := a + if alt := a + 360; alt <= st.LimitCW && absInt(alt-st.Azimuth) < absInt(a-st.Azimuth) { + target = alt + applog.Printf("rotator: %d° is nearer as %d° from the antenna's %d° (Genius limit %d)", + a, alt, st.Azimuth, st.LimitCW) + } + return c.GoTo(l.Num, target) +} + +// rotgeniusRangeWarn says, at most once a minute, that the two ends disagree +// about the mast. +var rotgeniusWarnedAt sync.Map // host:port → time.Time + +func rotgeniusRangeWarn(l rotorLink, st rotgenius.Status) { + key := fmt.Sprintf("%s:%d/%d", l.Host, l.Port, l.Num) + if v, ok := rotgeniusWarnedAt.Load(key); ok { + if t, _ := v.(time.Time); time.Since(t) < time.Minute { + return + } + } + rotgeniusWarnedAt.Store(key, time.Now()) + applog.Printf("rotator: OpsLog is set to a 450° mast but the Rotator Genius is configured %d° to %d° — "+ + "a range it will not go past, whatever is asked. Change the limits in the Genius's own Rotator "+ + "Configuration; until then the antenna takes the long way round through north.", + st.LimitCCW, st.LimitCW) +} + +func absInt(v int) int { + if v < 0 { + return -v + } + return v +} diff --git a/changelog.json b/changelog.json index 5a2d27f..9d88d5b 100644 --- a/changelog.json +++ b/changelog.json @@ -5,12 +5,14 @@ "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\"." + "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\".", + "Rotator Genius: a 450° mast can now be pointed through the overlap instead of the long way round. OpsLog clamped every target to 360°, so a bearing just past north meant the rotator stopped at 359 and the operator pressed \"clockwise\" by hand to get through it. A rotator range now appears for the Rotator Genius like the other backends it applies to, and the nearer of the two ways to a bearing is taken — 010° reached as 370° when the antenna is already at 350°. The Genius's own limits are read and they win: a box configured for 360° refuses a target beyond it, so OpsLog does not ask, and says in the log that the range has to be changed in the Genius's own Rotator Configuration." ], "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 »." + "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 ».", + "Rotator Genius : un pylône 450° peut désormais être pointé à travers le recouvrement au lieu de faire le tour. OpsLog écrêtait toute consigne à 360°, donc un cap juste après le nord arrêtait le rotor à 359 et il fallait cliquer « clockwise » à la main pour le franchir. L’amplitude du rotor apparaît maintenant pour le Rotator Genius comme pour les autres pilotes concernés, et le plus court des deux chemins est pris — 010° atteint comme 370° quand l’antenne est déjà à 350°. Les limites propres au Genius sont lues et priment : un boîtier configuré en 360° refuse une consigne au-delà, donc OpsLog ne la lui envoie pas et écrit dans le journal que l’amplitude est à changer dans la configuration du Genius lui-même." ] }, { diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 49cd679..9cf2088 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -5135,7 +5135,12 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged // controller. PstRotator knows which machine is on the other end and // does its own overlap; two programs each deciding to go the long // way round is how an antenna unwinds mid-pass. - const ownsOverlap = isERC || isEasycomm; + // + // A Rotator Genius is in between: it has its OWN limits, and they + // win — this setting only tells OpsLog it may ask for the far side + // of the overlap at all. If the Genius is configured 360°, it says + // so in the log rather than sending commands the box refuses. + const ownsOverlap = isERC || isEasycomm || isRG; return (