feat(antenna): three tracking modes for Ultrabeam and SteppIR
The follow loop only ever had one behaviour — re-tune once the rig moved further than a fixed step. The SteppIR's own controller software offers three, and operators arrive with that mental model: every frequency change, past a threshold, or only on a band change. They are real operating trade-offs, not preferences: "always" keeps resonance perfect at the cost of motors running constantly (and on a SteppIR every move inhibits transmit while the elements travel), "band" moves them a handful of times a day. "Always" is implemented as the threshold mode with a 1 kHz threshold rather than as a separate branch, so all modes keep the one deadband reference that matters: the rig frequency last commanded for, NOT the antenna's own reported frequency — a SteppIR flips its reported frequency between the commanded value and its home value, which would re-issue a SET on nearly every poll and leave the operator permanently unable to transmit. Band mode compares against the band last COMMANDED for, not the rig's previous band, so the first move after startup and any move made by hand still get reconciled. It applies to the immediate spot-click path too: a click inside the band the antenna is already resonant in moves nothing. An unset or unrecognised mode resolves to the step mode, so every config written before this option behaves exactly as it did. Also routes the antenna settings block through t() — it was hardcoded English.
This commit is contained in:
@@ -116,7 +116,7 @@ function RotatorWidget({ hd, refetch, centerLat, centerLon, bearing, t }: Rotato
|
||||
);
|
||||
}
|
||||
|
||||
type AntStatus = { enabled: boolean; type: string; connected: boolean; direction: number; frequency: number; moving: boolean; elements: number[]; follow?: boolean; step_khz?: number; bands?: string[] };
|
||||
type AntStatus = { enabled: boolean; type: string; connected: boolean; direction: number; frequency: number; moving: boolean; elements: number[]; follow?: boolean; step_khz?: number; track_mode?: string; bands?: string[] };
|
||||
|
||||
// Where each band button points the antenna.
|
||||
//
|
||||
@@ -277,23 +277,38 @@ function MotorAntennaWidget({ ant, refetch, t }: { ant: AntStatus; refetch: () =
|
||||
|
||||
{/* Tracking. Here rather than only in Settings because it is an operating
|
||||
decision — off to park the antenna, on to resume — not something set
|
||||
up once. The step only shows when tracking is on: a threshold for
|
||||
something switched off is a question the operator cannot act on. */}
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button"
|
||||
onClick={() => run(SetMotorFollow(!ant.follow, 0))}
|
||||
className={cn('flex-1 rounded-md border py-1.5 text-xs font-semibold transition-colors',
|
||||
ant.follow ? 'bg-success-muted text-success-muted-foreground border-success-border' : 'border-border hover:bg-muted')}>
|
||||
{ant.follow ? t('station.trackOn') : t('station.trackOff')}
|
||||
</button>
|
||||
up once. Mode and step only show when tracking is on: settings for
|
||||
something switched off are questions the operator cannot act on. And
|
||||
the step only shows in step mode, where it is the one thing it means. */}
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button"
|
||||
onClick={() => run(SetMotorFollow(!ant.follow, 0, ''))}
|
||||
className={cn('flex-1 rounded-md border py-1.5 text-xs font-semibold transition-colors',
|
||||
ant.follow ? 'bg-success-muted text-success-muted-foreground border-success-border' : 'border-border hover:bg-muted')}>
|
||||
{ant.follow ? t('station.trackOn') : t('station.trackOff')}
|
||||
</button>
|
||||
{ant.follow && (ant.track_mode || 'step') === 'step' && (
|
||||
<select
|
||||
value={String(ant.step_khz || 50)}
|
||||
onChange={(e) => run(SetMotorFollow(true, parseInt(e.target.value, 10), ''))}
|
||||
className="h-[30px] rounded-md border border-border bg-background px-1 text-xs font-mono"
|
||||
title={t('station.trackStepTip')}
|
||||
>
|
||||
{[25, 50, 100].map((s) => <option key={s} value={s}>{s} kHz</option>)}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
{ant.follow && (
|
||||
<select
|
||||
value={String(ant.step_khz || 50)}
|
||||
onChange={(e) => run(SetMotorFollow(true, parseInt(e.target.value, 10)))}
|
||||
className="h-[30px] rounded-md border border-border bg-background px-1 text-xs font-mono"
|
||||
title={t('station.trackStepTip')}
|
||||
value={ant.track_mode || 'step'}
|
||||
onChange={(e) => run(SetMotorFollow(true, 0, e.target.value))}
|
||||
className="w-full h-[30px] rounded-md border border-border bg-background px-1.5 text-xs"
|
||||
title={t('station.trackModeTip')}
|
||||
>
|
||||
{[25, 50, 100].map((s) => <option key={s} value={s}>{s} kHz</option>)}
|
||||
<option value="always" title={t('station.trackAlwaysTip')}>{t('station.trackAlways')}</option>
|
||||
<option value="step" title={t('station.trackStepTipMode')}>{t('station.trackStep')}</option>
|
||||
<option value="band" title={t('station.trackBandTip')}>{t('station.trackBand')}</option>
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user