mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Merge branch 'smittix:main' into main
This commit is contained in:
@@ -3,21 +3,39 @@
|
||||
<div class="section">
|
||||
<h3>CW/Morse Decoder</h3>
|
||||
<p class="info-text morse-mode-help">
|
||||
Decode CW (continuous wave) Morse with USB demod + Goertzel tone detection.
|
||||
Start with 700 Hz tone and 200 Hz bandwidth.
|
||||
Decode CW (continuous wave) Morse code. Supports HF amateur bands (USB + Goertzel tone
|
||||
detection) and ISM/UHF OOK signals (AM + envelope detection).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Detection Mode</h3>
|
||||
<div class="form-group">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<button class="preset-btn morseDetectBtn" id="morseDetectGoertzel"
|
||||
onclick="MorseMode.setDetectMode('goertzel')"
|
||||
style="flex: 1; background: var(--accent); color: #000;">CW Tone</button>
|
||||
<button class="preset-btn morseDetectBtn" id="morseDetectEnvelope"
|
||||
onclick="MorseMode.setDetectMode('envelope')"
|
||||
style="flex: 1;">OOK Envelope</button>
|
||||
</div>
|
||||
<input type="hidden" id="morseDetectMode" value="goertzel">
|
||||
<p id="morseDetectHint" class="info-text" style="font-size: 10px; color: var(--text-dim); margin-top: 4px;">
|
||||
CW Tone: HF bands, USB demod, Goertzel filter. For amateur CW.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Frequency</h3>
|
||||
<div class="form-group">
|
||||
<label>Frequency (MHz)</label>
|
||||
<input type="number" id="morseFrequency" value="14.060" step="0.001" min="0.5" max="30" placeholder="e.g., 14.060">
|
||||
<input type="number" id="morseFrequency" value="14.060" step="0.001" min="0.5" max="1766" placeholder="e.g., 14.060">
|
||||
<span class="help-text morse-help-text">Enter CW center frequency in MHz (e.g., 7.030 for 40m).</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Band Presets</label>
|
||||
<div class="morse-presets">
|
||||
<div class="morse-presets" id="morseHFPresets">
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(3.560)">80m</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(7.030)">40m</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(10.116)">30m</button>
|
||||
@@ -27,6 +45,13 @@
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(24.910)">12m</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(28.060)">10m</button>
|
||||
</div>
|
||||
<div class="morse-presets" id="morseISMPresets" style="display: none; flex-wrap: wrap; gap: 4px;">
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(315.000)">315</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(433.300)">433.3</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(433.920)">433.9</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(868.000)">868</button>
|
||||
<button class="preset-btn" onclick="MorseMode.setFreq(915.000)">915</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +67,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section" id="morseToneFreqGroup">
|
||||
<h3>CW Detector</h3>
|
||||
<div class="form-group">
|
||||
<label>Tone Frequency: <span id="morseToneFreqLabel">700</span> Hz</label>
|
||||
@@ -154,12 +179,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section" id="morseHFNote">
|
||||
<p class="info-text morse-hf-note">
|
||||
CW on HF (1-30 MHz) requires an HF-capable SDR path (direct sampling or upconverter)
|
||||
and an appropriate antenna.
|
||||
</p>
|
||||
</div>
|
||||
<div class="section" id="morseEnvelopeNote" style="display: none;">
|
||||
<p class="info-text" style="font-size: 11px; color: #ffaa00; line-height: 1.5;">
|
||||
OOK Envelope mode uses AM demodulation to detect carrier on/off keying.
|
||||
Suitable for ISM-band (315/433/868/915 MHz) Morse transmitters.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="run-btn" id="morseStartBtn" onclick="MorseMode.start()">Start Decoder</button>
|
||||
<button class="stop-btn" id="morseStopBtn" onclick="MorseMode.stop()" style="display: none;">Stop Decoder</button>
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
freq_min: freqMin,
|
||||
freq_max: freqMax,
|
||||
bias_t: typeof getBiasTEnabled === 'function' ? getBiasTEnabled() : false,
|
||||
latitude: radiosondeStationLocation.lat,
|
||||
longitude: radiosondeStationLocation.lon,
|
||||
})
|
||||
})
|
||||
.then(r => r.json())
|
||||
@@ -230,15 +232,23 @@
|
||||
let radiosondeMarkers = new Map();
|
||||
let radiosondeTracks = new Map();
|
||||
let radiosondeTrackPoints = new Map();
|
||||
let radiosondeStationLocation = { lat: 0, lon: 0 };
|
||||
let radiosondeStationMarker = null;
|
||||
|
||||
function initRadiosondeMap() {
|
||||
if (radiosondeMap) return;
|
||||
const container = document.getElementById('radiosondeMapContainer');
|
||||
if (!container) return;
|
||||
|
||||
// Resolve observer location
|
||||
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||
radiosondeStationLocation = ObserverLocation.getForModule('radiosonde_observerLocation');
|
||||
}
|
||||
const hasLocation = radiosondeStationLocation.lat !== 0 || radiosondeStationLocation.lon !== 0;
|
||||
|
||||
radiosondeMap = L.map('radiosondeMapContainer', {
|
||||
center: [40, -95],
|
||||
zoom: 4,
|
||||
center: hasLocation ? [radiosondeStationLocation.lat, radiosondeStationLocation.lon] : [40, -95],
|
||||
zoom: hasLocation ? 7 : 4,
|
||||
zoomControl: true,
|
||||
});
|
||||
|
||||
@@ -246,6 +256,50 @@
|
||||
attribution: '© OpenStreetMap © CARTO',
|
||||
maxZoom: 18,
|
||||
}).addTo(radiosondeMap);
|
||||
|
||||
// Add station marker if we have a location
|
||||
if (hasLocation) {
|
||||
radiosondeStationMarker = L.circleMarker(
|
||||
[radiosondeStationLocation.lat, radiosondeStationLocation.lon], {
|
||||
radius: 8,
|
||||
fillColor: '#00e5ff',
|
||||
color: '#00e5ff',
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.5,
|
||||
}).addTo(radiosondeMap);
|
||||
radiosondeStationMarker.bindTooltip('Station', { permanent: false, direction: 'top' });
|
||||
}
|
||||
|
||||
// Try GPS for live position updates
|
||||
fetch('/gps/position')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'ok' && data.position && data.position.latitude != null) {
|
||||
radiosondeStationLocation = { lat: data.position.latitude, lon: data.position.longitude };
|
||||
const ll = [data.position.latitude, data.position.longitude];
|
||||
if (radiosondeStationMarker) {
|
||||
radiosondeStationMarker.setLatLng(ll);
|
||||
} else {
|
||||
radiosondeStationMarker = L.circleMarker(ll, {
|
||||
radius: 8,
|
||||
fillColor: '#00e5ff',
|
||||
color: '#00e5ff',
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.5,
|
||||
}).addTo(radiosondeMap);
|
||||
radiosondeStationMarker.bindTooltip('Station (GPS)', { permanent: false, direction: 'top' });
|
||||
}
|
||||
if (!radiosondeMap._gpsInitialized) {
|
||||
radiosondeMap.setView(ll, 7);
|
||||
radiosondeMap._gpsInitialized = true;
|
||||
}
|
||||
// Re-render cards with updated distances
|
||||
updateRadiosondeCards();
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function updateRadiosondeMap(balloon) {
|
||||
@@ -284,12 +338,19 @@
|
||||
const tempStr = balloon.temp != null ? `${balloon.temp.toFixed(1)} °C` : '--';
|
||||
const humStr = balloon.humidity != null ? `${balloon.humidity.toFixed(0)}%` : '--';
|
||||
const velStr = balloon.vel_v != null ? `${balloon.vel_v.toFixed(1)} m/s` : '--';
|
||||
let distStr = '';
|
||||
if ((radiosondeStationLocation.lat !== 0 || radiosondeStationLocation.lon !== 0) && balloon.lat && balloon.lon) {
|
||||
const distM = radiosondeMap.distance(
|
||||
[radiosondeStationLocation.lat, radiosondeStationLocation.lon], latlng);
|
||||
distStr = `Dist: ${(distM / 1000).toFixed(1)} km<br>`;
|
||||
}
|
||||
radiosondeMarkers.get(id).bindPopup(
|
||||
`<strong>${id}</strong><br>` +
|
||||
`Type: ${balloon.sonde_type || '--'}<br>` +
|
||||
`Alt: ${altStr}<br>` +
|
||||
`Temp: ${tempStr} | Hum: ${humStr}<br>` +
|
||||
`Vert: ${velStr}<br>` +
|
||||
distStr +
|
||||
(balloon.freq ? `Freq: ${balloon.freq.toFixed(3)} MHz` : '')
|
||||
);
|
||||
|
||||
@@ -322,6 +383,7 @@
|
||||
if (!container) return;
|
||||
|
||||
const sorted = Object.values(radiosondeBalloons).sort((a, b) => (b.alt || 0) - (a.alt || 0));
|
||||
const hasStation = radiosondeStationLocation.lat !== 0 || radiosondeStationLocation.lon !== 0;
|
||||
container.innerHTML = sorted.map(b => {
|
||||
const alt = b.alt ? `${Math.round(b.alt).toLocaleString()} m` : '--';
|
||||
const temp = b.temp != null ? `${b.temp.toFixed(1)}°C` : '--';
|
||||
@@ -329,6 +391,13 @@
|
||||
const press = b.pressure != null ? `${b.pressure.toFixed(1)} hPa` : '--';
|
||||
const vel = b.vel_v != null ? `${b.vel_v > 0 ? '+' : ''}${b.vel_v.toFixed(1)} m/s` : '--';
|
||||
const freq = b.freq ? `${b.freq.toFixed(3)} MHz` : '--';
|
||||
let dist = '--';
|
||||
if (hasStation && b.lat && b.lon && radiosondeMap) {
|
||||
const distM = radiosondeMap.distance(
|
||||
[radiosondeStationLocation.lat, radiosondeStationLocation.lon],
|
||||
[b.lat, b.lon]);
|
||||
dist = `${(distM / 1000).toFixed(1)} km`;
|
||||
}
|
||||
return `
|
||||
<div class="radiosonde-card" onclick="radiosondeMap && radiosondeMap.setView([${b.lat || 0}, ${b.lon || 0}], 10)">
|
||||
<div class="radiosonde-card-header">
|
||||
@@ -360,6 +429,10 @@
|
||||
<span class="radiosonde-stat-value">${freq}</span>
|
||||
<span class="radiosonde-stat-label">FREQ</span>
|
||||
</div>
|
||||
<div class="radiosonde-stat">
|
||||
<span class="radiosonde-stat-value">${dist}</span>
|
||||
<span class="radiosonde-stat-label">DIST</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user