#![allow(clippy::unwrap_used, clippy::expect_used)]
use std::time::{Duration, Instant};
use m0601::error::Result;
use m0601::{Bus, DEFAULT_MIN_GAP, Transport};
const TIMEOUT: Duration = Duration::from_millis(150);
#[derive(Default)]
struct TimedTransport {
sent_at: Vec<Instant>,
}
impl Transport for TimedTransport {
fn send(&mut self, _data: &[u8]) -> Result<()> {
self.sent_at.push(Instant::now());
Ok(())
}
fn send_recv(&mut self, _data: &[u8], _wait: Duration) -> Result<Vec<u8>> {
self.sent_at.push(Instant::now());
Ok(Vec::new())
}
}
fn deltas(sent_at: &[Instant]) -> Vec<Duration> {
sent_at.windows(2).map(|w| w[1] - w[0]).collect()
}
#[test]
fn consecutive_frames_are_never_closer_than_min_gap() {
let gap = Duration::from_millis(20);
let bus = Bus::with_transport(TimedTransport::default(), TIMEOUT).with_min_gap(gap);
let mut motor = bus.motor(0x01).unwrap();
drop(bus);
for _ in 0..3 {
motor.drive_velocity(100).unwrap();
}
let t = motor.into_transport().expect("sole handle");
assert_eq!(t.sent_at.len(), 3);
for (i, d) in deltas(&t.sent_at).into_iter().enumerate() {
assert!(d >= gap, "frames {i}/{} only {d:?} apart", i + 1);
}
}
#[test]
fn zero_min_gap_restores_back_to_back_sends() {
let start = Instant::now();
let bus = Bus::with_transport(TimedTransport::default(), TIMEOUT).with_min_gap(Duration::ZERO);
let mut motor = bus.motor(0x01).unwrap();
drop(bus);
for _ in 0..20 {
motor.drive_velocity(100).unwrap();
}
assert!(
start.elapsed() < Duration::from_millis(40),
"opting out must not leave any enforced spacing behind (took {:?})",
start.elapsed()
);
}
#[test]
fn the_gap_holds_across_threads_and_cloned_handles() {
let gap = Duration::from_millis(10);
let bus = Bus::with_transport(TimedTransport::default(), TIMEOUT).with_min_gap(gap);
let mut left = bus.motor(0x01).unwrap();
let mut right = bus.motor(0x02).unwrap();
drop(bus);
std::thread::scope(|s| {
s.spawn(|| {
for _ in 0..4 {
left.drive_velocity(100).unwrap();
}
});
s.spawn(|| {
for _ in 0..4 {
right.drive_velocity(100).unwrap();
}
});
});
drop(right);
let t = left.into_transport().expect("last handle");
assert_eq!(t.sent_at.len(), 8);
for (i, d) in deltas(&t.sent_at).into_iter().enumerate() {
assert!(d >= gap, "frames {i}/{} only {d:?} apart", i + 1);
}
}
#[test]
fn a_four_motor_stop_round_fits_its_20_ms_budget() {
let bits_per_frame = m0601::protocol::FRAME_LEN as u64 * 10; let frame_time =
Duration::from_micros(bits_per_frame * 1_000_000 / u64::from(m0601::protocol::BAUD));
let round = 4 * (frame_time + DEFAULT_MIN_GAP);
assert!(
round <= Duration::from_millis(20) * 3 / 4,
"4 x (frame {frame_time:?} + gap {DEFAULT_MIN_GAP:?}) = {round:?} \
leaves too little of the 20 ms round for jitter"
);
}