mirror of
https://github.com/smittix/intercept.git
synced 2026-06-14 00:33:35 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b9c4ebebd | |||
| 7ed039564b | |||
| 8adfb3a40a |
@@ -2,6 +2,20 @@
|
|||||||
|
|
||||||
All notable changes to iNTERCEPT will be documented in this file.
|
All notable changes to iNTERCEPT will be documented in this file.
|
||||||
|
|
||||||
|
## [2.26.11] - 2026-03-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **APRS map ignores configured observer position** — The APRS map always fell back to the centre of the US (39.8°N, 98.6°W) when no live GPS fix was available, ignoring the observer position configured in `.env` (`INTERCEPT_DEFAULT_LAT` / `INTERCEPT_DEFAULT_LON`). Now seeds the APRS user location from the shared observer location on page load, so the map centres correctly and distance calculations work. (#193)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.26.10] - 2026-03-14
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **APRS stop timeout and inverted SDR device status** — The APRS stop endpoint terminated two processes sequentially (up to 4s) while the frontend timed out at 2.2s, causing console errors and the SDR status panel to show stale state (active after stop, idle during use). Now releases the SDR device immediately and terminates processes in a background thread so the response returns instantly. (#194)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [2.26.9] - 2026-03-14
|
## [2.26.9] - 2026-03-14
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -7,15 +7,22 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Application version
|
# Application version
|
||||||
VERSION = "2.26.9"
|
VERSION = "2.26.12"
|
||||||
|
|
||||||
# Changelog - latest release notes (shown on welcome screen)
|
# Changelog - latest release notes (shown on welcome screen)
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
{
|
{
|
||||||
"version": "2.26.9",
|
"version": "2.26.12",
|
||||||
"date": "March 2026",
|
"date": "March 2026",
|
||||||
"highlights": [
|
"highlights": [
|
||||||
"ADS-B bias-t fallback via rtl_biast for Blog V4 when dump1090 lacks native support",
|
"AIS and ADS-B dashboards now use configured observer position from .env",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2.26.11",
|
||||||
|
"date": "March 2026",
|
||||||
|
"highlights": [
|
||||||
|
"APRS map now centres on configured observer position from .env",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "intercept"
|
name = "intercept"
|
||||||
version = "2.26.9"
|
version = "2.26.11"
|
||||||
description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth"
|
description = "Signal Intelligence Platform - Pager/433MHz/ADS-B/Satellite/WiFi/Bluetooth"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ from config import (
|
|||||||
ADSB_DB_PORT,
|
ADSB_DB_PORT,
|
||||||
ADSB_DB_USER,
|
ADSB_DB_USER,
|
||||||
ADSB_HISTORY_ENABLED,
|
ADSB_HISTORY_ENABLED,
|
||||||
|
DEFAULT_LATITUDE,
|
||||||
|
DEFAULT_LONGITUDE,
|
||||||
SHARED_OBSERVER_LOCATION_ENABLED,
|
SHARED_OBSERVER_LOCATION_ENABLED,
|
||||||
)
|
)
|
||||||
from utils import aircraft_db
|
from utils import aircraft_db
|
||||||
@@ -1197,6 +1199,8 @@ def adsb_dashboard():
|
|||||||
'adsb_dashboard.html',
|
'adsb_dashboard.html',
|
||||||
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
||||||
adsb_auto_start=ADSB_AUTO_START,
|
adsb_auto_start=ADSB_AUTO_START,
|
||||||
|
default_latitude=DEFAULT_LATITUDE,
|
||||||
|
default_longitude=DEFAULT_LONGITUDE,
|
||||||
embedded=embedded,
|
embedded=embedded,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -15,7 +15,7 @@ import time
|
|||||||
from flask import Blueprint, Response, jsonify, render_template, request
|
from flask import Blueprint, Response, jsonify, render_template, request
|
||||||
|
|
||||||
import app as app_module
|
import app as app_module
|
||||||
from config import SHARED_OBSERVER_LOCATION_ENABLED
|
from config import DEFAULT_LATITUDE, DEFAULT_LONGITUDE, SHARED_OBSERVER_LOCATION_ENABLED
|
||||||
from utils.constants import (
|
from utils.constants import (
|
||||||
AIS_RECONNECT_DELAY,
|
AIS_RECONNECT_DELAY,
|
||||||
AIS_SOCKET_TIMEOUT,
|
AIS_SOCKET_TIMEOUT,
|
||||||
@@ -542,5 +542,7 @@ def ais_dashboard():
|
|||||||
return render_template(
|
return render_template(
|
||||||
'ais_dashboard.html',
|
'ais_dashboard.html',
|
||||||
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
shared_observer_location=SHARED_OBSERVER_LOCATION_ENABLED,
|
||||||
|
default_latitude=DEFAULT_LATITUDE,
|
||||||
|
default_longitude=DEFAULT_LONGITUDE,
|
||||||
embedded=embedded,
|
embedded=embedded,
|
||||||
)
|
)
|
||||||
|
|||||||
+30
-16
@@ -1924,7 +1924,13 @@ def start_aprs() -> Response:
|
|||||||
|
|
||||||
@aprs_bp.route('/stop', methods=['POST'])
|
@aprs_bp.route('/stop', methods=['POST'])
|
||||||
def stop_aprs() -> Response:
|
def stop_aprs() -> Response:
|
||||||
"""Stop APRS decoder."""
|
"""Stop APRS decoder.
|
||||||
|
|
||||||
|
Releases the SDR device immediately so the status panel updates
|
||||||
|
without waiting for process termination. Process cleanup runs in a
|
||||||
|
background thread to avoid blocking the HTTP response (which caused
|
||||||
|
frontend timeout errors when two processes each took up to 2s to die).
|
||||||
|
"""
|
||||||
global aprs_active_device, aprs_active_sdr_type
|
global aprs_active_device, aprs_active_sdr_type
|
||||||
|
|
||||||
with app_module.aprs_lock:
|
with app_module.aprs_lock:
|
||||||
@@ -1939,6 +1945,28 @@ def stop_aprs() -> Response:
|
|||||||
if not processes_to_stop:
|
if not processes_to_stop:
|
||||||
return api_error('APRS decoder not running', 400)
|
return api_error('APRS decoder not running', 400)
|
||||||
|
|
||||||
|
# Release SDR device immediately so status panel reflects the
|
||||||
|
# change without waiting for process termination.
|
||||||
|
if aprs_active_device is not None:
|
||||||
|
app_module.release_sdr_device(aprs_active_device, aprs_active_sdr_type or 'rtlsdr')
|
||||||
|
aprs_active_device = None
|
||||||
|
aprs_active_sdr_type = None
|
||||||
|
|
||||||
|
# Capture refs to clear before releasing the lock
|
||||||
|
master_fd = getattr(app_module, 'aprs_master_fd', None)
|
||||||
|
app_module.aprs_process = None
|
||||||
|
if hasattr(app_module, 'aprs_rtl_process'):
|
||||||
|
app_module.aprs_rtl_process = None
|
||||||
|
app_module.aprs_master_fd = None
|
||||||
|
|
||||||
|
# Terminate processes in background so the response returns fast.
|
||||||
|
# Each proc.wait() can block up to PROCESS_TERMINATE_TIMEOUT (2s),
|
||||||
|
# which previously caused the frontend 2200ms fetch to abort.
|
||||||
|
def _cleanup():
|
||||||
|
# Close PTY master fd first — this unblocks the stream thread
|
||||||
|
if master_fd is not None:
|
||||||
|
with contextlib.suppress(OSError):
|
||||||
|
os.close(master_fd)
|
||||||
for proc in processes_to_stop:
|
for proc in processes_to_stop:
|
||||||
try:
|
try:
|
||||||
proc.terminate()
|
proc.terminate()
|
||||||
@@ -1948,21 +1976,7 @@ def stop_aprs() -> Response:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping APRS process: {e}")
|
logger.error(f"Error stopping APRS process: {e}")
|
||||||
|
|
||||||
# Close PTY master fd
|
threading.Thread(target=_cleanup, daemon=True).start()
|
||||||
if hasattr(app_module, 'aprs_master_fd') and app_module.aprs_master_fd is not None:
|
|
||||||
with contextlib.suppress(OSError):
|
|
||||||
os.close(app_module.aprs_master_fd)
|
|
||||||
app_module.aprs_master_fd = None
|
|
||||||
|
|
||||||
app_module.aprs_process = None
|
|
||||||
if hasattr(app_module, 'aprs_rtl_process'):
|
|
||||||
app_module.aprs_rtl_process = None
|
|
||||||
|
|
||||||
# Release SDR device
|
|
||||||
if aprs_active_device is not None:
|
|
||||||
app_module.release_sdr_device(aprs_active_device, aprs_active_sdr_type or 'rtlsdr')
|
|
||||||
aprs_active_device = None
|
|
||||||
aprs_active_sdr_type = None
|
|
||||||
|
|
||||||
return jsonify({'status': 'stopped'})
|
return jsonify({'status': 'stopped'})
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
<script>
|
<script>
|
||||||
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
||||||
window.INTERCEPT_ADSB_AUTO_START = {{ adsb_auto_start | tojson }};
|
window.INTERCEPT_ADSB_AUTO_START = {{ adsb_auto_start | tojson }};
|
||||||
|
window.INTERCEPT_DEFAULT_LAT = {{ default_latitude | tojson }};
|
||||||
|
window.INTERCEPT_DEFAULT_LON = {{ default_longitude | tojson }};
|
||||||
</script>
|
</script>
|
||||||
{% if offline_settings.assets_source == 'local' %}
|
{% if offline_settings.assets_source == 'local' %}
|
||||||
<script defer src="{{ url_for('static', filename='vendor/leaflet/leaflet.js') }}"></script>
|
<script defer src="{{ url_for('static', filename='vendor/leaflet/leaflet.js') }}"></script>
|
||||||
@@ -340,8 +342,8 @@
|
|||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<span class="control-group-label">LOCATION</span>
|
<span class="control-group-label">LOCATION</span>
|
||||||
<div class="control-group-items">
|
<div class="control-group-items">
|
||||||
<input type="text" id="obsLat" value="51.5074" onchange="updateObserverLoc()" style="width: 70px;" title="Latitude" placeholder="Lat">
|
<input type="text" id="obsLat" value="{{ default_latitude }}" onchange="updateObserverLoc()" style="width: 70px;" title="Latitude" placeholder="Lat">
|
||||||
<input type="text" id="obsLon" value="-0.1278" onchange="updateObserverLoc()" style="width: 70px;" title="Longitude" placeholder="Lon">
|
<input type="text" id="obsLon" value="{{ default_longitude }}" onchange="updateObserverLoc()" style="width: 70px;" title="Longitude" placeholder="Lon">
|
||||||
<span id="gpsIndicator" class="gps-indicator" style="display: none;" title="GPS connected via gpsd"><span class="gps-dot"></span> GPS</span>
|
<span id="gpsIndicator" class="gps-indicator" style="display: none;" title="GPS connected via gpsd"><span class="gps-dot"></span> GPS</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -643,7 +645,9 @@
|
|||||||
if (parsed.lat !== undefined && parsed.lat !== null && parsed.lon !== undefined && parsed.lon !== null) return parsed;
|
if (parsed.lat !== undefined && parsed.lat !== null && parsed.lon !== undefined && parsed.lon !== null) return parsed;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
return { lat: 51.5074, lon: -0.1278 };
|
const defaultLat = window.INTERCEPT_DEFAULT_LAT || 51.5074;
|
||||||
|
const defaultLon = window.INTERCEPT_DEFAULT_LON || -0.1278;
|
||||||
|
return { lat: defaultLat, lon: defaultLon };
|
||||||
})();
|
})();
|
||||||
let rangeRingsLayer = null;
|
let rangeRingsLayer = null;
|
||||||
let observerMarker = null;
|
let observerMarker = null;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
<!-- Deferred scripts -->
|
<!-- Deferred scripts -->
|
||||||
<script>
|
<script>
|
||||||
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
window.INTERCEPT_SHARED_OBSERVER_LOCATION = {{ shared_observer_location | tojson }};
|
||||||
|
window.INTERCEPT_DEFAULT_LAT = {{ default_latitude | tojson }};
|
||||||
|
window.INTERCEPT_DEFAULT_LON = {{ default_longitude | tojson }};
|
||||||
</script>
|
</script>
|
||||||
{% if offline_settings.assets_source == 'local' %}
|
{% if offline_settings.assets_source == 'local' %}
|
||||||
<script defer src="{{ url_for('static', filename='vendor/leaflet/leaflet.js') }}"></script>
|
<script defer src="{{ url_for('static', filename='vendor/leaflet/leaflet.js') }}"></script>
|
||||||
@@ -185,8 +187,8 @@
|
|||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<span class="control-group-label">LOCATION</span>
|
<span class="control-group-label">LOCATION</span>
|
||||||
<div class="control-group-items">
|
<div class="control-group-items">
|
||||||
<input type="text" id="obsLat" value="51.5074" onchange="updateObserverLoc()" style="width: 70px;" title="Latitude" placeholder="Lat">
|
<input type="text" id="obsLat" value="{{ default_latitude }}" onchange="updateObserverLoc()" style="width: 70px;" title="Latitude" placeholder="Lat">
|
||||||
<input type="text" id="obsLon" value="-0.1278" onchange="updateObserverLoc()" style="width: 70px;" title="Longitude" placeholder="Lon">
|
<input type="text" id="obsLon" value="{{ default_longitude }}" onchange="updateObserverLoc()" style="width: 70px;" title="Longitude" placeholder="Lon">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -248,7 +250,9 @@
|
|||||||
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||||
return ObserverLocation.getForModule('ais_observerLocation');
|
return ObserverLocation.getForModule('ais_observerLocation');
|
||||||
}
|
}
|
||||||
return { lat: 51.5074, lon: -0.1278 };
|
const defaultLat = window.INTERCEPT_DEFAULT_LAT || 51.5074;
|
||||||
|
const defaultLon = window.INTERCEPT_DEFAULT_LON || -0.1278;
|
||||||
|
return { lat: defaultLat, lon: defaultLon };
|
||||||
})();
|
})();
|
||||||
let rangeRingsLayer = null;
|
let rangeRingsLayer = null;
|
||||||
let observerMarker = null;
|
let observerMarker = null;
|
||||||
|
|||||||
+21
-1
@@ -9849,8 +9849,28 @@
|
|||||||
let aprsMeterCheckInterval = null;
|
let aprsMeterCheckInterval = null;
|
||||||
const APRS_METER_TIMEOUT = 5000; // 5 seconds for "no signal" state
|
const APRS_METER_TIMEOUT = 5000; // 5 seconds for "no signal" state
|
||||||
|
|
||||||
// APRS user location (from GPS)
|
// APRS user location (from GPS or shared observer location)
|
||||||
let aprsUserLocation = { lat: null, lon: null };
|
let aprsUserLocation = { lat: null, lon: null };
|
||||||
|
|
||||||
|
// Seed from configured observer location so the map centres on the
|
||||||
|
// user's position even without a live GPS fix.
|
||||||
|
(function _seedAprsLocation() {
|
||||||
|
if (typeof ObserverLocation !== 'undefined' && ObserverLocation.getShared) {
|
||||||
|
const shared = ObserverLocation.getShared();
|
||||||
|
if (shared && shared.lat && shared.lon) {
|
||||||
|
aprsUserLocation.lat = shared.lat;
|
||||||
|
aprsUserLocation.lon = shared.lon;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback: read the Jinja-injected defaults directly
|
||||||
|
const lat = window.INTERCEPT_DEFAULT_LAT;
|
||||||
|
const lon = window.INTERCEPT_DEFAULT_LON;
|
||||||
|
if (lat && lon && Number.isFinite(lat) && Number.isFinite(lon)) {
|
||||||
|
aprsUserLocation.lat = lat;
|
||||||
|
aprsUserLocation.lon = lon;
|
||||||
|
}
|
||||||
|
})();
|
||||||
let aprsUserMarker = null;
|
let aprsUserMarker = null;
|
||||||
|
|
||||||
// Calculate distance in miles using Haversine formula
|
// Calculate distance in miles using Haversine formula
|
||||||
|
|||||||
Reference in New Issue
Block a user