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}