fix: Binding to Gui Client to get proper values for cw delay and pitch

This commit is contained in:
2026-06-20 19:34:10 +02:00
parent 4a6ea45665
commit 4b5e2e0b72
+50 -5
View File
@@ -38,6 +38,7 @@ type Flex struct {
amp flexAmp // external amplifier (PowerGenius XL) state amp flexAmp // external amplifier (PowerGenius XL) state
txSetAt map[string]time.Time // status field → when WE last set it (ignore the radio's lagging echo briefly) txSetAt map[string]time.Time // status field → when WE last set it (ignore the radio's lagging echo briefly)
lastStateSig string // last logged derived-state signature (log only on change) lastStateSig string // last logged derived-state signature (log only on change)
boundClientID string // GUI client (SmartSDR) we bound to; "" until bound. Binding lets this non-GUI client receive GUI-tied data (CW pitch/speed, break-in delay, RF power).
// Live meters streamed over UDP (VITA-49). meterMeta is the definitions // Live meters streamed over UDP (VITA-49). meterMeta is the definitions
// pushed over TCP; meterVal the latest scaled values keyed by meter id. // pushed over TCP; meterVal the latest scaled values keyed by meter id.
@@ -174,6 +175,7 @@ func (f *Flex) Connect() error {
f.slices = map[int]*flexSlice{} f.slices = map[int]*flexSlice{}
f.meterVal = map[int]float64{} f.meterVal = map[int]float64{}
f.meterSub = map[int]bool{} f.meterSub = map[int]bool{}
f.boundClientID = "" // re-bind to the GUI client on each (re)connect
f.mu.Unlock() f.mu.Unlock()
debugLog.Printf("Flex: connected to %s:%d", host, port) debugLog.Printf("Flex: connected to %s:%d", host, port)
@@ -187,6 +189,7 @@ func (f *Flex) Connect() error {
f.send("sub amplifier all") // external amplifier (PowerGenius XL) operate/standby f.send("sub amplifier all") // external amplifier (PowerGenius XL) operate/standby
f.send("sub radio all") // radio-wide incl. interlock (TX/RX state) f.send("sub radio all") // radio-wide incl. interlock (TX/RX state)
f.send("sub cwx all") // CWX: the LIVE CW speed/pitch/break-in (transmit holds only a static default) f.send("sub cwx all") // CWX: the LIVE CW speed/pitch/break-in (transmit holds only a static default)
f.send("sub client all") // learn the GUI client (SmartSDR) so we can bind to it (below)
f.startMeters(conn) // open the UDP VITA-49 stream for live meters f.startMeters(conn) // open the UDP VITA-49 stream for live meters
if f.spotsEnabled { if f.spotsEnabled {
// Subscribe so the radio pushes existing spots (we learn their indices), // Subscribe so the radio pushes existing spots (we learn their indices),
@@ -379,17 +382,59 @@ func (f *Flex) handleStatus(payload string) {
f.tx.cwMonLevel = atoiDefault(val, f.tx.cwMonLevel) f.tx.cwMonLevel = atoiDefault(val, f.tx.cwMonLevel)
case "sidetone", "cw_sidetone": case "sidetone", "cw_sidetone":
f.tx.cwSidetone = val == "1" f.tx.cwSidetone = val == "1"
// NOTE: speed/pitch/break_in_delay also appear in the transmit // Once bound to the GUI client (see the client branch) the transmit
// object, but they are STALE defaults there — the LIVE values come // object carries the GUI client's LIVE CW values, so read them here
// from the cwx object (see the cwx branch). Parsing them here too // (and from cwx). Before binding these are the radio's static
// would let the stale value overwrite the correct one depending on // defaults — that was the "always 600 / 5" bug.
// status arrival order, so we deliberately ignore them here. case "speed", "cwl_speed", "cw_speed", "wpm", "cw_wpm":
f.tx.cwSpeed = atoiDefault(val, f.tx.cwSpeed)
case "pitch", "cwl_pitch", "cw_pitch":
f.tx.cwPitch = atoiDefault(val, f.tx.cwPitch)
case "break_in_delay", "cwl_delay", "cw_break_in_delay", "delay":
f.tx.cwBreakInDelay = atoiDefault(val, f.tx.cwBreakInDelay)
case "mic_level", "miclevel": case "mic_level", "miclevel":
f.tx.micLevel = atoiDefault(val, f.tx.micLevel) f.tx.micLevel = atoiDefault(val, f.tx.micLevel)
} }
} }
f.mu.Unlock() f.mu.Unlock()
} }
// Client object — list of connected clients. GUI clients (SmartSDR /
// Maestro) carry a client_id; non-GUI clients don't. We bind to the GUI
// client so the radio routes GUI-tied data (CW pitch/speed, break-in
// delay, RF power) to us. Logged so the exact field names are confirmable.
if len(fields) >= 1 && fields[0] == "client" {
debugLog.Printf("Flex: status %s", payload)
var clientID, program string
disconnected := false
for _, kv := range fields[1:] {
if kv == "disconnected" {
disconnected = true
continue
}
key, val, ok := splitKV(kv)
if !ok {
continue
}
switch key {
case "client_id":
clientID = val
case "program":
program = val
}
}
f.mu.Lock()
alreadyBound := f.boundClientID != ""
f.mu.Unlock()
lp := strings.ToLower(program)
isGUI := program == "" || strings.Contains(lp, "smartsdr") || strings.Contains(lp, "maestro")
if !disconnected && clientID != "" && !alreadyBound && isGUI {
f.mu.Lock()
f.boundClientID = clientID
f.mu.Unlock()
f.send("client bind client_id=" + clientID)
debugLog.Printf("Flex: bound to GUI client %s (program=%q)", clientID, program)
}
}
// CWX object — the LIVE CW keyer values (speed/pitch/break-in delay). // CWX object — the LIVE CW keyer values (speed/pitch/break-in delay).
// SmartSDR reads these here; the transmit object only carries a static // SmartSDR reads these here; the transmit object only carries a static
// default. Logged in full so we can confirm the exact field names. // default. Logged in full so we can confirm the exact field names.