From 4b5e2e0b72c8d4e03163dbfe68019bdaa7268734 Mon Sep 17 00:00:00 2001 From: rouggy Date: Sat, 20 Jun 2026 19:34:10 +0200 Subject: [PATCH] fix: Binding to Gui Client to get proper values for cw delay and pitch --- internal/cat/flex.go | 55 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/internal/cat/flex.go b/internal/cat/flex.go index 984f6a5..bb78d29 100644 --- a/internal/cat/flex.go +++ b/internal/cat/flex.go @@ -38,6 +38,7 @@ type Flex struct { 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) 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 // 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.meterVal = map[int]float64{} f.meterSub = map[int]bool{} + f.boundClientID = "" // re-bind to the GUI client on each (re)connect f.mu.Unlock() 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 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 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 if f.spotsEnabled { // 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) case "sidetone", "cw_sidetone": f.tx.cwSidetone = val == "1" - // NOTE: speed/pitch/break_in_delay also appear in the transmit - // object, but they are STALE defaults there — the LIVE values come - // from the cwx object (see the cwx branch). Parsing them here too - // would let the stale value overwrite the correct one depending on - // status arrival order, so we deliberately ignore them here. + // Once bound to the GUI client (see the client branch) the transmit + // object carries the GUI client's LIVE CW values, so read them here + // (and from cwx). Before binding these are the radio's static + // defaults — that was the "always 600 / 5" bug. + 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": f.tx.micLevel = atoiDefault(val, f.tx.micLevel) } } 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). // SmartSDR reads these here; the transmit object only carries a static // default. Logged in full so we can confirm the exact field names.