Skip to content

Commit

Permalink
Merge pull request #16 from omelia-iliffe/generic_transport
Browse files Browse the repository at this point in the history
Moved to generic transports and no_std
  • Loading branch information
de-vri-es authored Sep 13, 2024
2 parents efa3b50 + 04d8f2b commit fc6054a
Show file tree
Hide file tree
Showing 21 changed files with 450 additions and 246 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Unreleased
- [major][add] Added `Transport` trait. Used to abstract the serial port.
- [major][add] Added `Serial2Port` struct, implementing the `Transport` trait for the `serial2` crate.
- [major][add] Added `std` and `serial2` as default features
- [major][add] Added `alloc` as a feature.
- [major][add] Added feature gates for `std`, `alloc` and `serial2`.
- [major][change] Changed `Bus` to be generic over `Transport`
- [major][change] Changed errors types to be generic over `Transport::Error`
- [major][change] Moved `crate::serial2` re-export to `crate::transport::serial2::serial2`.
- [minor][change] Changed `Bus::read_status_response_deadline()` to `Bus::read_status_response_timeout()`, which takes a `Duration` instead of an `Instant`.

# Version 0.9.1 - 2024-07-31
- [minor][add] Add missing `Error` impl for `InitializeError`.

Expand Down
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ publish = ["crates-io"]

[dependencies]
log = { version = "0.4.8", optional = true }
serial2 = "0.2.24"
serial2 = { version = "0.2.24", optional = true }

[dev-dependencies]
assert2 = "0.3.3"
env_logger = "0.11.5"

[features]
default = ["std", "serial2"]
alloc = []
std = []
rs4xx = ["serial2/rs4xx"]

[workspace]
Expand Down
5 changes: 3 additions & 2 deletions dynamixel2-cli/src/bin/dynamixel2/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::path::Path;
use std::time::{Duration, Instant};
use dynamixel2::transport::serial2::Serial2Port;

mod logging;
mod options;
Expand Down Expand Up @@ -151,15 +152,15 @@ fn do_main(options: Options) -> Result<(), ()> {
Ok(())
}

