This commit is contained in:
2026-06-07 02:51:00 +02:00
parent 16c04fc12b
commit 8040a37315
11 changed files with 1150 additions and 224 deletions
+81 -3
View File
@@ -11,6 +11,7 @@ import {
GetCATSettings, SaveCATSettings,
ListProfiles, GetActiveProfile, SaveProfile, DeleteProfile, ActivateProfile, DuplicateProfile,
GetRotatorSettings, SaveRotatorSettings, TestRotator, RotatorPark, RotatorStop,
GetUltrabeamSettings, SaveUltrabeamSettings, TestUltrabeam,
GetWinkeyerSettings, SaveWinkeyerSettings, ListSerialPorts,
GetAudioSettings, SaveAudioSettings, ListAudioInputDevices, ListAudioOutputDevices, PickAudioFolder, TestPTT,
GetClublogCtyInfo, SetClublogCtyEnabled, DownloadClublogCty,
@@ -193,7 +194,7 @@ const TREE: TreeNode[] = [
{ kind: 'item', label: 'CAT interface (OmniRig)', id: 'cat' },
{ kind: 'item', label: 'Rotator (PstRotator)', id: 'rotator' },
{ kind: 'item', label: 'CW Keyer (WinKeyer)', id: 'winkeyer' },
{ kind: 'item', label: 'Antenna (Ultrabeam)', id: 'antenna', disabled: true },
{ kind: 'item', label: 'Antenna (Ultrabeam)', id: 'antenna' },
{ kind: 'item', label: 'Audio devices & voice keyer', id: 'audio' },
],
},
@@ -368,6 +369,13 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
const [rotatorTesting, setRotatorTesting] = useState(false);
const [rotatorTest, setRotatorTest] = useState<{ ok: boolean; msg: string } | null>(null);
// Ultrabeam antenna (TCP) settings.
const [ultrabeam, setUltrabeam] = useState<{ enabled: boolean; host: string; port: number }>({
enabled: false, host: '', port: 23,
});
const [ubTesting, setUbTesting] = useState(false);
const [ubTest, setUbTest] = useState<{ ok: boolean; msg: string } | null>(null);
// WinKeyer CW keyer settings + macro editor.
type WKMac = { label: string; text: string };
type WKSettings = {
@@ -583,6 +591,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
await reloadClusterServers();
setCatCfg(c);
setRotator(r);
try { setUltrabeam(await GetUltrabeamSettings() as any); } catch {}
setBackupCfg(b as any);
setQslDefaults(qd as any);
setExtSvc(es as any);
@@ -751,6 +760,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
await SaveLookupSettings(lookup as any);
await SaveCATSettings(catCfg as any);
await SaveRotatorSettings(rotator as any);
await SaveUltrabeamSettings(ultrabeam as any);
await SaveWinkeyerSettings(wk as any);
await SaveAudioSettings(audioCfg as any);
await SaveEmailSettings(emailCfg as any);
@@ -1499,6 +1509,74 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
}
}
async function testUltrabeam() {
setUbTesting(true);
setUbTest(null);
try {
await TestUltrabeam(ultrabeam as any);
setUbTest({ ok: true, msg: 'Connected — the Ultrabeam responded with a status frame.' });
} catch (e: any) {
setUbTest({ ok: false, msg: String(e?.message ?? e) });
} finally {
setUbTesting(false);
}
}
function UltrabeamPanel() {
return (
<>
<SectionHeader
title="Antenna (Ultrabeam)"
hint="OpsLog talks to the Ultrabeam controller over TCP — typically via an RS232↔Ethernet adapter. Enter its IP address and port; the pattern direction (Normal / 180° / Bidirectional) is then controlled from the status bar."
/>
<div className="space-y-4 max-w-xl">
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox checked={ultrabeam.enabled} onCheckedChange={(c) => setUltrabeam((s) => ({ ...s, enabled: !!c }))} />
Enable Ultrabeam control
</label>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1 col-span-2">
<Label>Host / IP</Label>
<Input
value={ultrabeam.host ?? ''}
onChange={(e) => setUltrabeam((s) => ({ ...s, host: e.target.value }))}
placeholder="192.168.1.50"
className="font-mono"
/>
</div>
<div className="space-y-1">
<Label>TCP port</Label>
<Input
type="number" min={1} max={65535}
value={ultrabeam.port}
onChange={(e) => setUltrabeam((s) => ({ ...s, port: parseInt(e.target.value) || 23 }))}
className="font-mono"
/>
</div>
</div>
<div className="flex items-center gap-2 pt-2">
<Button variant="outline" size="sm" onClick={testUltrabeam} disabled={ubTesting || !ultrabeam.host.trim()}>
{ubTesting ? 'Connecting…' : 'Test connection'}
</Button>
</div>
{ubTest && (
<div className={cn(
'text-xs rounded-md p-2.5 border',
ubTest.ok
? 'bg-emerald-50 text-emerald-800 border-emerald-200'
: 'bg-destructive/10 text-destructive border-destructive/30',
)}>
{ubTest.msg}
</div>
)}
<p className="text-xs text-muted-foreground">
Once enabled, the antenna's direction control (Normal / 180° / Bidirectional) appears in the bottom status bar, next to CAT and Rotator. Changing the direction re-tunes the elements at the current frequency.
</p>
</div>
</>
);
}
function RotatorPanel() {
return (
<>
@@ -2715,7 +2793,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
</SelectContent>
</Select>
{audioCfg.ptt_method !== 'none' && (
<Button variant="outline" size="sm" className="h-8" onClick={() => { setDvkErr(''); TestPTT().catch((e: any) => setDvkErr('PTT test: ' + String(e?.message ?? e))); }}>
<Button variant="outline" size="sm" className="h-8" onClick={() => { setDvkErr(''); TestPTT(audioCfg as any).catch((e: any) => setDvkErr('PTT test: ' + String(e?.message ?? e))); }}>
Test PTT
</Button>
)}
@@ -2926,7 +3004,7 @@ export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
cat: CATPanel,
rotator: RotatorPanel,
winkeyer: WinkeyerPanel,
antenna: () => <ComingSoon id="antenna" icon={AntennaIcon} />,
antenna: UltrabeamPanel,
audio: AudioPanel,
};