diff --git a/adafruit_bno080/__init__.py b/adafruit_bno080/__init__.py index 0e245b2..baffa9e 100644 --- a/adafruit_bno080/__init__.py +++ b/adafruit_bno080/__init__.py @@ -29,8 +29,9 @@ from struct import unpack_from, pack_into from collections import namedtuple -from time import sleep, monotonic, monotonic_ns +import time from micropython import const +import digitalio # TODO: Remove on release from .debug import channels, reports @@ -66,18 +67,18 @@ # Calibrated Acceleration (m/s2) -_BNO_REPORT_ACCELEROMETER = const(0x01) +BNO_REPORT_ACCELEROMETER = const(0x01) # Calibrated gyroscope (rad/s). -_BNO_REPORT_GYROSCOPE = const(0x02) +BNO_REPORT_GYROSCOPE = const(0x02) # Magnetic field calibrated (in µTesla). The fully calibrated magnetic field measurement. -_BNO_REPORT_MAGNETIC_FIELD = const(0x03) +BNO_REPORT_MAGNETIC_FIELD = const(0x03) # Linear acceleration (m/s2). Acceleration of the device with gravity removed -_BNO_REPORT_LINEAR_ACCELERATION = const(0x04) +BNO_REPORT_LINEAR_ACCELERATION = const(0x04) # Rotation Vector -_BNO_REPORT_ROTATION_VECTOR = const(0x05) -_BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR = const(0x09) -_BNO_REPORT_STEP_COUNTER = const(0x11) -_BNO_REPORT_SHAKE_DETECTOR = const(0x19) +BNO_REPORT_ROTATION_VECTOR = const(0x05) +BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR = const(0x09) +BNO_REPORT_STEP_COUNTER = const(0x11) +BNO_REPORT_SHAKE_DETECTOR = const(0x19) _DEFAULT_REPORT_INTERVAL = const(50000) # in microseconds = 50ms @@ -111,15 +112,15 @@ _BNO_CMD_TIMESTAMP_REBASE: 5, } # length is probably deterministic, like axes * 2 +4 -_ENABLED_SENSOR_REPORTS = { - _BNO_REPORT_ACCELEROMETER: (_Q_POINT_8_SCALAR, 3, 10), - _BNO_REPORT_GYROSCOPE: (_Q_POINT_9_SCALAR, 3, 10), - _BNO_REPORT_MAGNETIC_FIELD: (_Q_POINT_4_SCALAR, 3, 10), - _BNO_REPORT_LINEAR_ACCELERATION: (_Q_POINT_8_SCALAR, 3, 10), - _BNO_REPORT_ROTATION_VECTOR: (_Q_POINT_14_SCALAR, 4, 14,), - _BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR: (_Q_POINT_12_SCALAR, 4, 14), - _BNO_REPORT_STEP_COUNTER: (1, 1, 12), - _BNO_REPORT_SHAKE_DETECTOR: (1, 1, 6), +_AVAIL_SENSOR_REPORTS = { + BNO_REPORT_ACCELEROMETER: (_Q_POINT_8_SCALAR, 3, 10), + BNO_REPORT_GYROSCOPE: (_Q_POINT_9_SCALAR, 3, 10), + BNO_REPORT_MAGNETIC_FIELD: (_Q_POINT_4_SCALAR, 3, 10), + BNO_REPORT_LINEAR_ACCELERATION: (_Q_POINT_8_SCALAR, 3, 10), + BNO_REPORT_ROTATION_VECTOR: (_Q_POINT_14_SCALAR, 4, 14,), + BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR: (_Q_POINT_12_SCALAR, 4, 14), + BNO_REPORT_STEP_COUNTER: (1, 1, 12), + BNO_REPORT_SHAKE_DETECTOR: (1, 1, 6), } DATA_BUFFER_SIZE = const(512) # data buffer size. obviously eats ram @@ -131,17 +132,22 @@ REPORT_STATUS = ["Unreliable", "Accuracy low", "Accuracy medium", "Accuracy high"] +class PacketError(Exception): + """Raised when the packet couldnt be parsed""" + pass + + def _elapsed(start_time): - return monotonic() - start_time + return time.monotonic() - start_time def elapsed_time(func): """Print the runtime of the decorated function""" def wrapper_timer(*args, **kwargs): - start_time = monotonic_ns() # 1 + start_time = time.monotonic_ns() # 1 value = func(*args, **kwargs) - end_time = monotonic_ns() # 2 + end_time = time.monotonic_ns() # 2 run_time = end_time - start_time # 3 print("Finished", func.__name__, "in", (run_time / 1000000.0), "ms") return value @@ -152,7 +158,7 @@ def wrapper_timer(*args, **kwargs): def _parse_sensor_report_data(report_bytes): data_offset = 4 # this may not always be true report_id = report_bytes[0] - scalar, count, _report_length = _ENABLED_SENSOR_REPORTS[report_id] + scalar, count, _report_length = _AVAIL_SENSOR_REPORTS[report_id] results = [] @@ -198,7 +204,7 @@ def parse_sensor_id(buffer): def _report_length(report_id): if report_id < 0xF0: # it's a sensor report - return _ENABLED_SENSOR_REPORTS[report_id][2] + return _AVAIL_SENSOR_REPORTS[report_id][2] return _REPORT_LENGTHS[report_id] @@ -248,7 +254,7 @@ def __str__(self): _BNO_CHANNEL_INPUT_SENSOR_REPORTS, ]: if self.report_id in reports: - outstr += "DBG::\t\t \tReport Type: %s(%d)\n" % ( + outstr += "DBG::\t\t \tReport Type: %s (0x%x)\n" % ( reports[self.report_id], self.report_id, ) @@ -332,8 +338,9 @@ class BNO080: """ - def __init__(self, debug=False): + def __init__(self, reset=None, debug=False): self._debug = debug + self._reset = reset self._dbg("********** __init__ *************") self._data_buffer = bytearray(DATA_BUFFER_SIZE) self._packet_slices = [] @@ -351,56 +358,76 @@ def __init__(self, debug=False): def initialize(self): """Initialize the sensor""" - self.reset() + self.hard_reset() + self.soft_reset() if not self._check_id(): raise RuntimeError("Could not read ID") - for report_type in _ENABLED_SENSOR_REPORTS: - self._enable_feature(report_type) @property def magnetic(self): """A tuple of the current magnetic field measurements on the X, Y, and Z axes""" self._process_available_packets() # decorator? - return self._readings[_BNO_REPORT_MAGNETIC_FIELD] + try: + return self._readings[BNO_REPORT_MAGNETIC_FIELD] + except KeyError: + raise RuntimeError("No magfield report found, is it enabled?") from None @property def quaternion(self): """A quaternion representing the current rotation vector""" self._process_available_packets() - return self._readings[_BNO_REPORT_ROTATION_VECTOR] + try: + return self._readings[BNO_REPORT_ROTATION_VECTOR] + except KeyError: + raise RuntimeError("No quaternion report found, is it enabled?") from None @property def geomagnetic_quaternion(self): """A quaternion representing the current geomagnetic rotation vector""" self._process_available_packets() - return self._readings[_BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR] + try: + return self._readings[BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR] + except KeyError: + raise RuntimeError("No geomag quaternion report found, is it enabled?") from None @property def steps(self): """The number of steps detected since the sensor was initialized""" self._process_available_packets() - return self._readings[_BNO_REPORT_STEP_COUNTER] + try: + return self._readings[BNO_REPORT_STEP_COUNTER] + except KeyError: + raise RuntimeError("No steps report found, is it enabled?") from None @property def linear_acceleration(self): """A tuple representing the current linear acceleration values on the X, Y, and Z axes in meters per second squared""" self._process_available_packets() - return self._readings[_BNO_REPORT_LINEAR_ACCELERATION] + try: + return self._readings[BNO_REPORT_LINEAR_ACCELERATION] + except KeyError: + raise RuntimeError("No lin. accel report found, is it enabled?") from None @property def acceleration(self): """A tuple representing the acceleration measurements on the X, Y, and Z axes in meters per second squared""" self._process_available_packets() - return self._readings[_BNO_REPORT_ACCELEROMETER] + try: + return self._readings[BNO_REPORT_ACCELEROMETER] + except KeyError: + raise RuntimeError("No accel report found, is it enabled?") from None @property def gyro(self): """A tuple representing Gyro's rotation measurements on the X, Y, and Z axes in radians per second""" self._process_available_packets() - return self._readings[_BNO_REPORT_GYROSCOPE] + try: + return self._readings[BNO_REPORT_GYROSCOPE] + except KeyError: + raise RuntimeError("No gyro report found, is it enabled?") from None @property def shake(self): @@ -411,20 +438,27 @@ def shake(self): this property is not guaranteed to reflect the shake state at the moment it is read """ self._process_available_packets() - shake_detected = self._readings[_BNO_REPORT_SHAKE_DETECTOR] - # clear on read - if shake_detected: - self._readings[_BNO_REPORT_SHAKE_DETECTOR] = False + try: + shake_detected = self._readings[BNO_REPORT_SHAKE_DETECTOR] + # clear on read + if shake_detected: + self._readings[BNO_REPORT_SHAKE_DETECTOR] = False + except KeyError: + raise RuntimeError("No shake report found, is it enabled?") from None # # decorator? def _process_available_packets(self): processed_count = 0 while self._data_ready: - new_packet = self._read_packet() + print("reading a packet") + try: + new_packet = self._read_packet() + except PacketError: + continue self._handle_packet(new_packet) processed_count += 1 self._dbg("") - self._dbg("Processesd", processed_count, "packets") + #print("Processed", processed_count, "packets") self._dbg("") # we'll probably need an exit here for fast sensor rates self._dbg("") @@ -436,7 +470,7 @@ def _wait_for_packet_type(self, channel_number, report_id=None, timeout=5.0): else: report_id_str = "" self._dbg("** Waiting for packet on channel", channel_number, report_id_str) - start_time = monotonic() + start_time = time.monotonic() while _elapsed(start_time) < timeout: new_packet = self._wait_for_packet() @@ -452,7 +486,7 @@ def _wait_for_packet_type(self, channel_number, report_id=None, timeout=5.0): raise RuntimeError("Timed out waiting for a packet on channel", channel_number) def _wait_for_packet(self, timeout=_PACKET_READ_TIMEOUT): - start_time = monotonic() + start_time = time.monotonic() while _elapsed(start_time) < timeout: if not self._data_ready: continue @@ -503,14 +537,14 @@ def _process_report(self, report_id, report_bytes): print(outstr) self._dbg("") - if report_id == _BNO_REPORT_STEP_COUNTER: + if report_id == BNO_REPORT_STEP_COUNTER: self._readings[report_id] = _parse_step_couter_report(report_bytes) return - if report_id == _BNO_REPORT_SHAKE_DETECTOR: + if report_id == BNO_REPORT_SHAKE_DETECTOR: shake_detected = _parse_shake_report(report_bytes) # shake not previously detected - auto cleared by 'shake' property - if not self._readings[_BNO_REPORT_SHAKE_DETECTOR]: - self._readings[_BNO_REPORT_SHAKE_DETECTOR] = shake_detected + if not self._readings[BNO_REPORT_SHAKE_DETECTOR]: + self._readings[BNO_REPORT_SHAKE_DETECTOR] = shake_detected return sensor_data = _parse_sensor_report_data(report_bytes) @@ -533,10 +567,11 @@ def _get_feature_enable_report( pack_into(" 5: self._dbg("channel number out of range:", header.channel_number) # data_length, packet_byte_count) diff --git a/examples/bno080_simpletest.py b/examples/bno080_simpletest.py index 61a2108..ed15062 100644 --- a/examples/bno080_simpletest.py +++ b/examples/bno080_simpletest.py @@ -1,23 +1,35 @@ # SPDX-FileCopyrightText: 2020 Bryan Siepert, written for Adafruit Industries # # SPDX-License-Identifier: Unlicense -from time import sleep +import time import board import busio +import adafruit_bno080 from adafruit_bno080.i2c import BNO080_I2C +from digitalio import DigitalInOut +i2c = busio.I2C(board.SCL, board.SDA, frequency=800000) +reset_pin = DigitalInOut(board.D5) +bno = BNO080_I2C(i2c, reset=reset_pin, debug=False) -i2c = busio.I2C(board.SCL, board.SDA) -bno = BNO080_I2C(i2c) +bno.enable_feature(adafruit_bno080.BNO_REPORT_ACCELEROMETER) +bno.enable_feature(adafruit_bno080.BNO_REPORT_GYROSCOPE) +bno.enable_feature(adafruit_bno080.BNO_REPORT_MAGNETIC_FIELD) +bno.enable_feature(adafruit_bno080.BNO_REPORT_LINEAR_ACCELERATION) +bno.enable_feature(adafruit_bno080.BNO_REPORT_ROTATION_VECTOR) +bno.enable_feature(adafruit_bno080.BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR) +bno.enable_feature(adafruit_bno080.BNO_REPORT_STEP_COUNTER) +bno.enable_feature(adafruit_bno080.BNO_REPORT_SHAKE_DETECTOR) while True: - - # print("Acceleration:") + time.sleep(0.1) + + print("Acceleration:") accel_x, accel_y, accel_z = bno.acceleration # pylint:disable=no-member print("X: %0.6f Y: %0.6f Z: %0.6f m/s^2" % (accel_x, accel_y, accel_z)) print("") - # print("Gyro:") + print("Gyro:") gyro_x, gyro_y, gyro_z = bno.gyro # pylint:disable=no-member print("X: %0.6f Y: %0.6f Z: %0.6f rads/s" % (gyro_x, gyro_y, gyro_z)) print("") @@ -38,13 +50,14 @@ % (linear_accel_x, linear_accel_y, linear_accel_z) ) print("") + print("Rotation Vector Quaternion:") quat_i, quat_j, quat_k, quat_real = bno.quaternion # pylint:disable=no-member - print( "I: %0.6f J: %0.6f K: %0.6f Real: %0.6f" % (quat_i, quat_j, quat_k, quat_real) ) print("") + print("Geomagnetic Rotation Vector Quaternion:") ( geo_quat_i, @@ -52,15 +65,14 @@ geo_quat_k, geo_quat_real, ) = bno.geomagnetic_quaternion # pylint:disable=no-member - print( "I: %0.6f J: %0.6f K: %0.6f Real: %0.6f" % (quat_i, quat_j, quat_k, quat_real) ) print("") + print("Steps detected:", bno.steps) print("") + if bno.shake: print("SHAKE DETECTED!") print("") - - sleep(0.5)