feat: amplifier band-follow — answer the amp's frequency polls
On its CAT/AUX connector an ACOM is the MASTER: it polls a transceiver and changes band from the reply, so nothing can be pushed to it. internal/catemu answers those polls on a second serial port, in ACOM command set 5 (Kenwood / Elecraft): FA;, FB;, IF; and ID;, and nothing else — a wrong-length reply is worse than none, it desynchronises the amp's parser for the following poll. Also offered for SPE. Some amps do not poll at all but read the radio↔PC CAT line in parallel; those never hear a responder, so an optional unprompted send (500/1000 ms) covers them. The TX frequency is what is sent: in split the amp must be tuned where we transmit. Frame lengths are pinned by tests — the failure they guard against is silent and only shows up as an amp that mistunes. Untested on hardware.
This commit is contained in:
@@ -627,7 +627,10 @@ function ADIFMonitorPanel() {
|
||||
}
|
||||
|
||||
// AmpUI mirrors the backend AmpConfig — one configured amplifier.
|
||||
type AmpUI = { id: string; name: string; enabled: boolean; type: string; transport: string; host: string; port: number; com_port: string; baud: number };
|
||||
type AmpUI = { id: string; name: string; enabled: boolean; type: string; transport: string; host: string; port: number; com_port: string; baud: number;
|
||||
// Band-follow (ACOM): a SECOND serial port on which OpsLog answers the amp's
|
||||
// frequency polls, pretending to be a Kenwood-format transceiver.
|
||||
freq_out?: boolean; freq_com_port?: string; freq_baud?: number; freq_broadcast_ms?: number };
|
||||
|
||||
// RelayAutoPanel configures automatic control of the Station Control relay boards
|
||||
// from the rig's frequency / band (PstRotator-style). Each relay carries one rule:
|
||||
@@ -2811,6 +2814,7 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
});
|
||||
const addAmp = () => setAmps((l) => [...l, {
|
||||
id: '', name: '', enabled: true, type: 'spe13', transport: 'tcp', host: '', port: 9008, com_port: '', baud: 115200,
|
||||
freq_out: false, freq_com_port: '', freq_baud: 9600, freq_broadcast_ms: 0,
|
||||
}]);
|
||||
return (
|
||||
<>
|
||||
@@ -2927,6 +2931,62 @@ export function SettingsModal({ onClose, onSaved, initialSection, onMainPaneChan
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Band-follow, for any amp that takes its band from a transceiver
|
||||
CAT link (ACOM and SPE both do) — never PowerGenius, which is
|
||||
driven over its network protocol. A SECOND serial port,
|
||||
separate from the metering one above. */}
|
||||
{!isPGXL && (
|
||||
<div className="border-t border-border/60 pt-3 space-y-3">
|
||||
<label className="flex items-center gap-2 text-sm cursor-pointer">
|
||||
<Checkbox
|
||||
checked={!!amp.freq_out}
|
||||
onCheckedChange={(c) => patchAmp(i, { freq_out: !!c })}
|
||||
/>
|
||||
{t('amp.freqOut')}
|
||||
</label>
|
||||
{amp.freq_out && (
|
||||
<>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>{t('amp.freqPort')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select value={amp.freq_com_port || '_'} onValueChange={(v) => patchAmp(i, { freq_com_port: v === '_' ? '' : v })}>
|
||||
<SelectTrigger className="h-9 flex-1"><SelectValue placeholder="— COM —" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{wkPorts.length === 0 && <SelectItem value="_" disabled>No ports found</SelectItem>}
|
||||
{wkPorts.map((p) => <SelectItem key={p} value={p}>{p}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button size="sm" variant="outline" className="h-9" onClick={() => ListSerialPorts().then((p) => setWkPorts((p ?? []) as string[])).catch(() => {})}>
|
||||
<ArrowDown className="size-3.5 rotate-90" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>Baud</Label>
|
||||
<Input type="number" min={1200} value={amp.freq_baud ?? 9600}
|
||||
onChange={(e) => patchAmp(i, { freq_baud: parseInt(e.target.value) || 9600 })} className="font-mono" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="space-y-1 col-span-2">
|
||||
<Label>{t('amp.freqBroadcast')}</Label>
|
||||
<Select value={String(amp.freq_broadcast_ms ?? 0)} onValueChange={(v) => patchAmp(i, { freq_broadcast_ms: parseInt(v) })}>
|
||||
<SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="0">{t('amp.freqPollOnly')}</SelectItem>
|
||||
<SelectItem value="500">{t('amp.freqEvery', { ms: 500 })}</SelectItem>
|
||||
<SelectItem value="1000">{t('amp.freqEvery', { ms: 1000 })}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-muted-foreground">{t('amp.freqHint')}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{amp.enabled && amp.id && <AmpStatusCard id={amp.id} />}
|
||||
{!isPGXL && !isACOM && (
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user