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 | + |
| A | Cargo.lock | | | 438 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | Cargo.toml | | | 9 | +++++++++ |
| A | src/ic7300.rs | | | 193 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/main.rs | | | 73 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/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();
+ }
+ }
+}