fix(icom): sidebands by name, model-aware controls, address 0x00
From an IC-7300 report, four faults and one addition. USB and LSB could not be commanded at all: modeCode knew 'SSB' — which resolves the sideband from the band — and answered 'unsupported mode' to the sideband names themselves. So an operator wanting USB on 40 m had no way to say it, from the console or from anywhere else. They are separate buttons now, and a rig reporting the folded ADIF 'SSB' still lights the side its frequency implies. The console offered controls the radio does not have: ANT1/ANT2 on a rig with one socket, and a PSK button every non-7610-class Icom NAKs. Both now follow the model, as the band buttons and attenuator steps already did. Mic gain stops being phone-only — on USB-D it still sets what the radio transmits at, so an operator who lives in FT8 had none. CI-V address 0x00 was refused by a 'n > 0' test and silently replaced by the IC-7610 default; an EMPTY setting is what means unconfigured, so the parse error decides now, not the value. Plus the 60 m band button that was missing.
This commit is contained in:
@@ -8330,8 +8330,15 @@ func (a *App) GetCATSettings() (CATSettings, error) {
|
|||||||
if n, _ := strconv.Atoi(m[keyCATIcomBaud]); n > 0 {
|
if n, _ := strconv.Atoi(m[keyCATIcomBaud]); n > 0 {
|
||||||
out.IcomBaud = n
|
out.IcomBaud = n
|
||||||
}
|
}
|
||||||
if n, _ := strconv.Atoi(m[keyCATIcomAddr]); n > 0 && n <= 0xFF {
|
// 0x00 is a real CI-V address an operator may need (a bare interface, a rig
|
||||||
out.IcomAddr = n
|
// left at its factory broadcast address), and "> 0" silently sent them back
|
||||||
|
// to the IC-7610 default with no way to say what they meant. An EMPTY
|
||||||
|
// setting is what means "never configured" — so the error is what decides,
|
||||||
|
// not the value.
|
||||||
|
if v := strings.TrimSpace(m[keyCATIcomAddr]); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil && n >= 0 && n <= 0xFF {
|
||||||
|
out.IcomAddr = n
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if out.Backend == "" {
|
if out.Backend == "" {
|
||||||
out.Backend = "omnirig"
|
out.Backend = "omnirig"
|
||||||
|
|||||||
+10
-2
@@ -3,10 +3,18 @@
|
|||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"date": "",
|
"date": "",
|
||||||
"en": [
|
"en": [
|
||||||
"The band matrix’s DIG row is now a rotation: click it and it answers for FT8, then FT4, then each digital mode your mode list holds — in YOUR order — then back to DIG. One row per digital mode would be the honest layout and there is no height for it beside the other widgets, so the row keeps its place and changes what it says. The label column is sized once for the longest mode it can show, so the matrix never shifts as the rotation comes round to RTTY."
|
"The band matrix’s DIG row is now a rotation: click it and it answers for FT8, then FT4, then each digital mode your mode list holds — in YOUR order — then back to DIG. One row per digital mode would be the honest layout and there is no height for it beside the other widgets, so the row keeps its place and changes what it says. The label column is sized once for the longest mode it can show, so the matrix never shifts as the rotation comes round to RTTY.",
|
||||||
|
"Icom console: LSB and USB are separate buttons and can finally be commanded by name — the single SSB button resolved the sideband from the band, so there was no way to ask an IC-7300 for USB on 40 m. A rig reporting the folded “SSB” still lights the side its frequency implies.",
|
||||||
|
"Icom console: a 60 m band button, and the antenna and PSK controls only appear on radios that have them. An IC-7300 has one antenna socket and no native PSK mode, so ANT1/ANT2 could only ever disagree with its front panel and the PSK button was dead furniture.",
|
||||||
|
"Icom console: the mic gain is no longer hidden outside phone modes — on USB-D it still sets what the radio transmits at, and an operator who lives in FT8 had none at all.",
|
||||||
|
"Icom CI-V: address 0x00 can be chosen. It was silently refused and replaced by the IC-7610 default, with no way to say what was meant."
|
||||||
],
|
],
|
||||||
"fr": [
|
"fr": [
|
||||||
"La ligne DIG de la matrice devient une rotation : un clic et elle répond pour FT8, puis FT4, puis chaque mode numérique de votre liste — dans VOTRE ordre — puis retour à DIG. Une ligne par mode numérique serait la mise en page honnête et la hauteur manque à côté des autres widgets : la ligne garde donc sa place et change ce qu’elle dit. La colonne des libellés est dimensionnée une fois pour le plus long mode qu’elle peut afficher : la matrice ne bouge donc plus quand la rotation arrive sur RTTY."
|
"La ligne DIG de la matrice devient une rotation : un clic et elle répond pour FT8, puis FT4, puis chaque mode numérique de votre liste — dans VOTRE ordre — puis retour à DIG. Une ligne par mode numérique serait la mise en page honnête et la hauteur manque à côté des autres widgets : la ligne garde donc sa place et change ce qu’elle dit. La colonne des libellés est dimensionnée une fois pour le plus long mode qu’elle peut afficher : la matrice ne bouge donc plus quand la rotation arrive sur RTTY.",
|
||||||
|
"Console Icom : LSB et USB sont deux boutons distincts et peuvent enfin être demandés par leur nom — le bouton SSB unique déduisait la bande latérale de la fréquence, impossible donc de demander l’USB à un IC-7300 sur 40 m. Une radio qui annonce le « SSB » générique allume malgré tout le côté que sa fréquence implique.",
|
||||||
|
"Console Icom : un bouton de bande 60 m, et les commandes antenne et PSK n’apparaissent que sur les radios qui en disposent. Un IC-7300 n’a qu’une prise d’antenne et pas de mode PSK natif : ANT1/ANT2 ne pouvait que contredire sa face avant, et le bouton PSK était un meuble mort.",
|
||||||
|
"Console Icom : le gain micro n’est plus masqué hors des modes phonie — en USB-D il règle toujours le niveau d’émission, et un opérateur qui vit en FT8 n’en avait aucun.",
|
||||||
|
"CI-V Icom : l’adresse 0x00 peut être choisie. Elle était refusée en silence et remplacée par le défaut IC-7610, sans moyen de dire ce que l’on voulait."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,7 +53,13 @@ const ZERO: IcomState = {
|
|||||||
type Band = { l: string; hz: number };
|
type Band = { l: string; hz: number };
|
||||||
|
|
||||||
const HF_BANDS: Band[] = [
|
const HF_BANDS: Band[] = [
|
||||||
{ l: '160', hz: 1_840_000 }, { l: '80', hz: 3_750_000 }, { l: '40', hz: 7_100_000 },
|
{ l: '160', hz: 1_840_000 }, { l: '80', hz: 3_750_000 },
|
||||||
|
// 60 m: the middle of the IARU Region 1 allocation (5351.5-5366.5 kHz), which
|
||||||
|
// every 60 m-capable rig can display. Where the band is channelised (the US)
|
||||||
|
// the operator moves to their channel from here — the button is a way onto
|
||||||
|
// the band, not a claim about what may be transmitted on it.
|
||||||
|
{ l: '60', hz: 5_354_000 },
|
||||||
|
{ l: '40', hz: 7_100_000 },
|
||||||
{ l: '30', hz: 10_130_000 }, { l: '20', hz: 14_150_000 }, { l: '17', hz: 18_130_000 },
|
{ l: '30', hz: 10_130_000 }, { l: '20', hz: 14_150_000 }, { l: '17', hz: 18_130_000 },
|
||||||
{ l: '15', hz: 21_250_000 }, { l: '12', hz: 24_950_000 }, { l: '10', hz: 28_400_000 },
|
{ l: '15', hz: 21_250_000 }, { l: '12', hz: 24_950_000 }, { l: '10', hz: 28_400_000 },
|
||||||
];
|
];
|
||||||
@@ -78,9 +84,36 @@ function bandsFor(model?: string): Band[] {
|
|||||||
return [...HF_BANDS, B6];
|
return [...HF_BANDS, B6];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode buttons for the console (like RS-BA1's row). SetCATMode picks USB/LSB for
|
// Mode buttons for the console (like RS-BA1's row).
|
||||||
// SSB by frequency and the rig's data variant for digital modes.
|
//
|
||||||
const MODES = ['SSB', 'CW', 'RTTY', 'PSK', 'AM', 'FM', 'DATA'];
|
// LSB and USB by NAME, not one "SSB" button that resolves by band: the band
|
||||||
|
// convention is right for a logged mode and useless when the operator means
|
||||||
|
// "put this radio in USB on 40 m", which the console could not express at all.
|
||||||
|
//
|
||||||
|
// PSK is native only on the 7610/7760/7851 class; every other rig NAKs 0x12, so
|
||||||
|
// there the button is dead furniture — see modesFor. Soundcard PSK31 rides on
|
||||||
|
// DATA, which every rig can do.
|
||||||
|
const MODES_BASE = ['LSB', 'USB', 'CW', 'RTTY', 'AM', 'FM', 'DATA'];
|
||||||
|
|
||||||
|
function hasNativePSK(model?: string): boolean {
|
||||||
|
const m = (model ?? '').toUpperCase();
|
||||||
|
return m.includes('7610') || m.includes('7760') || m.includes('7851') ||
|
||||||
|
m.includes('7800') || m.includes('7700');
|
||||||
|
}
|
||||||
|
|
||||||
|
function modesFor(model?: string): string[] {
|
||||||
|
if (!hasNativePSK(model)) return MODES_BASE;
|
||||||
|
return [...MODES_BASE.slice(0, 4), 'PSK', ...MODES_BASE.slice(4)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which radios actually have an antenna selector on the CI-V command (0x12).
|
||||||
|
// An IC-7300 has ONE socket: offering it ANT1/ANT2 was two buttons that could
|
||||||
|
// only ever disagree with the front panel.
|
||||||
|
function hasAntennaSelector(model?: string): boolean {
|
||||||
|
const m = (model ?? '').toUpperCase();
|
||||||
|
return m.includes('7610') || m.includes('7760') || m.includes('7851') ||
|
||||||
|
m.includes('7800') || m.includes('7700') || m.includes('9700');
|
||||||
|
}
|
||||||
|
|
||||||
// Attenuator steps are MODEL-dependent even though the CI-V command (0x11) is the
|
// Attenuator steps are MODEL-dependent even though the CI-V command (0x11) is the
|
||||||
// same: the value byte is the dB. The IC-7610 (and 7700/7800/7851) have a 6/12/18
|
// same: the value byte is the dB. The IC-7610 (and 7700/7800/7851) have a 6/12/18
|
||||||
@@ -155,9 +188,21 @@ function icomWatts(pct: number): { w: number; defl: number } {
|
|||||||
return { w: Math.round(w), defl };
|
return { w: Math.round(w), defl };
|
||||||
}
|
}
|
||||||
|
|
||||||
function modeMatches(btn: string, cur?: string): boolean {
|
// Which sideband a bare "SSB" means at this frequency — the same convention the
|
||||||
|
// backend applies when it resolves the mode for the radio.
|
||||||
|
function sideForHz(hz?: number): string | null {
|
||||||
|
if (!hz || hz <= 0) return null;
|
||||||
|
return hz < 10_000_000 ? 'LSB' : 'USB';
|
||||||
|
}
|
||||||
|
|
||||||
|
function modeMatches(btn: string, cur?: string, hz?: number): boolean {
|
||||||
if (!cur) return false;
|
if (!cur) return false;
|
||||||
if (btn === 'SSB') return cur === 'SSB' || cur === 'USB' || cur === 'LSB';
|
// A rig that reports the folded ADIF "SSB" still lights the side its
|
||||||
|
// frequency implies, so the row is never blank on a phone contact.
|
||||||
|
if (btn === 'USB' || btn === 'LSB') {
|
||||||
|
if (cur === btn) return true;
|
||||||
|
return cur === 'SSB' && btn === (sideForHz(hz) ?? '');
|
||||||
|
}
|
||||||
// The backend surfaces USB-D as the operator's digital default (FT8…), or as
|
// The backend surfaces USB-D as the operator's digital default (FT8…), or as
|
||||||
// plain DATA — either way it is the DATA button that should light.
|
// plain DATA — either way it is the DATA button that should light.
|
||||||
if (btn === 'DATA') return ['DATA', 'FT8', 'FT4', 'JS8', 'JT65', 'JT9', 'MFSK', 'OLIVIA'].includes(cur);
|
if (btn === 'DATA') return ['DATA', 'FT8', 'FT4', 'JS8', 'JT65', 'JT9', 'MFSK', 'OLIVIA'].includes(cur);
|
||||||
@@ -507,9 +552,10 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Mode selector row (RS-BA1's SSB/CW/RTTY/PSK/AM/FM). */}
|
{/* Mode selector row (RS-BA1's SSB/CW/RTTY/PSK/AM/FM). */}
|
||||||
<div className="grid grid-cols-7 border-t border-border/60 divide-x divide-border/60">
|
<div className="grid border-t border-border/60 divide-x divide-border/60"
|
||||||
{MODES.map((m) => {
|
style={{ gridTemplateColumns: `repeat(${modesFor(st.model).length}, minmax(0, 1fr))` }}>
|
||||||
const on = modeMatches(m, curMode);
|
{modesFor(st.model).map((m) => {
|
||||||
|
const on = modeMatches(m, curMode, mainHz);
|
||||||
return (
|
return (
|
||||||
<button key={m} type="button" onClick={() => setMode(m)}
|
<button key={m} type="button" onClick={() => setMode(m)}
|
||||||
className={cn('py-1.5 text-[11px] font-bold tracking-wide transition-colors',
|
className={cn('py-1.5 text-[11px] font-bold tracking-wide transition-colors',
|
||||||
@@ -561,10 +607,12 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Row label={t('icmp.antenna')}>
|
{hasAntennaSelector(st.model) && (
|
||||||
<Segmented value={String(st.antenna)} options={[{ v: '1', l: 'ANT1' }, { v: '2', l: 'ANT2' }]}
|
<Row label={t('icmp.antenna')}>
|
||||||
onChange={(v) => set({ antenna: parseInt(v) }, () => IcomSetAntenna(parseInt(v)))} />
|
<Segmented value={String(st.antenna)} options={[{ v: '1', l: 'ANT1' }, { v: '2', l: 'ANT2' }]}
|
||||||
</Row>
|
onChange={(v) => set({ antenna: parseInt(v) }, () => IcomSetAntenna(parseInt(v)))} />
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Clarifiers: RIT & ΔTX (XIT) — wheel or ± to shift, Ctrl+←/→ shifts RIT. */}
|
{/* Clarifiers: RIT & ΔTX (XIT) — wheel or ± to shift, Ctrl+←/→ shifts RIT. */}
|
||||||
@@ -588,7 +636,10 @@ export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (r
|
|||||||
{(st.model ?? '').includes('7760') ? `${st.rf_power * 2} W` : st.rf_power}
|
{(st.model ?? '').includes('7760') ? `${st.rf_power * 2} W` : st.rf_power}
|
||||||
</span>
|
</span>
|
||||||
</Row>
|
</Row>
|
||||||
{isPhone && (
|
{/* Not phone-only: on USB-D the same control still sets what the radio
|
||||||
|
transmits at, and hiding it left an operator who lives in FT8 with
|
||||||
|
no mic gain at all. */}
|
||||||
|
{(
|
||||||
<Row label={t('icmp.mic')}>
|
<Row label={t('icmp.mic')}>
|
||||||
<Slider value={st.mic_gain} accent="#ef4444" onChange={(v) => set({ mic_gain: v }, () => IcomSetMicGain(v))} />
|
<Slider value={st.mic_gain} accent="#ef4444" onChange={(v) => set({ mic_gain: v }, () => IcomSetMicGain(v))} />
|
||||||
<span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.mic_gain}</span>
|
<span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.mic_gain}</span>
|
||||||
|
|||||||
@@ -1543,6 +1543,14 @@ func (b *IcomSerial) modeCode(mode string) (code byte, data bool, err error) {
|
|||||||
return civ.ModeCW, false, nil
|
return civ.ModeCW, false, nil
|
||||||
case "SSB":
|
case "SSB":
|
||||||
return usb, false, nil
|
return usb, false, nil
|
||||||
|
case "USB":
|
||||||
|
// The SIDEBAND, asked for by name. "SSB" resolves to whichever side the
|
||||||
|
// band convention wants, which is right for a logged mode and useless
|
||||||
|
// when the operator means "put this radio in USB" — on 40 m there was no
|
||||||
|
// way to say it at all, and the console's own button could not either.
|
||||||
|
return civ.ModeUSB, false, nil
|
||||||
|
case "LSB":
|
||||||
|
return civ.ModeLSB, false, nil
|
||||||
case "AM":
|
case "AM":
|
||||||
return civ.ModeAM, false, nil
|
return civ.ModeAM, false, nil
|
||||||
case "FM":
|
case "FM":
|
||||||
|
|||||||
Reference in New Issue
Block a user