mirror of
https://github.com/smittix/intercept.git
synced 2026-04-23 22:30:00 -07:00
fix(weather-sat): lower default gain to 30 dB to prevent ADC saturation
Strong passes at 40 dB (the previous default) cause RTL-SDR ADC clipping, producing a distorted IQ stream that SatDump cannot lock onto. 30 dB is a safer starting point that still captures weak passes cleanly. Also adds a UI hint below the gain control explaining the saturation issue. Closes #185
This commit is contained in:
@@ -459,7 +459,7 @@ SATELLITE_TRAJECTORY_POINTS = _get_env_int('SATELLITE_TRAJECTORY_POINTS', 30)
|
||||
SATELLITE_ORBIT_MINUTES = _get_env_int('SATELLITE_ORBIT_MINUTES', 45)
|
||||
|
||||
# Weather satellite settings
|
||||
WEATHER_SAT_DEFAULT_GAIN = _get_env_float('WEATHER_SAT_GAIN', 40.0)
|
||||
WEATHER_SAT_DEFAULT_GAIN = _get_env_float('WEATHER_SAT_GAIN', 30.0)
|
||||
WEATHER_SAT_SAMPLE_RATE = _get_env_int('WEATHER_SAT_SAMPLE_RATE', 2400000)
|
||||
WEATHER_SAT_MIN_ELEVATION = _get_env_float('WEATHER_SAT_MIN_ELEVATION', 15.0)
|
||||
WEATHER_SAT_PREDICTION_HOURS = _get_env_int('WEATHER_SAT_PREDICTION_HOURS', 24)
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"""Weather Satellite decoder routes.
|
||||
|
||||
Provides endpoints for capturing and decoding Meteor LRPT weather
|
||||
imagery, including shared results produced by the ground-station
|
||||
observation pipeline.
|
||||
"""
|
||||
"""Weather Satellite decoder routes.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import queue
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Blueprint, Response, jsonify, request, send_file
|
||||
Provides endpoints for capturing and decoding Meteor LRPT weather
|
||||
imagery, including shared results produced by the ground-station
|
||||
observation pipeline.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import queue
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Blueprint, Response, jsonify, request, send_file
|
||||
|
||||
from utils.logging import get_logger
|
||||
from utils.responses import api_error
|
||||
@@ -33,21 +33,21 @@ from utils.weather_sat import (
|
||||
is_weather_sat_available,
|
||||
)
|
||||
|
||||
logger = get_logger('intercept.weather_sat')
|
||||
|
||||
weather_sat_bp = Blueprint('weather_sat', __name__, url_prefix='/weather-sat')
|
||||
logger = get_logger('intercept.weather_sat')
|
||||
|
||||
weather_sat_bp = Blueprint('weather_sat', __name__, url_prefix='/weather-sat')
|
||||
|
||||
# Queue for SSE progress streaming
|
||||
_weather_sat_queue: queue.Queue = queue.Queue(maxsize=100)
|
||||
|
||||
METEOR_NORAD_IDS = {
|
||||
'METEOR-M2-3': 57166,
|
||||
'METEOR-M2-4': 59051,
|
||||
}
|
||||
ALLOWED_TEST_DECODE_DIRS = (
|
||||
Path(__file__).resolve().parent.parent / 'data',
|
||||
Path(__file__).resolve().parent.parent / 'instance' / 'ground_station' / 'recordings',
|
||||
)
|
||||
_weather_sat_queue: queue.Queue = queue.Queue(maxsize=100)
|
||||
|
||||
METEOR_NORAD_IDS = {
|
||||
'METEOR-M2-3': 57166,
|
||||
'METEOR-M2-4': 59051,
|
||||
}
|
||||
ALLOWED_TEST_DECODE_DIRS = (
|
||||
Path(__file__).resolve().parent.parent / 'data',
|
||||
Path(__file__).resolve().parent.parent / 'instance' / 'ground_station' / 'recordings',
|
||||
)
|
||||
|
||||
|
||||
def _progress_callback(progress: CaptureProgress) -> None:
|
||||
@@ -132,9 +132,9 @@ def start_capture():
|
||||
|
||||
JSON body:
|
||||
{
|
||||
"satellite": "METEOR-M2-3", // Required: satellite key
|
||||
"satellite": "METEOR-M2-3", // Required: satellite key
|
||||
"device": 0, // RTL-SDR device index (default: 0)
|
||||
"gain": 40.0, // SDR gain in dB (default: 40)
|
||||
"gain": 30.0, // SDR gain in dB (default: 30)
|
||||
"bias_t": false // Enable bias-T for LNA (default: false)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ def start_capture():
|
||||
# Validate device index and gain
|
||||
try:
|
||||
device_index = validate_device_index(data.get('device', 0))
|
||||
gain = validate_gain(data.get('gain', 40.0))
|
||||
gain = validate_gain(data.get('gain', 30.0))
|
||||
except ValueError as e:
|
||||
logger.warning('Invalid parameter in start_capture: %s', e)
|
||||
return jsonify({
|
||||
@@ -260,7 +260,7 @@ def test_decode():
|
||||
|
||||
JSON body:
|
||||
{
|
||||
"satellite": "METEOR-M2-3", // Required: satellite key
|
||||
"satellite": "METEOR-M2-3", // Required: satellite key
|
||||
"input_file": "/path/to/file", // Required: server-side file path
|
||||
"sample_rate": 1000000 // Sample rate in Hz (default: 1000000)
|
||||
}
|
||||
@@ -304,14 +304,14 @@ def test_decode():
|
||||
from pathlib import Path
|
||||
input_path = Path(input_file)
|
||||
|
||||
# Restrict test-decode to application-owned sample and recording paths.
|
||||
try:
|
||||
resolved = input_path.resolve()
|
||||
if not any(resolved.is_relative_to(base) for base in ALLOWED_TEST_DECODE_DIRS):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'input_file must be under INTERCEPT data or ground-station recordings'
|
||||
}), 403
|
||||
# Restrict test-decode to application-owned sample and recording paths.
|
||||
try:
|
||||
resolved = input_path.resolve()
|
||||
if not any(resolved.is_relative_to(base) for base in ALLOWED_TEST_DECODE_DIRS):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'input_file must be under INTERCEPT data or ground-station recordings'
|
||||
}), 403
|
||||
except (OSError, ValueError):
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
@@ -388,8 +388,8 @@ def stop_capture():
|
||||
return jsonify({'status': 'stopped'})
|
||||
|
||||
|
||||
@weather_sat_bp.route('/images')
|
||||
def list_images():
|
||||
@weather_sat_bp.route('/images')
|
||||
def list_images():
|
||||
"""Get list of decoded weather satellite images.
|
||||
|
||||
Query parameters:
|
||||
@@ -399,41 +399,41 @@ def list_images():
|
||||
Returns:
|
||||
JSON with list of decoded images.
|
||||
"""
|
||||
decoder = get_weather_sat_decoder()
|
||||
images = [
|
||||
{
|
||||
**img.to_dict(),
|
||||
'source': 'weather_sat',
|
||||
'deletable': True,
|
||||
}
|
||||
for img in decoder.get_images()
|
||||
]
|
||||
images.extend(_get_ground_station_images())
|
||||
|
||||
# Filter by satellite if specified
|
||||
satellite_filter = request.args.get('satellite')
|
||||
if satellite_filter:
|
||||
images = [
|
||||
img for img in images
|
||||
if str(img.get('satellite', '')).upper() == satellite_filter.upper()
|
||||
]
|
||||
|
||||
images.sort(key=lambda img: img.get('timestamp') or '', reverse=True)
|
||||
|
||||
# Apply limit
|
||||
limit = request.args.get('limit', type=int)
|
||||
if limit and limit > 0:
|
||||
images = images[:limit]
|
||||
|
||||
return jsonify({
|
||||
'status': 'ok',
|
||||
'images': images,
|
||||
'count': len(images),
|
||||
})
|
||||
decoder = get_weather_sat_decoder()
|
||||
images = [
|
||||
{
|
||||
**img.to_dict(),
|
||||
'source': 'weather_sat',
|
||||
'deletable': True,
|
||||
}
|
||||
for img in decoder.get_images()
|
||||
]
|
||||
images.extend(_get_ground_station_images())
|
||||
|
||||
# Filter by satellite if specified
|
||||
satellite_filter = request.args.get('satellite')
|
||||
if satellite_filter:
|
||||
images = [
|
||||
img for img in images
|
||||
if str(img.get('satellite', '')).upper() == satellite_filter.upper()
|
||||
]
|
||||
|
||||
images.sort(key=lambda img: img.get('timestamp') or '', reverse=True)
|
||||
|
||||
# Apply limit
|
||||
limit = request.args.get('limit', type=int)
|
||||
if limit and limit > 0:
|
||||
images = images[:limit]
|
||||
|
||||
return jsonify({
|
||||
'status': 'ok',
|
||||
'images': images,
|
||||
'count': len(images),
|
||||
})
|
||||
|
||||
|
||||
@weather_sat_bp.route('/images/<filename>')
|
||||
def get_image(filename: str):
|
||||
@weather_sat_bp.route('/images/<filename>')
|
||||
def get_image(filename: str):
|
||||
"""Serve a decoded weather satellite image file.
|
||||
|
||||
Args:
|
||||
@@ -456,38 +456,38 @@ def get_image(filename: str):
|
||||
if not image_path.exists():
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
mimetype = 'image/png' if filename.endswith('.png') else 'image/jpeg'
|
||||
return send_file(image_path, mimetype=mimetype)
|
||||
|
||||
|
||||
@weather_sat_bp.route('/images/shared/<int:output_id>')
|
||||
def get_shared_image(output_id: int):
|
||||
"""Serve a Meteor image stored in ground-station outputs."""
|
||||
try:
|
||||
from utils.database import get_db
|
||||
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
'''
|
||||
SELECT file_path FROM ground_station_outputs
|
||||
WHERE id=? AND output_type='image'
|
||||
''',
|
||||
(output_id,),
|
||||
).fetchone()
|
||||
except Exception as e:
|
||||
logger.warning("Failed to load shared weather image %s: %s", output_id, e)
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
if not row:
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
image_path = Path(row['file_path'])
|
||||
if not image_path.exists():
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
suffix = image_path.suffix.lower()
|
||||
mimetype = 'image/png' if suffix == '.png' else 'image/jpeg'
|
||||
return send_file(image_path, mimetype=mimetype)
|
||||
mimetype = 'image/png' if filename.endswith('.png') else 'image/jpeg'
|
||||
return send_file(image_path, mimetype=mimetype)
|
||||
|
||||
|
||||
@weather_sat_bp.route('/images/shared/<int:output_id>')
|
||||
def get_shared_image(output_id: int):
|
||||
"""Serve a Meteor image stored in ground-station outputs."""
|
||||
try:
|
||||
from utils.database import get_db
|
||||
|
||||
with get_db() as conn:
|
||||
row = conn.execute(
|
||||
'''
|
||||
SELECT file_path FROM ground_station_outputs
|
||||
WHERE id=? AND output_type='image'
|
||||
''',
|
||||
(output_id,),
|
||||
).fetchone()
|
||||
except Exception as e:
|
||||
logger.warning("Failed to load shared weather image %s: %s", output_id, e)
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
if not row:
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
image_path = Path(row['file_path'])
|
||||
if not image_path.exists():
|
||||
return api_error('Image not found', 404)
|
||||
|
||||
suffix = image_path.suffix.lower()
|
||||
mimetype = 'image/png' if suffix == '.png' else 'image/jpeg'
|
||||
return send_file(image_path, mimetype=mimetype)
|
||||
|
||||
|
||||
@weather_sat_bp.route('/images/<filename>', methods=['DELETE'])
|
||||
@@ -512,71 +512,71 @@ def delete_image(filename: str):
|
||||
|
||||
|
||||
@weather_sat_bp.route('/images', methods=['DELETE'])
|
||||
def delete_all_images():
|
||||
def delete_all_images():
|
||||
"""Delete all decoded weather satellite images.
|
||||
|
||||
Returns:
|
||||
JSON with count of deleted images.
|
||||
"""
|
||||
decoder = get_weather_sat_decoder()
|
||||
count = decoder.delete_all_images()
|
||||
return jsonify({'status': 'ok', 'deleted': count})
|
||||
|
||||
|
||||
def _get_ground_station_images() -> list[dict]:
|
||||
try:
|
||||
from utils.database import get_db
|
||||
|
||||
with get_db() as conn:
|
||||
rows = conn.execute(
|
||||
'''
|
||||
SELECT id, norad_id, file_path, metadata_json, created_at
|
||||
FROM ground_station_outputs
|
||||
WHERE output_type='image' AND backend='meteor_lrpt'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 200
|
||||
'''
|
||||
).fetchall()
|
||||
except Exception as e:
|
||||
logger.debug("Failed to fetch ground-station weather outputs: %s", e)
|
||||
return []
|
||||
|
||||
images: list[dict] = []
|
||||
for row in rows:
|
||||
file_path = Path(row['file_path'])
|
||||
if not file_path.exists():
|
||||
continue
|
||||
|
||||
metadata = {}
|
||||
raw_metadata = row['metadata_json']
|
||||
if raw_metadata:
|
||||
try:
|
||||
metadata = json.loads(raw_metadata)
|
||||
except json.JSONDecodeError:
|
||||
metadata = {}
|
||||
|
||||
satellite = metadata.get('satellite') or _satellite_from_norad(row['norad_id'])
|
||||
images.append({
|
||||
'filename': file_path.name,
|
||||
'satellite': satellite,
|
||||
'mode': metadata.get('mode', 'LRPT'),
|
||||
'timestamp': metadata.get('timestamp') or row['created_at'],
|
||||
'frequency': metadata.get('frequency', 137.9),
|
||||
'size_bytes': metadata.get('size_bytes') or file_path.stat().st_size,
|
||||
'product': metadata.get('product', ''),
|
||||
'url': f"/weather-sat/images/shared/{row['id']}",
|
||||
'source': 'ground_station',
|
||||
'deletable': False,
|
||||
'output_id': row['id'],
|
||||
})
|
||||
return images
|
||||
|
||||
|
||||
def _satellite_from_norad(norad_id: int | None) -> str:
|
||||
for satellite, known_norad in METEOR_NORAD_IDS.items():
|
||||
if known_norad == norad_id:
|
||||
return satellite
|
||||
return 'METEOR'
|
||||
count = decoder.delete_all_images()
|
||||
return jsonify({'status': 'ok', 'deleted': count})
|
||||
|
||||
|
||||
def _get_ground_station_images() -> list[dict]:
|
||||
try:
|
||||
from utils.database import get_db
|
||||
|
||||
with get_db() as conn:
|
||||
rows = conn.execute(
|
||||
'''
|
||||
SELECT id, norad_id, file_path, metadata_json, created_at
|
||||
FROM ground_station_outputs
|
||||
WHERE output_type='image' AND backend='meteor_lrpt'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 200
|
||||
'''
|
||||
).fetchall()
|
||||
except Exception as e:
|
||||
logger.debug("Failed to fetch ground-station weather outputs: %s", e)
|
||||
return []
|
||||
|
||||
images: list[dict] = []
|
||||
for row in rows:
|
||||
file_path = Path(row['file_path'])
|
||||
if not file_path.exists():
|
||||
continue
|
||||
|
||||
metadata = {}
|
||||
raw_metadata = row['metadata_json']
|
||||
if raw_metadata:
|
||||
try:
|
||||
metadata = json.loads(raw_metadata)
|
||||
except json.JSONDecodeError:
|
||||
metadata = {}
|
||||
|
||||
satellite = metadata.get('satellite') or _satellite_from_norad(row['norad_id'])
|
||||
images.append({
|
||||
'filename': file_path.name,
|
||||
'satellite': satellite,
|
||||
'mode': metadata.get('mode', 'LRPT'),
|
||||
'timestamp': metadata.get('timestamp') or row['created_at'],
|
||||
'frequency': metadata.get('frequency', 137.9),
|
||||
'size_bytes': metadata.get('size_bytes') or file_path.stat().st_size,
|
||||
'product': metadata.get('product', ''),
|
||||
'url': f"/weather-sat/images/shared/{row['id']}",
|
||||
'source': 'ground_station',
|
||||
'deletable': False,
|
||||
'output_id': row['id'],
|
||||
})
|
||||
return images
|
||||
|
||||
|
||||
def _satellite_from_norad(norad_id: int | None) -> str:
|
||||
for satellite, known_norad in METEOR_NORAD_IDS.items():
|
||||
if known_norad == norad_id:
|
||||
return satellite
|
||||
return 'METEOR'
|
||||
|
||||
|
||||
@weather_sat_bp.route('/stream')
|
||||
@@ -689,7 +689,7 @@ def enable_schedule():
|
||||
"longitude": -0.1, // Required
|
||||
"min_elevation": 15, // Minimum pass elevation (default: 15)
|
||||
"device": 0, // RTL-SDR device index (default: 0)
|
||||
"gain": 40.0, // SDR gain (default: 40)
|
||||
"gain": 30.0, // SDR gain (default: 30)
|
||||
"bias_t": false // Enable bias-T (default: false)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<div id="weatherSatMode" class="mode-content">
|
||||
<div class="section">
|
||||
<h3>Weather Satellite Decoder</h3>
|
||||
<div class="alpha-mode-notice">
|
||||
ALPHA: Weather Satellite capture is experimental and may fail depending on SatDump version, SDR driver support, and pass conditions.
|
||||
</div>
|
||||
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 12px;">
|
||||
Receive and decode Meteor LRPT weather imagery.
|
||||
Uses SatDump for live SDR capture and image processing, and also shows Meteor imagery produced by the ground-station scheduler.
|
||||
</p>
|
||||
<div class="alpha-mode-notice">
|
||||
ALPHA: Weather Satellite capture is experimental and may fail depending on SatDump version, SDR driver support, and pass conditions.
|
||||
</div>
|
||||
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 12px;">
|
||||
Receive and decode Meteor LRPT weather imagery.
|
||||
Uses SatDump for live SDR capture and image processing, and also shows Meteor imagery produced by the ground-station scheduler.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -18,11 +18,12 @@
|
||||
<select id="weatherSatSelect" class="mode-select">
|
||||
<option value="METEOR-M2-3" selected>Meteor-M2-3 (137.900 MHz LRPT)</option>
|
||||
<option value="METEOR-M2-4">Meteor-M2-4 (137.900 MHz LRPT)</option>
|
||||
</select>
|
||||
</div>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Gain (dB)</label>
|
||||
<input type="number" id="weatherSatGain" value="40" step="0.1" min="0" max="50">
|
||||
<input type="number" id="weatherSatGain" value="30" step="0.1" min="0" max="50">
|
||||
<p class="info-text" style="font-size: 10px; color: var(--text-dim); margin-top: 3px;">Reduce if decoding fails on strong passes (ADC saturation).</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; gap: 6px;">
|
||||
@@ -69,7 +70,7 @@
|
||||
<li><strong style="color: var(--text-primary);">Connection:</strong> Solder elements to coax center + shield, connect to SDR via SMA</li>
|
||||
</ul>
|
||||
<p style="margin-top: 6px; color: var(--text-dim); font-style: italic;">
|
||||
Best starter antenna. Good enough for a clean Meteor LRPT pass when the satellite gets high overhead.
|
||||
Best starter antenna. Good enough for a clean Meteor LRPT pass when the satellite gets high overhead.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -132,8 +133,8 @@
|
||||
<li><strong style="color: var(--text-primary);">Antenna up:</strong> Point the antenna straight UP (zenith) for best overhead coverage</li>
|
||||
<li><strong style="color: var(--text-primary);">Avoid:</strong> Metal roofs, power lines, buildings blocking the sky</li>
|
||||
<li><strong style="color: var(--text-primary);">Coax length:</strong> Keep short (<10m). Signal loss at 137 MHz is ~3 dB per 10m of RG-58</li>
|
||||
<li><strong style="color: var(--text-primary);">LNA:</strong> Mount at the antenna feed point, NOT at the SDR end.
|
||||
Recommended: a low-noise 137 MHz filtered LNA near the antenna feed point</li>
|
||||
<li><strong style="color: var(--text-primary);">LNA:</strong> Mount at the antenna feed point, NOT at the SDR end.
|
||||
Recommended: a low-noise 137 MHz filtered LNA near the antenna feed point</li>
|
||||
<li><strong style="color: var(--text-primary);">Bias-T:</strong> Enable the Bias-T checkbox above if your LNA is powered via the coax from the SDR</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -162,9 +163,9 @@
|
||||
<td style="padding: 3px 4px; color: var(--text-dim);">Polarization</td>
|
||||
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">RHCP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 3px 4px; color: var(--text-dim);">Meteor (LRPT) bandwidth</td>
|
||||
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">~140 kHz</td>
|
||||
<tr>
|
||||
<td style="padding: 3px 4px; color: var(--text-dim);">Meteor (LRPT) bandwidth</td>
|
||||
<td style="padding: 3px 4px; color: var(--text-primary); text-align: right;">~140 kHz</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -177,29 +178,29 @@
|
||||
<span class="wxsat-collapse-icon collapsed" style="font-size: 10px; transition: transform 0.2s; display: inline-block;">▼</span>
|
||||
</h3>
|
||||
<div class="wxsat-test-decode-body collapsed" style="overflow: hidden;">
|
||||
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 8px;">
|
||||
Decode a pre-recorded Meteor IQ file without SDR hardware.
|
||||
Shared ground-station recordings are also accepted by the backend.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label>Satellite</label>
|
||||
<select id="wxsatTestSatSelect" class="mode-select">
|
||||
<option value="METEOR-M2-3" selected>Meteor-M2-3 (LRPT)</option>
|
||||
<option value="METEOR-M2-4">Meteor-M2-4 (LRPT)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>File Path (server-side)</label>
|
||||
<input type="text" id="wxsatTestFilePath" value="data/weather_sat/samples/meteor_lrpt.sigmf-data" style="font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 11px;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Sample Rate</label>
|
||||
<select id="wxsatTestSampleRate" class="mode-select">
|
||||
<option value="500000">500 kHz (IQ LRPT)</option>
|
||||
<option value="1000000">1 MHz (IQ narrow)</option>
|
||||
<option value="2400000" selected>2.4 MHz (INTERCEPT default)</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 8px;">
|
||||
Decode a pre-recorded Meteor IQ file without SDR hardware.
|
||||
Shared ground-station recordings are also accepted by the backend.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label>Satellite</label>
|
||||
<select id="wxsatTestSatSelect" class="mode-select">
|
||||
<option value="METEOR-M2-3" selected>Meteor-M2-3 (LRPT)</option>
|
||||
<option value="METEOR-M2-4">Meteor-M2-4 (LRPT)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>File Path (server-side)</label>
|
||||
<input type="text" id="wxsatTestFilePath" value="data/weather_sat/samples/meteor_lrpt.sigmf-data" style="font-family: 'Roboto Condensed', 'Arial Narrow', sans-serif; font-size: 11px;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Sample Rate</label>
|
||||
<select id="wxsatTestSampleRate" class="mode-select">
|
||||
<option value="500000">500 kHz (IQ LRPT)</option>
|
||||
<option value="1000000">1 MHz (IQ narrow)</option>
|
||||
<option value="2400000" selected>2.4 MHz (INTERCEPT default)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="mode-btn" onclick="WeatherSat.testDecode()" style="width: 100%; margin-top: 4px;">
|
||||
Test Decode
|
||||
</button>
|
||||
@@ -226,12 +227,12 @@
|
||||
<div class="section">
|
||||
<h3>Resources</h3>
|
||||
<div style="display: flex; flex-direction: column; gap: 6px;">
|
||||
<a href="https://github.com/SatDump/SatDump" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
|
||||
SatDump Documentation
|
||||
</a>
|
||||
<a href="https://www.rtl-sdr.com/rtl-sdr-tutorial-receiving-meteor-m2-weather-satellite-images/" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
|
||||
Meteor Reception Guide
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://github.com/SatDump/SatDump" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
|
||||
SatDump Documentation
|
||||
</a>
|
||||
<a href="https://www.rtl-sdr.com/rtl-sdr-tutorial-receiving-meteor-m2-weather-satellite-images/" target="_blank" rel="noopener" class="preset-btn" style="text-decoration: none; text-align: center;">
|
||||
Meteor Reception Guide
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user