Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove generic arguments for read and write buffer. #21

Merged
merged 3 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions dynamixel2-cli/src/bin/dynamixel2/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::path::Path;
use std::time::{Duration, Instant};
use dynamixel2::serial2::SerialPort;

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

fn open_client(options: &Options) -> Result<dynamixel2::Client<Vec<u8>, Vec<u8>, SerialPort>, ()> {
fn open_client(options: &Options) -> Result<dynamixel2::Client, ()> {
let client = dynamixel2::Client::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!("{:#?}", client);
Ok(client)
}

Expand Down
150 changes: 120 additions & 30 deletions src/bus/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Low level interface to a DYNAMIXEL Protocol 2.0 bus.

use crate::{checksum, ReadError, SerialPort, WriteError};
use crate::{checksum, ReadError, WriteError};
use core::time::Duration;

pub(crate) mod bytestuff;
Expand All @@ -21,19 +21,75 @@
/// Excludes the instruction ID, the error field of status packets, the parameters and the CRC.
const HEADER_SIZE: usize = 7;

/// Default buffer type.
///
/// Defaults to [`Vec<u8>`] if the `"alloc"` or `"std"` feature is enabled.
/// Otherwise, defaults to `&'mut static [u8]`.
#[cfg(feature = "alloc")]
pub type DefaultBuffer = alloc::vec::Vec<u8>;

/// Default buffer type.
///
/// Defaults to [`Vec<u8>`] if the `"alloc"` or `"std"` feature is enabled.
/// Otherwise, defaults to `&'mut static [u8]`.
#[cfg(not(feature = "alloc"))]
pub type DefaultBuffer = &'static mut [u8];

/// Create a mutable static buffer of size N.
///
/// This macro returns a `&'mut static [u8]`.
/// Each occurence of the macro may only be evaluated once,
/// any subsequent invocation will panic.
///
/// The macro is usable as expression in const context.
///
/// # Usage:
/// ```no_run
/// # fn main() -> Result<(), std::io::Error> {
/// # let serial_port = serial2::SerialPort::open("/dev/null", 57600)?;
/// use dynamixel2::{Client, static_buffer};
/// let client = Client::with_buffers(serial_port, static_buffer!(128), static_buffer!(64))?;
/// # Ok(())
/// # }
/// ```
#[macro_export]
macro_rules! static_buffer {
($N:literal) => {
{
use ::core::sync::atomic::{AtomicBool, Ordering};
static USED: AtomicBool = AtomicBool::new(false);
static mut BUFFER: [u8; $N] = [0; $N];
if USED.swap(true, Ordering::Relaxed) {
panic!("static buffer already used, each occurence of `static_buffer!()` may only be evaluated once");
}
unsafe {
// Use raw pointer to avoid compiler warning.
let buffer = &raw mut BUFFER;
// Convert to reference in separate expression to avoid clippy warning.
let buffer = &mut *buffer;
buffer.as_mut_slice()
}
}
};
}

/// Low level interface to a DYNAMIXEL Protocol 2.0 bus.
///
/// Does not assume anything about the direction of communication.
/// Used by [`crate::Client`] and [`crate::Device`].
pub(crate) struct Bus<ReadBuffer, WriteBuffer, T> {
pub(crate) struct Bus<SerialPort, Buffer>
where
SerialPort: crate::SerialPort,
Buffer: AsRef<[u8]> + AsMut<[u8]>,
{
/// The underlying stream (normally a serial port).
pub(crate) serial_port: T,
pub(crate) serial_port: SerialPort,

/// The baud rate of the serial port, if known.
pub(crate) baud_rate: u32,

/// The buffer for reading incoming messages.
pub(crate) read_buffer: ReadBuffer,
pub(crate) read_buffer: Buffer,

/// The total number of valid bytes in the read buffer.
pub(crate) read_len: usize,
Expand All @@ -42,43 +98,43 @@
pub(crate) used_bytes: usize,

/// The buffer for outgoing messages.
pub(crate) write_buffer: WriteBuffer,
pub(crate) write_buffer: Buffer,
}

impl<ReadBuffer, WriteBuffer, T> Bus<ReadBuffer, WriteBuffer, T>
impl<SerialPort, Buffer> Bus<SerialPort, Buffer>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
T: SerialPort,
SerialPort: crate::SerialPort,
Buffer: AsRef<[u8]> + AsMut<[u8]>,
{
/// 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: impl Into<T>,
read_buffer: ReadBuffer,
write_buffer: WriteBuffer,
) -> Result<Self, T::Error> {
let serial_port = serial_port.into();
serial_port: SerialPort,
read_buffer: Buffer,
write_buffer: Buffer,
) -> Result<Self, SerialPort::Error> {
let baud_rate = serial_port.baud_rate()?;
Ok(Self::with_buffers_and_baud_rate(serial_port, read_buffer, write_buffer, baud_rate))
}

/// Create a new bus using pre-allocated buffers.
pub fn with_buffers_and_baud_rate(
serial_port: impl Into<T>,
read_buffer: ReadBuffer,
mut write_buffer: WriteBuffer,
serial_port: SerialPort,
read_buffer: Buffer,
write_buffer: Buffer,
baud_rate: u32,
) -> Self {
let mut write_buffer = write_buffer;

// Pre-fill write buffer with the header prefix.
// TODO: return Err instead of panicking.
assert!(write_buffer.as_mut().len() >= HEADER_SIZE + 3);
write_buffer.as_mut()[..4].copy_from_slice(&HEADER_PREFIX);

Self {
serial_port: serial_port.into(),
serial_port,
baud_rate,
read_buffer,
read_len: 0,
Expand All @@ -88,7 +144,7 @@
}

/// Set the baud rate of the underlying serial port.
pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), T::Error> {
pub fn set_baud_rate(&mut self, baud_rate: u32) -> Result<(), SerialPort::Error> {
self.serial_port.set_baud_rate(baud_rate)?;
self.baud_rate = baud_rate;
Ok(())
Expand All @@ -101,7 +157,7 @@
error: u8,
parameter_count: usize,
encode_parameters: F,
) -> Result<(), WriteError<T::Error>>
) -> Result<(), WriteError<SerialPort::Error>>
where
F: FnOnce(&mut [u8]),
{
Expand All @@ -119,7 +175,7 @@
instruction_id: u8,
parameter_count: usize,
encode_parameters: F,
) -> Result<(), WriteError<T::Error>>
) -> Result<(), WriteError<SerialPort::Error>>
where
F: FnOnce(&mut [u8]),
{
Expand All @@ -133,7 +189,7 @@
instruction_id: u8,
parameter_count: usize,
encode_parameters: F,
) -> Result<(), WriteError<T::Error>>
) -> Result<(), WriteError<SerialPort::Error>>
where
F: FnOnce(&mut [u8]),
{
Expand Down Expand Up @@ -176,7 +232,11 @@
}

