feat: spots to panadapter for TCI radio
This commit is contained in:
+30
-3
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user