[feat](trx-rs): add cw auto/manual controls
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -417,6 +417,12 @@ pub async fn run_cw_decoder(
|
||||
let mut was_active = false;
|
||||
let mut last_reset_seq: u64 = 0;
|
||||
let mut active = matches!(state_rx.borrow().status.mode, RigMode::CW | RigMode::CWR);
|
||||
let mut last_auto = state_rx.borrow().cw_auto;
|
||||
let mut last_wpm = state_rx.borrow().cw_wpm;
|
||||
let mut last_tone = state_rx.borrow().cw_tone_hz;
|
||||
decoder.set_auto(last_auto);
|
||||
decoder.set_wpm(last_wpm);
|
||||
decoder.set_tone_hz(last_tone);
|
||||
|
||||
loop {
|
||||
if !active {
|
||||
@@ -427,6 +433,18 @@ pub async fn run_cw_decoder(
|
||||
if active {
|
||||
pcm_rx = pcm_rx.resubscribe();
|
||||
}
|
||||
if state.cw_auto != last_auto {
|
||||
last_auto = state.cw_auto;
|
||||
decoder.set_auto(last_auto);
|
||||
}
|
||||
if state.cw_wpm != last_wpm {
|
||||
last_wpm = state.cw_wpm;
|
||||
decoder.set_wpm(last_wpm);
|
||||
}
|
||||
if state.cw_tone_hz != last_tone {
|
||||
last_tone = state.cw_tone_hz;
|
||||
decoder.set_tone_hz(last_tone);
|
||||
}
|
||||
if state.cw_decode_reset_seq != last_reset_seq {
|
||||
last_reset_seq = state.cw_decode_reset_seq;
|
||||
decoder.reset();
|
||||
@@ -443,6 +461,18 @@ pub async fn run_cw_decoder(
|
||||
match recv {
|
||||
Ok(frame) => {
|
||||
let state = state_rx.borrow();
|
||||
if state.cw_auto != last_auto {
|
||||
last_auto = state.cw_auto;
|
||||
decoder.set_auto(last_auto);
|
||||
}
|
||||
if state.cw_wpm != last_wpm {
|
||||
last_wpm = state.cw_wpm;
|
||||
decoder.set_wpm(last_wpm);
|
||||
}
|
||||
if state.cw_tone_hz != last_tone {
|
||||
last_tone = state.cw_tone_hz;
|
||||
decoder.set_tone_hz(last_tone);
|
||||
}
|
||||
if state.cw_decode_reset_seq != last_reset_seq {
|
||||
last_reset_seq = state.cw_decode_reset_seq;
|
||||
decoder.reset();
|
||||
@@ -477,6 +507,18 @@ pub async fn run_cw_decoder(
|
||||
Ok(()) => {
|
||||
let state = state_rx.borrow();
|
||||
active = matches!(state.status.mode, RigMode::CW | RigMode::CWR);
|
||||
if state.cw_auto != last_auto {
|
||||
last_auto = state.cw_auto;
|
||||
decoder.set_auto(last_auto);
|
||||
}
|
||||
if state.cw_wpm != last_wpm {
|
||||
last_wpm = state.cw_wpm;
|
||||
decoder.set_wpm(last_wpm);
|
||||
}
|
||||
if state.cw_tone_hz != last_tone {
|
||||
last_tone = state.cw_tone_hz;
|
||||
decoder.set_tone_hz(last_tone);
|
||||
}
|
||||
if state.cw_decode_reset_seq != last_reset_seq {
|
||||
last_reset_seq = state.cw_decode_reset_seq;
|
||||
decoder.reset();
|
||||
|
||||
@@ -124,6 +124,10 @@ pub struct CwDecoder {
|
||||
// WPM
|
||||
wpm: u32,
|
||||
|
||||
// Auto control
|
||||
auto_tone: bool,
|
||||
auto_wpm: bool,
|
||||
|
||||
// Auto tone detection
|
||||
tone_scan_bins: Vec<ToneScanBin>,
|
||||
tone_stable_bin: i32,
|
||||
@@ -172,6 +176,8 @@ impl CwDecoder {
|
||||
current_symbol: String::new(),
|
||||
sample_counter: 0,
|
||||
wpm: 15,
|
||||
auto_tone: true,
|
||||
auto_wpm: true,
|
||||
tone_scan_bins,
|
||||
tone_stable_bin: -1,
|
||||
tone_stable_count: 0,
|
||||
@@ -180,6 +186,20 @@ impl CwDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_auto(&mut self, enabled: bool) {
|
||||
self.auto_tone = enabled;
|
||||
self.auto_wpm = enabled;
|
||||
}
|
||||
|
||||
pub fn set_wpm(&mut self, wpm: u32) {
|
||||
self.wpm = wpm.clamp(5, 40);
|
||||
}
|
||||
|
||||
pub fn set_tone_hz(&mut self, tone_hz: u32) {
|
||||
let tone_hz = tone_hz.clamp(TONE_SCAN_LOW, TONE_SCAN_HIGH);
|
||||
self.recompute_goertzel(tone_hz);
|
||||
}
|
||||
|
||||
fn recompute_goertzel(&mut self, new_freq: u32) {
|
||||
self.tone_freq = new_freq;
|
||||
let k = (new_freq as f32 * self.window_size as f32 / self.sample_rate as f32)
|
||||
@@ -300,8 +320,9 @@ impl CwDecoder {
|
||||
}
|
||||
|
||||
fn process_window(&mut self) {
|
||||
// Auto tone detection
|
||||
self.auto_detect_tone();
|
||||
if self.auto_tone {
|
||||
self.auto_detect_tone();
|
||||
}
|
||||
|
||||
let detected = self.goertzel_detect();
|
||||
let now = self.now_ms();
|
||||
@@ -345,12 +366,14 @@ impl CwDecoder {
|
||||
}
|
||||
self.tone_off_at = now;
|
||||
|
||||
// Collect for auto WPM
|
||||
self.on_durations.push(on_duration);
|
||||
if self.on_durations.len() > 30 {
|
||||
self.on_durations.remove(0);
|
||||
if self.auto_wpm {
|
||||
// Collect for auto WPM
|
||||
self.on_durations.push(on_duration);
|
||||
if self.on_durations.len() > 30 {
|
||||
self.on_durations.remove(0);
|
||||
}
|
||||
self.auto_detect_wpm();
|
||||
}
|
||||
self.auto_detect_wpm();
|
||||
}
|
||||
|
||||
// Flush pending character after long silence
|
||||
@@ -387,6 +410,10 @@ impl CwDecoder {
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
let tone = self.tone_freq;
|
||||
let wpm = self.wpm;
|
||||
let auto_tone = self.auto_tone;
|
||||
let auto_wpm = self.auto_wpm;
|
||||
self.sample_buf.fill(0.0);
|
||||
self.sample_idx = 0;
|
||||
self.tone_on = false;
|
||||
@@ -394,9 +421,11 @@ impl CwDecoder {
|
||||
self.tone_off_at = 0.0;
|
||||
self.current_symbol.clear();
|
||||
self.sample_counter = 0;
|
||||
self.wpm = 15;
|
||||
self.tone_freq = 700;
|
||||
self.recompute_goertzel(700);
|
||||
self.wpm = wpm;
|
||||
self.tone_freq = tone;
|
||||
self.auto_tone = auto_tone;
|
||||
self.auto_wpm = auto_wpm;
|
||||
self.recompute_goertzel(tone);
|
||||
self.tone_stable_bin = -1;
|
||||
self.tone_stable_count = 0;
|
||||
self.on_durations.clear();
|
||||
|
||||
@@ -190,6 +190,9 @@ fn map_command(cmd: ClientCommand) -> RigCommand {
|
||||
ClientCommand::SetTxLimit { limit } => RigCommand::SetTxLimit(limit),
|
||||
ClientCommand::SetAprsDecodeEnabled { enabled } => RigCommand::SetAprsDecodeEnabled(enabled),
|
||||
ClientCommand::SetCwDecodeEnabled { enabled } => RigCommand::SetCwDecodeEnabled(enabled),
|
||||
ClientCommand::SetCwAuto { enabled } => RigCommand::SetCwAuto(enabled),
|
||||
ClientCommand::SetCwWpm { wpm } => RigCommand::SetCwWpm(wpm),
|
||||
ClientCommand::SetCwToneHz { tone_hz } => RigCommand::SetCwToneHz(tone_hz),
|
||||
ClientCommand::ResetAprsDecoder => RigCommand::ResetAprsDecoder,
|
||||
ClientCommand::ResetCwDecoder => RigCommand::ResetCwDecoder,
|
||||
}
|
||||
|
||||
@@ -224,6 +224,9 @@ fn build_initial_state(cfg: &ServerConfig, resolved: &ResolvedConfig) -> RigStat
|
||||
server_longitude: resolved.longitude,
|
||||
aprs_decode_enabled: false,
|
||||
cw_decode_enabled: false,
|
||||
cw_auto: true,
|
||||
cw_wpm: 15,
|
||||
cw_tone_hz: 700,
|
||||
aprs_decode_reset_seq: 0,
|
||||
cw_decode_reset_seq: 0,
|
||||
}
|
||||
|
||||
@@ -125,6 +125,9 @@ pub async fn run_rig_task(
|
||||
server_longitude,
|
||||
aprs_decode_enabled: false,
|
||||
cw_decode_enabled: false,
|
||||
cw_auto: true,
|
||||
cw_wpm: 15,
|
||||
cw_tone_hz: 700,
|
||||
aprs_decode_reset_seq: 0,
|
||||
cw_decode_reset_seq: 0,
|
||||
};
|
||||
@@ -360,6 +363,21 @@ async fn process_command(
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
}
|
||||
RigCommand::SetCwAuto(en) => {
|
||||
ctx.state.cw_auto = en;
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
}
|
||||
RigCommand::SetCwWpm(wpm) => {
|
||||
ctx.state.cw_wpm = wpm.clamp(5, 40);
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
}
|
||||
RigCommand::SetCwToneHz(tone_hz) => {
|
||||
ctx.state.cw_tone_hz = tone_hz.clamp(300, 1200);
|
||||
let _ = ctx.state_tx.send(ctx.state.clone());
|
||||
return snapshot_from(ctx.state);
|
||||
}
|
||||
RigCommand::ResetAprsDecoder => {
|
||||
audio::clear_aprs_history();
|
||||
ctx.state.aprs_decode_reset_seq += 1;
|
||||
|
||||
Reference in New Issue
Block a user