[feat](trx-rs): add GetSatPasses protocol command for server-side TLE management

TLE refresh now happens only on trx-server (once at startup, then every
24h). Client fetches satellite predictions from server via new
GetSatPasses fast-path command and caches them locally, refreshing
every 5 minutes. Removes spawn_tle_refresh_task from trx-client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-03-28 15:38:39 +01:00
parent e831dff85d
commit 2f7adf05c8
10 changed files with 167 additions and 37 deletions
@@ -1384,43 +1384,34 @@ struct SatPassesResponse {
/// Return predicted passes for all known satellites over the next 24 h.
///
/// Requires the server station location to be configured. Returns an empty
/// `passes` array with an `error` field if the location is missing or TLE
/// data has not been fetched yet.
/// Reads cached predictions from the server (fetched via GetSatPasses).
/// Returns an empty `passes` array with an `error` field if predictions
/// are not yet available.
#[get("/sat_passes")]
pub async fn sat_passes(state: web::Data<watch::Receiver<RigState>>) -> impl Responder {
let rig_state = state.get_ref().borrow().clone();
let lat = rig_state.server_latitude;
let lon = rig_state.server_longitude;
let (Some(lat), Some(lon)) = (lat, lon) else {
return web::Json(SatPassesResponse {
pub async fn sat_passes(context: web::Data<Arc<FrontendRuntimeContext>>) -> impl Responder {
let cached = context.sat_passes.read().ok().and_then(|g| g.clone());
match cached {
Some(result) => {
let error = match result.tle_source {
trx_core::geo::TleSource::Unavailable => {
Some("TLE data not yet available — waiting for CelesTrak fetch".to_string())
}
trx_core::geo::TleSource::Celestrak => None,
};
web::Json(SatPassesResponse {
passes: result.passes,
error,
satellite_count: result.satellite_count,
tle_source: result.tle_source,
})
}
None => web::Json(SatPassesResponse {
passes: vec![],
error: Some("No station location configured".to_string()),
error: Some("Satellite predictions not yet available from server".to_string()),
satellite_count: 0,
tle_source: trx_core::geo::TleSource::Unavailable,
});
};
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64;
let window_ms = 24 * 60 * 60 * 1000_i64;
let result = trx_core::geo::compute_upcoming_passes(lat, lon, now_ms, window_ms);
let error = match result.tle_source {
trx_core::geo::TleSource::Unavailable => {
Some("TLE data not yet available — waiting for CelesTrak fetch".to_string())
}
trx_core::geo::TleSource::Celestrak => None,
};
web::Json(SatPassesResponse {
passes: result.passes,
error,
satellite_count: result.satellite_count,
tle_source: result.tle_source,
})
}),
}
}
#[post("/clear_ft8_decode")]
@@ -2400,6 +2391,7 @@ async fn send_command(
rig_id: None,
state: Some(snapshot),
rigs: None,
sat_passes: None,
error: None,
})),
Ok(Err(err)) => Ok(HttpResponse::BadRequest().json(ClientResponse {
@@ -2407,6 +2399,7 @@ async fn send_command(
rig_id: None,
state: None,
rigs: None,
sat_passes: None,
error: Some(err.message),
})),
Err(e) => Err(actix_web::error::ErrorInternalServerError(format!(