use linux_embedded_hal::I2cdev;
use pwm_pca9685::{Channel, Pca9685};
use crate::error::MotorError;
use crate::motor::{Motor, MotorDriver};
struct DcChannels {
ref_channel: Channel,
forward_channel: Channel,
backward_channel: Channel,
}
const fn channels_for(motor: Motor) -> DcChannels {
match motor {
Motor::Motor1 => DcChannels {
ref_channel: Channel::C8,
forward_channel: Channel::C9,
backward_channel: Channel::C10,
},
Motor::Motor2 => DcChannels {
ref_channel: Channel::C13,
forward_channel: Channel::C11,
backward_channel: Channel::C12,
},
Motor::Motor3 => DcChannels {
ref_channel: Channel::C2,
forward_channel: Channel::C3,
backward_channel: Channel::C4,
},
Motor::Motor4 => DcChannels {
ref_channel: Channel::C7,
forward_channel: Channel::C5,
backward_channel: Channel::C6,
},
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StopWrite {
On,
Off,
}
impl StopWrite {
const fn name(self) -> &'static str {
match self {
Self::On => "set_channel_on",
Self::Off => "set_channel_off",
}
}
}
fn stop_motor_outputs<E: std::fmt::Debug>(
motor: Motor,
mut write: impl FnMut(Channel, StopWrite, u16) -> Result<(), E>,
) -> Result<(), MotorError> {
let channels = channels_for(motor);
let mut failures = Vec::new();
for channel in [
channels.ref_channel,
channels.forward_channel,
channels.backward_channel,
] {
for operation in [StopWrite::On, StopWrite::Off] {
if let Err(error) = write(channel, operation, 0) {
failures.push(format!(
"{motor:?} channel {channel:?} {}(0): {error:?}",
operation.name()
));
}
}
}
if failures.is_empty() {
Ok(())
} else {
Err(MotorError::Pwm(format!(
"stop_motor failed: {}",
failures.join("; ")
)))
}
}
pub struct Pca9685MotorBoard {
pwm: std::sync::Mutex<Pca9685<I2cdev>>,
}
impl Pca9685MotorBoard {
pub fn new(i2c_bus: &str, address: u8) -> Result<Self, MotorError> {
let dev = I2cdev::new(i2c_bus)
.map_err(|e| MotorError::Init(format!("failed to open I2C bus {i2c_bus}: {e}")))?;
let mut pwm = Pca9685::new(dev, address).map_err(|e| {
MotorError::Init(format!("failed to create PCA9685 at {address:#04x}: {e:?}"))
})?;
pwm.set_prescale(100)
.map_err(|e| MotorError::Pwm(format!("failed to set prescale: {e:?}")))?;
pwm.enable()
.map_err(|e| MotorError::Pwm(format!("failed to enable PCA9685: {e:?}")))?;
Ok(Self {
pwm: std::sync::Mutex::new(pwm),
})
}
}
impl Drop for Pca9685MotorBoard {
fn drop(&mut self) {
let _ = self.stop_all();
}
}
impl MotorDriver for Pca9685MotorBoard {
fn set_motor_speed(&self, motor: Motor, speed: u16, forward: bool) -> Result<(), MotorError> {
let channels = channels_for(motor);
let mut pwm = self
.pwm
.lock()
.map_err(|e| MotorError::Pwm(format!("mutex poisoned: {e}")))?;
pwm.set_channel_on(channels.ref_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_off(channels.ref_channel, 4095)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
if forward {
pwm.set_channel_on(channels.forward_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_off(channels.forward_channel, speed)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_on(channels.backward_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_off(channels.backward_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
} else {
pwm.set_channel_on(channels.forward_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_off(channels.forward_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_on(channels.backward_channel, 0)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
pwm.set_channel_off(channels.backward_channel, speed)
.map_err(|e| MotorError::Pwm(format!("{e:?}")))?;
}
Ok(())
}
fn stop_motor(&self, motor: Motor) -> Result<(), MotorError> {
let mut pwm = self.pwm.lock().map_err(|e| {
MotorError::Pwm(format!("{motor:?} stop_motor lock: mutex poisoned: {e}"))
})?;
stop_motor_outputs(motor, |channel, operation, value| match operation {
StopWrite::On => pwm.set_channel_on(channel, value),
StopWrite::Off => pwm.set_channel_off(channel, value),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn motor_stop_attempts_all_writes_and_collects_failures() {
for (motor, channels) in [
(Motor::Motor1, [Channel::C8, Channel::C9, Channel::C10]),
(Motor::Motor2, [Channel::C13, Channel::C11, Channel::C12]),
(Motor::Motor3, [Channel::C2, Channel::C3, Channel::C4]),
(Motor::Motor4, [Channel::C7, Channel::C5, Channel::C6]),
] {
let expected = [
(channels[0], StopWrite::On, 0),
(channels[0], StopWrite::Off, 0),
(channels[1], StopWrite::On, 0),
(channels[1], StopWrite::Off, 0),
(channels[2], StopWrite::On, 0),
(channels[2], StopWrite::Off, 0),
];
for failures in 0_u8..64 {
let mut attempts = Vec::new();
let result = stop_motor_outputs(motor, |channel, operation, value| {
let index = attempts.len();
attempts.push((channel, operation, value));
if failures & (1 << index) == 0 {
Ok(())
} else {
Err(format!("injected failure {index}"))
}
});
assert_eq!(attempts, expected);
if failures == 0 {
assert!(result.is_ok());
continue;
}
let Err(MotorError::Pwm(message)) = result else {
panic!("expected stop failure for {motor:?}, mask {failures}");
};
for (index, (channel, _, _)) in expected.iter().enumerate() {
let operation = if index % 2 == 0 {
"set_channel_on"
} else {
"set_channel_off"
};
let diagnostic = format!(
"{motor:?} channel {channel:?} {operation}(0): \"injected failure {index}\""
);
assert_eq!(message.contains(&diagnostic), failures & (1 << index) != 0);
}
assert_eq!(
message.matches("injected failure").count(),
failures.count_ones() as usize
);
}
}
}
}