vexide_motorgroup/
lib.rs

1//! # vexide-motorgroup
2//!
3//! Missing `MotorGroup` from VEXCode or PROS? This is a simple implementation of a
4//! `MotorGroup` for vexide which allows you to group motors together and control
5//! them as one.
6//!
7//! ## Installation
8//!
9//! Add the following to your `Cargo.toml`:
10//!
11//! ```toml
12//! [dependencies]
13//! # ... other dependencies
14//! vexide-motorgroup = "2.1.0"
15//! ```
16//!
17//! Or if you prefer the command line:
18//!
19//! ```sh
20//! cargo add vexide-motorgroup
21//! ```
22//!
23//! ## Usage
24//!
25//! Normally, you would have to set each motor's target and other values
26//! individually even if the motors were physically connected in a drivetrain or
27//! similar, but with `MotorGroup`, you can control them as if they were one motor.
28//!
29//! Just create a `MotorGroup` with a `Vec` of `Motor`s and use the `MotorGroup`
30//! methods just like you would with a `Motor`. It's that simple!
31//!
32//! ```rust
33//! #![no_std]
34//! #![no_main]
35//!
36//! extern crate alloc;
37//!
38//! use core::time::Duration;
39//!
40//! use alloc::vec;
41//! use vexide_motorgroup::*;
42//!
43//! use vexide::prelude::*;
44//!
45//! #[vexide::main]
46//! async fn main(peripherals: Peripherals) {
47//!     // Here's where the magic happens
48//!     let mut motor_group = MotorGroup::new(vec![
49//!         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
50//!         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
51//!     ]);
52//!
53//!     // Set the motor group's target to a voltage as if it were a motor
54//!     motor_group.set_voltage(5.0).unwrap();
55//!     sleep(Duration::from_secs(1)).await;
56//!
57//!     // Set the motor group's target to a position
58//!     motor_group
59//!         .set_position_target(Position::from_degrees(90.0), 200)
60//!         .unwrap();
61//!     sleep(Duration::from_secs(1)).await;
62//!
63//!     // Set the motor group's target to a velocity
64//!     motor_group.set_velocity(100).unwrap();
65//!     sleep(Duration::from_secs(1)).await;
66//!
67//!     // Brake the motor group
68//!     motor_group.brake(BrakeMode::Hold).unwrap();
69//! }
70//! ```
71//!
72//! ## Error handling
73//!
74//! ### Read errors
75//!
76//! For functions returning values and reading data (i.e., those taking a
77//! read-only reference to self), upon encountering an error accessing any
78//! motor, the result will be a MotorGroupError that contains all the errors
79//! encountered during the operation. Using [`MotorGroupError::result`] will
80//! return the average of all the results that were successfully read.
81//!
82//! ### Write errors
83//!
84//! vexide-motorgroup provides two different strategies for handling write
85//! errors. Both of them will return an `Err` when any motor returns an error.
86//!
87//! 1. [`WriteErrorStrategy::Ignore`] (default): This strategy will ignore
88//!    errors and continue writing to the other motors.
89//! 2. [`WriteErrorStrategy::Stop`]: This strategy will stop writing to the
90//!    other motors and return the error immediately.
91
92#![no_std]
93
94extern crate alloc;
95
96mod macros;
97mod shared_motors;
98
99pub use shared_motors::SharedMotors;
100
101use alloc::vec::Vec;
102use vexide::{
103    devices::smart::{motor::MotorError, Motor},
104    prelude::{BrakeMode, Direction, Gearset, MotorControl, Position},
105};
106
107/// An error that occurs when controlling a motor group.
108///
109/// This error is returned when an individual motor in the group encounters an
110/// error. The error contains a list of all the errors that occurred.
111///
112/// A MotorGroupError is guaranteed to have at least one error in it.
113///
114/// MotorGroupError also implements `Into<MotorError>`, which will return the
115/// first error that occurred. This means that you can use the `?` operator
116/// with a `MotorGroupError` to return a `MotorError` to a result.
117#[derive(Debug)]
118#[non_exhaustive]
119pub struct MotorGroupError<T = ()> {
120    pub errors: Vec<MotorError>,
121    pub result: Option<T>,
122}
123
124impl MotorGroupError<()> {
125    /// Creates a new motor group error from a `Vec` of motor errors.
126    ///
127    /// # Panics
128    ///
129    /// Panics if the errors vector is empty.
130    pub(crate) fn new(errors: Vec<MotorError>) -> Self {
131        assert!(
132            !errors.is_empty(),
133            "Cannot create a MotorGroupError with no errors"
134        );
135        Self {
136            errors,
137            result: None,
138        }
139    }
140}
141
142impl<T> MotorGroupError<T> {
143    pub(crate) fn with_result(errors: Vec<MotorError>, result: T) -> Self {
144        assert!(
145            !errors.is_empty(),
146            "Cannot create a MotorGroupError with no errors"
147        );
148        Self {
149            errors,
150            result: Some(result),
151        }
152    }
153
154    pub(crate) fn with_empty_result(errors: Vec<MotorError>) -> Self {
155        assert!(
156            !errors.is_empty(),
157            "Cannot create a MotorGroupError with no errors"
158        );
159        Self {
160            errors,
161            result: None,
162        }
163    }
164
165    /// Returns the result of the motor group error.
166    ///
167    /// For getters that return a result, this is the value that would be returned
168    /// if there were no errors. It is usually an average of the available data.
169    /// If all motors in the group return an error, this will be None.
170    pub fn result(&self) -> &Option<T> {
171        &self.result
172    }
173
174    /// The first error that occurred in the motor group.
175    pub fn first(&self) -> &MotorError {
176        &self.errors[0]
177    }
178
179    /// Whether the motor group has a busy error.
180    ///
181    /// A busy error occurs when communication with a motor is not possible
182    /// when reading flags.
183    pub fn has_busy_error(&self) -> bool {
184        self.errors
185            .iter()
186            .any(|error| matches!(error, MotorError::Busy))
187    }
188
189    /// Whether the motor group has a port error.
190    ///
191    /// A port error occurs when a motor is not currently connected to a Smart
192    /// Port.
193    pub fn has_port_error(&self) -> bool {
194        self.errors
195            .iter()
196            .any(|error| matches!(error, MotorError::Port { source: _ }))
197    }
198}
199
200impl From<MotorGroupError> for MotorError {
201    fn from(error: MotorGroupError) -> Self {
202        error.errors.into_iter().next().unwrap()
203    }
204}
205
206impl core::fmt::Display for MotorGroupError {
207    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208        write!(f, "error(s) in MotorGroup: {:?}", self.errors)
209    }
210}
211
212impl core::error::Error for MotorGroupError {}
213
214/// The mode for handling errors when writing to a motor group.
215///
216/// This is used to determine how to handle errors when writing to a motor group.
217/// "Writing" means doing things like setting a target, setting a voltage,
218/// setting a gearset, etc.
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
220pub enum WriteErrorStrategy {
221    /// Ignore errors and continue writing.
222    ///
223    /// This means that if a motor is, for example, unplugged, then writes to
224    /// other plugged in motors will still be attempted. You should use this
225    /// mode for most places where redundancy is practiced. Note that methods will
226    /// still return an `Err` variant when an error occurs even if some writes
227    /// succeed.
228    ///
229    /// This is the default mode.
230    #[default]
231    Ignore,
232    /// Stop writing on the first error and return early.
233    ///
234    /// This means that if a motor encounters an error, no further writes will
235    /// be attempted, and the error will be returned immediately. This is useful
236    /// for debugging or when you want to ensure that all motors are in a valid
237    /// state at all times (e.g. a subsystem should either 100% work or not work
238    /// at all.)
239    Stop,
240}
241
242/// A group of motors that can be controlled together.
243///
244/// This is a simple wrapper around a vector of motors, with methods to easily
245/// control all motors in the group at once as if they were a single motor.
246///
247/// A motor group is guaranteed to have at least one motor in it.
248#[derive(Debug)]
249pub struct MotorGroup<M: AsRef<[Motor]> + AsMut<[Motor]> = Vec<Motor>> {
250    pub(crate) motors: M,
251    write_error_strategy: WriteErrorStrategy,
252}
253
254type GetterResult<T> = Result<T, MotorGroupError<T>>;
255
256impl<M: AsRef<[Motor]> + AsMut<[Motor]>> MotorGroup<M> {
257    /// Creates a new motor group from a vector of motors.
258    ///
259    /// You can set the write handling mode afterwards by calling
260    /// [`write_error_handling_mode`].
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use vexide::prelude::*;
266    /// use vexide_motorgroup::*;
267    ///
268    /// #[vexide::main]
269    /// async fn main(peripherals: Peripherals) {
270    ///     let motor_group = MotorGroup::new(vec![
271    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
272    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
273    ///     ]);
274    ///     _ = motor_group.set_voltage(5.0);
275    /// }
276    /// ```
277    ///
278    /// # Panics
279    ///
280    /// Panics if there are no motors in the vector.
281    pub fn new(motors: M) -> Self {
282        assert!(
283            !motors.as_ref().is_empty(),
284            "Cannot create a motor group with no motors"
285        );
286        Self {
287            motors,
288            write_error_strategy: WriteErrorStrategy::default(),
289        }
290    }
291
292    /// Sets the write error handling strategy for the motor group.
293    ///
294    /// This determines how to handle errors when writing to the motor group
295    /// using methods like [`set_target`], [`set_voltage`], etc.
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use vexide::prelude::*;
301    /// use vexide_motorgroup::*;
302    ///
303    pub fn write_error_strategy(&mut self, mode: WriteErrorStrategy) -> &mut Self {
304        self.write_error_strategy = mode;
305        self
306    }
307
308    /// Sets the target that the motor group should attempt to reach.
309    ///
310    /// This could be a voltage, velocity, position, or even brake mode.
311    ///
312    /// # Errors
313    ///
314    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// use vexide::prelude::*;
320    /// use vexide_motorgroup::*;
321    ///
322    /// #[vexide::main]
323    /// async fn main(peripherals: Peripherals) {
324    ///     let mut motor_group = MotorGroup::new(vec![
325    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
326    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
327    ///     ]);
328    ///     let _ = motor_group.set_target(MotorControl::Voltage(5.0));
329    ///     sleep(Duration::from_secs(1)).await;
330    ///     let _ = motor_group.set_target(MotorControl::Brake(BrakeMode::Hold));
331    /// }
332    /// ```
333    ///
334    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_target).
335    pub fn set_target(&mut self, target: MotorControl) -> Result<(), MotorGroupError> {
336        let mut errors = Vec::new();
337        for motor in self.motors.as_mut() {
338            if let Err(error) = motor.set_target(target) {
339                errors.push(error);
340                if self.write_error_strategy == WriteErrorStrategy::Stop {
341                    break;
342                }
343            }
344        }
345        if errors.is_empty() {
346            Ok(())
347        } else {
348            Err(MotorGroupError::new(errors))
349        }
350    }
351
352    /// Sets the motor group's target to a given [`BrakeMode`].
353    ///
354    /// # Errors
355    ///
356    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// use vexide::prelude::*;
362    /// use vexide_motorgroup::*;
363    ///
364    /// #[vexide::main]
365    /// async fn main(peripherals: Peripherals) {
366    ///     let mut motor_group = MotorGroup::new(vec![
367    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
368    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
369    ///     ]);
370    ///     let _ = motor_group.brake(BrakeMode::Hold);
371    /// }
372    /// ```
373    ///
374    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.brake).
375    pub fn brake(&mut self, mode: BrakeMode) -> Result<(), MotorGroupError> {
376        let mut errors = Vec::new();
377        for motor in self.motors.as_mut() {
378            if let Err(error) = motor.brake(mode) {
379                errors.push(error);
380                if self.write_error_strategy == WriteErrorStrategy::Stop {
381                    break;
382                }
383            }
384        }
385        if errors.is_empty() {
386            Ok(())
387        } else {
388            Err(MotorGroupError::new(errors))
389        }
390    }
391
392    /// Spins the motor group at a target velocity.
393    ///
394    /// This velocity corresponds to different actual speeds in RPM depending on the gearset used for the motor.
395    /// Velocity is held with an internal PID controller to ensure consistent speed, as opposed to setting the
396    /// motor's voltage.
397    ///
398    /// # Errors
399    ///
400    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
401    ///
402    /// # Examples
403    ///
404    /// Spin a motor group at 100 RPM:
405    ///
406    /// ```
407    /// use vexide::prelude::*;
408    /// use vexide_motorgroup::*;
409    ///
410    /// #[vexide::main]
411    /// async fn main(peripherals: Peripherals) {
412    ///     let mut motor_group = MotorGroup::new(vec![
413    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
414    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
415    ///     ]);
416    ///     let _ = motor_group.set_velocity(100);
417    ///     sleep(Duration::from_secs(1)).await;
418    /// }
419    /// ```
420    ///
421    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_velocity).
422    pub fn set_velocity(&mut self, rpm: i32) -> Result<(), MotorGroupError> {
423        let mut errors = Vec::new();
424        for motor in self.motors.as_mut() {
425            if let Err(error) = motor.set_velocity(rpm) {
426                errors.push(error);
427                if self.write_error_strategy == WriteErrorStrategy::Stop {
428                    break;
429                }
430            }
431        }
432        if errors.is_empty() {
433            Ok(())
434        } else {
435            Err(MotorGroupError::new(errors))
436        }
437    }
438
439    /// Sets the motor group's output voltage.
440    ///
441    /// This voltage value spans from -12 (fully spinning reverse) to +12 (fully spinning forwards) volts, and
442    /// controls the raw output of the motor.
443    ///
444    /// # Errors
445    ///
446    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
447    ///
448    /// # Examples
449    ///
450    /// Give the motor group full power:
451    ///
452    /// ```
453    /// use vexide::prelude::*;
454    /// use vexide_motorgroup::*;
455    ///
456    /// #[vexide::main]
457    /// async fn main(peripherals: Peripherals) {
458    ///     let mut motor_group = MotorGroup::new(vec![
459    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
460    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
461    ///     ]);
462    ///     let _ = motor_group.set_voltage(motor_group.max_voltage());
463    /// }
464    /// ```
465    ///
466    /// Drive the motor group based on a controller joystick:
467    ///
468    /// ```
469    /// use vexide::prelude::*;
470    /// use vexide_motorgroup::*;
471    ///
472    /// #[vexide::main]
473    /// async fn main(peripherals: Peripherals) {
474    ///     let mut motor_group = MotorGroup::new(vec![
475    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
476    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
477    ///     ]);
478    ///     let controller = peripherals.primary_controller;
479    ///     loop {
480    ///         let controller_state = controller.state().unwrap_or_default();
481    ///         let voltage = controller_state.left_stick.x() * motor_group.max_voltage();
482    ///         motor_group.set_voltage(voltage).unwrap();
483    ///     }
484    /// }
485    /// ```
486    ///
487    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_voltage).
488    pub fn set_voltage(&mut self, volts: f64) -> Result<(), MotorGroupError> {
489        let mut errors = Vec::new();
490        for motor in self.motors.as_mut() {
491            if let Err(error) = motor.set_voltage(volts) {
492                errors.push(error);
493                if self.write_error_strategy == WriteErrorStrategy::Stop {
494                    break;
495                }
496            }
497        }
498        if errors.is_empty() {
499            Ok(())
500        } else {
501            Err(MotorGroupError::new(errors))
502        }
503    }
504
505    /// Sets an absolute position target for the motor group to attempt to reach.
506    ///
507    /// # Errors
508    ///
509    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
510    ///
511    /// # Examples
512    ///
513    /// ```
514    /// use vexide::prelude::*;
515    /// use vexide_motorgroup::*;
516    ///
517    /// #[vexide::main]
518    ///
519    /// async fn main(peripherals: Peripherals) {
520    ///     let mut motor_group = MotorGroup::new(vec![
521    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
522    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
523    ///     ]);
524    ///     let _ = motor_group.set_position_target(Position::from_degrees(90.0), 200);
525    /// }
526    /// ```
527    ///
528    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_position_target).
529    pub fn set_position_target(
530        &mut self,
531        position: Position,
532        velocity: i32,
533    ) -> Result<(), MotorGroupError> {
534        let mut errors = Vec::new();
535        for motor in self.motors.as_mut() {
536            if let Err(error) = motor.set_position_target(position, velocity) {
537                errors.push(error);
538                if self.write_error_strategy == WriteErrorStrategy::Stop {
539                    break;
540                }
541            }
542        }
543        if errors.is_empty() {
544            Ok(())
545        } else {
546            Err(MotorGroupError::new(errors))
547        }
548    }
549
550    /// Changes the output velocity for a profiled movement (motor_move_absolute or motor_move_relative).
551    ///
552    /// This will have no effect if the motor group is not following a profiled movement.
553    ///
554    /// # Errors
555    ///
556    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
557    ///
558    /// # Examples
559    ///
560    /// ```
561    /// use vexide::prelude::*;
562    /// use vexide_motorgroup::*;
563    ///
564    /// #[vexide::main]
565    /// async fn main(peripherals: Peripherals) {
566    ///     let mut motor_group = MotorGroup::new(vec![
567    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
568    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
569    ///     ]);
570    ///     // Set the motor group's target to a Position so that changing the velocity isn't a noop.
571    ///     let _ = motor_group.set_target(MotorControl::Position(Position::from_degrees(90.0), 200));
572    ///     let _ = motor_group.set_profiled_velocity(100).unwrap();
573    /// }
574    /// ```
575    ///
576    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_profiled_velocity).
577    pub fn set_profiled_velocity(&mut self, velocity: i32) -> Result<(), MotorGroupError> {
578        let mut errors = Vec::new();
579        for motor in self.motors.as_mut() {
580            if let Err(error) = motor.set_profiled_velocity(velocity) {
581                errors.push(error);
582                if self.write_error_strategy == WriteErrorStrategy::Stop {
583                    break;
584                }
585            }
586        }
587        if errors.is_empty() {
588            Ok(())
589        } else {
590            Err(MotorGroupError::new(errors))
591        }
592    }
593
594    /// Sets the gearset of an 11W motor group.
595    ///
596    /// # Errors
597    ///
598    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
599    /// - A [`MotorError::SetGearsetExp`] is returned if the motor is a 5.5W EXP Smart Motor, which has no swappable gearset.
600    ///
601    /// # Examples
602    ///
603    /// ```
604    /// use vexide::prelude::*;
605    /// use vexide_motorgroup::*;
606    ///
607    /// #[vexide::main]
608    /// async fn main(peripherals: Peripherals) {
609    ///     // This must be a V5 motor group
610    ///     let mut motor_group = MotorGroup::new(vec![
611    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
612    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
613    ///     ]);
614    ///
615    ///     // Set the motor group to use the red gearset
616    ///     motor_group.set_gearset(Gearset::Red).unwrap();
617    /// }
618    /// ```
619    ///
620    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_gearset).
621    pub fn set_gearset(&mut self, gearset: Gearset) -> Result<(), MotorGroupError> {
622        let mut errors = Vec::new();
623        for motor in self.motors.as_mut() {
624            if let Err(error) = motor.set_gearset(gearset) {
625                errors.push(error);
626                if self.write_error_strategy == WriteErrorStrategy::Stop {
627                    break;
628                }
629            }
630        }
631        if errors.is_empty() {
632            Ok(())
633        } else {
634            Err(MotorGroupError::new(errors))
635        }
636    }
637
638    /// Returns `true` if the motor group has a 5.5W (EXP) Smart Motor.
639    ///
640    /// # Examples
641    ///
642    /// ```
643    /// use vexide::prelude::*;
644    /// use vexide_motorgroup::*;
645    ///
646    /// #[vexide::main]
647    /// async fn main(peripherals: Peripherals) {
648    ///     let motor_group = MotorGroup::new(vec![
649    ///         Motor::new_exp(peripherals.port_1, Direction::Forward),
650    ///         Motor::new_exp(peripherals.port_2, Direction::Forward),
651    ///     ]);
652    ///     if motor_group.has_exp() {
653    ///         println!("Motor group has a 5.5W EXP Smart Motor");
654    ///     }
655    /// }
656    /// ```
657    ///
658    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.is_exp).
659    pub fn has_exp(&self) -> bool {
660        self.motors.as_ref().iter().any(|motor| motor.is_exp())
661    }
662
663    /// Returns `true` if the motor group has an 11W (V5) Smart Motor.
664    ///
665    /// # Examples
666    ///
667    /// ```
668    /// use vexide::prelude::*;
669    /// use vexide_motorgroup::*;
670    ///
671    /// #[vexide::main]
672    /// async fn main(peripherals: Peripherals) {
673    ///     let motor_group = MotorGroup::new(vec![
674    ///         Motor::new(peripherals.port_1, Gearset::Red, Direction::Forward),
675    ///         Motor::new(peripherals.port_2, Gearset::Red, Direction::Forward),
676    ///     ]);
677    ///     if motor_group.has_v5() {
678    ///         println!("Motor group has an 11W V5 Smart Motor");
679    ///     }
680    /// }
681    /// ```
682    ///
683    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.is_v5).
684    pub fn has_v5(&self) -> bool {
685        self.motors.as_ref().iter().any(|motor| motor.is_v5())
686    }
687
688    /// Returns the maximum voltage for the motor group based off of its [motor type](Motor::motor_type).
689    ///
690    /// # Examples
691    ///
692    /// Run a motor group at max speed, agnostic of its type:
693    /// ```
694    /// use vexide::prelude::*;
695    /// use vexide_motorgroup::*;
696    ///
697    /// fn run_motor_group_at_max_speed(motor_group: &mut MotorGroup) {
698    ///     motor_group.set_voltage(motor_group.max_voltage()).unwrap();
699    /// }
700    /// ```
701    ///
702    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.max_voltage).
703    pub fn max_voltage(&self) -> f64 {
704        self.motors
705            .as_ref()
706            .iter()
707            .map(|motor| motor.max_voltage())
708            .reduce(f64::max)
709            .unwrap()
710    }
711
712    /// Returns the average estimated angular velocity of motors in a motor group in rotations per minute (RPM).
713    ///
714    /// # Accuracy
715    ///
716    /// In some cases, this reported value may be noisy or innaccurate, especially for systems where accurate
717    /// velocity control at high speeds is required (such as flywheels). If the accuracy of this value proves
718    /// inadequate, you may opt to perform your own velocity calculations by differentiating [`Motor::position`]
719    /// over the reported internal timestamp of the motor using [`Motor::timestamp`].
720    ///
721    /// > For more information about Smart motor velocity estimation, see [this article](https://sylvie.fyi/sylib/docs/db/d8e/md_module_writeups__velocity__estimation.html).
722    ///
723    /// # Note
724    ///
725    /// To get the current **target** velocity instead of the estimated velocity, use [`Motor::target`].
726    ///
727    /// # Errors
728    ///
729    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
730    ///
731    /// # Examples
732    ///
733    /// Get the current velocity of a motor group:
734    /// ```
735    /// use vexide::prelude::*;
736    /// use vexide_motorgroup::*;
737    ///
738    /// #[vexide::main]
739    /// async fn main(peripherals: Peripherals) {
740    ///     let motor_group = MotorGroup::new(vec![
741    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
742    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
743    ///     ]);
744    ///
745    ///     println!("{:?}", motor_group.velocity().unwrap());
746    /// }
747    /// ```
748    ///
749    /// Calculate acceleration of a motor group:
750    /// ```
751    /// use vexide::prelude::*;
752    /// use vexide_motorgroup::*;
753    ///
754    /// #[vexide::main]
755    /// async fn main(peripherals: Peripherals) {
756    ///     let motor_group = MotorGroup::new(vec![
757    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
758    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
759    ///     ]);
760    ///
761    ///     let mut last_velocity = motor_group.velocity().unwrap();
762    ///     let mut start_time = Instant::now();
763    ///     loop {
764    ///         let velocity = motor_group.velocity().unwrap();
765    ///         // Make sure we don't divide by zero
766    ///         let elapsed = start_time.elapsed().as_secs_f64() + 0.001;
767    ///
768    ///         // Calculate acceleration
769    ///         let acceleration = (velocity - last_velocity) / elapsed;
770    ///         println!("Velocity: {:.2} RPM, Acceleration: {:.2} RPM/s", velocity, acceleration);
771    ///
772    ///         last_velocity = velocity;
773    ///         start_time = Instant::now();
774    ///    }
775    /// }
776    /// ```
777    ///
778    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.velocity).
779    pub fn velocity(&self) -> GetterResult<f64> {
780        let mut errors = Vec::new();
781        let mut sum = 0.0;
782        let mut count = 0;
783        for motor in self.motors.as_ref() {
784            match motor.velocity() {
785                Ok(velocity) => {
786                    sum += velocity;
787                    count += 1;
788                }
789                Err(error) => errors.push(error),
790            }
791        }
792        if errors.is_empty() {
793            Ok(sum / count as f64)
794        } else if count > 0 {
795            Err(MotorGroupError::with_result(errors, sum / count as f64))
796        } else {
797            Err(MotorGroupError::with_empty_result(errors))
798        }
799    }
800
801    /// Returns the average power drawn by a motor in this the motor group in Watts.
802    ///
803    /// # Errors
804    ///
805    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
806    ///
807    /// # Examples
808    ///
809    /// Print the power drawn by a motor group:
810    /// ```
811    /// use vexide::prelude::*;
812    /// use vexide_motorgroup::*;
813    ///
814    /// #[vexide::main]
815    /// async fn main(peripherals: Peripherals) {
816    ///     let motor_group = MotorGroup::new(vec![
817    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
818    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
819    ///     ]);
820    ///
821    ///     println!("{:?}", motor_group.power().unwrap());
822    /// }
823    /// ```
824    ///
825    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.power).
826    pub fn power(&self) -> GetterResult<f64> {
827        let mut errors = Vec::new();
828        let mut sum = 0.0;
829        let mut count = 0;
830        for motor in self.motors.as_ref() {
831            match motor.power() {
832                Ok(power) => {
833                    sum += power;
834                    count += 1;
835                }
836                Err(error) => errors.push(error),
837            }
838        }
839        if errors.is_empty() {
840            Ok(sum / count as f64)
841        } else if count > 0 {
842            Err(MotorGroupError::with_result(errors, sum / count as f64))
843        } else {
844            Err(MotorGroupError::with_empty_result(errors))
845        }
846    }
847
848    /// Returns the average torque of motors in the motor group in Newton-meters.
849    ///
850    /// # Errors
851    ///
852    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
853    ///
854    /// # Examples
855    ///
856    /// Print the torque of a motor group:
857    /// ```
858    /// use vexide::prelude::*;
859    /// use vexide_motorgroup::*;
860    ///
861    /// #[vexide::main]
862    /// async fn main(peripherals: Peripherals) {
863    ///     let motor_group = MotorGroup::new(vec![
864    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
865    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
866    ///     ]);
867    ///
868    ///     println!("{:?}", motor_group.torque().unwrap());
869    /// }
870    /// ```
871    ///
872    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.torque).
873    pub fn torque(&self) -> GetterResult<f64> {
874        let mut errors = Vec::new();
875        let mut sum = 0.0;
876        let mut count = 0;
877        for motor in self.motors.as_ref() {
878            match motor.torque() {
879                Ok(torque) => {
880                    sum += torque;
881                    count += 1;
882                }
883                Err(error) => errors.push(error),
884            }
885        }
886        if errors.is_empty() {
887            Ok(sum / count as f64)
888        } else if count > 0 {
889            Err(MotorGroupError::with_result(errors, sum / count as f64))
890        } else {
891            Err(MotorGroupError::with_empty_result(errors))
892        }
893    }
894
895    /// Returns the motor group's output voltage.
896    ///
897    /// # Errors
898    ///
899    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
900    ///
901    /// # Examples
902    ///
903    /// Print the voltage of a motor group:
904    /// ```
905    /// use vexide::prelude::*;
906    /// use vexide_motorgroup::*;
907    ///
908    /// #[vexide::main]
909    /// async fn main(peripherals: Peripherals) {
910    ///     let motor_group = MotorGroup::new(vec![
911    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
912    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
913    ///     ]);
914    ///
915    ///     println!("{:?}", motor_group.voltage().unwrap());
916    /// }
917    /// ```
918    ///
919    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.voltage).
920    pub fn voltage(&self) -> GetterResult<f64> {
921        let mut errors = Vec::new();
922        let mut sum = 0.0;
923        let mut count = 0;
924        for motor in self.motors.as_ref() {
925            match motor.voltage() {
926                Ok(voltage) => {
927                    sum += voltage;
928                    count += 1;
929                }
930                Err(error) => errors.push(error),
931            }
932        }
933        if errors.is_empty() {
934            Ok(sum / count as f64)
935        } else if count > 0 {
936            Err(MotorGroupError::with_result(errors, sum / count as f64))
937        } else {
938            Err(MotorGroupError::with_empty_result(errors))
939        }
940    }
941
942    /// Returns the motor group's average position in ticks.
943    ///
944    /// # Errors
945    ///
946    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
947    ///
948    /// # Examples
949    ///
950    /// Print the position of a motor group:
951    /// ```
952    /// use vexide::prelude::*;
953    /// use vexide_motorgroup::*;
954    ///
955    /// #[vexide::main]
956    /// async fn main(peripherals: Peripherals) {
957    ///     let motor_group = MotorGroup::new(vec![
958    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
959    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
960    ///     ]);
961    ///
962    ///     println!("{:?}", motor_group.position().unwrap());
963    /// }
964    /// ```
965    ///
966    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.position).
967    pub fn position(&self) -> GetterResult<Position> {
968        let mut errors = Vec::new();
969        let mut sum = Position::from_ticks(0, 36000);
970        let mut count = 0;
971        for motor in self.motors.as_ref() {
972            match motor.position() {
973                Ok(position) => {
974                    sum += position;
975                    count += 1;
976                }
977                Err(error) => errors.push(error),
978            }
979        }
980        if errors.is_empty() {
981            Ok(sum / count as i64)
982        } else if count > 0 {
983            Err(MotorGroupError::with_result(errors, sum / count as i64))
984        } else {
985            Err(MotorGroupError::with_empty_result(errors))
986        }
987    }
988
989    /// Returns the motor group's average current in Amperes.
990    ///
991    /// # Errors
992    ///
993    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
994    ///
995    /// # Examples
996    ///
997    /// Print the current of a motor group:
998    /// ```
999    /// use vexide::prelude::*;
1000    /// use vexide_motorgroup::*;
1001    ///
1002    /// #[vexide::main]
1003    /// async fn main(peripherals: Peripherals) {
1004    ///     let motor_group = MotorGroup::new(vec![
1005    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1006    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1007    ///     ]);
1008    ///
1009    ///     println!("{:?}", motor_group.current().unwrap());
1010    /// }
1011    /// ```
1012    ///
1013    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.current).
1014    pub fn current(&self) -> GetterResult<f64> {
1015        let mut errors = Vec::new();
1016        let mut sum = 0.0;
1017        let mut count = 0;
1018        for motor in self.motors.as_ref() {
1019            match motor.current() {
1020                Ok(current) => {
1021                    sum += current;
1022                    count += 1;
1023                }
1024                Err(error) => errors.push(error),
1025            }
1026        }
1027        if errors.is_empty() {
1028            Ok(sum / count as f64)
1029        } else if count > 0 {
1030            Err(MotorGroupError::with_result(errors, sum / count as f64))
1031        } else {
1032            Err(MotorGroupError::with_empty_result(errors))
1033        }
1034    }
1035
1036    /// Returns the motor group's average efficiency as a percentage.
1037    ///
1038    /// # Errors
1039    ///
1040    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
1041    ///
1042    /// # Examples
1043    ///
1044    /// Print the efficiency of a motor group:
1045    /// ```
1046    /// use vexide::prelude::*;
1047    /// use vexide_motorgroup::*;
1048    ///
1049    /// #[vexide::main]
1050    /// async fn main(peripherals: Peripherals) {
1051    ///     let motor_group = MotorGroup::new(vec![
1052    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1053    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1054    ///     ]);
1055    ///
1056    ///     println!("{:?}", motor_group.efficiency().unwrap());
1057    /// }
1058    /// ```
1059    ///
1060    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.efficiency).
1061    pub fn efficiency(&self) -> GetterResult<f64> {
1062        let mut errors = Vec::new();
1063        let mut sum = 0.0;
1064        let mut count = 0;
1065        for motor in self.motors.as_ref() {
1066            match motor.efficiency() {
1067                Ok(efficiency) => {
1068                    sum += efficiency;
1069                    count += 1;
1070                }
1071                Err(error) => errors.push(error),
1072            }
1073        }
1074        if errors.is_empty() {
1075            Ok(sum / count as f64)
1076        } else if count > 0 {
1077            Err(MotorGroupError::with_result(errors, sum / count as f64))
1078        } else {
1079            Err(MotorGroupError::with_empty_result(errors))
1080        }
1081    }
1082
1083    /// Resets every motor in the motor group's position to zero.
1084    ///
1085    /// # Errors
1086    ///
1087    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
1088    ///
1089    /// # Examples
1090    ///
1091    /// Reset the position of a motor group:
1092    /// ```
1093    /// use vexide::prelude::*;
1094    /// use vexide_motorgroup::*;
1095    ///
1096    /// #[vexide::main]
1097    /// async fn main(peripherals: Peripherals) {
1098    ///     let mut motor_group = MotorGroup::new(vec![
1099    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1100    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1101    ///     ]);
1102    ///
1103    ///     motor_group.reset_position().unwrap();
1104    /// }
1105    /// ```
1106    ///
1107    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.reset_position).
1108    pub fn reset_position(&mut self) -> Result<(), MotorGroupError> {
1109        let mut errors = Vec::new();
1110        for motor in self.motors.as_mut() {
1111            if let Err(error) = motor.reset_position() {
1112                errors.push(error);
1113                if self.write_error_strategy == WriteErrorStrategy::Stop {
1114                    break;
1115                }
1116            }
1117        }
1118        if errors.is_empty() {
1119            Ok(())
1120        } else {
1121            Err(MotorGroupError::new(errors))
1122        }
1123    }
1124
1125    /// Sets the motor group's position to a given value.
1126    ///
1127    /// # Errors
1128    ///
1129    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
1130    ///
1131    /// # Examples
1132    ///
1133    /// Set the position of a motor group:
1134    /// ```
1135    /// use vexide::prelude::*;
1136    /// use vexide_motorgroup::*;
1137    ///
1138    /// #[vexide::main]
1139    /// async fn main(peripherals: Peripherals) {
1140    ///     let mut motor_group = MotorGroup::new(vec![
1141    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1142    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1143    ///     ]);
1144    ///
1145    ///     motor_group.set_position(Position::from_degrees(90.0)).unwrap();
1146    /// }
1147    /// ```
1148    ///
1149    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_position).
1150    pub fn set_position(&mut self, position: Position) -> Result<(), MotorGroupError> {
1151        let mut errors = Vec::new();
1152        for motor in self.motors.as_mut() {
1153            if let Err(error) = motor.set_position(position) {
1154                errors.push(error);
1155                if self.write_error_strategy == WriteErrorStrategy::Stop {
1156                    break;
1157                }
1158            }
1159        }
1160        if errors.is_empty() {
1161            Ok(())
1162        } else {
1163            Err(MotorGroupError::new(errors))
1164        }
1165    }
1166
1167    /// Sets the motor group's current limit in Amperes.
1168    ///
1169    /// # Errors
1170    ///
1171    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
1172    ///
1173    /// # Examples
1174    ///
1175    /// Set the current limit of a motor group:
1176    /// ```
1177    /// use vexide::prelude::*;
1178    /// use vexide_motorgroup::*;
1179    ///
1180    /// #[vexide::main]
1181    /// async fn main(peripherals: Peripherals) {
1182    ///     let mut motor_group = MotorGroup::new(vec![
1183    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1184    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1185    ///     ]);
1186    ///
1187    ///     motor_group.set_current_limit(2.5).unwrap();
1188    /// }
1189    /// ```
1190    ///
1191    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_current_limit).
1192    pub fn set_current_limit(&mut self, limit: f64) -> Result<(), MotorGroupError> {
1193        let mut errors = Vec::new();
1194        for motor in self.motors.as_mut() {
1195            if let Err(error) = motor.set_current_limit(limit) {
1196                errors.push(error);
1197                if self.write_error_strategy == WriteErrorStrategy::Stop {
1198                    break;
1199                }
1200            }
1201        }
1202        if errors.is_empty() {
1203            Ok(())
1204        } else {
1205            Err(MotorGroupError::new(errors))
1206        }
1207    }
1208
1209    /// Sets the motor group's voltage limit in Volts.
1210    ///
1211    /// # Errors
1212    ///
1213    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
1214    ///
1215    /// # Examples
1216    ///
1217    /// Set the voltage limit of a motor group:
1218    /// ```
1219    /// use vexide::prelude::*;
1220    /// use vexide_motorgroup::*;
1221    ///
1222    /// #[vexide::main]
1223    /// async fn main(peripherals: Peripherals) {
1224    ///     let mut motor_group = MotorGroup::new(vec![
1225    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1226    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1227    ///     ]);
1228    ///
1229    ///     motor_group.set_voltage_limit(10.0).unwrap();
1230    /// }
1231    /// ```
1232    ///
1233    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_voltage_limit).
1234    pub fn set_voltage_limit(&mut self, limit: f64) -> Result<(), MotorGroupError> {
1235        let mut errors = Vec::new();
1236        for motor in self.motors.as_mut() {
1237            if let Err(error) = motor.set_voltage_limit(limit) {
1238                errors.push(error);
1239                if self.write_error_strategy == WriteErrorStrategy::Stop {
1240                    break;
1241                }
1242            }
1243        }
1244        if errors.is_empty() {
1245            Ok(())
1246        } else {
1247            Err(MotorGroupError::new(errors))
1248        }
1249    }
1250
1251    /// Returns the motor group's temperature in degrees Celsius.
1252    ///
1253    /// # Errors
1254    ///
1255    /// - A [`MotorGroupError`] error is returned if any motor in the group encounters an error.
1256    ///
1257    /// # Examples
1258    ///
1259    /// Print the temperature of a motor group:
1260    /// ```
1261    /// use vexide::prelude::*;
1262    /// use vexide_motorgroup::*;
1263    ///
1264    /// #[vexide::main]
1265    /// async fn main(peripherals: Peripherals) {
1266    ///     let motor_group = MotorGroup::new(vec![
1267    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1268    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1269    ///     ]);
1270    ///
1271    ///     println!("{:?}", motor_group.temperature().unwrap());
1272    /// }
1273    /// ```
1274    ///
1275    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.temperature).
1276    pub fn temperature(&self) -> GetterResult<f64> {
1277        let mut errors = Vec::new();
1278        let mut sum = 0.0;
1279        let mut count = 0;
1280        for motor in self.motors.as_ref() {
1281            match motor.temperature() {
1282                Ok(temperature) => {
1283                    sum += temperature;
1284                    count += 1;
1285                }
1286                Err(error) => errors.push(error),
1287            }
1288        }
1289        if errors.is_empty() {
1290            Ok(sum / count as f64)
1291        } else if count > 0 {
1292            Err(MotorGroupError::with_result(errors, sum / count as f64))
1293        } else {
1294            Err(MotorGroupError::with_empty_result(errors))
1295        }
1296    }
1297
1298    /// Returns `true` if any motor in the motor group is over temperature.
1299    ///
1300    /// # Errors
1301    ///
1302    /// - A [`MotorGroupError`] error is returned if any motor encounters an error.
1303    ///
1304    /// Note that this method will still return `Ok` if a motor encounters an
1305    /// error but a motor in a group is still over temperature.
1306    ///
1307    /// # Examples
1308    ///
1309    /// Check if a motor group is over temperature:
1310    /// ```
1311    /// use vexide::prelude::*;
1312    /// use vexide_motorgroup::*;
1313    ///
1314    /// #[vexide::main]
1315    /// async fn main(peripherals: Peripherals) {
1316    ///     let motor_group = MotorGroup::new(vec![
1317    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1318    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1319    ///     ]);
1320    ///
1321    ///     println!("{:?}", motor_group.is_over_temperature().unwrap());
1322    /// }
1323    /// ```
1324    ///
1325    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.is_over_temperature).
1326    pub fn is_over_temperature(&self) -> Result<bool, MotorGroupError> {
1327        let mut errors = Vec::new();
1328        for motor in self.motors.as_ref() {
1329            match motor.is_over_temperature() {
1330                Ok(true) => return Ok(true),
1331                Err(error) => errors.push(error),
1332                _ => {}
1333            }
1334        }
1335        if errors.is_empty() {
1336            Ok(false)
1337        } else {
1338            Err(MotorGroupError::new(errors))
1339        }
1340    }
1341
1342    /// Returns `true` if any motor in the motor group is over current.
1343    ///
1344    /// # Errors
1345    ///
1346    /// - A [`MotorGroupError`] error is returned if any motor encounters an error.
1347    ///
1348    /// Note that this method will still return `Ok` if a motor encounters an
1349    /// error but a motor in a group is still over current.
1350    ///
1351    /// # Examples
1352    ///
1353    /// Check if a motor group is over current:
1354    /// ```
1355    /// use vexide::prelude::*;
1356    /// use vexide_motorgroup::*;
1357    ///
1358    /// #[vexide::main]
1359    /// async fn main(peripherals: Peripherals) {
1360    ///     let motor_group = MotorGroup::new(vec![
1361    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1362    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1363    ///     ]);
1364    ///
1365    ///     println!("{:?}", motor_group.is_over_current().unwrap());
1366    /// }
1367    /// ```
1368    ///
1369    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.is_over_current).
1370    pub fn is_over_current(&self) -> Result<bool, MotorGroupError> {
1371        let mut errors = Vec::new();
1372        for motor in self.motors.as_ref() {
1373            match motor.is_over_current() {
1374                Ok(true) => return Ok(true),
1375                Err(error) => errors.push(error),
1376                _ => {}
1377            }
1378        }
1379        if errors.is_empty() {
1380            Ok(false)
1381        } else {
1382            Err(MotorGroupError::new(errors))
1383        }
1384    }
1385
1386    /// Returns `true` if any motor in the motor group has a driver fault.
1387    ///
1388    /// # Errors
1389    ///
1390    /// - A [`MotorGroupError`] error is returned if any motor encounters an error.
1391    ///
1392    /// Note that this method will still return `Ok` if a motor encounters an
1393    /// error but a motor in a group has a driver fault.
1394    ///
1395    /// # Examples
1396    ///
1397    /// Check if a motor group has a driver fault:
1398    /// ```
1399    /// use vexide::prelude::*;
1400    /// use vexide_motorgroup::*;
1401    ///
1402    /// #[vexide::main]
1403    /// async fn main(peripherals: Peripherals) {
1404    ///     let motor_group = MotorGroup::new(vec![
1405    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1406    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1407    ///     ]);
1408    ///
1409    ///     println!("{:?}", motor_group.is_driver_fault().unwrap());
1410    /// }
1411    /// ```
1412    ///
1413    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.is_driver_fault).
1414    pub fn is_driver_fault(&self) -> Result<bool, MotorGroupError> {
1415        let mut errors = Vec::new();
1416        for motor in self.motors.as_ref() {
1417            match motor.is_driver_fault() {
1418                Ok(true) => return Ok(true),
1419                Err(error) => errors.push(error),
1420                _ => {}
1421            }
1422        }
1423        if errors.is_empty() {
1424            Ok(false)
1425        } else {
1426            Err(MotorGroupError::new(errors))
1427        }
1428    }
1429
1430    /// Returns `true` if the any motor in the motor group is over current.
1431    ///
1432    /// # Errors
1433    ///
1434    /// - A [`MotorGroupError`] error is returned if any motor encounters an error.
1435    ///
1436    /// Note that this method will still return `Ok` if a motor encounters an
1437    /// error but a motor in a group is still over current.
1438    ///
1439    /// # Examples
1440    ///
1441    /// Check if a motor group is over current:
1442    /// ```
1443    /// use vexide::prelude::*;
1444    /// use vexide_motorgroup::*;
1445    ///
1446    /// #[vexide::main]
1447    /// async fn main(peripherals: Peripherals) {
1448    ///     let motor_group = MotorGroup::new(vec![
1449    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1450    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1451    ///     ]);
1452    ///
1453    ///     println!("{:?}", motor_group.is_driver_over_current().unwrap());
1454    /// }
1455    /// ```
1456    ///
1457    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.is_driver_over_current).
1458    pub fn is_driver_over_current(&self) -> Result<bool, MotorGroupError> {
1459        let mut errors = Vec::new();
1460        for motor in self.motors.as_ref() {
1461            match motor.is_driver_over_current() {
1462                Ok(true) => return Ok(true),
1463                Err(error) => errors.push(error),
1464                _ => {}
1465            }
1466        }
1467        if errors.is_empty() {
1468            Ok(false)
1469        } else {
1470            Err(MotorGroupError::new(errors))
1471        }
1472    }
1473
1474    /// Sets the motor group's direction.
1475    ///
1476    /// # Errors
1477    ///
1478    /// - A [`MotorError::Port`] error is returned if a motor device is not currently connected to the Smart Port.
1479    ///
1480    /// # Examples
1481    ///
1482    /// Set the direction of a motor group:
1483    /// ```
1484    /// use vexide::prelude::*;
1485    /// use vexide_motorgroup::*;
1486    ///
1487    /// #[vexide::main]
1488    /// async fn main(peripherals: Peripherals) {
1489    ///     let mut motor_group = MotorGroup::new(vec![
1490    ///         Motor::new(peripherals.port_1, Gearset::Green, Direction::Forward),
1491    ///         Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward),
1492    ///     ]);
1493    ///
1494    ///     motor_group.set_direction(Direction::Reverse).unwrap();
1495    /// }
1496    /// ```
1497    ///
1498    /// See the original method [here](https://docs.rs/vexide/latest/vexide/devices/smart/struct.Motor.html#method.set_direction).
1499    pub fn set_direction(&mut self, direction: Direction) -> Result<(), MotorGroupError> {
1500        let mut errors = Vec::new();
1501        for motor in self.motors.as_mut() {
1502            if let Err(error) = motor.set_direction(direction) {
1503                errors.push(error);
1504                if self.write_error_strategy == WriteErrorStrategy::Stop {
1505                    break;
1506                }
1507            }
1508        }
1509        if errors.is_empty() {
1510            Ok(())
1511        } else {
1512            Err(MotorGroupError::new(errors))
1513        }
1514    }
1515}