#![cfg(feature = "tokio")]
use crate::command_ext::MaybeQuery;
use crate::controller::Controller;
use crate::device_address::DeviceAddress;
use crate::error::{Error, Result};
use crate::transport::async_singleton::get_async_singleton_transport;
use crate::transport::async_transport::{AsyncRouter, AsyncTransport, BoxFuture};
use crate::transport::transaction::Request;
use moteus_protocol::command::{
AuxPwmCommand, CurrentCommand, PositionCommand, StayWithinCommand, VFOCCommand,
ZeroVelocityCommand,
};
use moteus_protocol::query::{QueryFormat, QueryResult};
use moteus_protocol::Resolution;
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct AsyncController<T: AsyncTransport = Arc<Mutex<AsyncRouter>>> {
pub controller: Controller,
pub(crate) transport: T,
}
impl<T: AsyncTransport> std::fmt::Debug for AsyncController<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncController")
.field("id", &self.controller.id)
.field("address", &self.controller.address)
.finish()
}
}
impl AsyncController<Arc<Mutex<AsyncRouter>>> {
pub async fn new(address: impl Into<DeviceAddress>) -> Result<Self> {
Ok(Self {
controller: Controller::new(address),
transport: get_async_singleton_transport(None).await?,
})
}
pub async fn with_options(
address: impl Into<DeviceAddress>,
options: &crate::transport::async_factory::AsyncTransportOptions,
) -> Result<Self> {
Ok(Self {
controller: Controller::new(address),
transport: get_async_singleton_transport(Some(options)).await?,
})
}
pub async fn with_controller(controller: Controller) -> Result<Self> {
Ok(Self {
controller,
transport: get_async_singleton_transport(None).await?,
})
}
}
impl<T: AsyncTransport> AsyncController<T> {
pub fn with_transport(address: impl Into<DeviceAddress>, transport: T) -> Self {
Self {
controller: Controller::new(address),
transport,
}
}
pub fn query(&mut self) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_query())];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn query_with_format<'a>(
&'a mut self,
format: &'a QueryFormat,
) -> BoxFuture<'a, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_query_with_format(format),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_stop(&mut self) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_stop(true))];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_stop_no_query(&mut self) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_stop(false))];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_brake(&mut self) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_brake(true))];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_brake_no_query(&mut self) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_brake(false))];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_position(
&mut self,
cmd: impl Into<MaybeQuery<PositionCommand>>,
) -> BoxFuture<'_, Result<QueryResult>> {
let maybe = cmd.into();
Box::pin(async move {
let (command, query_override) = maybe.into_parts();
let query_format = query_override
.as_ref()
.unwrap_or(&self.controller.query_format);
let mut cmd = self.controller.prepare_command(true);
command.serialize(cmd.frame_mut(), &self.controller.position_format);
cmd.expected_reply_size = query_format.serialize(cmd.frame_mut());
let mut requests = [Request::from_command(cmd)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_position_no_query<'a>(
&'a mut self,
cmd: &'a PositionCommand,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_position_command(cmd, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn wait_for_trajectory_complete(
&mut self,
poll_interval: std::time::Duration,
timeout: std::time::Duration,
) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let start = std::time::Instant::now();
let timeout_dur = timeout;
let interval = poll_interval;
loop {
let mut requests = [Request::from_command(self.controller.make_query())];
self.transport.cycle(&mut requests).await?;
let result = requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)?;
if result.trajectory_complete {
return Ok(result);
}
if start.elapsed() > timeout_dur {
return Err(Error::Timeout);
}
tokio::time::sleep(interval).await;
}
})
}
pub fn set_current(
&mut self,
cmd: impl Into<MaybeQuery<CurrentCommand>>,
) -> BoxFuture<'_, Result<QueryResult>> {
let maybe = cmd.into();
Box::pin(async move {
let (command, query_override) = maybe.into_parts();
let query_format = query_override
.as_ref()
.unwrap_or(&self.controller.query_format);
let mut cmd = self.controller.prepare_command(true);
command.serialize(cmd.frame_mut(), &self.controller.current_format);
cmd.expected_reply_size = query_format.serialize(cmd.frame_mut());
let mut requests = [Request::from_command(cmd)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_current_no_query<'a>(
&'a mut self,
cmd: &'a CurrentCommand,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_current_command(cmd, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_vfoc(
&mut self,
cmd: impl Into<MaybeQuery<VFOCCommand>>,
) -> BoxFuture<'_, Result<QueryResult>> {
let maybe = cmd.into();
Box::pin(async move {
let (command, query_override) = maybe.into_parts();
let query_format = query_override
.as_ref()
.unwrap_or(&self.controller.query_format);
let mut cmd = self.controller.prepare_command(true);
command.serialize(cmd.frame_mut(), &self.controller.vfoc_format);
cmd.expected_reply_size = query_format.serialize(cmd.frame_mut());
let mut requests = [Request::from_command(cmd)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_vfoc_no_query<'a>(&'a mut self, cmd: &'a VFOCCommand) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_vfoc_command(cmd, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_stay_within(
&mut self,
cmd: impl Into<MaybeQuery<StayWithinCommand>>,
) -> BoxFuture<'_, Result<QueryResult>> {
let maybe = cmd.into();
Box::pin(async move {
let (command, query_override) = maybe.into_parts();
let query_format = query_override
.as_ref()
.unwrap_or(&self.controller.query_format);
let mut cmd = self.controller.prepare_command(true);
command.serialize(cmd.frame_mut(), &self.controller.stay_within_format);
cmd.expected_reply_size = query_format.serialize(cmd.frame_mut());
let mut requests = [Request::from_command(cmd)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_stay_within_no_query<'a>(
&'a mut self,
cmd: &'a StayWithinCommand,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_stay_within_command(cmd, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_zero_velocity(
&mut self,
cmd: impl Into<MaybeQuery<ZeroVelocityCommand>>,
) -> BoxFuture<'_, Result<QueryResult>> {
let maybe = cmd.into();
Box::pin(async move {
let (command, query_override) = maybe.into_parts();
let query_format = query_override
.as_ref()
.unwrap_or(&self.controller.query_format);
let mut cmd = self.controller.prepare_command(true);
command.serialize(cmd.frame_mut(), &self.controller.zero_velocity_format);
cmd.expected_reply_size = query_format.serialize(cmd.frame_mut());
let mut requests = [Request::from_command(cmd)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_zero_velocity_no_query<'a>(
&'a mut self,
cmd: &'a ZeroVelocityCommand,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_zero_velocity_command(cmd, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_output_nearest(&mut self, position: f32) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_set_output_nearest(position, true),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_output_exact(&mut self, position: f32) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_set_output_exact(position, true),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_require_reindex(&mut self) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_require_reindex(true),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_recapture_position_velocity(&mut self) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_recapture_position_velocity(true),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn read_gpio(&mut self) -> BoxFuture<'_, Result<(u8, u8)>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_read_gpio())];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| {
let gpio = self.controller.parse_gpio(&f);
(gpio.aux1, gpio.aux2)
})
.ok_or(Error::NoResponse)
})
}
pub fn set_write_gpio(
&mut self,
aux1: Option<u8>,
aux2: Option<u8>,
) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_write_gpio(aux1, aux2, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_write_gpio_query(
&mut self,
aux1: Option<u8>,
aux2: Option<u8>,
) -> BoxFuture<'_, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_write_gpio(aux1, aux2, true),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn custom_query<'a>(
&'a mut self,
registers: &'a [(u16, Resolution)],
) -> BoxFuture<'a, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_custom_query(registers),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_aux_pwm<'a>(
&'a mut self,
cmd: &'a AuxPwmCommand,
) -> BoxFuture<'a, Result<QueryResult>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_aux_pwm(cmd, true),
)];
self.transport.cycle(&mut requests).await?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
})
}
pub fn set_aux_pwm_no_query<'a>(
&'a mut self,
cmd: &'a AuxPwmCommand,
) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(
self.controller.make_aux_pwm(cmd, false),
)];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_trim(&mut self, trim: i32) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
let mut requests = [Request::from_command(self.controller.make_set_trim(trim))];
self.transport.cycle(&mut requests).await?;
Ok(())
})
}
pub fn set_position_wait_complete<'a>(
&'a mut self,
cmd: &'a PositionCommand,
poll_interval: std::time::Duration,
timeout: std::time::Duration,
) -> BoxFuture<'a, Result<QueryResult>> {
Box::pin(async move {
let start = std::time::Instant::now();
let timeout_dur = timeout;
let interval = poll_interval;
let mut query_format = self.controller.query_format.clone();
query_format.trajectory_complete = moteus_protocol::Resolution::Int8;
if query_format.mode == moteus_protocol::Resolution::Ignore {
query_format.mode = moteus_protocol::Resolution::Int8;
}
if query_format.fault == moteus_protocol::Resolution::Ignore {
query_format.fault = moteus_protocol::Resolution::Int8;
}
let mut success_count: i32 = 2;
loop {
let mut command = self.controller.prepare_command(true);
cmd.serialize(command.frame_mut(), &self.controller.position_format);
command.expected_reply_size = query_format.serialize(command.frame_mut());
let mut requests = [Request::from_command(command)];
self.transport.cycle(&mut requests).await?;
let result = requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f));
if let Some(ref r) = result {
success_count = success_count.saturating_sub(1);
if r.mode == moteus_protocol::Mode::Fault
|| r.mode == moteus_protocol::Mode::Timeout
{
return Err(Error::Fault {
mode: r.mode as u8,
code: r.fault,
});
}
if success_count == 0 && r.trajectory_complete {
return result.ok_or(Error::NoResponse);
}
}
if start.elapsed() > timeout_dur {
return Err(Error::Timeout);
}
tokio::time::sleep(interval).await;
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::async_transport::AsyncTransport;
use crate::transport::transaction::dispatch_frame;
use moteus_protocol::CanFdFrame;
struct MockTransport {
call_count: std::cell::Cell<usize>,
}
impl MockTransport {
fn new() -> Self {
MockTransport {
call_count: std::cell::Cell::new(0),
}
}
}
impl AsyncTransport for MockTransport {
fn cycle<'a>(&'a mut self, requests: &'a mut [Request]) -> BoxFuture<'a, Result<()>> {
self.call_count.set(self.call_count.get() + 1);
let mut response = CanFdFrame::new();
response.arbitration_id = 0x8100; response.data[0..3].copy_from_slice(&[0x21, 0x00, 0x0A]); response.size = 3;
Box::pin(async move {
dispatch_frame(&response, requests);
Ok(())
})
}
fn write<'a>(&'a mut self, _frame: &'a CanFdFrame) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { Ok(()) })
}
fn read<'a>(
&'a mut self,
_channel: Option<usize>,
) -> BoxFuture<'a, Result<Option<CanFdFrame>>> {
Box::pin(async move { Ok(None) })
}
fn flush_read<'a>(&'a mut self, _channel: Option<usize>) -> BoxFuture<'a, Result<()>> {
Box::pin(async move { Ok(()) })
}
}
#[test]
fn test_with_transport_is_infallible() {
let ctrl = AsyncController::with_transport(1, MockTransport::new());
assert_eq!(ctrl.controller.id, 1);
}
#[test]
fn test_explicit_transport() {
let ctrl = AsyncController::with_transport(1, MockTransport::new());
assert_eq!(ctrl.controller.id, 1);
}
}