mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
Fix satellite dashboard refresh flows
This commit is contained in:
@@ -48,11 +48,18 @@ _tle_cache = dict(TLE_SATELLITES)
|
||||
|
||||
# Ground track cache: key=(sat_name, tle_line1[:20]) -> (track_data, computed_at_timestamp)
|
||||
# TTL is 1800 seconds (30 minutes)
|
||||
_track_cache: dict = {}
|
||||
_TRACK_CACHE_TTL = 1800
|
||||
_track_cache: dict = {}
|
||||
_TRACK_CACHE_TTL = 1800
|
||||
|
||||
_BUILTIN_NORAD_TO_KEY = {
|
||||
25544: 'ISS',
|
||||
40069: 'METEOR-M2',
|
||||
57166: 'METEOR-M2-3',
|
||||
59051: 'METEOR-M2-4',
|
||||
}
|
||||
|
||||
|
||||
def _load_db_satellites_into_cache():
|
||||
def _load_db_satellites_into_cache():
|
||||
"""Load user-tracked satellites from DB into the TLE cache."""
|
||||
global _tle_cache
|
||||
try:
|
||||
@@ -67,8 +74,102 @@ def _load_db_satellites_into_cache():
|
||||
loaded += 1
|
||||
if loaded:
|
||||
logger.info(f"Loaded {loaded} user-tracked satellites into TLE cache")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load DB satellites into TLE cache: {e}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load DB satellites into TLE cache: {e}")
|
||||
|
||||
|
||||
def _normalize_satellite_name(value: object) -> str:
|
||||
"""Normalize satellite identifiers for loose name matching."""
|
||||
return str(value or '').strip().replace(' ', '-').upper()
|
||||
|
||||
|
||||
def _get_tracked_satellite_maps() -> tuple[dict[int, dict], dict[str, dict]]:
|
||||
"""Return tracked satellites indexed by NORAD ID and normalized name."""
|
||||
by_norad: dict[int, dict] = {}
|
||||
by_name: dict[str, dict] = {}
|
||||
try:
|
||||
for sat in get_tracked_satellites():
|
||||
try:
|
||||
norad_id = int(sat['norad_id'])
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
by_norad[norad_id] = sat
|
||||
by_name[_normalize_satellite_name(sat.get('name'))] = sat
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read tracked satellites for lookup: {e}")
|
||||
return by_norad, by_name
|
||||
|
||||
|
||||
def _resolve_satellite_request(sat: object, tracked_by_norad: dict[int, dict], tracked_by_name: dict[str, dict]) -> tuple[str, int | None, tuple[str, str, str] | None]:
|
||||
"""Resolve a satellite request to display name, NORAD ID, and TLE data."""
|
||||
norad_id: int | None = None
|
||||
sat_key: str | None = None
|
||||
tracked: dict | None = None
|
||||
|
||||
if isinstance(sat, int):
|
||||
norad_id = sat
|
||||
elif isinstance(sat, str):
|
||||
stripped = sat.strip()
|
||||
if stripped.isdigit():
|
||||
norad_id = int(stripped)
|
||||
else:
|
||||
sat_key = stripped
|
||||
|
||||
if norad_id is not None:
|
||||
tracked = tracked_by_norad.get(norad_id)
|
||||
sat_key = _BUILTIN_NORAD_TO_KEY.get(norad_id) or (tracked.get('name') if tracked else str(norad_id))
|
||||
else:
|
||||
normalized = _normalize_satellite_name(sat_key)
|
||||
tracked = tracked_by_name.get(normalized)
|
||||
if tracked:
|
||||
try:
|
||||
norad_id = int(tracked['norad_id'])
|
||||
except (TypeError, ValueError):
|
||||
norad_id = None
|
||||
sat_key = tracked.get('name') or sat_key
|
||||
|
||||
tle_data = None
|
||||
candidate_keys: list[str] = []
|
||||
if sat_key:
|
||||
candidate_keys.extend([
|
||||
sat_key,
|
||||
_normalize_satellite_name(sat_key),
|
||||
])
|
||||
if tracked and tracked.get('name'):
|
||||
candidate_keys.extend([
|
||||
tracked['name'],
|
||||
_normalize_satellite_name(tracked['name']),
|
||||
])
|
||||
|
||||
seen: set[str] = set()
|
||||
for key in candidate_keys:
|
||||
norm = _normalize_satellite_name(key)
|
||||
if norm in seen:
|
||||
continue
|
||||
seen.add(norm)
|
||||
if key in _tle_cache:
|
||||
tle_data = _tle_cache[key]
|
||||
break
|
||||
if norm in _tle_cache:
|
||||
tle_data = _tle_cache[norm]
|
||||
break
|
||||
|
||||
if tle_data is None and tracked and tracked.get('tle_line1') and tracked.get('tle_line2'):
|
||||
display_name = tracked.get('name') or sat_key or str(norad_id or 'UNKNOWN')
|
||||
tle_data = (display_name, tracked['tle_line1'], tracked['tle_line2'])
|
||||
_tle_cache[_normalize_satellite_name(display_name)] = tle_data
|
||||
|
||||
if tle_data is None and sat_key:
|
||||
normalized = _normalize_satellite_name(sat_key)
|
||||
for key, value in _tle_cache.items():
|
||||
if key == normalized or _normalize_satellite_name(value[0]) == normalized:
|
||||
tle_data = value
|
||||
break
|
||||
|
||||
display_name = _BUILTIN_NORAD_TO_KEY.get(norad_id or -1)
|
||||
if not display_name:
|
||||
display_name = (tracked.get('name') if tracked else None) or (tle_data[0] if tle_data else None) or (sat_key if sat_key else str(norad_id or 'UNKNOWN'))
|
||||
return display_name, norad_id, tle_data
|
||||
|
||||
|
||||
def _start_satellite_tracker():
|
||||
@@ -328,45 +429,30 @@ def predict_passes():
|
||||
except ValueError as e:
|
||||
return api_error(str(e), 400)
|
||||
|
||||
norad_to_name = {
|
||||
25544: 'ISS',
|
||||
40069: 'METEOR-M2',
|
||||
57166: 'METEOR-M2-3',
|
||||
59051: 'METEOR-M2-4',
|
||||
}
|
||||
|
||||
sat_input = data.get('satellites', ['ISS', 'METEOR-M2-3', 'METEOR-M2-4'])
|
||||
satellites = []
|
||||
for sat in sat_input:
|
||||
if isinstance(sat, int) and sat in norad_to_name:
|
||||
satellites.append(norad_to_name[sat])
|
||||
else:
|
||||
satellites.append(sat)
|
||||
|
||||
passes = []
|
||||
passes = []
|
||||
colors = {
|
||||
'ISS': '#00ffff',
|
||||
'METEOR-M2': '#9370DB',
|
||||
'METEOR-M2-3': '#ff00ff',
|
||||
'METEOR-M2-4': '#00ff88',
|
||||
}
|
||||
name_to_norad = {v: k for k, v in norad_to_name.items()}
|
||||
|
||||
ts = _get_timescale()
|
||||
observer = wgs84.latlon(lat, lon)
|
||||
t0 = ts.now()
|
||||
t1 = ts.utc(t0.utc_datetime() + timedelta(hours=hours))
|
||||
|
||||
for sat_name in satellites:
|
||||
if sat_name not in _tle_cache:
|
||||
continue
|
||||
|
||||
tle_data = _tle_cache[sat_name]
|
||||
|
||||
# Current position for map marker (computed once per satellite)
|
||||
current_pos = None
|
||||
try:
|
||||
satellite = EarthSatellite(tle_data[1], tle_data[2], tle_data[0], ts)
|
||||
tracked_by_norad, tracked_by_name = _get_tracked_satellite_maps()
|
||||
|
||||
ts = _get_timescale()
|
||||
observer = wgs84.latlon(lat, lon)
|
||||
t0 = ts.now()
|
||||
t1 = ts.utc(t0.utc_datetime() + timedelta(hours=hours))
|
||||
|
||||
for sat in sat_input:
|
||||
sat_name, norad_id, tle_data = _resolve_satellite_request(sat, tracked_by_norad, tracked_by_name)
|
||||
if not tle_data:
|
||||
continue
|
||||
|
||||
# Current position for map marker (computed once per satellite)
|
||||
current_pos = None
|
||||
try:
|
||||
satellite = EarthSatellite(tle_data[1], tle_data[2], tle_data[0], ts)
|
||||
geo = satellite.at(ts.now())
|
||||
sp = wgs84.subpoint(geo)
|
||||
current_pos = {
|
||||
@@ -376,14 +462,14 @@ def predict_passes():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sat_passes = _predict_passes(tle_data, observer, ts, t0, t1, min_el=min_el)
|
||||
for p in sat_passes:
|
||||
p['satellite'] = sat_name
|
||||
p['norad'] = name_to_norad.get(sat_name, 0)
|
||||
p['color'] = colors.get(sat_name, '#00ff00')
|
||||
if current_pos:
|
||||
p['currentPos'] = current_pos
|
||||
passes.extend(sat_passes)
|
||||
sat_passes = _predict_passes(tle_data, observer, ts, t0, t1, min_el=min_el)
|
||||
for p in sat_passes:
|
||||
p['satellite'] = sat_name
|
||||
p['norad'] = norad_id or 0
|
||||
p['color'] = colors.get(sat_name, '#00ff00')
|
||||
if current_pos:
|
||||
p['currentPos'] = current_pos
|
||||
passes.extend(sat_passes)
|
||||
|
||||
passes.sort(key=lambda p: p['startTimeISO'])
|
||||
|
||||
@@ -413,35 +499,23 @@ def get_satellite_position():
|
||||
sat_input = data.get('satellites', [])
|
||||
include_track = bool(data.get('includeTrack', True))
|
||||
|
||||
norad_to_name = {
|
||||
25544: 'ISS',
|
||||
40069: 'METEOR-M2',
|
||||
57166: 'METEOR-M2-3',
|
||||
59051: 'METEOR-M2-4',
|
||||
}
|
||||
|
||||
satellites = []
|
||||
for sat in sat_input:
|
||||
if isinstance(sat, int) and sat in norad_to_name:
|
||||
satellites.append(norad_to_name[sat])
|
||||
else:
|
||||
satellites.append(sat)
|
||||
|
||||
ts = _get_timescale()
|
||||
observer = wgs84.latlon(lat, lon)
|
||||
now = ts.now()
|
||||
now_dt = now.utc_datetime()
|
||||
|
||||
positions = []
|
||||
|
||||
for sat_name in satellites:
|
||||
# Special handling for ISS - use real-time API for accurate position
|
||||
if sat_name == 'ISS':
|
||||
iss_data = _fetch_iss_realtime(lat, lon)
|
||||
if iss_data:
|
||||
# Add orbit track if requested (using TLE for track prediction)
|
||||
if include_track and 'ISS' in _tle_cache:
|
||||
try:
|
||||
ts = _get_timescale()
|
||||
observer = wgs84.latlon(lat, lon)
|
||||
now = ts.now()
|
||||
now_dt = now.utc_datetime()
|
||||
tracked_by_norad, tracked_by_name = _get_tracked_satellite_maps()
|
||||
|
||||
positions = []
|
||||
|
||||
for sat in sat_input:
|
||||
sat_name, norad_id, tle_data = _resolve_satellite_request(sat, tracked_by_norad, tracked_by_name)
|
||||
# Special handling for ISS - use real-time API for accurate position
|
||||
if norad_id == 25544 or sat_name == 'ISS':
|
||||
iss_data = _fetch_iss_realtime(lat, lon)
|
||||
if iss_data:
|
||||
# Add orbit track if requested (using TLE for track prediction)
|
||||
if include_track and 'ISS' in _tle_cache:
|
||||
try:
|
||||
tle_data = _tle_cache['ISS']
|
||||
satellite = EarthSatellite(tle_data[1], tle_data[2], tle_data[0], ts)
|
||||
orbit_track = []
|
||||
@@ -460,16 +534,15 @@ def get_satellite_position():
|
||||
iss_data['track'] = orbit_track
|
||||
except Exception:
|
||||
pass
|
||||
positions.append(iss_data)
|
||||
continue
|
||||
|
||||
# Other satellites - use TLE data
|
||||
if sat_name not in _tle_cache:
|
||||
continue
|
||||
|
||||
tle_data = _tle_cache[sat_name]
|
||||
try:
|
||||
satellite = EarthSatellite(tle_data[1], tle_data[2], tle_data[0], ts)
|
||||
positions.append(iss_data)
|
||||
continue
|
||||
|
||||
# Other satellites - use TLE data
|
||||
if not tle_data:
|
||||
continue
|
||||
|
||||
try:
|
||||
satellite = EarthSatellite(tle_data[1], tle_data[2], tle_data[0], ts)
|
||||
|
||||
geocentric = satellite.at(now)
|
||||
subpoint = wgs84.subpoint(geocentric)
|
||||
@@ -480,7 +553,7 @@ def get_satellite_position():
|
||||
|
||||
pos_data = {
|
||||
'satellite': sat_name,
|
||||
'norad_id': next((nid for nid, name in norad_to_name.items() if name == sat_name), None),
|
||||
'norad_id': norad_id,
|
||||
'lat': float(subpoint.latitude.degrees),
|
||||
'lon': float(subpoint.longitude.degrees),
|
||||
'altitude': float(geocentric.distance().km - 6371),
|
||||
|
||||
@@ -11554,10 +11554,13 @@
|
||||
function fetchCelestrakCategory(category) {
|
||||
const status = document.getElementById('celestrakStatus');
|
||||
status.innerHTML = '<span style="color: var(--accent-cyan);">Fetching ' + category + '...</span>';
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 15000);
|
||||
|
||||
fetch('/satellite/celestrak/' + category)
|
||||
fetch('/satellite/celestrak/' + category, { signal: controller.signal })
|
||||
.then(r => r.json())
|
||||
.then(async data => {
|
||||
clearTimeout(timeout);
|
||||
if (data.status === 'success' && data.satellites) {
|
||||
const toAdd = data.satellites
|
||||
.filter(sat => !trackedSatellites.find(s => s.norad === String(sat.norad)))
|
||||
@@ -11602,8 +11605,10 @@
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
clearTimeout(timeout);
|
||||
const msg = err && err.message ? err.message : 'Network error';
|
||||
status.innerHTML = `<span style="color: var(--accent-red);">Import failed: ${msg}</span>`;
|
||||
const label = err && err.name === 'AbortError' ? 'Request timed out' : msg;
|
||||
status.innerHTML = `<span style="color: var(--accent-red);">Import failed: ${label}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11613,7 +11618,7 @@
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.satellites) {
|
||||
trackedSatellites = data.satellites.map(sat => ({
|
||||
id: sat.name.replace(/[^a-zA-Z0-9]/g, '-').toUpperCase(),
|
||||
id: String(sat.norad_id),
|
||||
name: sat.name,
|
||||
norad: sat.norad_id,
|
||||
builtin: sat.builtin,
|
||||
@@ -11627,9 +11632,9 @@
|
||||
// Fallback to hardcoded defaults if API fails
|
||||
if (trackedSatellites.length === 0) {
|
||||
trackedSatellites = [
|
||||
{ id: 'ISS', name: 'ISS (ZARYA)', norad: '25544', builtin: true, checked: true },
|
||||
{ id: 'METEOR-M2-3', name: 'Meteor-M2-3', norad: '57166', builtin: true, checked: true },
|
||||
{ id: 'METEOR-M2-4', name: 'Meteor-M2-4', norad: '59051', builtin: true, checked: true }
|
||||
{ id: '25544', name: 'ISS (ZARYA)', norad: '25544', builtin: true, checked: true },
|
||||
{ id: '57166', name: 'Meteor-M2-3', norad: '57166', builtin: true, checked: true },
|
||||
{ id: '59051', name: 'Meteor-M2-4', norad: '59051', builtin: true, checked: true }
|
||||
];
|
||||
renderSatelliteList();
|
||||
}
|
||||
|
||||
@@ -200,19 +200,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Decoded Packets -->
|
||||
<div class="panel packets-panel">
|
||||
<div class="panel-header">
|
||||
<span>DECODED PACKETS <span id="packetCount" style="color:var(--accent-cyan);"></span></span>
|
||||
<div class="panel-indicator"></div>
|
||||
</div>
|
||||
<div class="panel-content" id="packetList">
|
||||
<div style="text-align:center;color:var(--text-secondary);padding:15px;font-size:11px;">
|
||||
No packets received yet.<br>Run a ground-station observation with telemetry tasks enabled to populate this panel.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ground Station -->
|
||||
<div class="panel gs-panel" id="gsPanel">
|
||||
<div class="panel-header">
|
||||
@@ -352,6 +339,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Decoded Packets -->
|
||||
<div class="panel packets-panel">
|
||||
<div class="panel-header">
|
||||
<span>DECODED PACKETS <span id="packetCount" style="color:var(--accent-cyan);"></span></span>
|
||||
<div class="panel-indicator"></div>
|
||||
</div>
|
||||
<div class="panel-content" id="packetList">
|
||||
<div style="text-align:center;color:var(--text-secondary);padding:15px;font-size:11px;">
|
||||
No packets received yet.<br>Run a ground-station observation with telemetry tasks enabled to populate this panel.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls Bar -->
|
||||
@@ -601,6 +601,7 @@
|
||||
let agents = [];
|
||||
let _txRequestId = 0;
|
||||
let _telemetryPollTimer = null;
|
||||
let _passRequestId = 0;
|
||||
|
||||
let satellites = {
|
||||
25544: { name: 'ISS (ZARYA)', color: '#00ffff' },
|
||||
@@ -620,33 +621,49 @@
|
||||
fetch('/satellite/tracked?enabled=true')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success' && data.satellites && data.satellites.length > 0) {
|
||||
const prevSelected = selectedSatellite;
|
||||
const newSats = {};
|
||||
const select = document.getElementById('satSelect');
|
||||
select.innerHTML = '';
|
||||
const prevSelected = selectedSatellite;
|
||||
const newSats = {
|
||||
25544: { name: 'ISS (ZARYA)', color: satellites[25544]?.color || satColors[0] },
|
||||
57166: { name: 'METEOR-M2-3', color: satellites[57166]?.color || satColors[2] },
|
||||
59051: { name: 'METEOR-M2-4', color: satellites[59051]?.color || satColors[4] },
|
||||
};
|
||||
const select = document.getElementById('satSelect');
|
||||
if (!select) return;
|
||||
if (data.status === 'success' && Array.isArray(data.satellites)) {
|
||||
data.satellites.forEach((sat, i) => {
|
||||
const norad = parseInt(sat.norad_id);
|
||||
if (!Number.isFinite(norad)) return;
|
||||
newSats[norad] = {
|
||||
name: sat.name,
|
||||
color: satellites[norad]?.color || satColors[i % satColors.length]
|
||||
};
|
||||
const opt = document.createElement('option');
|
||||
opt.value = norad;
|
||||
opt.textContent = sat.name;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
satellites = newSats;
|
||||
// Restore previous selection if still available, else default to ISS
|
||||
if (newSats[prevSelected]) {
|
||||
select.value = prevSelected;
|
||||
} else if (newSats[25544]) {
|
||||
select.value = '25544';
|
||||
}
|
||||
selectedSatellite = parseInt(select.value);
|
||||
}
|
||||
satellites = newSats;
|
||||
select.innerHTML = '';
|
||||
Object.entries(newSats).forEach(([norad, sat]) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = norad;
|
||||
opt.textContent = sat.name;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
if (newSats[prevSelected]) {
|
||||
select.value = String(prevSelected);
|
||||
} else if (newSats[25544]) {
|
||||
select.value = '25544';
|
||||
}
|
||||
selectedSatellite = parseInt(select.value);
|
||||
clearTelemetry();
|
||||
loadTransmitters(selectedSatellite);
|
||||
calculatePasses();
|
||||
fetchCurrentTelemetry();
|
||||
if (window.gsLoadOutputs) window.gsLoadOutputs();
|
||||
if (window.gsOnSatelliteChange) window.gsOnSatelliteChange();
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
if (btn) btn.classList.remove('spinning');
|
||||
});
|
||||
}
|
||||
|
||||
function onSatelliteChange() {
|
||||
@@ -670,7 +687,7 @@
|
||||
loadTransmitters(selectedSatellite);
|
||||
calculatePasses();
|
||||
fetchCurrentTelemetry();
|
||||
gsLoadOutputs();
|
||||
if (window.gsLoadOutputs) window.gsLoadOutputs();
|
||||
if (window.gsOnSatelliteChange) gsOnSatelliteChange();
|
||||
}
|
||||
|
||||
@@ -1073,9 +1090,18 @@
|
||||
}
|
||||
|
||||
async function calculatePasses() {
|
||||
const requestId = ++_passRequestId;
|
||||
const lat = parseFloat(document.getElementById('obsLat').value);
|
||||
const lon = parseFloat(document.getElementById('obsLon').value);
|
||||
const satName = satellites[selectedSatellite]?.name || 'Unknown';
|
||||
const container = document.getElementById('passList');
|
||||
const button = document.querySelector('.controls-bar .btn.primary');
|
||||
if (container) {
|
||||
container.innerHTML = '<div style="text-align:center;color:var(--text-secondary);padding:20px;">Calculating passes...</div>';
|
||||
}
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.textContent = 'WORKING...';
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/satellite/predict', {
|
||||
@@ -1091,25 +1117,38 @@
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (requestId !== _passRequestId) return;
|
||||
if (data.status === 'success') {
|
||||
passes = data.passes;
|
||||
renderPassList();
|
||||
updateStats();
|
||||
if (passes.length > 0) {
|
||||
selectPass(0);
|
||||
} else {
|
||||
clearTelemetry();
|
||||
}
|
||||
updateObserverMarker(lat, lon);
|
||||
|
||||
document.getElementById('trackingStatus').textContent = 'TRACKING';
|
||||
document.getElementById('trackingDot').style.background = 'var(--accent-green)';
|
||||
} else {
|
||||
passes = [];
|
||||
renderPassList();
|
||||
document.getElementById('trackingStatus').textContent = 'ERROR';
|
||||
document.getElementById('trackingDot').style.background = 'var(--accent-red)';
|
||||
}
|
||||
} catch (err) {
|
||||
if (requestId !== _passRequestId) return;
|
||||
console.error('Pass calculation error:', err);
|
||||
passes = [];
|
||||
renderPassList();
|
||||
document.getElementById('trackingStatus').textContent = 'OFFLINE';
|
||||
document.getElementById('trackingDot').style.background = 'var(--accent-red)';
|
||||
} finally {
|
||||
if (requestId === _passRequestId && button) {
|
||||
button.disabled = false;
|
||||
button.textContent = 'CALCULATE';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2046,6 +2085,7 @@
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
window.gsLoadOutputs = gsLoadOutputs;
|
||||
|
||||
function gsLoadDecodeJobs(norad) {
|
||||
const panel = document.getElementById('gsOutputsPanel');
|
||||
|
||||
@@ -179,15 +179,27 @@ def get_transmitters(norad_id: int) -> list[dict]:
|
||||
"""
|
||||
global _transmitters, _fetched_at # noqa: PLW0603
|
||||
|
||||
with _fetch_lock:
|
||||
sat_id = int(norad_id)
|
||||
age = time.time() - _fetched_at
|
||||
|
||||
# Fast path: serve warm cache immediately.
|
||||
if _transmitters and age <= _CACHE_TTL:
|
||||
return _transmitters.get(sat_id, _BUILTIN_TRANSMITTERS.get(sat_id, []))
|
||||
|
||||
# Avoid blocking the UI behind a long-running background refresh.
|
||||
if not _fetch_lock.acquire(blocking=False):
|
||||
return _transmitters.get(sat_id, _BUILTIN_TRANSMITTERS.get(sat_id, []))
|
||||
|
||||
try:
|
||||
age = time.time() - _fetched_at
|
||||
if not _transmitters or age > _CACHE_TTL:
|
||||
fetched = fetch_transmitters()
|
||||
if fetched:
|
||||
_transmitters = fetched
|
||||
_fetched_at = time.time()
|
||||
|
||||
return _transmitters.get(int(norad_id), _BUILTIN_TRANSMITTERS.get(int(norad_id), []))
|
||||
return _transmitters.get(sat_id, _BUILTIN_TRANSMITTERS.get(sat_id, []))
|
||||
finally:
|
||||
_fetch_lock.release()
|
||||
|
||||
|
||||
def refresh_transmitters() -> int:
|
||||
|
||||
Reference in New Issue
Block a user