use core::{
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use bitflags::bitflags;
use pros_core::{
bail_on,
error::{take_errno, FromErrno, PortError},
map_errno,
time::Instant,
};
use pros_sys::{PROS_ERR, PROS_ERR_F};
use snafu::Snafu;
use super::{SmartDevice, SmartDeviceType, SmartPort};
#[derive(Debug, Eq, PartialEq)]
pub struct InertialSensor {
port: SmartPort,
}
impl InertialSensor {
pub const CALIBRATION_TIMEOUT: Duration = Duration::from_secs(3);
pub const MIN_DATA_RATE: Duration = Duration::from_millis(5);
pub const fn new(port: SmartPort) -> Self {
Self { port }
}
pub fn calibrate_blocking(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_reset_blocking(self.port.index())
});
Ok(())
}
pub fn calibrate(&mut self) -> InertialCalibrateFuture {
InertialCalibrateFuture::Calibrate(self.port.index())
}
pub fn is_calibrating(&mut self) -> Result<bool, InertialError> {
Ok(self.status()?.contains(InertialStatus::CALIBRATING))
}
pub fn rotation(&self) -> Result<f64, InertialError> {
Ok(bail_on!(PROS_ERR_F, unsafe {
pros_sys::imu_get_rotation(self.port.index())
}))
}
pub fn heading(&self) -> Result<f64, InertialError> {
Ok(bail_on!(PROS_ERR_F, unsafe {
pros_sys::imu_get_heading(self.port.index())
}))
}
pub fn pitch(&self) -> Result<f64, InertialError> {
Ok(bail_on!(PROS_ERR_F, unsafe {
pros_sys::imu_get_pitch(self.port.index())
}))
}
pub fn roll(&self) -> Result<f64, InertialError> {
Ok(bail_on!(PROS_ERR_F, unsafe {
pros_sys::imu_get_roll(self.port.index())
}))
}
pub fn yaw(&self) -> Result<f64, InertialError> {
Ok(bail_on!(PROS_ERR_F, unsafe {
pros_sys::imu_get_yaw(self.port.index())
}))
}
pub fn status(&self) -> Result<InertialStatus, InertialError> {
let bits = bail_on!(pros_sys::E_IMU_STATUS_ERROR, unsafe {
pros_sys::imu_get_status(self.port.index())
});
Ok(InertialStatus::from_bits_retain(bits))
}
pub fn quaternion(&self) -> Result<Quaternion, InertialError> {
unsafe { pros_sys::imu_get_quaternion(self.port.index()).try_into() }
}
pub fn euler(&self) -> Result<Euler, InertialError> {
unsafe { pros_sys::imu_get_euler(self.port.index()).try_into() }
}
pub fn gyro_rate(&self) -> Result<InertialRaw, InertialError> {
unsafe { pros_sys::imu_get_gyro_rate(self.port.index()).try_into() }
}
pub fn accel(&self) -> Result<InertialRaw, InertialError> {
unsafe { pros_sys::imu_get_accel(self.port.index()).try_into() }
}
pub fn zero_heading(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_tare_heading(self.port.index())
});
Ok(())
}
pub fn zero_rotation(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_tare_rotation(self.port.index())
});
Ok(())
}
pub fn zero_pitch(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_tare_pitch(self.port.index())
});
Ok(())
}
pub fn zero_roll(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_tare_roll(self.port.index())
});
Ok(())
}
pub fn zero_yaw(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_tare_yaw(self.port.index())
});
Ok(())
}
pub fn zero_euler(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_tare_euler(self.port.index())
});
Ok(())
}
pub fn zero(&mut self) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe { pros_sys::imu_tare(self.port.index()) });
Ok(())
}
pub fn set_euler(&mut self, euler: Euler) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_set_euler(self.port.index(), euler.into())
});
Ok(())
}
pub fn set_rotation(&mut self, rotation: f64) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_set_rotation(self.port.index(), rotation)
});
Ok(())
}
pub fn set_heading(&mut self, heading: f64) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_set_heading(self.port.index(), heading)
});
Ok(())
}
pub fn set_pitch(&mut self, pitch: f64) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_set_pitch(self.port.index(), pitch)
});
Ok(())
}
pub fn set_roll(&mut self, roll: f64) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_set_roll(self.port.index(), roll)
});
Ok(())
}
pub fn set_yaw(&mut self, yaw: f64) -> Result<(), InertialError> {
bail_on!(PROS_ERR, unsafe {
pros_sys::imu_set_yaw(self.port.index(), yaw)
});
Ok(())
}
pub fn set_data_rate(&mut self, data_rate: Duration) -> Result<(), InertialError> {
unsafe {
let rate_ms = if data_rate > Self::MIN_DATA_RATE {
if let Ok(rate) = u32::try_from(data_rate.as_millis()) {
rate
} else {
return Err(InertialError::InvalidDataRate);
}
} else {
return Err(InertialError::InvalidDataRate);
};
bail_on!(
PROS_ERR,
pros_sys::imu_set_data_rate(self.port.index(), rate_ms)
);
}
Ok(())
}
}
impl SmartDevice for InertialSensor {
fn port_index(&self) -> u8 {
self.port.index()
}
fn device_type(&self) -> SmartDeviceType {
SmartDeviceType::Imu
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct Quaternion {
pub x: f64,
pub y: f64,
pub z: f64,
pub w: f64,
}
impl TryFrom<pros_sys::quaternion_s_t> for Quaternion {
type Error = InertialError;
fn try_from(value: pros_sys::quaternion_s_t) -> Result<Quaternion, InertialError> {
Ok(Self {
x: bail_on!(PROS_ERR_F, value.x),
y: value.y,
z: value.z,
w: value.w,
})
}
}
impl From<Quaternion> for pros_sys::quaternion_s_t {
fn from(value: Quaternion) -> Self {
pros_sys::quaternion_s_t {
x: value.x,
y: value.y,
z: value.z,
w: value.w,
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct Euler {
pub pitch: f64,
pub roll: f64,
pub yaw: f64,
}
impl TryFrom<pros_sys::euler_s_t> for Euler {
type Error = InertialError;
fn try_from(value: pros_sys::euler_s_t) -> Result<Euler, InertialError> {
Ok(Self {
pitch: bail_on!(PROS_ERR_F, value.pitch),
roll: value.roll,
yaw: value.yaw,
})
}
}
impl From<Euler> for pros_sys::euler_s_t {
fn from(val: Euler) -> Self {
pros_sys::euler_s_t {
pitch: val.pitch,
roll: val.roll,
yaw: val.yaw,
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct InertialRaw {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl TryFrom<pros_sys::imu_raw_s> for InertialRaw {
type Error = InertialError;
fn try_from(value: pros_sys::imu_raw_s) -> Result<InertialRaw, InertialError> {
Ok(Self {
x: bail_on!(PROS_ERR_F, value.x),
y: value.y,
z: value.z,
})
}
}
bitflags! {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct InertialStatus: u32 {
const CALIBRATING = pros_sys::E_IMU_STATUS_CALIBRATING;
}
}
#[derive(Debug, Clone, Copy)]
pub enum InertialCalibrateFuture {
Calibrate(u8),
Waiting(u8, Instant),
}
impl core::future::Future for InertialCalibrateFuture {
type Output = Result<(), InertialError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match *self {
Self::Calibrate(port) => match unsafe { pros_sys::imu_reset(port) } {
PROS_ERR => {
let errno = take_errno();
Poll::Ready(Err(InertialError::from_errno(errno)
.unwrap_or_else(|| panic!("Unknown errno code {errno}"))))
}
_ => {
*self = Self::Waiting(port, Instant::now());
cx.waker().wake_by_ref();
Poll::Pending
}
},
Self::Waiting(port, timestamp) => {
let is_calibrating = match unsafe { pros_sys::imu_get_status(port) } {
pros_sys::E_IMU_STATUS_ERROR => {
let errno = take_errno();
return Poll::Ready(Err(InertialError::from_errno(take_errno())
.unwrap_or_else(|| panic!("Unknown errno code {errno}"))));
}
value => (value & pros_sys::E_IMU_STATUS_CALIBRATING) != 0,
};
if !is_calibrating {
return Poll::Ready(Ok(()));
} else if timestamp.elapsed() > InertialSensor::CALIBRATION_TIMEOUT {
return Poll::Ready(Err(InertialError::CalibrationTimedOut));
}
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
}
#[derive(Debug, Snafu)]
pub enum InertialError {
CalibrationTimedOut,
InvalidDataRate,
#[snafu(display("{source}"), context(false))]
Port {
source: PortError,
},
}
map_errno! {
InertialError {
EAGAIN => Self::CalibrationTimedOut,
}
inherit PortError;
}