TX {(st.tx_freq_hz / 1e6).toFixed(3)}
diff --git a/frontend/src/components/YaesuPanel.tsx b/frontend/src/components/YaesuPanel.tsx
index d6022b5..38dc3f2 100644
--- a/frontend/src/components/YaesuPanel.tsx
+++ b/frontend/src/components/YaesuPanel.tsx
@@ -18,7 +18,8 @@ type YaesuState = {
s_meter: number; power_meter: number; swr_meter: number;
rf_power: number; mic_gain: number; af_gain: number; rf_gain: number; squelch: number;
agc?: string; preamp: number; att: number; antenna: number;
- nb: boolean; nr: boolean; nr_level: number; narrow: boolean; vox: boolean;
+ nb: boolean; nr: boolean; nr_level: number; narrow: boolean; narrow_supported?: boolean; vox: boolean;
+ max_power?: number;
split_tx_hz?: number; key_speed?: number; break_in?: boolean; swr?: number; power_w?: number;
};
@@ -445,7 +446,13 @@ export function YaesuPanel({ onReportRST, onKeySpeed }: {
push('nb', !view.nb, () => SetYaesuNB(!view.nb))} label="NB" />
push('nr', !view.nr, () => SetYaesuNR(!view.nr))} label="DNR" />
- push('narrow', !view.narrow, () => SetYaesuNarrow(!view.narrow))} label="NAR" />
+ {/* NAR is the narrow IF filter. Shown only when the rig ANSWERED NA:
+ a button that does nothing when pressed reads as a fault in the
+ radio, which is how it was reported from an FTDX101. */}
+ {view.narrow_supported && (
+ push('narrow', !view.narrow, () => SetYaesuNarrow(!view.narrow))}
+ label="NAR" title={t('yaesu.narrowHint')} />
+ )}
{/* 1-15 on the rig, shown as-is rather than rescaled to a percentage:
@@ -462,7 +469,7 @@ export function YaesuPanel({ onReportRST, onKeySpeed }: {
{/* Watts, not a percentage: the rig reports and takes watts, and a
percentage would be a second unit to reconcile every time. */}
- push('rf_power', v, () => SetYaesuPower(v))} />
{view.rf_power}W
diff --git a/frontend/src/lib/i18n.tsx b/frontend/src/lib/i18n.tsx
index 0c7b533..182df1a 100644
--- a/frontend/src/lib/i18n.tsx
+++ b/frontend/src/lib/i18n.tsx
@@ -109,7 +109,7 @@ const en: Dict = {
'settings.pane.map1': 'Map — great-circle + beam', 'settings.pane.map2': 'Map — locator (street)',
'settings.pane.cluster': 'Cluster spots', 'settings.pane.worked': 'Worked before',
'settings.pane.recent': 'Recent QSOs', 'settings.pane.netcontrol': 'Net control', 'settings.pane.decodes': 'FT decodes',
- 'settings.pane.flex': 'Flex Console', 'settings.pane.icom': 'Icom Console', 'settings.pane.yaesu': 'Yaesu Console', 'yaesu.notConnected': 'Yaesu CAT not connected', 'yaesu.meters': 'Meters', 'yaesu.bandMode': 'Band & mode', 'yaesu.receive': 'Receive', 'yaesu.noiseFilter': 'Noise & filter', 'yaesu.transmit': 'Transmit', 'yaesu.refresh': 'Refresh', 'yaesu.tuneHint': 'Start an antenna-tuner cycle', 'yaesu.sToRst': 'Click to fill the RST sent', 'yaesu.sidebandHint': 'Click to select this mode; click again to switch sideband (U/L)', 'yaesu.splitUpHint': 'Transmit this far above the receive frequency, and turn split on', 'yaesu.txOn': 'TX', 'yaesu.cw': 'CW', 'yaesu.breakInHint': 'Break-in: the rig switches to receive between characters', 'yaesu.zinHint': 'Zero-in: retune so the station you hear lands on your CW pitch',
+ 'settings.pane.flex': 'Flex Console', 'settings.pane.icom': 'Icom Console', 'settings.pane.yaesu': 'Yaesu Console', 'yaesu.notConnected': 'Yaesu CAT not connected', 'yaesu.meters': 'Meters', 'yaesu.bandMode': 'Band & mode', 'yaesu.receive': 'Receive', 'yaesu.noiseFilter': 'Noise & filter', 'yaesu.transmit': 'Transmit', 'yaesu.refresh': 'Refresh', 'yaesu.narrowHint': 'Narrow IF filter (NAR) — tightens the receive bandwidth. Only shown when the radio answers the command.', 'yaesu.tuneHint': 'Start an antenna-tuner cycle', 'yaesu.sToRst': 'Click to fill the RST sent', 'yaesu.sidebandHint': 'Click to select this mode; click again to switch sideband (U/L)', 'yaesu.splitUpHint': 'Transmit this far above the receive frequency, and turn split on', 'yaesu.txOn': 'TX', 'yaesu.cw': 'CW', 'yaesu.breakInHint': 'Break-in: the rig switches to receive between characters', 'yaesu.zinHint': 'Zero-in: retune so the station you hear lands on your CW pitch',
'theme.auto': 'Auto (system)', 'theme.light-warm': 'Warm light', 'theme.light-cool': 'Cool light',
'theme.light-sage': 'Sage light', 'theme.light-nordic': 'Nordic light', 'theme.sahara': 'Sahara', 'theme.dim-slate': 'Dim slate', 'theme.dark-warm': 'Warm dark',
'theme.dark-graphite': 'Graphite dark', 'theme.dark-indigo': 'Indigo', 'theme.dark-teal': 'Ocean', 'theme.dark-plum': 'Plum', 'theme.high-contrast': 'High contrast',
@@ -596,7 +596,7 @@ const fr: Dict = {
'settings.pane.map1': 'Carte — orthodromie + faisceau', 'settings.pane.map2': 'Carte — locator (rue)',
'settings.pane.cluster': 'Spots cluster', 'settings.pane.worked': 'Déjà contactés',
'settings.pane.recent': 'QSO récents', 'settings.pane.netcontrol': 'Gestion de net', 'settings.pane.decodes': 'Decodes FT',
- 'settings.pane.flex': 'Flex Console', 'settings.pane.icom': 'Icom Console', 'settings.pane.yaesu': 'Yaesu Console', 'yaesu.notConnected': 'CAT Yaesu non connecté', 'yaesu.meters': 'Mesures', 'yaesu.bandMode': 'Bande et mode', 'yaesu.receive': 'Réception', 'yaesu.noiseFilter': 'Bruit et filtre', 'yaesu.transmit': 'Émission', 'yaesu.refresh': 'Actualiser', 'yaesu.tuneHint': "Lancer un cycle d'accord d'antenne", 'yaesu.sToRst': 'Cliquer pour remplir le RST envoyé', 'yaesu.sidebandHint': 'Cliquer pour choisir ce mode ; recliquer pour changer de bande latérale (U/L)', 'yaesu.splitUpHint': "Émettre à cette distance au-dessus de la fréquence de réception, et activer le split", 'yaesu.txOn': 'TX', 'yaesu.cw': 'CW', 'yaesu.breakInHint': "Break-in : la radio repasse en réception entre les caractères", 'yaesu.zinHint': "Zéro-in : réaccorde pour que la station entendue tombe sur votre note CW",
+ 'settings.pane.flex': 'Flex Console', 'settings.pane.icom': 'Icom Console', 'settings.pane.yaesu': 'Yaesu Console', 'yaesu.notConnected': 'CAT Yaesu non connecté', 'yaesu.meters': 'Mesures', 'yaesu.bandMode': 'Bande et mode', 'yaesu.receive': 'Réception', 'yaesu.noiseFilter': 'Bruit et filtre', 'yaesu.transmit': 'Émission', 'yaesu.refresh': 'Actualiser', 'yaesu.narrowHint': "Filtre FI étroit (NAR) — resserre la bande passante de réception. N'apparaît que si la radio répond à la commande.", 'yaesu.tuneHint': "Lancer un cycle d'accord d'antenne", 'yaesu.sToRst': 'Cliquer pour remplir le RST envoyé', 'yaesu.sidebandHint': 'Cliquer pour choisir ce mode ; recliquer pour changer de bande latérale (U/L)', 'yaesu.splitUpHint': "Émettre à cette distance au-dessus de la fréquence de réception, et activer le split", 'yaesu.txOn': 'TX', 'yaesu.cw': 'CW', 'yaesu.breakInHint': "Break-in : la radio repasse en réception entre les caractères", 'yaesu.zinHint': "Zéro-in : réaccorde pour que la station entendue tombe sur votre note CW",
'theme.auto': 'Auto (système)', 'theme.light-warm': 'Clair chaud', 'theme.light-cool': 'Clair froid',
'theme.light-sage': 'Clair sauge', 'theme.light-nordic': 'Clair nordique', 'theme.sahara': 'Sahara', 'theme.dim-slate': 'Ardoise tamisé', 'theme.dark-warm': 'Sombre chaud',
'theme.dark-graphite': 'Sombre graphite', 'theme.dark-indigo': 'Indigo', 'theme.dark-teal': 'Océan', 'theme.dark-plum': 'Prune', 'theme.high-contrast': 'Contraste élevé',
diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts
index 18ccdeb..1b3db30 100644
--- a/frontend/wailsjs/go/models.ts
+++ b/frontend/wailsjs/go/models.ts
@@ -1220,6 +1220,8 @@ export namespace cat {
power_meter: number;
swr_meter: number;
rf_power: number;
+ max_power: number;
+ narrow_supported: boolean;
mic_gain: number;
af_gain: number;
rf_gain: number;
@@ -1255,6 +1257,8 @@ export namespace cat {
this.power_meter = source["power_meter"];
this.swr_meter = source["swr_meter"];
this.rf_power = source["rf_power"];
+ this.max_power = source["max_power"];
+ this.narrow_supported = source["narrow_supported"];
this.mic_gain = source["mic_gain"];
this.af_gain = source["af_gain"];
this.rf_gain = source["rf_gain"];
diff --git a/internal/cat/yaesu_panel.go b/internal/cat/yaesu_panel.go
index 2969039..348821e 100644
--- a/internal/cat/yaesu_panel.go
+++ b/internal/cat/yaesu_panel.go
@@ -43,7 +43,15 @@ type YaesuTXState struct {
PowerMeter int `json:"power_meter"` // 0-100, TX only
SWRMeter int `json:"swr_meter"` // 0-100, TX only
- RFPower int `json:"rf_power"` // watts
+ RFPower int `json:"rf_power"` // watts
+ // MaxPower is what this model can actually be set to, so the slider stops
+ // where the radio does. An FTDX101MP is a 200 W transmitter and its operator
+ // could not ask for more than 100 W — the slider, not the rig, was the limit.
+ MaxPower int `json:"max_power"`
+ // 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
@@ -110,9 +118,29 @@ func (y *Yaesu) YaesuState() YaesuTXState {
st := y.panel
st.Available = y.port != nil
st.Model = y.model
+ st.MaxPower = yaesuMaxPower(y.model, st.RFPower)
return st
}
+// yaesuMaxPower is how far the power slider may go on this model.
+//
+// The rig itself is never asked — there is no CAT command for "how much power
+// can you make" — so it comes from the model name, with one safeguard: a rig
+// REPORTING more than the table expects is believed, because it has just proved
+// what it can do. Anything unknown gets 100 W, which is the family default and
+// errs towards asking for too little.
+func yaesuMaxPower(model string, reported int) int {
+ max := 100
+ switch {
+ case strings.Contains(model, "101MP"), strings.Contains(model, "5000"), strings.Contains(model, "9000"):
+ max = 200
+ }
+ if reported > max {
+ max = reported
+ }
+ return max
+}
+
// readPanel refreshes the meters, and the settings on the slower beat. Called
// from ReadState with the mutex HELD, so it shares the same serialised link.
func (y *Yaesu) readPanel(mode string, split bool, txHz int64) {
@@ -226,8 +254,15 @@ func (y *Yaesu) readPanelSettings() {
if v, ok := y.askNum("PC;", "PC", 3); ok {
y.panel.RFPower = v // watts, not a 0-255 scale
}
+ // MIC GAIN IS 0-100, not 0-255.
+ //
+ // It was read through the 0-255 scale like the audio gains, so an FTDX101
+ // set to 80 showed 38 — and worse, writing sent 80 → 204, outside the range
+ // the rig accepts, so the setting was refused and the slider sprang back to
+ // where it had been. Reported from a real FTDX101; the CAT reference gives
+ // MG000-100 for the whole current family.
if v, ok := y.askNum("MG;", "MG", 3); ok {
- y.panel.MicGain = scale255(v)
+ y.panel.MicGain = clampInt(v, 0, 100)
}
if v, ok := y.askNum("AG0;", "AG0", 3); ok {
y.panel.AFGain = scale255(v)
@@ -276,6 +311,7 @@ func (y *Yaesu) readPanelSettings() {
}
if v, ok := y.askNum("NA0;", "NA0", 1); ok {
y.panel.Narrow = v != 0
+ y.panel.NarrowSupported = true
}
if v, ok := y.askNum("VX;", "VX", 1); ok {
y.panel.VOX = v != 0
@@ -356,7 +392,7 @@ func (y *Yaesu) SetYaesuPower(w int) error {
}
func (y *Yaesu) SetYaesuMicGain(p int) error {
- return y.setAndRefresh(fmt.Sprintf("MG%03d;", from100(p)))
+ return y.setAndRefresh(fmt.Sprintf("MG%03d;", clampInt(p, 0, 100)))
}
func (y *Yaesu) SetYaesuAFGain(p int) error {