Set QT_QPA_PLATFORM=offscreen for both grgsm_livemon and
grgsm_scanner to prevent SIGABRT when no X11 display is available.
grgsm_livemon uses GNU Radio which loads Qt plugins — without a
display, Qt aborts with "could not connect to display".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add pre-flight checks (shutil.which) for grgsm_livemon and tshark
- Capture stderr when grgsm_livemon exits immediately (exit code 1)
- Start background stderr reader thread for ongoing livemon diagnostics
- Add idle_count grace period in SSE stream to handle scanner→monitor
transition without premature disconnect
- Forward monitor failure errors to SSE for frontend display
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The scanner and monitor are mutually exclusive (both need the SDR).
Previously auto-monitor tried to start mid-scan (causing device
conflicts) and required 3 towers (rarely achieved with weak signals).
Now after the first scan completes:
- If any towers were found, automatically stop scanner and start
grgsm_livemon + tshark on the strongest tower's ARFCN
- SDR handoff is clean (scanner process has already exited)
- If monitor fails to start, scanner loop resumes
- Scanner thread's finally block preserves SDR allocation when
monitor has taken over
- Frontend shows "Monitoring ARFCN X for devices..." status
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Forward scanner progress (%) and status to SSE stream
- Show progress bar and scan status in TRACKED TOWERS panel
- Send scan_complete event with tower count and duration
- Fix Europe BAND_CONFIG: only EGSM900 is recommended (GSM850/GSM800
are rarely used in Europe and waste scan time)
- DCS1800 available but not recommended (RTL-SDR sensitivity is lower)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CID=0 with valid MCC/MNC means the scanner found the cell but didn't
decode System Information 3/4 (which carries the Cell ID). These are
still valid towers worth displaying. Only filter when MCC=0 AND MNC=0
(truly unidentified signals).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- Filter out CID=0 and MCC=0 entries (ARFCNs with no decoded cell identity)
Frontend:
- Move stats update before coordinate check so towers always counted
- Fix signal_strength display using null check instead of || (0 is falsy)
- Show operator name, frequency, and status in tower detail panel
- Show "Located" indicator in tower list for geocoded towers
- Fix selectTower crash when tower has no coordinates
- Update placeholder text to "Select a tower from the list"
- Add try/catch to selectTower for error resilience
Tests:
- Add tests for CID=0 and MCC=0 filtering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Send all existing towers on SSE connect (fixes data loss on reconnect)
- Fix tower.signal -> tower.signal_strength field name in frontend
- Fix TypeError crash in selectTower when tower has no coordinates
- Add Connection: keep-alive header to SSE response
- Add comprehensive console.log debugging for SSE data flow
- Handle error/disconnected SSE event types in frontend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
grgsm_scanner is a Python/GNU Radio script, so stdbuf has no effect.
Setting PYTHONUNBUFFERED=1 in the subprocess env forces Python to
flush stdout on every write, enabling real-time scan output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
grgsm_scanner fully buffers stdout when piped, so scan results never
reach Python until the buffer fills or process exits. Wrapping with
stdbuf -oL forces line-buffered output for real-time data streaming.
Also increased scan timeout from 120s to 300s since scanning 4 bands
legitimately takes 2-3 minutes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ptrkrysik/gr-gsm repo uses SWIG which is incompatible with
GNU Radio 3.10+. The bkerler fork supports modern GNU Radio and
builds successfully on current Ubuntu/Debian systems.
Updated all references in Dockerfile, setup.sh, dependencies.py,
and error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
apt-packaged gr-gsm aborts with SIGABRT (-6) due to duplicate FSM
registration in libosmocore. Setting this env var suppresses the
fatal assertion, allowing grgsm_scanner to run normally.
Applied to both scanner and livemon subprocess spawns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
grgsm_scanner exits in <300ms with osmo_fsm assertion error due to
libosmocore incompatibility. Added crash detection: if process exits
in <5s with non-zero code, counts as crash. After 3 crashes, stops
retrying and sends error to SSE stream so the UI can display it.
Also drains remaining queue items after process exits and logs exit
code and scan duration for diagnostics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Global LOG_LEVEL defaults to WARNING, silencing all INFO/DEBUG logs.
GSM Spy needs verbose logging for scanner diagnostics. Override the
module logger level to DEBUG so scanner output is always visible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gsm_spy.py used logging.getLogger() directly which returns a bare
logger with no handler. The parent 'intercept' logger has
propagate=False, so all GSM Spy logs were silently dropped.
Now uses utils.logging.get_logger() which adds a stderr handler
and sets the log level, matching all other route modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
grgsm_scanner (like many GNU Radio tools) writes scan results to
stderr, not stdout. The stderr reader was only logging at debug
level and discarding lines. Now feeds stderr into the parse queue.
Also added info-level logging for all scanner output lines to aid
debugging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parser expected pipe-delimited table rows but grgsm_scanner outputs
comma-separated key-value pairs like:
ARFCN: 975, Freq: 925.2M, CID: 13522, LAC: 38722, MCC: 262, MNC: 1, Pwr: -58
This was the root cause of no data appearing in GSM Spy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
stop_scanner() cleared gsm_spy_active_device without calling
release_sdr_device(), so the device stayed claimed in the registry.
The scanner thread's finally block then saw None and skipped release.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GSM Spy was failing with FileNotFoundError because grgsm_scanner
wasn't installed. These tools are now installed automatically by
setup.sh (both Debian and macOS) and included in the Dockerfile,
matching how other tools like multimon-ng and ffmpeg are handled.
- setup.sh: Remove ask_yes_no prompts for gr-gsm and tshark, install
unconditionally; add check_recommended tier for final summary
- Dockerfile: Add tshark to apt layer, add gr-gsm RUN layer with
apt-then-source-build fallback, preseed debconf for tshark
- gsm_spy.py: Add shutil.which pre-check in start_scanner route,
catch FileNotFoundError in scanner_thread to stop retry loop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- kill_all() now resets gsm_spy_scanner_running and related state so
the scanner thread stops after killall
- scanner_thread sets flag to False instead of None on exit
- Restore alert_rules, alert_events, recording_sessions tables and
wifi_clients column removed by PR in database.py
- Escape all server-sourced values in analysis modals with escapeHtml()
- Reset gsm_towers_found/gsm_devices_tracked on stop to prevent
counter drift across sessions
- Replace raw terminate/kill with safe_terminate() in scanner_thread
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>