mod motor;
mod pacing;
mod timing;
pub use motor::{M0601, PositionMirror};
pub use timing::{BusTiming, DEFAULT_DRIVE_ACCEL, DEFAULT_MIN_GAP, bus_period};
use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration;
use crate::error::Result;
use crate::protocol::{self, frame_feedback, frame_id_query, frame_set_id, frames};
use crate::transport::{SerialTransport, Transport};
use crate::types::Mode;
use pacing::{Port, lock, mode_all, peek_min_gap, stop_all, with_gap};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ScanReport {
pub ids: Vec<u8>,
pub garbled: bool,
}
pub struct Bus<T: Transport = SerialTransport> {
port: Arc<Mutex<Port<T>>>,
timeout: Duration,
strict_crc: bool,
default_accel: u8,
}
impl<T: Transport> Clone for Bus<T> {
fn clone(&self) -> Self {
Self {
port: Arc::clone(&self.port),
timeout: self.timeout,
strict_crc: self.strict_crc,
default_accel: self.default_accel,
}
}
}
impl<T: Transport> std::fmt::Debug for Bus<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Bus")
.field("timeout", &self.timeout)
.field("strict_crc", &self.strict_crc)
.field("default_accel", &self.default_accel)
.field("min_gap", &peek_min_gap(&self.port))
.finish_non_exhaustive()
}
}
impl Bus<SerialTransport> {
pub fn open(port: &str, timeout: Duration) -> Result<Self> {
Ok(Self::with_transport(
SerialTransport::open(port, timeout)?,
timeout,
))
}
}
impl<T: Transport> Bus<T> {
pub fn with_transport(transport: T, timeout: Duration) -> Self {
Self {
port: Arc::new(Mutex::new(Port {
transport,
last_tx: None,
timing: BusTiming::default(),
})),
timeout,
strict_crc: false,
default_accel: DEFAULT_DRIVE_ACCEL,
}
}
#[must_use]
pub fn with_min_gap(self, gap: Duration) -> Self {
lock(&self.port).timing.min_gap = gap;
self
}
pub fn min_gap(&self) -> Duration {
lock(&self.port).timing.min_gap
}
#[must_use]
pub fn with_timing(self, timing: BusTiming) -> Self {
lock(&self.port).timing = timing;
self
}
#[must_use]
pub fn with_stop_accel(self, accel: u8) -> Self {
lock(&self.port).timing.stop_accel = accel;
self
}
pub fn timing(&self) -> BusTiming {
lock(&self.port).timing
}
#[must_use]
pub fn with_strict_crc(mut self, strict: bool) -> Self {
self.strict_crc = strict;
self
}
pub fn strict_crc(&self) -> bool {
self.strict_crc
}
#[must_use]
pub fn with_default_accel(mut self, accel: u8) -> Self {
self.default_accel = accel;
self
}
pub fn default_accel(&self) -> u8 {
self.default_accel
}
pub fn motor(&self, id: u8) -> Result<M0601<T>> {
protocol::validate_id(id)?;
Ok(M0601 {
port: Arc::clone(&self.port),
id,
timeout: self.timeout,
mirrored: false,
position_mirror: PositionMirror::default(),
strict_crc: self.strict_crc,
default_accel: self.default_accel,
})
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn send_raw(&self, frame: &[u8], wait: Duration) -> Result<Vec<u8>> {
with_gap(&self.port, |t| t.send_recv(frame, wait))
}
pub fn scan(
&self,
poll: impl IntoIterator<Item = u8>,
mut progress: impl FnMut(u8),
) -> Result<ScanReport> {
let mut found = std::collections::BTreeSet::new();
let broadcast_wait = self.timing().broadcast_wait;
let query = frame_id_query();
let resp = with_gap(&self.port, |t| t.send_recv(&query, broadcast_wait))?;
let payload = protocol::strip_echo(&query, &resp);
let mut garbled = false;
match frames(&query, &resp) {
Some(chunks) => {
for chunk in chunks {
if (0x01..=0xFE).contains(&chunk[0]) {
found.insert(chunk[0]);
} else {
garbled = true;
}
}
}
None => garbled = !payload.is_empty(),
}
for id in poll {
if !(0x01..=0xFE).contains(&id) {
continue;
}
progress(id);
let probe = frame_feedback(id);
let resp = with_gap(&self.port, |t| t.send_recv(&probe, self.timeout))?;
if frames(&probe, &resp)
.into_iter()
.flatten()
.any(|frame| frame[0] == id)
{
found.insert(id);
}
}
Ok(ScanReport {
ids: found.into_iter().collect(),
garbled,
})
}
pub fn set_id(&self, new_id: u8) -> Result<Option<u8>> {
let timing = self.timing();
let frame = frame_set_id(new_id)?;
for _ in 0..5 {
self.send_paced(&frame, timing.set_id_repeat_gap)?;
}
self.pause(timing.set_id_settle);
let query = frame_id_query();
let resp = with_gap(&self.port, |t| t.send_recv(&query, timing.broadcast_wait))?;
Ok(frames(&query, &resp)
.into_iter()
.flatten()
.map(|frame| frame[0])
.find(|b| (0x01..=0xFE).contains(b)))
}
pub fn safe_stop_all(&self, ids: &[u8]) {
stop_all(&self.port, ids);
}
pub fn set_mode_all(&self, ids: &[u8], mode: Mode) -> Result<()> {
for &id in ids {
protocol::validate_id(id)?;
}
mode_all(&self.port, ids, mode)
}
pub fn into_transport(self) -> Option<T> {
Arc::into_inner(self.port).map(|m| {
m.into_inner()
.unwrap_or_else(PoisonError::into_inner)
.transport
})
}
fn send_paced(&self, frame: &[u8], gap: Duration) -> Result<()> {
with_gap(&self.port, |t| t.send(frame))?;
self.pause(gap);
Ok(())
}
fn pause(&self, d: Duration) {
let d = lock(&self.port).transport.pace(d);
if !d.is_zero() {
std::thread::sleep(d);
}
}
}