ar_drivers/lib.rs
1// Copyright (C) 2023, Alex Badics
2// This file is part of ar-drivers-rs
3// Licensed under the MIT license. See LICENSE file in the project root for details.
4
5#![warn(missing_docs)]
6//! This crate contains a simplified driver for Rokid Air and Mad Gaze Glow AR glasses.
7//! It supports getting basic sensor data and setting up the display.
8//!
9//! Example usage (in a thread, probably):
10//! ```ignore
11//! let mut glasses = any_glasses().unwrap();
12//! loop {
13//! match glasses.read_event().unwrap() {
14//! GlassesEvent::AccGyro {accelerometer, gyroscope} => ...,
15//! GlassesEvent::Magnetometer(data) => ...,
16//! GlassesEvent::KeyPress(data) => ...,
17//! _ => {}
18//! }
19//! }
20//! ```
21//!
22//! As opposed to e.g. Rokid's own API, this is all that you get, since this is what comes
23//! out of the hardware. To get quaternions, you should probably use a lib that implements
24//! Madgwicks algorithm or a proper EKF. One good choice is the `eskf` crate.
25//!
26//! ## Feature flags
27//!
28//! Support for individual AR glasses types ca be enabled with the following features:
29//!
30//! * `mad_gaze`: Mad Gaze Glow
31//! * `nreal`: Nreal Light
32//! * `rokid`: Rokid Air
33//!
34//! All of them are enabled by default, which may bring in some unwanted dependencies if you
35//! only want to support a specific type.
36
37use nalgebra::{Isometry3, Matrix3, UnitQuaternion, Vector2, Vector3};
38
39#[cfg(feature = "grawoow")]
40pub mod grawoow;
41#[cfg(feature = "mad_gaze")]
42pub mod mad_gaze;
43#[cfg(feature = "nreal")]
44pub mod nreal_air;
45#[cfg(feature = "nreal")]
46pub mod nreal_light;
47#[cfg(feature = "rokid")]
48pub mod rokid;
49mod util;
50
51/// Possible errors resulting from `ar-drivers` API calls
52#[derive(Debug)]
53pub enum Error {
54 /// A standard IO error happened. See [`std::io::Error`] for specifics
55 IoError(std::io::Error),
56 /// An rusb error happened. See [`rusb::Error`] for specifics
57 #[cfg(feature = "rusb")]
58 UsbError(rusb::Error),
59 /// A hidapi error happened. See [`hidapi::HidError`] for specifics
60 #[cfg(feature = "hidapi")]
61 HidError(hidapi::HidError),
62 /// A serialport error happened. See [`serialport::Error`] for specifics
63 #[cfg(feature = "serialport")]
64 SerialPortError(serialport::Error),
65 /// No glasses were found.
66 NotFound,
67 /// Packet sending or reception timed out. Note that this is not the only
68 /// timeout error that is sent (e.g. UsbError can contain a timeout), and
69 /// also this is usually a fatal one.
70 PacketTimeout,
71 /// Other fatal error, usually a problem with the library itself, or
72 /// a device support issue. File a bug if you encounter this.
73 Other(&'static str),
74}
75
76type Result<T> = std::result::Result<T, Error>;
77
78impl std::error::Error for Error {
79 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
80 match self {
81 Error::IoError(e) => Some(e),
82 #[cfg(feature = "rusb")]
83 Error::UsbError(e) => Some(e),
84 #[cfg(feature = "hidapi")]
85 Error::HidError(e) => Some(e),
86 #[cfg(feature = "serialport")]
87 Error::SerialPortError(e) => Some(e),
88 _ => None,
89 }
90 }
91}
92
93impl std::fmt::Display for Error {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.write_str(match self {
96 Error::IoError(_) => "I/O error",
97 #[cfg(feature = "rusb")]
98 Error::UsbError(_) => "Libusb error",
99 #[cfg(feature = "hidapi")]
100 Error::HidError(_) => "Hidapi error",
101 #[cfg(feature = "serialport")]
102 Error::SerialPortError(_) => "Serial error",
103 Error::NotFound => "Glasses not found",
104 Error::PacketTimeout => "Packet timeout",
105 Error::Other(s) => s,
106 })
107 }
108}
109
110/// AR glasses sensor event, got from [`ARGlasses::read_event`]
111///
112/// Coordinate system is "RUB": Positive X is Right, Positive Y is Up, Positive Z is backwards.
113/// This is the same as the Android sensor coordinate system.
114#[derive(Debug, Clone)]
115pub enum GlassesEvent {
116 /// Synchronized accelerometer and gyroscope data.
117 AccGyro {
118 /// Accelerometer data in m^2/s.
119 ///
120 /// Remember that while gravitational acceleration is "down", the acceleration
121 /// the device "feels" is the one opposite from that, so the normal reading
122 /// when the device is upright is (0, 9.81, 0)
123 accelerometer: Vector3<f32>,
124 /// Gyroscope data. Right handed rotation in rad/sec,
125 /// e.g. turning left is positive y axis.
126 gyroscope: Vector3<f32>,
127 /// Timestamp, in device time, in microseconds
128 timestamp: u64,
129 },
130 /// Magnetometer data.
131 Magnetometer {
132 /// Direction of magnetic north (more or less). Unit is uT.
133 magnetometer: Vector3<f32>,
134 /// Timestamp, in device time, in microseconds
135 timestamp: u64,
136 },
137 /// A key was pressed (sent once per press)
138 /// The number is a key ID, starting from 0.
139 KeyPress(u8),
140
141 /// Proximity sensor senses the user, i.e. the glasses were put on
142 /// Sent once per event.
143 ProximityNear,
144
145 /// Proximity sensor senses the user, i.e. the glasses were taken off.
146 /// Sent once per event.
147 ProximityFar,
148 /// Ambient light level. Unit is vendor-specific
149 AmbientLight(u16),
150 /// V-sync happened on the device
151 VSync,
152}
153
154/// Display mode used by [`ARGlasses::set_display_mode`]
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum DisplayMode {
157 /// Picture should be same for both eyes (simple full HD mode)
158 SameOnBoth,
159 /// Set display to 3840*1080 or 3840x1200,
160 /// where the left half is the left eye, the right half is the right eye
161 Stereo,
162 /// Set display to half-SBS mode, which presents itself as 1920*1080 resolution,
163 /// but actually scales down everything to 960x540,then upscales to 3840x1080
164 HalfSBS,
165 /// Set display to mirrored high refresh rate mode (typically 120Hz)
166 HighRefreshRate,
167 /// Set display to high refresh rate SBS mode
168 HighRefreshRateSBS,
169}
170
171/// Display side used by [`ARGlasses::view_matrix`]
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub enum Side {
174 /// Left display
175 Left,
176 /// Right display
177 Right,
178}
179
180/// Common interface for AR implemented glasses
181pub trait ARGlasses: Send {
182 /// Get the serial number of the glasses
183 fn serial(&mut self) -> Result<String>;
184 /// Get a single sensor event. Blocks.
185 fn read_event(&mut self) -> Result<GlassesEvent>;
186 /// Get the display mode of the glasses. See [`DisplayMode`]
187 fn get_display_mode(&mut self) -> Result<DisplayMode>;
188 /// Set the display mode of the glasses. See [`DisplayMode`]
189 fn set_display_mode(&mut self, display_mode: DisplayMode) -> Result<()>;
190 /// Field of view of the display along the horizontal axis, in radians
191 fn display_fov(&self) -> f32;
192 /// Transformation from IMU frame to display frame, at the specified
193 /// IPD (interpupillary distance). The `ipd` parameter is in meters.
194 /// A typical value is 0.07.
195 fn imu_to_display_matrix(&self, side: Side, ipd: f32) -> Isometry3<f64>;
196 /// Name of the device
197 fn name(&self) -> &'static str;
198 /// Get built-in camera descriptors
199 fn cameras(&self) -> Result<Vec<CameraDescriptor>> {
200 Ok(Vec::new())
201 }
202 /// The additional delay (in usecs) of the glasses' display from getting the data
203 /// on DisplayPort. This is not really an absolute value, but more of
204 /// a relative measure between different glasses.
205 /// In the future this may depend on the current display mode.
206 fn display_delay(&self) -> u64;
207}
208
209/// Represents one built-in camera
210///
211/// Warning: Experimental. May change between any versions.
212#[derive(Debug, Clone)]
213pub struct CameraDescriptor {
214 /// The unique name for the type of the camera.
215 pub name: &'static str,
216 /// The width and height in pixels for the calibration data
217 pub resolution: Vector2<f64>,
218 /// The intrinsic matrix of the camera
219 pub intrinsic_matrix: Matrix3<f64>,
220 /// Distortion coefficients: k1, k2, p1, p2, k3
221 pub distortion: [f64; 5],
222 /// Additional rectification matrix for stereo cameras
223 pub stereo_rotation: UnitQuaternion<f64>,
224 /// Transformation from the IMU frame to the camera frame
225 pub imu_to_camera: Isometry3<f64>,
226}
227
228/// Convenience function to detect and connect to any of the supported glasses
229#[cfg(not(target_os = "android"))]
230pub fn any_glasses() -> Result<Box<dyn ARGlasses>> {
231 #[cfg(feature = "rokid")]
232 if let Ok(glasses) = rokid::RokidAir::new() {
233 return Ok(Box::new(glasses));
234 };
235 #[cfg(feature = "nreal")]
236 if let Ok(glasses) = nreal_air::NrealAir::new() {
237 return Ok(Box::new(glasses));
238 };
239 #[cfg(feature = "nreal")]
240 if let Ok(glasses) = nreal_light::NrealLight::new() {
241 return Ok(Box::new(glasses));
242 };
243 #[cfg(feature = "grawoow")]
244 if let Ok(glasses) = grawoow::GrawoowG530::new() {
245 return Ok(Box::new(glasses));
246 };
247 #[cfg(feature = "mad_gaze")]
248 if let Ok(glasses) = mad_gaze::MadGazeGlow::new() {
249 return Ok(Box::new(glasses));
250 };
251 Err(Error::NotFound)
252}
253
254impl From<std::io::Error> for Error {
255 fn from(e: std::io::Error) -> Self {
256 Error::IoError(e)
257 }
258}
259
260#[cfg(feature = "rusb")]
261impl From<rusb::Error> for Error {
262 fn from(e: rusb::Error) -> Self {
263 Error::UsbError(e)
264 }
265}
266
267#[cfg(feature = "hidapi")]
268impl From<hidapi::HidError> for Error {
269 fn from(e: hidapi::HidError) -> Self {
270 Error::HidError(e)
271 }
272}
273
274#[cfg(feature = "serialport")]
275impl From<serialport::Error> for Error {
276 fn from(e: serialport::Error) -> Self {
277 Error::SerialPortError(e)
278 }
279}
280
281impl From<&'static str> for Error {
282 fn from(e: &'static str) -> Self {
283 Error::Other(e)
284 }
285}