pros_devices/smart/
mod.rs

1//! Smart Ports & Devices
2//!
3//! This module provides abstractions over device access connected through VEX V5 Smart Ports. This
4//! includes motors, many common sensors, vexlink, and raw serial access.
5//!
6//! # Hardware Overview
7//!
8//! The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart Ports") for communicating with
9//! newer V5 peripherals. Smart port devices have a variable sample rate (unlike ADI, which is limited
10//! to 10ms), and can support basic data transfer over serial.
11//!
12//! # Smart Port Devices
13//!
14//! Most devices can be created with a `new` function that generally takes a port number along with other
15//! device-specific parameters. All sensors are thread safe, however sensors can only be safely constructed
16//! using the [`peripherals`](crate::peripherals) API.
17//!
18//! In cases where PROS gives the option of a blocking or non-blocking API,
19//! the blocking API is used for a synchronous method and the non-blocking API is used to create a future.
20//!
21//! More specific info for each device is availible in their respective modules.
22
23pub mod distance;
24pub mod expander;
25pub mod gps;
26pub mod imu;
27pub mod link;
28pub mod motor;
29pub mod optical;
30pub mod rotation;
31pub mod vision;
32
33use core::fmt;
34
35pub use distance::DistanceSensor;
36pub use expander::AdiExpander;
37pub use gps::GpsSensor;
38pub use imu::InertialSensor;
39pub use link::{Link, RxLink, TxLink};
40pub use motor::Motor;
41pub use optical::OpticalSensor;
42use pros_core::{bail_on, error::PortError};
43pub use rotation::RotationSensor;
44pub use vision::VisionSensor;
45
46/// Defines common functionality shared by all smart port devices.
47pub trait SmartDevice {
48    /// Get the index of the [`SmartPort`] this device is registered on.
49    ///
50    /// Ports are indexed starting from 1.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// let sensor = InertialSensor::new(peripherals.port_1)?;
56    /// assert_eq!(sensor.port_index(), 1);
57    /// ```
58    fn port_index(&self) -> u8;
59
60    /// Get the variant of [`SmartDeviceType`] that this device is associated with.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// let sensor = InertialSensor::new(peripherals.port_1)?;
66    /// assert_eq!(sensor.device_type(), SmartDeviceType::Imu);
67    /// ```
68    fn device_type(&self) -> SmartDeviceType;
69
70    /// Determine if this device type is currently connected to the [`SmartPort`]
71    /// that it's registered to.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// let sensor = InertialSensor::new(peripherals.port_1)?;
77    ///
78    /// if sensor.port_connected() {
79    ///     println!("IMU is connected!");
80    /// } else {
81    ///     println!("No IMU connection found.");
82    /// }
83    /// ```
84    fn port_connected(&self) -> bool {
85        let plugged_type_result: Result<SmartDeviceType, _> =
86            unsafe { pros_sys::apix::registry_get_plugged_type(self.port_index() - 1).try_into() };
87
88        if let Ok(plugged_type) = plugged_type_result {
89            plugged_type == self.device_type()
90        } else {
91            false
92        }
93    }
94}
95
96/// Represents a smart port on a V5 Brain
97#[derive(Debug, Eq, PartialEq)]
98pub struct SmartPort {
99    /// The index of the port (port number).
100    ///
101    /// Ports are indexed starting from 1.
102    index: u8,
103}
104
105impl SmartPort {
106    /// Creates a new smart port on a specified index.
107    ///
108    /// # Safety
109    ///
110    /// Creating new `SmartPort`s is inherently unsafe due to the possibility of constructing
111    /// more than one device on the same port index allowing multiple mutable references to
112    /// the same hardware device. This violates rust's borrow checked guarantees. Prefer using
113    /// [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// // Create a new smart port at index 1.
119    /// // This is unsafe! You are responsible for ensuring that only one device registered on a
120    /// // single port index.
121    /// let my_port = unsafe { SmartPort::new(1) };
122    /// ```
123    pub const unsafe fn new(index: u8) -> Self {
124        Self { index }
125    }
126
127    /// Get the index of the port (port number).
128    ///
129    /// Ports are indexed starting from 1.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// let my_port = unsafe { SmartPort::new(1) };
135    ///
136    /// assert_eq!(my_port.index(), 1);
137    /// ```
138    pub const fn index(&self) -> u8 {
139        self.index
140    }
141
142    /// Get the type of device currently connected to this port.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// let my_port = unsafe { SmartPort::new(1) };
148    ///
149    /// println!("Type of device connected to port 1: {:?}", my_port.connected_type()?);
150    /// ```
151    pub fn connected_type(&self) -> Result<SmartDeviceType, PortError> {
152        unsafe { pros_sys::apix::registry_get_plugged_type(self.index() - 1).try_into() }
153    }
154
155    /// Get the type of device this port is configured as.
156    ///
157    /// # Examples
158    ///
159    /// ```
160    /// let my_port = unsafe { SmartPort::new(1) };
161    /// let imu = InertialSensor::new(my_port)?;
162    ///
163    /// assert_eq!(my_port.configured_type()?, SmartDeviceType::Imu);
164    /// ```
165    pub fn configured_type(&self) -> Result<SmartDeviceType, PortError> {
166        unsafe { pros_sys::apix::registry_get_bound_type(self.index() - 1).try_into() }
167    }
168}
169
170/// Represents a possible type of device that can be registered on a [`SmartPort`].
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172#[repr(u32)]
173pub enum SmartDeviceType {
174    /// No device
175    None = pros_sys::apix::E_DEVICE_NONE,
176
177    /// Smart Motor
178    Motor = pros_sys::apix::E_DEVICE_MOTOR,
179
180    /// Rotation Sensor
181    Rotation = pros_sys::apix::E_DEVICE_ROTATION,
182
183    /// Inertial Sensor
184    Imu = pros_sys::apix::E_DEVICE_IMU,
185
186    /// Distance Sensor
187    Distance = pros_sys::apix::E_DEVICE_DISTANCE,
188
189    /// Vision Sensor
190    Vision = pros_sys::apix::E_DEVICE_VISION,
191
192    /// Optical Sensor
193    Optical = pros_sys::apix::E_DEVICE_OPTICAL,
194
195    /// GPS Sensor
196    Gps = pros_sys::apix::E_DEVICE_GPS,
197
198    /// Smart Radio
199    Radio = pros_sys::apix::E_DEVICE_RADIO,
200
201    /// ADI Expander
202    ///
203    /// This variant is also internally to represent the brain's onboard ADI slots.
204    Adi = pros_sys::apix::E_DEVICE_ADI,
205
206    /// Generic Serial Port
207    Serial = pros_sys::apix::E_DEVICE_SERIAL,
208}
209
210impl TryFrom<pros_sys::apix::v5_device_e_t> for SmartDeviceType {
211    type Error = PortError;
212
213    /// Convert a raw `pros_sys::apix::v5_device_e_t` from `pros_sys` into a [`SmartDeviceType`].
214    fn try_from(value: pros_sys::apix::v5_device_e_t) -> Result<Self, Self::Error> {
215        // PROS returns either -1 (WTF?!?!) or 255 which both cast to E_DEVICE_UNDEFINED
216        // when setting ERRNO, which can only be ENXIO.
217        //
218        // <https://github.com/purduesigbots/pros/issues/623>
219        bail_on!(pros_sys::apix::E_DEVICE_UNDEFINED, value);
220
221        Ok(match value {
222            pros_sys::apix::E_DEVICE_NONE => Self::None,
223            pros_sys::apix::E_DEVICE_MOTOR => Self::Motor,
224            pros_sys::apix::E_DEVICE_ROTATION => Self::Rotation,
225            pros_sys::apix::E_DEVICE_IMU => Self::Imu,
226            pros_sys::apix::E_DEVICE_DISTANCE => Self::Distance,
227            pros_sys::apix::E_DEVICE_VISION => Self::Vision,
228            pros_sys::apix::E_DEVICE_OPTICAL => Self::Optical,
229            pros_sys::apix::E_DEVICE_RADIO => Self::Radio,
230            pros_sys::apix::E_DEVICE_ADI => Self::Adi,
231            pros_sys::apix::E_DEVICE_SERIAL => Self::Serial,
232            _ => unreachable!(),
233        })
234    }
235}
236
237impl From<SmartDeviceType> for pros_sys::apix::v5_device_e_t {
238    /// Convert a [`SmartDeviceType`] into a raw `pros_sys::apix::v5_device_e_t`.
239    fn from(value: SmartDeviceType) -> Self {
240        value as _
241    }
242}
243
244/// Represents a timestamp on a smart device's internal clock. This type offers
245/// no guarantees that the device's clock is in sync with the internal clock of
246/// the brain, and thus cannot be safely compared with [`pros_core::time::Instant`]s.
247///
248/// There is additionally no guarantee that this is in sync with other smart devices,
249/// or even the same device if a disconnect occurred causing the clock to reset. As such,
250/// this is effectively a newtype wrapper of `u32`.
251///
252/// # Precision
253///
254/// This type has a precision of 1 millisecond.
255#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
256pub struct SmartDeviceTimestamp(pub u32);
257
258impl fmt::Debug for SmartDeviceTimestamp {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        self.0.fmt(f)
261    }
262}