pub mod args;
pub mod async_transport;
pub mod convenience;
pub mod device;
pub mod discovery;
pub mod factory;
pub mod fdcanusb;
pub mod singleton;
pub mod socketcan;
pub(crate) mod socketcan_common;
pub mod transaction;
#[cfg(feature = "tokio")]
pub mod async_factory;
#[cfg(feature = "tokio")]
pub mod async_fdcanusb;
#[cfg(feature = "tokio")]
pub mod async_singleton;
#[cfg(feature = "tokio")]
pub mod async_socketcan;
use crate::device_address::DeviceAddress;
use crate::error::{Error, Result};
use crate::transport::device::{TransportDevice, TransportDeviceInfo};
use moteus_protocol::{CanFdFrame, Register, Resolution};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub use transaction::{dispatch_frame, FrameFilter, Request, ResponseCollector};
pub trait Transport {
fn cycle(&mut self, requests: &mut [Request]) -> Result<()>;
fn write(&mut self, frame: &CanFdFrame) -> Result<()>;
fn read(&mut self, channel: Option<usize>) -> Result<Option<CanFdFrame>>;
fn flush_read(&mut self, channel: Option<usize>) -> Result<()>;
fn set_timeout(&mut self, timeout: Duration);
fn timeout(&self) -> Duration;
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct DeviceInfo {
pub can_id: u8,
pub uuid: Option<[u8; 16]>,
pub transport_device_idx: usize,
pub transport_device: String,
pub address: Option<DeviceAddress>,
}
impl DeviceInfo {
pub fn new(can_id: u8, transport_device_idx: usize, transport_device: String) -> Self {
Self {
can_id,
uuid: None,
transport_device_idx,
transport_device,
address: Some(DeviceAddress::can_id(can_id)),
}
}
pub fn with_uuid(
can_id: u8,
uuid: [u8; 16],
transport_device_idx: usize,
transport_device: String,
) -> Self {
Self {
can_id,
uuid: Some(uuid),
transport_device_idx,
transport_device,
address: Some(DeviceAddress::can_id(can_id)),
}
}
pub fn uuid_string(&self) -> Option<String> {
self.uuid.map(|bytes| {
format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3],
bytes[4], bytes[5],
bytes[6], bytes[7],
bytes[8], bytes[9],
bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]
)
})
}
}
impl std::fmt::Display for DeviceInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let uuid_str = self.uuid_string().unwrap_or_else(|| "None".to_string());
write!(
f,
"DeviceInfo(can_id={}, uuid={}, td={})",
self.can_id, uuid_str, self.transport_device
)
}
}
pub(crate) fn make_uuid_query_frame(source: u8, can_prefix: u16) -> (CanFdFrame, u8) {
use moteus_protocol::{WriteCanData, WriteCombiner};
let mut frame = CanFdFrame::new();
frame.arbitration_id =
moteus_protocol::calculate_arbitration_id(source as i8, 0x7F_i8, can_prefix, true);
let mut writer = WriteCanData::new(&mut frame);
let resolutions = [
Resolution::Int32, Resolution::Int32, Resolution::Int32, Resolution::Int32, Resolution::Ignore, Resolution::Ignore, Resolution::Ignore, Resolution::Ignore, Resolution::Int8, ];
let mut combiner = WriteCombiner::new(
0x10, Register::Uuid1.address(),
&resolutions,
);
for _ in 0..resolutions.len() {
combiner.maybe_write(&mut writer);
}
let reply_size = combiner.reply_size();
(frame, reply_size)
}
pub fn make_uuid_prefix(uuid_prefix: &[u8]) -> Vec<u8> {
use moteus_protocol::{WriteCanData, WriteCombiner};
if uuid_prefix.is_empty() {
return Vec::new();
}
assert!(
uuid_prefix.len() % 4 == 0 && uuid_prefix.len() <= 16,
"UUID prefix must be a multiple of 4 bytes and at most 16 bytes"
);
let mut frame = CanFdFrame::new();
let mut writer = WriteCanData::new(&mut frame);
let reg_count = uuid_prefix.len() / 4;
let resolutions = [Resolution::Int32; 4];
let mut combiner = WriteCombiner::new(
0x00, Register::UuidMask1.address(),
&resolutions[..reg_count],
);
for i in 0..reg_count {
if combiner.maybe_write(&mut writer) {
let offset = i * 4;
let val = u32::from_le_bytes([
uuid_prefix[offset],
uuid_prefix[offset + 1],
uuid_prefix[offset + 2],
uuid_prefix[offset + 3],
]);
writer.write_i32(val as i32);
}
}
frame.data[..frame.size as usize].to_vec()
}
pub(crate) fn extract_uuid_from_response(frame: &CanFdFrame) -> Option<[u8; 16]> {
use moteus_protocol::{parse_frame, Subframe};
let data = &frame.data[..frame.size as usize];
let mut uuid_parts: [Option<i32>; 4] = [None; 4];
let mut uuid_mask_capable: Option<i8> = None;
for subframe in parse_frame(data) {
let (register, value) = match subframe {
Subframe::Register {
register,
value: Some(value),
..
} => (register, value),
_ => continue,
};
match register {
r if r == Register::Uuid1.address() => {
uuid_parts[0] = Some(value.to_i32());
}
r if r == Register::Uuid2.address() => {
uuid_parts[1] = Some(value.to_i32());
}
r if r == Register::Uuid3.address() => {
uuid_parts[2] = Some(value.to_i32());
}
r if r == Register::Uuid4.address() => {
uuid_parts[3] = Some(value.to_i32());
}
r if r == Register::UuidMaskCapable.address() => {
uuid_mask_capable = Some(value.to_i32() as i8);
}
_ => {}
}
}
match uuid_mask_capable {
None => return None,
Some(0) => return None,
_ => {}
}
if uuid_parts.iter().any(|p| p.is_none()) {
return None;
}
let mut uuid = [0u8; 16];
for (i, part) in uuid_parts.iter().enumerate() {
let val = part.unwrap() as u32;
uuid[i * 4] = (val & 0xFF) as u8;
uuid[i * 4 + 1] = ((val >> 8) & 0xFF) as u8;
uuid[i * 4 + 2] = ((val >> 16) & 0xFF) as u8;
uuid[i * 4 + 3] = ((val >> 24) & 0xFF) as u8;
}
Some(uuid)
}
pub(crate) fn resolve_addresses(devices: &mut [DeviceInfo]) {
for i in 0..devices.len() {
let can_id = devices[i].can_id;
let transport_idx = devices[i].transport_device_idx;
let can_id_conflicts: Vec<usize> = devices
.iter()
.enumerate()
.filter(|(j, d)| {
*j != i && d.can_id == can_id && d.transport_device_idx == transport_idx
})
.map(|(j, _)| j)
.collect();
if can_id_conflicts.is_empty() {
devices[i].address =
Some(DeviceAddress::can_id(can_id).with_transport_device(transport_idx));
} else if let Some(uuid) = devices[i].uuid {
for prefix_len in [4usize, 8, 12, 16] {
let prefix = &uuid[..prefix_len];
let has_conflict = devices.iter().enumerate().any(|(j, d)| {
if j == i {
return false;
}
if let Some(other_uuid) = d.uuid {
other_uuid[..prefix_len] == *prefix
} else {
false
}
});
if !has_conflict {
devices[i].address = Some(
DeviceAddress::uuid(prefix.to_vec()).with_transport_device(transport_idx),
);
break;
}
}
} else {
devices[i].address = None;
}
}
}
pub struct Router {
devices: Vec<Box<dyn TransportDevice>>,
parent_indices: Vec<usize>,
routing_table: HashMap<DeviceAddress, usize>,
timeout: Duration,
}
impl std::fmt::Debug for Router {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let device_infos: Vec<&TransportDeviceInfo> =
self.devices.iter().map(|d| d.info()).collect();
f.debug_struct("Router")
.field("device_count", &self.devices.len())
.field("devices", &device_infos)
.field("routes", &self.routing_table.len())
.field("timeout", &self.timeout)
.finish()
}
}
fn compute_parent_indices(devices: &[Box<dyn TransportDevice>]) -> Vec<usize> {
let mut indices: Vec<usize> = devices
.iter()
.enumerate()
.map(|(i, d)| d.info().parent_index.unwrap_or(i))
.collect();
indices.sort_unstable();
indices.dedup();
indices
}
impl Router {
pub fn from_device(device: impl TransportDevice + 'static) -> Self {
Self::new(vec![Box::new(device)])
}
pub fn new(devices: Vec<Box<dyn TransportDevice>>) -> Self {
let parent_indices = compute_parent_indices(&devices);
Self {
devices,
parent_indices,
routing_table: HashMap::new(),
timeout: factory::DEFAULT_TIMEOUT,
}
}
pub fn from_devices<I, T>(devices: I) -> Self
where
I: IntoIterator<Item = T>,
T: TransportDevice + 'static,
{
let boxed: Vec<Box<dyn TransportDevice>> = devices
.into_iter()
.map(|d| Box::new(d) as Box<dyn TransportDevice>)
.collect();
Self::new(boxed)
}
pub fn device_count(&self) -> usize {
self.devices.len()
}
pub fn device_info(&self) -> Vec<&TransportDeviceInfo> {
self.devices.iter().map(|d| d.info()).collect()
}
pub fn add_route(
&mut self,
address: impl Into<DeviceAddress>,
device_idx: usize,
) -> Result<()> {
if device_idx >= self.devices.len() {
return Err(Error::Protocol(format!(
"Device index {} out of range (have {} devices)",
device_idx,
self.devices.len()
)));
}
self.routing_table.insert(address.into(), device_idx);
Ok(())
}
pub fn clear_routes(&mut self) {
self.routing_table.clear();
}
fn get_device_for_address(&self, address: &DeviceAddress) -> Option<usize> {
if self.devices.len() == 1 {
return Some(0);
}
self.routing_table.get(address).copied()
}
fn discover_device(
&mut self,
address: &DeviceAddress,
source: u8,
can_prefix: u16,
) -> Result<usize> {
if self.devices.is_empty() {
return Err(Error::NotConnected);
}
if self.devices.len() == 1 {
return Ok(0);
}
let can_id = address.as_can_id().unwrap_or(0x7F);
let mut query_frame = CanFdFrame::new();
query_frame.arbitration_id =
moteus_protocol::calculate_arbitration_id(source as i8, can_id as i8, can_prefix, true);
if address.can_id.is_none() {
if let Some(uuid) = address.as_uuid() {
let prefix_data = make_uuid_prefix(uuid);
query_frame.data[..prefix_data.len()].copy_from_slice(&prefix_data);
query_frame.size = prefix_data.len() as u8;
}
}
let offset = query_frame.size as usize;
query_frame.data[offset] = 0x50; query_frame.size += 1;
for device in &mut self.devices {
let _ = device.flush();
}
let mut found: Vec<usize> = Vec::new();
for (idx, device) in self.devices.iter_mut().enumerate() {
if !device.empty_bus_tx_safe() {
continue;
}
let mut requests = vec![Request::new(query_frame.clone())];
if device.transaction(&mut requests).is_ok() {
for response in requests[0].responses.take() {
let source_id = ((response.arbitration_id >> 8) & 0x7F) as u8;
if source_id != 0x7F && !found.contains(&idx) {
found.push(idx);
break;
}
}
}
}
match found.len() {
0 => Err(Error::DeviceNotFound(format!(
"{} not found on any CAN bus",
address
))),
1 => {
self.routing_table.insert(address.clone(), found[0]);
Ok(found[0])
}
_ => Err(Error::Protocol(format!(
"More than one {} found across connected CAN busses",
address
))),
}
}
fn get_or_discover_device(
&mut self,
address: &DeviceAddress,
source: u8,
can_prefix: u16,
) -> Result<usize> {
if let Some(idx) = self.get_device_for_address(address) {
return Ok(idx);
}
self.discover_device(address, source, can_prefix)
}
pub fn discover(&mut self, can_prefix: u16, source: u8) -> Result<Vec<DeviceInfo>> {
if self.devices.is_empty() {
return Err(Error::NotConnected);
}
let _ = self.flush_read(None);
let (query_frame, _reply_size) = make_uuid_query_frame(source, can_prefix);
let mut discovered = Vec::new();
for (idx, device) in self.devices.iter_mut().enumerate() {
if !device.empty_bus_tx_safe() {
continue;
}
let transport_device = device.info().to_string();
let mut requests = vec![
Request::new(query_frame.clone())
.with_filter(FrameFilter::Any) .with_expected_replies(127), ];
if device.transaction(&mut requests).is_ok() {
for response in requests[0].responses.take() {
let source_id = ((response.arbitration_id >> 8) & 0x7F) as u8;
let dest_id = (response.arbitration_id & 0x7F) as u8;
if source_id == 0x7F || dest_id != source {
continue;
}
let uuid = extract_uuid_from_response(&response);
let device_info = if let Some(uuid) = uuid {
DeviceInfo::with_uuid(source_id, uuid, idx, transport_device.clone())
} else {
DeviceInfo::new(source_id, idx, transport_device.clone())
};
discovered.push(device_info);
}
}
}
resolve_addresses(&mut discovered);
discovered.sort_by(|a, b| match a.can_id.cmp(&b.can_id) {
std::cmp::Ordering::Equal => a.uuid.cmp(&b.uuid),
other => other,
});
Ok(discovered)
}
fn execute_cycle(&mut self, requests: &mut [Request]) -> Result<()> {
if self.devices.is_empty() {
return Err(Error::NotConnected);
}
if self.devices.len() == 1 {
let result = self.devices[0].transaction(requests);
for req in requests.iter() {
let frames = req.responses.take();
for mut frame in frames {
frame.channel = Some(0);
req.responses.push(frame);
}
}
return result;
}
let mut device_request_indices: HashMap<usize, Vec<(bool, usize)>> = HashMap::new();
#[allow(clippy::needless_range_loop, clippy::manual_map)]
for i in 0..requests.len() {
if let Some(device_idx) = requests[i].channel {
device_request_indices
.entry(device_idx)
.or_default()
.push((false, i));
continue;
}
let dest_id = requests[i]
.frame
.as_ref()
.map(|frame| (frame.arbitration_id & 0x7F) as u8);
if let Some(dest_id) = dest_id {
let is_true_broadcast = dest_id == 0x7F
&& !matches!(
&requests[i].address,
Some(addr) if addr.uuid.is_some()
);
if is_true_broadcast {
let is_bwr = requests[i].expected_reply_count > 0;
for &device_idx in &self.parent_indices.clone() {
if self.devices[device_idx].empty_bus_tx_safe() {
device_request_indices
.entry(device_idx)
.or_default()
.push((is_bwr, i));
}
}
} else {
let lookup_addr = match &requests[i].address {
Some(addr) => addr.clone(),
None => DeviceAddress::can_id(dest_id),
};
let (source, can_prefix) = match &requests[i].frame {
Some(f) => {
let (src, _dest, pfx) =
moteus_protocol::parse_arbitration_id(f.arbitration_id);
(src as u8, pfx)
}
None => (0, 0),
};
let mut target_idx =
self.get_or_discover_device(&lookup_addr, source, can_prefix)?;
if let Some(parent_idx) = self.devices[target_idx].info().parent_index {
requests[i].child_device = Some(target_idx);
target_idx = parent_idx;
}
device_request_indices
.entry(target_idx)
.or_default()
.push((false, i));
}
}
}
for (device_idx, items) in device_request_indices {
if let Some(device) = self.devices.get_mut(device_idx) {
let bwr_indices: Vec<usize> = items
.iter()
.filter(|(bwr, _)| *bwr)
.map(|(_, i)| *i)
.collect();
let other_indices: Vec<usize> = items
.iter()
.filter(|(bwr, _)| !*bwr)
.map(|(_, i)| *i)
.collect();
for &orig_idx in &bwr_indices {
let mut req = requests[orig_idx].clone();
req.responses = ResponseCollector::new();
let mut reqs = vec![req];
if device.transaction(&mut reqs).is_ok() {
for mut frame in reqs[0].responses.take() {
frame.channel = Some(device_idx);
requests[orig_idx].responses.push(frame);
}
}
}
if !other_indices.is_empty() {
let mut device_requests: Vec<Request> = other_indices
.iter()
.map(|&i| {
let mut req = requests[i].clone();
req.responses = ResponseCollector::new();
req
})
.collect();
device.transaction(&mut device_requests)?;
for (local_idx, &orig_idx) in other_indices.iter().enumerate() {
for mut frame in device_requests[local_idx].responses.take() {
frame.channel = Some(device_idx);
requests[orig_idx].responses.push(frame);
}
}
}
}
}
Ok(())
}
pub fn cycle(&mut self, requests: &mut [Request]) -> Result<()> {
self.execute_cycle(requests)
}
pub fn write(&mut self, frame: &CanFdFrame) -> Result<()> {
if self.devices.is_empty() {
return Err(Error::NotConnected);
}
let dest_id = (frame.arbitration_id & 0x7F) as u8;
if dest_id == 0x7F {
for device in &mut self.devices {
if device.empty_bus_tx_safe() {
device.write(frame)?;
}
}
Ok(())
} else {
let addr = DeviceAddress::can_id(dest_id);
let (src, _dest, pfx) = moteus_protocol::parse_arbitration_id(frame.arbitration_id);
let device_idx = self.get_or_discover_device(&addr, src as u8, pfx)?;
self.devices[device_idx].write(frame)
}
}
pub fn read(&mut self, channel: Option<usize>) -> Result<Option<CanFdFrame>> {
if self.devices.is_empty() {
return Err(Error::NotConnected);
}
if let Some(idx) = channel {
if let Some(device) = self.devices.get_mut(idx) {
match device.read()? {
Some(mut frame) => {
frame.channel = Some(idx);
Ok(Some(frame))
}
None => Ok(None),
}
} else {
Err(Error::DeviceNotFound(format!("Channel {} not found", idx)))
}
} else {
for &idx in &self.parent_indices {
if let Ok(Some(mut frame)) = self.devices[idx].read() {
frame.channel = Some(idx);
return Ok(Some(frame));
}
}
Ok(None)
}
}
pub fn flush_read(&mut self, channel: Option<usize>) -> Result<()> {
if self.devices.is_empty() {
return Err(Error::NotConnected);
}
if let Some(idx) = channel {
if let Some(device) = self.devices.get_mut(idx) {
device.flush()
} else {
Err(Error::DeviceNotFound(format!("Channel {} not found", idx)))
}
} else {
for &idx in &self.parent_indices {
self.devices[idx].flush()?;
}
Ok(())
}
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
for device in &mut self.devices {
device.set_timeout(timeout);
}
}
pub fn timeout(&self) -> Duration {
self.timeout
}
}
impl<D: TransportDevice + 'static> From<D> for Router {
fn from(device: D) -> Self {
Router::from_device(device)
}
}
impl Transport for Router {
fn cycle(&mut self, requests: &mut [Request]) -> Result<()> {
Router::cycle(self, requests)
}
fn write(&mut self, frame: &CanFdFrame) -> Result<()> {
Router::write(self, frame)
}
fn read(&mut self, channel: Option<usize>) -> Result<Option<CanFdFrame>> {
Router::read(self, channel)
}
fn flush_read(&mut self, channel: Option<usize>) -> Result<()> {
Router::flush_read(self, channel)
}
fn set_timeout(&mut self, timeout: Duration) {
Router::set_timeout(self, timeout)
}
fn timeout(&self) -> Duration {
Router::timeout(self)
}
}
#[derive(Debug)]
pub struct NullTransport {
timeout: Duration,
}
impl NullTransport {
pub fn new() -> Self {
NullTransport {
timeout: factory::DEFAULT_TIMEOUT,
}
}
}
impl Default for NullTransport {
fn default() -> Self {
Self::new()
}
}
impl Transport for NullTransport {
fn cycle(&mut self, _requests: &mut [Request]) -> Result<()> {
Ok(())
}
fn write(&mut self, _frame: &CanFdFrame) -> Result<()> {
Ok(())
}
fn read(&mut self, _channel: Option<usize>) -> Result<Option<CanFdFrame>> {
Ok(None)
}
fn flush_read(&mut self, _channel: Option<usize>) -> Result<()> {
Ok(())
}
fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
fn timeout(&self) -> Duration {
self.timeout
}
}
impl<T: Transport> Transport for Arc<Mutex<T>> {
fn cycle(&mut self, requests: &mut [Request]) -> Result<()> {
self.lock()
.map_err(|_| Error::Protocol("Transport mutex poisoned".to_string()))?
.cycle(requests)
}
fn write(&mut self, frame: &CanFdFrame) -> Result<()> {
self.lock()
.map_err(|_| Error::Protocol("Transport mutex poisoned".to_string()))?
.write(frame)
}
fn read(&mut self, channel: Option<usize>) -> Result<Option<CanFdFrame>> {
self.lock()
.map_err(|_| Error::Protocol("Transport mutex poisoned".to_string()))?
.read(channel)
}
fn flush_read(&mut self, channel: Option<usize>) -> Result<()> {
self.lock()
.map_err(|_| Error::Protocol("Transport mutex poisoned".to_string()))?
.flush_read(channel)
}
fn set_timeout(&mut self, timeout: Duration) {
if let Ok(mut guard) = self.lock() {
guard.set_timeout(timeout);
}
}
fn timeout(&self) -> Duration {
self.lock()
.map(|g| g.timeout())
.unwrap_or(factory::DEFAULT_TIMEOUT)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::transaction::dispatch_frame;
use std::sync::{Arc, Mutex};
#[derive(Clone, Default)]
struct MockDeviceLog(Arc<Mutex<Vec<CanFdFrame>>>);
impl MockDeviceLog {
fn new() -> Self {
Self::default()
}
fn len(&self) -> usize {
self.0.lock().unwrap().len()
}
}
struct MockDevice {
info: TransportDeviceInfo,
timeout: Duration,
responses: Vec<CanFdFrame>,
log: MockDeviceLog,
}
impl MockDevice {
fn new(id: usize) -> Self {
Self {
info: TransportDeviceInfo::new(id, format!("mock{}", id)),
timeout: Duration::from_millis(100),
responses: Vec::new(),
log: MockDeviceLog::new(),
}
}
fn with_response(mut self, response: CanFdFrame) -> Self {
self.responses.push(response);
self
}
}
impl TransportDevice for MockDevice {
fn transaction(&mut self, requests: &mut [Request]) -> Result<()> {
for req in requests.iter() {
if let Some(frame) = &req.frame {
self.log.0.lock().unwrap().push(frame.clone());
}
}
for frame in std::mem::take(&mut self.responses) {
dispatch_frame(&frame, requests);
}
Ok(())
}
fn write(&mut self, frame: &CanFdFrame) -> Result<()> {
self.log.0.lock().unwrap().push(frame.clone());
Ok(())
}
fn read(&mut self) -> Result<Option<CanFdFrame>> {
Ok(None)
}
fn flush(&mut self) -> Result<()> {
Ok(())
}
fn info(&self) -> &TransportDeviceInfo {
&self.info
}
fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
fn timeout(&self) -> Duration {
self.timeout
}
}
#[test]
fn test_null_transport_cycle() {
let mut transport = NullTransport::new();
let frame = CanFdFrame::new();
let mut requests = vec![Request::new(frame)];
transport.cycle(&mut requests).unwrap();
assert!(requests[0].responses.is_empty());
}
#[test]
fn test_null_transport_write() {
let mut transport = NullTransport::new();
let frame = CanFdFrame::new();
transport.write(&frame).unwrap();
}
#[test]
fn test_null_transport_read() {
let mut transport = NullTransport::new();
let result = transport.read(None).unwrap();
assert!(result.is_none());
}
#[test]
fn test_null_transport_flush_read() {
let mut transport = NullTransport::new();
transport.flush_read(None).unwrap();
}
#[test]
fn test_null_transport_timeout() {
let mut transport = NullTransport::new();
assert_eq!(transport.timeout(), Duration::from_millis(100));
transport.set_timeout(Duration::from_millis(500));
assert_eq!(transport.timeout(), Duration::from_millis(500));
}
#[test]
fn test_transport_single_device() {
let device = MockDevice::new(0);
let mut transport = Router::from_devices(vec![device]);
assert_eq!(transport.device_count(), 1);
let frame = CanFdFrame::new();
let mut requests = vec![Request::new(frame)];
let result = transport.cycle(&mut requests);
assert!(result.is_ok());
}
#[test]
fn test_transport_multiple_devices() {
let device0 = MockDevice::new(0);
let device1 = MockDevice::new(1);
let mut transport = Router::from_devices(vec![device0, device1]);
assert_eq!(transport.device_count(), 2);
transport.add_route(1u8, 0).unwrap();
transport.add_route(2u8, 1).unwrap();
let result = transport.add_route(3u8, 5);
assert!(result.is_err());
}
#[test]
fn test_transport_timeout() {
let device0 = MockDevice::new(0);
let device1 = MockDevice::new(1);
let mut transport = Router::from_devices(vec![device0, device1]);
assert_eq!(transport.timeout(), Duration::from_millis(100));
transport.set_timeout(Duration::from_millis(500));
assert_eq!(transport.timeout(), Duration::from_millis(500));
}
#[test]
fn test_transport_empty() {
let devices: Vec<MockDevice> = vec![];
let mut transport = Router::from_devices(devices);
let frame = CanFdFrame::new();
let mut requests = vec![Request::new(frame)];
let result = transport.cycle(&mut requests);
assert!(matches!(result, Err(Error::NotConnected)));
}
#[test]
fn test_mock_device_with_response() {
use moteus_protocol::calculate_arbitration_id;
let mut cmd_frame = CanFdFrame::new();
cmd_frame.arbitration_id = calculate_arbitration_id(0, 1, 0, true);
let mut resp_frame = CanFdFrame::new();
resp_frame.arbitration_id = calculate_arbitration_id(1, 0, 0, false);
resp_frame.size = 3;
let device = MockDevice::new(0).with_response(resp_frame);
let mut transport = Router::from_devices(vec![device]);
let mut requests = vec![Request::new(cmd_frame)];
transport.cycle(&mut requests).unwrap();
assert_eq!(requests[0].responses.len(), 1);
let responses = requests[0].responses.peek();
assert_eq!(responses[0].size, 3);
assert_eq!(responses[0].channel, Some(0));
}
#[test]
fn test_transport_channel_routing() {
let device0 = MockDevice::new(0);
let device1 = MockDevice::new(1);
let mut transport = Router::from_devices(vec![device0, device1]);
let frame0 = CanFdFrame::new();
let frame1 = CanFdFrame::new();
let mut requests = vec![
Request::new(frame0).with_channel(0),
Request::new(frame1).with_channel(1),
];
let result = transport.cycle(&mut requests);
assert!(result.is_ok());
}
#[test]
fn test_add_route_overwrites() {
let device0 = MockDevice::new(0);
let device1 = MockDevice::new(1);
let mut transport = Router::from_devices(vec![device0, device1]);
transport.add_route(1u8, 0).unwrap();
assert_eq!(
transport.get_device_for_address(&DeviceAddress::can_id(1)),
Some(0)
);
transport.add_route(1u8, 1).unwrap();
assert_eq!(
transport.get_device_for_address(&DeviceAddress::can_id(1)),
Some(1)
);
}
#[test]
fn test_cycle_explicit_channel_bypasses_routing_table() {
use moteus_protocol::calculate_arbitration_id;
let device0 = MockDevice::new(0);
let log0 = device0.log.clone();
let device1 = MockDevice::new(1);
let log1 = device1.log.clone();
let mut transport = Router::from_devices(vec![device0, device1]);
transport.add_route(1u8, 0).unwrap();
let mut cmd_frame = CanFdFrame::new();
cmd_frame.arbitration_id = calculate_arbitration_id(0, 1, 0, true);
let mut requests = vec![Request::new(cmd_frame).with_channel(1)];
transport.cycle(&mut requests).unwrap();
assert_eq!(log0.len(), 0);
assert_eq!(log1.len(), 1);
}
#[test]
fn test_clear_routes() {
let device0 = MockDevice::new(0);
let device1 = MockDevice::new(1);
let mut transport = Router::from_devices(vec![device0, device1]);
transport.add_route(1u8, 0).unwrap();
transport.clear_routes();
assert!(transport
.get_device_for_address(&DeviceAddress::can_id(1))
.is_none());
}
#[test]
fn test_unique_can_id_single_device_routing() {
use moteus_protocol::calculate_arbitration_id;
let device0 = MockDevice::new(0);
let log0 = device0.log.clone();
let device1 = MockDevice::new(1);
let log1 = device1.log.clone();
let mut transport = Router::from_devices(vec![device0, device1]);
transport.add_route(1u8, 0).unwrap();
transport.add_route(2u8, 1).unwrap();
let mut frame1 = CanFdFrame::new();
frame1.arbitration_id = calculate_arbitration_id(0, 1, 0, true);
let mut requests = vec![Request::new(frame1)];
transport.cycle(&mut requests).unwrap();
assert_eq!(log0.len(), 1);
assert_eq!(log1.len(), 0);
let mut frame2 = CanFdFrame::new();
frame2.arbitration_id = calculate_arbitration_id(0, 2, 0, true);
let mut requests = vec![Request::new(frame2)];
transport.cycle(&mut requests).unwrap();
assert_eq!(log0.len(), 1);
assert_eq!(log1.len(), 1);
}
#[test]
fn test_uuid_address_routes_to_single_device() {
use moteus_protocol::calculate_arbitration_id;
let device0 = MockDevice::new(0);
let log0 = device0.log.clone();
let device1 = MockDevice::new(1);
let log1 = device1.log.clone();
let uuid_a = DeviceAddress {
can_id: Some(1),
uuid: Some(vec![0x01, 0x02, 0x03, 0x04]),
transport_device: None,
};
let uuid_b = DeviceAddress {
can_id: Some(1),
uuid: Some(vec![0x05, 0x06, 0x07, 0x08]),
transport_device: None,
};
let mut transport = Router::from_devices(vec![device0, device1]);
transport.add_route(uuid_a.clone(), 0).unwrap();
transport.add_route(uuid_b.clone(), 1).unwrap();
let mut cmd_frame = CanFdFrame::new();
cmd_frame.arbitration_id = calculate_arbitration_id(0, 1, 0, true);
let mut req = Request::new(cmd_frame);
req.address = Some(uuid_a.clone());
let mut requests = vec![req];
transport.cycle(&mut requests).unwrap();
assert_eq!(log0.len(), 1);
assert_eq!(log1.len(), 0);
let mut cmd_frame = CanFdFrame::new();
cmd_frame.arbitration_id = calculate_arbitration_id(0, 1, 0, true);
let mut req = Request::new(cmd_frame);
req.address = Some(uuid_b.clone());
let mut requests = vec![req];
transport.cycle(&mut requests).unwrap();
assert_eq!(log0.len(), 1);
assert_eq!(log1.len(), 1);
}
#[test]
fn test_write_routes_to_single_device() {
use moteus_protocol::calculate_arbitration_id;
let device0 = MockDevice::new(0);
let log0 = device0.log.clone();
let device1 = MockDevice::new(1);
let log1 = device1.log.clone();
let mut transport = Router::from_devices(vec![device0, device1]);
transport.add_route(1u8, 0).unwrap();
let mut frame = CanFdFrame::new();
frame.arbitration_id = calculate_arbitration_id(0, 1, 0, false);
transport.write(&frame).unwrap();
assert_eq!(log0.len(), 1);
assert_eq!(log1.len(), 0);
}
#[test]
fn test_write_broadcast_sends_to_all() {
use moteus_protocol::calculate_arbitration_id;
let device0 = MockDevice::new(0);
let log0 = device0.log.clone();
let device1 = MockDevice::new(1);
let log1 = device1.log.clone();
let mut transport = Router::from_devices(vec![device0, device1]);
let mut frame = CanFdFrame::new();
frame.arbitration_id = calculate_arbitration_id(0, 0x7F, 0, false);
transport.write(&frame).unwrap();
assert_eq!(log0.len(), 1);
assert_eq!(log1.len(), 1);
}
}