fix(tci): pick the sideband from the frequency just commanded
Clicking a spot sets the frequency, then the mode 150 ms later, and SetMode chose USB or LSB from t.freqA — the frequency the radio had ECHOED. From 7 MHz onto a 14 MHz spot that echo has often not landed, so the sideband was computed for the band being left: LSB on 20 m. Clicking the same spot again, the echo having arrived, gave USB. Reported from a SunSDR as 'the frequency is right, the mode is wrong until I click twice'. SetFrequency now records what was asked for and SetMode prefers it until the radio's own echo confirms the move. The server's 'protocol:' announcement is also kept instead of being filed under unhandled: it names the ExpertSDR and its TCI version, and panorama spots need TCI 1.5 — older servers accept the spot commands and silently drop them, which is indistinguishable from a broken logger. Now it says so.
This commit is contained in:
@@ -27,6 +27,12 @@ type TCI struct {
|
||||
|
||||
digitalDefault string // surfaced when the rig reports a digital mode (FT8/…)
|
||||
spotsEnabled bool // mirror cluster spots onto the TCI panorama
|
||||
// wantFreq is the frequency last COMMANDED and not yet echoed back, used to
|
||||
// pick the sideband before the radio has confirmed the move.
|
||||
wantFreq int64
|
||||
// What the server said it is, from its "protocol:" announcement.
|
||||
serverName string
|
||||
serverVersion string
|
||||
|
||||
// OnSpotClick is called when the user clicks one of our spots on the TCI
|
||||
// panorama (callsign + freq), so the host can fill the entry form. Set before
|
||||
@@ -334,6 +340,11 @@ func (t *TCI) ReadState() (RigState, error) {
|
||||
|
||||
// SetFrequency tunes VFO A (the main/RX VFO).
|
||||
func (t *TCI) SetFrequency(hz int64) error {
|
||||
// Remember what we ASKED for. SetMode reads it to choose the sideband, and
|
||||
// the radio's own echo can be a moment behind — see SetMode.
|
||||
t.mu.Lock()
|
||||
t.wantFreq = hz
|
||||
t.mu.Unlock()
|
||||
return t.send(fmt.Sprintf("vfo:0,0,%d;", hz))
|
||||
}
|
||||
|
||||
@@ -342,6 +353,17 @@ func (t *TCI) SetFrequency(hz int64) error {
|
||||
func (t *TCI) SetMode(mode string) error {
|
||||
t.mu.Lock()
|
||||
freq := t.freqA
|
||||
// Prefer the frequency we just COMMANDED over the one the radio has echoed.
|
||||
//
|
||||
// Clicking a spot sets the frequency and then the mode, and the sideband is
|
||||
// chosen from the frequency (below 10 MHz → LSB). Read from the echo, that
|
||||
// is the frequency we were on BEFORE the click whenever the echo has not
|
||||
// landed yet: a 14 MHz spot clicked from 7 MHz got LSB, and clicking the same
|
||||
// spot again — now that the echo has arrived — got USB. Reported from a
|
||||
// SunSDR as "the frequency is right, the mode is wrong until I click twice".
|
||||
if t.wantFreq > 0 {
|
||||
freq = t.wantFreq
|
||||
}
|
||||
t.mu.Unlock()
|
||||
m := adifToTCIMode(mode, freq)
|
||||
if m == "" {
|
||||
@@ -493,6 +515,18 @@ func (t *TCI) handle(msg string) {
|
||||
switch lower {
|
||||
case "device":
|
||||
t.device = strings.TrimSpace(args)
|
||||
// The server's own announcement: "protocol:ExpertSDR3,1.9;" — its name and
|
||||
// the TCI version it speaks. Worth keeping rather than filing under
|
||||
// "unhandled": panorama spots need a version that HAS the spot command, and
|
||||
// without this an operator on an older ExpertSDR sees nothing on the
|
||||
// waterfall and nothing anywhere saying why.
|
||||
case "protocol":
|
||||
t.serverName, t.serverVersion = get(0), get(1)
|
||||
debugLog.Printf("TCI: server is %s, TCI %s", t.serverName, t.serverVersion)
|
||||
if t.spotsEnabled && tciSpotsUnsupported(t.serverVersion) {
|
||||
debugLog.Printf("TCI: this server speaks TCI %s — panorama spots need 1.5 or later, so they will not appear",
|
||||
t.serverVersion)
|
||||
}
|
||||
// The radio ANNOUNCES its audio format at connect —
|
||||
// "audio_stream_sample_type:float32" and "audio_stream_channels:2" — which
|
||||
// is better evidence than anything derived from a frame, and it arrives
|
||||
@@ -516,6 +550,10 @@ func (t *TCI) handle(msg string) {
|
||||
switch get(1) {
|
||||
case "0":
|
||||
t.freqA = hz
|
||||
// The radio has caught up: from here the echo IS the truth.
|
||||
if t.wantFreq != 0 && absInt64(hz-t.wantFreq) < 100 {
|
||||
t.wantFreq = 0
|
||||
}
|
||||
case "1":
|
||||
t.freqB = hz
|
||||
}
|
||||
@@ -651,3 +689,18 @@ func adifToTCIMode(mode string, freqHz int64) string {
|
||||
return "digu"
|
||||
}
|
||||
}
|
||||
|
||||
// tciSpotsUnsupported reports whether a TCI version predates the spot commands.
|
||||
//
|
||||
// SPOT / SPOT_DELETE / SPOT_CLEAR arrived in TCI 1.5. An older ExpertSDR accepts
|
||||
// the connection, answers frequency and mode perfectly, and silently ignores
|
||||
// every spot — which is indistinguishable from a bug in the logger unless
|
||||
// somebody says so. Anything unparseable is treated as supported: refusing to
|
||||
// draw on a doubt would be the worse mistake.
|
||||
func tciSpotsUnsupported(version string) bool {
|
||||
var maj, min int
|
||||
if n, err := fmt.Sscanf(strings.TrimSpace(version), "%d.%d", &maj, &min); n < 2 || err != nil {
|
||||
return false
|
||||
}
|
||||
return maj < 1 || (maj == 1 && min < 5)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package cat
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTCISpotsUnsupported(t *testing.T) {
|
||||
// SPOT arrived in TCI 1.5. The SunSDR2 PRO report that prompted this was an
|
||||
// ExpertSDR announcing 1.3 — spots accepted and silently dropped.
|
||||
for _, c := range []struct {
|
||||
version string
|
||||
old bool
|
||||
}{
|
||||
{"1.3", true},
|
||||
{"1.4", true},
|
||||
{"1.5", false},
|
||||
{"1.9", false},
|
||||
{"2.0", false},
|
||||
{"", false}, // unparseable → assume it works
|
||||
{"weird", false}, // refusing to draw on a doubt is the worse mistake
|
||||
} {
|
||||
if got := tciSpotsUnsupported(c.version); got != c.old {
|
||||
t.Errorf("tciSpotsUnsupported(%q) = %v, want %v", c.version, got, c.old)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user