initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
scratch-space.txt
|
213
Cargo.lock
generated
Normal file
213
Cargo.lock
generated
Normal 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
8
Cargo.toml
Normal 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
1
src/hw/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod serial;
|
17
src/hw/serial.rs
Normal file
17
src/hw/serial.rs
Normal 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
32
src/main.rs
Normal 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
69
src/xm/command.rs
Normal 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
3
src/xm/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod packet;
|
||||
pub mod command;
|
||||
pub mod radio_id;
|
45
src/xm/packet.rs
Normal file
45
src/xm/packet.rs
Normal 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
28
src/xm/radio_id.rs
Normal 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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user