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

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
}
}