/// Read a raw packet from the bus with the given deadline.
pub fn read_packet_deadline(&mut self, deadline: T::Instant) -> Result<Packet<'_>, ReadError<T::Error>> {
pub fn read_packet_deadline(
&mut self,
deadline: SerialPort::Instant,
) -> Result<Packet<'_>, ReadError<SerialPort::Error>>
{
// Check that the read buffer is large enough to hold atleast a instruction packet with 0 parameters.
crate::error::BufferTooSmallError::check(HEADER_SIZE + 3, self.read_buffer.as_mut().len())?;

Expand Down Expand Up @@ -243,14 +303,7 @@

Ok(packet)
}
}

impl<ReadBuffer, WriteBuffer, T> Bus<ReadBuffer, WriteBuffer, T>
where
ReadBuffer: AsRef<[u8]> + AsMut<[u8]>,
WriteBuffer: AsRef<[u8]> + AsMut<[u8]>,
T: SerialPort,
{
/// Remove leading garbage data from the read buffer.
fn remove_garbage(&mut self) {
let read_buffer = self.read_buffer.as_mut();
Expand Down Expand Up @@ -372,4 +425,41 @@
assert!(find_header(&[0xFF, 1]) == 2);
assert!(find_header(&[0, 1, 2, 3, 4, 0xFF, 6]) == 7);
}

#[test]
fn test_static_buffer() {
let buffer1 = static_buffer!(128);
assert!(buffer1.len() == 128);
for i in 0..buffer1.len() {

Check warning on line 433 in src/bus/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

the loop variable `i` is only used to index `buffer1`

warning: the loop variable `i` is only used to index `buffer1` --> src/bus/mod.rs:433:12 | 433 | for i in 0..buffer1.len() { | ^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop = note: `#[warn(clippy::needless_range_loop)]` on by default help: consider using an iterator | 433 | for <item> in &buffer1 { | ~~~~~~ ~~~~~~~~
assert!(buffer1[i] == 0);
}

let buffer2 = static_buffer!(64);
assert!(buffer2.len() == 64);
for i in 0..buffer2.len() {

Check warning on line 439 in src/bus/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

the loop variable `i` is only used to index `buffer2`

warning: the loop variable `i` is only used to index `buffer2` --> src/bus/mod.rs:439:12 | 439 | for i in 0..buffer2.len() { | ^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop help: consider using an iterator | 439 | for <item> in &buffer2 { | ~~~~~~ ~~~~~~~~
assert!(buffer2[i] == 0);
}
}

#[test]
#[should_panic]
fn test_static_buffer_panics_when_evaluated_in_loop() {
for _ in 0..2 {
let buffer = static_buffer!(128);
assert!(buffer.len() == 128);
}
}

#[test]
#[should_panic]
fn test_static_buffer_panics_when_evaluated_in_loop_through_function() {
fn make_buffer() -> &'static mut [u8] {
static_buffer!(128)
}

for _ in 0..2 {
let buffer = make_buffer();
assert!(buffer.len() == 128);
}
}
}
Loading
Loading