kernelvex 0.2.2

VEX Controls library
Documentation
use crate::control::pid::{AngularPid, Pid};
use crate::dt::model::Tank;
use crate::util::si::QAngle;
use crate::{DifferentialDrive, MotorGroup, TrackingRig, TrackingWheel};
use vexide_async::time::sleep;
use vexide_devices::smart::imu::InertialSensor;
use core::time::Duration;

pub struct OdomChassis {
    dt: DifferentialDrive,
    imu: InertialSensor,
    tracking: TrackingRig,
}

impl OdomChassis {
    pub fn new(dt: DifferentialDrive, imu: InertialSensor, tracking: TrackingRig) -> Self {
        Self { dt, imu,  tracking}
    }

    pub async fn turn(&mut self, target: QAngle) -> Result<(), TurnError> {
        let angle_tolerance = QAngle::from_degrees(2.0);
        let max_output = 6.0;

        let mut pid = AngularPid::new()
            .set_gains(1.0, 0.0, 0.5)
            .with_output_limits(
                QAngle::from_radians(-max_output),
                QAngle::from_radians(max_output),
            );

        loop {
            let current_heading = self.heading();
            let error = (target - current_heading).remainder(QAngle::TAU);

            if error.abs().as_radians() <= angle_tolerance.as_radians() {
                self.dt.drive_tank(0.0, 0.0).await.map_err(TurnError::Drive)?;
                break;
            }

            let output = pid.calculate(target, current_heading);

            let turn_rate = output.as_radians() / max_output;
            let left = turn_rate;
            let right = -turn_rate;

            self.dt.drive_tank(left, right).await.map_err(TurnError::Drive)?;
        }

        Ok(())
    }

    pub async fn shoot(&mut self, target_rpm: i32, motor: &mut MotorGroup) -> Result<(), ShootError> {
        let mut pid = Pid::new()
            .set_gains(0.1, 0.001, 0.05)
            .with_output_limits(-12.0, 12.0);

        let mut stable_count = 0;
        let stable_threshold = 10;

        loop {
            let current_rpm = motor.velocity().await.unwrap_or(0);

            let error = (target_rpm - current_rpm) as f64;

            if error.abs() <= 10.0 {
                stable_count += 1;
                if stable_count >= stable_threshold {
                    break;
                }
            } else {
                stable_count = 0;
            }

            let output = pid.calculate(target_rpm as f64, current_rpm as f64);

            motor.set_voltage(output).await.map_err(ShootError::Motor)?;
            sleep(Duration::from_millis(10)).await;
        }

        Ok(())
    }

    pub fn heading(&self) -> QAngle {
        self.imu.heading()
            .map(|a| QAngle::from_radians(a.as_radians()))
            .unwrap_or(QAngle::from_radians(0.0))
    }
}

#[derive(Debug)]
pub enum TurnError {
    Drive(crate::util::utils::GroupErrors),
}

#[derive(Debug)]
pub enum ShootError {
    Motor(crate::util::utils::GroupErrors),
}