mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
190 lines
5.2 KiB
Python
190 lines
5.2 KiB
Python
"""Meteor LRPT offline decode backend for ground-station observations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import threading
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from utils.logging import get_logger
|
|
from utils.weather_sat import WeatherSatDecoder
|
|
|
|
logger = get_logger('intercept.ground_station.meteor_backend')
|
|
|
|
OUTPUT_ROOT = Path('instance/ground_station/weather_outputs')
|
|
DECODE_TIMEOUT_SECONDS = 30 * 60
|
|
|
|
_NORAD_TO_SAT_KEY = {
|
|
57166: 'METEOR-M2-3',
|
|
59051: 'METEOR-M2-4',
|
|
}
|
|
|
|
|
|
def resolve_meteor_satellite_key(norad_id: int, satellite_name: str) -> str | None:
|
|
if norad_id in _NORAD_TO_SAT_KEY:
|
|
return _NORAD_TO_SAT_KEY[norad_id]
|
|
|
|
upper = str(satellite_name or '').upper()
|
|
if 'M2-4' in upper:
|
|
return 'METEOR-M2-4'
|
|
if 'M2-3' in upper or 'METEOR' in upper:
|
|
return 'METEOR-M2-3'
|
|
return None
|
|
|
|
|
|
def launch_meteor_decode(
|
|
*,
|
|
obs_db_id: int | None,
|
|
norad_id: int,
|
|
satellite_name: str,
|
|
sample_rate: int,
|
|
data_path: Path,
|
|
emit_event,
|
|
register_output,
|
|
) -> None:
|
|
"""Run Meteor LRPT offline decode in a background thread."""
|
|
t = threading.Thread(
|
|
target=_run_decode,
|
|
kwargs={
|
|
'obs_db_id': obs_db_id,
|
|
'norad_id': norad_id,
|
|
'satellite_name': satellite_name,
|
|
'sample_rate': sample_rate,
|
|
'data_path': data_path,
|
|
'emit_event': emit_event,
|
|
'register_output': register_output,
|
|
},
|
|
daemon=True,
|
|
name=f'gs-meteor-decode-{norad_id}',
|
|
)
|
|
t.start()
|
|
|
|
|
|
def _run_decode(
|
|
*,
|
|
obs_db_id: int | None,
|
|
norad_id: int,
|
|
satellite_name: str,
|
|
sample_rate: int,
|
|
data_path: Path,
|
|
emit_event,
|
|
register_output,
|
|
) -> None:
|
|
sat_key = resolve_meteor_satellite_key(norad_id, satellite_name)
|
|
if not sat_key:
|
|
emit_event({
|
|
'type': 'weather_decode_failed',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'message': 'No Meteor satellite mapping is available for this observation.',
|
|
})
|
|
return
|
|
|
|
output_dir = OUTPUT_ROOT / f'{norad_id}_{int(time.time())}'
|
|
decoder = WeatherSatDecoder(output_dir=output_dir)
|
|
if decoder.decoder_available is None:
|
|
emit_event({
|
|
'type': 'weather_decode_failed',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'message': 'SatDump backend is not available for Meteor LRPT decode.',
|
|
})
|
|
return
|
|
|
|
def _progress_cb(progress):
|
|
progress_event = progress.to_dict()
|
|
progress_event.pop('type', None)
|
|
emit_event({
|
|
'type': 'weather_decode_progress',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
**progress_event,
|
|
})
|
|
|
|
decoder.set_callback(_progress_cb)
|
|
emit_event({
|
|
'type': 'weather_decode_started',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'input_path': str(data_path),
|
|
})
|
|
|
|
ok, error = decoder.start_from_file(
|
|
satellite=sat_key,
|
|
input_file=data_path,
|
|
sample_rate=sample_rate,
|
|
)
|
|
if not ok:
|
|
emit_event({
|
|
'type': 'weather_decode_failed',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'message': error or 'Meteor decode failed to start.',
|
|
})
|
|
return
|
|
|
|
started = time.time()
|
|
while decoder.is_running and (time.time() - started) < DECODE_TIMEOUT_SECONDS:
|
|
time.sleep(1.0)
|
|
|
|
if decoder.is_running:
|
|
decoder.stop()
|
|
emit_event({
|
|
'type': 'weather_decode_failed',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'message': 'Meteor decode timed out.',
|
|
})
|
|
return
|
|
|
|
images = decoder.get_images()
|
|
if not images:
|
|
emit_event({
|
|
'type': 'weather_decode_failed',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'message': 'Decode completed but no image outputs were produced.',
|
|
})
|
|
return
|
|
|
|
outputs = []
|
|
for image in images:
|
|
metadata = {
|
|
'satellite': image.satellite,
|
|
'mode': image.mode,
|
|
'frequency': image.frequency,
|
|
'product': image.product,
|
|
'timestamp': image.timestamp.isoformat(),
|
|
'size_bytes': image.size_bytes,
|
|
}
|
|
output_id = register_output(
|
|
observation_id=obs_db_id,
|
|
norad_id=norad_id,
|
|
output_type='image',
|
|
backend='meteor_lrpt',
|
|
file_path=image.path,
|
|
preview_path=image.path,
|
|
metadata=metadata,
|
|
)
|
|
outputs.append({
|
|
'id': output_id,
|
|
'file_path': str(image.path),
|
|
'filename': image.filename,
|
|
'product': image.product,
|
|
})
|
|
|
|
emit_event({
|
|
'type': 'weather_decode_complete',
|
|
'norad_id': norad_id,
|
|
'satellite': satellite_name,
|
|
'backend': 'meteor_lrpt',
|
|
'outputs': outputs,
|
|
})
|