Skip to content

Commit cb5a551

Browse files
committed
feat: antenna presets and tuned rotator defaults
Add antenna preset buttons (VHF Yagi, Dual-band, UHF Yagi, Dish) to the rotator Setup tab that set beam width, tolerance, and update rate together for common amateur satellite setups. Defaults now match Dual-band preset (20° beam, 3° tolerance, 2s rate). Tolerance slider max raised to 10°, update rate max to 20s to accommodate relaxed VHF tracking setups.
1 parent 544c7a8 commit cb5a551

3 files changed

Lines changed: 66 additions & 8 deletions

File tree

src/stores/beam.svelte.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class BeamStore {
4141
// Beam aim direction
4242
aimAz = $state(0);
4343
aimEl = $state(45);
44-
beamWidth = $state(3);
44+
beamWidth = $state(20);
4545

4646
// Lock-to-selected tracking
4747
locked = $state(false);

src/stores/rotator.svelte.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ export type ParkPreset = 'north' | 'south' | 'zenith' | 'custom';
1717

1818
export type PassEndAction = 'nothing' | 'park' | 'slew-next';
1919

20+
export interface AntennaPreset {
21+
label: string;
22+
beamWidth: number;
23+
tolerance: number;
24+
updateMs: number;
25+
}
26+
27+
export const ANTENNA_PRESETS: Record<string, AntennaPreset> = {
28+
vhf: { label: 'VHF Yagi', beamWidth: 30, tolerance: 5, updateMs: 5000 },
29+
dual: { label: 'Dual-band', beamWidth: 20, tolerance: 3, updateMs: 2000 },
30+
uhf: { label: 'UHF Yagi', beamWidth: 18, tolerance: 2, updateMs: 2000 },
31+
dish: { label: 'Dish', beamWidth: 5, tolerance: 1, updateMs: 500 },
32+
};
33+
2034
export const PARK_PRESETS: Record<Exclude<ParkPreset, 'custom'>, { label: string; az: number; el: number }> = {
2135
north: { label: 'North / Horizon', az: 0, el: 0 },
2236
south: { label: 'South / Horizon', az: 180, el: 0 },
@@ -29,8 +43,8 @@ class RotatorStore {
2943
serialProtocol = $state<SerialProtocol>('gs232');
3044
baudRate = $state(9600);
3145
wsUrl = $state('ws://localhost:4534');
32-
updateIntervalMs = $state(500);
33-
tolerance = $state(0.5);
46+
updateIntervalMs = $state(2000);
47+
tolerance = $state(3.0);
3448
parkPreset = $state<ParkPreset>('north');
3549
parkAz = $state(0);
3650
parkEl = $state(0);
@@ -472,7 +486,7 @@ class RotatorStore {
472486
}
473487

474488
setUpdateInterval(ms: number): void {
475-
this.updateIntervalMs = Math.max(100, Math.min(5000, ms));
489+
this.updateIntervalMs = Math.max(100, Math.min(20000, ms));
476490
this.save('update_interval', this.updateIntervalMs);
477491
if (this.driver?.connected) {
478492
this.stopTimers();
@@ -511,7 +525,7 @@ class RotatorStore {
511525
}
512526

513527
setTolerance(deg: number): void {
514-
this.tolerance = Math.max(0, Math.min(5, deg));
528+
this.tolerance = Math.max(0, Math.min(10, deg));
515529
this.save('tolerance', this.tolerance);
516530
}
517531
}

src/ui/RotatorWindow.svelte

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import InfoTip from './shared/InfoTip.svelte';
88
import { uiStore } from '../stores/ui.svelte';
99
import { beamStore, isInsideBeam } from '../stores/beam.svelte';
10-
import { rotatorStore, PARK_PRESETS, type ParkPreset, type PassEndAction } from '../stores/rotator.svelte';
10+
import { rotatorStore, PARK_PRESETS, ANTENNA_PRESETS, type ParkPreset, type PassEndAction } from '../stores/rotator.svelte';
1111
import { timeStore } from '../stores/time.svelte';
1212
import { satColorRgba } from '../constants';
1313
import { ViewMode } from '../types';
@@ -89,6 +89,22 @@
8989
let rateDisplay = $derived(`${(rotatorStore.updateIntervalMs / 1000).toFixed(1)}s`);
9090
let tolDisplay = $derived(`${rotatorStore.tolerance.toFixed(1)}°`);
9191
92+
let activeAntennaPreset = $derived.by(() => {
93+
for (const [key, p] of Object.entries(ANTENNA_PRESETS)) {
94+
if (beamStore.beamWidth === p.beamWidth && rotatorStore.tolerance === p.tolerance && rotatorStore.updateIntervalMs === p.updateMs)
95+
return key;
96+
}
97+
return null;
98+
});
99+
100+
function applyAntennaPreset(key: string) {
101+
const p = ANTENNA_PRESETS[key];
102+
if (!p) return;
103+
beamStore.setBeamWidth(p.beamWidth);
104+
rotatorStore.setTolerance(p.tolerance);
105+
rotatorStore.setUpdateInterval(p.updateMs);
106+
}
107+
92108
// In sky view, rotate the radar so the viewer's look direction is at the top
93109
let headingRad = $derived(uiStore.viewMode === ViewMode.VIEW_SKY ? uiStore.skyHeading : 0);
94110
@@ -880,6 +896,21 @@
880896

881897
{:else}
882898
<div class="setup-panel">
899+
<h4 class="section-header">Antenna</h4>
900+
<div class="rot-row">
901+
<label>Preset<InfoTip>Sets beam width, tolerance, and update rate for common antenna types.</InfoTip></label>
902+
<div class="antenna-presets">
903+
{#each Object.entries(ANTENNA_PRESETS) as [key, p]}
904+
<Button size="xs" active={activeAntennaPreset === key} onclick={() => applyAntennaPreset(key)}>{p.label}</Button>
905+
{/each}
906+
</div>
907+
</div>
908+
<div class="rot-row antenna-summary">
909+
<span>Beam {beamStore.beamWidth}°</span>
910+
<span>Tolerance {rotatorStore.tolerance}°</span>
911+
<span>Rate {rateDisplay}</span>
912+
</div>
913+
883914
<h4 class="section-header">Connection</h4>
884915
<div class="rot-row">
885916
<label>Mode</label>
@@ -922,12 +953,12 @@
922953
{/if}
923954

924955
<Slider label="Update rate" display={rateDisplay}
925-
min={100} max={5000} step={100} value={rotatorStore.updateIntervalMs}
956+
min={100} max={20000} step={100} value={rotatorStore.updateIntervalMs}
926957
oninput={(e) => rotatorStore.setUpdateInterval(Number((e.target as HTMLInputElement).value))} />
927958

928959
{#snippet tolTip()}<InfoTip>Minimum error before sending a new position command. Reduces wear on the rotator gears. Set to 0 for continuous tracking.</InfoTip>{/snippet}
929960
<Slider label="Tolerance" display={tolDisplay} tip={tolTip}
930-
min={0} max={5} step={0.1} value={rotatorStore.tolerance}
961+
min={0} max={10} step={0.5} value={rotatorStore.tolerance}
931962
oninput={(e) => rotatorStore.setTolerance(Number((e.target as HTMLInputElement).value))} />
932963

933964
<div class="rot-row">
@@ -1252,6 +1283,19 @@
12521283
gap: 4px;
12531284
}
12541285
1286+
/* ── Antenna presets ── */
1287+
.antenna-presets {
1288+
display: flex;
1289+
gap: 2px;
1290+
}
1291+
.antenna-summary {
1292+
gap: 10px;
1293+
justify-content: flex-start;
1294+
font-size: 9px;
1295+
color: var(--text-muted);
1296+
font-variant-numeric: tabular-nums;
1297+
}
1298+
12551299
/* ── Setup tab ── */
12561300
.setup-panel {
12571301
padding: 8px;

0 commit comments

Comments
 (0)