use serial2::SerialPort;
use std::path::Path;
use std::time::{Duration, Instant};
use crate::bytestuff;
use crate::checksum::calculate_checksum;
use crate::endian::{read_u16_le, read_u32_le, read_u8_le, write_u16_le};
use crate::{ReadError, TransferError, WriteError};
const HEADER_PREFIX: [u8; 4] = [0xFF, 0xFF, 0xFD, 0x00];
const HEADER_SIZE: usize = 8;
const STATUS_HEADER_SIZE: usize = 9;
pub struct Bus<ReadBuffer, WriteBuffer> {
serial_port: SerialPort,
read_timeout: Duration,
read_buffer: ReadBuffer,
read_len: usize,
write_buffer: WriteBuffer,
}
impl Bus<Vec<u8>, Vec<u8>> {
pub fn open(path: impl AsRef<Path>, baud_rate: u32, read_timeout: Duration) -> std::io::Result<Self> {
let port = SerialPort::open(path, baud_rate)?;
Ok(Self::new(port, read_timeout))
}
pub fn new(stream: SerialPort, read_timeout: Duration) -> Self {
Self::with_buffers(stream, read_timeout, vec![0; 128], vec![0; 128])
}
}
impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
pub fn open_with_buffers(
path: impl AsRef<Path>,
baud_rate: u32,
read_timeout: Duration,
read_buffer: ReadBuffer,
write_buffer: WriteBuffer,
) -> std::io::Result<Self> {
let port = SerialPort::open(path, baud_rate)?;
Ok(Self::with_buffers(port, read_timeout, read_buffer, write_buffer))
}
pub fn with_buffers(serial_port: SerialPort, read_timeout: Duration, read_buffer: ReadBuffer, mut write_buffer: WriteBuffer) -> Self {
assert!(write_buffer.as_mut().len() >= HEADER_SIZE + 2);
write_buffer.as_mut()[..4].copy_from_slice(&HEADER_PREFIX);
Self {
serial_port,
read_timeout,
read_buffer,
read_len: 0,
write_buffer,
}
}
pub fn transfer_single<F>(
&mut self,
packet_id: u8,
instruction_id: u8,
parameter_count: usize,
encode_parameters: F,
) -> Result<StatusPacket<ReadBuffer, WriteBuffer>, TransferError>
where
F: FnOnce(&mut [u8]),
{
self.write_instruction(packet_id, instruction_id, parameter_count, encode_parameters)?;
let response = self.read_status_response()?;
crate::error::InvalidPacketId::check(response.packet_id(), packet_id).map_err(crate::ReadError::from)?;
Ok(response)
}
pub fn write_instruction<F>(
&mut self,
packet_id: u8,
instruction_id: u8,
parameter_count: usize,
encode_parameters: F,
) -> Result<(), WriteError>
where
F: FnOnce(&mut [u8]),
{
self.read_len = 0;
let buffer = self.write_buffer.as_mut();
if buffer.len() < HEADER_SIZE + parameter_count + 2 {
panic!("write buffer not large enough for outgoing mesage");
}
buffer[4] = packet_id;
buffer[5] = 0;
buffer[6] = 0;
buffer[7] = instruction_id;
encode_parameters(&mut buffer[HEADER_SIZE..][..parameter_count]);
let stuffed_body_len = bytestuff::stuff_inplace(&mut buffer[HEADER_SIZE..], parameter_count).unwrap();
write_u16_le(&mut buffer[5..], stuffed_body_len as u16 + 3);
let checksum_index = HEADER_SIZE + stuffed_body_len;
let checksum = calculate_checksum(0, &buffer[..checksum_index]);
write_u16_le(&mut buffer[checksum_index..], checksum);
let stuffed_message = &buffer[..checksum_index + 2];
trace!("sending instruction: {:02X?}", stuffed_message);
self.serial_port.discard_input_buffer().map_err(WriteError::DiscardBuffer)?;
self.serial_port.write_all(stuffed_message).map_err(WriteError::Write)?;
Ok(())
}
pub fn read_status_response(&mut self) -> Result<StatusPacket<ReadBuffer, WriteBuffer>, ReadError> {
let deadline = Instant::now() + self.read_timeout;
let stuffed_message_len = loop {
self.remove_garbage();
if self.read_len > STATUS_HEADER_SIZE {
let read_buffer = &self.read_buffer.as_mut()[..self.read_len];
let body_len = read_buffer[5] as usize + read_buffer[6] as usize * 256;
let body_len = body_len - 2; if self.read_len >= STATUS_HEADER_SIZE + body_len {
break STATUS_HEADER_SIZE + body_len;
}
}
if Instant::now() > deadline {
trace!(
"timeout reading status response, data in buffer: {:02X?}",
&self.read_buffer.as_ref()[..self.read_len]
);
return Err(std::io::ErrorKind::TimedOut.into());
}
let new_data = self.serial_port.read(&mut self.read_buffer.as_mut()[self.read_len..])?;
if new_data == 0 {
continue;
}
self.read_len += new_data;
};
let buffer = self.read_buffer.as_mut();
let parameters_end = stuffed_message_len - 2;
trace!("read packet: {:02X?}", &buffer[..parameters_end]);
let checksum_message = read_u16_le(&buffer[parameters_end..]);
let checksum_computed = calculate_checksum(0, &buffer[..parameters_end]);
if checksum_message != checksum_computed {
self.consume_read_bytes(stuffed_message_len);
return Err(crate::InvalidChecksum {
message: checksum_message,
computed: checksum_computed,
}
.into());
}
let parameter_count = bytestuff::unstuff_inplace(&mut buffer[STATUS_HEADER_SIZE..parameters_end]);
let response = StatusPacket {
bus: self,
stuffed_message_len,
parameter_count,
};
crate::InvalidInstruction::check(response.instruction_id(), crate::instructions::instruction_id::STATUS)?;
crate::MotorError::check(response.error())?;
Ok(response)
}
}
impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
fn remove_garbage(&mut self) {
let read_buffer = self.read_buffer.as_mut();
let garbage_len = find_header(&read_buffer[..self.read_len]);
if garbage_len > 0 {
debug!("skipping {} bytes of leading garbage.", garbage_len);
trace!("skipped garbage: {:02X?}", &read_buffer[..garbage_len]);
}
self.consume_read_bytes(garbage_len);
}
fn consume_read_bytes(&mut self, len: usize) {
debug_assert!(len <= self.read_len);
self.read_buffer.as_mut().copy_within(len..self.read_len, 0);
self.read_len -= len;
}
}
pub struct StatusPacket<'a, ReadBuffer, WriteBuffer>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
bus: &'a mut Bus<ReadBuffer, WriteBuffer>,
stuffed_message_len: usize,
parameter_count: usize,
}
impl<'a, ReadBuffer, WriteBuffer> StatusPacket<'a, ReadBuffer, WriteBuffer>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
pub fn as_bytes(&self) -> &[u8] {
&self.bus.read_buffer.as_ref()[..STATUS_HEADER_SIZE + self.parameter_count]
}
pub fn packet_id(&self) -> u8 {
self.as_bytes()[4]
}
pub fn instruction_id(&self) -> u8 {
self.as_bytes()[7]
}
pub fn error(&self) -> u8 {
self.as_bytes()[8]
}
pub fn error_number(&self) -> u8 {
self.error() & !0x80
}
pub fn alert(&self) -> bool {
self.error() & 0x80 != 0
}
pub fn parameters(&self) -> &[u8] {
&self.as_bytes()[STATUS_HEADER_SIZE..][..self.parameter_count]
}
}
impl<'a, ReadBuffer, WriteBuffer> Drop for StatusPacket<'a, ReadBuffer, WriteBuffer>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
fn drop(&mut self) {
self.bus.consume_read_bytes(self.stuffed_message_len);
}
}
fn find_header(buffer: &[u8]) -> usize {
for i in 0..buffer.len() {
let possible_prefix = HEADER_PREFIX.len().min(buffer.len() - i);
if buffer[i..].starts_with(&HEADER_PREFIX[..possible_prefix]) {
return i;
}
}
buffer.len()
}
#[derive(Debug)]
pub struct Response<T> {
pub motor_id: u8,
pub alert: bool,
pub data: T,
}
impl<'a, ReadBuffer, WriteBuffer> TryFrom<StatusPacket<'a, ReadBuffer, WriteBuffer>> for Response<()>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
type Error = crate::InvalidParameterCount;
fn try_from(status_packet: StatusPacket<'a, ReadBuffer, WriteBuffer>) -> Result<Self, Self::Error> {
crate::InvalidParameterCount::check(status_packet.parameter_count, 0)?;
Ok(Self {
motor_id: status_packet.packet_id(),
alert: status_packet.alert(),
data: (),
})
}
}
impl<'a, 'b, ReadBuffer, WriteBuffer> From<&'b StatusPacket<'a, ReadBuffer, WriteBuffer>> for Response<&'b [u8]>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
fn from(status_packet: &'b StatusPacket<'a, ReadBuffer, WriteBuffer>) -> Self {
Self {
motor_id: status_packet.packet_id(),
alert: status_packet.alert(),
data: status_packet.parameters(),
}
}
}
impl<'a, ReadBuffer, WriteBuffer> From<StatusPacket<'a, ReadBuffer, WriteBuffer>> for Response<Vec<u8>>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
fn from(status_packet: StatusPacket<'a, ReadBuffer, WriteBuffer>) -> Self {
Self {
motor_id: status_packet.packet_id(),
alert: status_packet.alert(),
data: status_packet.parameters().to_owned(),
}
}
}
impl<'a, ReadBuffer, WriteBuffer> TryFrom<StatusPacket<'a, ReadBuffer, WriteBuffer>> for Response<u8>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
type Error = crate::InvalidParameterCount;
fn try_from(status_packet: StatusPacket<'a, ReadBuffer, WriteBuffer>) -> Result<Self, Self::Error> {
crate::InvalidParameterCount::check(status_packet.parameter_count, 1)?;
Ok(Self {
motor_id: status_packet.packet_id(),
alert: status_packet.alert(),
data: read_u8_le(status_packet.parameters()),
})
}
}
impl<'a, ReadBuffer, WriteBuffer> TryFrom<StatusPacket<'a, ReadBuffer, WriteBuffer>> for Response<u16>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
type Error = crate::InvalidParameterCount;
fn try_from(status_packet: StatusPacket<'a, ReadBuffer, WriteBuffer>) -> Result<Self, Self::Error> {
crate::InvalidParameterCount::check(status_packet.parameter_count, 2)?;
Ok(Self {
motor_id: status_packet.packet_id(),
alert: status_packet.alert(),
data: read_u16_le(status_packet.parameters()),
})
}
}
impl<'a, ReadBuffer, WriteBuffer> TryFrom<StatusPacket<'a, ReadBuffer, WriteBuffer>> for Response<u32>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
{
type Error = crate::InvalidParameterCount;
fn try_from(status_packet: StatusPacket<'a, ReadBuffer, WriteBuffer>) -> Result<Self, Self::Error> {
crate::InvalidParameterCount::check(status_packet.parameter_count, 4)?;
Ok(Self {
motor_id: status_packet.packet_id(),
alert: status_packet.alert(),
data: read_u32_le(status_packet.parameters()),
})
}
}
#[cfg(test)]
mod test {
use super::*;
use assert2::assert;
#[test]
fn test_find_garbage_end() {
assert!(find_header(&[0xFF]) == 0);
assert!(find_header(&[0xFF, 0xFF]) == 0);
assert!(find_header(&[0xFF, 0xFF, 0xFD]) == 0);
assert!(find_header(&[0xFF, 0xFF, 0xFD, 0x00]) == 0);
assert!(find_header(&[0xFF, 0xFF, 0xFD, 0x00, 9]) == 0);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF]) == 5);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF]) == 5);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF, 0xFD]) == 5);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF, 0xFD, 0x00]) == 5);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 0xFF, 0xFD, 0x00, 9]) == 5);
assert!(find_header(&[0xFF, 1]) == 2);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 6]) == 7);
}
}