mirror of
https://github.com/smittix/intercept.git
synced 2026-07-10 10:38:12 -07:00
Add weather satellite auto-scheduler, polar plot, ground track map, and rtlamr Docker support
- Fix SDR device stuck claimed on capture failure via on_complete callback - Improve SatDump output parsing to emit all lines (throttled 2s) for real-time feedback - Extract shared pass prediction into utils/weather_sat_predict.py with trajectory/ground track support - Add auto-scheduler (utils/weather_sat_scheduler.py) using threading.Timer for unattended captures - Add scheduler API endpoints (enable/disable/status/passes/skip) with SSE event notifications - Add countdown timer (D/H/M/S) with imminent/active glow states - Add 24h timeline bar with colored pass markers and current-time cursor - Add canvas polar plot showing az/el trajectory arc with cardinal directions - Add Leaflet ground track map with satellite path and observer marker - Restructure to 3-column layout (passes | polar+map | gallery) with responsive stacking - Add auto-schedule toggle in strip bar and sidebar - Add rtlamr (Go utility meter decoder) to Dockerfile Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+29
-10
@@ -149,6 +149,7 @@ class WeatherSatDecoder:
|
||||
self._capture_start_time: float = 0
|
||||
self._device_index: int = 0
|
||||
self._capture_output_dir: Path | None = None
|
||||
self._on_complete_callback: Callable[[], None] | None = None
|
||||
|
||||
# Ensure output directory exists
|
||||
self._output_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -189,6 +190,10 @@ class WeatherSatDecoder:
|
||||
"""Set callback for capture progress updates."""
|
||||
self._callback = callback
|
||||
|
||||
def set_on_complete(self, callback: Callable[[], None]) -> None:
|
||||
"""Set callback invoked when capture process ends (for SDR release)."""
|
||||
self._on_complete_callback = callback
|
||||
|
||||
def start(
|
||||
self,
|
||||
satellite: str,
|
||||
@@ -320,6 +325,8 @@ class WeatherSatDecoder:
|
||||
if not self._process or not self._process.stdout:
|
||||
return
|
||||
|
||||
last_emit_time = 0.0
|
||||
|
||||
try:
|
||||
for line in iter(self._process.stdout.readline, ''):
|
||||
if not self._running:
|
||||
@@ -331,12 +338,11 @@ class WeatherSatDecoder:
|
||||
|
||||
logger.debug(f"satdump: {line}")
|
||||
|
||||
# Parse progress from SatDump output
|
||||
elapsed = int(time.time() - self._capture_start_time)
|
||||
now = time.time()
|
||||
|
||||
# SatDump outputs progress info - parse key indicators
|
||||
# Parse progress from SatDump output
|
||||
if 'Progress' in line or 'progress' in line:
|
||||
# Try to extract percentage
|
||||
match = re.search(r'(\d+(?:\.\d+)?)\s*%', line)
|
||||
pct = int(float(match.group(1))) if match else 0
|
||||
self._emit_progress(CaptureProgress(
|
||||
@@ -348,6 +354,7 @@ class WeatherSatDecoder:
|
||||
progress_percent=pct,
|
||||
elapsed_seconds=elapsed,
|
||||
))
|
||||
last_emit_time = now
|
||||
elif 'Saved' in line or 'saved' in line or 'Writing' in line:
|
||||
self._emit_progress(CaptureProgress(
|
||||
status='decoding',
|
||||
@@ -357,6 +364,7 @@ class WeatherSatDecoder:
|
||||
message=line,
|
||||
elapsed_seconds=elapsed,
|
||||
))
|
||||
last_emit_time = now
|
||||
elif 'error' in line.lower() or 'fail' in line.lower():
|
||||
self._emit_progress(CaptureProgress(
|
||||
status='capturing',
|
||||
@@ -366,25 +374,29 @@ class WeatherSatDecoder:
|
||||
message=line,
|
||||
elapsed_seconds=elapsed,
|
||||
))
|
||||
last_emit_time = now
|
||||
else:
|
||||
# Generic progress update every ~10 seconds
|
||||
if elapsed % 10 == 0:
|
||||
# Emit all output lines, throttled to every 2 seconds
|
||||
if now - last_emit_time >= 2.0:
|
||||
self._emit_progress(CaptureProgress(
|
||||
status='capturing',
|
||||
satellite=self._current_satellite,
|
||||
frequency=self._current_frequency,
|
||||
mode=self._current_mode,
|
||||
message=f"Capturing... ({elapsed}s elapsed)",
|
||||
message=line,
|
||||
elapsed_seconds=elapsed,
|
||||
))
|
||||
last_emit_time = now
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading SatDump output: {e}")
|
||||
finally:
|
||||
# Process ended
|
||||
if self._running:
|
||||
self._running = False
|
||||
elapsed = int(time.time() - self._capture_start_time)
|
||||
# Process ended — release resources
|
||||
was_running = self._running
|
||||
self._running = False
|
||||
elapsed = int(time.time() - self._capture_start_time) if self._capture_start_time else 0
|
||||
|
||||
if was_running:
|
||||
self._emit_progress(CaptureProgress(
|
||||
status='complete',
|
||||
satellite=self._current_satellite,
|
||||
@@ -394,6 +406,13 @@ class WeatherSatDecoder:
|
||||
elapsed_seconds=elapsed,
|
||||
))
|
||||
|
||||
# Notify route layer to release SDR device
|
||||
if self._on_complete_callback:
|
||||
try:
|
||||
self._on_complete_callback()
|
||||
except Exception as e:
|
||||
logger.error(f"Error in on_complete callback: {e}")
|
||||
|
||||
def _watch_images(self) -> None:
|
||||
"""Watch output directory for new decoded images."""
|
||||
if not self._capture_output_dir:
|
||||
|
||||
Reference in New Issue
Block a user