mirror of
https://github.com/smittix/intercept.git
synced 2026-06-08 06:01:56 -07:00
Add ground station automation with 6-phase implementation
Phase 1 - Automated observation engine: - utils/ground_station/scheduler.py: GroundStationScheduler fires at AOS/LOS, claims SDR, manages IQBus lifecycle, emits SSE events - utils/ground_station/observation_profile.py: ObservationProfile dataclass + DB CRUD - routes/ground_station.py: REST API for profiles, scheduler, observations, recordings, rotator; SSE stream; /ws/satellite_waterfall WebSocket - DB tables: observation_profiles, ground_station_observations, ground_station_events, sigmf_recordings (added to utils/database.py init_db) - app.py: ground_station_queue, WebSocket init, scheduler startup in _deferred_init - routes/__init__.py: register ground_station_bp Phase 2 - Doppler correction: - utils/doppler.py: generalized DopplerTracker extracted from sstv_decoder.py; accepts satellite name or raw TLE tuple; thread-safe; update_tle() method - utils/sstv/sstv_decoder.py: replace inline DopplerTracker with import from utils.doppler - Scheduler runs 5s retune loop; calls rotator.point_to() if enabled Phase 3 - IQ recording (SigMF): - utils/sigmf.py: SigMFWriter writes .sigmf-data + .sigmf-meta; disk-free guard (500MB) - utils/ground_station/consumers/sigmf_writer.py: SigMFConsumer wraps SigMFWriter Phase 4 - Multi-decoder IQ broadcast pipeline: - utils/ground_station/iq_bus.py: IQBus single-producer fan-out; IQConsumer Protocol - utils/ground_station/consumers/waterfall.py: CU8→FFT→binary frames - utils/ground_station/consumers/fm_demod.py: CU8→FM demod (numpy)→decoder subprocess - utils/ground_station/consumers/gr_satellites.py: CU8→cf32→gr_satellites (optional) Phase 5 - Live spectrum waterfall: - static/js/modes/ground_station_waterfall.js: /ws/satellite_waterfall canvas renderer - Waterfall panel in satellite dashboard sidebar, auto-shown on iq_bus_started SSE event Phase 6 - Antenna rotator control (optional): - utils/rotator.py: RotatorController TCP client for rotctld (Hamlib line protocol) - Rotator panel in satellite dashboard; silently disabled if rotctld unreachable Also fixes pre-existing test_weather_sat_predict.py breakage: - utils/weather_sat_predict.py: rewritten with self-contained skyfield implementation using find_discrete (matching what committed tests expected); adds _format_utc_iso - tests/test_weather_sat_predict.py: add _MOCK_WEATHER_SATS and @patch decorators for tests that assumed NOAA-18 active (decommissioned Jun 2025, now active=False) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,20 @@ import pytest
|
||||
|
||||
from utils.weather_sat_predict import _format_utc_iso, predict_passes
|
||||
|
||||
# Controlled single-satellite config used by tests that need exactly one active satellite.
|
||||
# NOAA-18 was decommissioned Jun 2025 and is inactive in the real WEATHER_SATELLITES,
|
||||
# so tests that assert on satellite-specific fields patch the module-level name.
|
||||
_MOCK_WEATHER_SATS = {
|
||||
'NOAA-18': {
|
||||
'name': 'NOAA 18',
|
||||
'frequency': 137.9125,
|
||||
'mode': 'APT',
|
||||
'pipeline': 'noaa_apt',
|
||||
'tle_key': 'NOAA-18',
|
||||
'active': True,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestPredictPasses:
|
||||
"""Tests for predict_passes() function."""
|
||||
@@ -31,6 +45,7 @@ class TestPredictPasses:
|
||||
|
||||
assert passes == []
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -96,6 +111,7 @@ class TestPredictPasses:
|
||||
assert 'duration' in pass_data
|
||||
assert 'quality' in pass_data
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -150,6 +166,7 @@ class TestPredictPasses:
|
||||
|
||||
assert len(passes) == 0
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -207,6 +224,7 @@ class TestPredictPasses:
|
||||
assert 'trajectory' in passes[0]
|
||||
assert len(passes[0]['trajectory']) == 30
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -281,6 +299,7 @@ class TestPredictPasses:
|
||||
assert 'groundTrack' in passes[0]
|
||||
assert len(passes[0]['groundTrack']) == 60
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -336,6 +355,7 @@ class TestPredictPasses:
|
||||
assert passes[0]['quality'] == 'excellent'
|
||||
assert passes[0]['maxEl'] >= 60
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -391,6 +411,7 @@ class TestPredictPasses:
|
||||
assert passes[0]['quality'] == 'good'
|
||||
assert 30 <= passes[0]['maxEl'] < 60
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -530,6 +551,7 @@ class TestPredictPasses:
|
||||
predict_passes(lat=51.5, lon=-0.1, hours=24, min_elevation=15)
|
||||
# Should not raise
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
@@ -605,6 +627,7 @@ class TestPredictPasses:
|
||||
class TestPassDataStructure:
|
||||
"""Tests for pass data structure."""
|
||||
|
||||
@patch('utils.weather_sat_predict.WEATHER_SATELLITES', _MOCK_WEATHER_SATS)
|
||||
@patch('utils.weather_sat_predict.load')
|
||||
@patch('utils.weather_sat_predict.TLE_SATELLITES')
|
||||
@patch('utils.weather_sat_predict.wgs84')
|
||||
|
||||
Reference in New Issue
Block a user