up
This commit is contained in:
@@ -13,34 +13,96 @@
|
||||
$: elementLengths = status?.element_lengths || [];
|
||||
$: firmwareVersion = status ? `${status.firmware_major}.${status.firmware_minor}` : '0.0';
|
||||
|
||||
// Band names mapping
|
||||
// Band names mapping - VL2.3 covers 6M to 40M only
|
||||
// Band 0=6M, 1=10M, 2=12M, 3=15M, 4=17M, 5=20M, 6=30M, 7=40M
|
||||
const bandNames = [
|
||||
'160M', '80M', '60M', '40M', '30M', '20M',
|
||||
'17M', '15M', '12M', '10M', '6M'
|
||||
'6M', '10M', '12M', '15M', '17M', '20M', '30M', '40M'
|
||||
];
|
||||
|
||||
// Detect band from frequency
|
||||
$: detectedBand = detectBandFromFrequency(frequency, band);
|
||||
|
||||
function detectBandFromFrequency(freq, bandIndex) {
|
||||
// If band index is valid (0-7), use it directly
|
||||
if (bandIndex >= 0 && bandIndex <= 7) {
|
||||
return bandNames[bandIndex];
|
||||
}
|
||||
|
||||
// Otherwise detect from frequency (in kHz)
|
||||
if (freq >= 7000 && freq <= 7300) return '40M';
|
||||
if (freq >= 10100 && freq <= 10150) return '30M';
|
||||
if (freq >= 14000 && freq <= 14350) return '20M';
|
||||
if (freq >= 18068 && freq <= 18168) return '17M';
|
||||
if (freq >= 21000 && freq <= 21450) return '15M';
|
||||
if (freq >= 24890 && freq <= 24990) return '12M';
|
||||
if (freq >= 28000 && freq <= 29700) return '10M';
|
||||
if (freq >= 50000 && freq <= 54000) return '6M';
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
// Direction names
|
||||
const directionNames = ['Normal', '180°', 'Bi-Dir'];
|
||||
|
||||
// Auto-track threshold options
|
||||
const thresholdOptions = [
|
||||
{ value: 25, label: '25 kHz' },
|
||||
{ value: 50, label: '50 kHz' },
|
||||
{ value: 100, label: '100 kHz' }
|
||||
];
|
||||
|
||||
// Auto-track state
|
||||
let autoTrackEnabled = true; // Default enabled
|
||||
let autoTrackThreshold = 25; // Default 25 kHz
|
||||
|
||||
// Form state
|
||||
let targetFreq = 0;
|
||||
let targetDirection = 0;
|
||||
|
||||
// Auto-update targetDirection when status changes
|
||||
$: targetDirection = direction;
|
||||
|
||||
// Element names based on band (corrected order: 0=6M ... 10=160M)
|
||||
$: elementNames = getElementNames(band);
|
||||
|
||||
function getElementNames(band) {
|
||||
// 30M (band 6) and 40M (band 7): Reflector (inverted), Radiator (inverted)
|
||||
if (band === 6 || band === 7) {
|
||||
return ['Radiator (30/40M)', 'Reflector (30/40M)', null];
|
||||
}
|
||||
// 6M to 20M (bands 0-5): Reflector, Radiator, Director 1
|
||||
if (band >= 0 && band <= 5) {
|
||||
return ['Reflector', 'Radiator', 'Director 1'];
|
||||
}
|
||||
// Default
|
||||
return ['Element 1', 'Element 2', 'Element 3'];
|
||||
}
|
||||
|
||||
// Element calibration state
|
||||
let calibrationMode = false;
|
||||
let selectedElement = 0;
|
||||
let elementAdjustment = 0;
|
||||
|
||||
async function setFrequency() {
|
||||
if (targetFreq < 1800 || targetFreq > 30000) {
|
||||
alert('Frequency must be between 1.8 MHz and 30 MHz');
|
||||
return;
|
||||
async function setDirection() {
|
||||
if (frequency === 0) {
|
||||
return; // Silently skip if no frequency
|
||||
}
|
||||
try {
|
||||
await api.ultrabeam.setFrequency(targetFreq, targetDirection);
|
||||
// Send command to antenna with current frequency and new direction
|
||||
await api.ultrabeam.setFrequency(frequency, targetDirection);
|
||||
// Also save direction preference for auto-track
|
||||
await api.ultrabeam.setDirection(targetDirection);
|
||||
} catch (err) {
|
||||
console.error('Failed to set frequency:', err);
|
||||
alert('Failed to set frequency');
|
||||
// Log error but don't alert - code 30 (busy) is normal
|
||||
console.log('Direction change sent (may show code 30 if busy):', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAutoTrack() {
|
||||
try {
|
||||
await api.ultrabeam.setAutoTrack(autoTrackEnabled, autoTrackThreshold);
|
||||
} catch (err) {
|
||||
console.error('Failed to update auto-track:', err);
|
||||
alert('Failed to update auto-track settings');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,50 +150,56 @@
|
||||
|
||||
<div class="status-item">
|
||||
<div class="status-label">Band</div>
|
||||
<div class="status-value band">{bandNames[band] || 'Unknown'}</div>
|
||||
<div class="status-value band">{detectedBand}</div>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<div class="status-label">Direction</div>
|
||||
<div class="status-value direction">{directionNames[direction]}</div>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<div class="status-label">Firmware</div>
|
||||
<div class="status-value fw">v{firmwareVersion}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Frequency Control -->
|
||||
<div class="control-section">
|
||||
<h3>Frequency Control</h3>
|
||||
<div class="freq-control">
|
||||
<div class="input-group">
|
||||
<label for="target-freq">Target Frequency (KHz)</label>
|
||||
<input
|
||||
id="target-freq"
|
||||
type="number"
|
||||
bind:value={targetFreq}
|
||||
min="1800"
|
||||
max="30000"
|
||||
step="1"
|
||||
placeholder="e.g. 14200"
|
||||
/>
|
||||
</div>
|
||||
<!-- Auto-Track Control -->
|
||||
<div class="control-section compact">
|
||||
<h3>Auto Tracking</h3>
|
||||
<div class="auto-track-controls">
|
||||
<label class="toggle-label">
|
||||
<input type="checkbox" bind:checked={autoTrackEnabled} on:change={updateAutoTrack} />
|
||||
<span>Enable Auto-Track from Tuner</span>
|
||||
</label>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="target-dir">Direction</label>
|
||||
<select id="target-dir" bind:value={targetDirection}>
|
||||
<option value={0}>Normal</option>
|
||||
<option value={1}>180°</option>
|
||||
<option value={2}>Bi-Directional</option>
|
||||
<div class="threshold-group">
|
||||
<label for="threshold-select">Threshold:</label>
|
||||
<select id="threshold-select" bind:value={autoTrackThreshold} on:change={updateAutoTrack}>
|
||||
{#each thresholdOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn-primary" on:click={setFrequency}>
|
||||
<span class="icon">📡</span>
|
||||
Set Frequency
|
||||
</button>
|
||||
<div class="direction-buttons">
|
||||
<button
|
||||
class="dir-btn normal"
|
||||
class:active={targetDirection === 0}
|
||||
on:click={() => { targetDirection = 0; setDirection(); }}
|
||||
>
|
||||
Normal
|
||||
</button>
|
||||
<button
|
||||
class="dir-btn rotate180"
|
||||
class:active={targetDirection === 1}
|
||||
on:click={() => { targetDirection = 1; setDirection(); }}
|
||||
>
|
||||
180°
|
||||
</button>
|
||||
<button
|
||||
class="dir-btn bidir"
|
||||
class:active={targetDirection === 2}
|
||||
on:click={() => { targetDirection = 2; setDirection(); }}
|
||||
>
|
||||
Bi-Dir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,22 +214,25 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Element Lengths Display -->
|
||||
<!-- Element Lengths Display - HIDDEN: Command 9 returns status instead -->
|
||||
<!--
|
||||
<div class="elements-section">
|
||||
<h3>Element Lengths (mm)</h3>
|
||||
<div class="elements-grid">
|
||||
{#each elementLengths as length, i}
|
||||
{#if length > 0}
|
||||
{#each elementLengths.slice(0, 3) as length, i}
|
||||
{#if length > 0 && elementNames[i]}
|
||||
<div class="element-item">
|
||||
<div class="element-label">Element {i + 1}</div>
|
||||
<div class="element-label">{elementNames[i]}</div>
|
||||
<div class="element-value">{length} mm</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- Calibration Mode -->
|
||||
<!-- Calibration Mode - HIDDEN: Command 9 doesn't work -->
|
||||
<!--
|
||||
<div class="calibration-section">
|
||||
<div class="section-header">
|
||||
<h3>Calibration</h3>
|
||||
@@ -179,9 +250,9 @@
|
||||
<div class="input-group">
|
||||
<label for="element-select">Element</label>
|
||||
<select id="element-select" bind:value={selectedElement}>
|
||||
{#each elementLengths as length, i}
|
||||
{#if length > 0}
|
||||
<option value={i}>Element {i + 1} ({length}mm)</option>
|
||||
{#each elementLengths.slice(0, 3) as length, i}
|
||||
{#if length > 0 && elementNames[i]}
|
||||
<option value={i}>{elementNames[i]} ({length}mm)</option>
|
||||
{/if}
|
||||
{/each}
|
||||
</select>
|
||||
@@ -209,6 +280,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="actions">
|
||||
@@ -224,7 +296,7 @@
|
||||
.card {
|
||||
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
padding: 16px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
border: 1px solid rgba(79, 195, 247, 0.2);
|
||||
}
|
||||
@@ -240,7 +312,7 @@
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #4fc3f7 0%, #03a9f4 100%);
|
||||
-webkit-background-clip: text;
|
||||
@@ -278,14 +350,14 @@
|
||||
.metrics {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Status Grid */
|
||||
.status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
@@ -304,31 +376,139 @@
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #4fc3f7;
|
||||
text-shadow: 0 0 10px rgba(79, 195, 247, 0.5);
|
||||
}
|
||||
|
||||
.status-value.freq {
|
||||
color: #66bb6a;
|
||||
font-size: 24px;
|
||||
font-size: 22px;
|
||||
text-shadow: 0 0 10px rgba(102, 187, 106, 0.5);
|
||||
}
|
||||
|
||||
.status-value.band {
|
||||
color: #ffa726;
|
||||
text-shadow: 0 0 10px rgba(255, 167, 38, 0.5);
|
||||
}
|
||||
|
||||
.status-value.direction {
|
||||
color: #ab47bc;
|
||||
text-shadow: 0 0 10px rgba(171, 71, 188, 0.5);
|
||||
}
|
||||
|
||||
/* Control Section */
|
||||
.control-section {
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(79, 195, 247, 0.2);
|
||||
}
|
||||
|
||||
.control-section.compact {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.auto-track-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.toggle-label input[type="checkbox"] {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.threshold-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.threshold-group label {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.direction-buttons {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.dir-btn {
|
||||
padding: 14px 20px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
color: white;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.dir-btn.normal {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.dir-btn.normal:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
|
||||
}
|
||||
|
||||
.dir-btn.normal.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
box-shadow: 0 0 25px rgba(102, 126, 234, 0.8), 0 6px 20px rgba(102, 126, 234, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.dir-btn.rotate180 {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
}
|
||||
|
||||
.dir-btn.rotate180:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(245, 87, 108, 0.5);
|
||||
}
|
||||
|
||||
.dir-btn.rotate180.active {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
box-shadow: 0 0 25px rgba(245, 87, 108, 0.8), 0 6px 20px rgba(245, 87, 108, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.dir-btn.bidir {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
}
|
||||
|
||||
.dir-btn.bidir:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(79, 172, 254, 0.5);
|
||||
}
|
||||
|
||||
.dir-btn.bidir.active {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
box-shadow: 0 0 25px rgba(79, 172, 254, 0.8), 0 6px 20px rgba(79, 172, 254, 0.5);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.freq-control {
|
||||
display: grid;
|
||||
@@ -437,7 +617,7 @@
|
||||
/* Progress */
|
||||
.progress-section {
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||||
}
|
||||
@@ -468,7 +648,7 @@
|
||||
/* Elements */
|
||||
.elements-section {
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(79, 195, 247, 0.2);
|
||||
}
|
||||
@@ -502,7 +682,7 @@
|
||||
/* Calibration */
|
||||
.calibration-section {
|
||||
background: rgba(255, 152, 0, 0.05);
|
||||
padding: 20px;
|
||||
padding: 12px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 152, 0, 0.3);
|
||||
}
|
||||
@@ -517,7 +697,7 @@
|
||||
.calibration-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
|
||||
Reference in New Issue
Block a user