use crate::command_types::Command;
use crate::device_address::DeviceAddress;
use moteus_protocol::command::{
AuxPwmCommand, AuxPwmFormat, BrakeCommand, CurrentCommand, CurrentFormat, OutputExactCommand,
OutputNearestCommand, PositionCommand, PositionFormat, RecapturePositionVelocityCommand,
RequireReindexCommand, SetTrimCommand, StayWithinCommand, StayWithinFormat, StopCommand,
VFOCCommand, VFOCFormat, ZeroVelocityCommand, ZeroVelocityFormat,
};
use moteus_protocol::query::{QueryFormat, QueryResult};
use moteus_protocol::{CanFdFrame, Register, Resolution, WriteCanData, WriteCombiner};
pub struct Controller {
pub address: DeviceAddress,
pub id: u8,
pub source_id: u8,
pub can_prefix: u16,
pub query_format: QueryFormat,
pub position_format: PositionFormat,
pub vfoc_format: VFOCFormat,
pub current_format: CurrentFormat,
pub stay_within_format: StayWithinFormat,
pub zero_velocity_format: ZeroVelocityFormat,
pub aux_pwm_format: AuxPwmFormat,
uuid_prefix_data: Vec<u8>,
}
impl Controller {
pub fn new(address: impl Into<DeviceAddress>) -> Self {
let address = address.into();
let id = address.as_can_id().unwrap_or(0x7F);
let uuid_prefix_data = if address.can_id.is_none() {
if let Some(uuid) = address.as_uuid() {
crate::transport::make_uuid_prefix(uuid)
} else {
Vec::new()
}
} else {
Vec::new()
};
Controller {
address,
id,
source_id: 0,
can_prefix: 0x0000,
query_format: QueryFormat::default(),
position_format: PositionFormat::default(),
vfoc_format: VFOCFormat::default(),
current_format: CurrentFormat::default(),
stay_within_format: StayWithinFormat::default(),
zero_velocity_format: ZeroVelocityFormat::default(),
aux_pwm_format: AuxPwmFormat::default(),
uuid_prefix_data,
}
}
#[must_use]
pub fn source_id(mut self, id: u8) -> Self {
self.source_id = id;
self
}
#[must_use]
pub fn can_prefix(mut self, prefix: u16) -> Self {
self.can_prefix = prefix & 0x1fff;
self
}
#[must_use]
pub fn query_format(mut self, format: QueryFormat) -> Self {
self.query_format = format;
self
}
#[must_use]
pub fn position_format(mut self, format: PositionFormat) -> Self {
self.position_format = format;
self
}
#[must_use]
pub fn vfoc_format(mut self, format: VFOCFormat) -> Self {
self.vfoc_format = format;
self
}
#[must_use]
pub fn current_format(mut self, format: CurrentFormat) -> Self {
self.current_format = format;
self
}
#[must_use]
pub fn stay_within_format(mut self, format: StayWithinFormat) -> Self {
self.stay_within_format = format;
self
}
#[must_use]
pub fn zero_velocity_format(mut self, format: ZeroVelocityFormat) -> Self {
self.zero_velocity_format = format;
self
}
#[must_use]
pub fn aux_pwm_format(mut self, format: AuxPwmFormat) -> Self {
self.aux_pwm_format = format;
self
}
pub(crate) fn prepare_command(&self, query: bool) -> Command {
let mut cmd = Command::new(self.id as i8, self.source_id as i8, self.can_prefix)
.reply_required(query);
cmd.channel = self.address.transport_device;
cmd.address = Some(self.address.clone());
if query {
cmd.reply_filter = Some(Command::query_reply_filter());
}
if !self.uuid_prefix_data.is_empty() {
let frame = cmd.frame_mut();
let len = self.uuid_prefix_data.len();
frame.data[..len].copy_from_slice(&self.uuid_prefix_data);
frame.size = len as u8;
}
cmd
}
pub fn make_query(&self) -> Command {
self.make_query_with_format(&self.query_format)
}
pub fn make_query_with_format(&self, format: &QueryFormat) -> Command {
let mut cmd = self.prepare_command(true);
cmd.expected_reply_size = format.serialize(cmd.frame_mut());
cmd
}
pub fn parse_query(&self, frame: &CanFdFrame) -> QueryResult {
QueryResult::parse(frame)
}
pub fn make_stop(&self, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
StopCommand::serialize(cmd.frame_mut());
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_brake(&self, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
BrakeCommand::serialize(cmd.frame_mut());
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_position_command(&self, pos_cmd: &PositionCommand, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
pos_cmd.serialize(cmd.frame_mut(), &self.position_format);
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_current_command(&self, cur_cmd: &CurrentCommand, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
cur_cmd.serialize(cmd.frame_mut(), &self.current_format);
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_vfoc_command(&self, vfoc_cmd: &VFOCCommand, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
vfoc_cmd.serialize(cmd.frame_mut(), &self.vfoc_format);
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_stay_within_command(&self, sw_cmd: &StayWithinCommand, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
sw_cmd.serialize(cmd.frame_mut(), &self.stay_within_format);
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_zero_velocity_command(&self, zv_cmd: &ZeroVelocityCommand, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
zv_cmd.serialize(cmd.frame_mut(), &self.zero_velocity_format);
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_set_output_nearest(&self, position: f32, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
OutputNearestCommand::new(position).serialize(cmd.frame_mut());
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_set_output_exact(&self, position: f32, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
OutputExactCommand::new(position).serialize(cmd.frame_mut());
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_require_reindex(&self, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
RequireReindexCommand::serialize(cmd.frame_mut());
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_recapture_position_velocity(&self, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
RecapturePositionVelocityCommand::serialize(cmd.frame_mut());
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_read_gpio(&self) -> Command {
let mut cmd = self.prepare_command(true);
let frame = cmd.frame_mut();
let mut writer = WriteCanData::new(frame);
let resolutions = [Resolution::Int8, Resolution::Int8];
let mut combiner = WriteCombiner::new(
0x10, Register::Aux1GpioStatus.address(),
&resolutions,
);
for _ in 0..resolutions.len() {
combiner.maybe_write(&mut writer);
}
cmd.expected_reply_size = combiner.reply_size();
cmd
}
pub fn make_write_gpio(&self, aux1: Option<u8>, aux2: Option<u8>, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
{
let frame = cmd.frame_mut();
let mut writer = WriteCanData::new(frame);
let resolutions = [
if aux1.is_some() {
Resolution::Int8
} else {
Resolution::Ignore
},
if aux2.is_some() {
Resolution::Int8
} else {
Resolution::Ignore
},
];
let mut combiner = WriteCombiner::new(
0x00, Register::Aux1GpioCommand.address(),
&resolutions,
);
if combiner.maybe_write(&mut writer) {
writer.write_i8(aux1.unwrap_or(0) as i8);
}
if combiner.maybe_write(&mut writer) {
writer.write_i8(aux2.unwrap_or(0) as i8);
}
}
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn parse_gpio(&self, frame: &CanFdFrame) -> GpioResult {
let result = QueryResult::parse(frame);
GpioResult {
aux1: result.aux1_gpio as u8,
aux2: result.aux2_gpio as u8,
}
}
pub fn make_custom_query(&self, registers: &[(u16, Resolution)]) -> Command {
let mut cmd = self.prepare_command(true);
if registers.is_empty() {
return cmd;
}
let frame = cmd.frame_mut();
let mut writer = WriteCanData::new(frame);
let min_addr = registers.iter().map(|(a, _)| *a).min().unwrap();
let max_addr = registers.iter().map(|(a, _)| *a).max().unwrap();
let range_len = (max_addr - min_addr + 1) as usize;
let mut resolutions = vec![Resolution::Ignore; range_len];
for (addr, res) in registers {
let idx = (*addr - min_addr) as usize;
resolutions[idx] = *res;
}
let mut combiner = WriteCombiner::new(
0x10, min_addr,
&resolutions,
);
for _ in 0..range_len {
combiner.maybe_write(&mut writer);
}
cmd.expected_reply_size = combiner.reply_size();
cmd
}
pub fn make_aux_pwm(&self, pwm_cmd: &AuxPwmCommand, query: bool) -> Command {
let mut cmd = self.prepare_command(query);
pwm_cmd.serialize(cmd.frame_mut(), &self.aux_pwm_format);
if query {
cmd.expected_reply_size = self.query_format.serialize(cmd.frame_mut());
}
cmd
}
pub fn make_set_trim(&self, trim: i32) -> Command {
let mut cmd = self.prepare_command(false);
SetTrimCommand::new(trim).serialize(cmd.frame_mut());
cmd
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Default)]
pub struct GpioResult {
pub aux1: u8,
pub aux2: u8,
}
impl std::fmt::Debug for Controller {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Controller")
.field("address", &self.address)
.field("id", &self.id)
.field("source_id", &self.source_id)
.field("can_prefix", &self.can_prefix)
.finish()
}
}
impl Default for Controller {
fn default() -> Self {
Self::new(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_controller() {
let controller = Controller::new(5);
assert_eq!(controller.id, 5);
assert_eq!(controller.address, DeviceAddress::can_id(5));
assert_eq!(controller.source_id, 0);
}
#[test]
fn test_new_controller_with_device_address() {
let controller = Controller::new(DeviceAddress::can_id(7));
assert_eq!(controller.id, 7);
assert_eq!(controller.address, DeviceAddress::can_id(7));
}
#[test]
fn test_make_stop() {
let controller = Controller::new(1);
let cmd = controller.make_stop(false);
assert_eq!(cmd.destination, 1);
assert!(!cmd.reply_required);
assert!(cmd.data_size() >= 3);
}
#[test]
fn test_make_stop_into_frame() {
let controller = Controller::new(1);
let frame = controller.make_stop(false).into_frame();
assert_eq!(frame.arbitration_id, 0x0001);
assert!(frame.size >= 3);
}
#[test]
fn test_make_position_command() {
let controller = Controller::new(1);
let pos_cmd = PositionCommand::new().position(0.5).velocity(1.0);
let cmd = controller.make_position_command(&pos_cmd, true);
assert_eq!(cmd.destination, 1);
assert!(cmd.reply_required);
assert!(cmd.data_size() > 3);
}
#[test]
fn test_make_query() {
let controller = Controller::new(1);
let cmd = controller.make_query();
assert!(cmd.reply_required);
assert!(cmd.expected_reply_size > 0);
}
#[test]
fn test_make_query_into_frame() {
let controller = Controller::new(1);
let frame = controller.make_query().into_frame();
assert_eq!(frame.arbitration_id, 0x8001);
}
#[test]
fn test_new_controller_uuid_only() {
let uuid = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let controller = Controller::new(DeviceAddress::uuid(uuid));
assert_eq!(controller.id, 0x7F);
assert!(!controller.uuid_prefix_data.is_empty());
}
#[test]
fn test_uuid_controller_frame_has_prefix() {
let uuid = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let controller = Controller::new(DeviceAddress::uuid(uuid));
let cmd = controller.make_stop(false);
let frame = cmd.into_frame();
assert_eq!(frame.arbitration_id & 0x7F, 0x7F);
let prefix_len = controller.uuid_prefix_data.len();
assert!(frame.size as usize > prefix_len);
assert_eq!(&frame.data[..prefix_len], &controller.uuid_prefix_data[..]);
}
#[test]
fn test_uuid_controller_query_frame() {
let uuid = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
];
let controller = Controller::new(DeviceAddress::uuid(uuid));
let cmd = controller.make_query();
let frame = cmd.into_frame();
assert_eq!(frame.arbitration_id, 0x807F);
let prefix_len = controller.uuid_prefix_data.len();
assert!(frame.size as usize > prefix_len);
}
#[test]
fn test_can_id_controller_no_uuid_prefix() {
let controller = Controller::new(1);
assert!(controller.uuid_prefix_data.is_empty());
let cmd = controller.make_stop(false);
assert!(cmd.data_size() >= 3);
}
#[test]
fn test_uuid_controller_with_transport_device() {
let addr = DeviceAddress::uuid([0x01, 0x02, 0x03, 0x04]).with_transport_device(2);
let controller = Controller::new(addr);
assert_eq!(controller.id, 0x7F);
let cmd = controller.make_stop(false);
let frame = cmd.into_frame();
assert_eq!(frame.channel, Some(2));
}
}