Commit Graph

1585 Commits

Author SHA1 Message Date
Smittix 9641c43384 Merge pull request #217 from smittix/fix/meshcore-active-display
fix(meshcore): show mode panel when active
2026-05-13 10:11:10 +01:00
James Smith 1f7e0881f3 fix(meshcore): show mode panel when active
meshcore.css was missing the .active display rule, so the meshcoreMode
div (display:none inline) was never made visible when the mode was
selected, leaving only the generic sidebar visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:10:25 +01:00
Smittix b2cc6b65ad Merge pull request #216 from smittix/fix/meshcore-nav-and-card-styling
fix(meshcore): add to nav and fix welcome card styling
2026-05-13 10:06:40 +01:00
James Smith 28a779b91b fix(meshcore): add to nav and fix welcome card styling
Meshcore was missing from both the desktop Wireless dropdown and mobile
nav. The welcome card also used non-standard div/emoji markup instead of
the SVG icon pattern used by every other mode, causing wrong font and
colour rendering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 10:02:45 +01:00
James Smith e397a69dae fix(drone): use Settings tile layer for theme-aware map
Replace hardcoded OSM tiles with Settings.createTileLayer() + registerMap()
so the drone map respects the user's map theme preference and switches
automatically with light/dark theme changes. Falls back to CartoDB dark_all
if Settings is unavailable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:58:04 +01:00
James Smith 3554817f91 fix(drone): fix main panel height collapse in flex output container
Replace height:100% with flex:1+min-height:0 on .drone-visuals-container
so it fills the flex-column .output-panel correctly (height:100% collapses
inside a scroll container). Add min-height:400px to .drone-main-map so
Leaflet has pixel dimensions to render into.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:45:00 +01:00
James Smith 410225d54d fix(drone): conform to established SPA patterns throughout
- Device population: move refreshDroneDevices() inline to index.html
  (same pattern as refreshTscmDevices) and call it from switchMode
  alongside DroneMode.init(); remove _refreshDevices/populateSelect
  from drone.js which was never guaranteed to run before lazy-load
  completed, causing selects to stay on "Loading…" permanently

- IIFE pattern: change from named IIFE + window.DroneMode assignment
  to var DroneMode = (function(){...return{...}})() matching OOK/
  SpyStations convention

- Init guard: add _initialized flag (OOK state.initialized pattern);
  re-entry after destroy() re-registers map/SSE cleanly without
  duplicating click listeners on every mode switch

- Lifecycle: destroy() resets _initialized = false so map and SSE
  are correctly rebuilt on re-entry

- Stop phase: add isDroneRunning tracking variable in index.html;
  _setRunningUI() syncs it; switchMode stop phase now POSTs
  /drone/stop when leaving drone mode while active, matching TSCM

- /drone/devices: add monitor_capable field to WiFi interfaces,
  add running_as_root and warnings array to response (mirrors
  /tscm/devices shape); add os import; show privilege warning div
  in drone.html when not running as root

- drone.html: remove for= attribute from SDR label (plain <label>
  inside .form-group matches TSCM convention); add droneDeviceWarnings
  div for privilege warnings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:33:38 +01:00
James Smith 4ba8a40af9 feat(drone): replace freeform inputs with populated device selects
Add /drone/devices endpoint that enumerates available WiFi interfaces
(via iw/iwconfig) and RTL-SDR devices (via SDRFactory.detect_devices),
matching the pattern used by TSCM.

Sidebar WiFi interface and RTL-SDR inputs are now <select> elements
populated on init() from /drone/devices, consistent with how other
modes expose hardware selection. HackRF checkbox remains as a toggle
since it's a binary capability rather than an enumerated device list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:25:21 +01:00
James Smith 6523686aca feat(drone): add main visuals panel with map and contact list
- Sidebar inputs now use form-group/label pattern matching other modes
- Move map and contact list out of sidebar into a dedicated droneVisuals
  main panel (same pattern as tscm, spystations, etc.)
