CI / lint (pull_request) Successful in 2m16s
CI / frontend (pull_request) Successful in 4m12s
CI / reuse (pull_request) Successful in 2s
CI / lint (push) Successful in 2m15s
CI / test (pull_request) Successful in 9m37s
CI / test (push) Successful in 7m36s
CI / frontend (push) Failing after 31s
CI / reuse (push) Successful in 3s
Wires the SSTV decoder into the stack, from the audio the server already has to a panel in the browser that shows the picture arriving. Server: a decoder task alongside the WEFAX one, running whenever the decoder is enabled and the rig is in a mode SSTV is sent in. A finished picture is written to the cache as a PNG and sent on as a message; the rows are sent as they decode, so a client can watch two minutes of Martin M1 fill in rather than waiting for it. Pictures join the decode history, are replayed to a client that connects later, and survive a restart. Protocol: SetSstvDecodeEnabled and ResetSstvDecoder, a sstv_decode _enabled flag in the rig state, two audio message types, and Sstv and SstvProgress on DecodedMessage. The history stores the message without its base64 payload -- the picture is already on disk, and a megabyte per entry is not what a history is for. Client: pictures land in their own history, and the PNG the server sent is written to the local cache so /sstv-images/ can serve it back. That endpoint and the WEFAX one now share their filename checks rather than each carrying a copy: no separators, no parent references, .png only. Web UI: an SSTV sub-tab beside WEFAX, with a live canvas the rows paint into at the line number they carry, a card for the last picture, and a filterable history with links to the files. Rows below the one arriving are grey rather than black -- not yet received is a different thing from received as black. A picture is not a spot, so neither pictures nor their progress updates reach the decode statistics; that exclusion list had grown by hand for LRPT and WEFAX and is now one named set. The decoder crate gains what the server needed to hand a picture on: to_png, to_png_base64 and save_png, with file names stamped in UTC so they sort. Panel behaviour is tested with the plugin runtime: rows painting at their own line numbers rather than in arrival order, a completed picture linked by file name alone with no server path in the page, a cut-off picture reported as partial, clearing, and the toggle following the rig state. Signed-off-by: Stan Grams <sjg@haxx.space>
637 lines
21 KiB
Rust
637 lines
21 KiB
Rust
// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
//! Decoder toggle/clear endpoints and decode history.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use actix_web::http::header;
|
|
use actix_web::Error;
|
|
use actix_web::{get, post, web, HttpResponse, Responder};
|
|
use bytes::Bytes;
|
|
use futures_util::stream::{select, StreamExt};
|
|
use tokio::sync::{broadcast, mpsc, watch};
|
|
use tokio::time::{self, Duration};
|
|
use tokio_stream::wrappers::IntervalStream;
|
|
|
|
use trx_core::{RigCommand, RigRequest, RigState};
|
|
use trx_frontend::FrontendRuntimeContext;
|
|
|
|
use super::{gzip_bytes, send_command, RemoteQuery};
|
|
|
|
/// Resolve the rig state for a specific remote, falling back to the global
|
|
/// default when no `remote` is given or the rig is unknown.
|
|
fn resolve_rig_state(
|
|
remote: Option<&str>,
|
|
context: &FrontendRuntimeContext,
|
|
fallback: &watch::Receiver<RigState>,
|
|
) -> RigState {
|
|
remote
|
|
.filter(|s| !s.is_empty())
|
|
.and_then(|rid| context.rig_state_rx(rid))
|
|
.unwrap_or_else(|| fallback.clone())
|
|
.borrow()
|
|
.clone()
|
|
}
|
|
|
|
// ============================================================================
|
|
// Decoder registry
|
|
// ============================================================================
|
|
|
|
#[get("/decoders")]
|
|
pub async fn decoder_registry() -> impl Responder {
|
|
HttpResponse::Ok().json(trx_protocol::DECODER_REGISTRY)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Decode history types and helpers
|
|
// ============================================================================
|
|
|
|
#[derive(serde::Serialize)]
|
|
struct DecodeHistoryPayload {
|
|
ais: Vec<trx_core::decode::AisMessage>,
|
|
vdes: Vec<trx_core::decode::VdesMessage>,
|
|
aprs: Vec<trx_core::decode::AprsPacket>,
|
|
hf_aprs: Vec<trx_core::decode::AprsPacket>,
|
|
cw: Vec<trx_core::decode::CwEvent>,
|
|
ft8: Vec<trx_core::decode::Ft8Message>,
|
|
ft4: Vec<trx_core::decode::Ft8Message>,
|
|
ft2: Vec<trx_core::decode::Ft8Message>,
|
|
wspr: Vec<trx_core::decode::WsprMessage>,
|
|
wefax: Vec<trx_core::decode::WefaxMessage>,
|
|
sstv: Vec<trx_core::decode::SstvMessage>,
|
|
}
|
|
|
|
impl DecodeHistoryPayload {
|
|
fn total_messages(&self) -> usize {
|
|
self.ais.len()
|
|
+ self.vdes.len()
|
|
+ self.aprs.len()
|
|
+ self.hf_aprs.len()
|
|
+ self.cw.len()
|
|
+ self.ft8.len()
|
|
+ self.ft4.len()
|
|
+ self.ft2.len()
|
|
+ self.wspr.len()
|
|
+ self.wefax.len()
|
|
+ self.sstv.len()
|
|
}
|
|
}
|
|
|
|
/// Build the grouped decode history payload from all per-decoder ring-buffers.
|
|
fn collect_decode_history(
|
|
context: &FrontendRuntimeContext,
|
|
rig_filter: Option<&str>,
|
|
) -> DecodeHistoryPayload {
|
|
DecodeHistoryPayload {
|
|
ais: crate::server::audio::snapshot_ais_history(context, rig_filter),
|
|
vdes: crate::server::audio::snapshot_vdes_history(context, rig_filter),
|
|
aprs: crate::server::audio::snapshot_aprs_history(context, rig_filter),
|
|
hf_aprs: crate::server::audio::snapshot_hf_aprs_history(context, rig_filter),
|
|
cw: crate::server::audio::snapshot_cw_history(context, rig_filter),
|
|
ft8: crate::server::audio::snapshot_ft8_history(context, rig_filter),
|
|
ft4: crate::server::audio::snapshot_ft4_history(context, rig_filter),
|
|
ft2: crate::server::audio::snapshot_ft2_history(context, rig_filter),
|
|
wspr: crate::server::audio::snapshot_wspr_history(context, rig_filter),
|
|
wefax: crate::server::audio::snapshot_wefax_history(context, rig_filter),
|
|
sstv: crate::server::audio::snapshot_sstv_history(context, rig_filter),
|
|
}
|
|
}
|
|
|
|
fn encode_cbor_length(out: &mut Vec<u8>, major: u8, value: u64) {
|
|
debug_assert!(major <= 7);
|
|
match value {
|
|
0..=23 => out.push((major << 5) | (value as u8)),
|
|
24..=0xff => {
|
|
out.push((major << 5) | 24);
|
|
out.push(value as u8);
|
|
}
|
|
0x100..=0xffff => {
|
|
out.push((major << 5) | 25);
|
|
out.extend_from_slice(&(value as u16).to_be_bytes());
|
|
}
|
|
0x1_0000..=0xffff_ffff => {
|
|
out.push((major << 5) | 26);
|
|
out.extend_from_slice(&(value as u32).to_be_bytes());
|
|
}
|
|
_ => {
|
|
out.push((major << 5) | 27);
|
|
out.extend_from_slice(&value.to_be_bytes());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn encode_cbor_json_value(out: &mut Vec<u8>, value: &serde_json::Value) {
|
|
match value {
|
|
serde_json::Value::Null => out.push(0xf6),
|
|
serde_json::Value::Bool(false) => out.push(0xf4),
|
|
serde_json::Value::Bool(true) => out.push(0xf5),
|
|
serde_json::Value::Number(number) => {
|
|
if let Some(value) = number.as_u64() {
|
|
encode_cbor_length(out, 0, value);
|
|
} else if let Some(value) = number.as_i64() {
|
|
if value >= 0 {
|
|
encode_cbor_length(out, 0, value as u64);
|
|
} else {
|
|
encode_cbor_length(out, 1, value.unsigned_abs() - 1);
|
|
}
|
|
} else if let Some(value) = number.as_f64() {
|
|
out.push(0xfb);
|
|
out.extend_from_slice(&value.to_be_bytes());
|
|
} else {
|
|
out.push(0xf6);
|
|
}
|
|
}
|
|
serde_json::Value::String(text) => {
|
|
encode_cbor_length(out, 3, text.len() as u64);
|
|
out.extend_from_slice(text.as_bytes());
|
|
}
|
|
serde_json::Value::Array(items) => {
|
|
encode_cbor_length(out, 4, items.len() as u64);
|
|
for item in items {
|
|
encode_cbor_json_value(out, item);
|
|
}
|
|
}
|
|
serde_json::Value::Object(map) => {
|
|
encode_cbor_length(out, 5, map.len() as u64);
|
|
for (key, item) in map {
|
|
encode_cbor_length(out, 3, key.len() as u64);
|
|
out.extend_from_slice(key.as_bytes());
|
|
encode_cbor_json_value(out, item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn encode_decode_history_cbor(
|
|
history: &DecodeHistoryPayload,
|
|
) -> Result<Vec<u8>, serde_json::Error> {
|
|
let value = serde_json::to_value(history)?;
|
|
let mut out = Vec::with_capacity(history.total_messages().saturating_mul(96));
|
|
encode_cbor_json_value(&mut out, &value);
|
|
Ok(out)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Decode history endpoint
|
|
// ============================================================================
|
|
|
|
/// `GET /decode/history` — returns the full decode history as gzipped CBOR.
|
|
#[get("/decode/history")]
|
|
pub async fn decode_history(
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
query: web::Query<RemoteQuery>,
|
|
) -> impl Responder {
|
|
if context.audio.decode_rx.is_none() {
|
|
return HttpResponse::NotFound().body("decode not enabled");
|
|
}
|
|
let rig_filter = query.remote.as_deref().filter(|s| !s.is_empty());
|
|
let history = collect_decode_history(context.get_ref(), rig_filter);
|
|
let cbor = match encode_decode_history_cbor(&history) {
|
|
Ok(cbor) => cbor,
|
|
Err(err) => {
|
|
tracing::error!("failed to encode decode history as CBOR: {err}");
|
|
return HttpResponse::InternalServerError().finish();
|
|
}
|
|
};
|
|
let payload = match gzip_bytes(&cbor) {
|
|
Ok(payload) => payload,
|
|
Err(err) => {
|
|
tracing::error!("failed to gzip decode history payload: {err}");
|
|
return HttpResponse::InternalServerError().finish();
|
|
}
|
|
};
|
|
HttpResponse::Ok()
|
|
.insert_header((header::CONTENT_TYPE, "application/cbor"))
|
|
.insert_header((header::CONTENT_ENCODING, "gzip"))
|
|
.body(payload)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Decode SSE stream
|
|
// ============================================================================
|
|
|
|
#[get("/decode")]
|
|
pub async fn decode_events(
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let Some(decode_rx) = crate::server::audio::subscribe_decode(context.get_ref()) else {
|
|
tracing::warn!("/decode requested but decode channel not set (audio disabled?)");
|
|
return Ok(HttpResponse::NotFound().body("decode not enabled"));
|
|
};
|
|
tracing::info!("/decode SSE client connected");
|
|
|
|
let decode_stream = futures_util::stream::unfold(decode_rx, |mut rx| async move {
|
|
loop {
|
|
match rx.recv().await {
|
|
Ok(msg) => {
|
|
if let Ok(json) = serde_json::to_string(&msg) {
|
|
return Some((
|
|
Ok::<Bytes, Error>(Bytes::from(format!("data: {json}\n\n"))),
|
|
rx,
|
|
));
|
|
}
|
|
}
|
|
Err(broadcast::error::RecvError::Lagged(_)) => continue,
|
|
Err(broadcast::error::RecvError::Closed) => return None,
|
|
}
|
|
}
|
|
});
|
|
|
|
let pings = IntervalStream::new(time::interval(Duration::from_secs(15)))
|
|
.map(|_| Ok::<Bytes, Error>(Bytes::from(": ping\n\n")));
|
|
|
|
let stream = select(pings, decode_stream);
|
|
|
|
Ok(HttpResponse::Ok()
|
|
.insert_header((header::CONTENT_TYPE, "text/event-stream"))
|
|
.insert_header((header::CONTENT_ENCODING, "identity"))
|
|
.insert_header((header::CACHE_CONTROL, "no-cache"))
|
|
.insert_header((header::CONNECTION, "keep-alive"))
|
|
.streaming(stream))
|
|
}
|
|
|
|
// ============================================================================
|
|
// Decoder toggle endpoints
|
|
// ============================================================================
|
|
|
|
#[post("/toggle_aprs_decode")]
|
|
pub async fn toggle_aprs_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetAprsDecodeEnabled(!rig_state.decoders.aprs_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_hf_aprs_decode")]
|
|
pub async fn toggle_hf_aprs_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetHfAprsDecodeEnabled(!rig_state.decoders.hf_aprs_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_cw_decode")]
|
|
pub async fn toggle_cw_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetCwDecodeEnabled(!rig_state.decoders.cw_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct CwAutoQuery {
|
|
pub enabled: bool,
|
|
pub remote: Option<String>,
|
|
}
|
|
|
|
#[post("/set_cw_auto")]
|
|
pub async fn set_cw_auto(
|
|
query: web::Query<CwAutoQuery>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
send_command(&rig_tx, RigCommand::SetCwAuto(q.enabled), q.remote).await
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct CwWpmQuery {
|
|
pub wpm: u32,
|
|
pub remote: Option<String>,
|
|
}
|
|
|
|
#[post("/set_cw_wpm")]
|
|
pub async fn set_cw_wpm(
|
|
query: web::Query<CwWpmQuery>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
send_command(&rig_tx, RigCommand::SetCwWpm(q.wpm), q.remote).await
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct CwToneQuery {
|
|
pub tone_hz: u32,
|
|
pub remote: Option<String>,
|
|
}
|
|
|
|
#[post("/set_cw_tone")]
|
|
pub async fn set_cw_tone(
|
|
query: web::Query<CwToneQuery>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
send_command(&rig_tx, RigCommand::SetCwToneHz(q.tone_hz), q.remote).await
|
|
}
|
|
|
|
#[post("/toggle_ft8_decode")]
|
|
pub async fn toggle_ft8_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetFt8DecodeEnabled(!rig_state.decoders.ft8_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_ft4_decode")]
|
|
pub async fn toggle_ft4_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetFt4DecodeEnabled(!rig_state.decoders.ft4_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_ft2_decode")]
|
|
pub async fn toggle_ft2_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetFt2DecodeEnabled(!rig_state.decoders.ft2_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_wspr_decode")]
|
|
pub async fn toggle_wspr_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetWsprDecodeEnabled(!rig_state.decoders.wspr_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_lrpt_decode")]
|
|
pub async fn toggle_lrpt_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetLrptDecodeEnabled(!rig_state.decoders.lrpt_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_wefax_decode")]
|
|
pub async fn toggle_wefax_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetWefaxDecodeEnabled(!rig_state.decoders.wefax_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/toggle_sstv_decode")]
|
|
pub async fn toggle_sstv_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
state: web::Data<watch::Receiver<RigState>>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
let q = query.into_inner();
|
|
let rig_state = resolve_rig_state(q.remote.as_deref(), &context, state.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::SetSstvDecodeEnabled(!rig_state.decoders.sstv_decode_enabled),
|
|
q.remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
// ============================================================================
|
|
// Decoder clear endpoints
|
|
// ============================================================================
|
|
|
|
#[post("/clear_sstv_decode")]
|
|
pub async fn clear_sstv_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetSstvDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_wefax_decode")]
|
|
pub async fn clear_wefax_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetWefaxDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_lrpt_decode")]
|
|
pub async fn clear_lrpt_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetLrptDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_ft8_decode")]
|
|
pub async fn clear_ft8_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_ft8_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetFt8Decoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_ft4_decode")]
|
|
pub async fn clear_ft4_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_ft4_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetFt4Decoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_ft2_decode")]
|
|
pub async fn clear_ft2_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_ft2_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetFt2Decoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_wspr_decode")]
|
|
pub async fn clear_wspr_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_wspr_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetWsprDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_aprs_decode")]
|
|
pub async fn clear_aprs_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_aprs_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetAprsDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_hf_aprs_decode")]
|
|
pub async fn clear_hf_aprs_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_hf_aprs_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetHfAprsDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|
|
|
|
#[post("/clear_ais_decode")]
|
|
pub async fn clear_ais_decode(
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_ais_history(context.get_ref());
|
|
Ok(HttpResponse::Ok().finish())
|
|
}
|
|
|
|
#[post("/clear_vdes_decode")]
|
|
pub async fn clear_vdes_decode(
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_vdes_history(context.get_ref());
|
|
Ok(HttpResponse::Ok().finish())
|
|
}
|
|
|
|
#[post("/clear_cw_decode")]
|
|
pub async fn clear_cw_decode(
|
|
query: web::Query<RemoteQuery>,
|
|
context: web::Data<Arc<FrontendRuntimeContext>>,
|
|
rig_tx: web::Data<mpsc::Sender<RigRequest>>,
|
|
) -> Result<HttpResponse, Error> {
|
|
crate::server::audio::clear_cw_history(context.get_ref());
|
|
send_command(
|
|
&rig_tx,
|
|
RigCommand::ResetCwDecoder,
|
|
query.into_inner().remote,
|
|
)
|
|
.await
|
|
}
|