vexide_devices/smart/
gps.rs

1//! GPS Sensor
2//!
3//! This module provides an interface to interact with the VEX V5 Game Position System (GPS)
4//! Sensor, which uses computer vision and an inertial measurement unit (IMU) to provide
5//! absolute position tracking within a VEX Robotics Competition field.
6//!
7//! # Hardware Description
8//!
9//! The GPS sensor combines a monochrome camera and an IMU for robust position tracking
10//! through visual odometry. It works by detecting QR-like patterns on the field perimeter,
11//! using both the pattern sequence's and apparent size for position determination. The
12//! integrated IMU provides motion tracking for position estimation when visual tracking
13//! is unavailable or unreliable.
14//!
15//! The sensor has specific operating ranges: it requires a minimum
16//! distance of 20 inches from the field perimeter for reliable readings, has a deadzone
17//! between 0-13.5 inches, and maintains accuracy up to 12 feet from the perimeter.
18//!
19//! Sensor fusion between the camera and IMU helps maintain position tracking through
20//! dead zones and areas of inconsistent visual detection.
21//!
22//! Further information about the sensor's method of operation can be found in [IFI's patent](https://docs.google.com/viewerng/viewer?url=https://patentimages.storage.googleapis.com/4f/74/30/eccf334da0ae38/WO2020219788A1.pdf).
23
24use core::{marker::PhantomData, time::Duration};
25
26use vex_sdk::{
27    vexDeviceGpsAttitudeGet, vexDeviceGpsDataRateSet, vexDeviceGpsDegreesGet, vexDeviceGpsErrorGet,
28    vexDeviceGpsHeadingGet, vexDeviceGpsInitialPositionSet, vexDeviceGpsOriginGet,
29    vexDeviceGpsOriginSet, vexDeviceGpsQuaternionGet, vexDeviceGpsRawAccelGet,
30    vexDeviceGpsRawGyroGet, vexDeviceGpsStatusGet, V5_DeviceGpsAttitude, V5_DeviceGpsQuaternion,
31    V5_DeviceGpsRaw, V5_DeviceT,
32};
33use vexide_core::float::Float;
34
35use super::{SmartDevice, SmartDeviceType, SmartPort};
36use crate::{math::Point2, PortError};
37
38/// A GPS sensor plugged into a Smart Port.
39#[derive(Debug, PartialEq)]
40pub struct GpsSensor {
41    port: SmartPort,
42    device: V5_DeviceT,
43    rotation_offset: f64,
44    heading_offset: f64,
45}
46
47// SAFETY: Required because we store a raw pointer to the device handle to avoid it getting from the
48// SDK each device function. Simply sharing a raw pointer across threads is not inherently unsafe.
49unsafe impl Send for GpsSensor {}
50unsafe impl Sync for GpsSensor {}
51
52impl GpsSensor {
53    /// The maximum value that can be returned by [`Self::heading`].
54    pub const MAX_HEADING: f64 = 360.0;
55
56    /// Creates a new GPS sensor from a [`SmartPort`].
57    ///
58    /// # Sensor Configuration
59    ///
60    /// The sensor requires three measurements to be made at the start of a match, passed as arguments to this function:
61    ///
62    /// ## Sensor Offset
63    ///
64    /// `offset` is the physical offset of the sensor's mounting location from a reference point on the robot.
65    ///
66    /// Offset defines the exact point on the robot that is considered a "source of truth" for the robot's position.
67    /// For example, if you considered the center of your robot to be the reference point for coordinates, then this
68    /// value would be the signed 4-quadrant x and y offset from that point on your robot in meters. Similarly, if you
69    /// considered the sensor itself to be the robot's origin of tracking, then this value would simply be
70    /// `Point2 { x: 0.0, y: 0.0 }`.
71    ///
72    /// ## Initial Robot Position
73    ///
74    /// `initial_position` is an estimate of the robot's initial cartesian coordinates on the field in meters. This
75    /// value helpful for cases when the robot's starting point is near a field wall.
76    ///
77    /// When the GPS Sensor is too close to a field wall to properly read the GPS strips, the sensor will be unable
78    /// to localize the robot's position due the wall's proximity limiting the view of the camera. This can cause the
79    /// sensor inaccurate results at the start of a match, where robots often start directly near a wall.
80    ///
81    /// By providing an estimate of the robot's initial position on the field, this problem is partially mitigated by
82    /// giving the sensor an initial frame of reference to use.
83    ///
84    /// # Initial Robot Heading
85    ///
86    /// `initial_heading` is a value between 0 and 360 degrees that informs the GPS of its heading at the start of the
87    /// match. Similar to `initial_position`, this is useful for improving accuracy when the sensor is in close proximity
88    /// to a field wall, as the sensor's rotation values are continiously checked against the GPS field strips to prevent
89    /// drift over time. If the sensor starts too close to a field wall, providing an `initial_haeding` can help prevent
90    /// this drift at the start of the match.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use vexide::prelude::*;
96    ///
97    /// #[vexide::main]
98    /// async fn main(peripherals: Peripherals) {
99    ///     // Create a GPS sensor mounted 2 inches forward and 1 inch right of center
100    ///     // Starting at position (0, 0) with 90 degree heading
101    ///     let gps = GpsSensor::new(
102    ///         // Port 1
103    ///         peripherals.port_1,
104    ///
105    ///         // Sensor is mounted 0.225 meters to the left and 0.225 meters above the robot's tracking origin.
106    ///         Point2 { x: -0.225, y: 0.225 },
107    ///
108    ///         // Robot's starting point is at the center of the field.
109    ///         Point2 { x: 0.0, y: 0.0 },
110    ///
111    ///         // Robot is facing to the right initially.
112    ///         90.0,
113    ///     );
114    /// }
115    /// ```
116    pub fn new(
117        port: SmartPort,
118        offset: impl Into<Point2<f64>>,
119        initial_position: impl Into<Point2<f64>>,
120        initial_heading: f64,
121    ) -> Self {
122        let device = unsafe { port.device_handle() };
123
124        let initial_position = initial_position.into();
125        let offset = offset.into();
126
127        unsafe {
128            vexDeviceGpsOriginSet(device, offset.x, offset.y);
129            vexDeviceGpsInitialPositionSet(
130                device,
131                initial_position.x,
132                initial_position.y,
133                initial_heading,
134            );
135        }
136
137        Self {
138            device,
139            port,
140            rotation_offset: Default::default(),
141            heading_offset: Default::default(),
142        }
143    }
144
145    /// Returns the user-configured offset from a reference point on the robot.
146    ///
147    /// This offset value is passed to [`GpsSensor::new`] and can be changed using [`GpsSensor::set_offset`].
148    ///
149    /// # Errors
150    ///
151    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use vexide::prelude::*;
157    ///
158    /// #[vexide::main]
159    /// async fn main(peripherals: Peripherals) {
160    ///     let mut gps = GpsSensor::new(
161    ///         peripherals.port_1,
162    ///
163    ///         // Initial offset value is configured here!
164    ///         //
165    ///         // Let's assume that the sensor is mounted 0.225 meters to the left and 0.225 meters above
166    ///         // our desired tracking origin.
167    ///         Point2 { x: -0.225, y: 0.225 }, // Configure offset value
168    ///         Point2 { x: 0.0, y: 0.0 },
169    ///         90.0,
170    ///     );
171    ///
172    ///     // Get the configured offset of the sensor
173    ///     if let Ok(offset) = gps.offset() {
174    ///         println!("GPS sensor is mounted at x={}, y={}", offset.x, offset.y); // "Sensor is mounted at x=-0.225, y=0.225"
175    ///     }
176    ///
177    ///     // Change the offset to something new
178    ///     _ = gps.set_offset(Point2 { x: 0.0, y: 0.0 });
179    ///
180    ///     // Get the configured offset of the sensor again
181    ///     if let Ok(offset) = gps.offset() {
182    ///         println!("GPS sensor is mounted at x={}, y={}", offset.x, offset.y); // "Sensor is mounted at x=0.0, y=0.0"
183    ///     }
184    /// }
185    /// ```
186    pub fn offset(&self) -> Result<Point2<f64>, PortError> {
187        self.validate_port()?;
188
189        let mut data = Point2 { x: 0.0, y: 0.0 };
190        unsafe { vexDeviceGpsOriginGet(self.device, &mut data.x, &mut data.y) }
191
192        Ok(data)
193    }
194
195    /// Adjusts the sensor's physical offset from the robot's tracking origin.
196    ///
197    /// This value is also configured initially through [`GpsSensor::new`].
198    ///
199    /// Offset defines the exact point on the robot that is considered a "source of truth" for the robot's position.
200    /// For example, if you considered the center of your robot to be the reference point for coordinates, then this
201    /// value would be the signed 4-quadrant x and y offset from that point on your robot in meters. Similarly, if you
202    /// considered the sensor itself to be the robot's origin of tracking, then this value would simply be
203    /// `Point2 { x: 0.0, y: 0.0 }`.
204    ///
205    /// # Errors
206    ///
207    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
208    ///
209    /// # Examples
210    ///
211    /// ```
212    /// use vexide::prelude::*;
213    ///
214    /// #[vexide::main]
215    /// async fn main(peripherals: Peripherals) {
216    ///     let mut gps = GpsSensor::new(
217    ///         peripherals.port_1,
218    ///
219    ///         // Initial offset value is configured here!
220    ///         //
221    ///         // Let's assume that the sensor is mounted 0.225 meters to the left and 0.225 meters above
222    ///         // our desired tracking origin.
223    ///         Point2 { x: -0.225, y: 0.225 }, // Configure offset value
224    ///         Point2 { x: 0.0, y: 0.0 },
225    ///         90.0,
226    ///     );
227    ///
228    ///     // Get the configured offset of the sensor
229    ///     if let Ok(offset) = gps.offset() {
230    ///         println!("GPS sensor is mounted at x={}, y={}", offset.x, offset.y); // "Sensor is mounted at x=-0.225, y=0.225"
231    ///     }
232    ///
233    ///     // Change the offset to something new
234    ///     _ = gps.set_offset(Point2 { x: 0.0, y: 0.0 });
235    ///
236    ///     // Get the configured offset of the sensor again
237    ///     if let Ok(offset) = gps.offset() {
238    ///         println!("GPS sensor is mounted at x={}, y={}", offset.x, offset.y); // "Sensor is mounted at x=0.0, y=0.0"
239    ///     }
240    /// }
241    /// ```
242    pub fn set_offset(&mut self, offset: Point2<f64>) -> Result<(), PortError> {
243        self.validate_port()?;
244
245        unsafe { vexDeviceGpsOriginSet(self.device, offset.x, offset.y) }
246
247        Ok(())
248    }
249
250    /// Returns an estimate of the robot's location on the field as cartesian coordinates measured in meters.
251    ///
252    /// The reference point for a robot's position is determined by the sensor's configured [`offset`](`GpsSensor::offset`) value.
253    ///
254    /// # Errors
255    ///
256    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use vexide::prelude::*;
262    ///
263    /// #[vexide::main]
264    /// async fn main(peripherals: Peripherals) {
265    ///     // Assume we're starting in the middle of the field facing upwards, with the
266    ///     // sensor's mounting point being our reference for position.
267    ///     let gps = GpsSensor::new(
268    ///         peripherals.port_1,
269    ///         Point2 { x: 0.0, y: 0.0 },
270    ///         Point2 { x: 0.0, y: 0.0 },
271    ///         0.0,
272    ///     );
273    ///
274    ///     // Get current position and heading
275    ///     if let Ok(position) = gps.position() {
276    ///         println!(
277    ///             "Robot is at x={}, y={}",
278    ///             position.x,
279    ///             position.y,
280    ///         );
281    ///     }
282    /// }
283    /// ```
284    pub fn position(&self) -> Result<Point2<f64>, PortError> {
285        self.validate_port()?;
286
287        let mut attitude = V5_DeviceGpsAttitude::default();
288        unsafe {
289            vexDeviceGpsAttitudeGet(self.device, &mut attitude, false);
290        }
291
292        Ok(Point2 {
293            x: attitude.position_x,
294            y: attitude.position_y,
295        })
296    }
297
298    /// Returns the RMS (Root Mean Squared) error for the sensor's [position reading] in meters.
299    ///
300    /// [position reading]: GpsSensor::position
301    ///
302    /// # Errors
303    ///
304    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
305    ///
306    /// # Examples
307    ///
308    /// ```
309    /// use vexide::prelude::*;
310    ///
311    /// #[vexide::main]
312    /// async fn main(peripherals: Peripherals) {
313    ///     let gps = GpsSensor::new(
314    ///         peripherals.port_1,
315    ///         Point2 { x: 0.0, y: 0.0 },
316    ///         Point2 { x: 0.0, y: 0.0 },
317    ///         0.0,
318    ///     );
319    ///
320    ///     // Check position accuracy
321    ///     if gps.error().is_ok_and(|err| err > 0.3) {
322    ///         println!("Warning: GPS position accuracy is low ({}m error)", error);
323    ///     }
324    /// }
325    /// ```
326    pub fn error(&self) -> Result<f64, PortError> {
327        self.validate_port()?;
328
329        Ok(unsafe { vexDeviceGpsErrorGet(self.device) })
330    }
331
332    /// Returns the internal status code of the sensor.
333    ///
334    /// # Errors
335    ///
336    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
337    ///
338    /// # Examples
339    ///
340    /// ```
341    /// use vexide::prelude::*;
342    ///
343    /// #[vexide::main]
344    /// async fn main(peripherals: Peripherals) {
345    ///     let gps = GpsSensor::new(
346    ///         peripherals.port_1,
347    ///         Point2 { x: 0.0, y: 0.0 },
348    ///         Point2 { x: 0.0, y: 0.0 },
349    ///         0.0,
350    ///     );
351    ///
352    ///     if let Ok(status) = gps.status() {
353    ///         println!("Status: {:b}", status);
354    ///     }
355    /// }
356    /// ```
357    pub fn status(&self) -> Result<u32, PortError> {
358        self.validate_port()?;
359
360        Ok(unsafe { vexDeviceGpsStatusGet(self.device) })
361    }
362
363    /// Returns the sensor's yaw angle bounded by [0.0, 360.0) degrees.
364    ///
365    /// Clockwise rotations are represented with positive degree values, while counterclockwise rotations are
366    /// represented with negative ones. If a heading offset has not been set using [`GpsSensor::set_heading`],
367    /// then 90 degrees will located to the right of the field.
368    ///
369    /// # Errors
370    ///
371    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
372    ///
373    /// # Examples
374    ///
375    /// ```
376    /// use vexide::prelude::*;
377    /// use core::time::Duration;
378    ///
379    /// #[vexide::main]
380    /// async fn main(peripherals: Peripherals) {
381    ///     // Assume we're starting in the middle of the field facing upwards, with the
382    ///     // sensor's mounting point being our reference for position.
383    ///     let gps = GpsSensor::new(
384    ///         peripherals.port_1,
385    ///         Point2 { x: 0.0, y: 0.0 },
386    ///         Point2 { x: 0.0, y: 0.0 },
387    ///         0.0,
388    ///     );
389    ///
390    ///     if let Ok(heading) = gps.heading() {
391    ///         println!("Heading is {} degrees.", rotation);
392    ///     }
393    /// }
394    /// ```
395    pub fn heading(&self) -> Result<f64, PortError> {
396        self.validate_port()?;
397        Ok(
398            // The result needs to be [0, 360). Adding a significantly negative offset could take us
399            // below 0. Adding a significantly positive offset could take us above 360.
400            (unsafe { vexDeviceGpsDegreesGet(self.device) } + self.heading_offset)
401                .rem_euclid(Self::MAX_HEADING),
402        )
403    }
404
405    /// Returns the total number of degrees the GPS has spun about the z-axis.
406    ///
407    /// This value is theoretically unbounded. Clockwise rotations are represented with positive degree values,
408    /// while counterclockwise rotations are represented with negative ones. If a heading offset has not been set
409    /// using [`GpsSensor::set_rotation`], then 90 degrees will located to the right of the field.
410    ///
411    /// # Errors
412    ///
413    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
414    ///
415    /// # Examples
416    ///
417    /// ```
418    /// use vexide::prelude::*;
419    /// use core::time::Duration;
420    ///
421    /// #[vexide::main]
422    /// async fn main(peripherals: Peripherals) {
423    ///     // Assume we're starting in the middle of the field facing upwards, with the
424    ///     // sensor's mounting point being our reference for position.
425    ///     let gps = GpsSensor::new(
426    ///         peripherals.port_1,
427    ///         Point2 { x: 0.0, y: 0.0 },
428    ///         Point2 { x: 0.0, y: 0.0 },
429    ///         0.0,
430    ///     );
431    ///
432    ///     if let Ok(rotation) = gps.rotation() {
433    ///         println!("Robot has rotated {} degrees since calibration.", rotation);
434    ///     }
435    /// }
436    /// ```
437    pub fn rotation(&self) -> Result<f64, PortError> {
438        self.validate_port()?;
439        Ok(unsafe { vexDeviceGpsHeadingGet(self.device) } + self.rotation_offset)
440    }
441
442    /// Returns the Euler angles (pitch, yaw, roll) representing the GPS's orientation.
443    ///
444    /// # Errors
445    ///
446    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
447    ///
448    /// # Examples
449    ///
450    /// ```
451    /// use vexide::prelude::*;
452    /// use core::time::Duration;
453    ///
454    /// #[vexide::main]
455    /// async fn main(peripherals: Peripherals) {
456    ///     // Assume we're starting in the middle of the field facing upwards, with the
457    ///     // sensor's mounting point being our reference for position.
458    ///     let gps = GpsSensor::new(
459    ///         peripherals.port_1,
460    ///         Point2 { x: 0.0, y: 0.0 },
461    ///         Point2 { x: 0.0, y: 0.0 },
462    ///         0.0,
463    ///     );
464    ///
465    ///     if let Ok(angles) = gps.euler() {
466    ///         println!(
467    ///             "yaw: {}°, pitch: {}°, roll: {}°",
468    ///             angles.a.to_degrees(),
469    ///             angles.b.to_degrees(),
470    ///             angles.c.to_degrees(),
471    ///         );
472    ///     }
473    /// }
474    /// ```
475    pub fn euler(&self) -> Result<mint::EulerAngles<f64, f64>, PortError> {
476        self.validate_port()?;
477
478        let mut data = V5_DeviceGpsAttitude::default();
479        unsafe {
480            vexDeviceGpsAttitudeGet(self.device, &mut data, false);
481        }
482
483        Ok(mint::EulerAngles {
484            a: data.pitch.to_radians(),
485            b: data.yaw.to_radians(),
486            c: data.roll.to_radians(),
487            marker: PhantomData,
488        })
489    }
490
491    /// Returns a quaternion representing the sensor's orientation.
492    ///
493    /// # Errors
494    ///
495    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
496    ///
497    /// # Examples
498    ///
499    /// ```
500    /// use vexide::prelude::*;
501    /// use core::time::Duration;
502    ///
503    /// #[vexide::main]
504    /// async fn main(peripherals: Peripherals) {
505    ///     // Assume we're starting in the middle of the field facing upwards, with the
506    ///     // sensor's mounting point being our reference for position.
507    ///     let gps = GpsSensor::new(
508    ///         peripherals.port_1,
509    ///         Point2 { x: 0.0, y: 0.0 },
510    ///         Point2 { x: 0.0, y: 0.0 },
511    ///         0.0,
512    ///     );
513    ///
514    ///     if let Ok(quaternion) = gps.quaternion() {
515    ///         println!(
516    ///             "x: {}, y: {}, z: {}, scalar: {}",
517    ///             quaternion.v.x,
518    ///             quaternion.v.y,
519    ///             quaternion.v.z,
520    ///             quaternion.s,
521    ///         );
522    ///     }
523    /// }
524    /// ```
525    pub fn quaternion(&self) -> Result<mint::Quaternion<f64>, PortError> {
526        self.validate_port()?;
527
528        let mut data = V5_DeviceGpsQuaternion::default();
529        unsafe {
530            vexDeviceGpsQuaternionGet(self.device, &mut data);
531        }
532
533        Ok(mint::Quaternion {
534            v: mint::Vector3 {
535                x: data.x,
536                y: data.y,
537                z: data.z,
538            },
539            s: data.w,
540        })
541    }
542
543    /// Returns raw accelerometer values of the sensor's internal IMU.
544    ///
545    /// # Errors
546    ///
547    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
548    ///
549    /// # Examples
550    ///
551    /// ```
552    /// use vexide::prelude::*;
553    /// use core::time::Duration;
554    ///
555    /// #[vexide::main]
556    /// async fn main(peripherals: Peripherals) {
557    ///     // Assume we're starting in the middle of the field facing upwards, with the
558    ///     // sensor's mounting point being our reference for position.
559    ///     let gps = GpsSensor::new(
560    ///         peripherals.port_1,
561    ///         Point2 { x: 0.0, y: 0.0 },
562    ///         Point2 { x: 0.0, y: 0.0 },
563    ///         0.0,
564    ///     );
565    ///
566    ///     // Read out accleration values every 10mS
567    ///     loop {
568    ///         if let Ok(acceleration) = gps.acceleration() {
569    ///             println!(
570    ///                 "x: {}G, y: {}G, z: {}G",
571    ///                 acceleration.x,
572    ///                 acceleration.y,
573    ///                 acceleration.z,
574    ///             );
575    ///         }
576    ///
577    ///         sleep(Duration::from_millis(10)).await;
578    ///     }
579    /// }
580    /// ```
581    pub fn acceleration(&self) -> Result<mint::Vector3<f64>, PortError> {
582        self.validate_port()?;
583
584        let mut data = V5_DeviceGpsRaw::default();
585        unsafe {
586            vexDeviceGpsRawAccelGet(self.device, &mut data);
587        }
588
589        Ok(mint::Vector3 {
590            x: data.x,
591            y: data.y,
592            z: data.z,
593        })
594    }
595
596    /// Returns the raw gyroscope values of the sensor's internal IMU.
597    ///
598    /// # Errors
599    ///
600    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
601    ///
602    /// # Examples
603    ///
604    /// ```
605    /// use vexide::prelude::*;
606    /// use core::time::Duration;
607    ///
608    /// #[vexide::main]
609    /// async fn main(peripherals: Peripherals) {
610    ///     // Assume we're starting in the middle of the field facing upwards, with the
611    ///     // sensor's mounting point being our reference for position.
612    ///     let gps = GpsSensor::new(
613    ///         peripherals.port_1,
614    ///         Point2 { x: 0.0, y: 0.0 },
615    ///         Point2 { x: 0.0, y: 0.0 },
616    ///         0.0,
617    ///     );
618    ///
619    ///     // Read out angular velocity values every 10mS
620    ///     loop {
621    ///         if let Ok(rates) = gps.gyro_rate() {
622    ///             println!(
623    ///                 "x: {}°/s, y: {}°/s, z: {}°/s",
624    ///                 rates.x,
625    ///                 rates.y,
626    ///                 rates.z,
627    ///             );
628    ///         }
629    ///
630    ///         sleep(Duration::from_millis(10)).await;
631    ///     }
632    /// }
633    /// ```
634    pub fn gyro_rate(&self) -> Result<mint::Vector3<f64>, PortError> {
635        self.validate_port()?;
636
637        let mut data = V5_DeviceGpsRaw::default();
638        unsafe {
639            vexDeviceGpsRawGyroGet(self.device, &mut data);
640        }
641
642        Ok(mint::Vector3 {
643            x: data.x,
644            y: data.y,
645            z: data.z,
646        })
647    }
648
649    /// Offsets the reading of [`GpsSensor::heading`] to zero.
650    ///
651    /// This method has no effect on the values returned by [`GpsSensor::position`].
652    ///
653    /// # Errors
654    ///
655    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
656    ///
657    /// # Examples
658    ///
659    /// ```
660    /// use vexide::prelude::*;
661    /// use core::time::Duration;
662    ///
663    /// #[vexide::main]
664    /// async fn main(peripherals: Peripherals) {
665    ///     // Assume we're starting in the middle of the field facing upwards, with the
666    ///     // sensor's mounting point being our reference for position.
667    ///     let mut gps = GpsSensor::new(
668    ///         peripherals.port_1,
669    ///         Point2 { x: 0.0, y: 0.0 },
670    ///         Point2 { x: 0.0, y: 0.0 },
671    ///         0.0,
672    ///     );
673    ///
674    ///     // Sleep for two seconds to allow the robot to be moved.
675    ///     sleep(Duration::from_secs(2)).await;
676    ///
677    ///     // Store heading before reset.
678    ///     let heading = gps.heading().unwrap_or_default();
679    ///
680    ///     // Reset heading back to zero.
681    ///     _ = gps.reset_heading();
682    /// }
683    /// ```
684    pub fn reset_heading(&mut self) -> Result<(), PortError> {
685        self.set_heading(Default::default())
686    }
687
688    /// Offsets the reading of [`GpsSensor::rotation`] to zero.
689    ///
690    /// This method has no effect on the values returned by [`GpsSensor::position`].
691    ///
692    /// # Errors
693    ///
694    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
695    ///
696    /// # Examples
697    ///
698    /// ```
699    /// use vexide::prelude::*;
700    /// use core::time::Duration;
701    ///
702    /// #[vexide::main]
703    /// async fn main(peripherals: Peripherals) {
704    ///     // Assume we're starting in the middle of the field facing upwards, with the
705    ///     // sensor's mounting point being our reference for position.
706    ///     let mut gps = GpsSensor::new(
707    ///         peripherals.port_1,
708    ///         Point2 { x: 0.0, y: 0.0 },
709    ///         Point2 { x: 0.0, y: 0.0 },
710    ///         0.0,
711    ///     );
712    ///
713    ///     // Sleep for two seconds to allow the robot to be moved.
714    ///     sleep(Duration::from_secs(2)).await;
715    ///
716    ///     // Store rotation before reset.
717    ///     let rotation = gps.rotation().unwrap_or_default();
718    ///
719    ///     // Reset rotation back to zero.
720    ///     _ = gps.reset_rotation();
721    /// }
722    /// ```
723    pub fn reset_rotation(&mut self) -> Result<(), PortError> {
724        self.set_rotation(Default::default())
725    }
726
727    /// Offsets the reading of [`GpsSensor::rotation`] to a specified angle value.
728    ///
729    /// This method has no effect on the values returned by [`GpsSensor::position`].
730    ///
731    /// # Errors
732    ///
733    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
734    ///
735    /// # Examples
736    ///
737    /// ```
738    /// use vexide::prelude::*;
739    ///
740    /// #[vexide::main]
741    /// async fn main(peripherals: Peripherals) {
742    ///     // Assume we're starting in the middle of the field facing upwards, with the
743    ///     // sensor's mounting point being our reference for position.
744    ///     let mut gps = GpsSensor::new(
745    ///         peripherals.port_1,
746    ///         Point2 { x: 0.0, y: 0.0 },
747    ///         Point2 { x: 0.0, y: 0.0 },
748    ///         0.0,
749    ///     );
750    ///
751    ///     // Set rotation to 90 degrees clockwise.
752    ///     _ = gps.set_rotation(90.0);
753    ///
754    ///     println!("Rotation: {:?}", gps.rotation());
755    /// }
756    /// ```
757    pub fn set_rotation(&mut self, rotation: f64) -> Result<(), PortError> {
758        self.validate_port()?;
759
760        self.rotation_offset = rotation - unsafe { vexDeviceGpsHeadingGet(self.device) };
761
762        Ok(())
763    }
764
765    /// Offsets the reading of [`GpsSensor::heading`] to a specified angle value.
766    ///
767    /// Target will default to `360.0` if above `360.0` and default to `0.0` if below `0.0`.
768    ///
769    /// # Errors
770    ///
771    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
772    ///
773    /// # Examples
774    ///
775    /// ```
776    /// use vexide::prelude::*;
777    ///
778    /// #[vexide::main]
779    /// async fn main(peripherals: Peripherals) {
780    ///     // Assume we're starting in the middle of the field facing upwards, with the
781    ///     // sensor's mounting point being our reference for position.
782    ///     let mut gps = GpsSensor::new(
783    ///         peripherals.port_1,
784    ///         Point2 { x: 0.0, y: 0.0 },
785    ///         Point2 { x: 0.0, y: 0.0 },
786    ///         0.0,
787    ///     );
788    ///
789    ///     // Set heading to 90 degrees clockwise.
790    ///     _ = gps.set_heading(90.0);
791    ///
792    ///     println!("Heading: {:?}", gps.heading());
793    /// }
794    /// ```
795    pub fn set_heading(&mut self, heading: f64) -> Result<(), PortError> {
796        self.validate_port()?;
797
798        self.heading_offset = heading - unsafe { vexDeviceGpsDegreesGet(self.device) };
799
800        Ok(())
801    }
802
803    /// Sets the internal computation speed of the sensor's internal IMU.
804    ///
805    /// This method does NOT change the rate at which user code can read data off the GPS, as the
806    /// brain will only talk to the device every 10mS regardless of how fast data is being sent or
807    /// computed. This also has no effect on the speed of methods such as `GpsSensor::position`, as
808    /// it only changes the *internal* computation speed of the sensor's internal IMU.
809    ///
810    /// # Errors
811    ///
812    /// An error is returned if a GPS sensor is not currently connected to the Smart Port.
813    ///
814    /// # Examples
815    ///
816    /// ```
817    /// use vexide::prelude::*;
818    /// use core::time::Duration;
819    ///
820    /// #[vexide::main]
821    /// async fn main(peripherals: Peripherals) {
822    ///     let mut gps = GpsSensor::new(
823    ///         peripherals.port_1,
824    ///         Point2 { x: 0.0, y: 0.0 },
825    ///         Point2 { x: 0.0, y: 0.0 },
826    ///         0.0,
827    ///     );
828    ///
829    ///     // Set to minimum interval.
830    ///     _ = gps.set_data_interval(Duration::from_millis(5));
831    /// }
832    /// ```
833    pub fn set_data_interval(&mut self, interval: Duration) -> Result<(), PortError> {
834        self.validate_port()?;
835
836        unsafe {
837            vexDeviceGpsDataRateSet(self.device, interval.as_millis() as u32);
838        }
839
840        Ok(())
841    }
842}
843
844impl SmartDevice for GpsSensor {
845    fn port_number(&self) -> u8 {
846        self.port.number()
847    }
848
849    fn device_type(&self) -> SmartDeviceType {
850        SmartDeviceType::Gps
851    }
852}
853
854impl From<GpsSensor> for SmartPort {
855    fn from(device: GpsSensor) -> Self {
856        device.port
857    }
858}