From d4d22eb4b29416e1bf01237612c9299c0fd12b09 Mon Sep 17 00:00:00 2001 From: rouggy Date: Tue, 25 Aug 2026 23:51:28 +0200 Subject: [PATCH] =?UTF-8?q?fix(tci):=20the=20mode=20was=20never=20the=20ru?= =?UTF-8?q?le=20=E2=80=94=20ask=20the=20radio=20instead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Transmit over TCI is confirmed on a SunSDR: 80 W out of a 1 kHz tone at 70% of full scale into 80% drive, every request answered. Which makes the earlier rule wrong. 'Digital modes only' came from a real observation — SSB silent four times over, DIGU answering at once — but the mode was a coincidence. ExpertSDR3 has a transmit audio SOURCE, microphone or TCI, kept per mode, and it was on the microphone in SSB. Refusing SSB would have blocked the one thing a voice keyer exists for. So nothing is refused on the strength of the mode. The radio declares what it wants by asking for audio, 47 times a second when it wants any: key, wait for one request, and stop within two tenths of a second if none comes — naming the setting to change rather than theorising about it. That is better on three counts. It works in SSB when the source is set right, it cannot be wrong about a mode nobody thought to test (AM, FM, RTTY), and a misconfiguration costs a quarter-second of carrier instead of five seconds. --- internal/cat/tci_tx_probe.go | 61 ++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/internal/cat/tci_tx_probe.go b/internal/cat/tci_tx_probe.go index 3dd6b56..a376991 100644 --- a/internal/cat/tci_tx_probe.go +++ b/internal/cat/tci_tx_probe.go @@ -8,11 +8,13 @@ package cat // 1. The radio asks for audio only when the transmission is the CLIENT'S. With // the operator keying the microphone it sent 282 receive frames and nothing // else, over six seconds. -// 2. It asks only in a DIGITAL mode. Keyed from here in SSB: nothing, four -// times over. The same button in DIGU: chrono frames immediately. In SSB the -// modulator is wired to the microphone and no amount of network audio will -// reach it — which is also the honest answer to "why can I hear myself but -// not the tone". +// 2. It asks only when its TRANSMIT AUDIO SOURCE is TCI rather than the +// microphone. This first read as "digital modes only" — SSB produced +// nothing four times over, DIGU answered at once — but the mode was a +// coincidence: ExpertSDR3 keeps that source setting per mode, and it was on +// the microphone in SSB. Which is why nothing is refused on the strength of +// the mode: the radio is asked, and it answers by asking or by staying +// quiet. // 3. The chrono is a REQUEST, not a clock to follow. It carries no payload — // the message itself is the ask — and it names the size it wants in the // header's length field: 2048 samples, two channels interleaved, arriving @@ -118,9 +120,12 @@ func (t *TCI) setTXFeed(fn func(samples int) []byte) { // ProbeTXStream keys the radio, answers its chrono requests with a tone for the // given number of seconds, unkeys, and reports what happened. // -// INTO A DUMMY LOAD, AND IN A DIGITAL MODE. In SSB the radio takes the -// microphone and this produces nothing — which is a property of the radio, not -// a fault here, so it is said rather than worked around. +// INTO A DUMMY LOAD. Confirmed on a SunSDR: 80 W out of a 1 kHz tone at 70% of +// full scale into 80% drive. +// +// If the radio's transmit audio source is the microphone rather than TCI it +// will not ask for anything, and this stops within a fifth of a second and says +// which setting to change. func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { if seconds <= 0 { seconds = 5 @@ -144,10 +149,15 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { return fmt.Errorf("the radio refuses transmitting (tx_enable is false)") } if !tciDigitalMode(mode) { - // Refused rather than attempted. A pass in SSB keys the transmitter, - // produces nothing, and teaches nobody anything — and it is still a - // transmission. - return fmt.Errorf("the radio is in %s: transmit audio over TCI only reaches the modulator in a digital mode (DIGU, DIGL, or an FT8/data mode) — switch mode and try again", mode) + // A NOTE, not a refusal. + // + // The first experiments said "digital modes only": SSB produced nothing + // four times over, DIGU answered at once. That was a real observation + // and the wrong rule. ExpertSDR3 has a TRANSMIT AUDIO SOURCE — the + // microphone or TCI — and it was simply set to the microphone; the mode + // had nothing to do with it. Refusing SSB would have blocked the one + // thing a voice keyer exists for. + debugLog.Printf("TCI: TX PROBE — mode is %s, not a digital mode. That is fine IF ExpertSDR3's transmit audio source is set to TCI rather than the microphone; if it is not, the radio will not ask for audio and this stops straight away", mode) } t.audio.mu.Lock() @@ -206,7 +216,32 @@ func (t *TCI) ProbeTXStream(seconds int, toneHz float64) error { } }() - time.Sleep(time.Duration(seconds) * time.Second) + // Wait for the radio to ask, and give up quickly if it does not. + // + // The radio declares what it wants by requesting audio — 47 times a second + // when it wants any at all. So there is no need to decide in advance whether + // this mode or that setting will work: key, listen for one request, and if + // none comes in a fifth of a second, stop. That is a quarter of a second of + // carrier instead of five, and an answer that names the setting to change. + deadline := time.Now().Add(200 * time.Millisecond) + for time.Now().Before(deadline) { + t.audio.mu.Lock() + asked := t.audio.txSent > 0 + t.audio.mu.Unlock() + if asked { + break + } + time.Sleep(10 * time.Millisecond) + } + t.audio.mu.Lock() + started := t.audio.txSent + t.audio.mu.Unlock() + if started == 0 { + debugLog.Printf("TCI: TX PROBE — the radio never asked for audio; set ExpertSDR3's transmit audio source to TCI (it is on the microphone)") + return fmt.Errorf("the radio did not ask for any audio — set ExpertSDR3's transmit audio source to TCI instead of the microphone, then try again") + } + + time.Sleep(time.Duration(seconds)*time.Second - 200*time.Millisecond) t.audio.mu.Lock() sent, short := t.audio.txSent, t.audio.txShort