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}