feat(tci): the transmit meters, and no temperature invented

TX_POWER and TX_SWR are read-only commands the radio answers when asked,
so they are asked for on each poll of a KEYED radio and not at all
otherwise: a receiving station pays nothing for meters nobody is watching.
Both appear next to the S-meter only while transmitting, because showing
them the rest of the time would show the last thing that happened as if it
were now.

An SWR of 0 draws as '—' rather than as 1.0. A perfect match on an antenna
nobody has measured is the one reading an operator should never be handed.

There is no temperature. The protocol's command list has TX_POWER and
TX_SWR and nothing thermal at all — so rather than leave the question
hanging, it is written down where the next person will look for it. A
temperature invented from something else, on a transmitter, is exactly the
kind of number somebody would trust.
This commit is contained in:
2026-08-26 21:14:58 +02:00
parent 01d0b8f22b
commit 1201b44908
7 changed files with 66 additions and 14 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ type Flex struct {
meterRawLogged bool // log the first raw meter-definition status once
txRawLogged bool // log the first raw transmit status once (field-name audit)
spotsEnabled bool // push cluster spots + manage the panadapter overlay
spotsEnabled bool // push cluster spots + manage the panadapter overlay
// foreignSpotSeen counts what probeForeignSpot has already reported, so a
// skimmer posting all evening cannot turn the log into its own transcript.
foreignSpotSeen int
+3 -3
View File
@@ -98,11 +98,11 @@ type Kenwood struct {
// Panel state — the K3/K4 control panel, see kenwood_panel.go. Read on the
// same serialised link as everything else, on a slow beat for the settings
// and every poll for the meters.
panel KenwoodTXState
panel KenwoodTXState
// The icon/status word, for working out which bit says "ATU in line" — see
// probeIcons. Kept so only CHANGES are logged.
lastIcons string
iconProbes int
lastIcons string
iconProbes int
panelCycle int
panelLoaded bool
metersLogged int
+12
View File
@@ -304,6 +304,18 @@ func (t *TCI) ReadState() (RigState, error) {
} else {
st.FreqHz = t.freqA
}
// The transmit meters are asked for, not pushed: TX_POWER and TX_SWR are
// read-only commands the radio answers when asked, and asking is only worth
// anything while it is keyed. Fired and forgotten from here — the answers
// arrive on the reader like everything else — and only while transmitting,
// so a receiving station pays nothing for a meter nobody is watching.
if t.tx {
tx := t
go func() {
_ = tx.send("tx_power;")
_ = tx.send("tx_swr;")
}()
}
st.Mode = tciModeToADIF(t.mode, t.digitalDefault)
if st.FreqHz > 0 {
st.Band = BandFromHz(st.FreqHz)
+19
View File
@@ -68,6 +68,17 @@ type TCIPanelState struct {
// several times a second while receiving.
SMeter int `json:"smeter"`
// TXPowerW and TXSWR are the transmit meters. READ-ONLY in TCI, and only
// answered while transmitting — asked for on every poll of a keyed radio,
// see ReadState.
//
// There is no temperature in this protocol. The command list has TX_POWER
// and TX_SWR and nothing thermal at all, so a temperature reading here would
// have to be invented, and an invented temperature on a transmitter is the
// kind of number somebody trusts.
TXPowerW float64 `json:"tx_power_w"`
TXSWR float64 `json:"tx_swr"`
// Modulations is what this radio will accept, straight from its own
// announcement, so the mode buttons are the radio's and not a guess.
Modulations []string `json:"modulations,omitempty"`
@@ -194,6 +205,14 @@ func (t *TCI) handlePanel(name string, get func(int) string, args string) bool {
if forRX0() {
p.Lock = yes(get(1))
}
case "tx_power":
if v, err := strconv.ParseFloat(strings.TrimSpace(get(0)), 64); err == nil {
p.TXPowerW = v
}
case "tx_swr":
if v, err := strconv.ParseFloat(strings.TrimSpace(get(0)), 64); err == nil {
p.TXSWR = v
}
case "rx_smeter":
if n, ok := num(get(1)); ok && forRX0() {
p.SMeter = n
+7 -7
View File
@@ -51,13 +51,13 @@ type YaesuTXState struct {
// NarrowSupported says the rig answered NA at all. A button that reports a
// state the radio never gave, and does nothing when pressed, is worse than
// an absent one: it looks like a fault in the radio.
NarrowSupported bool `json:"narrow_supported"`
MicGain int `json:"mic_gain"` // 0-100
AFGain int `json:"af_gain"` // 0-100
RFGain int `json:"rf_gain"` // 0-100
Squelch int `json:"squelch"` // 0-100
AGC string `json:"agc,omitempty"`
Preamp int `json:"preamp"` // 0=IPO, 1=AMP1, 2=AMP2
NarrowSupported bool `json:"narrow_supported"`
MicGain int `json:"mic_gain"` // 0-100
AFGain int `json:"af_gain"` // 0-100
RFGain int `json:"rf_gain"` // 0-100
Squelch int `json:"squelch"` // 0-100
AGC string `json:"agc,omitempty"`
Preamp int `json:"preamp"` // 0=IPO, 1=AMP1, 2=AMP2
// Antenna is the selected jack, 1-3, or 0 when the rig has no AN command —
// an FT-891 or FT-991A has a single socket and answers nothing. 0 is what
// tells the panel to draw no selector at all rather than a dead one.