initial commit

This commit is contained in:
2024-10-06 20:15:18 -04:00
commit 61e78ce889
10 changed files with 418 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
scratch-space.txt

213
Cargo.lock generated Normal file
View File

@@ -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",
]

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "xmpcrd"
version = "0.1.0"
edition = "2021"
[dependencies]
serialport = "4.5.1"
text_io = "0.1.12"

1
src/hw/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod serial;

17
src/hw/serial.rs Normal file
View File

@@ -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<Box<dyn SerialPort>, io::Error> {
let port: Result<Box<dyn SerialPort>, 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;
}

32
src/main.rs Normal file
View File

@@ -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(())
}

69
src/xm/command.rs Normal file
View File

@@ -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<String, io::Error> {
loop {
// Send the command
port.write_all(&command.to_bytes())?;
port.flush()?;
// Read the response from the serial port
let mut response: Vec<u8> = 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);
}
}

3
src/xm/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod packet;
pub mod command;
pub mod radio_id;

45
src/xm/packet.rs Normal file
View File

@@ -0,0 +1,45 @@
pub struct XMPacket {
data_length: u16,
pub data: Vec<u8>,
}
impl XMPacket {
pub(crate) fn new(data: Vec<u8>) -> Self {
let data_length = data.len() as u16;
XMPacket { data_length, data }
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let mut command: Vec<u8> = 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
}
}

28
src/xm/radio_id.rs Normal file
View File

@@ -0,0 +1,28 @@
use super::packet::XMPacket;
pub struct XMRadioID {
radio_id: Vec<u8>,
}
impl XMRadioID {
pub(crate) fn from_packet(packet: &XMPacket) -> Result<Self, &'static str> {
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<u8> {
&self.radio_id
}
}