29
29
30
30
from struct import unpack_from , pack_into
31
31
from collections import namedtuple
32
- from time import sleep , monotonic , monotonic_ns
32
+ import time
33
33
from micropython import const
34
+ import digitalio
34
35
35
36
# TODO: Remove on release
36
37
from .debug import channels , reports
66
67
67
68
68
69
# Calibrated Acceleration (m/s2)
69
- _BNO_REPORT_ACCELEROMETER = const (0x01 )
70
+ BNO_REPORT_ACCELEROMETER = const (0x01 )
70
71
# Calibrated gyroscope (rad/s).
71
- _BNO_REPORT_GYROSCOPE = const (0x02 )
72
+ BNO_REPORT_GYROSCOPE = const (0x02 )
72
73
# 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 )
74
75
# 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 )
76
77
# 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 )
81
82
82
83
83
84
_DEFAULT_REPORT_INTERVAL = const (50000 ) # in microseconds = 50ms
111
112
_BNO_CMD_TIMESTAMP_REBASE : 5 ,
112
113
}
113
114
# 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 ),
123
124
}
124
125
125
126
DATA_BUFFER_SIZE = const (512 ) # data buffer size. obviously eats ram
131
132
REPORT_STATUS = ["Unreliable" , "Accuracy low" , "Accuracy medium" , "Accuracy high" ]
132
133
133
134
135
+ class PacketError (Exception ):
136
+ """Raised when the packet couldnt be parsed"""
137
+ pass
138
+
139
+
134
140
def _elapsed (start_time ):
135
- return monotonic () - start_time
141
+ return time . monotonic () - start_time
136
142
137
143
138
144
def elapsed_time (func ):
139
145
"""Print the runtime of the decorated function"""
140
146
141
147
def wrapper_timer (* args , ** kwargs ):
142
- start_time = monotonic_ns () # 1
148
+ start_time = time . monotonic_ns () # 1
143
149
value = func (* args , ** kwargs )
144
- end_time = monotonic_ns () # 2
150
+ end_time = time . monotonic_ns () # 2
145
151
run_time = end_time - start_time # 3
146
152
print ("Finished" , func .__name__ , "in" , (run_time / 1000000.0 ), "ms" )
147
153
return value
@@ -152,7 +158,7 @@ def wrapper_timer(*args, **kwargs):
152
158
def _parse_sensor_report_data (report_bytes ):
153
159
data_offset = 4 # this may not always be true
154
160
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 ]
156
162
157
163
results = []
158
164
@@ -198,7 +204,7 @@ def parse_sensor_id(buffer):
198
204
199
205
def _report_length (report_id ):
200
206
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 ]
202
208
203
209
return _REPORT_LENGTHS [report_id ]
204
210
@@ -248,7 +254,7 @@ def __str__(self):
248
254
_BNO_CHANNEL_INPUT_SENSOR_REPORTS ,
249
255
]:
250
256
if self .report_id in reports :
251
- outstr += "DBG::\t \t \t Report Type: %s(%d )\n " % (
257
+ outstr += "DBG::\t \t \t Report Type: %s (0x%x )\n " % (
252
258
reports [self .report_id ],
253
259
self .report_id ,
254
260
)
@@ -332,8 +338,9 @@ class BNO080:
332
338
333
339
"""
334
340
335
- def __init__ (self , debug = False ):
341
+ def __init__ (self , reset = None , debug = False ):
336
342
self ._debug = debug
343
+ self ._reset = reset
337
344
self ._dbg ("********** __init__ *************" )
338
345
self ._data_buffer = bytearray (DATA_BUFFER_SIZE )
339
346
self ._packet_slices = []
@@ -351,56 +358,76 @@ def __init__(self, debug=False):
351
358
352
359
def initialize (self ):
353
360
"""Initialize the sensor"""
354
- self .reset ()
361
+ self .hard_reset ()
362
+ self .soft_reset ()
355
363
if not self ._check_id ():
356
364
raise RuntimeError ("Could not read ID" )
357
- for report_type in _ENABLED_SENSOR_REPORTS :
358
- self ._enable_feature (report_type )
359
365
360
366
@property
361
367
def magnetic (self ):
362
368
"""A tuple of the current magnetic field measurements on the X, Y, and Z axes"""
363
369
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
365
374
366
375
@property
367
376
def quaternion (self ):
368
377
"""A quaternion representing the current rotation vector"""
369
378
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
371
383
372
384
@property
373
385
def geomagnetic_quaternion (self ):
374
386
"""A quaternion representing the current geomagnetic rotation vector"""
375
387
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
377
392
378
393
@property
379
394
def steps (self ):
380
395
"""The number of steps detected since the sensor was initialized"""
381
396
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
383
401
384
402
@property
385
403
def linear_acceleration (self ):
386
404
"""A tuple representing the current linear acceleration values on the X, Y, and Z
387
405
axes in meters per second squared"""
388
406
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
390
411
391
412
@property
392
413
def acceleration (self ):
393
414
"""A tuple representing the acceleration measurements on the X, Y, and Z
394
415
axes in meters per second squared"""
395
416
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
397
421
398
422
@property
399
423
def gyro (self ):
400
424
"""A tuple representing Gyro's rotation measurements on the X, Y, and Z
401
425
axes in radians per second"""
402
426
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
404
431
405
432
@property
406
433
def shake (self ):
@@ -411,20 +438,27 @@ def shake(self):
411
438
this property is not guaranteed to reflect the shake state at the moment it is read
412
439
"""
413
440
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
418
448
419
449
# # decorator?
420
450
def _process_available_packets (self ):
421
451
processed_count = 0
422
452
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
424
458
self ._handle_packet (new_packet )
425
459
processed_count += 1
426
460
self ._dbg ("" )
427
- self . _dbg ( "Processesd " , processed_count , "packets" )
461
+ #print("Processed ", processed_count, "packets")
428
462
self ._dbg ("" )
429
463
# we'll probably need an exit here for fast sensor rates
430
464
self ._dbg ("" )
@@ -436,7 +470,7 @@ def _wait_for_packet_type(self, channel_number, report_id=None, timeout=5.0):
436
470
else :
437
471
report_id_str = ""
438
472
self ._dbg ("** Waiting for packet on channel" , channel_number , report_id_str )
439
- start_time = monotonic ()
473
+ start_time = time . monotonic ()
440
474
while _elapsed (start_time ) < timeout :
441
475
new_packet = self ._wait_for_packet ()
442
476
@@ -452,7 +486,7 @@ def _wait_for_packet_type(self, channel_number, report_id=None, timeout=5.0):
452
486
raise RuntimeError ("Timed out waiting for a packet on channel" , channel_number )
453
487
454
488
def _wait_for_packet (self , timeout = _PACKET_READ_TIMEOUT ):
455
- start_time = monotonic ()
489
+ start_time = time . monotonic ()
456
490
while _elapsed (start_time ) < timeout :
457
491
if not self ._data_ready :
458
492
continue
@@ -503,14 +537,14 @@ def _process_report(self, report_id, report_bytes):
503
537
print (outstr )
504
538
self ._dbg ("" )
505
539
506
- if report_id == _BNO_REPORT_STEP_COUNTER :
540
+ if report_id == BNO_REPORT_STEP_COUNTER :
507
541
self ._readings [report_id ] = _parse_step_couter_report (report_bytes )
508
542
return
509
- if report_id == _BNO_REPORT_SHAKE_DETECTOR :
543
+ if report_id == BNO_REPORT_SHAKE_DETECTOR :
510
544
shake_detected = _parse_shake_report (report_bytes )
511
545
# 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
514
548
return
515
549
516
550
sensor_data = _parse_sensor_report_data (report_bytes )
@@ -533,10 +567,11 @@ def _get_feature_enable_report(
533
567
pack_into ("<I" , set_feature_report , 5 , report_interval )
534
568
return set_feature_report
535
569
536
- def _enable_feature (self , feature_id ):
570
+ def enable_feature (self , feature_id ):
537
571
self ._dbg ("\n ********** Enabling feature id:" , feature_id , "**********" )
538
572
539
573
set_feature_report = self ._get_feature_enable_report (feature_id )
574
+ print ("Enabling" , feature_id )
540
575
self ._send_packet (_BNO_CHANNEL_CONTROL , set_feature_report )
541
576
while True :
542
577
packet = self ._wait_for_packet_type (
@@ -545,15 +580,16 @@ def _enable_feature(self, feature_id):
545
580
546
581
if packet .data [1 ] == feature_id :
547
582
if (
548
- feature_id == _BNO_REPORT_ROTATION_VECTOR
583
+ feature_id == BNO_REPORT_ROTATION_VECTOR
549
584
): # check for other vector types as well
550
585
self ._readings [feature_id ] = (0.0 , 0.0 , 0.0 , 0.0 )
551
586
else :
552
587
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 )
555
592
556
- return False
557
593
558
594
def _check_id (self ):
559
595
self ._dbg ("\n ********** READ ID **********" )
@@ -605,22 +641,50 @@ def _get_data(self, index, fmt_string):
605
641
data_index = index + 4
606
642
return unpack_from (fmt_string , self ._data_buffer , offset = data_index )[0 ]
607
643
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
615
644
616
645
# pylint:disable=no-self-use
617
646
@property
618
647
def _data_ready (self ):
619
648
raise RuntimeError ("Not implemented" )
620
649
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 ):
622
664
"""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!
624
688
625
689
def _send_packet (self , channel , data ):
626
690
raise RuntimeError ("Not implemented" )
0 commit comments