feat(sat): PstRotator can point the antenna too
It handles azimuth and elevation, and a great many stations already run it in front of a controller OpsLog has never heard of. For those, OpsLog talking to the controller itself would be a second program fighting PstRotator over the same cable — so it hands over the bearing instead, and lets PstRotator turn the mast. Both kinds sit behind one small interface, chosen in Settings. Neither is more correct than the other: the right one is whichever the station already has working. The 450° overlap is deliberately NOT applied on the PstRotator path. PstRotator knows which machine is on the other end and does its own; two programs each deciding to go the long way round is exactly how an antenna unwinds in the middle of a pass. Position queries are asked at most every three seconds rather than on every tick. A PstRotator query binds a socket and waits up to a second and a half, and many setups answer nothing at all — so one silence is enough and it stops asking, reporting the commanded position instead and saying that is what it is.
This commit is contained in:
@@ -1848,7 +1848,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
// Satellites: the observer and the az/el rotator. The rest of the satellite
|
||||
// settings (favourites, the pass window) are set in the tab itself, where
|
||||
// they are used.
|
||||
const [satCfg, setSatCfg] = useState<any>({ min_el: 10, window_h: 24, auto_tle: true, grid: '', alt_m: 0, rot_on: false, rot_transport: 'serial', rot_host: '', rot_port: 4533, rot_com: '', rot_baud: 9600, rot_max_az: 360, rot_min_el: 0, rot_step: 5, rot_park: false });
|
||||
const [satCfg, setSatCfg] = useState<any>({ min_el: 10, window_h: 24, auto_tle: true, grid: '', alt_m: 0, rot_on: false, rot_type: 'easycomm', rot_pst_port: 12000, rot_transport: 'serial', rot_host: '', rot_port: 4533, rot_com: '', rot_baud: 9600, rot_max_az: 360, rot_min_el: 0, rot_step: 5, rot_park: false });
|
||||
const [satTest, setSatTest] = useState('');
|
||||
|
||||
// Amplifier list — operators can run SEVERAL amps (even two SPEs combined),
|
||||
@@ -4567,7 +4567,38 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
|
||||
{!!satCfg.rot_on && (
|
||||
<>
|
||||
{/* Who drives the mast. Not a detail: a station already running
|
||||
PstRotator must NOT have OpsLog on the same cable as well. */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotType')}</Label>
|
||||
<Select value={satCfg.rot_type || 'easycomm'} onValueChange={(v) => set('rot_type', v)}>
|
||||
<SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="easycomm">{t('satset.rotEasycomm')}</SelectItem>
|
||||
<SelectItem value="pstrotator">{t('satset.rotPst')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{satCfg.rot_type === 'pstrotator' && (
|
||||
<>
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotHost')}</Label>
|
||||
<Input className="font-mono" placeholder="127.0.0.1"
|
||||
value={satCfg.rot_host ?? ''} onChange={(e) => set('rot_host', e.target.value)} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotPstPort')}</Label>
|
||||
<Input className="font-mono" value={String(satCfg.rot_pst_port ?? 12000)}
|
||||
onChange={(e) => set('rot_pst_port', num(e.target.value))} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{satCfg.rot_type === 'pstrotator' && (
|
||||
<p className="text-xs text-muted-foreground">{t('satset.rotPstHint')}</p>
|
||||
)}
|
||||
<div className={cn('grid grid-cols-3 gap-3', satCfg.rot_type === 'pstrotator' && 'hidden')}>
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotLink')}</Label>
|
||||
<Select value={satCfg.rot_transport || 'serial'} onValueChange={(v) => set('rot_transport', v)}>
|
||||
@@ -4618,16 +4649,22 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotRange')}</Label>
|
||||
<Select value={String(satCfg.rot_max_az ?? 360)} onValueChange={(v) => set('rot_max_az', parseInt(v, 10))}>
|
||||
<SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="360">360°</SelectItem>
|
||||
<SelectItem value="450">450°</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* The rotator's range is ours to know only when we drive the
|
||||
controller. PstRotator knows which machine is on the other
|
||||
end and does its own overlap; two programs each deciding to
|
||||
go the long way round is how an antenna unwinds mid-pass. */}
|
||||
{satCfg.rot_type !== 'pstrotator' && (
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotRange')}</Label>
|
||||
<Select value={String(satCfg.rot_max_az ?? 360)} onValueChange={(v) => set('rot_max_az', parseInt(v, 10))}>
|
||||
<SelectTrigger className="h-9"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="360">360°</SelectItem>
|
||||
<SelectItem value="450">450°</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<Label>{t('satset.rotMinEl')}</Label>
|
||||
<Input className="font-mono" value={String(satCfg.rot_min_el ?? 0)}
|
||||
|
||||
Reference in New Issue
Block a user