1. utils/weather_sat.py — Added delete_all_images() method that globs for *.png, *.jpg, *.jpeg in the output dir, unlinks each, clears _images list, and returns the
count.
2. routes/weather_sat.py — Added DELETE /weather-sat/images route that calls decoder.delete_all_images() and returns {'status': 'ok', 'deleted': count}.
3. static/js/modes/weather-satellite.js:
- Added currentModalFilename state variable
- renderGallery() now sorts images by timestamp descending, groups by date using toLocaleDateString(), renders date headers spanning the grid, and adds a delete
overlay button on each card
- showImage() accepts a filename param, stores it in currentModalFilename, and creates a modal toolbar with a delete button
- Added deleteImage(filename) — confirm dialog → DELETE /weather-sat/images/{filename} → filter from array → re-render + close modal
- Added deleteAllImages() — confirm dialog → DELETE /weather-sat/images → clear array → re-render
- Exposed deleteImage, deleteAllImages, and _getModalFilename in public API
4. static/css/modes/weather-satellite.css:
- Added position: relative to .wxsat-image-card
- .wxsat-image-actions — absolute top-right overlay, hidden by default, appears on card hover
- .wxsat-image-actions button — dark background, turns red on hover
- .wxsat-date-header — full-grid-width date separator with dimmed uppercase text
- .wxsat-modal-toolbar — absolute top-left in modal for the delete button
- .wxsat-modal-btn.delete — turns red on hover
- .wxsat-gallery-clear-btn — subtle icon button, pushed right via margin-left: auto, turns red on hover
- Updated .wxsat-gallery-header from justify-content: space-between to gap: 8px for proper 3-child layout
5. templates/index.html — Added clear-all trash button with SVG icon in the gallery header, wired to WeatherSat.deleteAllImages().
Merges upstream changes into fork while preserving weather satellite
(NOAA APT/Meteor LRPT via SatDump), rtlamr, multi-arch build, and
decoder console features from our branch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Kill audio pipeline when startup produces no data instead of leaving
zombie processes running
- Skip unnecessary 1s USB release delay when no processes were active
- Remove racy fresh=1 pipeline restart from stream endpoint
- Await stopScanner() before starting direct listen to prevent race
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stream threads for sensor, pager, acars, rtlamr, dmr, and dsc modes
never called release_sdr_device() when their SDR process crashed,
leaving devices permanently locked in the registry. Also fixes orphaned
companion processes (rtl_fm, rtl_tcp) not being killed on crash, start
path failures leaking processes, DMR stop handler missing lock, and
listening post/audio websocket pkill nuking all system-wide rtl_fm
processes. Wires up register_process()/unregister_process() so the
atexit/signal cleanup safety net actually works, and adds rtl_tcp,
rtl_power, rtlamr, ffmpeg to the killall endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dmrRawOutput div was rendering garbled box-drawing characters from
the dsd-fme ASCII art banner below the signal activity canvas. Remove
the div and filter banner lines (box-drawing chars, version info) in
the parser so they never become events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The stream thread used a blocking readline() with no timeout, so once
DSD finished outputting its startup banner there were no more events
until actual signal activity. The frontend decayed to zero and appeared
dead. If DSD crashed, the synthesizer state never transitioned to
'stopped' so there was no visual or textual indication of failure.
- Use select() with 1s timeout on DSD stderr to avoid indefinite block
- Send heartbeat events every 3s while decoder is alive but idle
- Detect DSD crashes: capture exit code and remaining stderr, send as
'crashed' status with details and show notification to user
- Frontend properly transitions synthesizer to 'stopped' on process
death (was only happening on user-initiated stop)
- Increase idle breathing amplitude so LISTENING state is clearly
visible (0.12 +/- 0.06 vs old 0.05 +/- 0.035)
- Release device reservation on crash, not just user stop
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DMR was missing checkDeviceAvailability/reserveDevice/releaseDevice
calls that other modes (SSTV, listening post) use, so the device
dropdown showed device 0 as available even when another process held
it. Also detect USB claim errors from rtl_fm and surface a clear
message telling the user to pick a different device.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The DSD stderr parser had regex ordering bugs that swallowed voice and
call events as bare slot events, and only matched classic dsd output
format (not dsd-fme). Unmatched lines were silently dropped, leaving
the signal activity panel with nothing to display.
- Reorder regex checks: TG/Src before voice before slot
- Support dsd-fme comma-separated format (TG: x, Src: y)
- Make bare slot regex strict (only standalone "Slot N" lines)
- Forward unmatched DSD lines as raw events for diagnostics
- Add LISTENING state to signal activity panel for raw output
- Show raw decoder output text below synthesizer canvas
- Fix test mocks for find_dsd() tuple return value
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The APRS route called app_module.reserve_sdr_device() which does not
exist, causing an AttributeError that Flask returned as an HTML error
page. The frontend then failed to parse it as JSON, showing
"Unexpected token '<'" to the user. Fixed to use claim_sdr_device()
which is the correct function used by all other modes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SSTV was not claiming/releasing SDR devices through the centralized
registry, so the device state panel always showed the device as idle
during SSTV use. Added claim_sdr_device/release_sdr_device on the
backend and reserveDevice/releaseDevice on the frontend, matching the
pattern used by all other modes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Users can now manage decoded SSTV images with download and delete actions
accessible from hover overlays on gallery cards, the full-size image modal
toolbar, and a "Clear All" button in the gallery header. Both ISS and
General SSTV modes are supported.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
slowrx is a GTK GUI app that doesn't support CLI usage, so the SSTV
decoder was silently failing. This replaces it with a pure Python
implementation using numpy and Pillow that supports Robot36/72,
Martin1/2, Scottie1/2, and PD120/180 modes via VIS header auto-detection.
Key implementation details:
- Generalized Goertzel (DTFT) for exact-frequency tone detection
- Vectorized batch Goertzel for real-time pixel decoding performance
- Overlapping analysis windows for short-window frequency estimation
- VIS header detection state machine with parity validation
- Per-line sync re-synchronization for drift tolerance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rtl_fm stderr was sent to DEVNULL, hiding the actual failure reason
(rc=1). Now captured and surfaced in the error response. Also drains
rtl_fm stderr during normal operation to prevent pipe blocking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use -o - (stdout) instead of -o /dev/null for audio output, as
dsd-fme expects specific output targets. Remove -N flag which may
cause issues in headless mode. Add stderr capture on pipeline
failure for better error messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
dsd-fme uses different protocol flags than classic dsd (e.g. -fs for
DMR instead of -fd, -f1 for P25 instead of -fp). Add -N flag to
disable ncurses terminal which is required when reading from stdin pipe.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check for dsd-fme binary (common fork) before falling back to dsd.
Disable audio output with -o /dev/null to prevent PulseAudio
connection failures when running under sudo.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DMR/P25 digital voice decoder mode with DSD-FME integration
- WebSDR mode with KiwiSDR audio proxy and websocket-client support
- Listening post waterfall/spectrogram visualization and audio streaming
- Dockerfile updates for mbelib and DSD-FME build dependencies
- New tests for DMR, WebSDR, KiwiSDR, waterfall, and signal guess API
- Chart.js date adapter for time-scale axes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a general-purpose SSTV decoder alongside the existing ISS SSTV mode,
supporting USB/LSB/FM modulation on common amateur radio HF/VHF/UHF
frequencies (14.230 MHz USB, 3.845 MHz LSB, etc.) with auto-detection
of modulation from preset frequency table.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SDR device reservation to prevent conflicts with other modes, and
capture rtl_fm stderr so actual error messages are reported to the user
instead of a generic exit code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix SDR device stuck claimed on capture failure via on_complete callback
- Improve SatDump output parsing to emit all lines (throttled 2s) for real-time feedback
- Extract shared pass prediction into utils/weather_sat_predict.py with trajectory/ground track support
- Add auto-scheduler (utils/weather_sat_scheduler.py) using threading.Timer for unattended captures
- Add scheduler API endpoints (enable/disable/status/passes/skip) with SSE event notifications
- Add countdown timer (D/H/M/S) with imminent/active glow states
- Add 24h timeline bar with colored pass markers and current-time cursor
- Add canvas polar plot showing az/el trajectory arc with cardinal directions
- Add Leaflet ground track map with satellite path and observer marker
- Restructure to 3-column layout (passes | polar+map | gallery) with responsive stacking
- Add auto-schedule toggle in strip bar and sidebar
- Add rtlamr (Go utility meter decoder) to Dockerfile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fragile platform-specific WiFi detection with the same
scanner._detect_interfaces() used by the actual scanning code,
eliminating false "No wireless interfaces found" warnings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use networksetup instead of deprecated airport utility for macOS WiFi detection
- Fix SDRDevice attribute access (use getattr instead of dict .get())
- Move Detected Threats panel next to RF Signals in 2-column grid
- Always run correlation/identity analysis at sweep end, even if stopped by user
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The sweep loop's WiFi/BT/RF scan processing had unprotected
timeline_manager.add_observation() calls that could crash an entire
scan iteration, silently preventing all device events from reaching
the frontend. Additionally, scan interval timestamps were only updated
at the end of processing, causing tight retry loops on persistent errors.
- Wrap timeline observation calls in try/except for all three protocols
- Move last_*_scan timestamp updates immediately after scan completes
- Add per-device try/except so one bad device doesn't block others
- Emit sweep_progress after WiFi scan for real-time status visibility
- Log warning when WiFi scan returns 0 networks for easier diagnosis
- Add known_device and score_modifier fields to correlation engine
- Add TSCM scheduling, cases, known devices, and advanced WiFi indicators
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Open Notify API (api.open-notify.org) is frequently unreliable,
causing 5-second timeout delays on every ISS position request.
Promote wheretheiss.at as the primary API in both satellite.py
and sstv.py, demoting Open Notify to fallback.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /wifi/v2/clients endpoint was returning all clients regardless
of query parameters, because a duplicate route in wifi.py took
precedence over the filtered one in wifi_v2.py. Added bssid,
associated, and min_rssi filtering to the active route.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add retry mechanism (3 attempts) for usb_claim_interface errors when
the SDR device hasn't been fully released by a previous process. Also
kill rtl_power alongside rtl_fm during cleanup and increase the USB
release delay.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>