fix(tci): TUNE can be switched off again, and the meters watch it

Two faults with one cause: this radio does not echo 'tune:0,true'.

So the panel never knew a tune was running. The button stayed on TUNE and
every further press sent another START — there was no way to stop it from
here at all. The state is recorded when the command is sent now; whatever
the radio says afterwards still wins, it simply never says anything.

And the transmit meters were asked for only while t.tx, which a tune
carrier does not set: the radio reports tuning as its own state, not as a
transmission. So power and SWR sat at zero for the whole tune — the exact
carrier an operator holds a tune for in order to watch an SWR on. They now
follow PTT or TUNE, and the S-meter reads '—' under our own carrier
either way.
This commit is contained in:
2026-08-26 21:25:06 +02:00
parent 1201b44908
commit 980c54a616
3 changed files with 20 additions and 5 deletions
+3 -3
View File
@@ -274,15 +274,15 @@ export function TCIPanel({ onReportRST }: { onReportRST?: (rst: string) => void
There is no temperature: the protocol has no such command, and a There is no temperature: the protocol has no such command, and a
made-up figure on a transmitter is the kind somebody trusts. */} made-up figure on a transmitter is the kind somebody trusts. */}
<Card icon={Activity} title={t('tcip.meters')}> <Card icon={Activity} title={t('tcip.meters')}>
<MeterBar label="S-METER" value={st.tx ? 0 : sBar(st.smeter)} lo={0} hi={100} <MeterBar label="S-METER" value={st.tx || st.tuning ? 0 : sBar(st.smeter)} lo={0} hi={100}
accent="#16a34a" segColor={sSegColor} accent="#16a34a" segColor={sSegColor}
display={st.tx ? '—' : `${s.label} ${st.smeter} dBm`} display={st.tx || st.tuning ? '—' : `${s.label} ${st.smeter} dBm`}
onClick={() => { onClick={() => {
if (st.tx || !onReportRST) return; if (st.tx || !onReportRST) return;
onReportRST(sMeterRST(s.s, s.over, mode)); onReportRST(sMeterRST(s.s, s.over, mode));
}} }}
title={t('tcip.sMeterHint')} /> title={t('tcip.sMeterHint')} />
{st.tx && ( {(st.tx || st.tuning) && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<MeterBar label="PWR" value={st.tx_power_w} lo={0} hi={Math.max(10, Math.ceil(st.tx_power_w / 10) * 10)} <MeterBar label="PWR" value={st.tx_power_w} lo={0} hi={Math.max(10, Math.ceil(st.tx_power_w / 10) * 10)}
accent="#0ea5e9" display={`${st.tx_power_w.toFixed(1)} W`} /> accent="#0ea5e9" display={`${st.tx_power_w.toFixed(1)} W`} />
+5 -1
View File
@@ -309,7 +309,11 @@ func (t *TCI) ReadState() (RigState, error) {
// anything while it is keyed. Fired and forgotten from here — the answers // anything while it is keyed. Fired and forgotten from here — the answers
// arrive on the reader like everything else — and only while transmitting, // arrive on the reader like everything else — and only while transmitting,
// so a receiving station pays nothing for a meter nobody is watching. // so a receiving station pays nothing for a meter nobody is watching.
if t.tx { // Keyed by PTT **or** by TUNE. A tune carrier is exactly when the meters
// matter most — it is the carrier an operator is watching an SWR on — and
// asking only on t.tx left them at zero for the whole tune, because the
// radio reports tuning as its own state and not as a transmission.
if t.tx || t.panel.st.Tuning {
tx := t tx := t
go func() { go func() {
_ = tx.send("tx_power;") _ = tx.send("tx_power;")
+12 -1
View File
@@ -341,7 +341,18 @@ func (t *TCI) SetLock(on bool) error { return t.send(fmt.Sprintf("lock:0,%t;", o
// //
// It TRANSMITS, at tune_drive rather than at drive — which is the setting to // It TRANSMITS, at tune_drive rather than at drive — which is the setting to
// check before pressing it, and why the panel shows the two side by side. // check before pressing it, and why the panel shows the two side by side.
func (t *TCI) SetTune(on bool) error { return t.send(fmt.Sprintf("tune:0,%t;", on)) } //
// The state is recorded HERE rather than waited for. This radio does not echo
// "tune:0,true", so the panel had no way of knowing a tune was running: the
// button stayed on TUNE and every further press sent another START, which is
// why it could not be switched off again. Whatever the radio says afterwards
// still wins — it simply never says anything.
func (t *TCI) SetTune(on bool) error {
t.mu.Lock()
t.panel.st.Tuning = on
t.mu.Unlock()
return t.send(fmt.Sprintf("tune:0,%t;", on))
}
func clampTCIPct(v int) int { func clampTCIPct(v int) int {
if v < 0 { if v < 0 {