bno08x_rs/
lib.rs

1// Copyright 2025 Au-Zone Technologies Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! # BNO08x IMU Driver
5//!
6//! A Rust userspace driver for the BNO08x family of 9-axis IMU sensors
7//! from Bosch/Hillcrest Labs.
8//!
9//! ## Overview
10//!
11//! The BNO08x is a System-in-Package (SiP) that integrates:
12//! - Triaxial 14-bit accelerometer
13//! - Triaxial 16-bit gyroscope
14//! - Triaxial geomagnetic sensor
15//! - 32-bit microcontroller running sensor fusion firmware
16//!
17//! This crate provides a safe Rust interface for communicating with the
18//! sensor over SPI, handling the SHTP (Sensor Hub Transport Protocol) and
19//! providing high-level access to fused and raw sensor data.
20//!
21//! ## Features
22//!
23//! - **Sensor Fusion**: Rotation vectors (absolute, game, geomagnetic)
24//! - **Raw Sensors**: Accelerometer, gyroscope, magnetometer
25//! - **Derived Data**: Linear acceleration, gravity vector
26//! - **Configurable Rates**: 1 Hz to 1 kHz update rates
27//! - **GPIO Integration**: Device tree symbolic name support for Linux
28//! - **Callbacks**: Event-driven sensor data handling
29//!
30//! ## Quick Start
31//!
32//! ```no_run
33//! use bno08x_rs::{BNO08x, SENSOR_REPORTID_ACCELEROMETER};
34//!
35//! fn main() -> std::io::Result<()> {
36//!     // Create driver using GPIO symbolic names
37//!     let mut imu = BNO08x::new_spi_from_symbol(
38//!         "/dev/spidev1.0", // SPI device
39//!         "IMU_INT",        // Interrupt GPIO name
40//!         "IMU_RST",        // Reset GPIO name
41//!     )?;
42//!
43//!     // Initialize and configure
44//!     imu.init()?;
45//!     imu.enable_report(SENSOR_REPORTID_ACCELEROMETER, 100)?; // 10 Hz
46//!
47//!     // Main loop
48//!     loop {
49//!         imu.handle_all_messages(100);
50//!         let accel = imu.accelerometer()?;
51//!         println!("Accel: {:?}", accel);
52//!     }
53//! }
54//! ```
55//!
56//! ## Sensor Reports
57//!
58//! Enable specific sensor reports using their report ID constants:
59//!
60//! | Report | Constant | Method | Units |
61//! |--------|----------|--------|-------|
62//! | Accelerometer | [`SENSOR_REPORTID_ACCELEROMETER`] | [`accelerometer()`](BNO08x::accelerometer) | m/s² |
63//! | Gyroscope | [`SENSOR_REPORTID_GYROSCOPE`] | [`gyro()`](BNO08x::gyro) | rad/s |
64//! | Magnetometer | [`SENSOR_REPORTID_MAGNETIC_FIELD`] | [`mag_field()`](BNO08x::mag_field) | µT |
65//! | Rotation Vector | [`SENSOR_REPORTID_ROTATION_VECTOR`] | [`rotation_quaternion()`](BNO08x::rotation_quaternion) | quaternion |
66//! | Game Rotation | [`SENSOR_REPORTID_ROTATION_VECTOR_GAME`] | [`game_rotation_quaternion()`](BNO08x::game_rotation_quaternion) | quaternion |
67//! | Geomagnetic Rotation | [`SENSOR_REPORTID_ROTATION_VECTOR_GEOMAGNETIC`] | [`geomag_rotation_quaternion()`](BNO08x::geomag_rotation_quaternion) | quaternion |
68//! | Linear Acceleration | [`SENSOR_REPORTID_LINEAR_ACCEL`] | [`linear_accel()`](BNO08x::linear_accel) | m/s² |
69//! | Gravity | [`SENSOR_REPORTID_GRAVITY`] | [`gravity()`](BNO08x::gravity) | m/s² |
70//!
71//! ## Hardware Requirements
72//!
73//! - Linux with SPI (`spidev`) and GPIO (`gpiod`) support
74//! - BNO08x sensor connected via SPI
75//! - GPIO for interrupt (HINTN) and reset (RSTN) signals
76//!
77//! ## More Information
78//!
79//! - [Repository](https://github.com/EdgeFirstAI/bno08x-rs)
80//! - [crates.io](https://crates.io/crates/bno08x-rs)
81//! - [Maivin Platform](https://www.edgefirst.ai/edgefirstmodules)
82
83pub mod constants;
84pub mod driver;
85pub mod frs;
86pub mod interface;
87pub mod reports;
88
89// Re-export main driver types at crate root for convenience
90pub use constants::{
91    SENSOR_REPORTID_ACCELEROMETER, SENSOR_REPORTID_GRAVITY, SENSOR_REPORTID_GYROSCOPE,
92    SENSOR_REPORTID_GYROSCOPE_UNCALIB, SENSOR_REPORTID_LINEAR_ACCEL,
93    SENSOR_REPORTID_MAGNETIC_FIELD, SENSOR_REPORTID_ROTATION_VECTOR,
94    SENSOR_REPORTID_ROTATION_VECTOR_GAME, SENSOR_REPORTID_ROTATION_VECTOR_GEOMAGNETIC,
95};
96pub use driver::{BNO08x, DriverError};
97pub use reports::SensorData;
98
99/// Low-level errors from the communication interface
100#[derive(Debug)]
101pub enum Error<CommE, PinE> {
102    /// Sensor communication error
103    Comm(CommE),
104    /// Pin setting error
105    Pin(PinE),
106
107    /// The sensor is not responding
108    SensorUnresponsive,
109
110    /// Buffer overflow - packet too large for receive buffer
111    BufferOverflow {
112        /// Size of the packet that was received
113        packet_size: usize,
114        /// Size of the buffer available
115        buffer_size: usize,
116    },
117
118    /// No data available from sensor (timeout waiting for HINTN)
119    NoDataAvailable,
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_error_comm_variant() {
128        let err: Error<&str, ()> = Error::Comm("SPI failure");
129        match err {
130            Error::Comm(msg) => assert_eq!(msg, "SPI failure"),
131            _ => panic!("Expected Comm variant"),
132        }
133    }
134
135    #[test]
136    fn test_error_pin_variant() {
137        let err: Error<(), &str> = Error::Pin("GPIO error");
138        match err {
139            Error::Pin(msg) => assert_eq!(msg, "GPIO error"),
140            _ => panic!("Expected Pin variant"),
141        }
142    }
143
144    #[test]
145    fn test_error_sensor_unresponsive() {
146        let err: Error<(), ()> = Error::SensorUnresponsive;
147        match err {
148            Error::SensorUnresponsive => {} // expected
149            _ => panic!("Expected SensorUnresponsive variant"),
150        }
151    }
152
153    #[test]
154    fn test_error_buffer_overflow() {
155        let err: Error<(), ()> = Error::BufferOverflow {
156            packet_size: 4096,
157            buffer_size: 2048,
158        };
159        match err {
160            Error::BufferOverflow {
161                packet_size,
162                buffer_size,
163            } => {
164                assert_eq!(packet_size, 4096);
165                assert_eq!(buffer_size, 2048);
166            }
167            _ => panic!("Expected BufferOverflow variant"),
168        }
169    }
170
171    #[test]
172    fn test_error_no_data_available() {
173        let err: Error<(), ()> = Error::NoDataAvailable;
174        match err {
175            Error::NoDataAvailable => {} // expected
176            _ => panic!("Expected NoDataAvailable variant"),
177        }
178    }
179
180    #[test]
181    fn test_error_debug_formatting() {
182        // Test that Debug is implemented and produces reasonable output
183        let comm_err: Error<&str, ()> = Error::Comm("test");
184        let debug_str = format!("{:?}", comm_err);
185        assert!(debug_str.contains("Comm"));
186        assert!(debug_str.contains("test"));
187
188        let overflow_err: Error<(), ()> = Error::BufferOverflow {
189            packet_size: 100,
190            buffer_size: 50,
191        };
192        let debug_str = format!("{:?}", overflow_err);
193        assert!(debug_str.contains("BufferOverflow"));
194        assert!(debug_str.contains("100"));
195        assert!(debug_str.contains("50"));
196    }
197}