Commit Graph

1540 Commits

Author SHA1 Message Date
James Smith 8632e31c01 fix(drone): resolve critical pipeline, frontend, and input validation issues
Data pipeline (critical): scanners/detectors now write to a separate _obs_queue;
a relay thread reads observations and calls correlator.process(), which emits
processed DroneContact dicts to drone_queue for SSE. Without this the SSE stream
received raw unserializable dataclass objects causing JSON errors.

Frontend (critical):
- Add droneContactList container to drone.html so contact cards render
- Add droneMap container and initialize Leaflet in drone.js init()
- Define dsc-distress-pulse keyframes in drone.css (was referenced but missing)
- Fix SSE reconnect: null _sse before setTimeout to prevent _connectSSE no-op loop

Other fixes:
- Validate rtl_sdr_index with validate_device_index(), return 400 on bad input
- Move _ensure_workers() inside _drone_lock to prevent double-initialization race
- Add double-call guard to RemoteIDScanner.start()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 21:47:12 +01:00
James Smith 14e6305aa4 feat(drone): add frontend JS, modeCatalog entry, and switchMode wiring
Creates static/js/modes/drone.js IIFE module (SSE consumer, map markers,
contact cards, start/stop controls) and wires it into index.html via
INTERCEPT_MODE_SCRIPT_MAP, modeCatalog (intel group), and switchMode
init/destroy handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:44:07 +01:00
James Smith e059be2d84 feat(drone): add HTML partial, CSS, and index.html mode panel wiring
- Create templates/partials/modes/drone.html with drone mode sidebar panel
- Create static/css/modes/drone.css with scoped drone UI styles
- Wire drone mode into index.html: CSS map entry, partial include, classList toggle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:41:02 +01:00
James Smith 1a99a7213f fix(drone): add lock on _drone_running and null guards before start() calls
Concurrent POST /drone/start under gevent would race on _drone_running;
lock mirrors the ais_lock / dsc_lock pattern used throughout the codebase.
Null guards prevent AttributeError if worker constructors fail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:39:23 +01:00
James Smith f9e8fa896d feat(drone): add Flask blueprint, register routes, wire drone_queue
Implements Task 5: creates routes/drone.py with /status, /contacts,
/start, /stop, and /stream (SSE fanout) endpoints; registers the
drone_bp blueprint in routes/__init__.py; adds drone_queue to app.py;
adds opendroneid>=1.0 to requirements.txt. All 39 drone tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:37:02 +01:00
James Smith 59713ffc22 fix(drone): harden RFDetector threading, subprocess lifecycle, and frequency accuracy
- Replace _running bool with threading.Event for correct cross-thread visibility
- Add _proc_lock to guard _rtl_proc/_hackrf_proc across worker/main threads
- Use register_process + safe_terminate (pipe close + SIGKILL fallback on timeout)
- Compute HackRF frequency as band midpoint (hz_low+hz_high)//2, not hz_low
- Guard start() for idempotency — double-call no longer leaks threads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 17:33:55 +01:00
James Smith 681a498461 test(drone): fix test_start_stop isolation and add out-of-band filter coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:53:41 +01:00
James Smith e8b94b6efc feat(drone): add RFDetector for rtl_433 and hackrf_sweep control-link detection
Implements RFDetector class that wraps rtl_433 (433/868MHz) and hackrf_sweep
(2.4/5.8GHz) subprocesses, emitting RFObservation objects onto a shared queue.
Includes signature matching, frequency band validation, and power thresholding.

- _handle_rtl433_line(): Parse JSON output, filter drone bands, emit observations
- _handle_hackrf_line(): Parse CSV output, average power levels, threshold at -90dBm
- start()/stop(): Manage subprocess threads for concurrent RF detection
- Graceful handling of missing tools (rtl_433, hackrf_sweep)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:52:03 +01:00
James Smith 5dda961dbb fix(drone): assign self._sniffer only after successful AsyncSniffer.start()
Prevents a non-running sniffer object being stored when start() raises
(e.g. permission denied or interface not found).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:42:42 +01:00
James Smith a6ce5d5426 feat(drone): add RemoteIDScanner with BLE/WiFi ASTM F3411 parsing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:39:06 +01:00
James Smith 772b5d0973 feat(drone): add DroneCorrelator with TTL store and risk scoring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:11:04 +01:00
James Smith b707468cb6 feat(drone): add data models and RF signature table 2026-05-03 11:45:59 +01:00
James Smith e33dff1ab9 chore: add .worktrees/ to .gitignore 2026-05-03 11:13:07 +01:00
James Smith 58222b3474 fix(hackrf): resolve 'Tools Missing' on RPi when hackrf_info is present
Two root causes behind HackRF showing as unavailable when tools are installed:

1. get_tool_path() didn't search /usr/local/bin on Linux. HackRF tools built
   from source (as in the Dockerfile) land there, but the path wasn't checked
   when sudo/service environments have a restricted PATH.

2. check_hackrf() only tested hackrf_transfer, but the health check tests
   hackrf_info — both come from the same apt package but a user could have one
   visible and not the other. Now either binary confirms the tools are present.
   hackrf_transfer is still required for actual RX/TX operations.

