diff --git a/internal/cat/tci.go b/internal/cat/tci.go index 25cb1ec..f2f4927 100644 --- a/internal/cat/tci.go +++ b/internal/cat/tci.go @@ -66,7 +66,11 @@ type TCI struct { // txAllowedKnown keeps an OLDER ExpertSDR, or a TCI-compatible program that // never sends TX_ENABLE at all, from being treated as refusing: without a // word from the radio we key and let it decide. - txAllowed bool + txAllowed bool + // drive is the radio's transmit drive, 0-100. Kept because a quiet + // transmission has two possible causes — our level or the radio's — and a + // log that names both settles it in one line instead of an evening. + drive int txAllowedKnown bool lastSig string // last logged state signature (log only on change) @@ -462,6 +466,12 @@ func (t *TCI) handle(msg string) { t.noteTXTransition(t.tx) } } + case "drive": + if get(0) == "0" { + if v, err := strconv.Atoi(get(1)); err == nil { + t.drive = v + } + } case "tx_enable": if get(0) == "0" { allowed := get(1) == "true" diff --git a/internal/cat/tci_tx_probe.go b/internal/cat/tci_tx_probe.go index 3317429..3dd6b56 100644 --- a/internal/cat/tci_tx_probe.go +++ b/internal/cat/tci_tx_probe.go @@ -135,6 +135,7 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { t.mu.Lock() allowed, known, connected := t.txAllowed, t.txAllowedKnown, t.conn != nil mode := t.mode + drive := t.drive t.mu.Unlock() if !connected { return fmt.Errorf("not connected to the radio") @@ -161,9 +162,15 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { // sine restarted every frame is a click 47 times a second. phase := 0.0 step := 2 * math.Pi * toneHz / float64(rate) - // A quarter of full scale: enough to read on a meter, short of the level - // where the radio's own processing starts deciding things for us. - const amp = 0.25 + // Near full scale. + // + // A quarter was the first choice, out of caution, and the first real test + // showed exactly what that produces: a clean signal on the panadapter and a + // wattmeter that never moves. In a digital mode the radio expects a line + // level it can drive to full output — the POWER is set by its own drive + // control, not by how loud we send — so sending quietly just wastes the + // range. Short of 1.0 to leave room for the sine's peaks. + const amp = 0.7 const chans = 2 le := binary.LittleEndian t.setTXFeed(func(samples int) []byte { @@ -182,8 +189,10 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { }) defer t.setTXFeed(nil) - debugLog.Printf("TCI: TX PROBE starting — %d s of a %.0f Hz tone answered to the radio's own requests, mode %s, INTO A DUMMY LOAD", - seconds, toneHz, mode) + // The drive is in the line because it is half of "how much power came out". + // A tone at full scale into a drive of 15 is still 15% of the radio. + debugLog.Printf("TCI: TX PROBE starting — %d s of a %.0f Hz tone at %.0f%% of full scale, answered to the radio's own requests, mode %s, radio drive %d%%, INTO A DUMMY LOAD", + seconds, toneHz, amp*100, mode, drive) if err := t.SetPTT(true); err != nil { return fmt.Errorf("could not key the radio: %w", err)