feat: spots to panadapter for TCI radio

This commit is contained in:
2026-07-03 15:30:53 +02:00
parent fa7df57435
commit 8ccad7ca65
4 changed files with 48 additions and 7 deletions
+30 -3
View File
@@ -25,6 +25,7 @@ type TCI struct {
port int
digitalDefault string // surfaced when the rig reports a digital mode (FT8/…)
spotsEnabled bool // mirror cluster spots onto the TCI panorama
mu sync.Mutex // guards conn + writes + state
conn *websocket.Conn
@@ -44,12 +45,13 @@ type TCI struct {
const tciDefaultPort = 40001
// NewTCI builds a TCI backend for the given host/port. digitalDefault is the
// mode surfaced when the radio reports a generic digital modulation.
func NewTCI(host string, port int, digitalDefault string) *TCI {
// mode surfaced when the radio reports a generic digital modulation; spots turns
// on mirroring OpsLog's cluster spots onto the TCI panorama.
func NewTCI(host string, port int, digitalDefault string, spots bool) *TCI {
if port <= 0 || port > 65535 {
port = tciDefaultPort
}
return &TCI{host: strings.TrimSpace(host), port: port, digitalDefault: strings.TrimSpace(digitalDefault)}
return &TCI{host: strings.TrimSpace(host), port: port, digitalDefault: strings.TrimSpace(digitalDefault), spotsEnabled: spots}
}
func (t *TCI) Name() string { return "tci" }
@@ -79,9 +81,34 @@ func (t *TCI) Connect() error {
t.mu.Unlock()
debugLog.Printf("TCI: connected to %s", url)
go t.reader(conn)
if t.spotsEnabled {
_ = t.send("spot_clear;") // drop any leftover spots from a previous session
}
return nil
}
// SendSpot mirrors a cluster spot onto the TCI panorama (implements Spotter).
// The radio replaces a spot that has the same callsign, so re-spotting updates
// it in place. No-op when spot mirroring is disabled.
func (t *TCI) SendSpot(s SpotInfo) error {
if !t.spotsEnabled {
return nil
}
call := strings.TrimSpace(s.Callsign)
if call == "" || s.FreqHz <= 0 {
return nil
}
color := strings.TrimSpace(s.Color)
if color == "" {
color = "#FFFFA500" // opaque orange default
}
color = "0x" + strings.TrimPrefix(color, "#")
mode := strings.ToLower(strings.TrimSpace(s.Mode))
// Commas/semicolons would break TCI's comma-separated argument parsing.
text := strings.NewReplacer(",", " ", ";", " ").Replace(s.Comment)
return t.send(fmt.Sprintf("spot:%s,%s,%d,%s,%s;", call, mode, s.FreqHz, color, text))
}
// Disconnect closes the WebSocket; the reader goroutine then exits.
func (t *TCI) Disconnect() {
t.mu.Lock()