use crate::protocol::{Command, ErrorCode, Frame, Response};
use crate::{Result, V4Error};
use serialport::SerialPort;
use std::time::{Duration, Instant};
pub const DEFAULT_BAUD_RATE: u32 = 115200;
pub struct V4Serial {
port: Box<dyn SerialPort>,
}
impl V4Serial {
pub fn open(path: &str, baud_rate: u32) -> Result<Self> {
let port = serialport::new(path, baud_rate)
.timeout(Duration::from_secs(5))
.open()?;
Ok(Self { port })
}
pub fn open_default(path: &str) -> Result<Self> {
Self::open(path, DEFAULT_BAUD_RATE)
}
pub fn send_frame(&mut self, frame: &Frame) -> Result<()> {
let encoded = frame.encode();
self.port.write_all(&encoded)?;
self.port.flush()?;
Ok(())
}
pub fn recv_response(&mut self, timeout: Duration) -> Result<Vec<u8>> {
const STX: u8 = 0xA5;
let start = Instant::now();
let mut buffer = Vec::new();
while start.elapsed() < timeout {
let available = self.port.bytes_to_read()? as usize;
if available > 0 {
let mut buf = vec![0u8; available];
let n = self.port.read(&mut buf)?;
buffer.extend_from_slice(&buf[..n]);
if let Some(pos) = buffer.iter().position(|&b| b == STX) {
let mut response = vec![STX];
let mut remaining_start = pos + 1;
while response.len() < 4 && start.elapsed() < timeout {
if remaining_start < buffer.len() {
let to_copy =
std::cmp::min(4 - response.len(), buffer.len() - remaining_start);
response.extend_from_slice(
&buffer[remaining_start..remaining_start + to_copy],
);
remaining_start += to_copy;
} else {
let available = self.port.bytes_to_read()? as usize;
if available > 0 {
let mut buf = vec![0u8; available];
let n = self.port.read(&mut buf)?;
buffer.extend_from_slice(&buf[..n]);
} else {
std::thread::sleep(Duration::from_millis(20));
}
}
}
if response.len() >= 4 {
let payload_len = u16::from_le_bytes([response[1], response[2]]) as usize;
let total_frame_len = 1 + 2 + payload_len + 1;
while response.len() < total_frame_len && start.elapsed() < timeout {
if remaining_start < buffer.len() {
let to_copy = std::cmp::min(
total_frame_len - response.len(),
buffer.len() - remaining_start,
);
response.extend_from_slice(
&buffer[remaining_start..remaining_start + to_copy],
);
remaining_start += to_copy;
} else {
let available = self.port.bytes_to_read()? as usize;
if available > 0 {
let mut buf = vec![0u8; available];
let n = self.port.read(&mut buf)?;
buffer.extend_from_slice(&buf[..n]);
} else {
std::thread::sleep(Duration::from_millis(20));
}
}
}
if response.len() == total_frame_len {
return Ok(response);
}
}
}
}
std::thread::sleep(Duration::from_millis(20));
}
Err(V4Error::Timeout)
}
pub fn send_command(
&mut self,
command: Command,
payload: &[u8],
timeout: Duration,
) -> Result<Response> {
let frame = Frame::new(command, payload.to_vec())?;
self.send_frame(&frame)?;
let response = self.recv_response(timeout)?;
Frame::decode_response(&response)
}
pub fn ping(&mut self, timeout: Duration) -> Result<ErrorCode> {
Ok(self.send_command(Command::Ping, &[], timeout)?.error_code)
}
pub fn reset(&mut self, timeout: Duration) -> Result<ErrorCode> {
Ok(self.send_command(Command::Reset, &[], timeout)?.error_code)
}
pub fn exec(&mut self, bytecode: &[u8], timeout: Duration) -> Result<Response> {
self.send_command(Command::Exec, bytecode, timeout)
}
pub fn query_stack(&mut self, timeout: Duration) -> Result<Response> {
self.send_command(Command::QueryStack, &[], timeout)
}
pub fn query_memory(&mut self, addr: u32, len: u16, timeout: Duration) -> Result<Response> {
let mut payload = Vec::with_capacity(6);
payload.extend_from_slice(&addr.to_le_bytes());
payload.extend_from_slice(&len.to_le_bytes());
self.send_command(Command::QueryMemory, &payload, timeout)
}
pub fn query_word(&mut self, word_idx: u16, timeout: Duration) -> Result<Response> {
let payload = word_idx.to_le_bytes();
self.send_command(Command::QueryWord, &payload, timeout)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_baud_rate() {
assert_eq!(DEFAULT_BAUD_RATE, 115200);
}
}