From 61e78ce889ef802e2267e3ea82480ed0bc704485 Mon Sep 17 00:00:00 2001 From: William Date: Sun, 6 Oct 2024 20:15:18 -0400 Subject: [PATCH] initial commit --- .gitignore | 2 + Cargo.lock | 213 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 ++ src/hw/mod.rs | 1 + src/hw/serial.rs | 17 ++++ src/main.rs | 32 +++++++ src/xm/command.rs | 69 +++++++++++++++ src/xm/mod.rs | 3 + src/xm/packet.rs | 45 ++++++++++ src/xm/radio_id.rs | 28 ++++++ 10 files changed, 418 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/hw/mod.rs create mode 100644 src/hw/serial.rs create mode 100644 src/main.rs create mode 100644 src/xm/command.rs create mode 100644 src/xm/mod.rs create mode 100644 src/xm/packet.rs create mode 100644 src/xm/radio_id.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0817fec --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +scratch-space.txt \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..24a8d7d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,213 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[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 = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[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 = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[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.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serialport" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ba776acc8c373b9175829206229366273225436845c04f9c20aab8099960e2e" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "core-foundation-sys", + "io-kit-sys", + "libudev", + "mach2", + "nix", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "text_io" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f0c8eb2ad70c12a6a69508f499b3051c924f4b1cfeae85bfad96e6bc5bba46" + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +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 = "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 = "xmpcrd" +version = "0.1.0" +dependencies = [ + "serialport", + "text_io", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fc2d2b8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xmpcrd" +version = "0.1.0" +edition = "2021" + +[dependencies] +serialport = "4.5.1" +text_io = "0.1.12" diff --git a/src/hw/mod.rs b/src/hw/mod.rs new file mode 100644 index 0000000..85b9b05 --- /dev/null +++ b/src/hw/mod.rs @@ -0,0 +1 @@ +pub mod serial; \ No newline at end of file diff --git a/src/hw/serial.rs b/src/hw/serial.rs new file mode 100644 index 0000000..f188577 --- /dev/null +++ b/src/hw/serial.rs @@ -0,0 +1,17 @@ +use std::io; +use std::time::Duration; +use serialport::{SerialPort, DataBits, FlowControl, Parity, StopBits}; + +pub fn xm_pcr_serial(port_name: &str, baud_rate: u32, timeout: Duration) -> Result, io::Error> { + + let port: Result, io::Error> = serialport::new(port_name, baud_rate) + .data_bits(DataBits::Eight) + .flow_control(FlowControl::None) + .parity(Parity::None) + .stop_bits(StopBits::One) + .timeout(timeout) + .open() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("Failed to open serial port: {}", e))); + + return port; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..bba46ac --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +mod xm; +mod hw; + +use xm::command::{send_command, XMCommand}; +use hw::serial::xm_pcr_serial; +use std::time::Duration; +use std::io; + +fn main() -> Result<(), io::Error> { + let port_name: &str = "/dev/ttyUSB0"; // Adjust as per your system + let baud_rate: u32 = 9600; // Adjust baud rate as needed + let timeout: Duration = Duration::from_millis(500); // Short timeout + + let mut port = match xm_pcr_serial(port_name, baud_rate, timeout) { + Ok(p) => p, // Successfully got the serial port + Err(e) => { + eprintln!("Error opening serial port: {}", e); + return Err(e); // Return the error if we can't open the port + } + }; + + match send_command(&mut *port, XMCommand::get_radio_id(), timeout) { + Ok(serial_number_str) => { + println!("Received valid XMRadioID: {}", serial_number_str); + }, + Err(e) => { + eprintln!("Error: {}", e); + } + } + + Ok(()) +} diff --git a/src/xm/command.rs b/src/xm/command.rs new file mode 100644 index 0000000..2bae82b --- /dev/null +++ b/src/xm/command.rs @@ -0,0 +1,69 @@ +use serialport::SerialPort; +use std::thread::sleep; +use std::time::Duration; +use std::io; + +use super::packet::XMPacket; +use super::radio_id::XMRadioID; + +pub struct XMCommand; + +impl XMCommand { + #[allow(dead_code)] + pub fn power_on() -> XMPacket { + XMPacket::new(vec![0x00, 0x10, 0x10, 0x10, 0x01]) + } + #[allow(dead_code)] + pub fn power_off() -> XMPacket { + XMPacket::new(vec![0x01, 0x00]) + } + #[allow(dead_code)] + pub fn select_channel(channel: u8) -> XMPacket { + XMPacket::new(vec![0x10, 0x02, channel, 0x00, 0x00, 0x01]) + } + #[allow(dead_code)] + pub fn get_radio_id() -> XMPacket { + XMPacket::new(vec![0x31]) + } +} + +pub fn send_command(port: &mut dyn SerialPort, command: XMPacket, timeout: Duration) -> Result { + loop { + // Send the command + port.write_all(&command.to_bytes())?; + port.flush()?; + + // Read the response from the serial port + let mut response: Vec = vec![0; 1024]; // Buffer for response + match port.read(&mut response) { + Ok(n) => { + response.truncate(n); + + // Validate the response as an XMPacket + if XMPacket::is_valid_response(&response) { + let packet = XMPacket::new(response); + + // Parse the XMPacket into an XMRadioID + match XMRadioID::from_packet(&packet) { + Ok(radio_id) => { + let serial_number_bytes = radio_id.get_radio_id(); + let serial_number_str = String::from_utf8_lossy(serial_number_bytes).to_string(); + return Ok(serial_number_str); // Return the serial number string + }, + Err(e) => { + eprintln!("Error parsing XMRadioID: {}", e); + } + } + } else { + eprintln!("Received invalid XMPacket"); + } + }, + Err(e) => { + eprintln!("Error reading from serial port: {}", e); + } + } + + // Wait for the timeout period before retrying + sleep(timeout); + } +} diff --git a/src/xm/mod.rs b/src/xm/mod.rs new file mode 100644 index 0000000..9806bb2 --- /dev/null +++ b/src/xm/mod.rs @@ -0,0 +1,3 @@ +pub mod packet; +pub mod command; +pub mod radio_id; \ No newline at end of file diff --git a/src/xm/packet.rs b/src/xm/packet.rs new file mode 100644 index 0000000..06f31b7 --- /dev/null +++ b/src/xm/packet.rs @@ -0,0 +1,45 @@ +pub struct XMPacket { + data_length: u16, + pub data: Vec, +} + +impl XMPacket { + pub(crate) fn new(data: Vec) -> Self { + let data_length = data.len() as u16; + XMPacket { data_length, data } + } + + pub(crate) fn to_bytes(&self) -> Vec { + let mut command: Vec = vec![0x5A, 0xA5]; // Start bytes + command.extend_from_slice(&self.data_length.to_be_bytes()); // Length in big-endian + command.extend_from_slice(&self.data); // Command data + command.extend_from_slice(&[0xED, 0xED]); // Footer + command + } + + // Helper function to validate the response format + pub(crate) fn is_valid_response(response: &[u8]) -> bool { + // Validate that the response is at least the minimum size for a valid packet (6 bytes) + if response.len() < 6 { + return false; + } + + // Check the start of the response (5A A5) + if response[0] != 0x5A || response[1] != 0xA5 { + return false; + } + + // Extract the length field (xx xx), which is in big-endian format + let length = u16::from_be_bytes([response[2], response[3]]) as usize; + + // Ensure the total response length matches what is indicated in the length field + header + footer + // Header (4 bytes) + Data (length bytes) + Footer (2 bytes) + if response.len() != length + 6 { + return false; + } + + // If all checks pass, the response is valid + true + } + +} diff --git a/src/xm/radio_id.rs b/src/xm/radio_id.rs new file mode 100644 index 0000000..1e2fe5d --- /dev/null +++ b/src/xm/radio_id.rs @@ -0,0 +1,28 @@ +use super::packet::XMPacket; + +pub struct XMRadioID { + radio_id: Vec, +} + +impl XMRadioID { + pub(crate) fn from_packet(packet: &XMPacket) -> Result { + let packet_bytes = packet.to_bytes(); + + // Validate the packet + if !XMPacket::is_valid_response(&packet_bytes) { + return Err("Invalid packet"); + } + + // Extract the serial number (12 bytes starting from the 8th byte) + if packet_bytes.len() < 20 { + return Err("Packet too short to contain an XM Radio ID"); + } + + let radio_id = packet_bytes[8..20].to_vec(); + Ok(XMRadioID { radio_id }) + } + + pub fn get_radio_id(&self) -> &Vec { + &self.radio_id + } +} \ No newline at end of file