fn open_bus(options: &Options) -> Result<dynamixel2::Bus<Vec<u8>, Vec<u8>>, ()> {
fn open_bus(options: &Options) -> Result<dynamixel2::Bus<Vec<u8>, Vec<u8>, Serial2Port>, ()> {
let bus = dynamixel2::Bus::open(&options.serial_port, options.baud_rate)
.map_err(|e| log::error!("Failed to open serial port: {}: {}", options.serial_port.display(), e))?;
log::debug!(
"Using serial port {} with baud rate {}",
options.serial_port.display(),
options.baud_rate
);
log::trace!("{:#?}", bus);
// log::trace!("{:#?}", bus);
Ok(bus)
}

Expand Down
143 changes: 66 additions & 77 deletions src/bus.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use serial2::SerialPort;
use std::path::Path;
use std::time::{Duration, Instant};

use core::time::Duration;
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::transport::Transport;
use crate::{ReadError, TransferError, WriteError};

#[cfg(feature = "serial2")]
use std::path::Path;

#[cfg(feature = "alloc")]
use alloc::{vec::Vec, borrow::ToOwned};

const HEADER_PREFIX: [u8; 4] = [0xFF, 0xFF, 0xFD, 0x00];
const HEADER_SIZE: usize = 8;
const STATUS_HEADER_SIZE: usize = 9;

/// Dynamixel Protocol 2 communication bus.
pub struct Bus<ReadBuffer, WriteBuffer> {
pub struct Bus<ReadBuffer, WriteBuffer, T: Transport> {
/// The underlying stream (normally a serial port).
serial_port: SerialPort,
transport: T,

/// The baud rate of the serial port, if known.
baud_rate: u32,
Expand All @@ -31,43 +35,28 @@ pub struct Bus<ReadBuffer, WriteBuffer> {
/// The buffer for outgoing messages.
write_buffer: WriteBuffer,
}

impl<ReadBuffer, WriteBuffer> std::fmt::Debug for Bus<ReadBuffer, WriteBuffer> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[derive(Debug)]
#[allow(dead_code)] // Dead code analysis ignores derive debug impls, but that is the whole point of this struct.
enum Raw {
#[cfg(unix)]
Fd(std::os::unix::io::RawFd),
#[cfg(windows)]
Handle(std::os::windows::io::RawHandle),
}

#[cfg(unix)]
let raw = {
use std::os::unix::io::AsRawFd;
Raw::Fd(self.serial_port.as_raw_fd())
};
#[cfg(windows)]
let raw = {
use std::os::windows::io::AsRawHandle;
Raw::Handle(self.serial_port.as_raw_handle())
};

//
impl<ReadBuffer, WriteBuffer, T> core::fmt::Debug for Bus<ReadBuffer, WriteBuffer, T>
where
T: Transport + core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Bus")
.field("serial_port", &raw)
.field("transport", &self.transport)
.field("baud_rate", &self.baud_rate)
.finish_non_exhaustive()
}
}

impl Bus<Vec<u8>, Vec<u8>> {
#[cfg(feature = "serial2")]
impl Bus<Vec<u8>, Vec<u8>, crate::transport::serial2::Serial2Port> {
/// Open a serial port with the given baud rate.
///
/// This will allocate a new read and write buffer of 128 bytes each.
/// Use [`Self::open_with_buffers()`] if you want to use a custom buffers.
pub fn open(path: impl AsRef<Path>, baud_rate: u32) -> std::io::Result<Self> {
let port = SerialPort::open(path, baud_rate)?;
let port = serial2::SerialPort::open(path, baud_rate)?;

Ok(Self::with_buffers_and_baud_rate(port, vec![0; 128], vec![0; 128], baud_rate))
}

Expand All @@ -78,12 +67,13 @@ impl Bus<Vec<u8>, Vec<u8>> {
///
/// This will allocate a new read and write buffer of 128 bytes each.
/// Use [`Self::with_buffers()`] if you want to use a custom buffers.
pub fn new(serial_port: SerialPort) -> Result<Self, crate::InitializeError> {
Self::with_buffers(serial_port, vec![0; 128], vec![0; 128])
pub fn new(serial_port: serial2::SerialPort) -> Result<Self, crate::InitializeError<std::io::Error>> {
Self::with_buffers(serial_port.into(), vec![0; 128], vec![0; 128])
}
}

impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer>
#[cfg(feature = "serial2")]
impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer, crate::transport::serial2::Serial2Port>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
Expand All @@ -97,32 +87,41 @@ where
read_buffer: ReadBuffer,
write_buffer: WriteBuffer,
) -> std::io::Result<Self> {
let port = SerialPort::open(path, baud_rate)?;
let port = serial2::SerialPort::open(path, baud_rate)?;

Ok(Self::with_buffers_and_baud_rate(port, read_buffer, write_buffer, baud_rate))
}
}

impl<ReadBuffer, WriteBuffer, T> Bus<ReadBuffer, WriteBuffer, T>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
T: Transport,
{
/// Create a new bus using pre-allocated buffers.
///
/// The serial port must already be configured in raw mode with the correct baud rate,
/// character size (8), parity (disabled) and stop bits (1).
pub fn with_buffers(serial_port: SerialPort, read_buffer: ReadBuffer, write_buffer: WriteBuffer) -> Result<Self, crate::InitializeError> {
let baud_rate = serial_port.get_configuration()
.map_err(crate::InitializeError::GetConfiguration)?
.get_baud_rate()
.map_err(crate::InitializeError::GetBaudRate)?;
pub fn with_buffers(
transport: T,
read_buffer: ReadBuffer,
write_buffer: WriteBuffer,
) -> Result<Self, crate::InitializeError<T::Error>> {
let baud_rate = transport.baud_rate()?;

Ok(Self::with_buffers_and_baud_rate(serial_port, read_buffer, write_buffer, baud_rate))
Ok(Self::with_buffers_and_baud_rate(transport, read_buffer, write_buffer, baud_rate))
}

/// Create a new bus using pre-allocated buffers.
fn with_buffers_and_baud_rate(serial_port: SerialPort, read_buffer: ReadBuffer, mut write_buffer: WriteBuffer, baud_rate: u32) -> Self {
fn with_buffers_and_baud_rate(transport: impl Into<T>, read_buffer: ReadBuffer, mut write_buffer: WriteBuffer, baud_rate: u32) -> Self {
// Pre-fill write buffer with the header prefix.
// TODO: return Err instead of panicing.
// TODO: return Err instead of panicking.
assert!(write_buffer.as_mut().len() >= HEADER_SIZE + 2);
write_buffer.as_mut()[..4].copy_from_slice(&HEADER_PREFIX);

Self {
serial_port,
transport: transport.into(),
baud_rate,
read_buffer,
read_len: 0,
Expand All @@ -131,22 +130,22 @@ where
}
}

/// Get a reference to the underlying [`SerialPort`].
/// Get a reference to the underlying [`Transport`].
///
/// Note that performing any read or write with the [`SerialPort`] bypasses the read/write buffer of the bus,
/// Note that performing any read or write with the [`Transport`] bypasses the read/write buffer of the bus,
/// and may disrupt the communication with the motors.
/// In general, it should be safe to read and write to the bus manually in between instructions,
/// if the response from the motors has already been received.
pub fn serial_port(&self) -> &SerialPort {
&self.serial_port
pub fn transport(&self) -> &T {
&self.transport
}

/// Consume this bus object to get ownership of the serial port.
///
/// This discards any data in internal the read buffer of the bus object.
/// This is normally not a problem, since all data in the read buffer is also discarded when transmitting a new command.
pub fn into_serial_port(self) -> SerialPort {
self.serial_port
pub fn into_transport(self) -> T {
self.transport
}

/// Get the baud rate of the bus.
Expand All @@ -155,10 +154,8 @@ where
}

/// Set the baud rate of the underlying serial port.
pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), std::io::Error> {
let mut settings = self.serial_port.get_configuration()?;
settings.set_baud_rate(baud_rate)?;
self.serial_port.set_configuration(&settings)?;
pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), T::Error> {
self.transport.set_baud_rate(baud_rate)?;
self.baud_rate = baud_rate;
Ok(())
}
Expand All @@ -177,7 +174,7 @@ where
parameter_count: usize,
expected_response_parameters: u16,
encode_parameters: F,
) -> Result<StatusPacket<'_>, TransferError>
) -> Result<StatusPacket<'_>, TransferError<T::Error>>
where
F: FnOnce(&mut [u8]),
{
Expand All @@ -194,7 +191,7 @@ where
instruction_id: u8,
parameter_count: usize,
encode_parameters: F,
) -> Result<(), WriteError>
) -> Result<(), WriteError<T::Error>>
where
F: FnOnce(&mut [u8]),
{
Expand Down Expand Up @@ -226,20 +223,22 @@ where
// and read() can potentially read more than one reply per syscall.
self.read_len = 0;
self.used_bytes = 0;
self.serial_port.discard_input_buffer().map_err(WriteError::DiscardBuffer)?;
self.transport.discard_input_buffer().map_err(WriteError::DiscardBuffer)?;

// Send message.
let stuffed_message = &buffer[..checksum_index + 2];
trace!("sending instruction: {:02X?}", stuffed_message);
self.serial_port.write_all(stuffed_message).map_err(WriteError::Write)?;
self.transport.write_all(stuffed_message).map_err(WriteError::Write)?;
Ok(())
}

/// Read a raw status response from the bus with the given deadline.
pub fn read_status_response_deadline(&mut self, deadline: Instant) -> Result<StatusPacket, ReadError> {
pub fn read_status_response_timeout(&mut self, timeout: Duration) -> Result<StatusPacket, ReadError<T::Error>> {
// Check that the read buffer is large enough to hold atleast a status packet header.
crate::error::BufferTooSmallError::check(STATUS_HEADER_SIZE, self.read_buffer.as_mut().len())?;

self.transport.set_timeout(timeout).map_err(ReadError::Io)?;

let stuffed_message_len = loop {
self.remove_garbage();

Expand All @@ -259,20 +258,8 @@ where
}
}

let timeout = match deadline.checked_duration_since(Instant::now()) {
Some(x) => x,
None => {
trace!(
"timeout reading status response, data in buffer: {:02X?}",
&self.read_buffer.as_ref()[..self.read_len]
);
return Err(std::io::ErrorKind::TimedOut.into());
},
};

// Try to read more data into the buffer.
self.serial_port.set_read_timeout(timeout).ok();
let new_data = self.serial_port.read(&mut self.read_buffer.as_mut()[self.read_len..])?;
let new_data = self.transport.read(&mut self.read_buffer.as_mut()[self.read_len..])?;
if new_data == 0 {
continue;
}
Expand Down Expand Up @@ -314,11 +301,11 @@ where
/// Read a raw status response with an automatically calculated timeout.
///
/// The read timeout is determined by the expected number of response parameters and the baud rate of the bus.
pub fn read_status_response(&mut self, expected_parameters: u16) -> Result<StatusPacket, ReadError> {
// Offical SDK adds a flat 34 milliseconds, so lets just mimick that.
pub fn read_status_response(&mut self, expected_parameters: u16) -> Result<StatusPacket, ReadError<T::Error>> {
// Official SDK adds a flat 34 milliseconds, so lets just mimick that.
let message_size = STATUS_HEADER_SIZE as u32 + u32::from(expected_parameters) + 2;
let timeout = message_transfer_time(message_size, self.baud_rate) + Duration::from_millis(34);
self.read_status_response_deadline(Instant::now() + timeout)
self.read_status_response_timeout(timeout)
}
}

Expand All @@ -334,10 +321,11 @@ pub(crate) fn message_transfer_time(message_size: u32, baud_rate: u32) -> Durati
Duration::new(secs, nanos as u32)
}

impl<ReadBuffer, WriteBuffer> Bus<ReadBuffer, WriteBuffer>
impl<ReadBuffer, WriteBuffer, T> Bus<ReadBuffer, WriteBuffer, T>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
T: Transport,
{
/// Remove leading garbage data from the read buffer.
fn remove_garbage(&mut self) {
Expand Down Expand Up @@ -476,6 +464,7 @@ impl<'a, 'b> From<&'b StatusPacket<'a>> for Response<&'b [u8]> {
}
}

#[cfg(any(feature = "alloc", feature = "std"))]
impl<'a> From<StatusPacket<'a>> for Response<Vec<u8>> {
fn from(status_packet: StatusPacket<'a>) -> Self {
Self {
Expand Down
Loading

0 comments on commit fc6054a

Please sign in to comment.