use crate::command_ext::MaybeQuery;
use crate::controller::Controller;
use crate::device_address::DeviceAddress;
use crate::error::{Error, Result};
use crate::transport::factory::TransportOptions;
use crate::transport::singleton::get_singleton_transport;
use crate::transport::transaction::Request;
use crate::transport::{Router, Transport};
use moteus_protocol::command::{
AuxPwmCommand, CurrentCommand, PositionCommand, StayWithinCommand, VFOCCommand,
ZeroVelocityCommand,
};
use moteus_protocol::query::{QueryFormat, QueryResult};
use moteus_protocol::Resolution;
use std::sync::{Arc, Mutex};
use std::time::Duration;
pub struct BlockingController<T: Transport = Arc<Mutex<Router>>> {
pub controller: Controller,
pub(crate) transport: T,
}
impl<T: Transport> std::fmt::Debug for BlockingController<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlockingController")
.field("id", &self.controller.id)
.field("address", &self.controller.address)
.finish()
}
}
impl BlockingController<Arc<Mutex<Router>>> {
pub fn new(address: impl Into<DeviceAddress>) -> Result<Self> {
Ok(Self {
controller: Controller::new(address),
transport: get_singleton_transport(None)?,
})
}
pub fn with_options(
address: impl Into<DeviceAddress>,
options: &TransportOptions,
) -> Result<Self> {
Ok(Self {
controller: Controller::new(address),
transport: get_singleton_transport(Some(options))?,
})
}
pub fn with_controller(controller: Controller) -> Result<Self> {
Ok(Self {
controller,
transport: get_singleton_transport(None)?,
})
}
}
impl<T: Transport> BlockingController<T> {
pub fn with_transport(address: impl Into<DeviceAddress>, transport: T) -> Self {
Self {
controller: Controller::new(address),
transport,
}
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.transport.set_timeout(timeout);
}
pub fn timeout(&self) -> Duration {
self.transport.timeout()
}
pub fn query(&mut self) -> Result<QueryResult> {
let mut requests = [Request::from_command(self.controller.make_query())];
self.transport.cycle(&mut requests)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn query_with_format(&mut self, format: &QueryFormat) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_query_with_format(format),
)];
self.transport.cycle(&mut requests)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn set_stop(&mut self) -> Result<QueryResult> {
let mut requests = [Request::from_command(self.controller.make_stop(true))];
self.transport.cycle(&mut requests)?;
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) -> Result<()> {
let mut requests = [Request::from_command(self.controller.make_stop(false))];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_brake(&mut self) -> Result<QueryResult> {
let mut requests = [Request::from_command(self.controller.make_brake(true))];
self.transport.cycle(&mut requests)?;
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) -> Result<()> {
let mut requests = [Request::from_command(self.controller.make_brake(false))];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_position(
&mut self,
cmd: impl Into<MaybeQuery<PositionCommand>>,
) -> Result<QueryResult> {
let maybe = cmd.into();
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)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn set_position_no_query(&mut self, cmd: &PositionCommand) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_position_command(cmd, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn wait_for_trajectory_complete(
&mut self,
poll_interval: Duration,
timeout: Duration,
) -> Result<QueryResult> {
let start = std::time::Instant::now();
loop {
let result = self.query()?;
if result.trajectory_complete {
return Ok(result);
}
if start.elapsed() > timeout {
return Err(Error::Timeout);
}
std::thread::sleep(poll_interval);
}
}
pub fn set_current(
&mut self,
cmd: impl Into<MaybeQuery<CurrentCommand>>,
) -> Result<QueryResult> {
let maybe = cmd.into();
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)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn set_current_no_query(&mut self, cmd: &CurrentCommand) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_current_command(cmd, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_vfoc(&mut self, cmd: impl Into<MaybeQuery<VFOCCommand>>) -> Result<QueryResult> {
let maybe = cmd.into();
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)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn set_vfoc_no_query(&mut self, cmd: &VFOCCommand) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_vfoc_command(cmd, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_stay_within(
&mut self,
cmd: impl Into<MaybeQuery<StayWithinCommand>>,
) -> Result<QueryResult> {
let maybe = cmd.into();
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)?;
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(&mut self, cmd: &StayWithinCommand) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_stay_within_command(cmd, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_zero_velocity(
&mut self,
cmd: impl Into<MaybeQuery<ZeroVelocityCommand>>,
) -> Result<QueryResult> {
let maybe = cmd.into();
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)?;
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(&mut self, cmd: &ZeroVelocityCommand) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_zero_velocity_command(cmd, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_output_nearest(&mut self, position: f32) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_set_output_nearest(position, true),
)];
self.transport.cycle(&mut requests)?;
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) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_set_output_exact(position, true),
)];
self.transport.cycle(&mut requests)?;
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) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_require_reindex(true),
)];
self.transport.cycle(&mut requests)?;
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) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_recapture_position_velocity(true),
)];
self.transport.cycle(&mut requests)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn read_gpio(&mut self) -> Result<(u8, u8)> {
let mut requests = [Request::from_command(self.controller.make_read_gpio())];
self.transport.cycle(&mut requests)?;
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>) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_write_gpio(aux1, aux2, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_write_gpio_query(
&mut self,
aux1: Option<u8>,
aux2: Option<u8>,
) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_write_gpio(aux1, aux2, true),
)];
self.transport.cycle(&mut requests)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn custom_query(&mut self, registers: &[(u16, Resolution)]) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_custom_query(registers),
)];
self.transport.cycle(&mut requests)?;
requests[0]
.responses
.take()
.into_iter()
.next()
.map(|f| self.controller.parse_query(&f))
.ok_or(Error::NoResponse)
}
pub fn set_aux_pwm(&mut self, cmd: &AuxPwmCommand) -> Result<QueryResult> {
let mut requests = [Request::from_command(
self.controller.make_aux_pwm(cmd, true),
)];
self.transport.cycle(&mut requests)?;
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(&mut self, cmd: &AuxPwmCommand) -> Result<()> {
let mut requests = [Request::from_command(
self.controller.make_aux_pwm(cmd, false),
)];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_trim(&mut self, trim: i32) -> Result<()> {
let mut requests = [Request::from_command(self.controller.make_set_trim(trim))];
self.transport.cycle(&mut requests)?;
Ok(())
}
pub fn set_position_wait_complete(
&mut self,
cmd: &PositionCommand,
poll_interval: Duration,
timeout: Duration,
) -> Result<QueryResult> {
let start = std::time::Instant::now();
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)?;
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 {
return Err(Error::Timeout);
}
std::thread::sleep(poll_interval);
}
}
pub fn flush_transport(&mut self) -> Result<()> {
let old_timeout = self.transport.timeout();
self.transport.set_timeout(Duration::from_millis(20));
let mut requests: [Request; 0] = [];
let _ = self.transport.cycle(&mut requests); self.transport.set_timeout(old_timeout);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::NullTransport;
#[test]
fn test_with_transport_is_infallible() {
let _ctrl = BlockingController::with_transport(1, NullTransport::new());
}
#[test]
fn test_with_options_is_infallible() {
let opts = TransportOptions::new().timeout(Duration::from_millis(200));
let _: std::result::Result<BlockingController, _> =
BlockingController::with_options(1, &opts);
}
#[test]
fn test_explicit_transport() {
let mut ctrl = BlockingController::with_transport(1, NullTransport::new());
assert_eq!(ctrl.controller.id, 1);
let result = ctrl.set_stop();
assert!(result.is_err());
}
#[test]
fn test_timeout() {
let mut ctrl = BlockingController::with_transport(1, NullTransport::new());
assert_eq!(ctrl.timeout(), Duration::from_millis(100));
ctrl.set_timeout(Duration::from_millis(500));
assert_eq!(ctrl.timeout(), Duration::from_millis(500));
}
#[test]
fn test_set_position_no_query() {
let mut ctrl = BlockingController::with_transport(1, NullTransport::new());
let result = ctrl.set_position_no_query(&PositionCommand::new().position(0.5));
assert!(result.is_ok());
}
}