UNPKG

bno055-imu-node

Version:

Interact with a BNO055 IMU from your Node.js app

287 lines (238 loc) 8.23 kB
import * as i2c from 'i2c-bus'; import { BNO055_CONFIG_MODE_WAIT, BNO055_ID, BNO055_MODE_SWITCH_WAIT, DeviceAddress, EulerUnitScale, OpMode, PowerLevel, Reg, SystemError, SystemStatus, TempUnitScale, } from './constants'; import { CalibrationStatusMap, Offsets, SelfTestResult, SensorUnits, Versions, AxisMapping } from './types'; const wait = (t: number) => new Promise(ok => setTimeout(ok, t)); export class BNO055 { static async begin( address: DeviceAddress, mode: OpMode = OpMode.FullFusion, busNumber = 1 ): Promise<BNO055> { const bus = await i2c.openPromisified(busNumber); const device = new BNO055(bus, address); await device.verifyConnection(); await device.setMode(mode); await device.useExternalClock(); await device.getUnits(); return device; } mode: OpMode = OpMode.Config; units: SensorUnits = { accel: 'mps2', euler: 'deg', gyro: 'degps', temp: 'c', }; private constructor( private readonly bus: i2c.PromisifiedBus, readonly address: number ) { } async getAxisMapping(): Promise<AxisMapping> { const axisMaps = await this.bus.readByte(this.address, Reg.AXIS_MAP_CONFIG); const axisSigns = await this.bus.readByte(this.address, Reg.AXIS_MAP_SIGN); return { X: { axis: axisMaps & 0x3, sign: axisSigns >> 2 & 0x1, }, Y: { axis: axisMaps >> 2 & 0x3, sign: axisSigns >> 1 & 0x1, }, Z: { axis: axisMaps >> 4 & 0x3, sign: axisSigns & 0x1, }, }; } async getCalibrationStatuses(): Promise<CalibrationStatusMap> { const calByte = await this.bus.readByte(this.address, Reg.CALIB_STAT); return { sys: (calByte >> 6) & 0x03, gyro: (calByte >> 4) & 0x03, accel: (calByte >> 2) & 0x03, mag: calByte & 0x03, }; } async getEuler() { const buffer = await this.readBlock(Reg.EULER_H_LSB, 6); const scale = this.units.euler === 'deg' ? 1 / EulerUnitScale.Degs : 1 / EulerUnitScale.Rads; return { h: scale * buffer.readInt16LE(0), r: scale * buffer.readInt16LE(2), p: scale * buffer.readInt16LE(4), }; } async getMode(): Promise<OpMode> { const mode = (await this.bus.readByte(this.address, Reg.OPR_MODE)) & 0xF; this.mode = mode; return mode; } async getPage() { return await this.bus.readByte(this.address, Reg.PAGE_ID) & 0x1; } async getQuat() { const buffer = await this.readBlock(Reg.QUATERNION_DATA_W_LSB, 8); const scale = (1.0 / (1 << 14)); return { w: scale * buffer.readInt16LE(0), x: scale * buffer.readInt16LE(2), y: scale * buffer.readInt16LE(4), z: scale * buffer.readInt16LE(6), }; } async getSelfTestResults(): Promise<SelfTestResult> { const selfTest = await this.bus.readByte(this.address, Reg.SELFTEST_RESULT); return { mcuPassed: (selfTest >> 3 & 0x1) === 1, magPassed: (selfTest >> 2 & 0x1) === 1, accelPassed: (selfTest >> 1 & 0x1) === 1, gyroPassed: (selfTest & 0x1) === 1, }; } async getSensorOffsets(): Promise<Offsets | undefined> { if (await this.isFullyCalibrated()) { const savedMode = this.mode; await this.setMode(OpMode.Config); const offsets: Offsets = { accelX: await this.readDoubleByte(Reg.ACCEL_OFFSET_X_LSB), accelY: await this.bus.readByte(this.address, Reg.ACCEL_OFFSET_Y_LSB), accelZ: await this.bus.readByte(this.address, Reg.ACCEL_OFFSET_Z_LSB), magX: await this.bus.readByte(this.address, Reg.MAG_OFFSET_X_LSB), magY: await this.bus.readByte(this.address, Reg.MAG_OFFSET_Y_LSB), magZ: await this.bus.readByte(this.address, Reg.MAG_OFFSET_Z_LSB), gyroX: await this.bus.readByte(this.address, Reg.GYRO_OFFSET_X_LSB), gyroY: await this.bus.readByte(this.address, Reg.GYRO_OFFSET_Y_LSB), gyroZ: await this.bus.readByte(this.address, Reg.GYRO_OFFSET_Z_LSB), accelRadius: await this.bus.readByte(this.address, Reg.ACCEL_RADIUS_LSB), magRadius: await this.bus.readByte(this.address, Reg.MAG_RADIUS_LSB), }; await this.setMode(savedMode); return offsets; } else { return; } } async getSystemError(): Promise<SystemError> { return await this.bus.readByte(this.address, Reg.SYS_ERR) & 0xF; } async getSystemStatus(): Promise<SystemStatus> { return await this.bus.readByte(this.address, Reg.SYS_STAT) & 0x7; } async getTemperature() { const tempByte = await this.bus.readByte(this.address, Reg.TEMP); const temp = Buffer.of(tempByte).readInt8(0); return temp * (this.units.temp === 'c' ? TempUnitScale.C : TempUnitScale.F); } async getUnits(): Promise<SensorUnits> { const unitByte = await this.bus.readByte(this.address, Reg.UNIT_SEL); this.units = { accel: unitByte & 0x1 ? 'mg' : 'mps2', euler: (unitByte >> 2) & 0x1 ? 'rad' : 'deg', gyro: (unitByte >> 1) & 0x1 ? 'radps' : 'degps', temp: (unitByte >> 4) & 0x1 ? 'f' : 'c', }; return { ...this.units }; } async getVersions(): Promise<Versions> { const [ device, accel, mag, gyro, swLsb, swMsb, bootloader, ] = await this.readBlock(Reg.DEVICE_ID, 7); const software = `${swMsb >> 4}.${swMsb & 0xF}.${swLsb >> 4}.${swLsb & 0xF}`; return { device, accel, mag, gyro, software, bootloader }; } /** * Checks that all relevant calibration status values are set to 3 (fully calibrated) */ async isFullyCalibrated() { const { sys, accel, gyro, mag } = await this.getCalibrationStatuses(); switch (this.mode) { case OpMode.AccelOnly: return accel === 3; case OpMode.MagOnly: return mag === 3; case OpMode.GyroOnly: case OpMode.ImuMagForGyro: return gyro === 3; case OpMode.AccelMag: case OpMode.Compass: return accel === 3 && mag === 3; case OpMode.AccelGyro: case OpMode.Imu: return accel === 3 && gyro === 3; case OpMode.MagGyro: return mag === 3 && gyro === 3; default: return sys === 3 && gyro === 3 && accel === 3 && mag === 3; } } async resetSystem() { const savedMode = this.mode; await this.setMode(OpMode.Config); await this.bus.writeByte(this.address, Reg.SYS_TRIGGER, 0x20); await wait(2000); await this.setMode(savedMode); } async setAxisMapping({ X, Y, Z }: AxisMapping) { const savedMode = this.mode; await this.setMode(OpMode.Config); const axisMaps = (Z.axis << 4) | (Y.axis << 2) | X.axis; const axisSigns = (X.sign << 2) | (Y.sign << 1) | Z.sign; await this.bus.writeByte(this.address, Reg.AXIS_MAP_CONFIG, axisMaps); await this.bus.writeByte(this.address, Reg.AXIS_MAP_SIGN, axisSigns & 0x7); await this.setMode(savedMode); } async setMode(mode: OpMode) { await this.bus.writeByte(this.address, Reg.OPR_MODE, mode); await wait(mode === OpMode.Config ? BNO055_CONFIG_MODE_WAIT : BNO055_MODE_SWITCH_WAIT); this.mode = mode; } async setPowerLevel(level = PowerLevel.Normal) { const savedMode = this.mode; await this.setMode(OpMode.Config); await this.bus.writeByte(this.address, Reg.PWR_MODE, level); await this.bus.writeByte(this.address, Reg.PAGE_ID, 0); await this.setMode(savedMode); } async useExternalClock() { const savedMode = this.mode; await this.setMode(OpMode.Config); await this.bus.writeByte(this.address, Reg.PAGE_ID, 0); await this.bus.writeByte(this.address, Reg.SYS_TRIGGER, 0x80); await this.setMode(savedMode); } async verifyConnection() { if (await this.bus.readByte(this.address, Reg.DEVICE_ID) !== BNO055_ID) { throw new Error(`Device does not seem to be connected`); } } /** * I2C Helper Methods */ private async readDoubleByte(reg: number) { const [lsb, msb] = await this.readBlock(reg, 2); return (msb << 8) | lsb; } private async readBlock(reg: Reg, length = 1) { const buffer = Buffer.alloc(length); await this.bus.readI2cBlock(this.address, reg, length, buffer); return buffer; } }