fix(icom net): a sleeping rig keeps its session, and the console is reachable

From an operator's log: an IC-7760 in standby, and OpsLog dialling and
dropping every forty seconds for as long as it was left there — control
link up, login OK, token renewed, and not one CI-V answer.

lastGoodAt bounds "the link answers but no CI-V comes back". It belongs
to a session and was never cleared when a new one opened, so a rig that
went to standby half an hour ago handed every fresh session a
half-hour-old last good read: past the grace before the first command was
even sent. Torn down at once, redialled twenty seconds later, torn down
again. Cleared on connect, the rule reads as it was written — silent
since connect is a rig in standby, and the session is kept so it can be
woken.

The console it is woken from was missing too. It appeared only once the
live CAT state said "icom", which a sleeping rig never says, so the ON
button was absent at the one moment it exists for. It now follows the
CONFIGURED radio — which also had to start following a radio switched
from the status bar, instead of waiting for a trip through Settings and a
Save that changed nothing.
This commit is contained in:
2026-09-06 17:35:53 +02:00
parent 381a7fdc40
commit 507d1f0882
4 changed files with 79 additions and 7 deletions
+28 -5
View File
@@ -305,8 +305,12 @@ function FreqWheelDisplay({ mhz, onNudge, className, placeholder = '—.——
// pill. The full message stays in the tooltip. Recognises the common cases
// (OmniRig not installed, not registered) and otherwise truncates.
// RadioChip — the CAT status chip, and the radio picker behind it.
function RadioChip({ catUp, catState, onOpenSettings }: {
function RadioChip({ catUp, catState, onOpenSettings, onRadioSwitched }: {
catUp: boolean; catState: any; onOpenSettings: () => void;
// Switching radio here IS a settings change — the chosen entry becomes the CAT
// settings — so whatever reads those has to be told. Two Icoms swapped for one
// another never change the live backend name, and nothing else would notice.
onRadioSwitched?: () => void;
}) {
const [radios, setRadios] = useState<any[]>([]);
const [open, setOpen] = useState(false);
@@ -371,7 +375,7 @@ function RadioChip({ catUp, catState, onOpenSettings }: {
onClick={() => {
setOpen(false);
if (r.active) return;
SetActiveRadio(r.id).then(load).catch(() => {});
SetActiveRadio(r.id).then(() => { load(); onRadioSwitched?.(); }).catch(() => {});
}}
className={cn('flex w-full items-center gap-2 px-2.5 py-1 text-left text-xs hover:bg-muted',
r.active && 'font-semibold text-primary')}
@@ -717,6 +721,15 @@ export default function App() {
// hide the rig ON/OFF buttons on USB, where the interface is unpowered when the
// rig is off so power-ON can't work).
const [catBackend, setCatBackend] = useState('');
// icomConfigured is "this station's radio IS an Icom", from the settings
// rather than from the link.
//
// The console used to appear only once the rig was talking. Switching to an
// Icom that was switched OFF therefore showed no console at all — and the
// console is where the ON button lives, so the one moment the button exists
// for was the one moment it could not be reached.
const icomConfigured = catBackend === 'icom' || catBackend === 'icom-net';
const icomShown = catState.backend === 'icom' || icomConfigured;
// Live space-weather (solar flux / sunspots / A / K) for the header strip.
// Loaded on mount, refreshed on the backend 'solar:update' event, plus a slow
// fallback poll. These same numbers are stamped onto each logged QSO.
@@ -3265,6 +3278,15 @@ export default function App() {
setCatBackend(c.backend ?? '');
} catch {}
}, []);
// The configured backend follows every way the radio can change: the CAT
// panel's Save, a switch from the status bar's radio list, and the link
// itself reporting a different backend. It was read once at launch and after
// a Settings save only — so switching radio from the status bar left the
// console configured for the previous rig, and the Icom power buttons stayed
// hidden until the operator went into Settings and pressed Save for no
// reason.
useEffect(() => { loadCATCfg(); }, [catState.backend, loadCATCfg]);
const loadLists = useCallback(async () => {
try {
const l: ListsSettings = await GetListsSettings();
@@ -8020,7 +8042,7 @@ export default function App() {
</TabsTrigger>
)}
{catState.backend === 'flex' && <TabsTrigger value="flex">Flex Console</TabsTrigger>}
{catState.backend === 'icom' && <TabsTrigger value="icom">Icom Console</TabsTrigger>}
{icomShown && <TabsTrigger value="icom">Icom Console</TabsTrigger>}
{catState.backend === 'yaesu' && <TabsTrigger value="yaesu">Yaesu Console</TabsTrigger>}
{(catState.backend === 'elecraft' || catState.backend === 'kenwood') && <TabsTrigger value="elecraft">{t('k3.console')}</TabsTrigger>}
{catState.backend === 'tci' && <TabsTrigger value="tci">{t('tcip.console')}</TabsTrigger>}
@@ -8796,7 +8818,7 @@ export default function App() {
</TabsContent>
)}
{catState.backend === 'icom' && (
{icomShown && (
<TabsContent value="icom" className="flex-1 min-h-0 p-0">
<IcomPanel isNetwork={catBackend === 'icom-net'} onReportRST={(r) => { setRstSent(r); rstUserEditedRef.current = true; }} />
</TabsContent>
@@ -8967,6 +8989,7 @@ export default function App() {
catUp={catUp}
catState={catState}
onOpenSettings={() => { setSettingsSection('cat'); setShowSettings(true); }}
onRadioSwitched={loadCATCfg}
/>
<Chip
on={rotatorHeading.enabled && rotatorHeading.ok}
@@ -9212,7 +9235,7 @@ export default function App() {
onSaved={onSettingsSaved}
onMainPaneChanged={onSettingsPaneChanged}
flexAvailable={catState.backend === 'flex'}
icomAvailable={catState.backend === 'icom'}
icomAvailable={icomShown}
yaesuAvailable={catState.backend === 'yaesu'}
elecraftAvailable={catState.backend === 'elecraft' || catState.backend === 'kenwood'}
tciAvailable={catState.backend === 'tci'}