vfo-sync

Bidirectional VFO synchronization for IC-7300 and SDR++
git clone https://git.bradleygannon.com/vfo-sync.git
Log | Files | Refs

commit 6a16dec857c147fe154b3b49a97e89193309c43c
Author: Bradley Gannon <bradley@bradleygannon.com>
Date:   Fri, 22 Nov 2024 21:24:54 -0500

Add bidirectional frequency changing

Diffstat:
A.gitignore | 1+
ACargo.lock | 438+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ACargo.toml | 9+++++++++
Asrc/ic7300.rs | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main.rs | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/rspduo.rs | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 767 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock @@ -0,0 +1,438 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "libudev" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0" +dependencies = [ + "libc", + "libudev-sys", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "remote-sdr-control" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "serialport", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serialport" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7331eefcaafaa382c0df95bcd84068f0b3e3c215c300750dde2316e9b8806ed5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unescaper" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878a167baa8afd137494101a688ef8c67125089ff2249284bd2b5f9bfedb815" +dependencies = [ + "thiserror", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "remote-sdr-control" +version = "0.1.0" +edition = "2021" + +[dependencies] +env_logger = "0.11.5" +log = "0.4.22" +serialport = "4.6.0" diff --git a/src/ic7300.rs b/src/ic7300.rs @@ -0,0 +1,193 @@ +use log::{debug, trace}; +use serialport::{DataBits::Eight, Parity::None, StopBits::One}; +use std::{ + sync::mpsc::{Receiver, Sender}, + time::{Duration, Instant}, +}; + +use crate::{Freq, Ptt, State}; + +enum CivCmd { + SetFreq(Freq), + SetPtt(Ptt), + ReadPtt, +} + +fn to_bcd(n: u8) -> u8 { + ((n / 10) * 16) + (n % 10) +} + +fn from_bcd(n: u8) -> u8 { + ((n / 16) * 10) + (n % 16) +} + +impl From<CivCmd> for Vec<u8> { + fn from(value: CivCmd) -> Self { + match value { + CivCmd::SetFreq(f) => { + let tens_ones_hz = (f % 100) as u8; + let thousands_hundreds_hz = ((f / 100) % 100) as u8; + let hundreds_tens_khz = ((f / 10_000) % 100) as u8; + let tens_ones_mhz = ((f / 1_000_000) % 100) as u8; + let bytes = vec![ + 0xfe, + 0xfe, + 0x94, + 0xe1, + 0x00, + to_bcd(tens_ones_hz), + to_bcd(thousands_hundreds_hz), + to_bcd(hundreds_tens_khz), + to_bcd(tens_ones_mhz), + 0x00, + 0xfd, + ]; + trace!("Frequency is {}, converted to {:x?}", f, bytes); + bytes + } + CivCmd::SetPtt(p) => { + let bytes = vec![ + 0xfe, + 0xfe, + 0x94, + 0xe1, + 0x1c, + 0x00, + if p { 0x01 } else { 0x00 }, + 0xfd, + ]; + trace!("PTT is {}, converted to {:x?}", p, bytes); + bytes + } + CivCmd::ReadPtt => vec![0xfe, 0xfe, 0x94, 0xe1, 0x1c, 0x00, 0xfd], + } + } +} + +#[derive(Debug)] +enum CivMsg { + Freq(Freq), + Ptt(Ptt), +} + +#[derive(Debug)] +enum CivMsgError { + InputTooShort, + InvalidStartOrEnd, + WrongAddress, + WrongCommand, +} + +impl TryFrom<&[u8]> for CivMsg { + type Error = CivMsgError; + fn try_from(value: &[u8]) -> Result<Self, Self::Error> { + if value.len() < 8 { + trace!("Input too short: {} < 8", value.len()); + return Err(CivMsgError::InputTooShort); + } + if value[..2] != [0xfe, 0xfe] || *value.last().unwrap() != 0xfd { + trace!( + "Invalid start or end: [0x{:x}, 0x{:x}, ... , 0x{:x}]", + value[0], + value[1], + value.last().unwrap() + ); + return Err(CivMsgError::InvalidStartOrEnd); + } + if value[2..4] != [0xe1, 0x94] && value[2..4] != [0x00, 0x94] { + trace!( + "Wrong address: [..., 0x{:x}, 0x{:x}, ...]", + value[2], + value[3] + ); + return Err(CivMsgError::WrongAddress); + } + if value[4] == 0x00 && value.len() == 11 { + let freq = from_bcd(value[5]) as u32 + + from_bcd(value[6]) as u32 * 100 + + from_bcd(value[7]) as u32 * 10_000 + + from_bcd(value[8]) as u32 * 1_000_000; + return Ok(CivMsg::Freq(freq)); + } + if value[4..6] == [0x1c, 0x00] && value.len() == 8 { + return Ok(CivMsg::Ptt(value[6] != 0x00)); + } + trace!("Command doesn't apply to us"); + Err(CivMsgError::WrongCommand) + } +} + +pub(crate) fn ic7300_handler(tx: Sender<State>, rx: Receiver<State>) { + let mut port = serialport::new("/dev/ttyUSB1", 115_200) + .data_bits(Eight) + .parity(None) + .stop_bits(One) + .open() + .unwrap(); + debug!("Serial port set up"); + let mut current_state = State::default(); + let mut read_buf = [0u8; 1024]; + let mut last_ptt_check = Instant::now(); + loop { + if let Some(new_state) = rx.try_iter().last() { + trace!("Got new state from main thread"); + if new_state.freq != current_state.freq { + let mut freq_bytes: Vec<u8> = CivCmd::SetFreq(new_state.freq).into(); + port.write_all(&mut freq_bytes).unwrap(); + debug!("Set new frequency to {}", new_state.freq); + } + if new_state.ptt != current_state.ptt { + let mut ptt_bytes: Vec<u8> = CivCmd::SetPtt(new_state.ptt).into(); + port.write_all(&mut ptt_bytes).unwrap(); + debug!("Set PTT to {}", new_state.ptt); + } + current_state = new_state; + } + if last_ptt_check.elapsed() > Duration::from_millis(100) { + let mut ptt_bytes: Vec<u8> = CivCmd::ReadPtt.into(); + port.write_all(&mut ptt_bytes).unwrap(); + last_ptt_check = Instant::now(); + trace!("Sent ReadPtt command"); + } + let mut new_state = current_state; + if port.bytes_to_read().unwrap() == 0 { + // trace!("No bytes to read, continuing"); + continue; + } + let n = match port.read(&mut read_buf) { + Ok(n) => n, + Err(_) => return, + }; + trace!("{:?}", read_buf[..n].to_vec()); + let mut idx = 0; + let mut start = Option::None; + let mut msgs: Vec<CivMsg> = vec![]; + while idx < n { + // trace!("{}", idx); + if read_buf[idx] == 0xfe && read_buf[idx + 1] == 0xfe { + start = Some(idx); + } else if read_buf[idx] == 0xfd { + let end = idx + 1; + match read_buf[start.unwrap()..end].try_into() { + Ok(msg) => { + debug!("Got new CivMsg: {:?}", msg); + msgs.push(msg); + } + Err(e) => trace!("Got a message that we don't care about: {:?}", e), + } + start = Option::None; + } + idx += 1; + } + for msg in msgs { + match msg { + CivMsg::Freq(f) => new_state.freq = f, + CivMsg::Ptt(p) => new_state.ptt = p, + } + } + if new_state != current_state { + tx.send(new_state).unwrap(); + debug!("Sent new state to main thread"); + } + } +} diff --git a/src/main.rs b/src/main.rs @@ -0,0 +1,73 @@ +use std::{sync::mpsc, thread}; + +use ic7300::ic7300_handler; +use log::{debug, error, trace}; +use rspduo::rspduo_handler; + +mod ic7300; +mod rspduo; + +type Freq = u32; +type Ptt = bool; + +#[derive(Debug, Clone, Copy, PartialEq)] +struct State { + freq: Freq, + ptt: Ptt, +} + +impl Default for State { + fn default() -> Self { + State { + freq: 0, + ptt: false, + } + } +} + +fn main() { + env_logger::init(); + + // By convention, foo uses foo_tx_bar to transmit to bar and uses foo_rx_bar to receive from bar + let (main_tx_ic7300, ic7300_rx_main) = mpsc::channel(); + let (main_tx_rspduo, rspduo_rx_main) = mpsc::channel(); + let (ic7300_tx_main, main_rx_ic7300) = mpsc::channel(); + let (rspduo_tx_main, main_rx_rspduo) = mpsc::channel(); + debug!("Set up thread channels"); + + let mut current_state = State { + freq: 0, + ptt: false, + }; + + let ic7300 = thread::spawn(|| ic7300_handler(ic7300_tx_main, ic7300_rx_main)); + debug!("Spawned thread for IC-7300"); + let rspduo = thread::spawn(|| rspduo_handler(rspduo_tx_main, rspduo_rx_main)); + debug!("Spawned thread for RSPduo"); + + loop { + if ic7300.is_finished() || rspduo.is_finished() { + error!( + "A thread exited, closing... (IC-7300: {}, RSPduo: {})", + ic7300.is_finished(), + rspduo.is_finished() + ); + break; + } + if let Some(new_state) = main_rx_ic7300.try_iter().last() { + trace!("Got new state from IC-7300: {:?}", new_state); + if new_state != current_state { + main_tx_rspduo.send(new_state).unwrap(); + current_state = new_state; + debug!("Sent new state to RSPduo"); + } + } else if let Some(new_state) = main_rx_rspduo.try_iter().last() { + trace!("Got new state from RSPduo: {:?}", new_state); + if new_state != current_state { + main_tx_ic7300.send(new_state).unwrap(); + current_state = new_state; + debug!("Sent new state to IC-7300"); + } + } + } +} diff --git a/src/rspduo.rs b/src/rspduo.rs @@ -0,0 +1,53 @@ +use std::{ + io::{Read, Write}, + net::TcpStream, + sync::mpsc::{Receiver, Sender}, + time::{Duration, Instant}, +}; + +use log::{debug, trace}; + +use crate::State; + +pub(crate) fn rspduo_handler(tx: Sender<State>, rx: Receiver<State>) { + let mut stream = TcpStream::connect("127.0.0.1:4532").unwrap(); + stream + .set_read_timeout(Some(Duration::from_millis(100))) + .unwrap(); + debug!("Connected to rigctl server"); + let mut current_state = State::default(); + let mut read_buf = [0x00; 128]; + let mut last_freq_check = Instant::now(); + loop { + if let Some(new_state) = rx.try_iter().last() { + trace!("Got new state from main thread"); + if new_state.freq != current_state.freq { + let cmd = format!("F {}\n", new_state.freq); + let mut cmd_bytes = cmd.as_bytes(); + stream.write_all(&mut cmd_bytes).unwrap(); + stream.read(&mut read_buf).unwrap(); + debug!("Set new frequency to {}", new_state.freq); + } + current_state = new_state; + } + if last_freq_check.elapsed() > Duration::from_millis(100) { + let mut new_state = current_state; + let _ = stream.read(&mut read_buf); + let mut cmd_bytes = "f\n".as_bytes(); + stream.write_all(&mut cmd_bytes).unwrap(); + let n = stream.read(&mut read_buf).unwrap(); + let resp = String::from_utf8(read_buf[..n].to_vec()).unwrap(); + let resp = resp.trim(); + trace!("Received {:?} \"{}\"", read_buf[..n].to_vec(), resp); + let freq = resp.parse().unwrap(); + new_state.freq = freq; + trace!("Current frequency is {}", new_state.freq); + if new_state != current_state { + tx.send(new_state).unwrap(); + debug!("Sent new state to main thread"); + } + current_state = new_state; + last_freq_check = Instant::now(); + } + } +}