Compare commits
2 Commits
da799a1d1f
...
a823e66816
| Author | SHA1 | Date | |
|---|---|---|---|
| a823e66816 | |||
| 7044747ade |
@@ -141,8 +141,8 @@ fn unpack_message(bits: &[u8; NBITS]) -> Option<String> {
|
|||||||
power_code = (power_code << 1) | b as u32;
|
power_code = (power_code << 1) | b as u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// power_code = dBm + 64; valid WSPR levels are 0–60 dBm.
|
// power_code is the raw dBm value; valid WSPR levels are 0–60 dBm.
|
||||||
let power_dbm = power_code as i32 - 64;
|
let power_dbm = power_code as i32;
|
||||||
if !(0..=60).contains(&power_dbm) {
|
if !(0..=60).contains(&power_dbm) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ fn unpack_message(bits: &[u8; NBITS]) -> Option<String> {
|
|||||||
CS27[i4] as char,
|
CS27[i4] as char,
|
||||||
CS27[i5] as char,
|
CS27[i5] as char,
|
||||||
)
|
)
|
||||||
.trim_end()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
if callsign.len() < 3 || !callsign.chars().any(|c| c.is_alphabetic()) {
|
if callsign.len() < 3 || !callsign.chars().any(|c| c.is_alphabetic()) {
|
||||||
@@ -243,10 +243,7 @@ mod tests {
|
|||||||
let c4 = idx27(b'T');
|
let c4 = idx27(b'T');
|
||||||
let c5 = idx27(b' ');
|
let c5 = idx27(b' ');
|
||||||
|
|
||||||
let n1 = ((c0 * 36 + c1) * 10 + c2) * 27u32.pow(3)
|
let n1 = ((c0 * 36 + c1) * 10 + c2) * 27u32.pow(3) + c3 * 27u32.pow(2) + c4 * 27 + c5;
|
||||||
+ c3 * 27u32.pow(2)
|
|
||||||
+ c4 * 27
|
|
||||||
+ c5;
|
|
||||||
|
|
||||||
// Grid "FN20": loc1='F'=5 (lon), loc2='N'=13 (lat), loc3='2', loc4='0'
|
// Grid "FN20": loc1='F'=5 (lon), loc2='N'=13 (lat), loc3='2', loc4='0'
|
||||||
let loc1 = (b'F' - b'A') as u32; // 5
|
let loc1 = (b'F' - b'A') as u32; // 5
|
||||||
@@ -255,8 +252,8 @@ mod tests {
|
|||||||
let loc4 = 0u32;
|
let loc4 = 0u32;
|
||||||
let m1 = (179 - 10 * loc1 - loc3) * 180 + 10 * loc2 + loc4;
|
let m1 = (179 - 10 * loc1 - loc3) * 180 + 10 * loc2 + loc4;
|
||||||
|
|
||||||
// Power 37 dBm → power_code = 37 + 64 = 101
|
// Power 37 dBm → power_code = 37 (raw dBm value)
|
||||||
let power_code = 37u32 + 64;
|
let power_code = 37u32;
|
||||||
|
|
||||||
// Pack into 50-bit array
|
// Pack into 50-bit array
|
||||||
let mut bits = [0u8; NBITS];
|
let mut bits = [0u8; NBITS];
|
||||||
|
|||||||
+183
-74
@@ -23,25 +23,23 @@ use trx_ais::AisDecoder;
|
|||||||
use trx_aprs::AprsDecoder;
|
use trx_aprs::AprsDecoder;
|
||||||
use trx_core::audio::{
|
use trx_core::audio::{
|
||||||
parse_vchan_uuid_msg, read_audio_msg, write_audio_msg, write_vchan_audio_frame,
|
parse_vchan_uuid_msg, read_audio_msg, write_audio_msg, write_vchan_audio_frame,
|
||||||
write_vchan_uuid_msg, AudioStreamInfo,
|
write_vchan_uuid_msg, AudioStreamInfo, AUDIO_MSG_AIS_DECODE, AUDIO_MSG_APRS_DECODE,
|
||||||
AUDIO_MSG_AIS_DECODE, AUDIO_MSG_APRS_DECODE, AUDIO_MSG_CW_DECODE, AUDIO_MSG_FT2_DECODE,
|
AUDIO_MSG_CW_DECODE, AUDIO_MSG_FT2_DECODE, AUDIO_MSG_FT4_DECODE, AUDIO_MSG_FT8_DECODE,
|
||||||
AUDIO_MSG_FT4_DECODE,
|
AUDIO_MSG_HF_APRS_DECODE, AUDIO_MSG_HISTORY_COMPRESSED, AUDIO_MSG_RX_FRAME,
|
||||||
AUDIO_MSG_FT8_DECODE, AUDIO_MSG_HF_APRS_DECODE, AUDIO_MSG_HISTORY_COMPRESSED,
|
AUDIO_MSG_STREAM_INFO, AUDIO_MSG_TX_FRAME, AUDIO_MSG_VCHAN_ALLOCATED, AUDIO_MSG_VCHAN_BW,
|
||||||
AUDIO_MSG_RX_FRAME, AUDIO_MSG_STREAM_INFO, AUDIO_MSG_TX_FRAME, AUDIO_MSG_VCHAN_ALLOCATED,
|
AUDIO_MSG_VCHAN_DESTROYED, AUDIO_MSG_VCHAN_FREQ, AUDIO_MSG_VCHAN_MODE, AUDIO_MSG_VCHAN_REMOVE,
|
||||||
AUDIO_MSG_VCHAN_BW, AUDIO_MSG_VCHAN_DESTROYED, AUDIO_MSG_VCHAN_FREQ, AUDIO_MSG_VCHAN_MODE,
|
|
||||||
AUDIO_MSG_VCHAN_REMOVE,
|
|
||||||
AUDIO_MSG_VCHAN_SUB, AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE, AUDIO_MSG_WSPR_DECODE,
|
AUDIO_MSG_VCHAN_SUB, AUDIO_MSG_VCHAN_UNSUB, AUDIO_MSG_VDES_DECODE, AUDIO_MSG_WSPR_DECODE,
|
||||||
};
|
};
|
||||||
use trx_core::vchan::SharedVChanManager;
|
|
||||||
use uuid::Uuid;
|
|
||||||
use trx_core::decode::{
|
use trx_core::decode::{
|
||||||
AisMessage, AprsPacket, CwEvent, DecodedMessage, Ft8Message, VdesMessage, WsprMessage,
|
AisMessage, AprsPacket, CwEvent, DecodedMessage, Ft8Message, VdesMessage, WsprMessage,
|
||||||
};
|
};
|
||||||
use trx_core::rig::state::{RigMode, RigState};
|
use trx_core::rig::state::{RigMode, RigState};
|
||||||
|
use trx_core::vchan::SharedVChanManager;
|
||||||
use trx_cw::CwDecoder;
|
use trx_cw::CwDecoder;
|
||||||
use trx_ft8::Ft8Decoder;
|
use trx_ft8::Ft8Decoder;
|
||||||
use trx_vdes::VdesDecoder;
|
use trx_vdes::VdesDecoder;
|
||||||
use trx_wspr::WsprDecoder;
|
use trx_wspr::WsprDecoder;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::AudioConfig;
|
use crate::config::AudioConfig;
|
||||||
use trx_decode_log::DecoderLoggers;
|
use trx_decode_log::DecoderLoggers;
|
||||||
@@ -1053,6 +1051,8 @@ pub async fn run_aprs_decoder(
|
|||||||
last_reset_seq = reset_seq;
|
last_reset_seq = reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
info!("APRS decoder reset (seq={})", last_reset_seq);
|
info!("APRS decoder reset (seq={})", last_reset_seq);
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Downmix to mono if stereo
|
// Downmix to mono if stereo
|
||||||
@@ -1070,6 +1070,14 @@ pub async fn run_aprs_decoder(
|
|||||||
|
|
||||||
was_active = true;
|
was_active = true;
|
||||||
let packets = tokio::task::block_in_place(|| decoder.process_samples(&mono));
|
let packets = tokio::task::block_in_place(|| decoder.process_samples(&mono));
|
||||||
|
let latest_reset_seq = state_rx.borrow().aprs_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
decoder.reset();
|
||||||
|
info!("APRS decoder reset (seq={})", last_reset_seq);
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for mut pkt in packets {
|
for mut pkt in packets {
|
||||||
if let Some(logger) = decode_logs.as_ref() {
|
if let Some(logger) = decode_logs.as_ref() {
|
||||||
logger.log_aprs(&pkt);
|
logger.log_aprs(&pkt);
|
||||||
@@ -1124,7 +1132,10 @@ pub async fn run_hf_aprs_decoder(
|
|||||||
decode_logs: Option<Arc<DecoderLoggers>>,
|
decode_logs: Option<Arc<DecoderLoggers>>,
|
||||||
histories: Arc<DecoderHistories>,
|
histories: Arc<DecoderHistories>,
|
||||||
) {
|
) {
|
||||||
info!("HF APRS decoder started ({}Hz, {} ch)", sample_rate, channels);
|
info!(
|
||||||
|
"HF APRS decoder started ({}Hz, {} ch)",
|
||||||
|
sample_rate, channels
|
||||||
|
);
|
||||||
let mut decoder = AprsDecoder::new_hf(sample_rate);
|
let mut decoder = AprsDecoder::new_hf(sample_rate);
|
||||||
let mut was_active = false;
|
let mut was_active = false;
|
||||||
let mut last_reset_seq: u64 = 0;
|
let mut last_reset_seq: u64 = 0;
|
||||||
@@ -1162,6 +1173,8 @@ pub async fn run_hf_aprs_decoder(
|
|||||||
last_reset_seq = reset_seq;
|
last_reset_seq = reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
info!("HF APRS decoder reset (seq={})", last_reset_seq);
|
info!("HF APRS decoder reset (seq={})", last_reset_seq);
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mono = downmix_if_needed(frame, channels);
|
let mut mono = downmix_if_needed(frame, channels);
|
||||||
@@ -1169,6 +1182,14 @@ pub async fn run_hf_aprs_decoder(
|
|||||||
|
|
||||||
was_active = true;
|
was_active = true;
|
||||||
let packets = tokio::task::block_in_place(|| decoder.process_samples(&mono));
|
let packets = tokio::task::block_in_place(|| decoder.process_samples(&mono));
|
||||||
|
let latest_reset_seq = state_rx.borrow().hf_aprs_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
decoder.reset();
|
||||||
|
info!("HF APRS decoder reset (seq={})", last_reset_seq);
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for mut pkt in packets {
|
for mut pkt in packets {
|
||||||
if let Some(logger) = decode_logs.as_ref() {
|
if let Some(logger) = decode_logs.as_ref() {
|
||||||
logger.log_aprs(&pkt);
|
logger.log_aprs(&pkt);
|
||||||
@@ -1241,10 +1262,7 @@ pub async fn run_ais_decoder(
|
|||||||
let mut decoder_a = AisDecoder::new(sample_rate);
|
let mut decoder_a = AisDecoder::new(sample_rate);
|
||||||
let mut decoder_b = AisDecoder::new(sample_rate);
|
let mut decoder_b = AisDecoder::new(sample_rate);
|
||||||
let mut was_active = false;
|
let mut was_active = false;
|
||||||
let mut active = matches!(
|
let mut active = matches!(state_rx.borrow().status.mode, RigMode::AIS);
|
||||||
state_rx.borrow().status.mode,
|
|
||||||
RigMode::AIS
|
|
||||||
);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !active {
|
if !active {
|
||||||
@@ -1338,10 +1356,7 @@ pub async fn run_vdes_decoder(
|
|||||||
info!("VDES decoder started ({}Hz complex baseband)", sample_rate);
|
info!("VDES decoder started ({}Hz complex baseband)", sample_rate);
|
||||||
let mut decoder = VdesDecoder::new(sample_rate);
|
let mut decoder = VdesDecoder::new(sample_rate);
|
||||||
let mut was_active = false;
|
let mut was_active = false;
|
||||||
let mut active = matches!(
|
let mut active = matches!(state_rx.borrow().status.mode, RigMode::VDES);
|
||||||
state_rx.borrow().status.mode,
|
|
||||||
RigMode::VDES
|
|
||||||
);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !active {
|
if !active {
|
||||||
@@ -1486,6 +1501,8 @@ pub async fn run_cw_decoder(
|
|||||||
last_reset_seq = reset_seq;
|
last_reset_seq = reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
info!("CW decoder reset (seq={})", last_reset_seq);
|
info!("CW decoder reset (seq={})", last_reset_seq);
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if !process_enabled {
|
if !process_enabled {
|
||||||
if was_active {
|
if was_active {
|
||||||
@@ -1509,6 +1526,14 @@ pub async fn run_cw_decoder(
|
|||||||
};
|
};
|
||||||
was_active = true;
|
was_active = true;
|
||||||
let events = tokio::task::block_in_place(|| decoder.process_samples(&mono));
|
let events = tokio::task::block_in_place(|| decoder.process_samples(&mono));
|
||||||
|
let latest_reset_seq = state_rx.borrow().cw_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
decoder.reset();
|
||||||
|
info!("CW decoder reset (seq={})", last_reset_seq);
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for evt in events {
|
for evt in events {
|
||||||
if let Some(logger) = decode_logs.as_ref() {
|
if let Some(logger) = decode_logs.as_ref() {
|
||||||
logger.log_cw(&evt);
|
logger.log_cw(&evt);
|
||||||
@@ -1686,6 +1711,8 @@ pub async fn run_ft8_decoder(
|
|||||||
last_reset_seq = reset_seq;
|
last_reset_seq = reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
ft8_buf.clear();
|
ft8_buf.clear();
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mono = downmix_mono(frame, channels);
|
let mut mono = downmix_mono(frame, channels);
|
||||||
@@ -1702,6 +1729,14 @@ pub async fn run_ft8_decoder(
|
|||||||
decoder.process_block(&block);
|
decoder.process_block(&block);
|
||||||
decoder.decode_if_ready(100)
|
decoder.decode_if_ready(100)
|
||||||
});
|
});
|
||||||
|
let latest_reset_seq = state_rx.borrow().ft8_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
decoder.reset();
|
||||||
|
ft8_buf.clear();
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
for res in results {
|
for res in results {
|
||||||
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
@@ -1831,6 +1866,8 @@ pub async fn run_ft4_decoder(
|
|||||||
last_reset_seq = reset_seq;
|
last_reset_seq = reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
ft4_buf.clear();
|
ft4_buf.clear();
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mono = downmix_mono(frame, channels);
|
let mut mono = downmix_mono(frame, channels);
|
||||||
@@ -1847,6 +1884,14 @@ pub async fn run_ft4_decoder(
|
|||||||
decoder.process_block(&block);
|
decoder.process_block(&block);
|
||||||
decoder.decode_if_ready(100)
|
decoder.decode_if_ready(100)
|
||||||
});
|
});
|
||||||
|
let latest_reset_seq = state_rx.borrow().ft4_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
decoder.reset();
|
||||||
|
ft4_buf.clear();
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
for res in results {
|
for res in results {
|
||||||
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
@@ -1965,6 +2010,8 @@ pub async fn run_ft2_decoder(
|
|||||||
ft2_buf.clear();
|
ft2_buf.clear();
|
||||||
pending_decode_samples = 0;
|
pending_decode_samples = 0;
|
||||||
recent_decodes.clear();
|
recent_decodes.clear();
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mono = downmix_mono(frame, channels);
|
let mut mono = downmix_mono(frame, channels);
|
||||||
@@ -1984,6 +2031,16 @@ pub async fn run_ft2_decoder(
|
|||||||
let results = tokio::task::block_in_place(|| {
|
let results = tokio::task::block_in_place(|| {
|
||||||
decode_ft2_window(&mut decoder, &ft2_buf, 100)
|
decode_ft2_window(&mut decoder, &ft2_buf, 100)
|
||||||
});
|
});
|
||||||
|
let latest_reset_seq = state_rx.borrow().ft2_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
decoder.reset();
|
||||||
|
ft2_buf.clear();
|
||||||
|
pending_decode_samples = 0;
|
||||||
|
recent_decodes.clear();
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
for res in results {
|
for res in results {
|
||||||
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
@@ -2109,13 +2166,33 @@ pub async fn run_wspr_decoder(
|
|||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
};
|
};
|
||||||
let slot = now / slot_len_s;
|
let slot = now / slot_len_s;
|
||||||
|
let reset_seq = {
|
||||||
|
let state = state_rx.borrow();
|
||||||
|
state.wspr_decode_reset_seq
|
||||||
|
};
|
||||||
|
if reset_seq != last_reset_seq {
|
||||||
|
last_reset_seq = reset_seq;
|
||||||
|
slot_buf.clear();
|
||||||
|
last_slot = slot;
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if last_slot == -1 {
|
if last_slot == -1 {
|
||||||
last_slot = slot;
|
last_slot = slot;
|
||||||
} else if slot != last_slot {
|
} else if slot != last_slot {
|
||||||
let base_freq = state_rx.borrow().status.freq.hz;
|
let base_freq = state_rx.borrow().status.freq.hz;
|
||||||
match tokio::task::block_in_place(|| {
|
let decode_results = tokio::task::block_in_place(|| {
|
||||||
decoder.decode_slot(&slot_buf, Some(base_freq))
|
decoder.decode_slot(&slot_buf, Some(base_freq))
|
||||||
}) {
|
});
|
||||||
|
let latest_reset_seq = state_rx.borrow().wspr_decode_reset_seq;
|
||||||
|
if latest_reset_seq != reset_seq {
|
||||||
|
last_reset_seq = latest_reset_seq;
|
||||||
|
slot_buf.clear();
|
||||||
|
last_slot = slot;
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match decode_results {
|
||||||
Ok(results) => {
|
Ok(results) => {
|
||||||
for res in results {
|
for res in results {
|
||||||
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
let ts_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
@@ -2141,15 +2218,13 @@ pub async fn run_wspr_decoder(
|
|||||||
slot_buf.clear();
|
slot_buf.clear();
|
||||||
last_slot = slot;
|
last_slot = slot;
|
||||||
}
|
}
|
||||||
|
let latest_reset_seq = state_rx.borrow().wspr_decode_reset_seq;
|
||||||
let reset_seq = {
|
if latest_reset_seq != last_reset_seq {
|
||||||
let state = state_rx.borrow();
|
last_reset_seq = latest_reset_seq;
|
||||||
state.wspr_decode_reset_seq
|
|
||||||
};
|
|
||||||
if reset_seq != last_reset_seq {
|
|
||||||
last_reset_seq = reset_seq;
|
|
||||||
slot_buf.clear();
|
slot_buf.clear();
|
||||||
last_slot = slot;
|
last_slot = slot;
|
||||||
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mono = downmix_mono(frame, channels);
|
let mut mono = downmix_mono(frame, channels);
|
||||||
@@ -2348,8 +2423,7 @@ async fn run_background_ft8_decoder(
|
|||||||
loop {
|
loop {
|
||||||
match pcm_rx.recv().await {
|
match pcm_rx.recv().await {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
let now = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
{
|
|
||||||
Ok(dur) => dur.as_secs() as i64,
|
Ok(dur) => dur.as_secs() as i64,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
};
|
};
|
||||||
@@ -2427,11 +2501,10 @@ async fn run_background_ft4_decoder(
|
|||||||
match pcm_rx.recv().await {
|
match pcm_rx.recv().await {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now_ms =
|
let now_ms =
|
||||||
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
{
|
Ok(dur) => dur.as_millis() as i64,
|
||||||
Ok(dur) => dur.as_millis() as i64,
|
Err(_) => 0,
|
||||||
Err(_) => 0,
|
};
|
||||||
};
|
|
||||||
// FT4 slot period is 7.5s
|
// FT4 slot period is 7.5s
|
||||||
let slot = now_ms / 7_500;
|
let slot = now_ms / 7_500;
|
||||||
if slot != last_slot {
|
if slot != last_slot {
|
||||||
@@ -2540,11 +2613,7 @@ async fn run_background_ft2_decoder(
|
|||||||
},
|
},
|
||||||
message: res.text,
|
message: res.text,
|
||||||
};
|
};
|
||||||
if !should_emit_ft2_decode(
|
if !should_emit_ft2_decode(&mut recent_decodes, &msg.message, msg.freq_hz) {
|
||||||
&mut recent_decodes,
|
|
||||||
&msg.message,
|
|
||||||
msg.freq_hz,
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let _ = decode_tx.send(DecodedMessage::Ft2(msg));
|
let _ = decode_tx.send(DecodedMessage::Ft2(msg));
|
||||||
@@ -2584,8 +2653,7 @@ async fn run_background_wspr_decoder(
|
|||||||
loop {
|
loop {
|
||||||
match pcm_rx.recv().await {
|
match pcm_rx.recv().await {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
let now = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
{
|
|
||||||
Ok(dur) => dur.as_secs() as i64,
|
Ok(dur) => dur.as_secs() as i64,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
};
|
};
|
||||||
@@ -2755,15 +2823,51 @@ async fn handle_audio_client(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
push_history!(histories.snapshot_ais_history(), DecodedMessage::Ais, AUDIO_MSG_AIS_DECODE);
|
push_history!(
|
||||||
push_history!(histories.snapshot_vdes_history(), DecodedMessage::Vdes, AUDIO_MSG_VDES_DECODE);
|
histories.snapshot_ais_history(),
|
||||||
push_history!(histories.snapshot_aprs_history(), DecodedMessage::Aprs, AUDIO_MSG_APRS_DECODE);
|
DecodedMessage::Ais,
|
||||||
push_history!(histories.snapshot_hf_aprs_history(), DecodedMessage::HfAprs, AUDIO_MSG_HF_APRS_DECODE);
|
AUDIO_MSG_AIS_DECODE
|
||||||
push_history!(histories.snapshot_ft8_history(), DecodedMessage::Ft8, AUDIO_MSG_FT8_DECODE);
|
);
|
||||||
push_history!(histories.snapshot_ft4_history(), DecodedMessage::Ft4, AUDIO_MSG_FT4_DECODE);
|
push_history!(
|
||||||
push_history!(histories.snapshot_ft2_history(), DecodedMessage::Ft2, AUDIO_MSG_FT2_DECODE);
|
histories.snapshot_vdes_history(),
|
||||||
push_history!(histories.snapshot_wspr_history(), DecodedMessage::Wspr, AUDIO_MSG_WSPR_DECODE);
|
DecodedMessage::Vdes,
|
||||||
push_history!(histories.snapshot_cw_history(), DecodedMessage::Cw, AUDIO_MSG_CW_DECODE);
|
AUDIO_MSG_VDES_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_aprs_history(),
|
||||||
|
DecodedMessage::Aprs,
|
||||||
|
AUDIO_MSG_APRS_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_hf_aprs_history(),
|
||||||
|
DecodedMessage::HfAprs,
|
||||||
|
AUDIO_MSG_HF_APRS_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_ft8_history(),
|
||||||
|
DecodedMessage::Ft8,
|
||||||
|
AUDIO_MSG_FT8_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_ft4_history(),
|
||||||
|
DecodedMessage::Ft4,
|
||||||
|
AUDIO_MSG_FT4_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_ft2_history(),
|
||||||
|
DecodedMessage::Ft2,
|
||||||
|
AUDIO_MSG_FT2_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_wspr_history(),
|
||||||
|
DecodedMessage::Wspr,
|
||||||
|
AUDIO_MSG_WSPR_DECODE
|
||||||
|
);
|
||||||
|
push_history!(
|
||||||
|
histories.snapshot_cw_history(),
|
||||||
|
DecodedMessage::Cw,
|
||||||
|
AUDIO_MSG_CW_DECODE
|
||||||
|
);
|
||||||
|
|
||||||
(blob, count)
|
(blob, count)
|
||||||
};
|
};
|
||||||
@@ -2773,10 +2877,7 @@ async fn handle_audio_client(
|
|||||||
// well (~10-20x) so this dramatically reduces both transfer size and
|
// well (~10-20x) so this dramatically reduces both transfer size and
|
||||||
// the time the client spends waiting for data.
|
// the time the client spends waiting for data.
|
||||||
let compressed = {
|
let compressed = {
|
||||||
let mut enc = GzEncoder::new(
|
let mut enc = GzEncoder::new(Vec::with_capacity(blob.len() / 8), Compression::fast());
|
||||||
Vec::with_capacity(blob.len() / 8),
|
|
||||||
Compression::fast(),
|
|
||||||
);
|
|
||||||
enc.write_all(&blob)
|
enc.write_all(&blob)
|
||||||
.and_then(|_| enc.finish())
|
.and_then(|_| enc.finish())
|
||||||
.unwrap_or(blob.clone())
|
.unwrap_or(blob.clone())
|
||||||
@@ -2807,7 +2908,7 @@ async fn handle_audio_client(
|
|||||||
let (bg_decode_tx, mut bg_decode_rx) = broadcast::channel::<DecodedMessage>(128);
|
let (bg_decode_tx, mut bg_decode_rx) = broadcast::channel::<DecodedMessage>(128);
|
||||||
|
|
||||||
let opus_sample_rate = stream_info.sample_rate;
|
let opus_sample_rate = stream_info.sample_rate;
|
||||||
let opus_channels = stream_info.channels;
|
let opus_channels = stream_info.channels;
|
||||||
|
|
||||||
// Subscribe to server-side channel destruction events (SDR rigs only).
|
// Subscribe to server-side channel destruction events (SDR rigs only).
|
||||||
let mut destroyed_rx: Option<broadcast::Receiver<Uuid>> =
|
let mut destroyed_rx: Option<broadcast::Receiver<Uuid>> =
|
||||||
@@ -3160,9 +3261,7 @@ async fn handle_audio_client(
|
|||||||
// Payload: JSON { "uuid": "...", "freq_hz": N, "mode": "..." }
|
// Payload: JSON { "uuid": "...", "freq_hz": N, "mode": "..." }
|
||||||
match serde_json::from_slice::<serde_json::Value>(&payload) {
|
match serde_json::from_slice::<serde_json::Value>(&payload) {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
let uuid = v["uuid"]
|
let uuid = v["uuid"].as_str().and_then(|s| s.parse::<Uuid>().ok());
|
||||||
.as_str()
|
|
||||||
.and_then(|s| s.parse::<Uuid>().ok());
|
|
||||||
let freq_hz = v["freq_hz"].as_u64();
|
let freq_hz = v["freq_hz"].as_u64();
|
||||||
let mode_str = v["mode"].as_str().unwrap_or("USB");
|
let mode_str = v["mode"].as_str().unwrap_or("USB");
|
||||||
let hidden = v["hidden"].as_bool().unwrap_or(false);
|
let hidden = v["hidden"].as_bool().unwrap_or(false);
|
||||||
@@ -3184,9 +3283,16 @@ async fn handle_audio_client(
|
|||||||
};
|
};
|
||||||
match ensure_result {
|
match ensure_result {
|
||||||
Ok(pcm_rx) => {
|
Ok(pcm_rx) => {
|
||||||
if let Some(bandwidth_hz) = bandwidth_hz.filter(|bw| *bw > 0) {
|
if let Some(bandwidth_hz) =
|
||||||
if let Err(e) = mgr.set_channel_bandwidth(uuid, bandwidth_hz) {
|
bandwidth_hz.filter(|bw| *bw > 0)
|
||||||
warn!("Audio vchan SUB bandwidth apply failed: {}", e);
|
{
|
||||||
|
if let Err(e) =
|
||||||
|
mgr.set_channel_bandwidth(uuid, bandwidth_hz)
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"Audio vchan SUB bandwidth apply failed: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = vchan_cmd_tx
|
let _ = vchan_cmd_tx
|
||||||
@@ -3194,10 +3300,11 @@ async fn handle_audio_client(
|
|||||||
uuid,
|
uuid,
|
||||||
pcm_rx,
|
pcm_rx,
|
||||||
send_audio: !hidden,
|
send_audio: !hidden,
|
||||||
background_decode: (!decoder_kinds.is_empty()).then_some(BackgroundDecodeSpec {
|
background_decode: (!decoder_kinds.is_empty())
|
||||||
base_freq_hz: freq_hz,
|
.then_some(BackgroundDecodeSpec {
|
||||||
decoder_kinds,
|
base_freq_hz: freq_hz,
|
||||||
}),
|
decoder_kinds,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -3211,14 +3318,12 @@ async fn handle_audio_client(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((AUDIO_MSG_VCHAN_UNSUB, payload)) => {
|
Ok((AUDIO_MSG_VCHAN_UNSUB, payload)) => match parse_vchan_uuid_msg(&payload) {
|
||||||
match parse_vchan_uuid_msg(&payload) {
|
Ok(uuid) => {
|
||||||
Ok(uuid) => {
|
let _ = vchan_cmd_tx.send(VChanCmd::Unsubscribe(uuid)).await;
|
||||||
let _ = vchan_cmd_tx.send(VChanCmd::Unsubscribe(uuid)).await;
|
|
||||||
}
|
|
||||||
Err(e) => warn!("Audio vchan UNSUB: bad payload: {}", e),
|
|
||||||
}
|
}
|
||||||
}
|
Err(e) => warn!("Audio vchan UNSUB: bad payload: {}", e),
|
||||||
|
},
|
||||||
Ok((AUDIO_MSG_VCHAN_FREQ, payload)) => {
|
Ok((AUDIO_MSG_VCHAN_FREQ, payload)) => {
|
||||||
if let Some(ref mgr) = vchan_manager {
|
if let Some(ref mgr) = vchan_manager {
|
||||||
if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&payload) {
|
if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&payload) {
|
||||||
@@ -3236,7 +3341,8 @@ async fn handle_audio_client(
|
|||||||
Ok((AUDIO_MSG_VCHAN_MODE, payload)) => {
|
Ok((AUDIO_MSG_VCHAN_MODE, payload)) => {
|
||||||
if let Some(ref mgr) = vchan_manager {
|
if let Some(ref mgr) = vchan_manager {
|
||||||
if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&payload) {
|
if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&payload) {
|
||||||
if let Some(uuid) = v["uuid"].as_str().and_then(|s| s.parse::<Uuid>().ok()) {
|
if let Some(uuid) = v["uuid"].as_str().and_then(|s| s.parse::<Uuid>().ok())
|
||||||
|
{
|
||||||
let mode = trx_protocol::codec::parse_mode(
|
let mode = trx_protocol::codec::parse_mode(
|
||||||
v["mode"].as_str().unwrap_or("USB"),
|
v["mode"].as_str().unwrap_or("USB"),
|
||||||
);
|
);
|
||||||
@@ -3280,7 +3386,10 @@ async fn handle_audio_client(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((msg_type, _)) => {
|
Ok((msg_type, _)) => {
|
||||||
warn!("Audio: unexpected message type {:#04x} from {}", msg_type, peer);
|
warn!(
|
||||||
|
"Audio: unexpected message type {:#04x} from {}",
|
||||||
|
msg_type, peer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -671,7 +671,9 @@ async fn process_command(
|
|||||||
// Apply state updates based on command result
|
// Apply state updates based on command result
|
||||||
match cmd_result {
|
match cmd_result {
|
||||||
CommandResult::FreqUpdated(freq) => {
|
CommandResult::FreqUpdated(freq) => {
|
||||||
|
let prev_freq_hz = ctx.state.status.freq.hz;
|
||||||
ctx.state.apply_freq(freq);
|
ctx.state.apply_freq(freq);
|
||||||
|
invalidate_main_decoder_windows_on_freq_change(ctx.state, prev_freq_hz);
|
||||||
*ctx.poll_pause_until = Some(Instant::now() + Duration::from_millis(200));
|
*ctx.poll_pause_until = Some(Instant::now() + Duration::from_millis(200));
|
||||||
}
|
}
|
||||||
CommandResult::ModeUpdated(mode) => {
|
CommandResult::ModeUpdated(mode) => {
|
||||||
@@ -786,7 +788,9 @@ async fn refresh_state_from_cat(rig: &mut Box<dyn RigCat>, state: &mut RigState)
|
|||||||
let (freq, mode, vfo) = rig.get_status().await?;
|
let (freq, mode, vfo) = rig.get_status().await?;
|
||||||
state.filter = rig.filter_state();
|
state.filter = rig.filter_state();
|
||||||
state.control.enabled = Some(true);
|
state.control.enabled = Some(true);
|
||||||
|
let prev_freq_hz = state.status.freq.hz;
|
||||||
state.apply_freq(freq);
|
state.apply_freq(freq);
|
||||||
|
invalidate_main_decoder_windows_on_freq_change(state, prev_freq_hz);
|
||||||
state.apply_mode(mode);
|
state.apply_mode(mode);
|
||||||
state.status.vfo = vfo;
|
state.status.vfo = vfo;
|
||||||
|
|
||||||
@@ -923,9 +927,7 @@ fn map_signal_strength(mode: &RigMode, raw: u8) -> i32 {
|
|||||||
// FT-817 returns 0-15 for signal strength
|
// FT-817 returns 0-15 for signal strength
|
||||||
// Map to approximate dBm / S-units
|
// Map to approximate dBm / S-units
|
||||||
match mode {
|
match mode {
|
||||||
RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => {
|
RigMode::FM | RigMode::WFM | RigMode::AIS | RigMode::VDES => -120 + (raw as i32 * 6),
|
||||||
-120 + (raw as i32 * 6)
|
|
||||||
}
|
|
||||||
_ => -127 + (raw as i32 * 6),
|
_ => -127 + (raw as i32 * 6),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -937,6 +939,19 @@ fn snapshot_from(state: &RigState) -> RigResult<RigSnapshot> {
|
|||||||
.ok_or_else(|| RigError::invalid_state("Rig info unavailable"))
|
.ok_or_else(|| RigError::invalid_state("Rig info unavailable"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn invalidate_main_decoder_windows_on_freq_change(state: &mut RigState, prev_freq_hz: u64) {
|
||||||
|
if state.status.freq.hz == prev_freq_hz {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.aprs_decode_reset_seq += 1;
|
||||||
|
state.hf_aprs_decode_reset_seq += 1;
|
||||||
|
state.cw_decode_reset_seq += 1;
|
||||||
|
state.ft8_decode_reset_seq += 1;
|
||||||
|
state.ft4_decode_reset_seq += 1;
|
||||||
|
state.ft2_decode_reset_seq += 1;
|
||||||
|
state.wspr_decode_reset_seq += 1;
|
||||||
|
}
|
||||||
|
|
||||||
fn sync_machine_state(machine: &mut RigStateMachine, state: &RigState) {
|
fn sync_machine_state(machine: &mut RigStateMachine, state: &RigState) {
|
||||||
let desired = desired_machine_state(state);
|
let desired = desired_machine_state(state);
|
||||||
match (machine.state().clone(), &desired) {
|
match (machine.state().clone(), &desired) {
|
||||||
@@ -1069,3 +1084,50 @@ fn tx_meter_parts(tx: Option<&RigTxStatus>) -> (Option<u8>, Option<u8>, Option<f
|
|||||||
tx.map(|tx| (tx.power, tx.limit, tx.swr, tx.alc))
|
tx.map(|tx| (tx.power, tx.limit, tx.swr, tx.alc))
|
||||||
.unwrap_or((None, None, None, None))
|
.unwrap_or((None, None, None, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn freq_change_invalidates_all_main_decoder_windows() {
|
||||||
|
let mut state = RigState::new_uninitialized();
|
||||||
|
let prev_freq_hz = state.status.freq.hz;
|
||||||
|
state.apply_freq(Freq {
|
||||||
|
hz: prev_freq_hz + 2_700,
|
||||||
|
});
|
||||||
|
|
||||||
|
invalidate_main_decoder_windows_on_freq_change(&mut state, prev_freq_hz);
|
||||||
|
|
||||||
|
assert_eq!(state.aprs_decode_reset_seq, 1);
|
||||||
|
assert_eq!(state.hf_aprs_decode_reset_seq, 1);
|
||||||
|
assert_eq!(state.cw_decode_reset_seq, 1);
|
||||||
|
assert_eq!(state.ft8_decode_reset_seq, 1);
|
||||||
|
assert_eq!(state.ft4_decode_reset_seq, 1);
|
||||||
|
assert_eq!(state.ft2_decode_reset_seq, 1);
|
||||||
|
assert_eq!(state.wspr_decode_reset_seq, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unchanged_freq_keeps_decoder_windows_intact() {
|
||||||
|
let mut state = RigState::new_uninitialized();
|
||||||
|
state.aprs_decode_reset_seq = 2;
|
||||||
|
state.hf_aprs_decode_reset_seq = 3;
|
||||||
|
state.cw_decode_reset_seq = 4;
|
||||||
|
state.ft8_decode_reset_seq = 5;
|
||||||
|
state.ft4_decode_reset_seq = 6;
|
||||||
|
state.ft2_decode_reset_seq = 7;
|
||||||
|
state.wspr_decode_reset_seq = 8;
|
||||||
|
let prev_freq_hz = state.status.freq.hz;
|
||||||
|
|
||||||
|
invalidate_main_decoder_windows_on_freq_change(&mut state, prev_freq_hz);
|
||||||
|
|
||||||
|
assert_eq!(state.aprs_decode_reset_seq, 2);
|
||||||
|
assert_eq!(state.hf_aprs_decode_reset_seq, 3);
|
||||||
|
assert_eq!(state.cw_decode_reset_seq, 4);
|
||||||
|
assert_eq!(state.ft8_decode_reset_seq, 5);
|
||||||
|
assert_eq!(state.ft4_decode_reset_seq, 6);
|
||||||
|
assert_eq!(state.ft2_decode_reset_seq, 7);
|
||||||
|
assert_eq!(state.wspr_decode_reset_seq, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user