- droneVisuals: stats header (contacts / non-compliant / high-risk),
  left contact card panel, and full-height Leaflet map on the right
- Wire droneVisuals into switchMode display toggle and modesWithVisuals
  so the shared signal-feed output is hidden when drone mode is active
- Add invalidateMap() to force Leaflet to recalculate after the
  container becomes visible
- Stats now update both sidebar counts and main panel values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:16:54 +01:00
James Smith 2475e5dd5a fix(drone): show sidebar panel and expose SDR config options
Remove inline style="display: none;" that was preventing the droneMode
panel from becoming visible when the active class was toggled — inline
styles override CSS class rules without !important. Add RTL-SDR device
index and HackRF toggle inputs that the backend already accepted but
were never surfaced in the UI; wire them through to the /drone/start
POST body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:07:42 +01:00
James Smith 8f6bfb4df1 feat(meshcore): wire Meshcore into index.html (14 insertion points) 2026-05-11 15:47:27 +01:00
James Smith 36a1542176 feat(meshcore): add frontend JS module (IIFE, SSE, map, telemetry, traceroute) 2026-05-11 14:58:45 +01:00
James Smith 71011dd67c feat(meshcore): add HTML partial (sidebar, tabs, modals)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:58:29 +01:00
James Smith 173ddc9eac feat(meshcore): add CSS for Meshcore mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:56:58 +01:00
James Smith 53699482e1 feat(meshcore): register blueprint and add meshcore dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:55:37 +01:00
James Smith e5c5afb158 test(meshcore): assert error message propagated in on_error test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:54:30 +01:00
James Smith f2af2ad0b6 test(meshcore): fix BLE docker test and add library boundary isolation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:51:45 +01:00
James Smith 3f6f8a5695 test(meshcore): add integration tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:49:05 +01:00
James Smith 0d9bb53722 test(meshcore): strengthen connect and send boundary tests
Add 237-char boundary test proving the send limit accepts exactly 237
characters, and upgrade connect tests to assert the correct config
dataclass type and field values are passed to connect().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:47:10 +01:00
James Smith d84cd41896 test(meshcore): add missing coverage for 7 endpoints + SSE keepalive
Adds 16 new tests covering POST /disconnect, GET /ble/scan, GET /stream
(keepalive and event data), GET /messages, GET /nodes, GET /contacts,
GET /telemetry/<node_id>, and GET /repeaters, bringing total from 17 to 33.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:44:11 +01:00
James Smith ed4b6ef897 test(meshcore): add route tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:41:49 +01:00
James Smith f50f5e2d44 feat(meshcore): add Flask blueprint with all 15 endpoints + SSE stream 2026-05-11 12:35:16 +01:00
James Smith 71eaf1d22a fix(meshcore): fix traceroute logging, asyncio task leaks, stop race, channel sender, and battery cap
- Log node_id hint in request_traceroute instead of silently dropping it
- Replace asyncio.shield/wait_for pattern with _wait_or_stop() to prevent orphan tasks on retry delays
- Poll _stop_event every 1s in _do_connect keep-alive loop to handle stop() race before _asyncio_stop is set
- Extract pubkey_prefix/sender_id in _on_channel_msg instead of hardcoding "unknown"
- Close coroutine and log in _submit() when worker is not running to prevent ResourceWarning
- Cap battery_pct at 100 to prevent values exceeding 100%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 12:33:31 +01:00
James Smith ba02f761c6 feat(meshcore): add async worker bridge (utils/meshcore_client.py)
Implements AsyncWorker — the daemon asyncio thread that owns the meshcore
library connection, subscribes to all relevant EventTypes, and feeds events
back into MeshcoreClient via on_message/on_node/on_telemetry/on_traceroute/
on_connected/on_error. Includes retry-with-backoff (3 attempts: 5s/15s/45s),
thread-safe send_text/request_traceroute/scan_ble_sync for Flask callers,
and a standalone _scan_ble() coroutine using bleak.BleakScanner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 11:18:50 +01:00
James Smith 80bbdb2c09 fix(meshcore): fix thread safety in _set_state/connect, add missing tests
- Lock-protect `get_state` and `_set_state` to prevent data race
  between Flask and asyncio daemon threads
