linux_bno055/
lib.rs

1mod registers;
2use byteorder::{ByteOrder, LittleEndian};
3use i2cdev::core::I2CDevice;
4use i2cdev::linux::LinuxI2CDevice;
5use log::{debug, error, warn};
6pub use registers::OperationMode;
7use registers::{
8    AccelRegisters, ChipRegisters, Constants, EulerRegisters, GravityRegisters, GyroRegisters,
9    LinearAccelRegisters, MagRegisters, QuaternionRegisters, RegisterPage, StatusRegisters,
10};
11use std::sync::mpsc;
12use std::sync::{Arc, RwLock};
13use std::thread;
14use std::time::Duration;
15
16#[derive(Debug)]
17pub enum Error {
18    I2c(i2cdev::linux::LinuxI2CError),
19    InvalidChipId,
20    CalibrationFailed,
21    ReadError,
22    WriteError,
23}
24
25impl std::fmt::Display for Error {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Error::I2c(err) => write!(f, "I2C error: {}", err),
29            Error::InvalidChipId => write!(f, "Invalid chip ID"),
30            Error::CalibrationFailed => write!(f, "Calibration failed"),
31            Error::ReadError => write!(f, "Read error"),
32            Error::WriteError => write!(f, "Write error"),
33        }
34    }
35}
36
37impl std::error::Error for Error {
38    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
39        match self {
40            Error::I2c(err) => Some(err),
41            _ => None,
42        }
43    }
44}
45
46impl From<i2cdev::linux::LinuxI2CError> for Error {
47    fn from(err: i2cdev::linux::LinuxI2CError) -> Self {
48        Error::I2c(err)
49    }
50}
51
52#[derive(Debug, Clone, Copy)]
53pub struct Quaternion {
54    pub w: f32,
55    pub x: f32,
56    pub y: f32,
57    pub z: f32,
58}
59
60#[derive(Debug, Clone, Copy)]
61pub struct EulerAngles {
62    pub roll: f32,  // x-axis rotation
63    pub pitch: f32, // y-axis rotation
64    pub yaw: f32,   // z-axis rotation
65}
66
67#[derive(Debug, Clone, Copy)]
68pub struct Vector3 {
69    pub x: f32,
70    pub y: f32,
71    pub z: f32,
72}
73
74#[derive(Debug, Clone, Copy)]
75pub struct BnoData {
76    pub quaternion: Quaternion,
77    pub euler: EulerAngles,
78    pub accelerometer: Vector3,
79    pub gyroscope: Vector3,
80    pub magnetometer: Vector3,
81    pub linear_acceleration: Vector3,
82    pub gravity: Vector3,
83    pub temperature: i8,
84    pub calibration_status: u8,
85}
86
87impl Default for BnoData {
88    fn default() -> Self {
89        BnoData {
90            quaternion: Quaternion {
91                w: 0.0,
92                x: 0.0,
93                y: 0.0,
94                z: 0.0,
95            },
96            euler: EulerAngles {
97                roll: 0.0,
98                pitch: 0.0,
99                yaw: 0.0,
100            },
101            accelerometer: Vector3 {
102                x: 0.0,
103                y: 0.0,
104                z: 0.0,
105            },
106            gyroscope: Vector3 {
107                x: 0.0,
108                y: 0.0,
109                z: 0.0,
110            },
111            magnetometer: Vector3 {
112                x: 0.0,
113                y: 0.0,
114                z: 0.0,
115            },
116            linear_acceleration: Vector3 {
117                x: 0.0,
118                y: 0.0,
119                z: 0.0,
120            },
121            gravity: Vector3 {
122                x: 0.0,
123                y: 0.0,
124                z: 0.0,
125            },
126            temperature: 0,
127            calibration_status: 0,
128        }
129    }
130}
131
132pub struct Bno055 {
133    i2c: LinuxI2CDevice,
134}
135
136impl Bno055 {
137    /// Creates a new BNO055 device instance using the specified I2C bus.
138    ///
139    /// # Arguments
140    /// * `i2c_bus` - The I2C bus path (e.g., "/dev/i2c-1")
141    pub fn new(i2c_bus: &str) -> Result<Self, Error> {
142        let i2c = LinuxI2CDevice::new(i2c_bus, Constants::DefaultI2cAddr as u16)?;
143        let mut bno = Bno055 { i2c };
144
145        // Set page 0 before initialization
146        bno.set_page(RegisterPage::Page0)?;
147
148        // Verify we're talking to the right chip
149        bno.verify_chip_id()?;
150
151        // Reset the device
152        bno.reset()?;
153
154        // Configure for NDOF mode (9-axis fusion)
155        bno.set_mode(OperationMode::Ndof)?;
156
157        Ok(bno)
158    }
159
160    fn set_page(&mut self, page: RegisterPage) -> Result<(), Error> {
161        self.i2c
162            .smbus_write_byte_data(ChipRegisters::PageId as u8, page as u8)
163            .map_err(Error::I2c)
164    }
165
166    fn verify_chip_id(&mut self) -> Result<(), Error> {
167        self.set_page(RegisterPage::Page0)?;
168        let chip_id = self.i2c.smbus_read_byte_data(ChipRegisters::ChipId as u8)?;
169        if Constants::ChipId as u8 != chip_id {
170            error!("Invalid chip ID. Expected 0xA0, got {:#x}", chip_id);
171            return Err(Error::InvalidChipId);
172        }
173        Ok(())
174    }
175
176    pub fn reset(&mut self) -> Result<(), Error> {
177        self.set_page(RegisterPage::Page0)?;
178        self.i2c
179            .smbus_write_byte_data(StatusRegisters::SysTrigger as u8, 0x20)?;
180        thread::sleep(Duration::from_millis(650));
181        Ok(())
182    }
183
184    /// Returns the current orientation as a quaternion.
185    /// The quaternion values are normalized and unitless.
186    pub fn get_quaternion(&mut self) -> Result<Quaternion, Error> {
187        self.set_page(RegisterPage::Page0)?;
188        let mut buf = [0u8; 8];
189
190        // Read all quaternion data at once
191        for (i, byte) in buf.iter_mut().enumerate() {
192            *byte = self
193                .i2c
194                .smbus_read_byte_data((QuaternionRegisters::WLsb as u8) + i as u8)?;
195        }
196
197        let scale = 1.0 / ((1 << 14) as f32);
198        Ok(Quaternion {
199            w: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
200            x: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
201            y: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
202            z: (LittleEndian::read_i16(&buf[6..8]) as f32) * scale,
203        })
204    }
205
206    /// Returns the current orientation in Euler angles.
207    /// All angles (roll, pitch, yaw) are in degrees.
208    pub fn get_euler_angles(&mut self) -> Result<EulerAngles, Error> {
209        self.set_page(RegisterPage::Page0)?;
210        let mut buf = [0u8; 6];
211
212        // Read all euler angle data at once
213        for (i, byte) in buf.iter_mut().enumerate() {
214            *byte = self
215                .i2c
216                .smbus_read_byte_data((EulerRegisters::HLsb as u8) + i as u8)?;
217        }
218
219        // Convert to degrees (scale factor is 16)
220        let scale = 1.0 / 16.0;
221        Ok(EulerAngles {
222            yaw: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
223            roll: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
224            pitch: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
225        })
226    }
227
228    /// Returns the linear acceleration vector with gravity compensation.
229    /// All components (x, y, z) are in meters per second squared (m/s²).
230    pub fn get_linear_acceleration(&mut self) -> Result<Vector3, Error> {
231        self.set_page(RegisterPage::Page0)?;
232        let mut buf = [0u8; 6];
233
234        // Read all linear acceleration data at once
235        for (i, byte) in buf.iter_mut().enumerate() {
236            *byte = self
237                .i2c
238                .smbus_read_byte_data((LinearAccelRegisters::XLsb as u8) + i as u8)?;
239        }
240
241        // Convert to m/s² (scale factor is 100)
242        let scale = 1.0 / 100.0;
243        Ok(Vector3 {
244            x: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
245            y: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
246            z: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
247        })
248    }
249
250    /// Returns the gravity vector with linear acceleration compensation.
251    /// All components (x, y, z) are in meters per second squared (m/s²).
252    pub fn get_gravity_vector(&mut self) -> Result<Vector3, Error> {
253        self.set_page(RegisterPage::Page0)?;
254        let mut buf = [0u8; 6];
255
256        // Read all gravity vector data at once
257        for (i, byte) in buf.iter_mut().enumerate() {
258            *byte = self
259                .i2c
260                .smbus_read_byte_data((GravityRegisters::XLsb as u8) + i as u8)?;
261        }
262
263        let scale = 1.0 / 100.0;
264        Ok(Vector3 {
265            x: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
266            y: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
267            z: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
268        })
269    }
270
271    /// Sets the operation mode of the BNO055.
272    ///
273    /// # Arguments
274    /// * `mode` - The operation mode to set.
275    pub fn set_mode(&mut self, mode: OperationMode) -> Result<(), Error> {
276        self.set_page(RegisterPage::Page0)?;
277        self.i2c
278            .smbus_write_byte_data(StatusRegisters::OprMode as u8, mode as u8)?;
279        // Wait for mode switch to complete
280        thread::sleep(Duration::from_millis(20));
281        Ok(())
282    }
283
284    /// Returns the raw accelerometer readings including gravity.
285    /// All components (x, y, z) are in meters per second squared (m/s²).
286    pub fn get_accelerometer(&mut self) -> Result<Vector3, Error> {
287        self.set_page(RegisterPage::Page0)?;
288        let mut buf = [0u8; 6];
289
290        // Read all accelerometer data at once
291        for (i, byte) in buf.iter_mut().enumerate() {
292            *byte = self
293                .i2c
294                .smbus_read_byte_data((AccelRegisters::XLsb as u8) + i as u8)?;
295        }
296
297        // Convert to m/s² (scale factor is 100)
298        let scale = 1.0 / 100.0;
299        Ok(Vector3 {
300            x: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
301            y: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
302            z: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
303        })
304    }
305
306    /// Returns the magnetometer readings.
307    /// All components (x, y, z) are in microTesla (µT).
308    pub fn get_magnetometer(&mut self) -> Result<Vector3, Error> {
309        self.set_page(RegisterPage::Page0)?;
310        let mut buf = [0u8; 6];
311
312        // Read all magnetometer data at once
313        for (i, byte) in buf.iter_mut().enumerate() {
314            *byte = self
315                .i2c
316                .smbus_read_byte_data((MagRegisters::XLsb as u8) + i as u8)?;
317        }
318
319        // Convert to microTesla
320        let scale = 1.0 / 16.0;
321        Ok(Vector3 {
322            x: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
323            y: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
324            z: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
325        })
326    }
327
328    /// Returns the gyroscope readings.
329    /// All components (x, y, z) are in degrees per second (°/s).
330    pub fn get_gyroscope(&mut self) -> Result<Vector3, Error> {
331        self.set_page(RegisterPage::Page0)?;
332        let mut buf = [0u8; 6];
333
334        // Read all gyroscope data at once
335        for (i, byte) in buf.iter_mut().enumerate() {
336            *byte = self
337                .i2c
338                .smbus_read_byte_data((GyroRegisters::XLsb as u8) + i as u8)?;
339        }
340
341        // Convert to degrees per second
342        let scale = 1.0 / 16.0;
343        Ok(Vector3 {
344            x: (LittleEndian::read_i16(&buf[0..2]) as f32) * scale,
345            y: (LittleEndian::read_i16(&buf[2..4]) as f32) * scale,
346            z: (LittleEndian::read_i16(&buf[4..6]) as f32) * scale,
347        })
348    }
349
350    /// Returns the chip temperature.
351    /// Temperature is in degrees Celsius (°C).
352    pub fn get_temperature(&mut self) -> Result<i8, Error> {
353        self.set_page(RegisterPage::Page0)?;
354        let temp = self
355            .i2c
356            .smbus_read_byte_data(StatusRegisters::Temperature as u8)? as i8;
357        Ok(temp)
358    }
359
360    /// Returns the calibration status byte.
361    /// Bits 5-4: gyroscope (0-3)
362    /// Bits 3-2: accelerometer (0-3)
363    /// Bits 1-0: magnetometer (0-3)
364    /// For each sensor, 0 = uncalibrated, 3 = fully calibrated
365    pub fn get_calibration_status(&mut self) -> Result<u8, Error> {
366        self.set_page(RegisterPage::Page0)?;
367        let status = self
368            .i2c
369            .smbus_read_byte_data(StatusRegisters::CalibStat as u8)?;
370        Ok(status)
371    }
372}
373
374pub struct Bno055Reader {
375    data: Arc<RwLock<BnoData>>,
376    command_tx: mpsc::Sender<ImuCommand>,
377}
378
379impl Bno055Reader {
380    pub fn new(i2c_bus: &str) -> Result<Self, Error> {
381        let data = Arc::new(RwLock::new(BnoData::default()));
382        let (command_tx, command_rx) = mpsc::channel();
383
384        // Synchronously initialize (calibrate) the IMU.
385        // If this fails, the error is propagated immediately.
386        let imu = Bno055::new(i2c_bus)?;
387
388        // Spawn a thread that continuously reads sensor data using the initialized IMU.
389        Self::start_reading_thread_with_imu(imu, Arc::clone(&data), command_rx);
390
391        Ok(Bno055Reader { data, command_tx })
392    }
393
394    fn start_reading_thread_with_imu(
395        mut imu: Bno055,
396        data: Arc<RwLock<BnoData>>,
397        command_rx: mpsc::Receiver<ImuCommand>,
398    ) {
399        thread::spawn(move || {
400            debug!("BNO055 reading thread started");
401            loop {
402                // Process any pending commands
403                if let Ok(command) = command_rx.try_recv() {
404                    match command {
405                        ImuCommand::SetMode(mode) => {
406                            if let Err(e) = imu.set_mode(mode) {
407                                error!("Failed to set mode: {}", e);
408                            }
409                        }
410                        ImuCommand::Reset => {
411                            if let Err(e) = imu.reset() {
412                                error!("Failed to reset: {}", e);
413                            }
414                        }
415                        ImuCommand::Stop => break,
416                    }
417                }
418
419                // Read sensor data and update shared data
420                let mut data_holder = BnoData::default();
421
422                if let Ok(quat) = imu.get_quaternion() {
423                    data_holder.quaternion = quat;
424                } else {
425                    warn!("Failed to get quaternion");
426                }
427
428                if let Ok(euler) = imu.get_euler_angles() {
429                    data_holder.euler = euler;
430                } else {
431                    warn!("Failed to get euler angles");
432                }
433
434                if let Ok(accel) = imu.get_accelerometer() {
435                    data_holder.accelerometer = accel;
436                } else {
437                    warn!("Failed to get accelerometer");
438                }
439
440                if let Ok(gyro) = imu.get_gyroscope() {
441                    data_holder.gyroscope = gyro;
442                } else {
443                    warn!("Failed to get gyroscope");
444                }
445
446                if let Ok(mag) = imu.get_magnetometer() {
447                    data_holder.magnetometer = mag;
448                } else {
449                    warn!("Failed to get magnetometer");
450                }
451
452                if let Ok(linear_accel) = imu.get_linear_acceleration() {
453                    data_holder.linear_acceleration = linear_accel;
454                } else {
455                    warn!("Failed to get linear acceleration");
456                }
457
458                if let Ok(gravity) = imu.get_gravity_vector() {
459                    data_holder.gravity = gravity;
460                } else {
461                    warn!("Failed to get gravity vector");
462                }
463
464                if let Ok(temp) = imu.get_temperature() {
465                    data_holder.temperature = temp;
466                } else {
467                    warn!("Failed to get temperature");
468                }
469
470                if let Ok(status) = imu.get_calibration_status() {
471                    data_holder.calibration_status = status;
472                } else {
473                    warn!("Failed to get calibration status");
474                }
475
476                // Update shared data
477                if let Ok(mut imu_data) = data.write() {
478                    *imu_data = data_holder;
479                }
480
481                // IMU sends data at 100 Hz
482                thread::sleep(Duration::from_millis(10));
483            }
484            debug!("BNO055 reading thread exiting");
485        });
486    }
487
488    pub fn set_mode(&self, mode: OperationMode) -> Result<(), Error> {
489        self.command_tx
490            .send(ImuCommand::SetMode(mode))
491            .map_err(|_| Error::WriteError)
492    }
493
494    pub fn reset(&self) -> Result<(), Error> {
495        self.command_tx
496            .send(ImuCommand::Reset)
497            .map_err(|_| Error::WriteError)
498    }
499
500    pub fn stop(&self) -> Result<(), Error> {
501        self.command_tx
502            .send(ImuCommand::Stop)
503            .map_err(|_| Error::WriteError)
504    }
505
506    pub fn get_data(&self) -> Result<BnoData, Error> {
507        self.data
508            .read()
509            .map(|data| *data)
510            .map_err(|_| Error::ReadError)
511    }
512}
513
514impl Drop for Bno055Reader {
515    fn drop(&mut self) {
516        let _ = self.stop();
517    }
518}
519
520#[derive(Debug)]
521pub enum ImuCommand {
522    SetMode(OperationMode),
523    Reset,
524    Stop,
525}