Files
OpsLog/internal/cat/flex_rxslice_test.go
T
rouggy 961357474d feat: Flex stays on the RX slice in split
SmartSDR moves its active flag onto the TX slice as soon as the transmit
frequency is touched, and OpsLog followed it. But the operator is listening to
the DX on the other slice: the S-meter, audio level, filter and DSP they are
adjusting all belong there, so following the transmitter handed them the
controls of a receiver they were not using.

In split the receive slice now wins. Simplex is unchanged, and two same-band
slices in different classes (SSB alongside FT8) are still not a split.

A slice clicked IN OPSLOG is remembered and overrides both the split rule and
the radio's focus — that is the operator speaking. Activating a slice on the
radio or in SmartSDR deliberately does not move OpsLog. The pin is dropped when
that slice stops being in use, so a closed receiver cannot strand the display.

The panel now highlights the slice OpsLog is working with rather than the radio's
focused one; reporting the radio's flag would light up one slice while every
control acted on another.
2026-07-30 23:03:29 +02:00

69 lines
2.7 KiB
Go

package cat
import "testing"
// In split, OpsLog stays on the RECEIVE slice.
//
// SmartSDR moves its "active" flag onto the TX slice as soon as the transmit
// frequency is touched. Following it hands the operator the S-meter, audio
// level, filter and DSP of a receiver they are not listening to — while the DX
// they are working is on the other slice.
func TestMainSliceStaysOnRXInSplit(t *testing.T) {
mk := func(hz int64, mode string, active, tx bool) *flexSlice {
return &flexSlice{freqHz: hz, mode: mode, active: active, tx: tx, inUse: true}
}
// Working a DX on 14.100, transmitting up on 14.200. The radio has focused
// the transmitter.
f := &Flex{pinnedSlice: -1, slices: map[int]*flexSlice{
0: mk(14_100_000, "USB", false, false), // where the DX is heard
1: mk(14_200_000, "USB", true, true), // where we transmit, radio-focused
}}
idx, s := f.mainSliceLocked()
if idx != 0 || s == nil || s.freqHz != 14_100_000 {
t.Errorf("main slice = %d (%v Hz) — want slice 0, the RX side on 14.100", idx, s.freqHz)
}
// Simplex: the radio's focus is authoritative again, nothing to prefer.
f2 := &Flex{pinnedSlice: -1, slices: map[int]*flexSlice{
0: mk(14_100_000, "USB", false, false),
1: mk(14_200_000, "USB", true, true),
}}
f2.slices[0].inUse = false // only the TX slice is in use → simplex
if idx, _ := f2.mainSliceLocked(); idx != 1 {
t.Errorf("simplex main slice = %d, want 1 — the only slice in use", idx)
}
// Two same-band slices in DIFFERENT classes are not a split (SSB + FT8), so
// there is nothing to prefer and the radio's focus stands.
f3 := &Flex{pinnedSlice: -1, slices: map[int]*flexSlice{
0: mk(14_074_000, "DIGU", false, false),
1: mk(14_200_000, "USB", true, true),
}}
if idx, _ := f3.mainSliceLocked(); idx != 1 {
t.Errorf("SSB+FT8 main slice = %d, want 1 — that is not a split", idx)
}
}
// A slice picked IN OPSLOG wins over everything, including the split rule and
// the radio's own focus. Picking one on the radio does not move OpsLog.
func TestPinnedSliceWins(t *testing.T) {
mk := func(hz int64, mode string, active, tx bool) *flexSlice {
return &flexSlice{freqHz: hz, mode: mode, active: active, tx: tx, inUse: true}
}
f := &Flex{pinnedSlice: 1, slices: map[int]*flexSlice{
0: mk(14_100_000, "USB", true, false), // radio-focused
1: mk(14_200_000, "USB", false, true), // chosen in OpsLog
}}
if idx, _ := f.mainSliceLocked(); idx != 1 {
t.Errorf("main slice = %d — an explicit choice in OpsLog must win", idx)
}
// A pin on a slice that is no longer in use is not a choice any more: it
// must not strand OpsLog on a receiver that has been closed.
f.slices[1].inUse = false
if idx, _ := f.mainSliceLocked(); idx != 0 {
t.Errorf("main slice = %d — a closed slice cannot stay pinned", idx)
}
}