- Atomically check-and-set CONNECTING guard in `connect()` to close
  TOCTOU window between concurrent Flask threads
- Push status events outside the lock in both `_set_state` and
  `connect()` to avoid potential deadlock
- Add TestMeshcoreContact, TestMeshcoreClientStateMachine tests
  covering to_dict keys, queue push on state change, message append
  and 500-item cap (9 -> 13 tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 11:12:11 +01:00
James Smith 6807ee6878 fix(meshcore): fix test isolation, enum value assertions, and os import 2026-05-11 10:39:32 +01:00
James Smith 04d2e1a7bf feat(meshcore): add data model, connection config, MeshcoreClient skeleton
Implements utils/meshcore.py with all dataclasses (MeshcoreMessage,
MeshcoreNode, MeshcoreContact, MeshcoreTelemetry, MeshcoreTraceroute),
connection configs (SerialConfig, TCPConfig, BLEConfig), ConnectionState
enum, serial port discovery, and the MeshcoreClient singleton skeleton.
Adds tests/test_meshcore_client.py covering all dataclasses, availability
check, and state enum (8/8 tests passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 10:12:23 +01:00
James Smith 07e45f508a docs: add Meshcore implementation plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 14:37:19 +01:00
James Smith 2b9665c723 docs: add Meshcore integration design spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 14:22:07 +01:00
James Smith c73d02f76c fix: remove opendroneid dependency incompatible with Python 3.11
The opendroneid package caps at Python <3.11, breaking Docker builds on
the current python:3.11-slim base image. The package is unused — drone
Remote ID parsing is handled natively via scapy and struct in
utils/drone/remote_id.py.

Closes #214

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 15:10:06 +01:00
Smittix 5048711acb Merge pull request #213 from smittix/fix/adsb-photos-and-drone-docs
fix(adsb): fix aircraft photo display and add Drone Intelligence docs
2026-05-05 09:25:47 +01:00
James Smith 62e53c5dfa fix(adsb): fix aircraft photo display and add Drone Intelligence docs
- Fix stale DOM refs in fetchAircraftPhoto: elements were captured before
  await fetch(), but showAircraftDetails rebuilds innerHTML on every RAF
  update, leaving the async path writing to detached nodes. Now re-queries
  the DOM after await, and the cache (synchronous) path queries inline so
  refs are always fresh.
- Add thumbnail fallback in aircraft_photo route: fall back to thumbnail
  when thumbnail_large.src is absent rather than returning null.
- Add Drone Intelligence to nav, help modal, cheat sheets, README, and docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 09:24:30 +01:00
James Smith 333c5cf8d3 feat(drone): merge Drone Intelligence module
Multi-vector UAV detection mode: Remote ID (WiFi/BLE ASTM F3411),
RTL-SDR 433/868MHz control-link detection, HackRF 2.4/5.8GHz wideband.

Workers feed a shared observation queue; DroneCorrelator merges into
DroneContact objects with TTL store, risk scoring, and SSE streaming.
Frontend: two-panel sidebar + Leaflet map with contact cards and trails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 08:36:27 +01:00
James Smith d033a95b0e chore: add .worktrees/ to .gitignore 2026-05-05 08:36:16 +01:00
James Smith 3b480eb183 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-05 08:36:16 +01:00
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 b78ca51db1 Merge pull request #202 from mitchross/misc-fixes
Fix Meteor LRPT, global timezone, VDL2 correlation, and weather sat UX
2026-04-26 15:05:56 +01:00