diff --git a/app.go b/app.go index d790b0a..252f529 100644 --- a/app.go +++ b/app.go @@ -22518,71 +22518,40 @@ 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. +// rotgeniusGoTo picks which way round to reach a bearing when the controller +// has an overlap to offer. // -// 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, and it needs no setting from us. It reports the limits it +// is configured with on every heading query, and those are the truth about what +// is bolted to the tower: if the far side of an overlap is reachable it says so, +// and if it is not, asking anyway turns a working command into a rejected one. +// Its manual is unambiguous — "you will not be able to give it a target beyond +// the limits". // -// 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. +// In practice today that means the plain bearing, every time. A Rotator Genius +// is a 360° controller: its Limits fields say where the mechanical stop sits +// within one turn ("5 to 4" is a dead zone at four and a half degrees), not how +// far the mast can travel, and an operator with a 450° rotator gets 360° of it. +// The overlap branch stays because the decision is made from what the device +// reports rather than from an assumption about it — a controller that one day +// answers 450 will be driven through the overlap without a line changing 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. +// Which of the two forms is right depends on where the antenna IS, so the +// heading is read first and the nearer one wins: 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. + if err != nil || !st.Connected || st.LimitCW <= 360 { 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, alt) } - 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) + return c.GoTo(l.Num, a) } func absInt(v int) int { diff --git a/changelog.json b/changelog.json index 9d88d5b..44c2a42 100644 --- a/changelog.json +++ b/changelog.json @@ -6,13 +6,13 @@ "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\".", - "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." + "Rotator Genius: OpsLog no longer clamps a target to 360°, and reads the limits the Genius reports so it can drive an overlap when the controller offers one. In practice a Rotator Genius is a 360° controller — its Limits fields say where the mechanical stop sits within one turn, not how far the mast travels — so an operator with a 450° rotator still gets 360° of it, and that limit is the controller’s, not OpsLog’s. The rotator range is therefore not offered for it: a setting that can only ever be refused by the box is worse than none." ], "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 ».", - "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." + "Rotator Genius : OpsLog n’écrête plus une consigne à 360° et lit les limites que le Genius rapporte, de façon à exploiter un recouvrement quand le contrôleur en offre un. Dans les faits, le Rotator Genius est un contrôleur 360° — ses champs Limits indiquent où se trouve la butée mécanique dans un tour, pas la course du pylône — donc un rotor 450° n’en donne que 360, et cette limite est celle du contrôleur, pas d’OpsLog. L’amplitude du rotor n’est donc pas proposée pour lui : un réglage que le boîtier ne pourra que refuser est pire que pas de réglage du tout." ] }, { diff --git a/frontend/src/components/SettingsModal.tsx b/frontend/src/components/SettingsModal.tsx index 9cf2088..2d7e8fe 100644 --- a/frontend/src/components/SettingsModal.tsx +++ b/frontend/src/components/SettingsModal.tsx @@ -5136,11 +5136,15 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged // does its own overlap; two programs each deciding to go the long // way round is how an antenna unwinds mid-pass. // - // 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; + // NOT offered for a Rotator Genius. Its manual is plain — "you will + // not be able to give it a target beyond the limits" — and its + // Limits fields say where the mechanical stop sits within ONE turn + // ("5 to 4" is a dead zone at four and a half degrees), not how far + // the mast travels. Offering 450° there would be offering a setting + // that can only ever be refused by the box. Whether the overlap is + // used is decided from what the Genius itself reports, in + // rotgeniusGoTo, and needs no setting at all. + const ownsOverlap = isERC || isEasycomm; return (