Skip to content

Commit 0015261

Browse files
authored
Merge pull request #2 from adafruit/lf
Refactor how we enable reports
2 parents 787a2fd + 1e254f2 commit 0015261

File tree

3 files changed

+163
-97
lines changed

3 files changed

+163
-97
lines changed

adafruit_bno080/__init__.py

Lines changed: 125 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@
2929

3030
from struct import unpack_from, pack_into
3131
from collections import namedtuple
32-
from time import sleep, monotonic, monotonic_ns
32+
import time
3333
from micropython import const
34+
import digitalio
3435

3536
# TODO: Remove on release
3637
from .debug import channels, reports
@@ -66,18 +67,18 @@
6667

6768

6869
# Calibrated Acceleration (m/s2)
69-
_BNO_REPORT_ACCELEROMETER = const(0x01)
70+
BNO_REPORT_ACCELEROMETER = const(0x01)
7071
# Calibrated gyroscope (rad/s).
71-
_BNO_REPORT_GYROSCOPE = const(0x02)
72+
BNO_REPORT_GYROSCOPE = const(0x02)
7273
# Magnetic field calibrated (in µTesla). The fully calibrated magnetic field measurement.
73-
_BNO_REPORT_MAGNETIC_FIELD = const(0x03)
74+
BNO_REPORT_MAGNETIC_FIELD = const(0x03)
7475
# Linear acceleration (m/s2). Acceleration of the device with gravity removed
75-
_BNO_REPORT_LINEAR_ACCELERATION = const(0x04)
76+
BNO_REPORT_LINEAR_ACCELERATION = const(0x04)
7677
# Rotation Vector
77-
_BNO_REPORT_ROTATION_VECTOR = const(0x05)
78-
_BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR = const(0x09)
79-
_BNO_REPORT_STEP_COUNTER = const(0x11)
80-
_BNO_REPORT_SHAKE_DETECTOR = const(0x19)
78+
BNO_REPORT_ROTATION_VECTOR = const(0x05)
79+
BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR = const(0x09)
80+
BNO_REPORT_STEP_COUNTER = const(0x11)
81+
BNO_REPORT_SHAKE_DETECTOR = const(0x19)
8182

8283

