diff --git a/routes/__init__.py b/routes/__init__.py index 0cdc50f..ddb23d4 100644 --- a/routes/__init__.py +++ b/routes/__init__.py @@ -20,12 +20,12 @@ def register_blueprints(app): from .correlation import correlation_bp from .dsc import dsc_bp from .gps import gps_bp + from .ground_station import ground_station_bp from .listening_post import receiver_bp from .meshtastic import meshtastic_bp from .meteor_websocket import meteor_bp from .morse import morse_bp from .offline import offline_bp - from .ground_station import ground_station_bp from .ook import ook_bp from .pager import pager_bp from .radiosonde import radiosonde_bp diff --git a/routes/controller.py b/routes/controller.py index c91bbaf..500a71b 100644 --- a/routes/controller.py +++ b/routes/controller.py @@ -728,8 +728,9 @@ def agent_management_page(): @controller_bp.route('/monitor') def network_monitor_page(): """Render the network monitor page for multi-agent aggregated view.""" - from flask import render_template - from config import VERSION + from flask import render_template + + from config import VERSION return render_template('network_monitor.html', version=VERSION) diff --git a/routes/ground_station.py b/routes/ground_station.py index 8144931..d9aa213 100644 --- a/routes/ground_station.py +++ b/routes/ground_station.py @@ -11,7 +11,6 @@ from __future__ import annotations import json import queue -import threading from pathlib import Path from flask import Blueprint, Response, jsonify, request, send_file @@ -102,6 +101,8 @@ def update_profile(norad_id: int): data = request.get_json(force=True) or {} from utils.ground_station.observation_profile import ( get_profile as _get, + ) + from utils.ground_station.observation_profile import ( legacy_decoder_to_tasks, normalize_tasks, save_profile, @@ -443,7 +444,6 @@ def init_ground_station_websocket(app) -> None: @sock.route('/ws/satellite_waterfall') def satellite_waterfall_ws(ws): """Stream binary waterfall frames from the active ground station IQ bus.""" - import app as _app_mod scheduler = _get_scheduler() wf_queue = scheduler.waterfall_queue diff --git a/routes/radiosonde.py b/routes/radiosonde.py index b9115d9..0f67f5e 100644 --- a/routes/radiosonde.py +++ b/routes/radiosonde.py @@ -88,7 +88,7 @@ def find_auto_rx() -> str | None: def _resolve_shebang_interpreter(script_path: str) -> str | None: """Resolve a Python interpreter from a script shebang if possible.""" try: - with open(script_path, 'r', encoding='utf-8', errors='ignore') as handle: + with open(script_path, encoding='utf-8', errors='ignore') as handle: first_line = handle.readline().strip() except OSError: return None diff --git a/routes/satellite.py b/routes/satellite.py index b0e12e6..1ce501d 100644 --- a/routes/satellite.py +++ b/routes/satellite.py @@ -12,7 +12,6 @@ import requests from flask import Blueprint, Response, jsonify, make_response, render_template, request from config import SHARED_OBSERVER_LOCATION_ENABLED -from utils.sse import sse_stream_fanout from data.satellites import TLE_SATELLITES from utils.database import ( add_tracked_satellite, @@ -23,6 +22,7 @@ from utils.database import ( ) from utils.logging import satellite_logger as logger from utils.responses import api_error +from utils.sse import sse_stream_fanout from utils.validation import validate_elevation, validate_hours, validate_latitude, validate_longitude satellite_bp = Blueprint('satellite', __name__, url_prefix='/satellite') @@ -55,6 +55,7 @@ _TRACK_CACHE_TTL = 1800 # Thread pool for background ground-track computation (non-blocking from 1Hz tracker loop) from concurrent.futures import ThreadPoolExecutor as _ThreadPoolExecutor + _track_executor = _ThreadPoolExecutor(max_workers=2, thread_name_prefix='sat-track') _track_in_progress: set = set() # cache keys currently being computed _pass_cache: dict = {} @@ -726,6 +727,7 @@ def get_transmitters_endpoint(norad_id: int): def parse_packet(): """Parse a raw satellite telemetry packet (base64-encoded).""" import base64 + from utils.satellite_telemetry import auto_parse data = request.json or {} try: diff --git a/semver.py b/semver.py index 9f4278f..d1a5eb1 100644 --- a/semver.py +++ b/semver.py @@ -7,8 +7,8 @@ dependency is missing from the target Python environment. from __future__ import annotations -from dataclasses import dataclass, replace import re +from dataclasses import dataclass, replace from typing import Iterable _SEMVER_RE = re.compile( @@ -56,7 +56,7 @@ class VersionInfo: build: str | None = None @classmethod - def parse(cls, version: str) -> "VersionInfo": + def parse(cls, version: str) -> VersionInfo: match = _SEMVER_RE.match(str(version)) if not match: raise ValueError(f"{version!r} is not valid SemVer") @@ -77,25 +77,25 @@ class VersionInfo: def is_valid(cls, version: str) -> bool: return cls.isvalid(version) - def compare(self, other: str | "VersionInfo") -> int: + def compare(self, other: str | VersionInfo) -> int: return compare(self, other) def match(self, expr: str) -> bool: return match(str(self), expr) - def bump_major(self) -> "VersionInfo": + def bump_major(self) -> VersionInfo: return VersionInfo(self.major + 1, 0, 0) - def bump_minor(self) -> "VersionInfo": + def bump_minor(self) -> VersionInfo: return VersionInfo(self.major, self.minor + 1, 0) - def bump_patch(self) -> "VersionInfo": + def bump_patch(self) -> VersionInfo: return VersionInfo(self.major, self.minor, self.patch + 1) - def finalize_version(self) -> "VersionInfo": + def finalize_version(self) -> VersionInfo: return VersionInfo(self.major, self.minor, self.patch) - def replace(self, **changes) -> "VersionInfo": + def replace(self, **changes) -> VersionInfo: return replace(self, **changes) def __str__(self) -> str: diff --git a/tests/test_satellite.py b/tests/test_satellite.py index cc86e1f..d6b45f5 100644 --- a/tests/test_satellite.py +++ b/tests/test_satellite.py @@ -80,7 +80,6 @@ def test_tracker_position_has_no_observer_fields(): receives the client's actual location, should emit elevation/azimuth/ distance/visible. """ - import sys from routes.satellite import _start_satellite_tracker ISS_TLE = ( diff --git a/utils/ground_station/consumers/sigmf_writer.py b/utils/ground_station/consumers/sigmf_writer.py index 4e5385c..d49e87d 100644 --- a/utils/ground_station/consumers/sigmf_writer.py +++ b/utils/ground_station/consumers/sigmf_writer.py @@ -14,7 +14,7 @@ class SigMFConsumer: def __init__( self, metadata: SigMFMetadata, - on_complete: 'callable | None' = None, + on_complete: callable | None = None, ): """ Args: diff --git a/utils/ground_station/consumers/waterfall.py b/utils/ground_station/consumers/waterfall.py index 125ce69..72c0c90 100644 --- a/utils/ground_station/consumers/waterfall.py +++ b/utils/ground_station/consumers/waterfall.py @@ -12,8 +12,6 @@ from __future__ import annotations import queue import time -import numpy as np - from utils.logging import get_logger from utils.waterfall_fft import ( build_binary_frame, diff --git a/utils/ground_station/observation_profile.py b/utils/ground_station/observation_profile.py index 98a071f..d32fe9a 100644 --- a/utils/ground_station/observation_profile.py +++ b/utils/ground_station/observation_profile.py @@ -113,9 +113,9 @@ class ObservationProfile: return tasks @classmethod - def from_row(cls, row) -> 'ObservationProfile': + def from_row(cls, row) -> ObservationProfile: tasks = [] - raw_tasks = row['tasks_json'] if 'tasks_json' in row.keys() else None + raw_tasks = row.get('tasks_json', None) if raw_tasks: try: tasks = normalize_tasks(json.loads(raw_tasks)) diff --git a/utils/ground_station/scheduler.py b/utils/ground_station/scheduler.py index d277368..c3bfd9a 100644 --- a/utils/ground_station/scheduler.py +++ b/utils/ground_station/scheduler.py @@ -22,7 +22,6 @@ from __future__ import annotations import json import queue import threading -import time import uuid from datetime import datetime, timedelta, timezone from pathlib import Path @@ -283,6 +282,7 @@ class GroundStationScheduler: ) -> list[ScheduledObservation]: """Predict passes for each profile and return ScheduledObservation list.""" from skyfield.api import load, wgs84 + from utils.satellite_predict import predict_passes as _predict_passes try: @@ -525,8 +525,8 @@ class GroundStationScheduler: def _attach_sigmf_consumer(self, bus, profile, obs_db_id: int | None) -> None: """Attach a SigMFConsumer for raw IQ recording.""" - from utils.sigmf import SigMFMetadata from utils.ground_station.consumers.sigmf_writer import SigMFConsumer + from utils.sigmf import SigMFMetadata meta = SigMFMetadata( sample_rate=profile.iq_sample_rate, @@ -681,8 +681,9 @@ class GroundStationScheduler: def _insert_observation_record(obs: ScheduledObservation, profile) -> int | None: try: - from utils.database import get_db from datetime import datetime, timezone + + from utils.database import get_db with get_db() as conn: cur = conn.execute(''' INSERT INTO ground_station_observations @@ -719,8 +720,9 @@ def _insert_event_record(obs_db_id: int | None, event_type: str, payload: str) - if obs_db_id is None: return try: - from utils.database import get_db from datetime import datetime, timezone + + from utils.database import get_db with get_db() as conn: conn.execute(''' INSERT INTO ground_station_events (observation_id, event_type, payload_json, timestamp) @@ -768,9 +770,10 @@ def _build_packet_event(payload, source: str) -> dict[str, Any]: if parsed is None: try: - from utils.satellite_telemetry import auto_parse import base64 + from utils.satellite_telemetry import auto_parse + for token in text.replace(',', ' ').split(): cleaned = token.strip() if not cleaned or len(cleaned) < 8: @@ -794,8 +797,9 @@ def _build_packet_event(payload, source: str) -> dict[str, Any]: def _insert_recording_record(obs_db_id: int | None, meta_path: Path, data_path: Path, profile) -> None: try: - from utils.database import get_db from datetime import datetime, timezone + + from utils.database import get_db size = data_path.stat().st_size if data_path.exists() else 0 with get_db() as conn: conn.execute(''' @@ -827,9 +831,10 @@ def _insert_output_record( metadata: dict[str, Any] | None = None, ) -> int | None: try: - from utils.database import get_db from datetime import datetime, timezone + from utils.database import get_db + with get_db() as conn: cur = conn.execute( ''' diff --git a/utils/satellite_predict.py b/utils/satellite_predict.py index d6be91c..ff4f8fd 100644 --- a/utils/satellite_predict.py +++ b/utils/satellite_predict.py @@ -6,8 +6,6 @@ Uses Skyfield's find_events() for accurate AOS/TCA/LOS event detection. from __future__ import annotations -import datetime -import math from typing import Any from utils.logging import get_logger diff --git a/utils/satellite_telemetry.py b/utils/satellite_telemetry.py index 39303b3..2f89ba3 100644 --- a/utils/satellite_telemetry.py +++ b/utils/satellite_telemetry.py @@ -12,11 +12,10 @@ views of raw binary data (hex dump, float32, uint16/32, strings). from __future__ import annotations import math -import struct import string +import struct from datetime import datetime - # --------------------------------------------------------------------------- # AX.25 parser # --------------------------------------------------------------------------- diff --git a/utils/sstv/sstv_decoder.py b/utils/sstv/sstv_decoder.py index b650543..24497f7 100644 --- a/utils/sstv/sstv_decoder.py +++ b/utils/sstv/sstv_decoder.py @@ -22,13 +22,12 @@ from typing import Callable import numpy as np -from utils.logging import get_logger - # DopplerTracker/DopplerInfo now live in the shared utils/doppler module. # Import them here so existing code that does # ``from utils.sstv.sstv_decoder import DopplerTracker`` # continues to work unchanged. from utils.doppler import DopplerInfo, DopplerTracker # noqa: F401 +from utils.logging import get_logger from .constants import ISS_SSTV_FREQ, SAMPLE_RATE from .dsp import goertzel_mag, normalize_audio