Fixes #212

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 09:24:22 +01:00
Smittix dd1c6b8b62 Merge pull request #210 from smittix/fix/wefax-stations-missing-in-docker
fix(wefax): include station JSON in Docker image and handle missing file
2026-04-22 16:45:03 +01:00
James Smith a982ff5885 fix(wefax): include station JSON in Docker image and handle missing file
data/*.json was excluded by .dockerignore, so wefax_stations.json was
never copied into the container image. The volume mounts in docker-compose
only cover subdirectories (weather_sat, adsb, etc.), leaving the stations
file inaccessible at runtime — causing the /wefax/stations route to 500
and the station/frequency dropdowns to appear empty.

Also adds a graceful file-existence check in load_stations() so a missing
file logs a warning and returns an empty list instead of an unhandled
FileNotFoundError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 20:51:24 +01:00
James Smith 7cf94cce14 fix(sstv): fix inaccurate ISS orbit tracking — three root causes
1. iss_schedule() was importing TLE_SATELLITES directly from data/satellites.py
   (hardcoded, 446 days stale) instead of the live _tle_cache kept fresh by
   the 24h auto-refresh. Add get_cached_tle() to satellite.py and use it.

2. Ground track was a fake sine wave (inclination * sin(phase)) that mapped
   longitude offset directly to orbital phase, ignoring Earth's rotation under
   the satellite (~23° westward shift per orbit). Replace with a /sstv/iss-track
   endpoint that propagates the orbit via skyfield SGP4 over ±90 minutes, and
   update the frontend to call it. Past/future track rendered with separate
   polylines (dim solid vs bright dashed).

3. refresh_tle_data() updated _tle_cache in memory but never persisted back to
   data/satellites.py, so every restart reloaded the stale hardcoded TLE. Add
   _persist_tle_cache() called after each successful refresh.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 15:37:02 +01:00
James Smith 1dc45a285d perf(css): fix rendering slowdown on low-power hardware (Pi5)
- Remove backdrop-filter: blur(5px) from .card and .panel — on ARM/Linux
  Chromium this is software-rendered, causing severe CPU overhead at 42+
  instances. The opaque surface gradient makes blur imperceptible anyway.
- Remove inset vignette box-shadow from .panel added in 51c1014
- Rewrite panel-pulse keyframes to animate opacity only (was box-shadow,
  which triggers CPU repaint every frame; opacity is compositor-only)
- Gate body::before and .visuals-container::after scanline pseudo-elements
  under [data-animations="off"] — the toggle was blind to both
- Gate panel-indicator pulse under [data-animations="off"] for consistency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 14:54:34 +01:00
James Smith c70c93c814 chore: clean up project root
- Delete stale aircraft_db.json + aircraft_db_meta.json (code uses data/adsb/)
- Delete orphaned gp.php (TLE data fetched at runtime from celestrak.org)
- Move oui_database.json → data/ and update path in data/oui.py
- Move favicon.svg → static/ and update send_file path in app.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 13:05:35 +01:00
James Smith f51682f929 chore: exclude docs/superpowers/ from git tracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 12:59:05 +01:00
James Smith f12f4145ef docs(alerts): document webhook/notification system in USAGE.md
fix(db): use gevent-safe local storage for DB connections under gunicorn+gevent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 12:45:58 +01:00
James Smith 6dce911172 fix(maps): remove duplicate overlays — drop HUD panels from APRS/GPS (have own header), drop MapUtils range rings from ADS-B (drawRangeRings() owns them) 2026-04-13 23:46:03 +01:00
James Smith 8ae19beef6 fix(maps): remove defer from map-utils.js in index.html for consistency 2026-04-13 23:33:40 +01:00
James Smith 3693b02cb9 refactor(radiosonde): use MapUtils.init + Settings tile layer on sonde map
Replace hardcoded L.map() + CartoCD dark tile layer with MapUtils.init()
and add tactical overlays. Adds test verifying the cartocdn URL is gone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:27:36 +01:00
James Smith ac2f7ea032 fix(aprs): correct aprsMarkers variable name and reset aprsMapOverlays on teardown 2026-04-13 23:15:53 +01:00
James Smith 24f12b1220 refactor(aprs,gps): use MapUtils.init + HUD panels on APRS and GPS maps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 23:12:46 +01:00
James Smith 88db107691 fix(satellite): use MapUtils._buildReticle consistently in updateObserverMarker 2026-04-13 23:03:46 +01:00
James Smith 7dfefb48e6 refactor(satellite): use MapUtils.init + HUD on ground track map
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:56:20 +01:00
James Smith 12af6e250e refactor(ais): use MapUtils.init + HUD panels on vessel map
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:41:26 +01:00
James Smith 5ffd9e5fb3 refactor(adsb): use MapUtils.init + tactical overlays on radar map
Replace custom createFallbackGridLayer/upgradeRadarTilesFromSettings with
MapUtils.init(), add range ring + reticle + HUD panel overlays via
MapUtils.addTacticalOverlays(), and wire updateCount/updateReticle into
the SSE aircraft handler and drawRangeRings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:35:43 +01:00
James Smith 7d78bb45d6 fix(maps): add !important to glass popup overrides to beat global index.css rules 2026-04-13 22:29:23 +01:00
James Smith 9a82328de2 feat(maps): add map-utils.css for HUD panels, glass popup, range ring labels 2026-04-13 22:27:19 +01:00
James Smith 51f8a6f65b fix(maps): fix _upgradeTiles race guard, interval leak, graticule events, ring labels
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:24:23 +01:00
James Smith 99edea33e3 feat(maps): add MapUtils shared map initialisation utility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:19:44 +01:00
James Smith f97782724e fix(maps): exclude stadia key from localStorage cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:11:34 +01:00
James Smith e4df3eaecb feat(maps): add Stadia dark + tactical tile providers with API key support
- Add offline.stadia_key to OFFLINE_DEFAULTS in routes/offline.py
- Add stadia_dark and tactical tile providers to Settings.tileProviders
- Update getTileConfig() to inject Stadia API key or fall back to CartoDB dark
- Add setStadiaKey() method for saving and applying the API key
- Show/hide Stadia key row in setTileProvider() and _updateUI()
- Add Stadia options to tile provider select in settings modal
- Add Stadia API key input row to settings modal
- Add TDD tests for stadia_key backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 22:04:07 +01:00
James Smith 16b95e4804 test: add system group to nav state coverage 2026-04-13 21:10:14 +01:00
James Smith f1a029262b feat: persist nav group open/closed state to localStorage
Adds initNavGroupState() and saveNavGroupState() functions so the
open/closed state of each .mode-nav-dropdown survives page reloads.
Active groups are never force-closed even if localStorage says closed.
Adds test_nav_state.py with two tests verifying presence of the
functions and data-group attributes on all five nav groups.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 21:08:27 +01:00
James Smith 51c10144c7 style: add card vignette and scanline texture to visuals containers 2026-04-13 21:05:08 +01:00
James Smith b5ae7fe472 style: add pulse animation to active panel indicators 2026-04-13 21:03:08 +01:00
James Smith b75d28f284 style: fix padding compensation and light theme border on nav active states 2026-04-13 21:01:34 +01:00
James Smith 4a3a7127ca style: nav active state → left-border cyan glow, hover → glow bg 2026-04-13 18:18:38 +01:00
James Smith bfff092657 style: group --accent-cyan-glow with other cyan tokens 2026-04-13 18:17:42 +01:00
James Smith f2f17ac26e style: deepen background tokens and add scanline/glow variables 2026-04-13 18:16:03 +01:00
James Smith 8c61af2863 docs: add Sub-Project 1 implementation plan (design system uplift)
6-task plan covering token deepening, nav active state glow, panel pulse
animation, scanline texture, and localStorage nav group persistence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:50:22 +01:00
James Smith 34fb030af1 docs: add UI/UX improvements design spec
Mission Control aesthetic, maps overhaul, mode polish sprint for 12 modes,
and 5 new features (Spectrum Overview, Alerts Engine, Signal Recording,
Signal ID, Mobile PWA) — decomposed into 4 sequential sub-projects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 16:47:30 +01:00
James Smith 238ad7936a chore: add pre-commit hook to catch lint errors before push
Adds ruff pre-commit hook that auto-fixes and formats on every commit,
preventing lint CI failures from reaching GitHub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 13:40:22 +01:00
Smittix b01598753d fix(adsb): disable bias-T on stop and warn when toggled while running (#207)
* fix(adsb): disable bias-T on stop and warn when toggled while running

The RTL-SDR bias-T hardware register persists after the device is closed,
so toggling bias-T off in the UI and stopping the SDR had no effect on the
actual hardware — verified with a multimeter in issue #205.

- Add disable_bias_t_via_rtl_biast() to rtlsdr.py (mirrors enable, uses -b 0)
- Track adsb_bias_t_active in adsb.py; call disable on stop_adsb() so the
  hardware register is cleared when ADS-B is stopped
- Show an inline warning in the UI when the bias-T checkbox is toggled while
  any SDR mode is active, since the setting only takes effect at start time

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(lint): remove unused imports in tscm sweep.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 13:18:50 +01:00
Smittix 3fedff9d08 Merge pull request #206 from smittix/fix/sensor-removeChild-freeze
fix(sensor): replace static NodeList loops causing page freeze and removeChild TypeError
2026-04-12 21:29:41 +01:00
James Smith 1fc80b05b1 fix(sensor): replace static NodeList while-loops causing page freeze and removeChild TypeError
Four list-trimming loops used querySelectorAll (static NodeList) inside a
while condition, so .length never decreased — causing infinite loops that
froze the page, or repeated removeChild calls on already-removed nodes
(TypeError: parameter 1 is not of type 'Node').

Also replaces blocking alert() with showInfo() for start errors and adds
a .catch() handler to the start_sensor fetch so network failures surface
cleanly instead of leaving the UI in a broken state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 21:28:50 +01:00