use crate::frame::CanFdFrame;
use crate::mode::{HomeState, Mode};
use crate::multiplex::{parse_frame, Subframe, Value, WriteCanData, WriteCombiner};
use crate::register::Register;
use crate::resolution::Resolution;
use crate::scaling;
pub const MAX_EXTRA: usize = 16;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct QueryFormat {
pub mode: Resolution,
pub position: Resolution,
pub velocity: Resolution,
pub torque: Resolution,
pub q_current: Resolution,
pub d_current: Resolution,
pub abs_position: Resolution,
pub power: Resolution,
pub motor_temperature: Resolution,
pub trajectory_complete: Resolution,
pub home_state: Resolution,
pub voltage: Resolution,
pub temperature: Resolution,
pub fault: Resolution,
pub aux1_gpio: Resolution,
pub aux2_gpio: Resolution,
pub aux1_pwm_input_period_us: Resolution,
pub aux1_pwm_input_duty_cycle: Resolution,
pub aux2_pwm_input_period_us: Resolution,
pub aux2_pwm_input_duty_cycle: Resolution,
pub extra: [(u16, Resolution); MAX_EXTRA],
}
impl Default for QueryFormat {
fn default() -> Self {
QueryFormat {
mode: Resolution::Int8,
position: Resolution::Float,
velocity: Resolution::Float,
torque: Resolution::Float,
q_current: Resolution::Ignore,
d_current: Resolution::Ignore,
abs_position: Resolution::Ignore,
power: Resolution::Ignore,
motor_temperature: Resolution::Ignore,
trajectory_complete: Resolution::Ignore,
home_state: Resolution::Ignore,
voltage: Resolution::Int8,
temperature: Resolution::Int8,
fault: Resolution::Int8,
aux1_gpio: Resolution::Ignore,
aux2_gpio: Resolution::Ignore,
aux1_pwm_input_period_us: Resolution::Ignore,
aux1_pwm_input_duty_cycle: Resolution::Ignore,
aux2_pwm_input_period_us: Resolution::Ignore,
aux2_pwm_input_duty_cycle: Resolution::Ignore,
extra: [(u16::MAX, Resolution::Ignore); MAX_EXTRA],
}
}
}
impl QueryFormat {
pub fn minimal() -> Self {
QueryFormat {
voltage: Resolution::Ignore,
temperature: Resolution::Ignore,
fault: Resolution::Ignore,
..Default::default()
}
}
pub fn comprehensive() -> Self {
QueryFormat {
q_current: Resolution::Float,
d_current: Resolution::Float,
abs_position: Resolution::Float,
power: Resolution::Float,
motor_temperature: Resolution::Float,
trajectory_complete: Resolution::Int8,
home_state: Resolution::Int8,
..Default::default()
}
}
pub fn extra_count(&self) -> usize {
self.extra
.iter()
.filter(|(_, r)| *r != Resolution::Ignore)
.count()
}
pub fn add_extra(&mut self, register: u16, resolution: Resolution) {
let slot = self
.extra
.iter()
.position(|(_, r)| *r == Resolution::Ignore);
if let Some(idx) = slot {
self.extra[idx] = (register, resolution);
let count = idx + 1;
for i in (1..count).rev() {
if self.extra[i].0 < self.extra[i - 1].0 {
self.extra.swap(i, i - 1);
} else {
break;
}
}
}
}
pub fn serialize(&self, frame: &mut CanFdFrame) -> u8 {
let mut writer = WriteCanData::new(frame);
let mut reply_size: u8 = 0;
{
let resolutions = [
self.mode,
self.position,
self.velocity,
self.torque,
self.q_current,
self.d_current,
self.abs_position,
self.power,
];
let mut combiner = WriteCombiner::new(
0x10, Register::Mode.address(),
&resolutions,
);
for _ in 0..resolutions.len() {
combiner.maybe_write(&mut writer);
}
reply_size += combiner.reply_size();
}
{
let resolutions = [
self.motor_temperature,
self.trajectory_complete,
self.home_state,
self.voltage,
self.temperature,
self.fault,
];
let mut combiner =
WriteCombiner::new(0x10, Register::MotorTemperature.address(), &resolutions);
for _ in 0..resolutions.len() {
combiner.maybe_write(&mut writer);
}
reply_size += combiner.reply_size();
}
{
let resolutions = [self.aux1_gpio, self.aux2_gpio];
let mut combiner =
WriteCombiner::new(0x10, Register::Aux1GpioStatus.address(), &resolutions);
for _ in 0..resolutions.len() {
combiner.maybe_write(&mut writer);
}
reply_size += combiner.reply_size();
}
{
let resolutions = [
self.aux1_pwm_input_period_us,
self.aux1_pwm_input_duty_cycle,
self.aux2_pwm_input_period_us,
self.aux2_pwm_input_duty_cycle,
];
let mut combiner =
WriteCombiner::new(0x10, Register::Aux1PwmInputPeriod.address(), &resolutions);
for _ in 0..resolutions.len() {
combiner.maybe_write(&mut writer);
}
reply_size += combiner.reply_size();
}
let extra_count = self.extra_count();
if extra_count > 0 {
const MAX_GROUP_SPAN: usize = 64;
let mut group_start = 0;
while group_start < extra_count {
let base_reg = self.extra[group_start].0;
let mut group_end = group_start + 1;
while group_end < extra_count
&& (self.extra[group_end].0 - base_reg) < MAX_GROUP_SPAN as u16
{
group_end += 1;
}
let last_reg = self.extra[group_end - 1].0;
let span = (last_reg - base_reg + 1) as usize;
let mut resolutions = [Resolution::Ignore; MAX_GROUP_SPAN];
for i in group_start..group_end {
let (reg, res) = self.extra[i];
resolutions[(reg - base_reg) as usize] = res;
}
let mut combiner = WriteCombiner::new(0x10, base_reg, &resolutions[..span]);
for _ in 0..span {
combiner.maybe_write(&mut writer);
}
reply_size += combiner.reply_size();
group_start = group_end;
}
}
reply_size
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ExtraValue {
pub register: u16,
pub value: f32,
}
#[non_exhaustive]
#[derive(Debug, Clone, Default)]
pub struct QueryResult {
pub mode: Mode,
pub position: f32,
pub velocity: f32,
pub torque: f32,
pub q_current: f32,
pub d_current: f32,
pub abs_position: f32,
pub power: f32,
pub motor_temperature: f32,
pub trajectory_complete: bool,
pub home_state: HomeState,
pub voltage: f32,
pub temperature: f32,
pub fault: i8,
pub aux1_gpio: i8,
pub aux2_gpio: i8,
pub aux1_pwm_input_period_us: i32,
pub aux1_pwm_input_duty_cycle: f32,
pub aux2_pwm_input_period_us: i32,
pub aux2_pwm_input_duty_cycle: f32,
pub extra: [Option<ExtraValue>; MAX_EXTRA],
}
impl QueryResult {
pub fn new() -> Self {
QueryResult {
mode: Mode::Stopped,
position: f32::NAN,
velocity: f32::NAN,
torque: f32::NAN,
q_current: f32::NAN,
d_current: f32::NAN,
abs_position: f32::NAN,
power: f32::NAN,
motor_temperature: f32::NAN,
trajectory_complete: false,
home_state: HomeState::Relative,
voltage: f32::NAN,
temperature: f32::NAN,
fault: 0,
aux1_gpio: 0,
aux2_gpio: 0,
aux1_pwm_input_period_us: 0,
aux1_pwm_input_duty_cycle: 0.0,
aux2_pwm_input_period_us: 0,
aux2_pwm_input_duty_cycle: 0.0,
extra: [None; MAX_EXTRA],
}
}
pub fn parse(frame: &CanFdFrame) -> Self {
Self::parse_data(&frame.data[..frame.size as usize])
}
pub fn parse_data(data: &[u8]) -> Self {
let mut result = QueryResult::new();
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::Mode.address() => {
result.mode = Mode::try_from(value.to_i32() as u8).unwrap_or(Mode::Stopped);
}
r if r == Register::Position.address() => {
result.position = value.to_f32(&scaling::POSITION);
}
r if r == Register::Velocity.address() => {
result.velocity = value.to_f32(&scaling::VELOCITY);
}
r if r == Register::Torque.address() => {
result.torque = value.to_f32(&scaling::TORQUE);
}
r if r == Register::QCurrent.address() => {
result.q_current = value.to_f32(&scaling::CURRENT);
}
r if r == Register::DCurrent.address() => {
result.d_current = value.to_f32(&scaling::CURRENT);
}
r if r == Register::AbsPosition.address() => {
result.abs_position = value.to_f32(&scaling::POSITION);
}
r if r == Register::Power.address() => {
result.power = value.to_f32(&scaling::POWER);
}
r if r == Register::MotorTemperature.address() => {
result.motor_temperature = value.to_f32(&scaling::TEMPERATURE);
}
r if r == Register::TrajectoryComplete.address() => {
result.trajectory_complete = value.to_i32() != 0;
}
r if r == Register::HomeState.address() => {
result.home_state =
HomeState::try_from(value.to_i32() as u8).unwrap_or(HomeState::Relative);
}
r if r == Register::Voltage.address() => {
result.voltage = value.to_f32(&scaling::VOLTAGE);
}
r if r == Register::Temperature.address() => {
result.temperature = value.to_f32(&scaling::TEMPERATURE);
}
r if r == Register::Fault.address() => {
result.fault = value.to_i32() as i8;
}
r if r == Register::Aux1GpioStatus.address() => {
result.aux1_gpio = value.to_i32() as i8;
}
r if r == Register::Aux2GpioStatus.address() => {
result.aux2_gpio = value.to_i32() as i8;
}
r if r == Register::Aux1PwmInputPeriod.address() => {
result.aux1_pwm_input_period_us = value.to_i32();
}
r if r == Register::Aux1PwmInputDutyCycle.address() => {
result.aux1_pwm_input_duty_cycle = value.to_f32(&scaling::PWM);
}
r if r == Register::Aux2PwmInputPeriod.address() => {
result.aux2_pwm_input_period_us = value.to_i32();
}
r if r == Register::Aux2PwmInputDutyCycle.address() => {
result.aux2_pwm_input_duty_cycle = value.to_f32(&scaling::PWM);
}
_ => {
if let Some(slot) = result.extra.iter().position(|e| e.is_none()) {
result.extra[slot] = Some(ExtraValue {
register,
value: parse_generic(register, value),
});
} else {
debug_assert!(
false,
"MAX_EXTRA ({}) exceeded, register 0x{:x} dropped",
MAX_EXTRA, register
);
}
}
}
}
result
}
pub fn get_extra(&self, register: u16) -> Option<f32> {
for entry in &self.extra {
match entry {
Some(ev) if ev.register == register => return Some(ev.value),
None => return None,
_ => {}
}
}
None
}
}
fn parse_generic(register: u16, value: Value) -> f32 {
use crate::scaling;
let reg = Register::from_address(register);
let scaling = match reg {
Some(Register::Position)
| Some(Register::AbsPosition)
| Some(Register::CommandPosition)
| Some(Register::CommandStopPosition)
| Some(Register::CommandStayWithinLowerBound)
| Some(Register::CommandStayWithinUpperBound)
| Some(Register::ControlPosition)
| Some(Register::ControlPositionError)
| Some(Register::Encoder0Position)
| Some(Register::Encoder1Position)
| Some(Register::Encoder2Position) => &scaling::POSITION,
Some(Register::Velocity)
| Some(Register::CommandVelocity)
| Some(Register::CommandVelocityLimit)
| Some(Register::ControlVelocity)
| Some(Register::ControlVelocityError)
| Some(Register::Encoder0Velocity)
| Some(Register::Encoder1Velocity)
| Some(Register::Encoder2Velocity) => &scaling::VELOCITY,
Some(Register::Torque)
| Some(Register::CommandFeedforwardTorque)
| Some(Register::CommandPositionMaxTorque)
| Some(Register::ControlTorque)
| Some(Register::ControlTorqueError)
| Some(Register::PositionKp)
| Some(Register::PositionKi)
| Some(Register::PositionKd)
| Some(Register::PositionFeedforward)
| Some(Register::PositionCommand) => &scaling::TORQUE,
Some(Register::QCurrent)
| Some(Register::DCurrent)
| Some(Register::CommandQCurrent)
| Some(Register::CommandDCurrent)
| Some(Register::CommandFixedCurrentOverride) => &scaling::CURRENT,
Some(Register::Voltage)
| Some(Register::VoltagePhaseA)
| Some(Register::VoltagePhaseB)
| Some(Register::VoltagePhaseC)
| Some(Register::VFocVoltage)
| Some(Register::VoltageDqD)
| Some(Register::VoltageDqQ)
| Some(Register::CommandFixedVoltageOverride) => &scaling::VOLTAGE,
Some(Register::Temperature) | Some(Register::MotorTemperature) => &scaling::TEMPERATURE,
Some(Register::Power) => &scaling::POWER,
Some(Register::CommandTimeout) | Some(Register::CommandStayWithinTimeout) => &scaling::TIME,
Some(Register::CommandAccelLimit) => &scaling::ACCELERATION,
Some(Register::CommandKpScale)
| Some(Register::CommandKdScale)
| Some(Register::CommandIlimitScale)
| Some(Register::PwmPhaseA)
| Some(Register::PwmPhaseB)
| Some(Register::PwmPhaseC)
| Some(Register::Aux1PwmInputDutyCycle)
| Some(Register::Aux2PwmInputDutyCycle) => &scaling::PWM,
_ => &scaling::INT,
};
value.to_f32(scaling)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_format_default() {
let format = QueryFormat::default();
assert_eq!(format.mode, Resolution::Int8);
assert_eq!(format.position, Resolution::Float);
assert_eq!(format.q_current, Resolution::Ignore);
}
fn bytes(frame: &CanFdFrame) -> &[u8] {
&frame.data[..frame.size as usize]
}
#[test]
fn test_query_format_serialize() {
let format = QueryFormat::default();
let mut frame = CanFdFrame::new();
let reply_size = format.serialize(&mut frame);
assert_eq!(reply_size, 22);
assert_eq!(bytes(&frame), &[0x11, 0x00, 0x1f, 0x01, 0x13, 0x0d]);
}
#[test]
fn test_query_format_serialize_with_extras() {
let mut format = QueryFormat::minimal();
format.add_extra(0x100, Resolution::Float);
format.add_extra(0x102, Resolution::Int16);
let mut frame = CanFdFrame::new();
let reply_size = format.serialize(&mut frame);
assert_eq!(reply_size, 29);
assert_eq!(
bytes(&frame),
&[0x11, 0x00, 0x1f, 0x01, 0x1d, 0x80, 0x02, 0x15, 0x82, 0x02]
);
}
#[test]
fn test_query_format_serialize_with_distant_extras() {
let mut format = QueryFormat::minimal();
format.add_extra(0x010, Resolution::Int32);
format.add_extra(0x100, Resolution::Float);
let mut frame = CanFdFrame::new();
let reply_size = format.serialize(&mut frame);
assert_eq!(reply_size, 30);
assert_eq!(
bytes(&frame),
&[0x11, 0x00, 0x1f, 0x01, 0x19, 0x10, 0x1d, 0x80, 0x02]
);
}
#[test]
fn test_query_result_parse() {
let data = [
0x21, 0x00, 0x0a, 0x2d, 0x01, 0x00, 0x00, 0x00, 0x3f, ];
let result = QueryResult::parse_data(&data);
assert_eq!(result.mode, Mode::Position);
assert!((result.position - 0.5).abs() < 0.001);
}
#[test]
fn test_query_result_parse_extras() {
let data = [
0x21, 0x00, 0x0a, 0x2d, 0x80, 0x02, 0x00, 0x00, 0xc8, 0x42, 0x25, 0x82, 0x02, 0x39, 0x30, ];
let result = QueryResult::parse_data(&data);
assert_eq!(result.mode, Mode::Position);
assert!(result.extra[0].is_some());
let e0 = result.extra[0].unwrap();
assert_eq!(e0.register, 0x100);
assert!((e0.value - 100.0).abs() < 0.001);
assert!(result.extra[1].is_some());
let e1 = result.extra[1].unwrap();
assert_eq!(e1.register, 0x102);
assert_eq!(e1.value as i32, 12345);
assert!(result.extra[2].is_none());
assert!((result.get_extra(0x100).unwrap() - 100.0).abs() < 0.001);
assert_eq!(result.get_extra(0x102).unwrap() as i32, 12345);
assert!(result.get_extra(0x999).is_none());
}
}