[feat](trx-wefax): save images as YYYY-MM-DD_HH-MM-SS-freq_kHz_MODE.png
Include rig dial frequency and mode in WEFAX image filenames, matching fldigi's approach of capturing tuning at save time. Images are saved to ~/.cache/trx-rs/wefax/. Server passes current rig state to the decoder via set_tuning() before each processing block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -80,6 +80,10 @@ pub struct WefaxDecoder {
|
|||||||
signal_detect_count: u32,
|
signal_detect_count: u32,
|
||||||
/// Accumulator for computing luminance variance within the current window.
|
/// Accumulator for computing luminance variance within the current window.
|
||||||
signal_detect_buf: Vec<f32>,
|
signal_detect_buf: Vec<f32>,
|
||||||
|
/// Current rig dial frequency in Hz (for image filenames).
|
||||||
|
freq_hz: u64,
|
||||||
|
/// Current rig mode name (for image filenames).
|
||||||
|
mode: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WefaxDecoder {
|
impl WefaxDecoder {
|
||||||
@@ -102,6 +106,8 @@ impl WefaxDecoder {
|
|||||||
sent_idle_event: false,
|
sent_idle_event: false,
|
||||||
signal_detect_count: 0,
|
signal_detect_count: 0,
|
||||||
signal_detect_buf: Vec::with_capacity(INTERNAL_RATE as usize / 2),
|
signal_detect_buf: Vec::with_capacity(INTERNAL_RATE as usize / 2),
|
||||||
|
freq_hz: 0,
|
||||||
|
mode: String::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,6 +325,12 @@ impl WefaxDecoder {
|
|||||||
events
|
events
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the current rig tuning (used for image filenames).
|
||||||
|
pub fn set_tuning(&mut self, freq_hz: u64, mode: &str) {
|
||||||
|
self.freq_hz = freq_hz;
|
||||||
|
self.mode = mode.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if the decoder is currently receiving an image.
|
/// Check if the decoder is currently receiving an image.
|
||||||
pub fn is_receiving(&self) -> bool {
|
pub fn is_receiving(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
@@ -400,7 +412,7 @@ impl WefaxDecoder {
|
|||||||
// Save PNG if output directory is configured.
|
// Save PNG if output directory is configured.
|
||||||
if let Some(ref dir) = self.config.output_dir {
|
if let Some(ref dir) = self.config.output_dir {
|
||||||
let output_path = PathBuf::from(dir);
|
let output_path = PathBuf::from(dir);
|
||||||
match image.save_png(&output_path, ioc, lpm) {
|
match image.save_png(&output_path, self.freq_hz, &self.mode) {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
path_str = Some(p.to_string_lossy().into_owned());
|
path_str = Some(p.to_string_lossy().into_owned());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ impl ImageAssembler {
|
|||||||
pub fn save_png(
|
pub fn save_png(
|
||||||
&self,
|
&self,
|
||||||
output_dir: &Path,
|
output_dir: &Path,
|
||||||
ioc: u16,
|
freq_hz: u64,
|
||||||
lpm: u16,
|
mode: &str,
|
||||||
) -> Result<PathBuf, String> {
|
) -> Result<PathBuf, String> {
|
||||||
if self.lines.is_empty() {
|
if self.lines.is_empty() {
|
||||||
return Err("no image lines to save".into());
|
return Err("no image lines to save".into());
|
||||||
@@ -53,7 +53,7 @@ impl ImageAssembler {
|
|||||||
std::fs::create_dir_all(output_dir)
|
std::fs::create_dir_all(output_dir)
|
||||||
.map_err(|e| format!("create output dir: {}", e))?;
|
.map_err(|e| format!("create output dir: {}", e))?;
|
||||||
|
|
||||||
let filename = generate_filename(ioc, lpm);
|
let filename = generate_filename(freq_hz, mode);
|
||||||
let path = output_dir.join(&filename);
|
let path = output_dir.join(&filename);
|
||||||
|
|
||||||
let file = std::fs::File::create(&path)
|
let file = std::fs::File::create(&path)
|
||||||
@@ -89,7 +89,7 @@ impl ImageAssembler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_filename(ioc: u16, lpm: u16) -> String {
|
fn generate_filename(freq_hz: u64, mode: &str) -> String {
|
||||||
let now = std::time::SystemTime::now()
|
let now = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
@@ -97,10 +97,11 @@ fn generate_filename(ioc: u16, lpm: u16) -> String {
|
|||||||
|
|
||||||
// Convert to UTC datetime components manually (avoid chrono dependency).
|
// Convert to UTC datetime components manually (avoid chrono dependency).
|
||||||
let (year, month, day, hour, min, sec) = unix_to_utc(secs);
|
let (year, month, day, hour, min, sec) = unix_to_utc(secs);
|
||||||
|
let freq_khz = freq_hz / 1000;
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
"WEFAX-{:04}-{:02}-{:02}T{:02}{:02}{:02}-IOC{}-{}lpm.png",
|
"{:04}-{:02}-{:02}_{:02}-{:02}-{:02}-{}_kHz_{}.png",
|
||||||
year, month, day, hour, min, sec, ioc, lpm
|
year, month, day, hour, min, sec, freq_khz, mode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +180,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dir = std::env::temp_dir().join("trx-wefax-test");
|
let dir = std::env::temp_dir().join("trx-wefax-test");
|
||||||
let result = asm.save_png(&dir, 576, 120);
|
let result = asm.save_png(&dir, 7880000, "USB");
|
||||||
assert!(result.is_ok(), "save_png failed: {:?}", result.err());
|
assert!(result.is_ok(), "save_png failed: {:?}", result.err());
|
||||||
let path = result.unwrap();
|
let path = result.unwrap();
|
||||||
assert!(path.exists());
|
assert!(path.exists());
|
||||||
|
|||||||
@@ -2780,6 +2780,13 @@ pub async fn run_wefax_decoder(
|
|||||||
};
|
};
|
||||||
|
|
||||||
was_active = true;
|
was_active = true;
|
||||||
|
{
|
||||||
|
let state = state_rx.borrow();
|
||||||
|
decoder.set_tuning(
|
||||||
|
state.status.freq.hz,
|
||||||
|
&format!("{:?}", state.status.mode),
|
||||||
|
);
|
||||||
|
}
|
||||||
let events = tokio::task::block_in_place(|| {
|
let events = tokio::task::block_in_place(|| {
|
||||||
let _span = info_span!("wefax_decode").entered();
|
let _span = info_span!("wefax_decode").entered();
|
||||||
decoder.process_samples(&mono)
|
decoder.process_samples(&mono)
|
||||||
|
|||||||
Reference in New Issue
Block a user