feat(elecraft): a K3/K4 console — power, volume, S-meter, SWR, MOX, tune
The Elecraft backend already existed; what was missing was somewhere to operate the radio from. This adds the panel, on the Kenwood-dialect client the K3 already speaks, in a tab of its own beside the Yaesu and Icom consoles. Scope is the six controls asked for and nothing else. Every extra command added without a radio to test it against is a control that may or may not do what its label says, and a K3 exposes dozens. No K3 was available while writing this, so the two halves are treated differently. The setters are the commands the reference documents unambiguously and whose effect is visible and reversible (PC, AG, TX/RX). The meters are the opposite: their scaling differs by model and firmware, so the raw answers are logged next to the power setting, the panel says the scaling is provisional, and an unmeasured SWR shows as '—' rather than as a perfect 1.0 — a good match on an antenna nobody measured is the reading that costs a radio. ATU tune sends SWT20 (the K3 front-panel tap) and logs exactly what it sent, so a wrong mapping names itself instead of leaving an operator guessing which button OpsLog pressed.
This commit is contained in:
+18
-2
@@ -78,6 +78,7 @@ import { WorldMap, LocatorMap } from '@/components/MainMap';
|
||||
import { FlexPanel } from '@/components/FlexPanel';
|
||||
import { IcomPanel } from '@/components/IcomPanel';
|
||||
import { YaesuPanel } from '@/components/YaesuPanel';
|
||||
import { ElecraftPanel } from '@/components/ElecraftPanel';
|
||||
import { AntGeniusPanel, type AGStatus } from '@/components/AntGeniusPanel';
|
||||
import { MotorAntennaWidget, type AntStatus } from '@/components/MotorAntennaWidget';
|
||||
import { TunerGeniusPanel, type TGStatus } from '@/components/TunerGeniusPanel';
|
||||
@@ -1861,7 +1862,7 @@ export default function App() {
|
||||
// so it's loaded async on mount and re-read on profile:changed below.
|
||||
// 'none' is only ever stored for the third and fourth panes: the first two are
|
||||
// the Main view, and a layout with no panes at all is not a layout.
|
||||
type MainPaneKind = 'map1' | 'map2' | 'cluster' | 'worked' | 'flex' | 'recent' | 'icom' | 'yaesu' | 'netcontrol' | 'decodes' | 'none';
|
||||
type MainPaneKind = 'map1' | 'map2' | 'cluster' | 'worked' | 'flex' | 'recent' | 'icom' | 'yaesu' | 'elecraft' | 'netcontrol' | 'decodes' | 'none';
|
||||
const [mapZoomSignal, setMapZoomSignal] = useState(0); // bump → world map auto-zooms now
|
||||
const [mainPaneLeft, setMainPaneLeft] = useState<MainPaneKind>('map1');
|
||||
const [mainPaneRight, setMainPaneRight] = useState<MainPaneKind>('map2');
|
||||
@@ -1872,7 +1873,7 @@ export default function App() {
|
||||
// quarter-width map is unreadable.
|
||||
const [mainLayout4, setMainLayout4] = useState<'cols' | 'quad'>('quad');
|
||||
const loadMainPanes = useCallback(async () => {
|
||||
const valid = (v: string): v is MainPaneKind => v === 'map1' || v === 'map2' || v === 'cluster' || v === 'worked' || v === 'flex' || v === 'recent' || v === 'icom' || v === 'yaesu' || v === 'netcontrol' || v === 'decodes';
|
||||
const valid = (v: string): v is MainPaneKind => v === 'map1' || v === 'map2' || v === 'cluster' || v === 'worked' || v === 'flex' || v === 'recent' || v === 'icom' || v === 'yaesu' || v === 'elecraft' || v === 'netcontrol' || v === 'decodes';
|
||||
const [l, r, p3, p4, lay] = await Promise.all([
|
||||
GetUIPref('mainPaneLeft').catch(() => ''),
|
||||
GetUIPref('mainPaneRight').catch(() => ''),
|
||||
@@ -6037,6 +6038,12 @@ export default function App() {
|
||||
<YaesuPanel onReportRST={(r) => { setRstSent(r); rstUserEditedRef.current = true; }} onKeySpeed={setCWSpeedEverywhere} />
|
||||
</div>
|
||||
);
|
||||
case 'elecraft':
|
||||
return (
|
||||
<div className="h-full w-full min-h-0 rounded-lg overflow-hidden border border-border">
|
||||
<ElecraftPanel onReportRST={(r) => { setRstSent(r); rstUserEditedRef.current = true; }} />
|
||||
</div>
|
||||
);
|
||||
case 'icom':
|
||||
return (
|
||||
<div className="h-full w-full min-h-0 rounded-lg overflow-hidden border border-border">
|
||||
@@ -7303,6 +7310,7 @@ export default function App() {
|
||||
{catState.backend === 'flex' && <TabsTrigger value="flex">Flex Console</TabsTrigger>}
|
||||
{catState.backend === 'icom' && <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>}
|
||||
{statsTabOpen && (
|
||||
<TabsTrigger value="stats" className="gap-1.5">
|
||||
{t('stats.tab')}
|
||||
@@ -7982,6 +7990,14 @@ export default function App() {
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{/* Elecraft K3/K4 console. Shown for the Kenwood backend too: the
|
||||
K3 speaks that dialect and the panel reads whatever answers. */}
|
||||
{(catState.backend === 'elecraft' || catState.backend === 'kenwood') && (
|
||||
<TabsContent value="elecraft" className="flex-1 min-h-0 p-0">
|
||||
<ElecraftPanel onReportRST={(r) => { setRstSent(r); rstUserEditedRef.current = true; }} />
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{catState.backend === 'icom' && (
|
||||
<TabsContent value="icom" className="flex-1 min-h-0 p-0">
|
||||
<IcomPanel isNetwork={catBackend === 'icom-net'} onReportRST={(r) => { setRstSent(r); rstUserEditedRef.current = true; }} />
|
||||
|
||||
Reference in New Issue
Block a user