8384
_DEFAULT_REPORT_INTERVAL = const(50000) # in microseconds = 50ms
@@ -111,15 +112,15 @@
111112
_BNO_CMD_TIMESTAMP_REBASE: 5,
112113
}
113114
# length is probably deterministic, like axes * 2 +4
114-
_ENABLED_SENSOR_REPORTS = {
115-
_BNO_REPORT_ACCELEROMETER: (_Q_POINT_8_SCALAR, 3, 10),
116-
_BNO_REPORT_GYROSCOPE: (_Q_POINT_9_SCALAR, 3, 10),
117-
_BNO_REPORT_MAGNETIC_FIELD: (_Q_POINT_4_SCALAR, 3, 10),
118-
_BNO_REPORT_LINEAR_ACCELERATION: (_Q_POINT_8_SCALAR, 3, 10),
119-
_BNO_REPORT_ROTATION_VECTOR: (_Q_POINT_14_SCALAR, 4, 14,),
120-
_BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR: (_Q_POINT_12_SCALAR, 4, 14),
121-
_BNO_REPORT_STEP_COUNTER: (1, 1, 12),
122-
_BNO_REPORT_SHAKE_DETECTOR: (1, 1, 6),
115+
_AVAIL_SENSOR_REPORTS = {
116+
BNO_REPORT_ACCELEROMETER: (_Q_POINT_8_SCALAR, 3, 10),
117+
BNO_REPORT_GYROSCOPE: (_Q_POINT_9_SCALAR, 3, 10),
118+
BNO_REPORT_MAGNETIC_FIELD: (_Q_POINT_4_SCALAR, 3, 10),
119+
BNO_REPORT_LINEAR_ACCELERATION: (_Q_POINT_8_SCALAR, 3, 10),
120+
BNO_REPORT_ROTATION_VECTOR: (_Q_POINT_14_SCALAR, 4, 14,),
121+
BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR: (_Q_POINT_12_SCALAR, 4, 14),
122+
BNO_REPORT_STEP_COUNTER: (1, 1, 12),
123+
BNO_REPORT_SHAKE_DETECTOR: (1, 1, 6),
123124
}
124125

125126
DATA_BUFFER_SIZE = const(512) # data buffer size. obviously eats ram
@@ -131,17 +132,22 @@
131132
REPORT_STATUS = ["Unreliable", "Accuracy low", "Accuracy medium", "Accuracy high"]
132133

133134

135+
class PacketError(Exception):
136+
"""Raised when the packet couldnt be parsed"""
137+
pass
138+
139+
134140
def _elapsed(start_time):
135-
return monotonic() - start_time
141+
return time.monotonic() - start_time
136142

137143

138144
def elapsed_time(func):
139145
"""Print the runtime of the decorated function"""
140146

141147
def wrapper_timer(*args, **kwargs):
142-
start_time = monotonic_ns() # 1
148+
start_time = time.monotonic_ns() # 1
143149
value = func(*args, **kwargs)
144-
end_time = monotonic_ns() # 2
150+
end_time = time.monotonic_ns() # 2
145151
run_time = end_time - start_time # 3
146152
print("Finished", func.__name__, "in", (run_time / 1000000.0), "ms")
147153
return value
@@ -152,7 +158,7 @@ def wrapper_timer(*args, **kwargs):
152158
def _parse_sensor_report_data(report_bytes):
153159
data_offset = 4 # this may not always be true
154160
report_id = report_bytes[0]
155-
scalar, count, _report_length = _ENABLED_SENSOR_REPORTS[report_id]
161+
scalar, count, _report_length = _AVAIL_SENSOR_REPORTS[report_id]
156162

157163
results = []
158164

@@ -198,7 +204,7 @@ def parse_sensor_id(buffer):
198204

199205
def _report_length(report_id):
200206
if report_id < 0xF0: # it's a sensor report
201-
return _ENABLED_SENSOR_REPORTS[report_id][2]
207+
return _AVAIL_SENSOR_REPORTS[report_id][2]
202208

203209
return _REPORT_LENGTHS[report_id]
204210

@@ -248,7 +254,7 @@ def __str__(self):
248254
_BNO_CHANNEL_INPUT_SENSOR_REPORTS,
249255
]:
250256
if self.report_id in reports:
251-
outstr += "DBG::\t\t \tReport Type: %s(%d)\n" % (
257+
outstr += "DBG::\t\t \tReport Type: %s (0x%x)\n" % (
252258
reports[self.report_id],
253259
self.report_id,
254260
)
@@ -332,8 +338,9 @@ class BNO080:
332338
333339
"""
334340

335-
def __init__(self, debug=False):
341+
def __init__(self, reset=None, debug=False):
336342
self._debug = debug
343+
self._reset = reset
337344
self._dbg("********** __init__ *************")
338345
self._data_buffer = bytearray(DATA_BUFFER_SIZE)
339346
self._packet_slices = []
@@ -351,56 +358,76 @@ def __init__(self, debug=False):
351358

352359
def initialize(self):
353360
"""Initialize the sensor"""
354-
self.reset()
361+
self.hard_reset()
362+
self.soft_reset()
355363
if not self._check_id():
356364
raise RuntimeError("Could not read ID")
357-
for report_type in _ENABLED_SENSOR_REPORTS:
358-
self._enable_feature(report_type)
359365

360366
@property
361367
def magnetic(self):
362368
"""A tuple of the current magnetic field measurements on the X, Y, and Z axes"""
363369
self._process_available_packets() # decorator?
364-
return self._readings[_BNO_REPORT_MAGNETIC_FIELD]
370+
try:
371+
return self._readings[BNO_REPORT_MAGNETIC_FIELD]
372+
except KeyError:
373+
raise RuntimeError("No magfield report found, is it enabled?") from None
365374

366375
@property
367376
def quaternion(self):
368377
"""A quaternion representing the current rotation vector"""
369378
self._process_available_packets()
370-
return self._readings[_BNO_REPORT_ROTATION_VECTOR]
379+
try:
380+
return self._readings[BNO_REPORT_ROTATION_VECTOR]
381+
except KeyError:
382+
raise RuntimeError("No quaternion report found, is it enabled?") from None
371383

372384
@property
373385
def geomagnetic_quaternion(self):
374386
"""A quaternion representing the current geomagnetic rotation vector"""
375387
self._process_available_packets()
376-
return self._readings[_BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR]
388+
try:
389+
return self._readings[BNO_REPORT_GEOMAGNETIC_ROTATION_VECTOR]
390+
except KeyError:
391+
raise RuntimeError("No geomag quaternion report found, is it enabled?") from None
377392

378393
@property
379394
def steps(self):
380395
"""The number of steps detected since the sensor was initialized"""
381396
self._process_available_packets()
382-
return self._readings[_BNO_REPORT_STEP_COUNTER]
397+
try:
398+
return self._readings[BNO_REPORT_STEP_COUNTER]
399+
except KeyError:
400+
raise RuntimeError("No steps report found, is it enabled?") from None
383401

384402
@property
385403
def linear_acceleration(self):
386404
"""A tuple representing the current linear acceleration values on the X, Y, and Z
387405
axes in meters per second squared"""
388406
self._process_available_packets()
389-
return self._readings[_BNO_REPORT_LINEAR_ACCELERATION]
407+
try:
408+
return self._readings[BNO_REPORT_LINEAR_ACCELERATION]
409+
except KeyError:
410+
raise RuntimeError("No lin. accel report found, is it enabled?") from None
390411

391412
@property
392413
def acceleration(self):
393414
"""A tuple representing the acceleration measurements on the X, Y, and Z
394415
axes in meters per second squared"""
395416
self._process_available_packets()
396-
return self._readings[_BNO_REPORT_ACCELEROMETER]
417+
try:
418+
return self._readings[BNO_REPORT_ACCELEROMETER]
419+
except KeyError:
420+
raise RuntimeError("No accel report found, is it enabled?") from None
397421

398422
@property
399423
def gyro(self):
400424
"""A tuple representing Gyro's rotation measurements on the X, Y, and Z
401425
axes in radians per second"""
402426
self._process_available_packets()
403-
return self._readings[_BNO_REPORT_GYROSCOPE]
427+
try:
428+
return self._readings[BNO_REPORT_GYROSCOPE]
429+
except KeyError:
430+
raise RuntimeError("No gyro report found, is it enabled?") from None
404431

405432
@property
406433
def shake(self):
@@ -411,20 +438,27 @@ def shake(self):
411438
this property is not guaranteed to reflect the shake state at the moment it is read
412439
"""
413440
self._process_available_packets()
414-
shake_detected = self._readings[_BNO_REPORT_SHAKE_DETECTOR]
415-
# clear on read
416-
if shake_detected:
417-
self._readings[_BNO_REPORT_SHAKE_DETECTOR] = False
441+
try:
442+
shake_detected = self._readings[BNO_REPORT_SHAKE_DETECTOR]
443+
# clear on read
444+
if shake_detected:
445+
self._readings[BNO_REPORT_SHAKE_DETECTOR] = False
446+
except KeyError:
447+
raise RuntimeError("No shake report found, is it enabled?") from None
418448

419449
# # decorator?
420450
def _process_available_packets(self):
421451
processed_count = 0
422452
while self._data_ready:
423-
new_packet = self._read_packet()
453+
print("reading a packet")
454+
try:
455+
new_packet = self._read_packet()
456+
except PacketError:
457+
continue
424458
self._handle_packet(new_packet)
425459
processed_count += 1
426460
self._dbg("")
427-
self._dbg("Processesd", processed_count, "packets")
461+
#print("Processed", processed_count, "packets")
428462
self._dbg("")
429463
# we'll probably need an exit here for fast sensor rates
430464
self._dbg("")
@@ -436,7 +470,7 @@ def _wait_for_packet_type(self, channel_number, report_id=None, timeout=5.0):
436470
else:
437471
report_id_str = ""
438472
self._dbg("** Waiting for packet on channel", channel_number, report_id_str)
439-
start_time = monotonic()
473+
start_time = time.monotonic()
440474
while _elapsed(start_time) < timeout:
441475
new_packet = self._wait_for_packet()
442476

@@ -452,7 +486,7 @@ def _wait_for_packet_type(self, channel_number, report_id=None, timeout=5.0):
452486
raise RuntimeError("Timed out waiting for a packet on channel", channel_number)
453487

454488
def _wait_for_packet(self, timeout=_PACKET_READ_TIMEOUT):
455-
start_time = monotonic()
489+
start_time = time.monotonic()
456490
while _elapsed(start_time) < timeout:
457491
if not self._data_ready:
458492
continue
@@ -503,14 +537,14 @@ def _process_report(self, report_id, report_bytes):
503537
print(outstr)
504538
self._dbg("")
505539

506-
if report_id == _BNO_REPORT_STEP_COUNTER:
540+
if report_id == BNO_REPORT_STEP_COUNTER:
507541
self._readings[report_id] = _parse_step_couter_report(report_bytes)
508542
return
509-
if report_id == _BNO_REPORT_SHAKE_DETECTOR:
543+
if report_id == BNO_REPORT_SHAKE_DETECTOR:
510544
shake_detected = _parse_shake_report(report_bytes)
511545
# shake not previously detected - auto cleared by 'shake' property
512-
if not self._readings[_BNO_REPORT_SHAKE_DETECTOR]:
513-
self._readings[_BNO_REPORT_SHAKE_DETECTOR] = shake_detected
546+
if not self._readings[BNO_REPORT_SHAKE_DETECTOR]:
547+
self._readings[BNO_REPORT_SHAKE_DETECTOR] = shake_detected
514548
return
515549

516550
sensor_data = _parse_sensor_report_data(report_bytes)
@@ -533,10 +567,11 @@ def _get_feature_enable_report(
533567
pack_into("<I", set_feature_report, 5, report_interval)
534568
return set_feature_report
535569

536-
def _enable_feature(self, feature_id):
570+
def enable_feature(self, feature_id):
537571
self._dbg("\n********** Enabling feature id:", feature_id, "**********")
538572

539573
set_feature_report = self._get_feature_enable_report(feature_id)
574+
print("Enabling", feature_id)
540575
self._send_packet(_BNO_CHANNEL_CONTROL, set_feature_report)
541576
while True:
542577
packet = self._wait_for_packet_type(
@@ -545,15 +580,16 @@ def _enable_feature(self, feature_id):
545580

546581
if packet.data[1] == feature_id:
547582
if (
548-
feature_id == _BNO_REPORT_ROTATION_VECTOR
583+
feature_id == BNO_REPORT_ROTATION_VECTOR
549584
): # check for other vector types as well
550585
self._readings[feature_id] = (0.0, 0.0, 0.0, 0.0)
551586
else:
552587
self._readings[feature_id] = (0.0, 0.0, 0.0)
553-
self._dbg("Enabled")
554-
return True
588+
print("Enabled", feature_id)
589+
break
590+
else:
591+
raise RuntimeError("Was not able to enable feature", feature_id)
555592

556-
return False
557593

558594
def _check_id(self):
559595
self._dbg("\n********** READ ID **********")
@@ -605,22 +641,50 @@ def _get_data(self, index, fmt_string):
605641
data_index = index + 4
606642
return unpack_from(fmt_string, self._data_buffer, offset=data_index)[0]
607643

608-
def _read_header(self):
609-
"""Reads the first 4 bytes available as a header"""
610-
with self.bus_device_obj as bus_dev: # pylint:disable=no-member
611-
bus_dev.readinto(self._data_buffer, end=4)
612-
packet_header = Packet.header_from_buffer(self._data_buffer)
613-
self._dbg(packet_header)
614-
return packet_header
615644

616645
# pylint:disable=no-self-use
617646
@property
618647
def _data_ready(self):
619648
raise RuntimeError("Not implemented")
620649

621-
def reset(self):
650+
def hard_reset(self):
651+
"""Hardware reset the sensor to an initial unconfigured state"""
652+
if not self._reset:
653+
return
654+
#print("Hard resetting...")
655+
self._reset.direction = digitalio.Direction.OUTPUT
656+
self._reset.value = True
657+
time.sleep(0.01)
658+
self._reset.value = False
659+
time.sleep(0.01)
660+
self._reset.value = True
661+
time.sleep(0.5)
662+
663+
def soft_reset(self):
622664
"""Reset the sensor to an initial unconfigured state"""
623-
raise RuntimeError("Not implemented")
665+
print("Soft resetting...", end="")
666+
data = bytearray(1)
667+
data[0] = 1
668+
seq = self._send_packet(BNO_CHANNEL_EXE, data)
669+
time.sleep(0.5)
670+
671+
for i in range(3):
672+
while True: # retry reading packets until ready!
673+
try:
674+
packet = self._read_packet()
675+
break
676+
except PacketError:
677+
time.sleep(0.1)
678+
679+
#print(packet)
680+
if i == 0 and packet.channel_number != _BNO_CHANNEL_SHTP_COMMAND:
681+
raise RuntimeError("Expected an SHTP announcement")
682+
if i == 1 and packet.channel_number != BNO_CHANNEL_EXE:
683+
raise RuntimeError("Expected a reset reply")
684+
if i == 2 and packet.channel_number != _BNO_CHANNEL_CONTROL:
685+
raise RuntimeError("Expected a control announcement")
686+
print("OK!");
687+
# all is good!
624688

625689
def _send_packet(self, channel, data):
626690
raise RuntimeError("Not implemented")

0 commit comments

Comments
 (0)