1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! Smart Ports & Devices
//!
//! This module provides abstractions over device access connected through VEX V5 Smart Ports. This
//! includes motors, many common sensors, vexlink, and raw serial access.
//!
//! # Hardware Overview
//!
//! The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart Ports") for communicating with
//! newer V5 peripherals. Smart port devices have a variable sample rate (unlike ADI, which is limited
//! to 10ms), and can support basic data transfer over serial.
//!
//! # Smart Port Devices
//!
//! Most devices can be created with a `new` function that generally takes a port number along with other
//! device-specific parameters. All sensors are thread safe, however sensors can only be safely constructed
//! using the [`peripherals`](crate::peripherals) API.
//!
//! In cases where PROS gives the option of a blocking or non-blocking API,
//! the blocking API is used for a synchronous method and the non-blocking API is used to create a future.
//!
//! More specific info for each device is availible in their respective modules.

pub mod distance;
pub mod expander;
pub mod gps;
pub mod imu;
pub mod link;
pub mod motor;
pub mod optical;
pub mod rotation;
pub mod vision;

pub use distance::DistanceSensor;
pub use expander::AdiExpander;
pub use gps::GpsSensor;
pub use imu::InertialSensor;
pub use link::{Link, RxLink, TxLink};
pub use motor::Motor;
pub use optical::OpticalSensor;
use pros_core::{bail_on, error::PortError};
pub use rotation::RotationSensor;
pub use vision::VisionSensor;

/// Defines common functionality shared by all smart port devices.
pub trait SmartDevice {
    /// Get the index of the [`SmartPort`] this device is registered on.
    ///
    /// Ports are indexed starting from 1.
    ///
    /// # Examples
    ///
    /// ```
    /// let sensor = InertialSensor::new(peripherals.port_1)?;
    /// assert_eq!(sensor.port_index(), 1);
    /// ```
    fn port_index(&self) -> u8;

    /// Get the variant of [`SmartDeviceType`] that this device is associated with.
    ///
    /// # Examples
    ///
    /// ```
    /// let sensor = InertialSensor::new(peripherals.port_1)?;
    /// assert_eq!(sensor.device_type(), SmartDeviceType::Imu);
    /// ```
    fn device_type(&self) -> SmartDeviceType;

    /// Determine if this device type is currently connected to the [`SmartPort`]
    /// that it's registered to.
    ///
    /// # Examples
    ///
    /// ```
    /// let sensor = InertialSensor::new(peripherals.port_1)?;
    ///
    /// if sensor.port_connected() {
    ///     println!("IMU is connected!");
    /// } else {
    ///     println!("No IMU connection found.");
    /// }
    /// ```
    fn port_connected(&self) -> bool {
        let plugged_type_result: Result<SmartDeviceType, _> =
            unsafe { pros_sys::apix::registry_get_plugged_type(self.port_index() - 1).try_into() };

        if let Ok(plugged_type) = plugged_type_result {
            plugged_type == self.device_type()
        } else {
            false
        }
    }
}

/// Represents a smart port on a V5 Brain
#[derive(Debug, Eq, PartialEq)]
pub struct SmartPort {
    /// The index of the port (port number).
    ///
    /// Ports are indexed starting from 1.
    index: u8,
}

impl SmartPort {
    /// Creates a new smart port on a specified index.
    ///
    /// # Safety
    ///
    /// Creating new `SmartPort`s is inherently unsafe due to the possibility of constructing
    /// more than one device on the same port index allowing multiple mutable references to
    /// the same hardware device. This violates rust's borrow checked guarantees. Prefer using
    /// [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// // Create a new smart port at index 1.
    /// // This is unsafe! You are responsible for ensuring that only one device registered on a
    /// // single port index.
    /// let my_port = unsafe { SmartPort::new(1) };
    /// ```
    pub const unsafe fn new(index: u8) -> Self {
        Self { index }
    }

    /// Get the index of the port (port number).
    ///
    /// Ports are indexed starting from 1.
    ///
    /// # Examples
    ///
    /// ```
    /// let my_port = unsafe { SmartPort::new(1) };
    ///
    /// assert_eq!(my_port.index(), 1);
    /// ```
    pub const fn index(&self) -> u8 {
        self.index
    }

