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>
- 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>
Remove the split fast-path in doLocateHandoff that called BtLocate.handoff()
directly when the module was already loaded. That path relied on handoff()
internally calling switchMode, causing a double switchMode in the lazy-load
path and no guaranteed mode switch in the fast path.
Now doLocateHandoff always calls switchMode('bt_locate') first (lazy-loading
script/styles as needed), then calls BtLocate.handoff() in .then(). Removed
the redundant switchMode call from BtLocate.handoff() since the caller owns
the mode transition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the requestAnimationFrame loop in proximity-radar.js with a
CSS @keyframes rotation on .bt-radar-sweep, mirroring the WiFi radar
pattern. Adds two trailing arc paths for a glow effect and updates
setPaused() to toggle animationPlayState instead of the rAF flag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace static channel bar chart and security dots with a scrolling
2.4 GHz channel heatmap (up to 10 scan snapshots) and an SVG donut
security ring showing WPA2/WPA3/WEP/Open network distribution.
Replaces the 7-column <table> network list with flex div rows featuring
two-line layout (SSID + security badges on top, signal bar + meta on
bottom), coloured left-border threat indicators, and new sort controls.
Renames selectedNetwork → selectedBssid and updateNetworkTable → renderNetworks throughout wifi.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove 'METEOR-M2' (NORAD 40069) from WEATHER_SAT_KEYS — it has no
entry in WEATHER_SATELLITES and no dropdown option, so the Capture
button was silently scheduling against the wrong satellite
- Dispatch a 'change' event after setting satSelect.value in preSelect()
and startPass() so any UI listeners (frequency display, mode info)
update correctly when the satellite is set programmatically
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add --no-cache-dir and --timeout 120 to all pip calls to prevent hanging
on corrupt/stale pip HTTP cache (cachecontrol .pyc issue)
- Replace silent python -c import verification with pip show to avoid
import-time side effects hanging the installer
- Switch optional packages to --only-binary :all: to skip source compilation
on Python versions without pre-built wheels (prevents gevent/numpy hangs)
- Warn early when Python 3.13+ is detected that some packages may be skipped
- Add ground track caching with 30-minute TTL to satellite route
- Add live satellite position tracker background thread via SSE fanout
- Add satellite_predict, satellite_telemetry, and satnogs utilities
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dispatch observer-location-changed event from settings manager and
listen for it in APRS mode so manual location saves propagate to
the map and distance calculations. Also refresh ObserverLocation in
initAprsMap() to catch changes between page load and first map use.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ensureModeScript() used document.body.appendChild() to load lazy mode
scripts, but the preload for ?mode= query params runs in <head> before
<body> exists, causing all deep-linked modes to silently fail.
Also fix cross-mode handoffs (BT→BT Locate, WiFi→WiFi Locate,
Spy Stations→Waterfall) that assumed target module was already loaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend sends rssi_current but frontend was reading net.signal || net.rssi,
causing RSSI to parse as NaN and silently skipping all meter/audio updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add WiFi Locate mode for locating access points by BSSID with real-time
signal meter, distance estimation, RSSI history chart, and audio
proximity tones. Includes hand-off from WiFi detail drawer, environment
presets (Free Space/Outdoor/Indoor), and signal-lost detection.
Also includes:
- Mobile navigation reorganized into labeled groups (SIG/TRK/SPC/WIFI/INTEL/SYS)
- flask-limiter made optional with graceful degradation
- Fix radiosonde setup missing semver Python dependency
- Documentation updates (FEATURES, USAGE, UI_GUIDE, GitHub Pages site)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The OOK subprocess was spawned without start_new_session=True, so
process.terminate() only signalled the parent — child processes kept
running. Now uses os.killpg() to terminate the entire process group,
matching the pattern used by all other routes (ADS-B, AIS, ACARS, etc.).
Also fixes silent error swallowing in the frontend stop handler so the
UI resets even if the backend request fails.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses final upstream review — all backend-derived values (timestamp,
bit_count, rssi, hex, ascii) now use DOM methods instead of innerHTML
interpolation, closing the last XSS surface. Bumps cache-buster to ook2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Detect crashed rtl_433 process via poll() and clean up stale state
instead of permanently blocking restarts with 409
- Replace innerHTML+onclick preset rendering with createElement/addEventListener
to prevent XSS via crafted localStorage frequency values
- Normalize preset frequencies to toFixed(3) on save and render
- Add try/catch + shape validation to loadPresets() for corrupted localStorage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical:
- Pass sdr_type_str to claim/release_sdr_device (was missing 3rd arg)
- Add ook_active_sdr_type module-level var for proper device registry tracking
- Add server-side range validation on all timing params via validate_positive_int
Major:
- Extract cleanup_ook() function for full teardown (stop_event, pipes, process,
SDR release) — called from both stop_ook() and kill_all()
- Replace Popen monkey-patching with module-level _ook_stop_event/_ook_parser_thread
- Fix XSS: define local _esc() fallback in ook.js, never use raw innerHTML
- Remove dead inversion code path in utils/ook.py (bytes.fromhex on same
string that already failed decode — could never produce a result)
Minor:
- Status event key 'status' → 'text' for consistency with other modules
- Parser thread logging: debug → warning for missing code field and errors
- Parser thread emits status:stopped on exit (normal EOF or crash)
- Add cache-busting ?v={{ version }}&r=ook1 to ook.js script include
- Fix gain/ppm comparison: != '0' (string) → != 0 (number)
Tests: 22 → 33 (added start success, stop with process, SSE stream,
timing range validation, stopped-on-exit event)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix XSS: escape ASCII output in innerHTML via escapeHtml()
- Fix deadlock: use put_nowait() for queue ops under ook_lock
- Fix SSE leak: add ook to moduleDestroyMap so switching modes
closes the EventSource
- Fix RSSI: explicit null check preserves valid zero values in
JSON export
- Add frame cap: trim oldest frames at 5000 to prevent unbounded
memory growth on busy bands
- Validate timing params: wrap int() casts in try/except, return
400 instead of 500 on invalid input
- Fix PWM hint: correct to short=0/long=1 matching rtl_433
OOK_PWM convention (UI, JS hints, and cheat sheet)
- Fix inversion docstring: clarify fallback only applies when
primary hex parse fails, not for valid decoded frames
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded frequency buttons with localStorage-backed presets.
Default presets are standard ISM frequencies (433.920, 315, 868, 915 MHz).
Users can add custom frequencies, right-click to remove, and reset to
defaults — matching the pager module pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Covers identifying modulation type (PWM/PPM/Manchester), finding
pulse timing via rtl_433 -A, common ISM frequencies and timings,
and troubleshooting tips for tolerance and bit order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix double-scroll by switching ookOutputPanel to flex layout
- Keep decoded frames visible after stopping (persist for review)
- Wire global Clear/CSV/JSON status bar buttons to OOK functions
- Hide default output pane in OOK mode (uses own panel)
- Add command display showing the active rtl_433 command
- Add JSON export and auto-scroll support
- Fix 0x prefix stripping in OOK hex decoder
- Fix PWM encoding hint text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Timing presets: five quick-fill buttons (300/600, 300/900, 400/800, 500/1500, 500 MC)
that populate all six pulse-timing fields at once — maps to CTF flag timing profiles
- RSSI per frame: add -M level to rtl_433 command; parse snr/rssi/level from JSON;
display dB SNR inline with each frame; include rssi_db column in CSV export
- Auto bit-order suggest: "Suggest" button counts printable chars across all stored
frames for MSB vs LSB, selects the winner, shows count — no decoder restart needed
- Pattern filter: live hex/ASCII filter input above the frame log; hides non-matching
frames and highlights matches in green; respects current bit order
- TSCM integration: "Decode (OOK)" button in RF signal device details panel switches
to OOK mode and pre-fills frequency — frontend-only, no backend changes needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>