fix: Flex with multiple slices opened was always showing slice A s-meter.

This commit is contained in:
2026-07-10 23:13:06 +02:00
parent 6c39204301
commit 73f3ec51f7
10 changed files with 192 additions and 15 deletions
+50 -6
View File
@@ -815,6 +815,12 @@ func (a *App) startup(ctx context.Context) {
// CAT manager: emit pushes state to the frontend via Wails events, and
// forwards frequency/mode to any outbound UDP emitters (PstRotator, N1MM).
a.cat = cat.NewManager(func(s cat.RigState) {
// DIAGNOSTIC: the manager only fires this on a USER-relevant change, so a
// burst of these lines = the frontend is being re-rendered rapidly (the
// "screen flickers" symptom). Shows WHAT is churning — connection flap,
// or freq/split/mode oscillating between slices during FT8.
applog.Printf("cat:state → connected=%v freq=%d rx=%d split=%v mode=%s band=%s",
s.Connected, s.FreqHz, s.RxFreqHz, s.Split, s.Mode, s.Band)
if a.ctx != nil {
wruntime.EventsEmit(a.ctx, "cat:state", s)
}
@@ -7870,6 +7876,12 @@ func (a *App) consumeUDPEvents() {
if a.udp == nil {
return
}
// Rate-limit decode spots: an FT8 opening decodes the SAME stations every
// cycle, so spot each call at most once per window (well within its display
// lifetime) instead of blasting the radio with a burst of adds+removes every
// 15 s. Single-goroutine loop → the map needs no lock.
const decodeSpotWindow = 20 * time.Second
lastDecodeSpot := map[string]time.Time{}
for ev := range a.udp.Events() {
if a.ctx == nil {
continue
@@ -7880,6 +7892,14 @@ func (a *App) consumeUDPEvents() {
// panadapter when the option is on; green + SNR comment, auto-expiring
// after the configured duration. De-duped per call in the Flex backend.
if a.catFlexDecodeSpots && a.cat != nil {
now := time.Now()
if t, seen := lastDecodeSpot[ev.DecodeCall]; seen && now.Sub(t) < decodeSpotWindow {
continue // spotted this call very recently — don't re-hammer the radio
}
if len(lastDecodeSpot) > 4000 {
lastDecodeSpot = map[string]time.Time{} // bound memory on long sessions
}
lastDecodeSpot[ev.DecodeCall] = now
secs := a.catFlexDecodeSecs
if secs <= 0 {
secs = 120
@@ -8203,6 +8223,7 @@ func (a *App) SetCATFrequency(hz int64) error {
if a.cat == nil {
return fmt.Errorf("cat not initialized")
}
applog.Printf("cat: SetCATFrequency %.3f MHz — deliberate frontend set (spot click / band / memory / entry)", float64(hz)/1e6)
err := a.cat.SetFrequency(hz)
if err != nil {
applog.Printf("cat: SetFrequency(%d Hz) dispatch error: %v", hz, err)
@@ -8226,6 +8247,7 @@ func (a *App) ultrabeamFollowNow(freqHz int64) {
if err != nil || !s.Enabled || !s.Follow {
return
}
applog.Printf("ultrabeam: followNow — deliberate CAT set to %.3f MHz", float64(freqHz)/1e6)
step := s.StepKHz
if step <= 0 {
step = 50
@@ -8241,17 +8263,22 @@ func (a *App) ultrabeamFollowNow(freqHz int64) {
}
}
khz := int(freqHz / 1000)
diff := khz - st.Frequency
ref := st.Frequency
if ref <= 0 {
ref = c.LastSetKHz()
}
diff := khz - ref
if diff < 0 {
diff = -diff
}
if st.Frequency > 0 && diff < step {
if ref > 0 && diff < step {
applog.Printf("ultrabeam: followNow within deadband (%d kHz vs ref %d, step %d) — no move", khz, ref, step)
return // within the deadband — don't chase a tiny QSY
}
if err := c.SetFrequency(khz, st.Direction); err != nil {
applog.Printf("ultrabeam: immediate re-tune to %d kHz failed: %v", khz, err)
} else {
applog.Printf("ultrabeam: re-tuned on freq set → %d kHz (dir %d)", khz, st.Direction)
applog.Printf("ultrabeam: re-tuned on freq set → %d kHz (dir %d, was ref %d kHz)", khz, st.Direction, ref)
}
}
@@ -9594,6 +9621,7 @@ func (a *App) ultrabeamFollowLoop(c *ultrabeam.Client, stepKHz int, stop <-chan
}
ticker := time.NewTicker(1500 * time.Millisecond)
defer ticker.Stop()
lastRigKHz := 0 // only log when the followed rig frequency actually changes
for {
select {
case <-stop:
@@ -9611,6 +9639,15 @@ func (a *App) ultrabeamFollowLoop(c *ultrabeam.Client, stepKHz int, stop <-chan
continue
}
rigKHz := int(rs.FreqHz / 1000)
// Log the moment the poll sees a NEW rig frequency — this is what the
// follow loop chases. If the antenna QSYs unexpectedly, this shows the
// CAT backend started reporting that frequency (e.g. WSJT-X moved the
// dial, or the active slice changed) even though you didn't touch the VFO.
if rigKHz != lastRigKHz {
applog.Printf("ultrabeam: follow loop reads rig freq %.3f MHz (mode %s) — antenna at %d kHz, step %d kHz",
float64(rs.FreqHz)/1e6, rs.Mode, st.Frequency, stepKHz)
lastRigKHz = rigKHz
}
// Skip frequencies outside the antenna's tunable range (other band).
if st.FreqMin > 0 && st.FreqMax > 0 {
rigMHz := rs.FreqHz / 1_000_000
@@ -9618,17 +9655,24 @@ func (a *App) ultrabeamFollowLoop(c *ultrabeam.Client, stepKHz int, stop <-chan
continue
}
}
diff := rigKHz - st.Frequency
// Deadband reference = the antenna's reported freq, or (when it hasn't
// reported one yet) the last freq we commanded — so a 0/blank status
// doesn't bypass the deadband and re-tune on every small QSY.
ref := st.Frequency
if ref <= 0 {
ref = c.LastSetKHz()
}
diff := rigKHz - ref
if diff < 0 {
diff = -diff
}
if st.Frequency > 0 && diff < stepKHz {
if ref > 0 && diff < stepKHz {
continue // within the deadband — leave the motors alone
}
if err := c.SetFrequency(rigKHz, st.Direction); err != nil {
applog.Printf("ultrabeam: follow re-tune to %d kHz failed: %v", rigKHz, err)
} else {
applog.Printf("ultrabeam: followed rig → %d kHz (dir %d)", rigKHz, st.Direction)
applog.Printf("ultrabeam: followed rig → %d kHz (dir %d, was ref %d kHz, step %d)", rigKHz, st.Direction, ref, stepKHz)
}
}
}