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}