pros_devices/smart/
motor.rs

1//! V5 Smart Motors
2
3use core::time::Duration;
4
5use bitflags::bitflags;
6use pros_core::{bail_on, error::PortError, map_errno};
7use pros_sys::{PROS_ERR, PROS_ERR_F};
8use snafu::Snafu;
9
10use super::{SmartDevice, SmartDeviceTimestamp, SmartDeviceType, SmartPort};
11use crate::Position;
12
13/// The basic motor struct.
14#[derive(Debug, PartialEq)]
15pub struct Motor {
16    port: SmartPort,
17    target: MotorControl,
18}
19
20/// Represents a possible target for a [`Motor`].
21#[derive(Clone, Copy, Debug, PartialEq)]
22pub enum MotorControl {
23    /// Motor is braking using a [`BrakeMode`].
24    Brake(BrakeMode),
25
26    /// Motor is outputting a raw voltage.
27    Voltage(f64),
28
29    /// Motor is attempting to hold a velocity using internal PID control.
30    Velocity(i32),
31
32    /// Motor is attempting to reach a position using internal PID control.
33    Position(Position, i32),
34}
35
36/// Represents a possible direction that a motor can be configured as.
37#[derive(Debug, Clone, Copy, Eq, PartialEq)]
38pub enum Direction {
39    /// Motor rotates in the forward direction.
40    Forward,
41
42    /// Motor rotates in the reverse direction.
43    Reverse,
44}
45
46impl Direction {
47    /// Returns `true` if the level is [`Forward`].
48    pub const fn is_forward(&self) -> bool {
49        match self {
50            Self::Forward => true,
51            Self::Reverse => false,
52        }
53    }
54
55    /// Returns `true` if the level is [`Reverse`].
56    pub const fn is_reverse(&self) -> bool {
57        match self {
58            Self::Forward => false,
59            Self::Reverse => true,
60        }
61    }
62}
63
64impl Motor {
65    /// The maximum voltage value that can be sent to a [`Motor`].
66    pub const MAX_VOLTAGE: f64 = 12.0;
67
68    /// The rate at which data can be read from a [`Motor`].
69    pub const DATA_READ_RATE: Duration = Duration::from_millis(10);
70
71    /// The rate at which data can be written to a [`Motor`].
72    pub const DATA_WRITE_RATE: Duration = Duration::from_millis(5);
73
74    /// Create a new motor from a smart port index.
75    pub fn new(
76        port: SmartPort,
77        gearset: Gearset,
78        direction: Direction,
79    ) -> Result<Self, MotorError> {
80        bail_on!(PROS_ERR, unsafe {
81            pros_sys::motor_set_encoder_units(port.index() as i8, pros_sys::E_MOTOR_ENCODER_DEGREES)
82        });
83
84        let mut motor = Self {
85            port,
86            target: MotorControl::Voltage(0.0),
87        };
88
89        motor.set_gearset(gearset)?;
90        motor.set_direction(direction)?;
91
92        Ok(motor)
93    }
94
95    /// Sets the target that the motor should attempt to reach.
96    ///
97    /// This could be a voltage, velocity, position, or even brake mode.
98    pub fn set_target(&mut self, target: MotorControl) -> Result<(), MotorError> {
99        match target {
100            MotorControl::Brake(mode) => unsafe {
101                bail_on!(
102                    PROS_ERR,
103                    pros_sys::motor_set_brake_mode(self.port.index() as i8, mode.into())
104                );
105                bail_on!(PROS_ERR, pros_sys::motor_brake(self.port.index() as i8));
106            },
107            MotorControl::Velocity(rpm) => unsafe {
108                bail_on!(
109                    PROS_ERR,
110                    pros_sys::motor_set_brake_mode(
111                        self.port.index() as i8,
112                        pros_sys::E_MOTOR_BRAKE_COAST
113                    )
114                );
115                bail_on!(
116                    PROS_ERR,
117                    pros_sys::motor_move_velocity(self.port.index() as i8, rpm)
118                );
119            },
120            MotorControl::Voltage(volts) => {
121                bail_on!(PROS_ERR, unsafe {
122                    pros_sys::motor_move_voltage(self.port.index() as i8, (volts * 1000.0) as i32)
123                });
124            }
125            MotorControl::Position(position, velocity) => unsafe {
126                bail_on!(
127                    PROS_ERR,
128                    pros_sys::motor_set_brake_mode(
129                        self.port.index() as i8,
130                        pros_sys::E_MOTOR_BRAKE_COAST
131                    )
132                );
133                bail_on!(
134                    PROS_ERR,
135                    pros_sys::motor_move_absolute(
136                        self.port.index() as i8,
137                        position.into_degrees(),
138                        velocity,
139                    )
140                );
141            },
142        }
143
144        self.target = target;
145        Ok(())
146    }
147
148    /// Sets the motors target to a given [`BrakeMode`].
149    pub fn brake(&mut self, mode: BrakeMode) -> Result<(), MotorError> {
150        self.set_target(MotorControl::Brake(mode))
151    }
152
153    /// Spins the motor at a target velocity.
154    ///
155    /// This velocity corresponds to different actual speeds in RPM depending on the gearset used for the motor.
156    /// Velocity is held with an internal PID controller to ensure consistent speed, as opposed to setting the
157    /// motor's voltage.
158    pub fn set_velocity(&mut self, rpm: i32) -> Result<(), MotorError> {
159        self.set_target(MotorControl::Velocity(rpm))
160    }
161
162    /// Sets the motor's ouput voltage.
163    ///
164    /// This voltage value spans from -12 (fully spinning reverse) to +12 (fully spinning forwards) volts, and
165    /// controls the raw output of the motor.
166    pub fn set_voltage(&mut self, volts: f64) -> Result<(), MotorError> {
167        self.set_target(MotorControl::Voltage(volts))
168    }
169
170    /// Sets an absolute position target for the motor to attempt to reach.
171    pub fn set_position_target(
172        &mut self,
173        position: Position,
174        velocity: i32,
175    ) -> Result<(), MotorError> {
176        self.set_target(MotorControl::Position(position, velocity))
177    }
178
179    /// Changes the output velocity for a profiled movement (motor_move_absolute or motor_move_relative).
180    ///
181    /// This will have no effect if the motor is not following a profiled movement.
182    pub fn update_profiled_velocity(&mut self, velocity: i32) -> Result<(), MotorError> {
183        bail_on!(PROS_ERR, unsafe {
184            pros_sys::motor_modify_profiled_velocity(self.port.index() as i8, velocity)
185        });
186
187        match self.target {
188            MotorControl::Position(position, _) => {
189                self.target = MotorControl::Position(position, velocity)
190            }
191            _ => {}
192        }
193
194        Ok(())
195    }
196
197    /// Get the current [`MotorControl`] value that the motor is attempting to use.
198    pub fn target(&self) -> MotorControl {
199        self.target
200    }
201
202    /// Sets the gearset of the motor.
203    pub fn set_gearset(&mut self, gearset: Gearset) -> Result<(), MotorError> {
204        bail_on!(PROS_ERR, unsafe {
205            pros_sys::motor_set_gearing(self.port.index() as i8, gearset as i32)
206        });
207        Ok(())
208    }
209
210    /// Gets the gearset of the motor.
211    pub fn gearset(&self) -> Result<Gearset, MotorError> {
212        unsafe { pros_sys::motor_get_gearing(self.port.index() as i8).try_into() }
213    }
214
215    /// Gets the estimated angular velocity (RPM) of the motor.
216    pub fn velocity(&self) -> Result<f64, MotorError> {
217        Ok(bail_on!(PROS_ERR_F, unsafe {
218            pros_sys::motor_get_actual_velocity(self.port.index() as i8)
219        }))
220    }
221
222    /// Returns the power drawn by the motor in Watts.
223    pub fn power(&self) -> Result<f64, MotorError> {
224        Ok(bail_on!(PROS_ERR_F, unsafe {
225            pros_sys::motor_get_power(self.port.index() as i8)
226        }))
227    }
228
229    /// Returns the torque output of the motor in Nm.
230    pub fn torque(&self) -> Result<f64, MotorError> {
231        Ok(bail_on!(PROS_ERR_F, unsafe {
232            pros_sys::motor_get_torque(self.port.index() as i8)
233        }))
234    }
235
236    /// Returns the voltage the motor is drawing in volts.
237    pub fn voltage(&self) -> Result<f64, MotorError> {
238        // docs say this function returns PROS_ERR_F but it actually returns PROS_ERR
239        let millivolts = bail_on!(PROS_ERR, unsafe {
240            pros_sys::motor_get_voltage(self.port.index() as i8)
241        });
242        Ok(millivolts as f64 / 1000.0)
243    }
244
245    /// Returns the current position of the motor.
246    pub fn position(&self) -> Result<Position, MotorError> {
247        Ok(Position::from_degrees(bail_on!(PROS_ERR_F, unsafe {
248            pros_sys::motor_get_position(self.port.index() as i8)
249        })))
250    }
251
252    /// Returns the most recently recorded raw encoder tick data from the motor's IME
253    /// along with a timestamp of the internal clock of the motor indicating when the
254    /// data was recorded.
255    pub fn raw_position(&self) -> Result<(i32, SmartDeviceTimestamp), MotorError> {
256        let timestamp = 0 as *mut u32;
257
258        // PROS docs claim that this function gets the position *at* a recorded timestamp,
259        // but in reality the "timestamp" paramater is a mutable outvalue. The function
260        // outputs the most recent recorded posision AND the timestamp it was measured at,
261        // rather than a position at a requested timestamp.
262        let ticks = bail_on!(PROS_ERR, unsafe {
263            pros_sys::motor_get_raw_position(self.port.index() as i8, timestamp)
264        });
265
266        Ok((ticks, SmartDeviceTimestamp(unsafe { *timestamp })))
267    }
268
269    /// Returns the electrical current draw of the motor in amps.
270    pub fn current(&self) -> Result<f64, MotorError> {
271        Ok(bail_on!(PROS_ERR, unsafe {
272            pros_sys::motor_get_current_draw(self.port.index() as i8)
273        }) as f64
274            / 1000.0)
275    }
276
277    /// Gets the efficiency of the motor from a range of [0.0, 1.0].
278    ///
279    /// An efficiency of 1.0 means that the motor is moving electrically while
280    /// drawing no electrical power, and an efficiency of 0.0 means that the motor
281    /// is drawing power but not moving.
282    pub fn efficiency(&self) -> Result<f64, MotorError> {
283        Ok(bail_on!(PROS_ERR_F, unsafe {
284            pros_sys::motor_get_efficiency(self.port.index() as i8)
285        }) / 100.0)
286    }
287
288    /// Sets the current encoder position to zero without moving the motor.
289    /// Analogous to taring or resetting the encoder to the current position.
290    pub fn zero(&mut self) -> Result<(), MotorError> {
291        bail_on!(PROS_ERR, unsafe {
292            pros_sys::motor_tare_position(self.port.index() as i8)
293        });
294        Ok(())
295    }
296
297    /// Sets the current encoder position to the given position without moving the motor.
298    /// Analogous to taring or resetting the encoder so that the new position is equal to the given position.
299    pub fn set_position(&mut self, position: Position) -> Result<(), MotorError> {
300        bail_on!(PROS_ERR, unsafe {
301            pros_sys::motor_set_zero_position(self.port.index() as i8, position.into_degrees())
302        });
303        Ok(())
304    }
305
306    /// Sets the current limit for the motor in amps.
307    pub fn set_current_limit(&mut self, limit: f64) -> Result<(), MotorError> {
308        bail_on!(PROS_ERR, unsafe {
309            pros_sys::motor_set_current_limit(self.port.index() as i8, (limit * 1000.0) as i32)
310        });
311        Ok(())
312    }
313
314    /// Sets the voltage limit for the motor in volts.
315    pub fn set_voltage_limit(&mut self, limit: f64) -> Result<(), MotorError> {
316        bail_on!(PROS_ERR, unsafe {
317            // Docs claim that this function takes volts, but this is incorrect. It takes millivolts,
318            // just like all other SDK voltage-related functions.
319            pros_sys::motor_set_voltage_limit(self.port.index() as i8, (limit * 1000.0) as i32)
320        });
321
322        Ok(())
323    }
324
325    /// Gets the current limit for the motor in amps.
326    pub fn current_limit(&self) -> Result<f64, MotorError> {
327        Ok(bail_on!(PROS_ERR, unsafe {
328            pros_sys::motor_get_current_limit(self.port.index() as i8)
329        }) as f64
330            / 1000.0)
331    }
332
333    // /// Gets the voltage limit for the motor if one has been explicitly set.
334    // /// NOTE: Broken until next kernel version due to mutex release bug.
335    // pub fn voltage_limit(&self) -> Result<f64, MotorError> {
336    //     // NOTE: PROS docs claim that this function will return zero if voltage is uncapped.
337    //     //
338    //     // From testing this does not appear to be true, so we don't need to perform any
339    //     // special checks for a zero return value.
340    //     Ok(bail_on!(PROS_ERR, unsafe {
341    //         pros_sys::motor_get_voltage_limit(self.port.index() as i8)
342    //     }) as f64
343    //         / 1000.0)
344    // }
345
346    /// Get the status flags of a motor.
347    pub fn status(&self) -> Result<MotorStatus, MotorError> {
348        let bits = bail_on!(PROS_ERR as u32, unsafe {
349            pros_sys::motor_get_flags(self.port.index() as i8)
350        });
351
352        // For some reason, PROS doesn't set errno if this flag is returned,
353        // even though it is by-definition an error (failing to retrieve flags).
354        if (bits & pros_sys::E_MOTOR_FLAGS_BUSY) != 0 {
355            return Err(MotorError::Busy);
356        }
357
358        Ok(MotorStatus::from_bits_retain(bits))
359    }
360
361    /// Get the fault flags of the motor.
362    pub fn faults(&self) -> Result<MotorFaults, MotorError> {
363        let bits = bail_on!(PROS_ERR as u32, unsafe {
364            pros_sys::motor_get_faults(self.port.index() as i8)
365        });
366
367        Ok(MotorFaults::from_bits_retain(bits))
368    }
369
370    /// Check if the motor's over temperature flag is set.
371    pub fn is_over_temperature(&self) -> Result<bool, MotorError> {
372        Ok(self.faults()?.contains(MotorFaults::OVER_TEMPERATURE))
373    }
374
375    /// Check if the motor's overcurrent flag is set.
376    pub fn is_over_current(&self) -> Result<bool, MotorError> {
377        Ok(self.faults()?.contains(MotorFaults::OVER_CURRENT))
378    }
379
380    /// Check if a H-bridge (motor driver) fault has occurred.
381    pub fn is_driver_fault(&self) -> Result<bool, MotorError> {
382        Ok(self.faults()?.contains(MotorFaults::DRIVER_FAULT))
383    }
384
385    /// Check if the motor's H-bridge has an overucrrent fault.
386    pub fn is_driver_over_current(&self) -> Result<bool, MotorError> {
387        Ok(self.faults()?.contains(MotorFaults::OVER_CURRENT))
388    }
389
390    /// Set the [`Direction`] of this motor.
391    pub fn set_direction(&mut self, direction: Direction) -> Result<(), MotorError> {
392        bail_on!(PROS_ERR, unsafe {
393            pros_sys::motor_set_reversed(self.port.index() as i8, direction.is_reverse())
394        });
395        Ok(())
396    }
397
398    /// Get the [`Direction`] of this motor.
399    pub fn direction(&self) -> Result<Direction, MotorError> {
400        let reversed = bail_on!(PROS_ERR, unsafe {
401            pros_sys::motor_is_reversed(self.port.index() as i8)
402        }) == 1;
403
404        Ok(match reversed {
405            false => Direction::Forward,
406            true => Direction::Reverse,
407        })
408    }
409
410    /// Adjusts the internal tuning constants of the motor when using velocity control.
411    ///
412    /// # Hardware Safety
413    ///
414    /// Modifying internal motor control is **dangerous**, and can result in permanent hardware damage
415    /// to smart motors if done incorrectly. Use these functions entirely at your own risk.
416    ///
417    /// VEX has chosen not to disclose the default constants used by smart motors, and currently
418    /// has no plans to do so. As such, the units and finer details of [`MotorTuningConstants`] are not
419    /// well-known or understood, as we have no reference for what these constants should look
420    /// like.
421    #[cfg(feature = "dangerous_motor_tuning")]
422    pub fn set_velocity_tuning_constants(
423        &mut self,
424        constants: MotorTuningConstants,
425    ) -> Result<(), MotorError> {
426        bail_on!(PROS_ERR, unsafe {
427            #[allow(deprecated)]
428            pros_sys::motor_set_pos_pid_full(self.port.index() as i8, constants.into())
429        });
430        Ok(())
431    }
432
433    /// Adjusts the internal tuning constants of the motor when using position control.
434    ///
435    /// # Hardware Safety
436    ///
437    /// Modifying internal motor control is **dangerous**, and can result in permanent hardware damage
438    /// to smart motors if done incorrectly. Use these functions entirely at your own risk.
439    ///
440    /// VEX has chosen not to disclose the default constants used by smart motors, and currently
441    /// has no plans to do so. As such, the units and finer details of [`MotorTuningConstants`] are not
442    /// well-known or understood, as we have no reference for what these constants should look
443    /// like.
444    #[cfg(feature = "dangerous_motor_tuning")]
445    pub fn set_position_tuning_constants(
446        &mut self,
447        constants: MotorTuningConstants,
448    ) -> Result<(), MotorError> {
449        bail_on!(PROS_ERR, unsafe {
450            #[allow(deprecated)]
451            pros_sys::motor_set_vel_pid_full(self.port.index() as i8, constants.into())
452        });
453        Ok(())
454    }
455}
456
457impl SmartDevice for Motor {
458    fn port_index(&self) -> u8 {
459        self.port.index()
460    }
461
462    fn device_type(&self) -> SmartDeviceType {
463        SmartDeviceType::Motor
464    }
465}
466
467/// Determines how a motor should act when braking.
468#[derive(Debug, Clone, Copy, Eq, PartialEq)]
469#[repr(i32)]
470pub enum BrakeMode {
471    /// Motor never brakes.
472    None = pros_sys::E_MOTOR_BRAKE_COAST,
473    /// Motor uses regenerative braking to slow down faster.
474    Brake = pros_sys::E_MOTOR_BRAKE_BRAKE,
475    /// Motor exerts force to hold the same position.
476    Hold = pros_sys::E_MOTOR_BRAKE_HOLD,
477}
478
479impl TryFrom<pros_sys::motor_brake_mode_e_t> for BrakeMode {
480    type Error = MotorError;
481
482    fn try_from(value: pros_sys::motor_brake_mode_e_t) -> Result<Self, MotorError> {
483        bail_on!(PROS_ERR, value);
484
485        Ok(match value {
486            pros_sys::E_MOTOR_BRAKE_COAST => Self::None,
487            pros_sys::E_MOTOR_BRAKE_BRAKE => Self::Brake,
488            pros_sys::E_MOTOR_BRAKE_HOLD => Self::Hold,
489            _ => unreachable!(),
490        })
491    }
492}
493
494impl From<BrakeMode> for pros_sys::motor_brake_mode_e_t {
495    fn from(value: BrakeMode) -> pros_sys::motor_brake_mode_e_t {
496        value as _
497    }
498}
499
500bitflags! {
501    /// The fault flags returned by a [`Motor`].
502    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
503    pub struct MotorFaults: u32 {
504        /// The motor's temperature is above its limit.
505        const OVER_TEMPERATURE = pros_sys::E_MOTOR_FAULT_MOTOR_OVER_TEMP;
506
507        /// The motor is over current.
508        const OVER_CURRENT = pros_sys::E_MOTOR_FAULT_OVER_CURRENT;
509
510        /// The motor's H-bridge has encountered a fault.
511        const DRIVER_FAULT = pros_sys::E_MOTOR_FAULT_DRIVER_FAULT;
512
513        /// The motor's H-bridge is over current.
514        const DRIVER_OVER_CURRENT = pros_sys::E_MOTOR_FAULT_DRV_OVER_CURRENT;
515    }
516}
517
518bitflags! {
519    /// The status bits returned by a [`Motor`].
520    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
521    pub struct MotorStatus: u32 {
522        /// The motor is currently near zero velocity.
523        #[deprecated(
524            since = "0.9.0",
525            note = "This flag will never be set by the hardware, even though it exists in the SDK. This may change in the future."
526        )]
527        const ZERO_VELOCITY = pros_sys::E_MOTOR_FLAGS_ZERO_VELOCITY;
528
529        /// The motor is at its zero position.
530        #[deprecated(
531            since = "0.9.0",
532            note = "This flag will never be set by the hardware, even though it exists in the SDK. This may change in the future."
533        )]
534        const ZERO_POSITION = pros_sys::E_MOTOR_FLAGS_ZERO_POSITION;
535
536        /// Cannot currently communicate to the motor
537        const BUSY = pros_sys::E_MOTOR_FLAGS_BUSY;
538    }
539}
540
541/// Internal gearset used by VEX smart motors.
542#[derive(Debug, Clone, Copy, PartialEq, Eq)]
543#[repr(i32)]
544pub enum Gearset {
545    /// 36:1 gear ratio
546    Red = pros_sys::E_MOTOR_GEAR_RED,
547    /// 18:1 gear ratio
548    Green = pros_sys::E_MOTOR_GEAR_GREEN,
549    /// 6:1 gear ratio
550    Blue = pros_sys::E_MOTOR_GEAR_BLUE,
551}
552
553impl Gearset {
554    /// 36:1 gear ratio (alias to `Self::Red`)
555    pub const RATIO_36: Gearset = Self::Red;
556    /// 18:1 gear ratio (alias to `Self::Green`)
557    pub const RATIO_18: Gearset = Self::Green;
558    /// 6:1 gear ratio (alias to `Self::Blue`)
559    pub const RATIO_6: Gearset = Self::Blue;
560
561    /// 100 rpm gearset (alias to `Self::Red`)
562    pub const RPM_100: Gearset = Self::Red;
563    /// 200 rpm (alias to `Self::Green`)
564    pub const RPM_200: Gearset = Self::Green;
565    /// 600 rpm (alias to `Self::Blue`)
566    pub const RPM_600: Gearset = Self::Blue;
567
568    /// Rated max speed for a smart motor with a [`Red`] gearset.
569    pub const MAX_RED_RPM: f64 = 100.0;
570    /// Rated speed for a smart motor with a [`Green`] gearset.
571    pub const MAX_GREEN_RPM: f64 = 200.0;
572    /// Rated speed for a smart motor with a [`Blue`] gearset.
573    pub const MAX_BLUE_RPM: f64 = 600.0;
574
575    /// Number of encoder ticks per revolution for the [`Red`] gearset.
576    pub const RED_TICKS_PER_REVOLUTION: u32 = 1800;
577    /// Number of encoder ticks per revolution for the [`Green`] gearset.
578    pub const GREEN_TICKS_PER_REVOLUTION: u32 = 900;
579    /// Number of encoder ticks per revolution for the [`Blue`] gearset.
580    pub const BLUE_TICKS_PER_REVOLUTION: u32 = 300;
581
582    /// Get the rated maximum speed for this motor gearset.
583    pub const fn max_rpm(&self) -> f64 {
584        match self {
585            Self::Red => Self::MAX_RED_RPM,
586            Self::Green => Self::MAX_GREEN_RPM,
587            Self::Blue => Self::MAX_BLUE_RPM,
588        }
589    }
590
591    /// Get the number of encoder ticks per revolution for this motor gearset.
592    pub const fn ticks_per_revolution(&self) -> u32 {
593        match self {
594            Self::Red => Self::RED_TICKS_PER_REVOLUTION,
595            Self::Green => Self::GREEN_TICKS_PER_REVOLUTION,
596            Self::Blue => Self::BLUE_TICKS_PER_REVOLUTION,
597        }
598    }
599}
600
601impl From<Gearset> for pros_sys::motor_gearset_e_t {
602    fn from(value: Gearset) -> Self {
603        value as _
604    }
605}
606
607impl TryFrom<pros_sys::motor_gearset_e_t> for Gearset {
608    type Error = MotorError;
609
610    fn try_from(value: pros_sys::motor_gearset_e_t) -> Result<Self, MotorError> {
611        bail_on!(PROS_ERR, value);
612
613        Ok(match value {
614            pros_sys::E_MOTOR_GEAR_RED => Self::Red,
615            pros_sys::E_MOTOR_GEAR_GREEN => Self::Green,
616            pros_sys::E_MOTOR_GEAR_BLUE => Self::Blue,
617            _ => unreachable!(),
618        })
619    }
620}
621
622/// Holds the information about a Motor's position or velocity PID controls.
623///
624/// # Hardware Safety
625///
626/// Modifying internal motor control is **dangerous**, and can result in permanent hardware damage
627/// to smart motors if done incorrectly. Use these functions entirely at your own risk.
628///
629/// VEX has chosen not to disclose the default constants used by smart motors, and currently
630/// has no plans to do so. As such, the units and finer details of [`MotorTuningConstants`] are not
631/// well-known or understood, as we have no reference for what these constants should look
632/// like.
633#[cfg(feature = "dangerous_motor_tuning")]
634#[derive(Debug, Clone, Copy, PartialEq)]
635pub struct MotorTuningConstants {
636    /// The feedforward constant.
637    pub kf: f64,
638
639    /// The proportional constant.
640    pub kp: f64,
641
642    /// The integral constant.
643    pub ki: f64,
644
645    /// The derivative constant.
646    pub kd: f64,
647
648    /// A constant used for filtering the profile acceleration.
649    pub filter: f64,
650
651    /// The integral limit.
652    ///
653    /// Presumably used for anti-windup protection.
654    pub integral_limit: f64,
655
656    /// The threshold for determining if a position movement has reached its goal.
657    ///
658    /// This has no effect for velocity PID calculations.
659    pub tolerance: f64,
660
661    /// The rate at which the PID computation is run in ms.
662    pub sample_rate: Duration,
663}
664
665#[cfg(feature = "dangerous_motor_tuning")]
666impl From<MotorTuningConstants> for pros_sys::motor_pid_full_s_t {
667    fn from(value: MotorTuningConstants) -> Self {
668        unsafe {
669            // Docs incorrectly claim that this function can set errno.
670            // It can't. <https://github.com/purduesigbots/pros/blob/master/src/devices/vdml_motors.c#L250>.
671            #[allow(deprecated)]
672            pros_sys::motor_convert_pid_full(
673                value.kf,
674                value.kp,
675                value.ki,
676                value.kd,
677                value.filter,
678                value.limit,
679                value.tolerance,
680                value.sample_rate.as_millis() as f64,
681            )
682        }
683    }
684}
685
686#[derive(Debug, Snafu)]
687/// Errors that can occur when using a motor.
688pub enum MotorError {
689    /// Failed to communicate with the motor while attempting to read flags.
690    Busy,
691
692    /// This functionality is not currently implemented in hardware, even
693    /// though the SDK may support it.
694    NotImplemented,
695
696    /// Generic port related error.
697    #[snafu(display("{source}"), context(false))]
698    Port {
699        /// The source of the error.
700        source: PortError,
701    },
702}
703
704map_errno! {
705    MotorError {
706        ENOSYS => Self::NotImplemented,
707    }
708    inherit PortError;
709}