Strong passes at 40 dB (the previous default) cause RTL-SDR ADC clipping,
producing a distorted IQ stream that SatDump cannot lock onto. 30 dB is
a safer starting point that still captures weak passes cleanly.
Also adds a UI hint below the gain control explaining the saturation issue.
Closes#185
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>
The updateHeaderClock function in index.html was inlined and still using
raw UTC (toISOString), while nav.html's version used InterceptTime.
Both ran on 1-second intervals updating the same element, causing the
clock to rapidly alternate between ET and UTC.
Fix: Updated the inline version in index.html to use InterceptTime,
matching nav.html. Added _navClockStarted guard and onChange listener
so only one interval runs and timezone changes apply instantly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
app.js and nav.html both started 1-second intervals updating the same
#headerUtcTime element. Even though both used InterceptTime, their
slightly different timing caused visible text flicker.
Fix: app.js now sets window._navClockStarted before starting its
interval, so nav.html's guard condition skips its duplicate. Also
register InterceptTime.onChange listener in app.js for instant updates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Timezone fixes:
- Add utils.js (InterceptTime) to adsb_dashboard.html — was completely
missing, causing all times to fall back to UTC regardless of setting
- Register onChange listener in nav.html so clock updates instantly
when timezone/format is changed in Settings
- Initialize timezone/format dropdowns on ADS-B dashboard page load
- Browser-verified: ET/12h ↔ UTC/24h switches instantly on ADS-B page
VDL2 correlation fix:
- Force ICAO hex to uppercase when promoting from VDL2 src.addr (dumpvdl2
may output lowercase, ADS-B stores uppercase — case mismatch prevented
correlator from matching)
- Move ICAO/addr promotion before ACARS field extraction so even
non-ACARS VDL2 frames (XID, connection mgmt) get correlated
Auth:
- Add INTERCEPT_DISABLE_AUTH env var to skip login for local/dev use
- Configurable via docker-compose.yml environment
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: dumpvdl2 outputs nested JSON (vdl2.avlc.acars.flight) but
FlightCorrelator only checks top-level fields. VDL2 messages were stored
in the correlator but never matched to any aircraft.
Fix: Promote identifying fields (flight, reg, tail, icao, addr, label,
text) from the nested VDL2 structure to top-level before storing in the
correlator. Also promote AVLC source address as ICAO when src.type is
"Aircraft".
Also fix VDL2 sidebar timestamps to use global InterceptTime setting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use global InterceptTime for all ACARS timestamps (respects Eastern/12h)
- Add weather message rendering (wind, temperature, turbulence)
- Add CPDLC controller-pilot message rendering (purple highlight)
- Add squawk code change rendering (red highlight)
- Fix engine_data crash when parsed value isn't an object
- Show tail/registration alongside flight number on all cards
- Increase message text truncation to 200 chars
- Add FL prefix to flight level in position reports
- Applied consistently across ADS-B dashboard, sidebar feed, and standalone ACARS mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Global time preferences (Settings > Display > Time & Timezone):
- InterceptTime utility in core/utils.js with timezone + 12h/24h support
- Timezone options: UTC, Local, Eastern, Central, Mountain, Pacific
- Time format: 12-hour (AM/PM) or 24-hour toggle
- Defaults to US/Eastern + 12-hour
- Header nav clock updates to use selected timezone and format
- Weather satellite mode delegates to global InterceptTime
- Settings persist via localStorage, change listeners notify all modes
Weather satellite improvements:
- Satellite dropdown defaults to "All Meteor Satellites" showing all passes
- Can still filter to specific satellite (M2-3, M2-4, M2-4-80K)
- Capture button on pass cards auto-selects the correct satellite
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass prediction improvements:
- Widen prediction window to 48h at 5° min elevation (was 24h/15°)
- Add AOS/TCA/LOS pass geometry detail panel with times and bearings
- Fix duration display (was showing seconds labeled as minutes)
- Enhanced pass cards with AOS/LOS times, bearings, and directions
- Add REFRESH button in passes panel header
- Better empty state with clear "set your location" prompt and icon
Countdown and visual:
- Pulse animation on countdown when pass is imminent or active
- Countdown numbers scale up and change color for urgency
Sidebar getting started guide:
- New "Getting Started" section explaining what Meteor satellites are,
polar orbits, 4-8 passes/day, step-by-step workflow
- "When to look" tips (elevation, day vs night, pass direction)
- "What you need" equipment table with costs
- Collapsed antenna guide by default to reduce initial overwhelm
- Improved offline decode section with clear instructions on where
to get IQ recordings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Docker fixes:
- Add missing COPY for /usr/local/share/ (pipeline definitions were never
reaching the runtime image — root cause of silent SatDump failures)
- Add libfftw3-double3 and libfftw3-single3 runtime dependencies
- Handle arm64 vs x86 install path differences (/usr vs /usr/local)
- Split SatDump compile and staging into separate layers for better caching
- Add build-time assertions to catch missing pipelines early
UI enhancements:
- Timezone selector (UTC, Local, Eastern, Central, Mountain, Pacific)
with localStorage persistence — all time displays update instantly
- Pass analysis bar showing 24h quality breakdown and best upcoming pass
- Enhanced pass cards with cardinal direction (NW→SE), BEST badge
- Console timestamps, log level filters (ALL/SIGNAL/PROG/ERR), COPY/CLR
- Pass count in stats strip
- Demo data mode for UI testing without SDR or live satellite pass
- Meteor M2-4 80k baud fallback pipeline option
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
config.VERSION was not updated when the v2.26.13 tag was created,
causing the update checker to always report an update available on
fresh installs and git pulls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move aircraft_db.json and aircraft_db_meta.json from the project root
to data/adsb/ so they survive container restarts and rebuilds. Add
matching volume mount to both Docker Compose profiles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mounting ./data:/app/data caused the host directory to shadow the
entire /app/data Python package, making modules like data.oui
unavailable and crashing gunicorn on startup. Mount only the three
runtime output subdirectories instead.
Fixes#200
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Access module-level _sweep_running, _current_sweep_id, and tscm_queue
via explicit package import to avoid UnboundLocalError from closure
variable shadowing in route handlers
- Remove orphaned tscmProgressBar.style.width assignment in index.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix deep scan with 'All bands' never scanning 5GHz: band='all' now
correctly passes --band abg to airodump-ng (previously no flag was
added, causing airodump-ng to default to 2.4GHz-only)
- Fix APs first seen without channel info permanently stuck at
band='unknown': _update_access_point now backfills channel, frequency,
and band when a subsequent observation resolves the channel
- Fix legacy /wifi/scan/start combining mutually exclusive --band and -c
flags: --band is now only added when no explicit channel list is given,
and the interface is always placed as the last argument
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When using a remote SBS feed, no local SDR is needed. The pre-flight
device conflict check was running regardless and stopping whichever
mode had the selected SDR device claimed — even though ADS-B remote
mode never touches a local SDR. Skip the conflict check when remoteConfig is set.
check_tools() was using cmd_exists on 'auto_rx.py' which fails because
it's never in PATH — installed to /opt/radiosonde_auto_rx/. Now uses
the same file-based check as tool_is_installed(), consistent with
health check and status view.
Precise calculation showed mission-drawer (~1100px) was 213px taller
than command-rail content (~887px), leaving 213px of empty background
at the bottom of the right column.
Three targeted reductions to mission-drawer height (~234px total):
- drawer-actions: stacked 1-column → 3-column row (-76px)
- drawer-list: max-height 240px → 180px (-40px)
- drawer-info-grid: 1-column → 2-column for Quick Info (-118px)
Mission-drawer drops to ~866px, command-rail (~887px) now drives the
primary-layout row height — gap closes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The command-rail's 1fr last row caused the sky view panel to fill all
remaining column height (driven by the tall mission-drawer), showing a
large empty bordered space below the pass data strip.
Switch to all-auto rows with align-content: start so each panel is
exactly as tall as its content — the open background below the column
looks intentional rather than a panel with dead space inside it.
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>
1fr in the grid row caused the panel to fill the entire remaining page
height on mobile (~1000px+), leaving large gaps around the centred content.
max-height: min(55vh, 520px) keeps it proportionate on any screen size.
Also switch to justify-content: flex-start so the canvas+strip pack at
the top rather than floating in the middle of a large void.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fills the empty space below the sky view circle with a compact
three-column AOS / TCA / LOS readout (time + azimuth/elevation)
and a duration + max elevation footer line.
Populated by drawPolarPlot() when a pass is selected; shows a
placeholder prompt otherwise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>