    /// Get the type of device currently connected to this port.
    ///
    /// # Examples
    ///
    /// ```
    /// let my_port = unsafe { SmartPort::new(1) };
    ///
    /// println!("Type of device connected to port 1: {:?}", my_port.connected_type()?);
    /// ```
    pub fn connected_type(&self) -> Result<SmartDeviceType, PortError> {
        unsafe { pros_sys::apix::registry_get_plugged_type(self.index() - 1).try_into() }
    }

    /// Get the type of device this port is configured as.
    ///
    /// # Examples
    ///
    /// ```
    /// let my_port = unsafe { SmartPort::new(1) };
    /// let imu = InertialSensor::new(my_port)?;
    ///
    /// assert_eq!(my_port.configured_type()?, SmartDeviceType::Imu);
    /// ```
    pub fn configured_type(&self) -> Result<SmartDeviceType, PortError> {
        unsafe { pros_sys::apix::registry_get_bound_type(self.index() - 1).try_into() }
    }
}

/// Represents a possible type of device that can be registered on a [`SmartPort`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum SmartDeviceType {
    /// No device
    None = pros_sys::apix::E_DEVICE_NONE,

    /// Smart Motor
    Motor = pros_sys::apix::E_DEVICE_MOTOR,

    /// Rotation Sensor
    Rotation = pros_sys::apix::E_DEVICE_ROTATION,

    /// Inertial Sensor
    Imu = pros_sys::apix::E_DEVICE_IMU,

    /// Distance Sensor
    Distance = pros_sys::apix::E_DEVICE_DISTANCE,

    /// Vision Sensor
    Vision = pros_sys::apix::E_DEVICE_VISION,

    /// Optical Sensor
    Optical = pros_sys::apix::E_DEVICE_OPTICAL,

    /// GPS Sensor
    Gps = pros_sys::apix::E_DEVICE_GPS,

    /// Smart Radio
    Radio = pros_sys::apix::E_DEVICE_RADIO,

    /// ADI Expander
    ///
    /// This variant is also internally to represent the brain's onboard ADI slots.
    Adi = pros_sys::apix::E_DEVICE_ADI,

    /// Generic Serial Port
    Serial = pros_sys::apix::E_DEVICE_SERIAL,
}

impl TryFrom<pros_sys::apix::v5_device_e_t> for SmartDeviceType {
    type Error = PortError;

    /// Convert a raw `pros_sys::apix::v5_device_e_t` from `pros_sys` into a [`SmartDeviceType`].
    fn try_from(value: pros_sys::apix::v5_device_e_t) -> Result<Self, Self::Error> {
        // PROS returns either -1 (WTF?!?!) or 255 which both cast to E_DEVICE_UNDEFINED
        // when setting ERRNO, which can only be ENXIO.
        //
        // <https://github.com/purduesigbots/pros/issues/623>
        bail_on!(pros_sys::apix::E_DEVICE_UNDEFINED, value);

        Ok(match value {
            pros_sys::apix::E_DEVICE_NONE => Self::None,
            pros_sys::apix::E_DEVICE_MOTOR => Self::Motor,
            pros_sys::apix::E_DEVICE_ROTATION => Self::Rotation,
            pros_sys::apix::E_DEVICE_IMU => Self::Imu,
            pros_sys::apix::E_DEVICE_DISTANCE => Self::Distance,
            pros_sys::apix::E_DEVICE_VISION => Self::Vision,
            pros_sys::apix::E_DEVICE_OPTICAL => Self::Optical,
            pros_sys::apix::E_DEVICE_RADIO => Self::Radio,
            pros_sys::apix::E_DEVICE_ADI => Self::Adi,
            pros_sys::apix::E_DEVICE_SERIAL => Self::Serial,
            _ => unreachable!(),
        })
    }
}

impl From<SmartDeviceType> for pros_sys::apix::v5_device_e_t {
    /// Convert a [`SmartDeviceType`] into a raw `pros_sys::apix::v5_device_e_t`.
    fn from(value: SmartDeviceType) -> Self {
        value as _
    }
}