From bb7df48a81349aac1a09dbb1b8167cda4b39685f Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 16 Sep 2026 17:45:19 +0200 Subject: [PATCH] drivers/imu/bmi270: Improved and extended BMI270 drivers. Replaces existing `bmi270` driver with a version using the a much smaller device configuration loaded using improved timings. This results in a drop in replacement driver that is 25x faster to load and 5x smaller in size. The driver has been extended to allow sensor scaling changes and data rates to be adjusted after init, powersave modes to be set, and exposes the internal temperature sensor. An additional `bmi270_legacy` driver is also included; this uses the full 'legacy' device config from Bosch and brings access to many Tablet and Phone related features: orientation, drop detection, step counting, tap detection and more. Signed-off-by: Owen --- micropython/drivers/imu/bmi270/README.md | 171 +++ micropython/drivers/imu/bmi270/bmi270.py | 725 +++-------- .../drivers/imu/bmi270/examples/demo_imu.py | 133 ++ micropython/drivers/imu/bmi270/manifest.py | 2 +- .../drivers/imu/bmi270_legacy/README.md | 521 ++++++++ .../imu/bmi270_legacy/bmi270_legacy.py | 1132 +++++++++++++++++ .../imu/bmi270_legacy/examples/demo_legacy.py | 213 ++++ .../drivers/imu/bmi270_legacy/manifest.py | 2 + 8 files changed, 2377 insertions(+), 522 deletions(-) create mode 100644 micropython/drivers/imu/bmi270/README.md create mode 100644 micropython/drivers/imu/bmi270/examples/demo_imu.py create mode 100644 micropython/drivers/imu/bmi270_legacy/README.md create mode 100644 micropython/drivers/imu/bmi270_legacy/bmi270_legacy.py create mode 100644 micropython/drivers/imu/bmi270_legacy/examples/demo_legacy.py create mode 100644 micropython/drivers/imu/bmi270_legacy/manifest.py diff --git a/micropython/drivers/imu/bmi270/README.md b/micropython/drivers/imu/bmi270/README.md new file mode 100644 index 000000000..78fae1ce8 --- /dev/null +++ b/micropython/drivers/imu/bmi270/README.md @@ -0,0 +1,171 @@ +------------ + License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause + + Homepage: https://codeberg.org/easytarget/bmi270-micropython + Datasheet: ./reference/bmi270-base-datasheet.pdf + +--------------------------- + +# Micropython driver for the BMI270 IMU + +## [`bmi270.py`](bmi270.py) +Homepage: [Here](https://codeberg.org/easytarget/bmi270-micropython) + +Original BMI270 driver on the micropython library had limited functionality and some issues with size and speed during init. + +This driver retains the original features: +* Setting Sensor Ranges and Output Data Rates at init +* Reading Accelerometer and Gyroscope reeadings + +Enhancements from original driver: +* Modifying Sensor Ranges and Output Data Rates (ODR) during use. +* Entering 'adavnced powersave mode' and enabling / disabling specific sensors and filter power modes. +* Device internal `temperature()` method has been added. + +Some minor code cleanup has been done; more consistent use of constants and names applied, etc. + +## Install + +Install by copying `bmi270.py` to the root (or import path) of your device in your IDE. +* Or install with **MIP** + * `mip install https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/imu/bmi270/bmi270.py` + * run this on the device itself (when connected via WiFi/Network) +* You can also use mip via **mpremote**: + * `mpremote mip install https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/imu/bmi270/bmi270.py` + * run this on the commandline of your host PC/system + +## Demos and Example +* [`demo_imu.py`](demo_imu.py) : demonstrates the IMU (Accelerometer and Gyroscope) detector + * Press 'b' for a visual/bars mode + +### test.py: +```python +import time +from bmi270 import BMI270 +from machine import Pin, I2C + +# Init in I2C mode, adjust pins as needed +imu = BMI270(I2C(1, scl=Pin(32), sda=Pin(31)) + +while (True): + print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) + print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) + print('Temperature: {:>4.1f}'.format(imu.temperature())) + print("") + time.sleep_ms(100) +``` + +## Reference +See the [BMI270](https://www.bosch-sensortec.com/products/motion-sensors/imus/bmi270/) documentation at the Bosch site. + +### Datasheets: +* Main datasheet with base feature config: [bmi270-base-datasheet.pdf](./reference/bmi270-base-datasheet.pdf) + * Detailed documentation for interrupt pin and power mode control is in this sheet +* Application note for FIFO mode (no features): [bmi270-fifo-appication-note.pdf](./reference/bmi270-fifo-appication-note.pdf) +* Application note for legacy features config [bmi270-legacy-appication-note.pdf](./reference/bmi270-legacy-appication-note.pdf) + +### Official reference API (C): +https://github.com/boschsensortec/BMI270_SensorAPI + +# Usage: + +## Classes; Objects and Attributes + +```python +class BMI270() +``` + +#### init() + +```python +def __init__(i2c, + address = 0x68, + gyro_odr = 100, + gyro_scale = 2000, + accel_odr = 100, + accel_scale = 4, + force_reset = False, + bmm_magnet = None) +``` + +Initializes Device, Gyro and Accelerometer. +* `i2c`: `machine.I2C()` bus object +* `address`: I2C address, default 0x68 +* `gyro_odr`: Hz (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, **100**, 200, 400, 800, 1600) +* `gyro_scale`: dps (125, 250, 500, 1000, **2000**) +* `accel_odr`: Hz (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, **100**, 200, 400, 800, 1600) +* `accel_scale`: g (+/-2, **+/-4**, +/-8, +/-16) +* `force_reset`: Forces a reset and reinitialisation of the device +* `bmm_magnet`: allows passing of a magnetometer device, provided for compatibility + +#### sensor\_settings() + +```python +def sensor_settings(gyro_odr=None, + gyro_scale=None, + accel_odr=None, + accel_scale=None) +``` + +Sets or modifies sensor Output Data Rates (ODR) and Scale +* Values are as specified during `init()` +* `None` indicates the setting should be left as-is + +#### power() + +```python +def power(powersave=None, + accel=None, + gyro=None, + acc_filter=None, + gyr_filter=None, + gyr_noise=None) +``` + +Set power modes; +* See `base` datasheet section 4.5 'Power Modes' (page 26) +* `None` indicates the setting should be left as-is + +Disabling the gyroscope produces greatest savings + +* `powersave` = device 'advanced power save' mode, default `False` +* `accel` = enable/disable accellerometer, default `True` +* `gyro` = enable/disable gyroscope, default `True` +* `acc_filter` = powresave mode for accel filter, default `True` +* `gyr_filter` = powresave mode for gyro filter, default `True` +* `gyr_noise` = powersave mode for gyro noise filter, default `False` + +Note that gesture detection features in the extended configs do not use the gyroscope + +#### gyro() + +```python +def gyro() +``` + +Returns gyroscope vector in degrees/sec. + +#### accel() + +```python +def accel() +``` + +Returns acceleration vector in gravity units (9.81m/s^2). + +#### magnet() + +```python +def magnet() +``` + +Compatibility method, generally unused: Returns magnetometer vector if a magnetomter has been attached and configured at init. + +#### temperature() + +```python +def temperature() +``` + +Returns temperature value in degrees C. This is the chip temperature, not environmental. + diff --git a/micropython/drivers/imu/bmi270/bmi270.py b/micropython/drivers/imu/bmi270/bmi270.py index 1e4a940c3..b5afee271 100644 --- a/micropython/drivers/imu/bmi270/bmi270.py +++ b/micropython/drivers/imu/bmi270/bmi270.py @@ -21,616 +21,299 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Modified to use 'maximum fifo' configuration and optimised for speed +by Owen Carter, Feb 2026 + +Homepage: https://codeberg.org/easytarget/bmi270-micropython/ + Basic example usage:: import time from bmi270 import BMI270 - from machine import Pin, SPI, I2C - - # Init in I2C mode. - imu = BMI270(I2C(1, scl=Pin(15), sda=Pin(14))) + from machine import Pin, I2C - # Or init in SPI mode. - # TODO: Not supported yet. - # imu = BMI270(SPI(5), cs=Pin(10)) + # Init in I2C mode, adjust pins as needed + i2c = I2C(1, scl=Pin(32), sda=Pin(31)) + imu = BMI270(i2c) while (True): print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) - print('Magnetometer: x:{:>8.3f} y:{:>8.3f} z:{:>8.3f}'.format(*imu.magnet())) + print('Temperature: {:>4.1f}'.format(imu.temperature())) print("") time.sleep_ms(100) """ import array -import time +from time import sleep_ms, sleep_us from micropython import const +# I2C address _DEFAULT_ADDR = const(0x68) + +# Core device registers (8 bit) _CHIP_ID = const(0x00) -_STATUS = const(0x21) -_INIT_ADDR_0 = const(0x5B) -_INIT_ADDR_1 = const(0x5C) _DATA_8 = const(0x0C) _DATA_14 = const(0x12) +_EVENT = const(0x1B) +_INTERNAL_STATUS = const(0x21) +_TEMP_REG = const(0x22) +_ACC_CONF = const(0x40) +_ACC_RANGE = const(0x41) +_GYR_CONF = const(0x42) +_GYR_RANGE = const(0x43) +_INIT_CTRL = const(0x59) +_INIT_ADDR_0 = const(0x5B) +_INIT_ADDR_1 = const(0x5C) +_INIT_DATA = const(0x5E) +_PWR_CONF = const(0x7C) +_PWR_CTRL = const(0x7D) _CMD = const(0x7E) + +# Config for the maximum fifo profile (328 bytes) _CONFIG_DATA = const( - b"\xc8\x2e\x00\x2e\x80\x2e\x3d\xb1\xc8\x2e\x00\x2e\x80\x2e\x91\x03\x80\x2e\xbc" - b"\xb0\x80\x2e\xa3\x03\xc8\x2e\x00\x2e\x80\x2e\x00\xb0\x50\x30\x21\x2e\x59\xf5" - b"\x10\x30\x21\x2e\x6a\xf5\x80\x2e\x3b\x03\x00\x00\x00\x00\x08\x19\x01\x00\x22" - b"\x00\x75\x00\x00\x10\x00\x10\xd1\x00\xb3\x43\x80\x2e\x00\xc1\x80\x2e\x00\xc1" - b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" - b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" - b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" - b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" - b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" - b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" - b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\xe0\x5f\x00\x00\x00\x00\x01\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x92\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x08\x19\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\x05" - b"\xe0\xaa\x38\x05\xe0\x90\x30\xfa\x00\x96\x00\x4b\x09\x11\x00\x11\x00\x02\x00" - b"\x2d\x01\xd4\x7b\x3b\x01\xdb\x7a\x04\x00\x3f\x7b\xcd\x6c\xc3\x04\x85\x09\xc3" - b"\x04\xec\xe6\x0c\x46\x01\x00\x27\x00\x19\x00\x96\x00\xa0\x00\x01\x00\x0c\x00" - b"\xf0\x3c\x00\x01\x01\x00\x03\x00\x01\x00\x0e\x00\x00\x00\x32\x00\x05\x00\xee" - b"\x06\x04\x00\xc8\x00\x00\x00\x04\x00\xa8\x05\xee\x06\x00\x04\xbc\x02\xb3\x00" - b"\x85\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\xb4\x00\x01\x00\xb9\x00\x01\x00\x98\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x01\x00\x80\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x2e\x00\xc1\xfd\x2d\xde" - b"\x00\xeb\x00\xda\x00\x00\x0c\xff\x0f\x00\x04\xc0\x00\x5b\xf5\xc9\x01\x1e\xf2" - b"\x80\x00\x3f\xff\x19\xf4\x58\xf5\x66\xf5\x64\xf5\xc0\xf1\xf0\x00\xe0\x00\xcd" - b"\x01\xd3\x01\xdb\x01\xff\x7f\xff\x01\xe4\x00\x74\xf7\xf3\x00\xfa\x00\xff\x3f" - b"\xca\x03\x6c\x38\x56\xfe\x44\xfd\xbc\x02\xf9\x06\x00\xfc\x12\x02\xae\x01\x58" - b"\xfa\x9a\xfd\x77\x05\xbb\x02\x96\x01\x95\x01\x7f\x01\x82\x01\x89\x01\x87\x01" - b"\x88\x01\x8a\x01\x8c\x01\x8f\x01\x8d\x01\x92\x01\x91\x01\xdd\x00\x9f\x01\x7e" - b"\x01\xdb\x00\xb6\x01\x70\x69\x26\xd3\x9c\x07\x1f\x05\x9d\x00\x00\x08\xbc\x05" - b"\x37\xfa\xa2\x01\xaa\x01\xa1\x01\xa8\x01\xa0\x01\xa8\x05\xb4\x01\xb4\x01\xce" - b"\x00\xd0\x00\xfc\x00\xc5\x01\xff\xfb\xb1\x00\x00\x38\x00\x30\xfd\xf5\xfc\xf5" - b"\xcd\x01\xa0\x00\x5f\xff\x00\x40\xff\x00\x00\x80\x6d\x0f\xeb\x00\x7f\xff\xc2" - b"\xf5\x68\xf7\xb3\xf1\x67\x0f\x5b\x0f\x61\x0f\x80\x0f\x58\xf7\x5b\xf7\x83\x0f" - b"\x86\x00\x72\x0f\x85\x0f\xc6\xf1\x7f\x0f\x6c\xf7\x00\xe0\x00\xff\xd1\xf5\x87" - b"\x0f\x8a\x0f\xff\x03\xf0\x3f\x8b\x00\x8e\x00\x90\x00\xb9\x00\x2d\xf5\xca\xf5" - b"\xcb\x01\x20\xf2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x30\x50\x98\x2e" - b"\xd7\x0e\x50\x32\x98\x2e\xfa\x03\x00\x30\xf0\x7f\x00\x2e\x00\x2e\xd0\x2e\x00" - b"\x2e\x01\x80\x08\xa2\xfb\x2f\x98\x2e\xba\x03\x21\x2e\x19\x00\x01\x2e\xee\x00" - b"\x00\xb2\x07\x2f\x01\x2e\x19\x00\x00\xb2\x03\x2f\x01\x50\x03\x52\x98\x2e\x07" - b"\xcc\x01\x2e\xdd\x00\x00\xb2\x27\x2f\x05\x2e\x8a\x00\x05\x52\x98\x2e\xc7\xc1" - b"\x03\x2e\xe9\x00\x40\xb2\xf0\x7f\x08\x2f\x01\x2e\x19\x00\x00\xb2\x04\x2f\x00" - b"\x30\x21\x2e\xe9\x00\x98\x2e\xb4\xb1\x01\x2e\x18\x00\x00\xb2\x10\x2f\x05\x50" - b"\x98\x2e\x4d\xc3\x05\x50\x98\x2e\x5a\xc7\x98\x2e\xf9\xb4\x98\x2e\x54\xb2\x98" - b"\x2e\x67\xb6\x98\x2e\x17\xb2\x10\x30\x21\x2e\x77\x00\x01\x2e\xef\x00\x00\xb2" - b"\x04\x2f\x98\x2e\x7a\xb7\x00\x30\x21\x2e\xef\x00\x01\x2e\xd4\x00\x04\xae\x0b" - b"\x2f\x01\x2e\xdd\x00\x00\xb2\x07\x2f\x05\x52\x98\x2e\x8e\x0e\x00\xb2\x02\x2f" - b"\x10\x30\x21\x2e\x7d\x00\x01\x2e\x7d\x00\x00\x90\x90\x2e\xf1\x02\x01\x2e\xd7" - b"\x00\x00\xb2\x04\x2f\x98\x2e\x2f\x0e\x00\x30\x21\x2e\x7b\x00\x01\x2e\x7b\x00" - b"\x00\xb2\x12\x2f\x01\x2e\xd4\x00\x00\x90\x02\x2f\x98\x2e\x1f\x0e\x09\x2d\x98" - b"\x2e\x81\x0d\x01\x2e\xd4\x00\x04\x90\x02\x2f\x50\x32\x98\x2e\xfa\x03\x00\x30" - b"\x21\x2e\x7b\x00\x01\x2e\x7c\x00\x00\xb2\x90\x2e\x09\x03\x01\x2e\x7c\x00\x01" - b"\x31\x01\x08\x00\xb2\x04\x2f\x98\x2e\x47\xcb\x10\x30\x21\x2e\x77\x00\x81\x30" - b"\x01\x2e\x7c\x00\x01\x08\x00\xb2\x61\x2f\x03\x2e\x89\x00\x01\x2e\xd4\x00\x98" - b"\xbc\x98\xb8\x05\xb2\x0f\x58\x23\x2f\x07\x90\x09\x54\x00\x30\x37\x2f\x15\x41" - b"\x04\x41\xdc\xbe\x44\xbe\xdc\xba\x2c\x01\x61\x00\x0f\x56\x4a\x0f\x0c\x2f\xd1" - b"\x42\x94\xb8\xc1\x42\x11\x30\x05\x2e\x6a\xf7\x2c\xbd\x2f\xb9\x80\xb2\x08\x22" - b"\x98\x2e\xc3\xb7\x21\x2d\x61\x30\x23\x2e\xd4\x00\x98\x2e\xc3\xb7\x00\x30\x21" - b"\x2e\x5a\xf5\x18\x2d\xe1\x7f\x50\x30\x98\x2e\xfa\x03\x0f\x52\x07\x50\x50\x42" - b"\x70\x30\x0d\x54\x42\x42\x7e\x82\xe2\x6f\x80\xb2\x42\x42\x05\x2f\x21\x2e\xd4" - b"\x00\x10\x30\x98\x2e\xc3\xb7\x03\x2d\x60\x30\x21\x2e\xd4\x00\x01\x2e\xd4\x00" - b"\x06\x90\x18\x2f\x01\x2e\x76\x00\x0b\x54\x07\x52\xe0\x7f\x98\x2e\x7a\xc1\xe1" - b"\x6f\x08\x1a\x40\x30\x08\x2f\x21\x2e\xd4\x00\x20\x30\x98\x2e\xaf\xb7\x50\x32" - b"\x98\x2e\xfa\x03\x05\x2d\x98\x2e\x38\x0e\x00\x30\x21\x2e\xd4\x00\x00\x30\x21" - b"\x2e\x7c\x00\x18\x2d\x01\x2e\xd4\x00\x03\xaa\x01\x2f\x98\x2e\x45\x0e\x01\x2e" - b"\xd4\x00\x3f\x80\x03\xa2\x01\x2f\x00\x2e\x02\x2d\x98\x2e\x5b\x0e\x30\x30\x98" - b"\x2e\xce\xb7\x00\x30\x21\x2e\x7d\x00\x50\x32\x98\x2e\xfa\x03\x01\x2e\x77\x00" - b"\x00\xb2\x24\x2f\x98\x2e\xf5\xcb\x03\x2e\xd5\x00\x11\x54\x01\x0a\xbc\x84\x83" - b"\x86\x21\x2e\xc9\x01\xe0\x40\x13\x52\xc4\x40\x82\x40\xa8\xb9\x52\x42\x43\xbe" - b"\x53\x42\x04\x0a\x50\x42\xe1\x7f\xf0\x31\x41\x40\xf2\x6f\x25\xbd\x08\x08\x02" - b"\x0a\xd0\x7f\x98\x2e\xa8\xcf\x06\xbc\xd1\x6f\xe2\x6f\x08\x0a\x80\x42\x98\x2e" - b"\x58\xb7\x00\x30\x21\x2e\xee\x00\x21\x2e\x77\x00\x21\x2e\xdd\x00\x80\x2e\xf4" - b"\x01\x1a\x24\x22\x00\x80\x2e\xec\x01\x10\x50\xfb\x7f\x98\x2e\xf3\x03\x57\x50" - b"\xfb\x6f\x01\x30\x71\x54\x11\x42\x42\x0e\xfc\x2f\xc0\x2e\x01\x42\xf0\x5f\x80" - b"\x2e\x00\xc1\xfd\x2d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x01" - b"\x34\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x20\x50\xe7\x7f\xf6\x7f\x06\x32\x0f\x2e\x61\xf5\xfe\x09\xc0\xb3\x04" - b"\x2f\x17\x30\x2f\x2e\xef\x00\x2d\x2e\x61\xf5\xf6\x6f\xe7\x6f\xe0\x5f\xc8\x2e" - b"\x20\x50\xe7\x7f\xf6\x7f\x46\x30\x0f\x2e\xa4\xf1\xbe\x09\x80\xb3\x06\x2f\x0d" - b"\x2e\xd4\x00\x84\xaf\x02\x2f\x16\x30\x2d\x2e\x7b\x00\x86\x30\x2d\x2e\x60\xf5" - b"\xf6\x6f\xe7\x6f\xe0\x5f\xc8\x2e\x01\x2e\x77\xf7\x09\xbc\x0f\xb8\x00\xb2\x10" - b"\x50\xfb\x7f\x10\x30\x0b\x2f\x03\x2e\x8a\x00\x96\xbc\x9f\xb8\x40\xb2\x05\x2f" - b"\x03\x2e\x68\xf7\x9e\xbc\x9f\xb8\x40\xb2\x07\x2f\x03\x2e\x7e\x00\x41\x90\x01" - b"\x2f\x98\x2e\xdc\x03\x03\x2c\x00\x30\x21\x2e\x7e\x00\xfb\x6f\xf0\x5f\xb8\x2e" - b"\x20\x50\xe0\x7f\xfb\x7f\x00\x2e\x27\x50\x98\x2e\x3b\xc8\x29\x50\x98\x2e\xa7" - b"\xc8\x01\x50\x98\x2e\x55\xcc\xe1\x6f\x2b\x50\x98\x2e\xe0\xc9\xfb\x6f\x00\x30" - b"\xe0\x5f\x21\x2e\x7e\x00\xb8\x2e\x73\x50\x01\x30\x57\x54\x11\x42\x42\x0e\xfc" - b"\x2f\xb8\x2e\x21\x2e\x59\xf5\x10\x30\xc0\x2e\x21\x2e\x4a\xf1\x90\x50\xf7\x7f" - b"\xe6\x7f\xd5\x7f\xc4\x7f\xb3\x7f\xa1\x7f\x90\x7f\x82\x7f\x7b\x7f\x98\x2e\x35" - b"\xb7\x00\xb2\x90\x2e\x97\xb0\x03\x2e\x8f\x00\x07\x2e\x91\x00\x05\x2e\xb1\x00" - b"\x3f\xba\x9f\xb8\x01\x2e\xb1\x00\xa3\xbd\x4c\x0a\x05\x2e\xb1\x00\x04\xbe\xbf" - b"\xb9\xcb\x0a\x4f\xba\x22\xbd\x01\x2e\xb3\x00\xdc\x0a\x2f\xb9\x03\x2e\xb8\x00" - b"\x0a\xbe\x9a\x0a\xcf\xb9\x9b\xbc\x01\x2e\x97\x00\x9f\xb8\x93\x0a\x0f\xbc\x91" - b"\x0a\x0f\xb8\x90\x0a\x25\x2e\x18\x00\x05\x2e\xc1\xf5\x2e\xbd\x2e\xb9\x01\x2e" - b"\x19\x00\x31\x30\x8a\x04\x00\x90\x07\x2f\x01\x2e\xd4\x00\x04\xa2\x03\x2f\x01" - b"\x2e\x18\x00\x00\xb2\x0c\x2f\x19\x50\x05\x52\x98\x2e\x4d\xb7\x05\x2e\x78\x00" - b"\x80\x90\x10\x30\x01\x2f\x21\x2e\x78\x00\x25\x2e\xdd\x00\x98\x2e\x3e\xb7\x00" - b"\xb2\x02\x30\x01\x30\x04\x2f\x01\x2e\x19\x00\x00\xb2\x00\x2f\x21\x30\x01\x2e" - b"\xea\x00\x08\x1a\x0e\x2f\x23\x2e\xea\x00\x33\x30\x1b\x50\x0b\x09\x01\x40\x17" - b"\x56\x46\xbe\x4b\x08\x4c\x0a\x01\x42\x0a\x80\x15\x52\x01\x42\x00\x2e\x01\x2e" - b"\x18\x00\x00\xb2\x1f\x2f\x03\x2e\xc0\xf5\xf0\x30\x48\x08\x47\xaa\x74\x30\x07" - b"\x2e\x7a\x00\x61\x22\x4b\x1a\x05\x2f\x07\x2e\x66\xf5\xbf\xbd\xbf\xb9\xc0\x90" - b"\x0b\x2f\x1d\x56\x2b\x30\xd2\x42\xdb\x42\x01\x04\xc2\x42\x04\xbd\xfe\x80\x81" - b"\x84\x23\x2e\x7a\x00\x02\x42\x02\x32\x25\x2e\x62\xf5\x05\x2e\xd6\x00\x81\x84" - b"\x25\x2e\xd6\x00\x02\x31\x25\x2e\x60\xf5\x05\x2e\x8a\x00\x0b\x50\x90\x08\x80" - b"\xb2\x0b\x2f\x05\x2e\xca\xf5\xf0\x3e\x90\x08\x25\x2e\xca\xf5\x05\x2e\x59\xf5" - b"\xe0\x3f\x90\x08\x25\x2e\x59\xf5\x90\x6f\xa1\x6f\xb3\x6f\xc4\x6f\xd5\x6f\xe6" - b"\x6f\xf7\x6f\x7b\x6f\x82\x6f\x70\x5f\xc8\x2e\xc0\x50\x90\x7f\xe5\x7f\xd4\x7f" - b"\xc3\x7f\xb1\x7f\xa2\x7f\x87\x7f\xf6\x7f\x7b\x7f\x00\x2e\x01\x2e\x60\xf5\x60" - b"\x7f\x98\x2e\x35\xb7\x02\x30\x63\x6f\x15\x52\x50\x7f\x62\x7f\x5a\x2c\x02\x32" - b"\x1a\x09\x00\xb3\x14\x2f\x00\xb2\x03\x2f\x09\x2e\x18\x00\x00\x91\x0c\x2f\x43" - b"\x7f\x98\x2e\x97\xb7\x1f\x50\x02\x8a\x02\x32\x04\x30\x25\x2e\x64\xf5\x15\x52" - b"\x50\x6f\x43\x6f\x44\x43\x25\x2e\x60\xf5\xd9\x08\xc0\xb2\x36\x2f\x98\x2e\x3e" - b"\xb7\x00\xb2\x06\x2f\x01\x2e\x19\x00\x00\xb2\x02\x2f\x50\x6f\x00\x90\x0a\x2f" - b"\x01\x2e\x79\x00\x00\x90\x19\x2f\x10\x30\x21\x2e\x79\x00\x00\x30\x98\x2e\xdc" - b"\x03\x13\x2d\x01\x2e\xc3\xf5\x0c\xbc\x0f\xb8\x12\x30\x10\x04\x03\xb0\x26\x25" - b"\x21\x50\x03\x52\x98\x2e\x4d\xb7\x10\x30\x21\x2e\xee\x00\x02\x30\x60\x7f\x25" - b"\x2e\x79\x00\x60\x6f\x00\x90\x05\x2f\x00\x30\x21\x2e\xea\x00\x15\x50\x21\x2e" - b"\x64\xf5\x15\x52\x23\x2e\x60\xf5\x02\x32\x50\x6f\x00\x90\x02\x2f\x03\x30\x27" - b"\x2e\x78\x00\x07\x2e\x60\xf5\x1a\x09\x00\x91\xa3\x2f\x19\x09\x00\x91\xa0\x2f" - b"\x90\x6f\xa2\x6f\xb1\x6f\xc3\x6f\xd4\x6f\xe5\x6f\x7b\x6f\xf6\x6f\x87\x6f\x40" - b"\x5f\xc8\x2e\xc0\x50\xe7\x7f\xf6\x7f\x26\x30\x0f\x2e\x61\xf5\x2f\x2e\x7c\x00" - b"\x0f\x2e\x7c\x00\xbe\x09\xa2\x7f\x80\x7f\x80\xb3\xd5\x7f\xc4\x7f\xb3\x7f\x91" - b"\x7f\x7b\x7f\x0b\x2f\x23\x50\x1a\x25\x12\x40\x42\x7f\x74\x82\x12\x40\x52\x7f" - b"\x00\x2e\x00\x40\x60\x7f\x98\x2e\x6a\xd6\x81\x30\x01\x2e\x7c\x00\x01\x08\x00" - b"\xb2\x42\x2f\x03\x2e\x89\x00\x01\x2e\x89\x00\x97\xbc\x06\xbc\x9f\xb8\x0f\xb8" - b"\x00\x90\x23\x2e\xd8\x00\x10\x30\x01\x30\x2a\x2f\x03\x2e\xd4\x00\x44\xb2\x05" - b"\x2f\x47\xb2\x00\x30\x2d\x2f\x21\x2e\x7c\x00\x2b\x2d\x03\x2e\xfd\xf5\x9e\xbc" - b"\x9f\xb8\x40\x90\x14\x2f\x03\x2e\xfc\xf5\x99\xbc\x9f\xb8\x40\x90\x0e\x2f\x03" - b"\x2e\x49\xf1\x25\x54\x4a\x08\x40\x90\x08\x2f\x98\x2e\x35\xb7\x00\xb2\x10\x30" - b"\x03\x2f\x50\x30\x21\x2e\xd4\x00\x10\x2d\x98\x2e\xaf\xb7\x00\x30\x21\x2e\x7c" - b"\x00\x0a\x2d\x05\x2e\x69\xf7\x2d\xbd\x2f\xb9\x80\xb2\x01\x2f\x21\x2e\x7d\x00" - b"\x23\x2e\x7c\x00\xe0\x31\x21\x2e\x61\xf5\xf6\x6f\xe7\x6f\x80\x6f\xa2\x6f\xb3" - b"\x6f\xc4\x6f\xd5\x6f\x7b\x6f\x91\x6f\x40\x5f\xc8\x2e\x60\x51\x0a\x25\x36\x88" - b"\xf4\x7f\xeb\x7f\x00\x32\x31\x52\x32\x30\x13\x30\x98\x2e\x15\xcb\x0a\x25\x33" - b"\x84\xd2\x7f\x43\x30\x05\x50\x2d\x52\x98\x2e\x95\xc1\xd2\x6f\x27\x52\x98\x2e" - b"\xd7\xc7\x2a\x25\xb0\x86\xc0\x7f\xd3\x7f\xaf\x84\x29\x50\xf1\x6f\x98\x2e\x4d" - b"\xc8\x2a\x25\xae\x8a\xaa\x88\xf2\x6e\x2b\x50\xc1\x6f\xd3\x6f\xf4\x7f\x98\x2e" - b"\xb6\xc8\xe0\x6e\x00\xb2\x32\x2f\x33\x54\x83\x86\xf1\x6f\xc3\x7f\x04\x30\x30" - b"\x30\xf4\x7f\xd0\x7f\xb2\x7f\xe3\x30\xc5\x6f\x56\x40\x45\x41\x28\x08\x03\x14" - b"\x0e\xb4\x08\xbc\x82\x40\x10\x0a\x2f\x54\x26\x05\x91\x7f\x44\x28\xa3\x7f\x98" - b"\x2e\xd9\xc0\x08\xb9\x33\x30\x53\x09\xc1\x6f\xd3\x6f\xf4\x6f\x83\x17\x47\x40" - b"\x6c\x15\xb2\x6f\xbe\x09\x75\x0b\x90\x42\x45\x42\x51\x0e\x32\xbc\x02\x89\xa1" - b"\x6f\x7e\x86\xf4\x7f\xd0\x7f\xb2\x7f\x04\x30\x91\x6f\xd6\x2f\xeb\x6f\xa0\x5e" - b"\xb8\x2e\x03\x2e\x97\x00\x1b\xbc\x60\x50\x9f\xbc\x0c\xb8\xf0\x7f\x40\xb2\xeb" - b"\x7f\x2b\x2f\x03\x2e\x7f\x00\x41\x40\x01\x2e\xc8\x00\x01\x1a\x11\x2f\x37\x58" - b"\x23\x2e\xc8\x00\x10\x41\xa0\x7f\x38\x81\x01\x41\xd0\x7f\xb1\x7f\x98\x2e\x64" - b"\xcf\xd0\x6f\x07\x80\xa1\x6f\x11\x42\x00\x2e\xb1\x6f\x01\x42\x11\x30\x01\x2e" - b"\xfc\x00\x00\xa8\x03\x30\xcb\x22\x4a\x25\x01\x2e\x7f\x00\x3c\x89\x35\x52\x05" - b"\x54\x98\x2e\xc4\xce\xc1\x6f\xf0\x6f\x98\x2e\x95\xcf\x04\x2d\x01\x30\xf0\x6f" - b"\x98\x2e\x95\xcf\xeb\x6f\xa0\x5f\xb8\x2e\x03\x2e\xb3\x00\x02\x32\xf0\x30\x03" - b"\x31\x30\x50\x8a\x08\x08\x08\xcb\x08\xe0\x7f\x80\xb2\xf3\x7f\xdb\x7f\x25\x2f" - b"\x03\x2e\xca\x00\x41\x90\x04\x2f\x01\x30\x23\x2e\xca\x00\x98\x2e\x3f\x03\xc0" - b"\xb2\x05\x2f\x03\x2e\xda\x00\x00\x30\x41\x04\x23\x2e\xda\x00\x98\x2e\x92\xb2" - b"\x10\x25\xf0\x6f\x00\xb2\x05\x2f\x01\x2e\xda\x00\x02\x30\x10\x04\x21\x2e\xda" - b"\x00\x40\xb2\x01\x2f\x23\x2e\xc8\x01\xdb\x6f\xe0\x6f\xd0\x5f\x80\x2e\x95\xcf" - b"\x01\x30\xe0\x6f\x98\x2e\x95\xcf\x11\x30\x23\x2e\xca\x00\xdb\x6f\xd0\x5f\xb8" - b"\x2e\xd0\x50\x0a\x25\x33\x84\x55\x50\xd2\x7f\xe2\x7f\x03\x8c\xc0\x7f\xbb\x7f" - b"\x00\x30\x05\x5a\x39\x54\x51\x41\xa5\x7f\x96\x7f\x80\x7f\x98\x2e\xd9\xc0\x05" - b"\x30\xf5\x7f\x20\x25\x91\x6f\x3b\x58\x3d\x5c\x3b\x56\x98\x2e\x67\xcc\xc1\x6f" - b"\xd5\x6f\x52\x40\x50\x43\xc1\x7f\xd5\x7f\x10\x25\x98\x2e\xfe\xc9\x10\x25\x98" - b"\x2e\x74\xc0\x86\x6f\x30\x28\x92\x6f\x82\x8c\xa5\x6f\x6f\x52\x69\x0e\x39\x54" - b"\xdb\x2f\x19\xa0\x15\x30\x03\x2f\x00\x30\x21\x2e\x81\x01\x0a\x2d\x01\x2e\x81" - b"\x01\x05\x28\x42\x36\x21\x2e\x81\x01\x02\x0e\x01\x2f\x98\x2e\xf3\x03\x57\x50" - b"\x12\x30\x01\x40\x98\x2e\xfe\xc9\x51\x6f\x0b\x5c\x8e\x0e\x3b\x6f\x57\x58\x02" - b"\x30\x21\x2e\x95\x01\x45\x6f\x2a\x8d\xd2\x7f\xcb\x7f\x13\x2f\x02\x30\x3f\x50" - b"\xd2\x7f\xa8\x0e\x0e\x2f\xc0\x6f\x53\x54\x02\x00\x51\x54\x42\x0e\x10\x30\x59" - b"\x52\x02\x30\x01\x2f\x00\x2e\x03\x2d\x50\x42\x42\x42\x12\x30\xd2\x7f\x80\xb2" - b"\x03\x2f\x00\x30\x21\x2e\x80\x01\x12\x2d\x01\x2e\xc9\x00\x02\x80\x05\x2e\x80" - b"\x01\x11\x30\x91\x28\x00\x40\x25\x2e\x80\x01\x10\x0e\x05\x2f\x01\x2e\x7f\x01" - b"\x01\x90\x01\x2f\x98\x2e\xf3\x03\x00\x2e\xa0\x41\x01\x90\xa6\x7f\x90\x2e\xe3" - b"\xb4\x01\x2e\x95\x01\x00\xa8\x90\x2e\xe3\xb4\x5b\x54\x95\x80\x82\x40\x80\xb2" - b"\x02\x40\x2d\x8c\x3f\x52\x96\x7f\x90\x2e\xc2\xb3\x29\x0e\x76\x2f\x01\x2e\xc9" - b"\x00\x00\x40\x81\x28\x45\x52\xb3\x30\x98\x2e\x0f\xca\x5d\x54\x80\x7f\x00\x2e" - b"\xa1\x40\x72\x7f\x82\x80\x82\x40\x60\x7f\x98\x2e\xfe\xc9\x10\x25\x98\x2e\x74" - b"\xc0\x62\x6f\x05\x30\x87\x40\xc0\x91\x04\x30\x05\x2f\x05\x2e\x83\x01\x80\xb2" - b"\x14\x30\x00\x2f\x04\x30\x05\x2e\xc9\x00\x73\x6f\x81\x40\xe2\x40\x69\x04\x11" - b"\x0f\xe1\x40\x16\x30\xfe\x29\xcb\x40\x02\x2f\x83\x6f\x83\x0f\x22\x2f\x47\x56" - b"\x13\x0f\x12\x30\x77\x2f\x49\x54\x42\x0e\x12\x30\x73\x2f\x00\x91\x0a\x2f\x01" - b"\x2e\x8b\x01\x19\xa8\x02\x30\x6c\x2f\x63\x50\x00\x2e\x17\x42\x05\x42\x68\x2c" - b"\x12\x30\x0b\x25\x08\x0f\x50\x30\x02\x2f\x21\x2e\x83\x01\x03\x2d\x40\x30\x21" - b"\x2e\x83\x01\x2b\x2e\x85\x01\x5a\x2c\x12\x30\x00\x91\x2b\x25\x04\x2f\x63\x50" - b"\x02\x30\x17\x42\x17\x2c\x02\x42\x98\x2e\xfe\xc9\x10\x25\x98\x2e\x74\xc0\x05" - b"\x2e\xc9\x00\x81\x84\x5b\x30\x82\x40\x37\x2e\x83\x01\x02\x0e\x07\x2f\x5f\x52" - b"\x40\x30\x62\x40\x41\x40\x91\x0e\x01\x2f\x21\x2e\x83\x01\x05\x30\x2b\x2e\x85" - b"\x01\x12\x30\x36\x2c\x16\x30\x15\x25\x81\x7f\x98\x2e\xfe\xc9\x10\x25\x98\x2e" - b"\x74\xc0\x19\xa2\x16\x30\x15\x2f\x05\x2e\x97\x01\x80\x6f\x82\x0e\x05\x2f\x01" - b"\x2e\x86\x01\x06\x28\x21\x2e\x86\x01\x0b\x2d\x03\x2e\x87\x01\x5f\x54\x4e\x28" - b"\x91\x42\x00\x2e\x82\x40\x90\x0e\x01\x2f\x21\x2e\x88\x01\x02\x30\x13\x2c\x05" - b"\x30\xc0\x6f\x08\x1c\xa8\x0f\x16\x30\x05\x30\x5b\x50\x09\x2f\x02\x80\x2d\x2e" - b"\x82\x01\x05\x42\x05\x80\x00\x2e\x02\x42\x3e\x80\x00\x2e\x06\x42\x02\x30\x90" - b"\x6f\x3e\x88\x01\x40\x04\x41\x4c\x28\x01\x42\x07\x80\x10\x25\x24\x40\x00\x40" - b"\x00\xa8\xf5\x22\x23\x29\x44\x42\x7a\x82\x7e\x88\x43\x40\x04\x41\x00\xab\xf5" - b"\x23\xdf\x28\x43\x42\xd9\xa0\x14\x2f\x00\x90\x02\x2f\xd2\x6f\x81\xb2\x05\x2f" - b"\x63\x54\x06\x28\x90\x42\x85\x42\x09\x2c\x02\x30\x5b\x50\x03\x80\x29\x2e\x7e" - b"\x01\x2b\x2e\x82\x01\x05\x42\x12\x30\x2b\x2e\x83\x01\x45\x82\x00\x2e\x40\x40" - b"\x7a\x82\x02\xa0\x08\x2f\x63\x50\x3b\x30\x15\x42\x05\x42\x37\x80\x37\x2e\x7e" - b"\x01\x05\x42\x12\x30\x01\x2e\xc9\x00\x02\x8c\x40\x40\x84\x41\x7a\x8c\x04\x0f" - b"\x03\x2f\x01\x2e\x8b\x01\x19\xa4\x04\x2f\x2b\x2e\x82\x01\x98\x2e\xf3\x03\x12" - b"\x30\x81\x90\x61\x52\x08\x2f\x65\x42\x65\x42\x43\x80\x39\x84\x82\x88\x05\x42" - b"\x45\x42\x85\x42\x05\x43\x00\x2e\x80\x41\x00\x90\x90\x2e\xe1\xb4\x65\x54\xc1" - b"\x6f\x80\x40\x00\xb2\x43\x58\x69\x50\x44\x2f\x55\x5c\xb7\x87\x8c\x0f\x0d\x2e" - b"\x96\x01\xc4\x40\x36\x2f\x41\x56\x8b\x0e\x2a\x2f\x0b\x52\xa1\x0e\x0a\x2f\x05" - b"\x2e\x8f\x01\x14\x25\x98\x2e\xfe\xc9\x4b\x54\x02\x0f\x69\x50\x05\x30\x65\x54" - b"\x15\x2f\x03\x2e\x8e\x01\x4d\x5c\x8e\x0f\x3a\x2f\x05\x2e\x8f\x01\x98\x2e\xfe" - b"\xc9\x4f\x54\x82\x0f\x05\x30\x69\x50\x65\x54\x30\x2f\x6d\x52\x15\x30\x42\x8c" - b"\x45\x42\x04\x30\x2b\x2c\x84\x43\x6b\x52\x42\x8c\x00\x2e\x85\x43\x15\x30\x24" - b"\x2c\x45\x42\x8e\x0f\x20\x2f\x0d\x2e\x8e\x01\xb1\x0e\x1c\x2f\x23\x2e\x8e\x01" - b"\x1a\x2d\x0e\x0e\x17\x2f\xa1\x0f\x15\x2f\x23\x2e\x8d\x01\x13\x2d\x98\x2e\x74" - b"\xc0\x43\x54\xc2\x0e\x0a\x2f\x65\x50\x04\x80\x0b\x30\x06\x82\x0b\x42\x79\x80" - b"\x41\x40\x12\x30\x25\x2e\x8c\x01\x01\x42\x05\x30\x69\x50\x65\x54\x84\x82\x43" - b"\x84\xbe\x8c\x84\x40\x86\x41\x26\x29\x94\x42\xbe\x8e\xd5\x7f\x19\xa1\x43\x40" - b"\x0b\x2e\x8c\x01\x84\x40\xc7\x41\x5d\x29\x27\x29\x45\x42\x84\x42\xc2\x7f\x01" - b"\x2f\xc0\xb3\x1d\x2f\x05\x2e\x94\x01\x99\xa0\x01\x2f\x80\xb3\x13\x2f\x80\xb3" - b"\x18\x2f\xc0\xb3\x16\x2f\x12\x40\x01\x40\x92\x7f\x98\x2e\x74\xc0\x92\x6f\x10" - b"\x0f\x20\x30\x03\x2f\x10\x30\x21\x2e\x7e\x01\x0a\x2d\x21\x2e\x7e\x01\x07\x2d" - b"\x20\x30\x21\x2e\x7e\x01\x03\x2d\x10\x30\x21\x2e\x7e\x01\xc2\x6f\x01\x2e\xc9" - b"\x00\xbc\x84\x02\x80\x82\x40\x00\x40\x90\x0e\xd5\x6f\x02\x2f\x15\x30\x98\x2e" - b"\xf3\x03\x41\x91\x05\x30\x07\x2f\x67\x50\x3d\x80\x2b\x2e\x8f\x01\x05\x42\x04" - b"\x80\x00\x2e\x05\x42\x02\x2c\x00\x30\x00\x30\xa2\x6f\x98\x8a\x86\x40\x80\xa7" - b"\x05\x2f\x98\x2e\xf3\x03\xc0\x30\x21\x2e\x95\x01\x06\x25\x1a\x25\xe2\x6f\x76" - b"\x82\x96\x40\x56\x43\x51\x0e\xfb\x2f\xbb\x6f\x30\x5f\xb8\x2e\x01\x2e\xb8\x00" - b"\x01\x31\x41\x08\x40\xb2\x20\x50\xf2\x30\x02\x08\xfb\x7f\x01\x30\x10\x2f\x05" - b"\x2e\xcc\x00\x81\x90\xe0\x7f\x03\x2f\x23\x2e\xcc\x00\x98\x2e\x55\xb6\x98\x2e" - b"\x1d\xb5\x10\x25\xfb\x6f\xe0\x6f\xe0\x5f\x80\x2e\x95\xcf\x98\x2e\x95\xcf\x10" - b"\x30\x21\x2e\xcc\x00\xfb\x6f\xe0\x5f\xb8\x2e\x00\x51\x05\x58\xeb\x7f\x2a\x25" - b"\x89\x52\x6f\x5a\x89\x50\x13\x41\x06\x40\xb3\x01\x16\x42\xcb\x16\x06\x40\xf3" - b"\x02\x13\x42\x65\x0e\xf5\x2f\x05\x40\x14\x30\x2c\x29\x04\x42\x08\xa1\x00\x30" - b"\x90\x2e\x52\xb6\xb3\x88\xb0\x8a\xb6\x84\xa4\x7f\xc4\x7f\xb5\x7f\xd5\x7f\x92" - b"\x7f\x73\x30\x04\x30\x55\x40\x42\x40\x8a\x17\xf3\x08\x6b\x01\x90\x02\x53\xb8" - b"\x4b\x82\xad\xbe\x71\x7f\x45\x0a\x09\x54\x84\x7f\x98\x2e\xd9\xc0\xa3\x6f\x7b" - b"\x54\xd0\x42\xa3\x7f\xf2\x7f\x60\x7f\x20\x25\x71\x6f\x75\x5a\x77\x58\x79\x5c" - b"\x75\x56\x98\x2e\x67\xcc\xb1\x6f\x62\x6f\x50\x42\xb1\x7f\xb3\x30\x10\x25\x98" - b"\x2e\x0f\xca\x84\x6f\x20\x29\x71\x6f\x92\x6f\xa5\x6f\x76\x82\x6a\x0e\x73\x30" - b"\x00\x30\xd0\x2f\xd2\x6f\xd1\x7f\xb4\x7f\x98\x2e\x2b\xb7\x15\xbd\x0b\xb8\x02" - b"\x0a\xc2\x6f\xc0\x7f\x98\x2e\x2b\xb7\x15\xbd\x0b\xb8\x42\x0a\xc0\x6f\x08\x17" - b"\x41\x18\x89\x16\xe1\x18\xd0\x18\xa1\x7f\x27\x25\x16\x25\x98\x2e\x79\xc0\x8b" - b"\x54\x90\x7f\xb3\x30\x82\x40\x80\x90\x0d\x2f\x7d\x52\x92\x6f\x98\x2e\x0f\xca" - b"\xb2\x6f\x90\x0e\x06\x2f\x8b\x50\x14\x30\x42\x6f\x51\x6f\x14\x42\x12\x42\x01" - b"\x42\x00\x2e\x31\x6f\x98\x2e\x74\xc0\x41\x6f\x80\x7f\x98\x2e\x74\xc0\x82\x6f" - b"\x10\x04\x43\x52\x01\x0f\x05\x2e\xcb\x00\x00\x30\x04\x30\x21\x2f\x51\x6f\x43" - b"\x58\x8c\x0e\x04\x30\x1c\x2f\x85\x88\x41\x6f\x04\x41\x8c\x0f\x04\x30\x16\x2f" - b"\x84\x88\x00\x2e\x04\x41\x04\x05\x8c\x0e\x04\x30\x0f\x2f\x82\x88\x31\x6f\x04" - b"\x41\x04\x05\x8c\x0e\x04\x30\x08\x2f\x83\x88\x00\x2e\x04\x41\x8c\x0f\x04\x30" - b"\x02\x2f\x21\x2e\xad\x01\x14\x30\x00\x91\x14\x2f\x03\x2e\xa1\x01\x41\x90\x0e" - b"\x2f\x03\x2e\xad\x01\x14\x30\x4c\x28\x23\x2e\xad\x01\x46\xa0\x06\x2f\x81\x84" - b"\x8d\x52\x48\x82\x82\x40\x21\x2e\xa1\x01\x42\x42\x5c\x2c\x02\x30\x05\x2e\xaa" - b"\x01\x80\xb2\x02\x30\x55\x2f\x03\x2e\xa9\x01\x92\x6f\xb3\x30\x98\x2e\x0f\xca" - b"\xb2\x6f\x90\x0f\x00\x30\x02\x30\x4a\x2f\xa2\x6f\x87\x52\x91\x00\x85\x52\x51" - b"\x0e\x02\x2f\x00\x2e\x43\x2c\x02\x30\xc2\x6f\x7f\x52\x91\x0e\x02\x30\x3c\x2f" - b"\x51\x6f\x81\x54\x98\x2e\xfe\xc9\x10\x25\xb3\x30\x21\x25\x98\x2e\x0f\xca\x32" - b"\x6f\xc0\x7f\xb3\x30\x12\x25\x98\x2e\x0f\xca\x42\x6f\xb0\x7f\xb3\x30\x12\x25" - b"\x98\x2e\x0f\xca\xb2\x6f\x90\x28\x83\x52\x98\x2e\xfe\xc9\xc2\x6f\x90\x0f\x00" - b"\x30\x02\x30\x1d\x2f\x05\x2e\xa1\x01\x80\xb2\x12\x30\x0f\x2f\x42\x6f\x03\x2e" - b"\xab\x01\x91\x0e\x02\x30\x12\x2f\x52\x6f\x03\x2e\xac\x01\x91\x0f\x02\x30\x0c" - b"\x2f\x21\x2e\xaa\x01\x0a\x2c\x12\x30\x03\x2e\xcb\x00\x8d\x58\x08\x89\x41\x40" - b"\x11\x43\x00\x43\x25\x2e\xa1\x01\xd4\x6f\x8f\x52\x00\x43\x3a\x89\x00\x2e\x10" - b"\x43\x10\x43\x61\x0e\xfb\x2f\x03\x2e\xa0\x01\x11\x1a\x02\x2f\x02\x25\x21\x2e" - b"\xa0\x01\xeb\x6f\x00\x5f\xb8\x2e\x91\x52\x10\x30\x02\x30\x95\x56\x52\x42\x4b" - b"\x0e\xfc\x2f\x8d\x54\x88\x82\x93\x56\x80\x42\x53\x42\x40\x42\x42\x86\x83\x54" - b"\xc0\x2e\xc2\x42\x00\x2e\xa3\x52\x00\x51\x52\x40\x47\x40\x1a\x25\x01\x2e\x97" - b"\x00\x8f\xbe\x72\x86\xfb\x7f\x0b\x30\x7c\xbf\xa5\x50\x10\x08\xdf\xba\x70\x88" - b"\xf8\xbf\xcb\x42\xd3\x7f\x6c\xbb\xfc\xbb\xc5\x0a\x90\x7f\x1b\x7f\x0b\x43\xc0" - b"\xb2\xe5\x7f\xb7\x7f\xa6\x7f\xc4\x7f\x90\x2e\x1c\xb7\x07\x2e\xd2\x00\xc0\xb2" - b"\x0b\x2f\x97\x52\x01\x2e\xcd\x00\x82\x7f\x98\x2e\xbb\xcc\x0b\x30\x37\x2e\xd2" - b"\x00\x82\x6f\x90\x6f\x1a\x25\x00\xb2\x8b\x7f\x14\x2f\xa6\xbd\x25\xbd\xb6\xb9" - b"\x2f\xb9\x80\xb2\xd4\xb0\x0c\x2f\x99\x54\x9b\x56\x0b\x30\x0b\x2e\xb1\x00\xa1" - b"\x58\x9b\x42\xdb\x42\x6c\x09\x2b\x2e\xb1\x00\x8b\x42\xcb\x42\x86\x7f\x73\x84" - b"\xa7\x56\xc3\x08\x39\x52\x05\x50\x72\x7f\x63\x7f\x98\x2e\xc2\xc0\xe1\x6f\x62" - b"\x6f\xd1\x0a\x01\x2e\xcd\x00\xd5\x6f\xc4\x6f\x72\x6f\x97\x52\x9d\x5c\x98\x2e" - b"\x06\xcd\x23\x6f\x90\x6f\x99\x52\xc0\xb2\x04\xbd\x54\x40\xaf\xb9\x45\x40\xe1" - b"\x7f\x02\x30\x06\x2f\xc0\xb2\x02\x30\x03\x2f\x9b\x5c\x12\x30\x94\x43\x85\x43" - b"\x03\xbf\x6f\xbb\x80\xb3\x20\x2f\x06\x6f\x26\x01\x16\x6f\x6e\x03\x45\x42\xc0" - b"\x90\x29\x2e\xce\x00\x9b\x52\x14\x2f\x9b\x5c\x00\x2e\x93\x41\x86\x41\xe3\x04" - b"\xae\x07\x80\xab\x04\x2f\x80\x91\x0a\x2f\x86\x6f\x73\x0f\x07\x2f\x83\x6f\xc0" - b"\xb2\x04\x2f\x54\x42\x45\x42\x12\x30\x04\x2c\x11\x30\x02\x2c\x11\x30\x11\x30" - b"\x02\xbc\x0f\xb8\xd2\x7f\x00\xb2\x0a\x2f\x01\x2e\xfc\x00\x05\x2e\xc7\x01\x10" - b"\x1a\x02\x2f\x21\x2e\xc7\x01\x03\x2d\x02\x2c\x01\x30\x01\x30\xb0\x6f\x98\x2e" - b"\x95\xcf\xd1\x6f\xa0\x6f\x98\x2e\x95\xcf\xe2\x6f\x9f\x52\x01\x2e\xce\x00\x82" - b"\x40\x50\x42\x0c\x2c\x42\x42\x11\x30\x23\x2e\xd2\x00\x01\x30\xb0\x6f\x98\x2e" - b"\x95\xcf\xa0\x6f\x01\x30\x98\x2e\x95\xcf\x00\x2e\xfb\x6f\x00\x5f\xb8\x2e\x83" - b"\x86\x01\x30\x00\x30\x94\x40\x24\x18\x06\x00\x53\x0e\x4f\x02\xf9\x2f\xb8\x2e" - b"\xa9\x52\x00\x2e\x60\x40\x41\x40\x0d\xbc\x98\xbc\xc0\x2e\x01\x0a\x0f\xb8\xab" - b"\x52\x53\x3c\x52\x40\x40\x40\x4b\x00\x82\x16\x26\xb9\x01\xb8\x41\x40\x10\x08" - b"\x97\xb8\x01\x08\xc0\x2e\x11\x30\x01\x08\x43\x86\x25\x40\x04\x40\xd8\xbe\x2c" - b"\x0b\x22\x11\x54\x42\x03\x80\x4b\x0e\xf6\x2f\xb8\x2e\x9f\x50\x10\x50\xad\x52" - b"\x05\x2e\xd3\x00\xfb\x7f\x00\x2e\x13\x40\x93\x42\x41\x0e\xfb\x2f\x98\x2e\xa5" - b"\xb7\x98\x2e\x87\xcf\x01\x2e\xd9\x00\x00\xb2\xfb\x6f\x0b\x2f\x01\x2e\x69\xf7" - b"\xb1\x3f\x01\x08\x01\x30\xf0\x5f\x23\x2e\xd9\x00\x21\x2e\x69\xf7\x80\x2e\x7a" - b"\xb7\xf0\x5f\xb8\x2e\x01\x2e\xc0\xf8\x03\x2e\xfc\xf5\x15\x54\xaf\x56\x82\x08" - b"\x0b\x2e\x69\xf7\xcb\x0a\xb1\x58\x80\x90\xdd\xbe\x4c\x08\x5f\xb9\x59\x22\x80" - b"\x90\x07\x2f\x03\x34\xc3\x08\xf2\x3a\x0a\x08\x02\x35\xc0\x90\x4a\x0a\x48\x22" - b"\xc0\x2e\x23\x2e\xfc\xf5\x10\x50\xfb\x7f\x98\x2e\x56\xc7\x98\x2e\x49\xc3\x10" - b"\x30\xfb\x6f\xf0\x5f\x21\x2e\xcc\x00\x21\x2e\xca\x00\xb8\x2e\x03\x2e\xd3\x00" - b"\x16\xb8\x02\x34\x4a\x0c\x21\x2e\x2d\xf5\xc0\x2e\x23\x2e\xd3\x00\x03\xbc\x21" - b"\x2e\xd5\x00\x03\x2e\xd5\x00\x40\xb2\x10\x30\x21\x2e\x77\x00\x01\x30\x05\x2f" - b"\x05\x2e\xd8\x00\x80\x90\x01\x2f\x23\x2e\x6f\xf5\xc0\x2e\x21\x2e\xd9\x00\x11" - b"\x30\x81\x08\x01\x2e\x6a\xf7\x71\x3f\x23\xbd\x01\x08\x02\x0a\xc0\x2e\x21\x2e" - b"\x6a\xf7\x30\x25\x00\x30\x21\x2e\x5a\xf5\x10\x50\x21\x2e\x7b\x00\x21\x2e\x7c" - b"\x00\xfb\x7f\x98\x2e\xc3\xb7\x40\x30\x21\x2e\xd4\x00\xfb\x6f\xf0\x5f\x03\x25" - b"\x80\x2e\xaf\xb7\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" - b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" - b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1\x80\x2e\x00\xc1\x01\x2e\x5d\xf7\x08\xbc\x80\xac\x0e\xbb\x02\x2f" - b"\x00\x30\x41\x04\x82\x06\xc0\xa4\x00\x30\x11\x2f\x40\xa9\x03\x2f\x40\x91\x0d" - b"\x2f\x00\xa7\x0b\x2f\x80\xb3\xb3\x58\x02\x2f\x90\xa1\x26\x13\x20\x23\x80\x90" - b"\x10\x30\x01\x2f\xcc\x0e\x00\x2f\x00\x30\xb8\x2e\xb5\x50\x18\x08\x08\xbc\x88" - b"\xb6\x0d\x17\xc6\xbd\x56\xbc\xb7\x58\xda\xba\x04\x01\x1d\x0a\x10\x50\x05\x30" - b"\x32\x25\x45\x03\xfb\x7f\xf6\x30\x21\x25\x98\x2e\x37\xca\x16\xb5\x9a\xbc\x06" - b"\xb8\x80\xa8\x41\x0a\x0e\x2f\x80\x90\x02\x2f\x2d\x50\x48\x0f\x09\x2f\xbf\xa0" - b"\x04\x2f\xbf\x90\x06\x2f\xb7\x54\xca\x0f\x03\x2f\x00\x2e\x02\x2c\xb7\x52\x2d" - b"\x52\xf2\x33\x98\x2e\xd9\xc0\xfb\x6f\xf1\x37\xc0\x2e\x01\x08\xf0\x5f\xbf\x56" - b"\xb9\x54\xd0\x40\xc4\x40\x0b\x2e\xfd\xf3\xbf\x52\x90\x42\x94\x42\x95\x42\x05" - b"\x30\xc1\x50\x0f\x88\x06\x40\x04\x41\x96\x42\xc5\x42\x48\xbe\x73\x30\x0d\x2e" - b"\xd8\x00\x4f\xba\x84\x42\x03\x42\x81\xb3\x02\x2f\x2b\x2e\x6f\xf5\x06\x2d\x05" - b"\x2e\x77\xf7\xbd\x56\x93\x08\x25\x2e\x77\xf7\xbb\x54\x25\x2e\xc2\xf5\x07\x2e" - b"\xfd\xf3\x42\x30\xb4\x33\xda\x0a\x4c\x00\x27\x2e\xfd\xf3\x43\x40\xd4\x3f\xdc" - b"\x08\x43\x42\x00\x2e\x00\x2e\x43\x40\x24\x30\xdc\x0a\x43\x42\x04\x80\x03\x2e" - b"\xfd\xf3\x4a\x0a\x23\x2e\xfd\xf3\x61\x34\xc0\x2e\x01\x42\x00\x2e\x60\x50\x1a" - b"\x25\x7a\x86\xe0\x7f\xf3\x7f\x03\x25\xc3\x52\x41\x84\xdb\x7f\x33\x30\x98\x2e" - b"\x16\xc2\x1a\x25\x7d\x82\xf0\x6f\xe2\x6f\x32\x25\x16\x40\x94\x40\x26\x01\x85" - b"\x40\x8e\x17\xc4\x42\x6e\x03\x95\x42\x41\x0e\xf4\x2f\xdb\x6f\xa0\x5f\xb8\x2e" - b"\xb0\x51\xfb\x7f\x98\x2e\xe8\x0d\x5a\x25\x98\x2e\x0f\x0e\xcb\x58\x32\x87\xc4" - b"\x7f\x65\x89\x6b\x8d\xc5\x5a\x65\x7f\xe1\x7f\x83\x7f\xa6\x7f\x74\x7f\xd0\x7f" - b"\xb6\x7f\x94\x7f\x17\x30\xc7\x52\xc9\x54\x51\x7f\x00\x2e\x85\x6f\x42\x7f\x00" - b"\x2e\x51\x41\x45\x81\x42\x41\x13\x40\x3b\x8a\x00\x40\x4b\x04\xd0\x06\xc0\xac" - b"\x85\x7f\x02\x2f\x02\x30\x51\x04\xd3\x06\x41\x84\x05\x30\x5d\x02\xc9\x16\xdf" - b"\x08\xd3\x00\x8d\x02\xaf\xbc\xb1\xb9\x59\x0a\x65\x6f\x11\x43\xa1\xb4\x52\x41" - b"\x53\x41\x01\x43\x34\x7f\x65\x7f\x26\x31\xe5\x6f\xd4\x6f\x98\x2e\x37\xca\x32" - b"\x6f\x75\x6f\x83\x40\x42\x41\x23\x7f\x12\x7f\xf6\x30\x40\x25\x51\x25\x98\x2e" - b"\x37\xca\x14\x6f\x20\x05\x70\x6f\x25\x6f\x69\x07\xa2\x6f\x31\x6f\x0b\x30\x04" - b"\x42\x9b\x42\x8b\x42\x55\x42\x32\x7f\x40\xa9\xc3\x6f\x71\x7f\x02\x30\xd0\x40" - b"\xc3\x7f\x03\x2f\x40\x91\x15\x2f\x00\xa7\x13\x2f\x00\xa4\x11\x2f\x84\xbd\x98" - b"\x2e\x79\xca\x55\x6f\xb7\x54\x54\x41\x82\x00\xf3\x3f\x45\x41\xcb\x02\xf6\x30" - b"\x98\x2e\x37\xca\x35\x6f\xa4\x6f\x41\x43\x03\x2c\x00\x43\xa4\x6f\x35\x6f\x17" - b"\x30\x42\x6f\x51\x6f\x93\x40\x42\x82\x00\x41\xc3\x00\x03\x43\x51\x7f\x00\x2e" - b"\x94\x40\x41\x41\x4c\x02\xc4\x6f\xd1\x56\x63\x0e\x74\x6f\x51\x43\xa5\x7f\x8a" - b"\x2f\x09\x2e\xd8\x00\x01\xb3\x21\x2f\xcb\x58\x90\x6f\x13\x41\xb6\x6f\xe4\x7f" - b"\x00\x2e\x91\x41\x14\x40\x92\x41\x15\x40\x17\x2e\x6f\xf5\xb6\x7f\xd0\x7f\xcb" - b"\x7f\x98\x2e\x00\x0c\x07\x15\xc2\x6f\x14\x0b\x29\x2e\x6f\xf5\xc3\xa3\xc1\x8f" - b"\xe4\x6f\xd0\x6f\xe6\x2f\x14\x30\x05\x2e\x6f\xf5\x14\x0b\x29\x2e\x6f\xf5\x18" - b"\x2d\xcd\x56\x04\x32\xb5\x6f\x1c\x01\x51\x41\x52\x41\xc3\x40\xb5\x7f\xe4\x7f" - b"\x98\x2e\x1f\x0c\xe4\x6f\x21\x87\x00\x43\x04\x32\xcf\x54\x5a\x0e\xef\x2f\x15" - b"\x54\x09\x2e\x77\xf7\x22\x0b\x29\x2e\x77\xf7\xfb\x6f\x50\x5e\xb8\x2e\x10\x50" - b"\x01\x2e\xd4\x00\x00\xb2\xfb\x7f\x51\x2f\x01\xb2\x48\x2f\x02\xb2\x42\x2f\x03" - b"\x90\x56\x2f\xd7\x52\x79\x80\x42\x40\x81\x84\x00\x40\x42\x42\x98\x2e\x93\x0c" - b"\xd9\x54\xd7\x50\xa1\x40\x98\xbd\x82\x40\x3e\x82\xda\x0a\x44\x40\x8b\x16\xe3" - b"\x00\x53\x42\x00\x2e\x43\x40\x9a\x02\x52\x42\x00\x2e\x41\x40\x15\x54\x4a\x0e" - b"\x3a\x2f\x3a\x82\x00\x30\x41\x40\x21\x2e\x85\x0f\x40\xb2\x0a\x2f\x98\x2e\xb1" - b"\x0c\x98\x2e\x45\x0e\x98\x2e\x5b\x0e\xfb\x6f\xf0\x5f\x00\x30\x80\x2e\xce\xb7" - b"\xdd\x52\xd3\x54\x42\x42\x4f\x84\x73\x30\xdb\x52\x83\x42\x1b\x30\x6b\x42\x23" - b"\x30\x27\x2e\xd7\x00\x37\x2e\xd4\x00\x21\x2e\xd6\x00\x7a\x84\x17\x2c\x42\x42" - b"\x30\x30\x21\x2e\xd4\x00\x12\x2d\x21\x30\x00\x30\x23\x2e\xd4\x00\x21\x2e\x7b" - b"\xf7\x0b\x2d\x17\x30\x98\x2e\x51\x0c\xd5\x50\x0c\x82\x72\x30\x2f\x2e\xd4\x00" - b"\x25\x2e\x7b\xf7\x40\x42\x00\x2e\xfb\x6f\xf0\x5f\xb8\x2e\x70\x50\x0a\x25\x39" - b"\x86\xfb\x7f\xe1\x32\x62\x30\x98\x2e\xc2\xc4\xb5\x56\xa5\x6f\xab\x08\x91\x6f" - b"\x4b\x08\xdf\x56\xc4\x6f\x23\x09\x4d\xba\x93\xbc\x8c\x0b\xd1\x6f\x0b\x09\xcb" - b"\x52\xe1\x5e\x56\x42\xaf\x09\x4d\xba\x23\xbd\x94\x0a\xe5\x6f\x68\xbb\xeb\x08" - b"\xbd\xb9\x63\xbe\xfb\x6f\x52\x42\xe3\x0a\xc0\x2e\x43\x42\x90\x5f\xd1\x50\x03" - b"\x2e\x25\xf3\x13\x40\x00\x40\x9b\xbc\x9b\xb4\x08\xbd\xb8\xb9\x98\xbc\xda\x0a" - b"\x08\xb6\x89\x16\xc0\x2e\x19\x00\x62\x02\x10\x50\xfb\x7f\x98\x2e\x81\x0d\x01" - b"\x2e\xd4\x00\x31\x30\x08\x04\xfb\x6f\x01\x30\xf0\x5f\x23\x2e\xd6\x00\x21\x2e" - b"\xd7\x00\xb8\x2e\x01\x2e\xd7\x00\x03\x2e\xd6\x00\x48\x0e\x01\x2f\x80\x2e\x1f" - b"\x0e\xb8\x2e\xe3\x50\x21\x34\x01\x42\x82\x30\xc1\x32\x25\x2e\x62\xf5\x01\x00" - b"\x22\x30\x01\x40\x4a\x0a\x01\x42\xb8\x2e\xe3\x54\xf0\x3b\x83\x40\xd8\x08\xe5" - b"\x52\x83\x42\x00\x30\x83\x30\x50\x42\xc4\x32\x27\x2e\x64\xf5\x94\x00\x50\x42" - b"\x40\x42\xd3\x3f\x84\x40\x7d\x82\xe3\x08\x40\x42\x83\x42\xb8\x2e\xdd\x52\x00" - b"\x30\x40\x42\x7c\x86\xb9\x52\x09\x2e\x70\x0f\xbf\x54\xc4\x42\xd3\x86\x54\x40" - b"\x55\x40\x94\x42\x85\x42\x21\x2e\xd7\x00\x42\x40\x25\x2e\xfd\xf3\xc0\x42\x7e" - b"\x82\x05\x2e\x7d\x00\x80\xb2\x14\x2f\x05\x2e\x89\x00\x27\xbd\x2f\xb9\x80\x90" - b"\x02\x2f\x21\x2e\x6f\xf5\x0c\x2d\x07\x2e\x71\x0f\x14\x30\x1c\x09\x05\x2e\x77" - b"\xf7\xbd\x56\x47\xbe\x93\x08\x94\x0a\x25\x2e\x77\xf7\xe7\x54\x50\x42\x4a\x0e" - b"\xfc\x2f\xb8\x2e\x50\x50\x02\x30\x43\x86\xe5\x50\xfb\x7f\xe3\x7f\xd2\x7f\xc0" - b"\x7f\xb1\x7f\x00\x2e\x41\x40\x00\x40\x48\x04\x98\x2e\x74\xc0\x1e\xaa\xd3\x6f" - b"\x14\x30\xb1\x6f\xe3\x22\xc0\x6f\x52\x40\xe4\x6f\x4c\x0e\x12\x42\xd3\x7f\xeb" - b"\x2f\x03\x2e\x86\x0f\x40\x90\x11\x30\x03\x2f\x23\x2e\x86\x0f\x02\x2c\x00\x30" - b"\xd0\x6f\xfb\x6f\xb0\x5f\xb8\x2e\x40\x50\xf1\x7f\x0a\x25\x3c\x86\xeb\x7f\x41" - b"\x33\x22\x30\x98\x2e\xc2\xc4\xd3\x6f\xf4\x30\xdc\x09\x47\x58\xc2\x6f\x94\x09" - b"\xeb\x58\x6a\xbb\xdc\x08\xb4\xb9\xb1\xbd\xe9\x5a\x95\x08\x21\xbd\xf6\xbf\x77" - b"\x0b\x51\xbe\xf1\x6f\xeb\x6f\x52\x42\x54\x42\xc0\x2e\x43\x42\xc0\x5f\x50\x50" - b"\xf5\x50\x31\x30\x11\x42\xfb\x7f\x7b\x30\x0b\x42\x11\x30\x02\x80\x23\x33\x01" - b"\x42\x03\x00\x07\x2e\x80\x03\x05\x2e\xd3\x00\x23\x52\xe2\x7f\xd3\x7f\xc0\x7f" - b"\x98\x2e\xb6\x0e\xd1\x6f\x08\x0a\x1a\x25\x7b\x86\xd0\x7f\x01\x33\x12\x30\x98" - b"\x2e\xc2\xc4\xd1\x6f\x08\x0a\x00\xb2\x0d\x2f\xe3\x6f\x01\x2e\x80\x03\x51\x30" - b"\xc7\x86\x23\x2e\x21\xf2\x08\xbc\xc0\x42\x98\x2e\xa5\xb7\x00\x2e\x00\x2e\xd0" - b"\x2e\xb0\x6f\x0b\xb8\x03\x2e\x1b\x00\x08\x1a\xb0\x7f\x70\x30\x04\x2f\x21\x2e" - b"\x21\xf2\x00\x2e\x00\x2e\xd0\x2e\x98\x2e\x6d\xc0\x98\x2e\x5d\xc0\xed\x50\x98" - b"\x2e\x44\xcb\xef\x50\x98\x2e\x46\xc3\xf1\x50\x98\x2e\x53\xc7\x35\x50\x98\x2e" - b"\x64\xcf\x10\x30\x98\x2e\xdc\x03\x20\x26\xc0\x6f\x02\x31\x12\x42\xab\x33\x0b" - b"\x42\x37\x80\x01\x30\x01\x42\xf3\x37\xf7\x52\xfb\x50\x44\x40\xa2\x0a\x42\x42" - b"\x8b\x31\x09\x2e\x5e\xf7\xf9\x54\xe3\x08\x83\x42\x1b\x42\x23\x33\x4b\x00\xbc" - b"\x84\x0b\x40\x33\x30\x83\x42\x0b\x42\xe0\x7f\xd1\x7f\x98\x2e\x58\xb7\xd1\x6f" - b"\x80\x30\x40\x42\x03\x30\xe0\x6f\xf3\x54\x04\x30\x00\x2e\x00\x2e\x01\x89\x62" - b"\x0e\xfa\x2f\x43\x42\x11\x30\xfb\x6f\xc0\x2e\x01\x42\xb0\x5f\xc1\x4a\x00\x00" - b"\x6d\x57\x00\x00\x77\x8e\x00\x00\xe0\xff\xff\xff\xd3\xff\xff\xff\xe5\xff\xff" - b"\xff\xee\xe1\xff\xff\x7c\x13\x00\x00\x46\xe6\xff\xff\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" - b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" - b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" - b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\xc8\x2e\x00\x2e\x80\x2e\x1a\x00\xc8\x2e\x00\x2e\xc8\x2e\x00\x2e\xc8\x2e\x00" + b"\x2e\xc8\x2e\x00\x2e\xc8\x2e\x00\x2e\xc8\x2e\x00\x2e\x90\x32\x21\x2e\x59\xf5" + b"\x10\x30\x21\x2e\x6a\xf5\x1a\x24\x22\x00\x80\x2e\x3b\x00\xc8\x2e\x44\x47\x22" + b"\x00\x37\x00\xa4\x00\xff\x0f\xd1\x00\x07\xad\x80\x2e\x00\xc1\x80\x2e\x00\xc1" b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" - b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" - b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" - b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" - b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" - b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" - b"\x2e\x00\xc1" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x00\x00" + b"\x00\x00\x00\x00\x11\x24\xfc\xf5\x80\x30\x40\x42\x50\x50\x00\x30\x12\x24\xeb" + b"\x00\x03\x30\x00\x2e\xc1\x86\x5a\x0e\xfb\x2f\x21\x2e\xfc\xf5\x13\x24\x63\xf5" + b"\xe0\x3c\x48\x00\x22\x30\xf7\x80\xc2\x42\xe1\x7f\x3a\x25\xfc\x86\xf0\x7f\x41" + b"\x33\x98\x2e\xc2\xc4\xd6\x6f\xf1\x30\xf1\x08\xc4\x6f\x11\x24\xff\x03\x12\x24" + b"\x00\xfc\x61\x09\xa2\x08\x36\xbe\x2a\xb9\x13\x24\x38\x00\x64\xbb\xd1\xbe\x94" + b"\x0a\x71\x08\xd5\x42\x21\xbd\x91\xbc\xd2\x42\xc1\x42\x00\xb2\xfe\x82\x05\x2f" + b"\x50\x30\x21\x2e\x21\xf2\x00\x2e\x00\x2e\xd0\x2e\xf0\x6f\x02\x30\x02\x42\x20" + b"\x26\xe0\x6f\x02\x31\x03\x40\x9a\x0a\x02\x42\xf0\x37\x05\x2e\x5e\xf7\x10\x08" + b"\x12\x24\x1e\xf2\x80\x42\x83\x84\xf1\x7f\x0a\x25\x13\x30\x83\x42\x3b\x82\xf0" + b"\x6f\x00\x2e\x00\x2e\xd0\x2e\x12\x40\x52\x42\x00\x2e\x12\x40\x52\x42\x3e\x84" + b"\x00\x40\x40\x42\x7e\x82\xe1\x7f\xf2\x7f\x98\x2e\x6a\xd6\x21\x30\x23\x2e\x61" + b"\xf5\xeb\x2c\xe1\x6f" ) class BMI270: + ACCEL_SCALE = (2, 4, 8, 16) + GYRO_SCALE = (2000, 1000, 500, 250, 125) + ODR = (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, 100, 200, 400, 800, 1200) + def __init__( self, - bus, - cs=None, + i2c, address=_DEFAULT_ADDR, gyro_odr=100, gyro_scale=2000, accel_odr=100, accel_scale=4, + force_reset=False, bmm_magnet=None, ): - """Initializes Gyro and Accelerometer. - bus: IMU bus - address: I2C address (in I2C mode). - cs: SPI CS pin (in SPI mode). + """Initializes Device, Gyro and Accelerometer. + i2c: machine.I2C bus object + address: I2C address gyro_odr: (0.78, 1.5Hz, 3.1Hz, 6.25Hz, 12.5Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, 800Hz, 1600Hz) gyro_scale: (125dps, 250dps, 500dps, 1000dps, 2000dps) accel_odr: (0.78, 1.5Hz, 3.1Hz, 6.25Hz, 12.5Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, 800Hz, 1600Hz) accel_scale: (+/-2g, +/-4g, +/-8g, +-16g) + force_reset: Forces a reset and reinitialisation of the device + bmm_magnet: allows passing of a magnetometer device, not used in practice """ - self.bus = bus + self.bus = i2c self.bmm_magnet = bmm_magnet - self.cs = cs self.address = address - self._use_i2c = hasattr(self.bus, "readfrom_mem") - - ACCEL_SCALE = (2, 4, 8, 16) - GYRO_SCALE = (2000, 1000, 500, 250, 125) - ODR = (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, 100, 200, 400, 800, 1200) - - # Sanity checks - if not self._use_i2c: - raise ValueError("SPI mode is not supported") - if gyro_odr not in ODR: - raise ValueError("Invalid gyro sampling rate: %d" % gyro_odr) - if gyro_scale not in GYRO_SCALE: - raise ValueError("Invalid gyro scaling: %d" % gyro_scale) - if accel_odr not in ODR: - raise ValueError("Invalid accelerometer sampling rate: %d" % accel_odr) - if accel_scale not in ACCEL_SCALE: - raise ValueError("Invalid accelerometer scaling: %d" % accel_scale) + self.powersave = True + self.reset_flag = False + + # Self checks if self._read_reg(_CHIP_ID) != 0x24: raise OSError("No BMI270 device was found at address 0x%x" % (self.address)) - - # Perform initialization sequence. - # 0. Soft-reset + # check power on reset and error flags + needs_reset = False + if self._read_reg(_EVENT) & 0x01: # power on reset + needs_reset = True + if self._read_reg(_INTERNAL_STATUS) != 0x01: # init not ok + needs_reset = True + # reset and upload config as needed + if force_reset or needs_reset: + self._reset() + # apply sensor settings + self.sensor_settings(gyro_odr, gyro_scale, accel_odr, accel_scale) + # Allocate scratch buffers. + self._scratch = memoryview(array.array("h", [0, 0, 0])) + self._word = memoryview(array.array("h", [0])) + + def _reset(self): + """Soft reset the device and upload config""" + # This will leave the device in full power mode self._write_reg(_CMD, 0xB6) - time.sleep_ms(250) - - # 1. Disable power save mode. - self._write_reg(0x7C, 0x00) - time.sleep_ms(10) - - # 2. Prepare config load. - self._write_reg(0x59, 0x00) - - # 3. Load config data. - self._write_burst(0x5E, _CONFIG_DATA) - - # 4. Finish config load. - self._write_reg(0x59, 0x01) - - # 5. Check correct initialization status. - if not self._poll_reg(_STATUS, 0x01): - raise OSError("Init sequence failed") - - # 6. Configure the device in normal power mode + sleep_ms(50) + # Disable power save mode. + self._write_reg(_PWR_CONF, 0x00) + self.powersave = False + # Prepare config load. + self._write_reg(_INIT_CTRL, 0x00) + # Load config data. + self._write_burst(_CONFIG_DATA) + # Finish config load. + self._write_reg(_INIT_CTRL, 0x01) + sleep_ms(20) + # Check correct initialization status. + if not self._poll_reg(_INTERNAL_STATUS, 0x01): + raise OSError("Init configuration load failed") # FIFO Reset self._write_reg(_CMD, 0xB0) - # Enable accel, gyro and temperature data. - self._write_reg(0x7D, 0x0E) - # acc_filter_perf | acc_bwp normal mode | ODR - self._write_reg(0x40, 0xA | (ODR.index(accel_odr) + 1)) - # gyr_filter_perf | gyr_bwp normal mode | ODR - self._write_reg(0x42, 0xA | (ODR.index(gyro_odr) + 1)) # Disable adv_power_save | Enable fifo_self_wakeup. - self._write_reg(0x7C, 0x02) - - # Set accelerometer scale and range. - self.accel_scale = 32768 / accel_scale - self._write_reg(0x41, ACCEL_SCALE.index(accel_scale)) - - # Set gyroscope scale and range. - self.gyro_scale = 32768 / gyro_scale - self._write_reg(0x43, GYRO_SCALE.index(gyro_scale)) + self._write_reg(_PWR_CONF, 0x02) + # Enable accel, gyro and temperature data. + self._write_reg(_PWR_CTRL, 0x0E) + self.reset_flag = True - # Allocate scratch buffer and set scale. - self.scratch = memoryview(array.array("h", [0, 0, 0])) + # + # Device register functions (8 bit) + # def _read_reg(self, reg, size=1): + """Read a register, or set of registers, to a list""" buf = self.bus.readfrom_mem(self.address, reg, size) if size == 1: return int(buf[0]) return buf def _read_reg_into(self, reg, buf): + """Read register(s) into a bytearray""" self.bus.readfrom_mem_into(self.address, reg, buf) def _write_reg(self, reg, val): + """Write a single integer byte, or a bytearray, to register(s)""" if isinstance(val, int): val = bytes([val]) self.bus.writeto_mem(self.address, reg, val) - time.sleep_ms(1) + sleep_us(500 if self.powersave else 2) - def _write_burst(self, reg, data, chunk=16): + def _write_burst(self, data, chunk=16): + """Burst write config (bytearray) in 16b chunks, then the remainder""" self._write_reg(_INIT_ADDR_0, 0) self._write_reg(_INIT_ADDR_1, 0) for i in range(len(data) // chunk): offs = i * chunk - self._write_reg(reg, data[offs : offs + chunk]) + self._write_reg(_INIT_DATA, data[offs : offs + chunk]) init_addr = ((i + 1) * chunk) // 2 self._write_reg(_INIT_ADDR_0, (init_addr & 0x0F)) self._write_reg(_INIT_ADDR_1, (init_addr >> 4) & 0xFF) + remainder = len(data) % chunk + if remainder != 0: + offs = (len(data) // chunk) * chunk + self._write_reg(_INIT_DATA, data[offs : offs + remainder]) def _poll_reg(self, reg, mask, retry=10, delay=100): + """Wait for a register bit to become true""" for i in range(retry): if self._read_reg(reg) & mask: return True - time.sleep_ms(delay) + sleep_ms(delay) return False - def reset(self): - self._write_reg(_CMD, 0xB6) + def _set_bit(self, state, register, bit): + """Set a single bit's state in a device register""" + old = self._read_reg(register) + new = ((~(2**bit) & 0xFF) & old) + (state << bit) + self._write_reg(register, bytes([new])) + + # + # Base Functions for all configs + # + + def sensor_settings(self, gyro_odr=None, gyro_scale=None, accel_odr=None, accel_scale=None): + """ + Set sensor Output Data Rares (ODR) and Scale + - see init() for values + """ + if gyro_odr is not None: + if gyro_odr not in self.ODR: + raise ValueError("Invalid gyro sampling rate: %d" % gyro_odr) + self.gyro_odr = gyro_odr + if gyro_scale is not None: + if gyro_scale not in self.GYRO_SCALE: + raise ValueError("Invalid gyro scaling: %d" % gyro_scale) + self.gyro_scale = gyro_scale + if accel_odr is not None: + if accel_odr not in self.ODR: + raise ValueError("Invalid accelerometer sampling rate: %d" % accel_odr) + self.accel_odr = accel_odr + if accel_scale is not None: + if accel_scale not in self.ACCEL_SCALE: + raise ValueError("Invalid accelerometer scaling: %d" % accel_scale) + self.accel_scale = accel_scale + # Set gyroscope scale and range. + self._write_reg(_GYR_RANGE, self.GYRO_SCALE.index(self.gyro_scale)) + # gyr_filter_perf | gyr_bwp normal mode | ODR + self._write_reg(_GYR_CONF, 0xA | (self.ODR.index(self.gyro_odr) + 1)) + # Set accelerometer scale and range. + self._write_reg(_ACC_RANGE, self.ACCEL_SCALE.index(self.accel_scale)) + # acc_filter_perf | acc_bwp normal mode | ODR + self._write_reg(_ACC_CONF, 0xA | (self.ODR.index(self.accel_odr) + 1)) + # scale factors + self.accel_scale_factor = 32768 / self.accel_scale + self.gyro_scale_factor = 32768 / self.gyro_scale + + def power( + self, + powersave=None, + accel=None, + gyro=None, + acc_filter=None, + gyr_filter=None, + gyr_noise=None, + ): + """ + Set power modes; + - See base datasheet section 4.5 'Power Modes' (page 26) + - disabling the gyroscope produces greatest savings + - gesture detection features do not use the gyroscope + + powersave = device 'advanced power save' mode, default False + accel = enable/disable accellerometer, default True + gyro = enable/disable gyroscope, default True + acc_filter = powresave mode for accel filter, default True + gyr_filter = powresave mode for gyro filter, default True + gyr_noise = powersave mode for gyro noise filter, default False + """ + if powersave in (True, False): + self._set_bit(powersave, _PWR_CONF, 0) + self.powersave = powersave + if gyro in (True, False): + self._set_bit(gyro, _PWR_CTRL, 1) + if accel in (True, False): + self._set_bit(accel, _PWR_CTRL, 2) + if acc_filter in (True, False): + self._set_bit(acc_filter, _ACC_CONF, 7) + if gyr_filter in (True, False): + self._set_bit(gyr_filter, _GYR_CONF, 7) + if gyr_noise in (True, False): + self._set_bit(gyr_noise, _GYR_CONF, 6) def gyro(self): """Returns gyroscope vector in degrees/sec.""" - f = self.gyro_scale - self._read_reg_into(_DATA_14, self.scratch) - return (self.scratch[0] / f, self.scratch[1] / f, self.scratch[2] / f) + f = self.gyro_scale_factor + self._read_reg_into(_DATA_14, self._scratch) + return (self._scratch[0] / f, self._scratch[1] / f, self._scratch[2] / f) def accel(self): """Returns acceleration vector in gravity units (9.81m/s^2).""" - f = self.accel_scale - self._read_reg_into(_DATA_8, self.scratch) - return (self.scratch[0] / f, self.scratch[1] / f, self.scratch[2] / f) + f = self.accel_scale_factor + self._read_reg_into(_DATA_8, self._scratch) + return (self._scratch[0] / f, self._scratch[1] / f, self._scratch[2] / f) def magnet(self): - """Returns magnetometer vector.""" + """Returns magnetometer vector if a magnetomter attached and supplied.""" if self.bmm_magnet is not None: return self.bmm_magnet.magnet() return (0.0, 0.0, 0.0) + + def temperature(self): + """Returns temperature value in degrees C.""" + self._read_reg_into(_TEMP_REG, self._word) + return (self._word[0] / 512) + 23.0 diff --git a/micropython/drivers/imu/bmi270/examples/demo_imu.py b/micropython/drivers/imu/bmi270/examples/demo_imu.py new file mode 100644 index 000000000..842af725b --- /dev/null +++ b/micropython/drivers/imu/bmi270/examples/demo_imu.py @@ -0,0 +1,133 @@ +""" +Interactive demo for BMI270 bsic IMU data +- columnar output is easier to read and compare as device moves +- on the fly speed adjustment and pausing +- Numeric / Visual modes +- Powersave modes +""" + +from bmi270 import BMI270 +# or: +# from bmi270_legacy import BMI270_LEGACY as BMI270 + +from machine import I2C, Pin +from time import sleep_ms, ticks_ms, ticks_diff + +# This lets us sense user input in the repl console +from sys import stdin +from select import poll, POLLIN + +stdinpoll = poll() +stdinpoll.register(stdin, POLLIN) + +# Set up the I2C bus +# - adjust pins to suit your system +SYS_SDA = 31 +SYS_SCL = 32 +sys_i2c = I2C(0, sda=Pin(SYS_SDA), scl=Pin(SYS_SCL)) + +# The bmi270 is on the I2C bus +imu_address = 0x68 + +print("Init:", end="") +start = ticks_ms() + +# Init in I2C mode. +imu = BMI270(sys_i2c, address=imu_address) + +duration = ticks_diff(ticks_ms(), start) +print(" Completed in {}ms".format(duration)) +if imu.reset_flag: + print("Device was reset/re-initialised") + + +def show_help(): + print('"l" / "L" to enable / disable low power mode') + print('"b" to show visual bars instead of values,') + print('"+" / "-" to increase / decrease frequency,') + print('"p" to pause, "?" for help, "x" to exit.\n') + + +afmt = "> 5.1f" +gfmt = "> 4.0f" + + +def show_data(): + ms = ticks_ms() % 1000 + s = ticks_ms() // 1000 + m = s // 60 + print("{:02d}:{:02d}.{:03d}".format(m % 60, s % 60, ms), end="") + print(" | Accel: X={1:{0:}}, Y={2:{0:}}, Z={3:{0:}}".format(afmt, *imu.accel()), end="") + print(" | Gyro: X={1:{0:}}, Y={2:{0:}}, Z={3:{0:}}".format(gfmt, *imu.gyro()), end="") + print(" | Temp:{:> 5.1f}°".format(imu.temperature())) + + +def bar(val, scale, char): + v = int(val * scale) + start = min(0, max(-10, v)) + end = max(0, min(10, v)) + return "{}{}{}".format(" " * (10 + start), char * ((end - start) + 1), " " * (10 - end)) + + +afac = 8 +gfac = 0.05 + + +def show_bars(): + ms = ticks_ms() % 1000 + s = ticks_ms() // 1000 + m = s // 60 + print("{:02d}:{:02d}.{:03d}".format(m % 60, s % 60, ms), end="") + x, y, z = imu.accel() + print( + " | Accel: {} {} {}".format(bar(x, afac, "X"), bar(y, afac, "Y"), bar(z, afac, "Z")), + end="", + ) + x, y, z = imu.gyro() + print(" | Gyro: {} {} {}".format(bar(x, gfac, "x"), bar(y, gfac, "y"), bar(z, gfac, "z"))) + + +def data_loop(freq=float(8)): + "Simply loop showing imu data until repl input detected" + paused = False + bars = False + show_help() + start = ticks_ms() + while True: + cmd = "" + while ticks_diff(ticks_ms(), start) < (1000 / freq) and len(cmd) == 0: + while len(stdinpoll.poll(0)) > 0: + cmd += stdin.read(1) + sleep_ms(50) + if len(cmd) > 0: + if cmd[0] in ("x", "X"): + break + if cmd[0] in ("?", "h"): + show_help() + elif cmd[0] in ("p", "P"): + paused = not paused + print("Paused :: {}".format(paused)) + elif cmd[0] in ("-", "_"): + freq = freq / 2 + print("freq- ({} Hz)".format(freq)) + elif cmd[0] in ("+", "="): + freq = min(4096, freq * 2) + print("freq+ ({} Hz)".format(freq)) + elif cmd[0] in ("b", "B"): + bars = not bars + elif cmd[0] == "l": + imu.power(powersave=True) + print("Advanced PowerSave enabled") + elif cmd[0] == "L": + imu.power(powersave=False) + print("Advanced PowerSave disabled") + if not paused: + if bars: + show_bars() + else: + show_data() + start = ticks_ms() + + +# Run the main data loop +data_loop() diff --git a/micropython/drivers/imu/bmi270/manifest.py b/micropython/drivers/imu/bmi270/manifest.py index 2d89bfe9d..6c51c5b0b 100644 --- a/micropython/drivers/imu/bmi270/manifest.py +++ b/micropython/drivers/imu/bmi270/manifest.py @@ -1,2 +1,2 @@ -metadata(description="BOSCH BMI270 IMU driver.", version="1.0.0") +metadata(description="BOSCH BMI270 IMU driver.", version="2.0.0") module("bmi270.py", opt=3) diff --git a/micropython/drivers/imu/bmi270_legacy/README.md b/micropython/drivers/imu/bmi270_legacy/README.md new file mode 100644 index 000000000..42af4af1e --- /dev/null +++ b/micropython/drivers/imu/bmi270_legacy/README.md @@ -0,0 +1,521 @@ +------------ + License: 3-clause BSD, see https://opensource.org/licenses/BSD-3-Clause + + Homepage: https://codeberg.org/easytarget/bmi270-micropython + Datasheet: ./reference/bmi270-base-datasheet.pdf + +--------------------------- + +# Micropython driver for the BMI270 IMU with Legacy (Tablet/Phone) features + +## [`bmi270_legacy.py`](bmi270_legacy.py) +Homepage: [Here](https://codeberg.org/easytarget/bmi270-micropython) + +Original BMI270 driver on the micropython library had limited functionality and some issues with size and speed during init. + +This driver retains the original features: +* Setting Sensor Ranges and Output Data Rates at init +* Reading Accelerometer and Gyroscope reeadings + +Enhancements from original driver: +* Modifying Sensor Ranges and Output Data Rates (ODR) during use. +* Entering 'adavnced powersave mode' and enabling / disabling specific sensors and filter power modes. +* Device internal `temperature()` method has been added. + +Provides all the features of the `base` driver, but has been extended to use the '**legacy**' config provided by Bosch +* This config profile is optimised for Tablet and Phone devices (eg the Tab5 I am working with). +* It is considerably larger than the 'fast' config (8k vs 328b), and the driver itself is unavoidably large, but is still fast to load at init(). +* There are other Bosch configs targeting wearables, watches and security monitoring. + * It would be possible to use the legacy driver as a template and create variants for them. + +Working: +* Acceleration and Gyro readings +* Orientation, faceup/down, activity step counting and interrupts +* No-motion, any-motion, significant-motion, flat, hi and low G detection with interrupts +* Tap detection, settings and interrupt +* Step detection and count watermark interrupts +* Detailed options for orientation, motion and detectors +* Device temperature +* Interrupt pin mode setting and masking +* PowerSave mode settings + +Not Implemented: +* Fast data logging (FIFO mode) +* Gyro and acceleration calibration and advanced filtering +* Slave (accessory) devices; eg magnetometer +* OIS (image stabilisation) slave mode +* NVM settings memory +* A few other advanced/obscure things this chip can do + + + +## Install + +Install by copying `bmi270_legacy.py` to the root (or import path) of your device in your IDE. +* Or install with **MIP** + * `mip install https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/imu/bmi270/bmi270_legacy.py` + * run this on the device itself (when connected via WiFi/Network) +* You can also use mip via **mpremote**: + * `mpremote mip install https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/imu/bmi270/bmi270_legacy.py` + * run this on the commandline of your host PC/system + +## Demo and Example +* [`demo_legacy.py`](demo_legacy.py) : demonstrates the orientation and activity detection features of the **legacy** profile + * See the 'commands summary' at startup, `p` to pause + +### test.py: +```python +import time +from bmi270 import BMI270_LEGACY as BMI270 +from machine import Pin, I2C + +# Init in I2C mode, adjust pins as needed +imu = BMI270(I2C(1, scl=Pin(32), sda=Pin(31)) + +while (True): + print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) + print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) + print('Temperature: {:>4.1f}'.format(imu.temperature())) + print("") + time.sleep_ms(100) +``` + +## Reference +See the [BMI270](https://www.bosch-sensortec.com/products/motion-sensors/imus/bmi270/) documentation at the Bosch site. + +### Datasheets: +* Main datasheet with base feature config: [bmi270-base-datasheet.pdf](./reference/bmi270-base-datasheet.pdf) + * Detailed documentation for interrupt pin and power mode control is in this sheet +* Application note for FIFO mode (no features): [bmi270-fifo-appication-note.pdf](./reference/bmi270-fifo-appication-note.pdf) +* Application note for legacy features config [bmi270-legacy-appication-note.pdf](./reference/bmi270-legacy-appication-note.pdf) + +### Official reference API (C): +https://github.com/boschsensortec/BMI270_SensorAPI + +# Usage: + +## Classes; Objects and Attributes + +```python +class BMI270_LEGACY() +``` + +#### init() + +```python +def __init__(i2c, + address = 0x68, + gyro_odr = 100, + gyro_scale = 2000, + accel_odr = 100, + accel_scale = 4, + force_reset = False, + bmm_magnet = None) +``` + +Initializes Device, Gyro and Accelerometer. +* `i2c`: `machine.I2C()` bus object +* `address`: I2C address, default 0x68 +* `gyro_odr`: Hz (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, **100**, 200, 400, 800, 1600) +* `gyro_scale`: dps (125, 250, 500, 1000, **2000**) +* `accel_odr`: Hz (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, **100**, 200, 400, 800, 1600) +* `accel_scale`: g (+/-2, **+/-4**, +/-8, +/-16) +* `force_reset`: Forces a reset and reinitialisation of the device +* `bmm_magnet`: allows passing of a magnetometer device, provided for compatibility + +#### sensor\_settings() + +```python +def sensor_settings(gyro_odr=None, + gyro_scale=None, + accel_odr=None, + accel_scale=None) +``` + +Sets or modifies sensor Output Data Rates (ODR) and Scale +* Values are as specified during `init()` +* `None` indicates the setting should be left as-is + +#### power() + +```python +def power(powersave=None, + accel=None, + gyro=None, + acc_filter=None, + gyr_filter=None, + gyr_noise=None) +``` + +Set power modes; +* See `base` datasheet section 4.5 'Power Modes' (page 26) +* `None` indicates the setting should be left as-is + +Disabling the gyroscope produces greatest savings + +* `powersave` = device 'advanced power save' mode, default `False` +* `accel` = enable/disable accellerometer, default `True` +* `gyro` = enable/disable gyroscope, default `True` +* `acc_filter` = powresave mode for accel filter, default `True` +* `gyr_filter` = powresave mode for gyro filter, default `True` +* `gyr_noise` = powersave mode for gyro noise filter, default `False` + +Note that gesture detection features in the extended configs do not use the gyroscope + +#### gyro() + +```python +def gyro() +``` + +Returns gyroscope vector in degrees/sec. + +#### accel() + +```python +def accel() +``` + +Returns acceleration vector in gravity units (9.81m/s^2). + +#### magnet() + +```python +def magnet() +``` + +Compatibility method, generally unused: Returns magnetometer vector if a magnetomter has been attached and configured at init. + +#### temperature() + +```python +def temperature() +``` + +Returns temperature value in degrees C. This is the chip temperature, not environmental. + +### Interrupt Pin Control and Masking + +#### device\_interrupt\_mask() + +```python +def device_interrupt_mask(pin1=0b1000, pin2=0b1000) +``` + +Enable/disable device interrupts (4 bits) +* Default is to enable device error interrupts on both pins, but data ready and fifo full interrupts are suppressed + * `0` - fifo full + * `1` - fifo watermark + * `2` - data ready + * `3` - error + +#### feature\_interrupt\_mask() + +```python +def feature_interrupt_mask(pin1=0b11111111, pin2=0b11111111) +``` + +Enable/disable feature interrupts (8 bits) +* Default is to enable all feature interrupts on both interrupt output pins +* Enable/Disable features to control which interrupts will be flagged + * `0` - significant motion + * `1` - step activity + * `2` - high and low G + * `3` - tap + * `4` - flat + * `5` - no motion + * `6` - any motion + * `7` - orientation + +#### int1\_pin\_setup() + +```python +def int1_pin_setup(enable, active_low=True, pushpull=True) +``` + +Set interrupt pin 1 electrical characteristics +* Pin is disabled by default +* see datasheet for full description + +#### int2\_pin\_setup() + +```python +def int2_pin_setup(enable, active_low=True, pushpull=True) +``` + +Set interrupt pin 2 electrical characteristics +* Pin is disabled by default +* see datasheet for full description + +#### int\_pin\_latch() + +```python +def int_pin_latch(latching=False) +``` + +Set the interrupt non-latching / latching flag + +#### interrupts() + +```python +def interrupts() +``` + +Report all active interrupt flags and values as appropriate +* Possible feature interrupt flags are: + * `sig_motion`,`step_act`,`high_low_g`,`tap`,`flat`,`no_motion`,`any_motion`,`orientation` + * Note that some interrupts are combined (step/activity, hiG/loG, etc.) +* There are also device level interrupts: + * `fifo_full`,`fifo_watermark`,`error`,`aux_data_ready`,`gyro_data_ready`,`accel_data_ready` + * see the datasheet(s) for more. +* The attributes `FEATURE_INT_FLAGS` and `DEVICE_INT_FLAGS` reference these in the code + +Note: In order to be flagged here the Feature *must* be enabled (`*_enable()` methods below) *AND* it *must* be assigned to one of the two interrupt pins (`feature_interrupt_mask()` above). This is a device limitation. + +## Tap detection + +#### taps() + +```python +def taps() +``` + +Return last recorded tap. + +#### tap\_enable() + +```python +def tap_enable(*taps) +``` + +Enable the requested `taps` (list), and set `wait_timeout` true as needed + +#### tap\_settings() + +```python +def tap_settings(sense_threshold=9, maximum_duration=130, quiet_time=80, wait_timeout=False, axis=2) +``` + +Tap detection settings: +* `threshold`in units of ~78mg +* `duration` and `quiet_time` in units of 5ms +* `wait_timeout` guards against the 'first' tap of a double/triple tap causing an immediate interrupt +* `axis` is one of `0`(X), `1`(Y) or `2`(Z, default) +* see legacy application datasheet for full description + +## Motion Detection + +#### sigmotion\_enable() + +```python +def sigmotion_enable(enable=None) +``` + +Enable sigmotion detection as required and return status + +#### sigmotion\_settings() + +```python +def sigmotion_settings(block_time=250) +``` + +Signifigent motion settings: +* ` block_time` (trigger delay) in units of 20ms +* see legacy application datasheet for full description + +#### nomotion\_enable() + +```python +def nomotion_enable(enable=None) +``` + +Enable nomotion detection as required and return status + +#### nomotion\_settings() + +```python +def nomotion_settings(duration=5, threshold=144, axes=0b111) +``` + +No motion settings: +* `duration` in units of 20ms +* `threshold` in units of 0.5mg +* per-axis select, 3 bits; `x` = bit0, `y` = bit1, `z` = bit2 +* see legacy application datasheet for full description + +#### anymotion\_enable() + +```python +def anymotion_enable(enable=None) +``` + +Enable anymotion detection as required and return status + +#### anymotion\_settings() + +```python +def anymotion_settings(duration=5, threshold=170, axes=0b111) +``` + +Any motion detection settings: +* `duration` in units of 20ms +* `threshold` in units of 0.5mg +* per-axis select, 3 bits; `x` = bit0, `y` = bit1, `z` = bit2 +* see legacy application datasheet for full description + +#### high\_g\_enable() + +```python +def high_g_enable(enable=None) +``` + +Enable high-G detection as required and return status + +#### high\_g\_settings() + +```python +def high_g_settings(duration=4, hysteresis=1000, threshold=10000, axes=0b111) +``` + +High G settings: +* `duration` in units of 20ms +* per-axis select, 3 bits; `x` = bit0, `y` = bit1, `z` = bit2 +* see legacy application datasheet for full description + +#### low\_g\_enable() + +```python +def low_g_enable(enable=None) +``` + +Enable low-G detection as required and return status + +#### low\_g\_settings() + +```python +def low_g_settings(duration=0, hysteresis=256, threshold=512) +``` + +Low G settings: +* `duration` in units of 20ms +* see legacy application datasheet for full description + +## Orientation + +#### attributes + +```python +ORIENTATIONS = ['portrait_upright', 'landscape_left', 'portrait_upside_down', 'landscape_right']` +``` +Defines possible device Orientation values + +```python +FACEDIRECTIONS = ['face_down', 'face_up'] +``` +Defines the Face Up / Face Down states + +#### orientation() + +```python +def orientation() +``` + +Returns the orientation and face up/down status as strings +* See the attributes above + +#### orientation\_enable() + +```python +def orientation_enable(enable=None) +``` + +Enable orientation and faceupdown detection and return current status + +#### orientation\_settings() + +```python +def orientation_settings(mode=0, blocking=3, theta=40, hysteresis=128) +``` + +Orientation settings, see the application note! +* `mode` = `0` (normal), `1` (high asymmetric) or `2` (low asymmetric) +* see legacy application datasheet for full description + +#### flat\_enable() + +```python +def flat_enable(enable=None) +``` + +Enable as required and return status + +#### flat\_settings() + +```python +def flat_settings(hold_time=32, theta=8, hysteresis=9, blocking=2) +``` + +Flat detection settings: +* `hold_time` in units of 20ms +* see legacy application datasheet for full description + +## Step and Activity tracking + +#### step\_count() + +```python +def step_count() +``` + +Return step counter value as an integer + +#### Attribute: +```python +ACTIVITIES = ['still', 'walking', 'running', 'unknown'] +``` +Defines the possible activity states + +#### activity() + +```python +def activity() +``` + +Return current activity status as a string: +* See the `ACTIVITIES` attribute + +#### step\_count\_enable() + +```python +def step_count_enable(enable=None) +``` + +Enable step counting + +#### activity\_enable() + +```python +def activity_enable(enable=None) +``` + +Enable activity detection and return current status + +#### step\_count\_reset() + +```python +def step_count_reset() +``` + +Reset the step counter to zero + +#### step\_settings() + +```python +def step_settings(watermark=None) +``` + +Sets single step detection mode or a watermark for step counting +* If `watermark` is None, no step interrupts +* If `watermark` = 0 enable `step_detect` (interrupt on every step) +* If it is a positive integer disable `step_detect` and flag an interrupt every (`watermark` * 20) steps + diff --git a/micropython/drivers/imu/bmi270_legacy/bmi270_legacy.py b/micropython/drivers/imu/bmi270_legacy/bmi270_legacy.py new file mode 100644 index 000000000..487d34a76 --- /dev/null +++ b/micropython/drivers/imu/bmi270_legacy/bmi270_legacy.py @@ -0,0 +1,1132 @@ +""" +The MIT License (MIT) + +Copyright (c) 2023 Arduino SA + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Modified to use 'legacy' feature configuration +by Owen Carter, Feb 2026 + +Homepage: https://codeberg.org/easytarget/bmi270-micropython/ + +Basic example usage:: + + import time + from bmi270_legacy import BMI270_LEGACY + from machine import Pin, I2C + + # Init in I2C mode, adjust pins as needed + i2c = I2C(1, scl=Pin(32), sda=Pin(31)) + imu = BMI270_LEGACY(i2c) + + while (True): + print('Accelerometer: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.accel())) + print('Gyroscope: x:{:>6.3f} y:{:>6.3f} z:{:>6.3f}'.format(*imu.gyro())) + print('Temperature: {:>4.1f}'.format(imu.temperature())) + print("") + time.sleep_ms(100) + +""" + +import array +from time import sleep_ms, sleep_us + +from micropython import const + +# I2C address +_DEFAULT_ADDR = const(0x68) + +# Core device registers (8 bit) +_CHIP_ID = const(0x00) +_DATA_8 = const(0x0C) +_DATA_14 = const(0x12) +_EVENT = const(0x1B) +_INTERNAL_STATUS = const(0x21) +_TEMP_REG = const(0x22) +_ACC_CONF = const(0x40) +_ACC_RANGE = const(0x41) +_GYR_CONF = const(0x42) +_GYR_RANGE = const(0x43) +_INIT_CTRL = const(0x59) +_INIT_ADDR_0 = const(0x5B) +_INIT_ADDR_1 = const(0x5C) +_INIT_DATA = const(0x5E) +_PWR_CONF = const(0x7C) +_PWR_CTRL = const(0x7D) +_CMD = const(0x7E) + +# Feature related device registers (8 bit) +_INT_STATUS_0 = const(0x1C) +_INT_STATUS_1 = const(0x1D) +_STEP_OUT = const(0x1E) +_ORIENT_ACT = const(0x20) +_FEAT_PAGE = const(0x2F) +_FEATURES = const(0x30) +_INT1_IO_CTRL = const(0x53) +_INT2_IO_CTRL = const(0x54) +_INT_LATCH = const(0x55) +_INT1_MAP_FEAT = const(0x56) +_INT2_MAP_FEAT = const(0x57) +_INT_MAP_DATA = const(0x58) + + +# Feature registers (16 bit) +# (page, word, bit, length) +# 8 pages with 8 registers each +_F_SC_OUT_0_1 = const((0, 0, 0, 16)) +_F_SC_OUT_2_3 = const((0, 1, 0, 16)) +_F_ACT_OUT = const((0, 2, 0, 2)) +_F_ORIENT_OUT = const((0, 3, 0, 2)) +_F_FACEUP_OUT = const((0, 3, 2, 1)) +_F_HI_G_OUT = const((0, 3, 3, 4)) +_F_ANYMOTION_DURATION = const((1, 3, 0, 13)) +_F_ANYMOTION_AXES = const((1, 3, 13, 3)) +_F_ANYMOTION_THRESHOLD = const((1, 4, 0, 11)) +_F_ANYMOTION_EN = const((1, 4, 15, 1)) +_F_NOMOTION_DURATION = const((1, 5, 0, 13)) +_F_NOMOTION_AXES = const((1, 5, 13, 3)) +_F_NOMOTION_THRESHOLD = const((1, 6, 0, 11)) +_F_NOMOTION_EN = const((1, 6, 15, 1)) +_F_ORIENT_EN = const((2, 0, 0, 1)) +_F_UPDOWN_EN = const((2, 0, 1, 1)) +_F_ORIENT_MODE = const((2, 0, 2, 2)) +_F_ORIENT_BLOCKING = const((2, 0, 4, 2)) +_F_ORIENT_THETA = const((2, 0, 6, 6)) +_F_ORIENT_HYSTERESIS = const((2, 1, 0, 11)) +_F_HIGH_G_THRESHOLD = const((2, 2, 0, 15)) +_F_HIGH_G_HYSTERESIS = const((2, 3, 0, 12)) +_F_HIGH_G_AXES = const((2, 3, 12, 3)) +_F_HIGH_G_EN = const((2, 3, 15, 1)) +_F_HIGH_G_DURATION = const((2, 4, 0, 12)) +_F_LOW_G_THRESHOLD = const((2, 5, 0, 15)) +_F_LOW_G_HYSTERESIS = const((2, 6, 0, 12)) +_F_LOW_G_EN = const((2, 6, 12, 1)) +_F_LOW_G_DURATION = const((2, 7, 0, 12)) +_F_FLAT_EN = const((3, 0, 0, 1)) +_F_FLAT_THETA = const((3, 0, 1, 6)) +_F_FLAT_BLOCKING = const((3, 0, 7, 2)) +_F_FLAT_HYSTERESIS = const((3, 1, 0, 6)) +_F_FLAT_HOLD = const((3, 1, 6, 8)) +_F_SIGMOTION_BLOCK = const((3, 2, 0, 16)) +_F_SIGMOTION_EN = const((3, 7, 0, 1)) +_F_STEP_WATERMARK = const((4, 0, 0, 10)) +_F_STEP_COUNT_RST = const((4, 0, 10, 1)) +_F_STEP_DETECT_EN = const((4, 0, 11, 1)) +_F_STEP_COUNT_EN = const((4, 0, 12, 1)) +_F_ACTIVITY_EN = const((4, 0, 13, 1)) +_F_TAP_1_EN = const((5, 0, 0, 1)) +_F_TAP_2_EN = const((5, 0, 1, 1)) +_F_TAP_3_EN = const((5, 0, 2, 1)) +_F_TAP_SENS_THRES = const((5, 2, 0, 16)) +_F_TAP_MAX_GEST_DUR = const((5, 3, 0, 16)) +_F_TAP_QUIET_TIME = const((5, 7, 0, 16)) +_F_TAP_WAIT_TIMEOUT = const((6, 0, 0, 1)) +_F_TAP_AXIS = const((6, 2, 0, 2)) + +# Config for the legacy feature profile +_CONFIG_DATA = const( + b"\xc8\x2e\x00\x2e\x80\x2e\x3c\xb2\xc8\x2e\x00\x2e\x80\x2e\x9b\x03\x80\x2e\xa4" + b"\xb3\x80\x2e\xb8\x03\xc8\x2e\x00\x2e\x80\x2e\xc4\xb2\x50\x30\x21\x2e\x59\xf5" + b"\x10\x30\x21\x2e\x4a\xf1\x21\x2e\x6a\xf5\x80\x2e\xe5\x01\x00\x5d\x01\x00\x22" + b"\x00\x77\x00\x00\x10\x00\x10\xd1\x00\x45\xf5\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x45\x5a\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x5d\x00\x00\x88\x00\x05\xe0\xaa\x38\x05\xe0\x90" + b"\x30\x00\x00\x30\x0a\x80\x40\x10\x27\xe8\x73\x04\x30\x00\x02\x00\x01\x00\x30" + b"\x10\x0b\x09\x08\xfa\x00\x96\x00\x4b\x09\x11\x00\x11\x00\x02\x00\x00\x00\x22" + b"\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x06\x00\x09\x00" + b"\x82\x00\x06\x00\x06\x00\x08\x00\x50\x00\x00\x00\x4c\x04\x02\x00\x03\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" + b"\x00\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x32\x01\xe6\x78\x84\x00\x9c\x6c" + b"\x07\x00\x64\x75\xaa\x7e\x5f\x05\xbe\x0a\x5f\x05\x96\xe8\xef\x41\x01\x00\x0c" + b"\x00\x0c\x00\x4a\x00\xa0\x00\x00\x00\x0c\x00\xf0\x3c\x00\x01\x00\x00\x00\x00" + b"\x00\x00\x00\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x9a\x00\x9a" + b"\x01\x9f\x01\x88\x01\x96\x01\x84\x01\x00\x0c\xff\x0f\x00\x04\xc0\x00\x5b\xf5" + b"\x98\xf1\x7b\x01\x7a\x01\x1e\xf2\xfd\xf5\xfc\xf5\x76\x01\x7e\x01\x80\x00\xa0" + b"\x00\x5f\xff\xc8\x00\xcb\x00\xcb\x00\xd2\x00\x81\x01\x69\xf5\xe0\x00\x3f\xff" + b"\x19\xf4\x58\xf5\x66\xf5\xff\x00\x64\xf5\xc0\xf1\xba\xf1\xae\xf1\xa0\x00\xdf" + b"\x00\xa2\x01\xfa\x00\xfc\x00\xff\x3f\xff\xfb\x00\x38\x00\x30\xb3\x01\xba\x01" + b"\xbc\x01\xc2\x01\xca\x01\xff\x7f\xff\x01\x8e\x01\x74\xf7\x00\x40\x5f\x0f\xeb" + b"\x00\x7f\xff\xc2\xf5\x68\xf7\x59\x0f\x4d\x0f\x53\x0f\x72\x0f\x58\xf7\x5b\xf7" + b"\x75\x0f\x00\x80\x86\x00\x64\x0f\x77\x0f\xb3\xf1\x71\x0f\x6c\xf7\xb9\xf1\xc6" + b"\xf1\x00\xe0\x00\xff\xd1\xf5\x79\x0f\x7c\x0f\xff\x03\x00\xfc\xf0\x3f\x8b\x00" + b"\x90\x00\x95\x00\x92\x00\x98\x00\x8d\x00\xa2\x00\xb9\x00\x2d\xf5\xca\xf5\x7c" + b"\x01\x20\xf2\xdb\x01\x00\x08\x00\xf8\xe8\x03\x01\x80\xb8\x7e\xe1\x7a\xf0\x00" + b"\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\xa9\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x1a\x24\x22\x00\x80\x2e\x00\xb0\x1d\x52\x00\x2e\x60\x40\x41\x40\x0d\xbc" + b"\x98\xbc\xc0\x2e\x01\x0a\x0f\xb8\x1f\x52\x53\x3c\x52\x40\x40\x40\x4b\x00\x82" + b"\x16\x26\xb9\x01\xb8\x41\x40\x10\x08\x97\xb8\x01\x08\xc0\x2e\x11\x30\x01\x08" + b"\x43\x86\x25\x40\x04\x40\xd8\xbe\x2c\x0b\x22\x11\x54\x42\x03\x80\x4b\x0e\xf6" + b"\x2f\xb8\x2e\x21\x50\x10\x50\x23\x52\x05\x2e\xd5\x00\xfb\x7f\x00\x2e\x13\x40" + b"\x93\x42\x41\x0e\xfb\x2f\x98\x2e\x91\x03\x98\x2e\x87\xcf\x01\x2e\x70\x01\x00" + b"\xb2\xfb\x6f\x0b\x2f\x01\x2e\x69\xf7\xb1\x3f\x01\x08\x01\x30\xf0\x5f\x23\x2e" + b"\x70\x01\x21\x2e\x69\xf7\x80\x2e\x2e\x02\xf0\x5f\xb8\x2e\x03\x2e\xfc\xf5\x01" + b"\x2e\xc0\xf8\x25\x54\x27\x56\x82\x08\x0b\x2e\x69\xf7\xcb\x0a\x29\x58\x80\x90" + b"\xdd\xbe\x4c\x08\x5f\xb9\x59\x22\x80\x90\x07\x2f\x03\x34\xc3\x08\xf2\x3a\x0a" + b"\x08\x02\x35\xc0\x90\x4a\x0a\x48\x22\xc0\x2e\x23\x2e\xfc\xf5\x01\x2e\x7e\x01" + b"\x01\x8a\xf0\x50\x50\x41\x1a\x25\x03\xbd\x71\x82\x02\xbe\x2f\xb9\x42\x42\x81" + b"\xbd\x45\x41\x4f\xba\xf1\x7f\xbf\xb8\x0f\xb8\x5c\xb9\x24\x7f\x00\xb2\x31\x7f" + b"\xeb\x7f\xd2\x7f\x08\x2f\x10\x6f\x00\x90\x0b\x2f\x20\x6f\x00\x90\x08\x2f\x30" + b"\x6f\x00\x90\x05\x2f\x01\x30\x23\x2e\x7d\x00\xd0\x6f\x98\x2e\x95\xcf\x05\x2e" + b"\x7d\x00\x80\x90\x00\x30\x2b\x52\x2f\x56\x07\x2f\x2b\x58\x00\x2e\x10\x43\x63" + b"\x0e\xfc\x2f\x81\x84\x25\x2e\x7d\x00\x09\x2e\x7e\x01\x02\x85\x13\x41\x04\x41" + b"\xb1\xbd\xb1\xb9\x44\xbe\x82\x40\x44\xba\x24\xbd\xb3\x7f\x5c\x05\x24\xb9\x01" + b"\x56\x2d\x58\x92\x7f\xa5\x7f\x80\x7f\xc0\x7f\x73\x7f\x64\x7f\x00\x2e\xf2\x6f" + b"\x40\x7f\x51\x7f\x00\x2e\x90\x40\xf2\x7f\x00\x90\x04\x2f\x51\x6f\x00\x30\x40" + b"\x42\x45\x2c\x62\x6f\xc1\x40\x98\x2e\x74\xc0\x51\x6f\x00\x2e\x43\x40\xc0\xb2" + b"\x2d\x2f\x62\x6f\xa5\x6f\x84\x40\xc5\x0e\x07\x2f\x75\x6f\x10\x30\x45\x41\x40" + b"\xa1\x05\x30\x05\x22\x20\x1a\x02\x2f\x00\x30\x40\x42\x2c\x2d\x10\x30\x18\x28" + b"\x93\x6f\x40\x42\xc3\x0e\x25\x2f\xc0\x6f\x00\x90\x22\x2f\x45\x6f\x10\x30\xc5" + b"\x14\x46\xbf\xb3\xbf\xb7\x0b\x2b\x58\x65\x01\x2f\x5e\x0b\x30\x25\x1a\x00\x2f" + b"\x0b\x43\x01\x89\x2d\x2e\x79\x01\x67\x0e\xf7\x2f\x80\x7f\xc3\x7f\x0e\x2d\xb2" + b"\x6f\xc2\x0e\x08\x2f\x10\x30\x40\x42\x02\x30\x74\x6f\x63\x6f\x04\x41\x00\xa1" + b"\x02\x22\xc0\x42\x00\x2e\x62\x6f\x40\x6f\x73\x6f\x01\x80\xc1\x86\x81\x84\x41" + b"\x82\x03\xa2\x62\x7f\x73\x7f\xa4\x2f\xeb\x6f\xd0\x6f\x81\x6f\x10\x5f\x80\x2e" + b"\x95\xcf\x03\x2e\x7f\x01\x41\x84\x50\x50\x90\x40\x82\x40\x83\xbd\xbf\xb9\x2c" + b"\xba\xfb\x7f\xc0\xb2\xe4\x7f\x0b\x30\x36\x2f\x07\x2e\x7e\x00\xc0\x90\x04\x2f" + b"\xc1\x86\x27\x2e\x7e\x00\x37\x2e\x7f\x00\xa4\xbd\x04\xbd\x34\xb8\x41\x40\x91" + b"\xbc\x91\xb9\xb3\x7f\x24\xb9\x09\x52\xc0\x7f\xd2\x7f\x98\x2e\xb3\xc0\xd1\x6f" + b"\xb2\x6f\x51\x28\x41\x0f\x11\x30\x0d\x2f\xc2\x0e\x07\x2e\x7f\x00\x19\x28\x04" + b"\x2f\xc0\xa6\x04\x2f\x21\x2e\x7f\x00\x02\x2d\x21\x2e\x7f\x00\x04\x2c\x02\x30" + b"\x02\x30\x25\x2e\x7f\x00\xc0\x6f\x07\x2e\x7f\x00\x58\x0f\xfb\x6f\xe0\x6f\xb0" + b"\x5f\x4a\x22\x80\x2e\x95\xcf\xe0\x6f\x01\x30\x98\x2e\x95\xcf\x00\x30\x21\x2e" + b"\x7e\x00\xfb\x6f\xb0\x5f\xb8\x2e\x21\x2e\x59\xf5\x10\x30\xc0\x2e\x21\x2e\x4a" + b"\xf1\x80\x2e\x00\xc1\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9a\x01" + b"\x34\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x03\x2e\xd5\x00\x16\xb8\x02\x34\x4a\x0c\x21\x2e\x2d\xf5\xc0\x2e\x23" + b"\x2e\xd5\x00\x20\x50\xf6\x7f\xe7\x7f\x00\x2e\x35\x5c\x00\x2e\x87\x41\xff\xbf" + b"\xff\xbb\xc0\x91\x02\x2f\x37\x30\x2f\x2e\x69\xf5\xb8\x8f\x06\x32\xc7\x41\xfe" + b"\x09\xc0\xb3\x04\x2f\x17\x30\x2f\x2e\x95\x01\x2d\x2e\x61\xf5\xf6\x6f\xe7\x6f" + b"\xe0\x5f\xc8\x2e\x30\x50\xe5\x7f\xf6\x7f\xd7\x7f\x00\x2e\x35\x5a\x00\x2e\x46" + b"\x41\x6f\xbf\x6f\xbb\x80\x91\x02\x2f\x36\x30\x2d\x2e\x69\xf5\x46\x30\x0f\x2e" + b"\xa4\xf1\xbe\x09\x77\x8b\x80\xb3\x06\x2f\x0d\x2e\x6b\x01\x84\xaf\x02\x2f\x16" + b"\x30\x2d\x2e\xde\x00\x86\x30\x46\x43\x00\x2e\xf6\x6f\xe5\x6f\xd7\x6f\xd0\x5f" + b"\xc8\x2e\x10\x50\xfb\x7f\x98\x2e\x56\xc7\x98\x2e\x49\xc3\x02\x30\x30\x30\x11" + b"\x30\xfb\x6f\xf0\x5f\x25\x2e\x7e\x00\x25\x2e\xce\x00\x21\x2e\xdf\x00\x23\x2e" + b"\x7c\x00\x23\x2e\xfe\x00\x21\x2e\x78\x01\xb8\x2e\x11\x30\x81\x08\x01\x2e\x6a" + b"\xf7\x71\x3f\x23\xbd\x01\x08\x02\x0a\xc0\x2e\x21\x2e\x6a\xf7\x40\x50\x98\x2e" + b"\xbd\x0e\x50\x32\x98\x2e\x48\x03\x00\x30\xf0\x7f\xe0\x7f\x21\x2e\x69\xf5\x00" + b"\x2e\x00\x2e\xd0\x2e\x00\x2e\x01\x80\x08\xa2\xfb\x2f\x05\x2e\xa8\x00\x2f\xbc" + b"\xae\xbc\xad\xbd\x0f\xb8\x9f\xb8\x01\x0a\x05\x2e\x93\x00\xbf\xb9\xaf\xb8\x03" + b"\x0a\x01\x0a\x21\x2e\x7a\x00\x98\x2e\xeb\xb5\x03\x2e\x9d\x01\x21\x2e\x7b\x00" + b"\x40\xb2\x1a\x2f\x05\x2e\x8a\x00\x01\x52\x98\x2e\xc7\xc1\x03\x2e\xa8\x00\x9c" + b"\xbc\x9f\xb8\x23\x2e\xdd\x00\x40\x90\xf0\x7f\x03\x2f\x01\x52\x98\x2e\x4c\xb6" + b"\x03\x2d\x03\x52\x98\x2e\x4c\xb6\xe0\x7f\x98\x2e\x4b\x02\x10\x30\x21\x2e\x7c" + b"\x00\x01\x2e\x99\x01\x00\xb2\x07\x2f\x01\x2e\x7b\x00\x00\xb2\x03\x2f\x05\x50" + b"\x07\x52\x98\x2e\x07\xcc\x01\x2e\x87\x01\x00\xb2\x35\x2f\x05\x2e\x8a\x00\x09" + b"\x52\x98\x2e\xc7\xc1\x03\x2e\x93\x01\x40\xb2\xf0\x7f\x08\x2f\x01\x2e\x7b\x00" + b"\x00\xb2\x04\x2f\x00\x30\x21\x2e\x93\x01\x98\x2e\x88\xb5\x01\x2e\x79\x00\x00" + b"\xb2\x1e\x2f\x98\x2e\x78\xb4\x98\x2e\x49\xb5\x09\x50\x98\x2e\x4d\xc3\x09\x50" + b"\x98\x2e\x5a\xc7\x98\x2e\xf9\x02\x81\x3f\x01\x2e\x79\x01\x01\x08\x09\x52\xd0" + b"\x7f\x21\x2e\x79\x01\x98\x2e\xff\xc5\xd1\x6f\x08\x0a\x21\x2e\x79\x01\x98\x2e" + b"\xc3\xb1\x10\x30\x21\x2e\x7c\x00\x01\x2e\x95\x01\x00\xb2\x04\x2f\x98\x2e\x2e" + b"\x02\x00\x30\x21\x2e\x95\x01\x01\x2e\x6b\x01\x04\xae\x0b\x2f\x01\x2e\x87\x01" + b"\x00\xb2\x07\x2f\x09\x52\x98\x2e\x74\x0e\x00\xb2\x02\x2f\x10\x30\x21\x2e\xd7" + b"\x00\x01\x2e\xd7\x00\x00\x90\x90\x2e\x72\xb1\x01\x2e\x6e\x01\x00\xb2\x04\x2f" + b"\x98\x2e\x15\x0e\x00\x30\x21\x2e\xde\x00\x01\x2e\xde\x00\x00\xb2\x12\x2f\x01" + b"\x2e\x6b\x01\x00\x90\x02\x2f\x98\x2e\x05\x0e\x09\x2d\x98\x2e\x5d\x0d\x01\x2e" + b"\x6b\x01\x04\x90\x02\x2f\x50\x32\x98\x2e\x48\x03\x00\x30\x21\x2e\xde\x00\x01" + b"\x2e\xd6\x00\x00\xb2\x90\x2e\x8a\xb1\x01\x2e\xd6\x00\x01\x31\x01\x08\x00\xb2" + b"\x04\x2f\x98\x2e\x47\xcb\x10\x30\x21\x2e\x7c\x00\x81\x30\x01\x2e\xd6\x00\x01" + b"\x08\x00\xb2\x61\x2f\x03\x2e\x89\x00\x01\x2e\x6b\x01\x98\xbc\x98\xb8\x05\xb2" + b"\x13\x58\x23\x2f\x07\x90\x0d\x54\x00\x30\x37\x2f\x15\x41\x04\x41\xdc\xbe\x44" + b"\xbe\xdc\xba\x2c\x01\x61\x00\x13\x56\x4a\x0f\x0c\x2f\xd1\x42\x94\xb8\xc1\x42" + b"\x11\x30\x05\x2e\x6a\xf7\x2c\xbd\x2f\xb9\x80\xb2\x08\x22\x98\x2e\xf5\x03\x21" + b"\x2d\x61\x30\x23\x2e\x6b\x01\x98\x2e\xf5\x03\x00\x30\x21\x2e\x5a\xf5\x18\x2d" + b"\xd1\x7f\x50\x30\x98\x2e\x48\x03\x13\x52\x0b\x50\x50\x42\x70\x30\x11\x54\x42" + b"\x42\x7e\x82\xd2\x6f\x80\xb2\x42\x42\x05\x2f\x21\x2e\x6b\x01\x10\x30\x98\x2e" + b"\xf5\x03\x03\x2d\x60\x30\x21\x2e\x6b\x01\x01\x2e\x6b\x01\x06\x90\x18\x2f\x01" + b"\x2e\x78\x00\x0f\x54\x0b\x52\xd0\x7f\x98\x2e\x7a\xc1\xd1\x6f\x08\x1a\x40\x30" + b"\x08\x2f\x21\x2e\x6b\x01\x20\x30\x98\x2e\x24\xb6\x50\x32\x98\x2e\x48\x03\x05" + b"\x2d\x98\x2e\x1e\x0e\x00\x30\x21\x2e\x6b\x01\x41\x30\x01\x2e\xd6\x00\x01\x08" + b"\x00\xb2\x26\x2f\x01\x2e\x71\x01\x00\xb2\x0d\x2f\x01\x90\x1d\x2f\x05\x2e\xbf" + b"\x00\x07\x2e\x72\x01\xa1\x32\x17\x58\x98\x2e\xae\xb7\x00\x30\x21\x2e\x71\x01" + b"\x12\x2d\x15\x58\xa3\x32\x10\x41\x11\x41\x18\xb9\x04\x41\x98\xbc\x41\x0a\x94" + b"\x0a\x98\x2e\xa0\xb7\x11\x30\x21\x2e\x72\x01\x21\x2e\x7b\x01\x23\x2e\x71\x01" + b"\x10\x30\x21\x2e\x7c\x00\x00\x30\x21\x2e\xd6\x00\x18\x2d\x01\x2e\x6b\x01\x03" + b"\xaa\x01\x2f\x98\x2e\x2b\x0e\x01\x2e\x6b\x01\x3f\x80\x03\xa2\x01\x2f\x00\x2e" + b"\x02\x2d\x98\x2e\x41\x0e\x30\x30\x98\x2e\x38\xb6\x00\x30\x21\x2e\xd7\x00\x50" + b"\x32\x98\x2e\x48\x03\x01\x2e\x7c\x00\x00\xb2\x29\x2f\x98\x2e\xf5\xcb\x03\x2e" + b"\x6c\x01\x19\x54\x41\x0a\xbc\x84\x23\x2e\x7a\x01\x83\x80\x74\x30\x23\x40\x05" + b"\x40\x1b\x50\x81\x40\xdc\x08\x18\xb9\x11\x42\xd3\xbe\xe4\x6f\xc5\xbc\xdd\x0a" + b"\x12\x42\x59\x0a\x11\x42\xd0\x7f\xf1\x31\x00\x40\xf2\x6f\x25\xbd\x01\x08\x02" + b"\x0a\xc0\x7f\x98\x2e\xa8\xcf\x06\xbc\xc1\x6f\xd2\x6f\x08\x0a\x80\x42\x98\x2e" + b"\x0c\x02\x00\x30\x21\x2e\x9d\x01\x21\x2e\x99\x01\x21\x2e\x7c\x00\x21\x2e\x87" + b"\x01\x80\x2e\x09\xb0\x01\x2e\x80\x01\x01\x40\x90\x50\x9f\xbd\x13\xbd\xbf\xb9" + b"\x2c\xb9\xfb\x7f\xe2\x7f\xc0\xb2\x0b\x30\x62\x2f\x07\x2e\xce\x00\x31\x54\xc0" + b"\x90\x0b\x2f\x9b\x42\xc1\x86\x9b\x42\x27\x2e\xce\x00\x37\x2e\xcf\x00\x37\x2e" + b"\xd0\x00\x37\x2e\xd1\x00\x8b\x42\x01\x84\x17\xbc\x83\x40\x32\xbd\x99\xbc\x9a" + b"\xba\xba\xbd\x0e\xb8\x28\xb9\xba\xb9\x09\x58\x1a\x25\x0d\x2e\xcf\x00\x77\x82" + b"\x85\x7f\xa6\x7f\x74\x7f\x93\x7f\xd2\x7f\xc0\x7f\x98\x2e\xd1\xc3\x03\x2e\xcf" + b"\x00\x08\x1a\xb0\x7f\x01\x30\x01\x2f\x23\x2e\xd1\x00\x01\x2e\xd1\x00\xd1\x6f" + b"\x41\x0e\x14\x2f\xc1\x6f\x40\xb2\x0b\x2f\x43\xb2\x09\x2f\x09\x54\x31\x56\x98" + b"\x2e\x0b\xc4\x00\x90\x06\x2f\xb1\x6f\x23\x2e\xd0\x00\x03\x2d\xb1\x6f\x23\x2e" + b"\xd0\x00\xd1\x6f\x23\x2e\xd1\x00\x03\x2e\xd1\x00\x41\x82\x23\x2e\xd1\x00\x09" + b"\x50\x31\x52\x12\x40\x52\x42\x00\x2e\x12\x40\x52\x42\x00\x2e\x00\x40\x40\x42" + b"\x00\x2e\x03\x2e\xd0\x00\xe0\x6f\x98\x2e\x95\xcf\xb1\x6f\x23\x2e\xcf\x00\x06" + b"\x2d\x37\x2e\xce\x00\xe0\x6f\x01\x30\x98\x2e\x95\xcf\xfb\x6f\x70\x5f\xb8\x2e" + b"\xd0\x50\x80\x7f\x91\x7f\xd7\x7f\xc5\x7f\xb3\x7f\xa2\x7f\xe4\x7f\xf6\x7f\x7b" + b"\x7f\x00\x2e\x35\x50\x00\x2e\x01\x40\x9f\xbc\x9f\xb8\x40\x90\x02\x2f\x31\x30" + b"\x23\x2e\x69\xf5\x38\x82\x61\x7f\x20\x30\x41\x40\x23\x2e\xd6\x00\x03\x2e\xd6" + b"\x00\x08\x08\x00\xb2\x11\x2f\x33\x50\x1a\x25\x12\x40\x32\x7f\x73\x82\x12\x40" + b"\x42\x7f\x00\x2e\x00\x40\x50\x7f\x98\x2e\x6a\xd6\x01\x2e\x61\xf7\x01\x31\x01" + b"\x0a\x21\x2e\x61\xf7\x80\x30\x03\x2e\xd6\x00\x08\x08\x00\xb2\x40\x2f\x03\x2e" + b"\x89\x00\x17\xbc\x96\xbc\x0f\xb8\x9f\xb8\x40\x90\x21\x2e\x6f\x01\x10\x30\x01" + b"\x30\x2a\x2f\x03\x2e\x6b\x01\x44\xb2\x05\x2f\x47\xb2\x00\x30\x2d\x2f\x21\x2e" + b"\xd6\x00\x2b\x2d\x03\x2e\xfd\xf5\x9e\xbc\x9f\xb8\x40\x90\x14\x2f\x03\x2e\xfc" + b"\xf5\x99\xbc\x9f\xb8\x40\x90\x0e\x2f\x03\x2e\x49\xf1\x37\x54\x4a\x08\x40\x90" + b"\x08\x2f\x98\x2e\xe9\x01\x00\xb2\x10\x30\x03\x2f\x50\x30\x21\x2e\x6b\x01\x10" + b"\x2d\x98\x2e\x24\xb6\x00\x30\x21\x2e\xd6\x00\x0a\x2d\x05\x2e\x69\xf7\x2d\xbd" + b"\x2f\xb9\x80\xb2\x01\x2f\x21\x2e\xd7\x00\x23\x2e\xd6\x00\x60\x6f\xe1\x31\x01" + b"\x42\x00\x2e\xf6\x6f\xe4\x6f\x80\x6f\x91\x6f\xa2\x6f\xb3\x6f\xc5\x6f\xd7\x6f" + b"\x7b\x6f\x30\x5f\xc8\x2e\xa0\x50\x82\x7f\x90\x7f\xd7\x7f\xc5\x7f\xb3\x7f\xa1" + b"\x7f\xe4\x7f\xf6\x7f\x7b\x7f\x00\x2e\x35\x54\x00\x2e\x80\x40\x0f\xbc\x0f\xb8" + b"\x00\x90\x02\x2f\x30\x30\x21\x2e\x69\xf5\xb7\x84\x62\x7f\x98\x2e\xe9\x01\x00" + b"\xb2\x90\x2e\x7f\xb3\x05\x2e\xa0\x00\x03\x2e\x8c\x00\x01\x2e\x8e\x00\xa3\xbd" + b"\x9f\xb8\x0f\xb8\x08\x0a\xbf\xb9\xa4\xbc\x03\x0a\x22\xbe\x9f\xb8\x07\x2e\x96" + b"\x00\x41\x0a\x4f\xba\x05\x2e\x98\x00\xb3\xbd\x0c\x0b\x01\x2e\x90\x00\xbf\xb9" + b"\x2f\xbd\x03\x2e\x9f\x00\x2f\xb9\x0f\xbc\xe3\x0a\x0f\xb8\x9f\xbc\x9a\x0a\x9f" + b"\xb8\x90\x0a\x91\x0a\x25\x2e\x79\x00\x05\x2e\xc1\xf5\x2e\xbd\x2e\xb9\x01\x2e" + b"\x7b\x00\x31\x30\x8a\x04\x00\x90\x0b\x2f\x01\x2e\x6b\x01\x04\xa2\x07\x2f\x01" + b"\x2e\x79\x00\x00\x90\x03\x2f\x01\x2e\xd9\x00\x00\xb2\x19\x2f\x3b\x50\x09\x52" + b"\x98\x2e\x01\x02\x05\x2e\xd8\x00\x25\x2e\x87\x01\x05\x2e\xd8\x00\x80\x90\x02" + b"\x2f\x12\x30\x25\x2e\xd8\x00\x01\x2e\xd9\x00\x00\xb2\x10\x30\x05\x2e\x79\x00" + b"\x01\x2f\x21\x2e\x79\x00\x25\x2e\xd9\x00\x98\x2e\xf2\x01\x00\xb2\x22\x30\x21" + b"\x30\x03\x2f\x01\x2e\x7b\x00\x00\x90\x05\x2f\x01\x2e\x7a\x00\x00\x90\x30\x30" + b"\x01\x30\x41\x22\x01\x2e\x94\x01\x08\x1a\x0e\x2f\x23\x2e\x94\x01\x33\x30\x3d" + b"\x50\x0b\x09\x01\x40\x39\x56\x46\xbe\x4b\x08\x4c\x0a\x01\x42\x0a\x80\x25\x52" + b"\x01\x42\x00\x2e\x01\x2e\x79\x00\x00\xb2\x1f\x2f\x03\x2e\xc0\xf5\xf0\x30\x48" + b"\x08\x47\xaa\x74\x30\x07\x2e\xdc\x00\x61\x22\x4b\x1a\x05\x2f\x07\x2e\x66\xf5" + b"\xbf\xbd\xbf\xb9\xc0\x90\x0b\x2f\x3f\x56\x04\x30\xd4\x42\xd2\x42\x81\x04\x24" + b"\xbd\xfe\x80\x81\x84\xc4\x42\x23\x2e\xdc\x00\x02\x42\x02\x32\x25\x2e\x62\xf5" + b"\x05\x2e\x6d\x01\x81\x80\x03\x2e\x8a\x00\x21\x2e\x6d\x01\x0f\x56\x62\x6f\x4b" + b"\x08\x00\x31\x80\x42\x40\xb2\x0b\x2f\xf2\x3e\x01\x2e\xca\xf5\x82\x08\x25\x2e" + b"\xca\xf5\x05\x2e\x59\xf5\xe0\x3f\x90\x08\x25\x2e\x59\xf5\xf6\x6f\xe4\x6f\xa1" + b"\x6f\xb3\x6f\xc5\x6f\xd7\x6f\x7b\x6f\x82\x6f\x90\x6f\x60\x5f\xc8\x2e\xc0\x50" + b"\x80\x7f\x92\x7f\xd5\x7f\xc4\x7f\xb3\x7f\xa1\x7f\xe7\x7f\xf6\x7f\x7b\x7f\x00" + b"\x2e\x35\x50\x00\x2e\x02\x40\x2f\xbd\x2f\xb9\x80\x90\x02\x2f\x32\x30\x25\x2e" + b"\x69\xf5\x37\x80\x00\x2e\x00\x40\x60\x7f\x98\x2e\xe9\x01\x0b\x30\x5b\x7f\x40" + b"\x7f\x02\x32\x64\x6f\x25\x52\x43\x56\x0b\x30\x80\x2e\x65\xb4\x62\x09\x40\xb3" + b"\x0b\x2f\x00\xb2\x03\x2f\x0b\x2e\x79\x00\x40\x91\x03\x2f\xc2\x8a\x25\x2e\x64" + b"\xf5\x4b\x43\x25\x2e\x60\xf5\x21\x09\x00\xb3\x79\x2f\x98\x2e\xf2\x01\x00\xb2" + b"\x06\x2f\x01\x2e\x7b\x00\x00\xb2\x02\x2f\x40\x6f\x00\x90\x0a\x2f\x01\x2e\xdb" + b"\x00\x00\x90\x19\x2f\x10\x30\x21\x2e\xdb\x00\x00\x30\x98\x2e\x0d\xb6\x13\x2d" + b"\x01\x2e\xc3\xf5\x0c\xbc\x0f\xb8\x12\x30\x10\x04\x03\xb0\x26\x25\x45\x50\x07" + b"\x52\x98\x2e\x01\x02\x10\x30\x21\x2e\x99\x01\x02\x30\x50\x7f\x25\x2e\xdb\x00" + b"\x40\x6f\x00\xb2\x03\x2f\x05\x2e\x7a\x00\x80\x90\x0e\x2f\x05\x2e\xda\x00\x80" + b"\x90\x36\x2f\x11\x30\x02\x30\x23\x2e\xda\x00\x23\x2e\x74\x01\x25\x2e\x7d\x00" + b"\x25\x2e\x9e\x01\x2c\x2d\x05\x2e\x9e\x01\x81\x82\x23\x2e\x9e\x01\x12\x30\x4a" + b"\x08\x40\xb2\x05\x2f\x03\x2e\x58\xf5\x98\xbc\x9e\xb8\x43\x90\x1a\x2f\x01\x2e" + b"\xc1\xf5\x0e\xbd\x30\x30\x2e\xb9\x82\x04\x01\x52\x47\x50\x62\x7f\x98\x2e\x01" + b"\x02\x01\x2e\xdd\x00\x01\x90\x49\x50\x03\x52\x02\x2f\x62\x6f\x98\x2e\x01\x02" + b"\x12\x30\x25\x2e\x9d\x01\x00\x30\x21\x2e\xda\x00\x40\x6f\x52\x7f\x00\x2e\x52" + b"\x6f\x80\x90\x05\x2f\x02\x30\x25\x2e\x94\x01\x25\x54\x25\x2e\x64\xf5\x25\x52" + b"\x23\x2e\x60\xf5\x02\x32\x43\x56\x0b\x30\x00\x90\x0b\x2f\x37\x2e\xd8\x00\x41" + b"\x50\x21\x2e\x5a\xf2\x98\x2e\xdd\x03\x40\x6f\x02\x32\x25\x52\x43\x56\x0b\x30" + b"\x09\x2e\x60\xf5\x62\x09\x40\x91\x90\x2e\xc9\xb3\x61\x09\x40\x91\x90\x2e\xc9" + b"\xb3\x80\x6f\x92\x6f\xa1\x6f\xb3\x6f\xc4\x6f\xd5\x6f\x7b\x6f\xf6\x6f\xe7\x6f" + b"\x40\x5f\xc8\x2e\x4b\x52\x00\x51\x52\x40\x47\x40\xf8\xbc\x9c\xb9\x01\x2e\x9f" + b"\x00\xf3\x7f\x8f\xbd\x3f\xba\x1a\x25\x78\xb8\x59\x56\xd3\x08\x75\x8c\x73\x8a" + b"\xeb\x7f\xfc\xbf\xd4\x7f\x0b\x30\xfc\xbb\x1c\x0b\x4b\x7f\x8b\x43\x4b\x43\x21" + b"\x2e\xe4\x00\x00\xb3\xa7\x7f\xb5\x7f\xc6\x7f\x93\x7f\x90\x2e\x35\xb5\x01\x2e" + b"\xfe\x00\x00\xb2\x0b\x2f\x4f\x52\x01\x2e\xf9\x00\x82\x7f\x98\x2e\xbb\xcc\x0b" + b"\x30\x37\x2e\xfe\x00\x82\x6f\x93\x6f\x1a\x25\xc0\xb2\x8b\x7f\x14\x2f\x26\xbc" + b"\x25\xbd\x06\xb8\x2f\xb9\x80\xb2\x14\xb0\x0c\x2f\x51\x50\x53\x54\x0b\x30\x0b" + b"\x2e\xa0\x00\x57\x58\x1b\x42\x9b\x42\x6c\x09\x2b\x2e\xa0\x00\x0b\x42\x8b\x42" + b"\x86\x7f\x70\x84\x5b\x50\xd8\x08\x55\x52\x09\x50\x72\x7f\x63\x7f\x98\x2e\xc2" + b"\xc0\xd1\x6f\x62\x6f\xd1\x0a\x01\x2e\xf9\x00\xc5\x6f\xb4\x6f\x72\x6f\x4f\x52" + b"\x4d\x5c\x98\x2e\x06\xcd\x53\x6f\x90\x6f\x51\x52\xc0\xb2\x04\xbd\x54\x40\xaf" + b"\xb9\x45\x40\xd1\x7f\x02\x30\x06\x2f\xc0\xb2\x02\x30\x03\x2f\x53\x5c\x12\x30" + b"\x94\x43\x85\x43\x03\xbf\x6f\xbb\x80\xb3\x20\x2f\x36\x6f\x26\x01\x46\x6f\x6e" + b"\x03\x45\x42\xc0\x90\x29\x2e\xfa\x00\x53\x52\x14\x2f\x53\x5c\x00\x2e\x93\x41" + b"\x86\x41\xe3\x04\xae\x07\x80\xab\x04\x2f\x80\x91\x0a\x2f\x86\x6f\x73\x0f\x07" + b"\x2f\x83\x6f\xc0\xb2\x04\x2f\x54\x42\x45\x42\x12\x30\x04\x2c\x11\x30\x02\x2c" + b"\x11\x30\x11\x30\x02\xbc\x0f\xb8\xc2\x7f\x00\x90\x06\x2f\x31\x30\x23\x2e\x78" + b"\x01\x23\x2e\xdf\x00\x0b\x2c\x01\x30\x01\x2e\xdf\x00\x05\x2e\x78\x01\x10\x1a" + b"\x02\x2f\x21\x2e\x78\x01\x01\x2d\x01\x30\xf0\x6f\x98\x2e\x95\xcf\xc1\x6f\xa0" + b"\x6f\x98\x2e\x95\xcf\xd2\x6f\x21\x52\x01\x2e\xfa\x00\x82\x40\x50\x42\x11\x2c" + b"\x42\x42\x10\x30\x31\x30\x21\x2e\xfe\x00\x23\x2e\x78\x01\x23\x2e\xdf\x00\xf0" + b"\x6f\x01\x30\x98\x2e\x95\xcf\xa0\x6f\x01\x30\x98\x2e\x95\xcf\x00\x2e\xeb\x6f" + b"\x00\x5f\xb8\x2e\x07\x2e\x9f\x00\x3b\xbc\x60\x50\xbf\xbd\x0c\xb8\xf0\x7f\xc0" + b"\xb2\xeb\x7f\x2d\x2f\x07\x2e\xff\x00\xc3\x40\x01\x2e\x69\x01\x03\x1a\x11\x2f" + b"\x5f\x52\x27\x2e\x69\x01\x50\x40\xa0\x7f\x78\x80\x43\x40\xd0\x7f\xb3\x7f\x98" + b"\x2e\x64\xcf\xd0\x6f\x07\x80\xa3\x6f\x13\x42\x00\x2e\xb3\x6f\x03\x42\x03\x30" + b"\x01\x2e\xdf\x00\x01\xb2\x01\x2f\x02\x90\x00\x2f\x13\x30\x1a\x25\x7c\x88\x01" + b"\x2e\xff\x00\x5d\x52\x09\x54\x98\x2e\xc4\xce\xc1\x6f\xf0\x6f\x98\x2e\x95\xcf" + b"\x04\x2d\x01\x30\xf0\x6f\x98\x2e\x95\xcf\xeb\x6f\xa0\x5f\xb8\x2e\x60\x51\x0a" + b"\x25\x36\x88\xf4\x7f\xeb\x7f\x00\x32\x6b\x52\x32\x30\x13\x30\x98\x2e\x15\xcb" + b"\x0a\x25\x33\x84\xd2\x7f\x43\x30\x09\x50\x67\x52\x98\x2e\x95\xc1\xd2\x6f\x61" + b"\x52\x98\x2e\xd7\xc7\x2a\x25\xb0\x86\xc0\x7f\xd3\x7f\xaf\x84\x63\x50\xf1\x6f" + b"\x98\x2e\x4d\xc8\x2a\x25\xae\x8a\xaa\x88\xf2\x6e\x65\x50\xc1\x6f\xd3\x6f\xf4" + b"\x7f\x98\x2e\xb6\xc8\xe0\x6e\x00\xb2\x32\x2f\x6d\x54\x83\x86\xf1\x6f\xc3\x7f" + b"\x04\x30\x30\x30\xf4\x7f\xd0\x7f\xb2\x7f\xe3\x30\xc5\x6f\x56\x40\x45\x41\x28" + b"\x08\x03\x14\x0e\xb4\x08\xbc\x82\x40\x10\x0a\x69\x54\x26\x05\x91\x7f\x44\x28" + b"\xa3\x7f\x98\x2e\xd9\xc0\x08\xb9\x33\x30\x53\x09\xc1\x6f\xd3\x6f\xf4\x6f\x83" + b"\x17\x47\x40\x6c\x15\xb2\x6f\xbe\x09\x75\x0b\x90\x42\x45\x42\x51\x0e\x32\xbc" + b"\x02\x89\xa1\x6f\x7e\x86\xf4\x7f\xd0\x7f\xb2\x7f\x04\x30\x91\x6f\xd6\x2f\xeb" + b"\x6f\xa0\x5e\xb8\x2e\x01\x2e\x77\xf7\x09\xbc\x0f\xb8\x00\xb2\x10\x50\xfb\x7f" + b"\x10\x30\x0b\x2f\x03\x2e\x8a\x00\x96\xbc\x9f\xb8\x40\xb2\x05\x2f\x03\x2e\x68" + b"\xf7\x9e\xbc\x9f\xb8\x40\xb2\x07\x2f\x03\x2e\x6a\x01\x41\x90\x01\x2f\x98\x2e" + b"\x0d\xb6\x03\x2c\x00\x30\x21\x2e\x6a\x01\xfb\x6f\xf0\x5f\xb8\x2e\x20\x50\xe0" + b"\x7f\xfb\x7f\x00\x2e\x61\x50\x98\x2e\x3b\xc8\x63\x50\x98\x2e\xa7\xc8\x05\x50" + b"\x98\x2e\x55\xcc\xe1\x6f\x65\x50\x98\x2e\xe0\xc9\xfb\x6f\x00\x30\xe0\x5f\x21" + b"\x2e\x6a\x01\xb8\x2e\x03\xbc\x21\x2e\x6c\x01\x03\x2e\x6c\x01\x40\xb2\x10\x30" + b"\x21\x2e\x7c\x00\x01\x30\x05\x2f\x05\x2e\x6f\x01\x80\x90\x01\x2f\x23\x2e\x6f" + b"\xf5\xc0\x2e\x21\x2e\x70\x01\x30\x25\x00\x30\x21\x2e\x5a\xf5\x10\x50\x21\x2e" + b"\xde\x00\x21\x2e\xd6\x00\xfb\x7f\x98\x2e\xf5\x03\x40\x30\x21\x2e\x6b\x01\xfb" + b"\x6f\xf0\x5f\x03\x25\x80\x2e\x24\xb6\x01\x2e\xa8\x00\x31\x25\x08\xbd\x71\x30" + b"\x20\x50\x2c\xb9\x01\x09\xf2\x7f\x00\x91\xeb\x7f\x0a\x2f\x01\x30\xf0\x6f\x98" + b"\x2e\x95\xcf\x13\x30\x27\x2e\x74\x01\xeb\x6f\xc0\x2e\x00\x30\xe0\x5f\x01\x2e" + b"\x74\x01\x00\xb2\x05\x2f\x06\x30\xc3\x50\x98\x2e\x84\xb6\x2d\x2e\x74\x01\x05" + b"\x2e\x73\x01\x8b\x80\xc3\x52\x04\x42\x98\x2e\xbf\xb6\x10\x25\xf0\x6f\xf1\x7f" + b"\x98\x2e\x95\xcf\xf0\x6f\x00\x90\x02\x2f\x01\x2e\x75\x01\x02\x2d\x21\x2e\x75" + b"\x01\xeb\x6f\xe0\x5f\xb8\x2e\x0a\x82\x02\x30\x12\x42\x41\x0e\xfc\x2f\x37\x80" + b"\xc5\x52\x11\x42\x00\x2e\xc7\x52\x01\x42\xb8\x2e\x07\x88\x46\x8e\x06\x41\x3c" + b"\x8b\x32\x1a\x42\x41\xc7\x41\x16\x2f\x80\x91\x23\x2f\x57\x0f\x16\x30\x0c\x2f" + b"\x06\x80\x00\x2e\x00\x40\x00\xb2\x04\x2f\x45\x82\x00\x2e\x41\x40\xd1\x0f\x02" + b"\x2f\x06\x30\x06\x43\x46\x43\x80\xb3\x11\x2f\x03\x43\x47\x43\xb8\x2e\xd7\x0e" + b"\x0c\x2f\x43\x82\x17\x04\x41\x40\x41\x0f\x07\x2f\x03\x43\x42\x83\x47\x43\x00" + b"\x2e\x43\x40\xc1\x86\x43\x42\xb8\x2e\xb8\x2e\x88\x88\xc0\x50\x39\x8d\x88\x81" + b"\xe2\x7f\x02\x30\x05\x40\xf2\x7f\x41\xab\x22\x30\x95\x22\x86\x41\x27\x5e\x9a" + b"\x00\x04\x41\x37\x18\x66\x01\xc9\x56\xeb\x00\xc1\x7f\xb3\x7f\xd5\x7f\x48\x82" + b"\x39\x80\x82\x40\xa0\x7f\x91\x7f\x8b\x7f\x06\x30\x6f\x5a\xcb\x58\x6f\x56\x98" + b"\x2e\x67\xcc\x91\x6f\x7b\x82\xc6\x6f\x42\x40\x81\x84\x42\x42\x43\x82\x83\x8b" + b"\x42\x40\x86\x89\x7b\x82\x84\x87\x71\x7f\x80\xb2\x95\x7f\x64\x7f\x50\x7f\x02" + b"\x2f\xc5\x40\x41\x8b\xc5\x42\x87\x89\x67\x40\x07\x0f\x85\x8b\x41\x7f\x82\x8d" + b"\x47\x40\x3c\x2f\x81\x41\x01\x0e\xe1\x6f\x2d\x2f\x46\x8c\x90\x6f\x86\x41\x07" + b"\x40\xfe\x0e\x02\x2f\x04\x41\x00\xb3\x21\x2f\x44\x82\x04\x30\x41\x40\x71\x00" + b"\xf9\x0e\x36\x2f\x46\x41\x80\xa7\x01\x30\x04\x30\x0d\x2f\xe4\x6f\x00\x2e\x04" + b"\x41\x74\x0f\x04\x30\x07\x2f\x80\x90\x00\x2f\xc1\x42\x03\x86\x81\x84\xc2\x42" + b"\x01\x42\x14\x30\x42\x85\x41\x43\xa1\x42\x00\x2e\x82\x40\x80\x90\x1c\x2f\x01" + b"\x42\x1b\x2d\x06\x42\x19\x2c\x04\x30\xb2\x6f\xba\x04\x02\x1e\x80\x43\x12\x30" + b"\xc0\x6f\x23\x30\x98\x2e\x90\xb6\x0e\x2c\x04\x30\xb2\x6f\xc0\x6f\xba\x00\x51" + b"\x6f\x01\x86\x4a\x1c\xc1\x42\x22\x30\xe1\x6f\x13\x30\x98\x2e\x90\xb6\x04\x30" + b"\x52\x6f\xcd\x52\x40\x6f\xc4\x7f\x98\x2e\xa0\xcc\x41\x6f\x70\x6f\x42\x40\xcf" + b"\x52\x98\x2e\xa0\xcc\x41\x6f\x70\x6f\x42\x40\x01\x40\xd3\x6f\xd3\x00\xcb\x1e" + b"\xcf\x52\x13\x42\xb0\x7f\x98\x2e\xa0\xcc\xb0\x6f\x3e\x84\xd1\x6f\x82\x40\x03" + b"\x40\x51\x04\x59\x1c\x01\x42\x02\x82\xa0\x6f\x42\x40\x03\x40\xd3\x0f\x00\x30" + b"\x14\x30\x60\x22\xc5\x6f\x40\x91\x01\x2f\x53\x0e\x26\x2f\xe5\x6f\x47\x85\x00" + b"\x2e\x83\x40\x59\x0f\x20\x2f\x62\x6f\x4a\x8f\x85\x40\xc7\x41\x7f\x8d\x6f\x0f" + b"\xa6\x15\x17\x30\x04\x30\x0f\x2f\xe0\x6f\x0b\x80\x00\x2e\x07\x40\x3e\x08\x00" + b"\xb2\x00\x30\x06\x2f\x59\x1a\x04\x2f\xfd\x12\xc0\x90\x13\x30\x4b\x22\x06\x25" + b"\x71\x25\xc0\xb3\x04\x2f\xa4\x42\xa4\x42\x83\x82\x84\x42\x44\x42\x00\x2e\x8b" + b"\x6f\x40\x5f\xb8\x2e\x40\x50\x0a\x25\x3c\x80\xfb\x7f\x01\x42\xd2\x7f\xe3\x7f" + b"\x32\x30\x10\x25\x98\x2e\x7a\xc1\xfb\x6f\xc0\x5f\xb8\x2e\x70\x50\x0a\x25\x39" + b"\x80\xf3\x7f\x03\x42\xa1\x7f\xc2\x7f\xd1\x7f\x03\x30\x03\x43\xe4\x7f\xbb\x7f" + b"\x22\x30\x10\x25\x98\x2e\x7a\xc1\xd2\x6f\x02\x17\x04\x08\xc1\x6f\x0c\x09\x04" + b"\x1a\x10\x30\x04\x30\x20\x22\x01\xb2\x14\x2f\xd1\x58\x14\x09\xf3\x30\x93\x08" + b"\x24\xbd\x44\xba\x94\x0a\x02\x17\xf3\x6f\x4c\x08\x9a\x08\x8a\x0a\x9d\x52\x51" + b"\x08\x41\x58\x94\x08\x28\xbd\x98\xb8\xe4\x6f\x51\x0a\x01\x43\x00\x2e\xbb\x6f" + b"\x90\x5f\xb8\x2e\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1\x80\x2e\x00\xc1\x01\x2e\x5d\xf7\x08\xbc\x80\xac\x0e\xbb\x02\x2f" + b"\x00\x30\x41\x04\x82\x06\xc0\xa4\x00\x30\x11\x2f\x40\xa9\x03\x2f\x40\x91\x0d" + b"\x2f\x00\xa7\x0b\x2f\x80\xb3\x6f\x58\x02\x2f\x90\xa1\x26\x13\x20\x23\x80\x90" + b"\x10\x30\x01\x2f\xcc\x0e\x00\x2f\x00\x30\xb8\x2e\x77\x56\x71\x54\xd0\x40\xc4" + b"\x40\x0b\x2e\xfd\xf3\x77\x52\x90\x42\x94\x42\x95\x42\x05\x30\x79\x50\x0f\x88" + b"\x06\x40\x04\x41\x96\x42\xc5\x42\x48\xbe\x73\x30\x0d\x2e\x6f\x01\x4f\xba\x84" + b"\x42\x03\x42\x81\xb3\x02\x2f\x2b\x2e\x6f\xf5\x06\x2d\x05\x2e\x77\xf7\x75\x56" + b"\x93\x08\x25\x2e\x77\xf7\x73\x54\x25\x2e\xc2\xf5\x07\x2e\xfd\xf3\x42\x30\xb4" + b"\x33\xda\x0a\x4c\x00\x27\x2e\xfd\xf3\x43\x40\xd4\x3f\xdc\x08\x43\x42\x00\x2e" + b"\x00\x2e\x43\x40\x24\x30\xdc\x0a\x43\x42\x04\x80\x03\x2e\xfd\xf3\x4a\x0a\x23" + b"\x2e\xfd\xf3\x61\x34\xc0\x2e\x01\x42\x00\x2e\xd0\x51\xfb\x7f\x98\x2e\xcf\x0d" + b"\x5a\x25\x98\x2e\xf6\x0d\x6b\x87\x81\x54\xe1\x7f\xa3\x7f\xb3\x7f\xb2\x88\x7b" + b"\x52\xc2\x7f\x65\x8b\x7d\x56\x84\x7f\x61\x7f\x75\x7f\xd0\x7f\x95\x7f\x53\x7f" + b"\x14\x30\x7f\x54\x81\x6f\x42\x7f\x00\x2e\x53\x40\x45\x8c\x42\x40\x90\x41\xbb" + b"\x83\x86\x41\xd8\x04\x16\x06\x00\xac\x81\x7f\x02\x2f\x02\x30\xd3\x04\x10\x06" + b"\xc1\x84\x01\x30\xc1\x02\x0b\x16\x04\x09\x14\x01\x99\x02\xc1\xb9\xaf\xbc\x59" + b"\x0a\x64\x6f\x51\x43\xa1\xb4\x12\x41\x13\x41\x41\x43\x35\x7f\x64\x7f\x26\x31" + b"\xe5\x6f\xd4\x6f\x98\x2e\x37\xca\x32\x6f\x75\x6f\x83\x40\x42\x41\x23\x7f\x12" + b"\x7f\xf6\x30\x40\x25\x51\x25\x98\x2e\x37\xca\x14\x6f\x20\x05\x70\x6f\x25\x6f" + b"\x69\x07\xa2\x6f\x31\x6f\x0b\x30\x04\x42\x9b\x42\x8b\x42\x55\x42\x32\x7f\x40" + b"\xa9\xc3\x6f\x71\x7f\x02\x30\xd0\x40\xc3\x7f\x03\x2f\x40\x91\x15\x2f\x00\xa7" + b"\x13\x2f\x00\xa4\x11\x2f\x84\xbd\x98\x2e\x79\xca\x55\x6f\x89\x54\x54\x41\x82" + b"\x00\xf3\x3f\x45\x41\xcb\x02\xf6\x30\x98\x2e\x37\xca\x33\x6f\xa4\x6f\xc1\x42" + b"\x03\x2c\x00\x43\xa4\x6f\x33\x6f\x00\x2e\x42\x6f\x55\x6f\x91\x40\x42\x8b\x00" + b"\x41\x41\x00\x01\x43\x55\x7f\x14\x30\xc1\x40\x95\x40\x4d\x02\xc5\x6f\x87\x50" + b"\x68\x0e\x75\x6f\xd1\x42\xa3\x7f\x8a\x2f\x09\x2e\x6f\x01\x01\xb3\x22\x2f\x81" + b"\x58\x90\x6f\x17\x30\x13\x41\xb6\x6f\xe4\x7f\x00\x2e\x91\x41\x14\x40\x92\x41" + b"\x15\x40\x17\x2e\x6f\xf5\xb6\x7f\xd0\x7f\xcb\x7f\x98\x2e\x00\x0c\x07\x15\xc2" + b"\x6f\x14\x0b\x29\x2e\x6f\xf5\xc3\xa3\xc1\x8f\xe4\x6f\xd0\x6f\xe6\x2f\x14\x30" + b"\x05\x2e\x6f\xf5\x14\x0b\x29\x2e\x6f\xf5\x44\x2d\x83\x54\x01\x32\x89\x58\x05" + b"\x30\x41\x50\x83\x40\xd8\x08\x91\x01\xb8\xbd\x38\xb5\xe6\x7f\x0a\x16\xb1\x6f" + b"\x2a\xbb\xa6\xbd\x1c\x01\x06\xbc\x52\x40\x06\x0a\x53\x40\x45\x03\xb1\x7f\xf6" + b"\x30\x98\x2e\x37\xca\x1a\xbd\x16\xb6\x86\xba\x00\xa9\xaa\x0a\x67\x52\x0f\x2f" + b"\x00\x91\x67\x52\x03\x2f\x67\x5a\x55\x0f\x67\x52\x08\x2f\x3f\xa1\x04\x2f\x3f" + b"\x91\x03\x2f\x89\x58\xd4\x0f\x00\x2f\x89\x54\x12\x25\xf2\x33\x98\x2e\xd9\xc0" + b"\xe4\x6f\xf5\x37\x45\x09\x21\x85\x05\x43\x05\x30\x85\x52\x51\x0e\x01\x32\x89" + b"\x58\xc5\x2f\x25\x54\x09\x2e\x77\xf7\x22\x0b\x29\x2e\x77\xf7\xfb\x6f\x30\x5e" + b"\xb8\x2e\x10\x50\x01\x2e\x6b\x01\x00\xb2\xfb\x7f\x5d\x2f\x01\xb2\x54\x2f\x02" + b"\xb2\x4e\x2f\x03\x90\x63\x2f\x8f\x50\x39\x82\x02\x40\x81\x88\x91\x54\x41\x40" + b"\x97\x56\x04\x42\x00\x2e\x94\x40\x95\x40\xd8\xbe\x2c\x0b\x45\x40\x6c\x01\x55" + b"\x42\x0c\x17\x45\x40\x2c\x03\x54\x42\x53\x0e\xf2\x2f\x99\x56\x3e\x82\xe2\x40" + b"\xc3\x40\x28\xbd\x93\x0a\x43\x40\xda\x00\x53\x42\x8a\x16\x43\x40\x9a\x02\x52" + b"\x42\x00\x2e\x41\x40\x25\x54\x4a\x0e\x3b\x2f\x3a\x82\x00\x30\x41\x40\x21\x2e" + b"\x77\x0f\x40\xb2\x0a\x2f\x98\x2e\x61\x0c\x98\x2e\x2b\x0e\x98\x2e\x41\x0e\xfb" + b"\x6f\xf0\x5f\x00\x30\x80\x2e\x38\xb6\x95\x54\x8b\x56\x83\x42\x8f\x86\x74\x30" + b"\x93\x54\xc4\x42\x11\x30\x23\x2e\x6b\x01\xa1\x42\x23\x30\x27\x2e\x6e\x01\x21" + b"\x2e\x6d\x01\xba\x82\x18\x2c\x81\x42\x30\x30\x21\x2e\x6b\x01\x13\x2d\x21\x30" + b"\x00\x30\x23\x2e\x6b\x01\x21\x2e\x7b\xf7\x0c\x2d\x77\x30\x98\x2e\x1f\x0c\x8d" + b"\x50\x0c\x82\x12\x30\x40\x42\x25\x2e\x6b\x01\x2f\x2e\x7b\xf7\xfb\x6f\xf0\x5f" + b"\xb8\x2e\x70\x50\x0a\x25\x39\x86\xfb\x7f\xe1\x32\x62\x30\x98\x2e\xc2\xc4\x41" + b"\x56\xa5\x6f\xab\x08\x91\x6f\x4b\x08\x9b\x56\xc4\x6f\x23\x09\x4d\xba\x93\xbc" + b"\x8c\x0b\xd1\x6f\x0b\x09\x81\x52\x9d\x5e\x56\x42\xaf\x09\x4d\xba\x23\xbd\x94" + b"\x0a\xe5\x6f\x68\xbb\xeb\x08\xbd\xb9\x63\xbe\xfb\x6f\x52\x42\xe3\x0a\xc0\x2e" + b"\x43\x42\x90\x5f\x87\x50\x03\x2e\x25\xf3\x12\x40\x00\x40\x28\xba\x9b\xbc\x88" + b"\xbd\x93\xb4\xe3\x0a\x89\x16\x08\xb6\xc0\x2e\x19\x00\x62\x02\x10\x50\xfb\x7f" + b"\x98\x2e\x5d\x0d\x01\x2e\x6b\x01\x31\x30\x08\x04\xfb\x6f\x01\x30\xf0\x5f\x23" + b"\x2e\x6d\x01\x21\x2e\x6e\x01\xb8\x2e\x01\x2e\x6e\x01\x03\x2e\x6d\x01\x48\x0e" + b"\x01\x2f\x80\x2e\x05\x0e\xb8\x2e\x9f\x50\x21\x34\x01\x42\x82\x30\xc1\x32\x25" + b"\x2e\x62\xf5\x01\x00\x22\x30\x01\x40\x4a\x0a\x01\x42\xb8\x2e\x9f\x54\xf0\x3b" + b"\x83\x40\xd8\x08\xa1\x52\x83\x42\x00\x30\x83\x30\x50\x42\xc4\x32\x27\x2e\x64" + b"\xf5\x94\x00\x50\x42\x40\x42\xd3\x3f\x84\x40\x7d\x82\xe3\x08\x40\x42\x83\x42" + b"\xb8\x2e\x95\x52\x00\x30\x40\x42\x7c\x86\x71\x52\x09\x2e\x62\x0f\x77\x54\xc4" + b"\x42\xd3\x86\x54\x40\x55\x40\x94\x42\x85\x42\x21\x2e\x6e\x01\x42\x40\x25\x2e" + b"\xfd\xf3\xc0\x42\x7e\x82\x05\x2e\xd7\x00\x80\xb2\x14\x2f\x05\x2e\x89\x00\x27" + b"\xbd\x2f\xb9\x80\x90\x02\x2f\x21\x2e\x6f\xf5\x0c\x2d\x07\x2e\x63\x0f\x14\x30" + b"\x1c\x09\x05\x2e\x77\xf7\x75\x56\x47\xbe\x93\x08\x94\x0a\x25\x2e\x77\xf7\xa3" + b"\x54\x50\x42\x4a\x0e\xfc\x2f\xb8\x2e\x50\x50\x02\x30\x43\x86\xa1\x50\xfb\x7f" + b"\xe3\x7f\xd2\x7f\xc0\x7f\xb1\x7f\x00\x2e\x41\x40\x00\x40\x48\x04\x98\x2e\x74" + b"\xc0\x1e\xaa\xd3\x6f\x14\x30\xb1\x6f\xe3\x22\xc0\x6f\x52\x40\xe4\x6f\x4c\x0e" + b"\x12\x42\xd3\x7f\xeb\x2f\x03\x2e\x78\x0f\x40\x90\x11\x30\x03\x2f\x23\x2e\x78" + b"\x0f\x02\x2c\x00\x30\xd0\x6f\xfb\x6f\xb0\x5f\xb8\x2e\x40\x50\xf1\x7f\x0a\x25" + b"\x3c\x86\xeb\x7f\x41\x33\x22\x30\x98\x2e\xc2\xc4\xd3\x6f\xf4\x30\xdc\x09\xa7" + b"\x58\xc2\x6f\x94\x09\xa9\x58\x6a\xbb\xdc\x08\xb4\xb9\xb1\xbd\xa5\x5a\x95\x08" + b"\x21\xbd\xf6\xbf\x77\x0b\x51\xbe\xf1\x6f\xeb\x6f\x52\x42\x54\x42\xc0\x2e\x43" + b"\x42\xc0\x5f\x50\x50\xbb\x50\x31\x30\x11\x42\xfb\x7f\x7b\x30\x0b\x42\x11\x30" + b"\x02\x80\x23\x33\x01\x42\x03\x00\x07\x2e\x80\x03\x05\x2e\xd5\x00\x33\x52\xe2" + b"\x7f\xd3\x7f\xc0\x7f\x98\x2e\x9c\x0e\xd1\x6f\x08\x0a\x1a\x25\x7b\x86\xd0\x7f" + b"\x01\x33\x12\x30\x98\x2e\xc2\xc4\xd1\x6f\x08\x0a\x00\xb2\x0d\x2f\xe3\x6f\x01" + b"\x2e\x80\x03\x51\x30\xc7\x86\x23\x2e\x21\xf2\x08\xbc\xc0\x42\x98\x2e\x91\x03" + b"\x00\x2e\x00\x2e\xd0\x2e\xb0\x6f\x0b\xb8\x03\x2e\x1b\x00\x08\x1a\xb0\x7f\x70" + b"\x30\x04\x2f\x21\x2e\x21\xf2\x00\x2e\x00\x2e\xd0\x2e\x98\x2e\x6d\xc0\x98\x2e" + b"\x5d\xc0\xab\x50\x98\x2e\x46\xc3\xad\x50\x98\x2e\xfc\xc5\x5d\x50\x98\x2e\x64" + b"\xcf\xb1\x50\x21\x2e\x7e\x01\xaf\x56\xb3\x52\x27\x2e\x7f\x01\x23\x2e\x80\x01" + b"\xb5\x50\x98\x2e\x53\xc7\xb7\x50\x98\x2e\x44\xcb\x10\x30\x98\x2e\x0d\xb6\x20" + b"\x26\xc0\x6f\x02\x31\x12\x42\xeb\x33\x0b\x42\x37\x80\x01\x30\x01\x42\xf3\x37" + b"\xbd\x52\xc1\x50\x44\x40\xa2\x0a\x42\x42\x8b\x31\x09\x2e\x5e\xf7\xbf\x54\xe3" + b"\x08\x83\x42\x1b\x42\x23\x33\x4b\x00\xbc\x84\x0b\x40\x33\x30\x83\x42\x0b\x42" + b"\xe0\x7f\xd1\x7f\x98\x2e\x0c\x02\xd1\x6f\x80\x30\x40\x42\x03\x30\xe0\x6f\xb9" + b"\x54\x04\x30\x00\x2e\x00\x2e\x01\x89\x62\x0e\xfa\x2f\x43\x42\x11\x30\xfb\x6f" + b"\xc0\x2e\x01\x42\xb0\x5f\xc1\x4a\x00\x00\x6d\x57\x00\x00\x77\x8e\x00\x00\xe0" + b"\xff\xff\xff\xd3\xff\xff\xff\xe5\xff\xff\xff\xee\xe1\xff\xff\x7c\x13\x00\x00" + b"\x46\xe6\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1" + b"\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00" + b"\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e" + b"\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80\x2e\x00\xc1\x80" + b"\x2e\x00\xc1" +) + + +class BMI270_LEGACY: + ACCEL_SCALE = (2, 4, 8, 16) + GYRO_SCALE = (2000, 1000, 500, 250, 125) + ODR = (0.78, 1.5, 3.1, 6.25, 12.5, 25, 50, 100, 200, 400, 800, 1200) + + def __init__( + self, + i2c, + address=_DEFAULT_ADDR, + gyro_odr=100, + gyro_scale=2000, + accel_odr=100, + accel_scale=4, + force_reset=False, + bmm_magnet=None, + ): + """Initializes Device, Gyro and Accelerometer. + i2c: machine.I2C bus object + address: I2C address + gyro_odr: (0.78, 1.5Hz, 3.1Hz, 6.25Hz, 12.5Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, 800Hz, 1600Hz) + gyro_scale: (125dps, 250dps, 500dps, 1000dps, 2000dps) + accel_odr: (0.78, 1.5Hz, 3.1Hz, 6.25Hz, 12.5Hz, 25Hz, 50Hz, 100Hz, 200Hz, 400Hz, 800Hz, 1600Hz) + accel_scale: (+/-2g, +/-4g, +/-8g, +-16g) + force_reset: Forces a reset and reinitialisation of the device + bmm_magnet: allows passing of a magnetometer device, not used in practice + """ + self.bus = i2c + self.bmm_magnet = bmm_magnet + self.address = address + self.powersave = True + self.reset_flag = False + + # Self checks + if self._read_reg(_CHIP_ID) != 0x24: + raise OSError("No BMI270 device was found at address 0x%x" % (self.address)) + # check power on reset and error flags + needs_reset = False + if self._read_reg(_EVENT) & 0x01: # power on reset + needs_reset = True + if self._read_reg(_INTERNAL_STATUS) != 0x01: # init not ok + needs_reset = True + # reset and upload config as needed + if force_reset or needs_reset: + self._reset() + # apply sensor settings + self.sensor_settings(gyro_odr, gyro_scale, accel_odr, accel_scale) + # Allocate scratch buffers. + self._scratch = memoryview(array.array("h", [0, 0, 0])) + self._word = memoryview(array.array("h", [0])) + + def _reset(self): + """Soft reset the device and upload config""" + # This will leave the device in full power mode + self._write_reg(_CMD, 0xB6) + sleep_ms(50) + # Disable power save mode. + self._write_reg(_PWR_CONF, 0x00) + self.powersave = False + # Prepare config load. + self._write_reg(_INIT_CTRL, 0x00) + # Load config data. + self._write_burst(_CONFIG_DATA) + # Finish config load. + self._write_reg(_INIT_CTRL, 0x01) + sleep_ms(20) + # Check correct initialization status. + if not self._poll_reg(_INTERNAL_STATUS, 0x01): + raise OSError("Init configuration load failed") + # FIFO Reset + self._write_reg(_CMD, 0xB0) + # Disable adv_power_save | Enable fifo_self_wakeup. + self._write_reg(_PWR_CONF, 0x02) + # Enable accel, gyro and temperature data. + self._write_reg(_PWR_CTRL, 0x0E) + self.reset_flag = True + + # + # Device register functions (8 bit) + # + + def _read_reg(self, reg, size=1): + """Read a register, or set of registers, to a list""" + buf = self.bus.readfrom_mem(self.address, reg, size) + if size == 1: + return int(buf[0]) + return buf + + def _read_reg_into(self, reg, buf): + """Read register(s) into a bytearray""" + self.bus.readfrom_mem_into(self.address, reg, buf) + + def _write_reg(self, reg, val): + """Write a single integer byte, or a bytearray, to register(s)""" + if isinstance(val, int): + val = bytes([val]) + self.bus.writeto_mem(self.address, reg, val) + sleep_us(500 if self.powersave else 2) + + def _write_burst(self, data, chunk=16): + """Burst write config (bytearray) in 16b chunks, then the remainder""" + self._write_reg(_INIT_ADDR_0, 0) + self._write_reg(_INIT_ADDR_1, 0) + for i in range(len(data) // chunk): + offs = i * chunk + self._write_reg(_INIT_DATA, data[offs : offs + chunk]) + init_addr = ((i + 1) * chunk) // 2 + self._write_reg(_INIT_ADDR_0, (init_addr & 0x0F)) + self._write_reg(_INIT_ADDR_1, (init_addr >> 4) & 0xFF) + remainder = len(data) % chunk + if remainder != 0: + offs = (len(data) // chunk) * chunk + self._write_reg(_INIT_DATA, data[offs : offs + remainder]) + + def _poll_reg(self, reg, mask, retry=10, delay=100): + """Wait for a register bit to become true""" + for i in range(retry): + if self._read_reg(reg) & mask: + return True + sleep_ms(delay) + return False + + def _set_bit(self, state, register, bit): + """Set a single bit's state in a device register""" + old = self._read_reg(register) + new = ((~(2**bit) & 0xFF) & old) + (state << bit) + self._write_reg(register, bytes([new])) + + # + # Feature register functions (16 bit) + # + + def _f_read_word(self, page, word): + """get feature page register""" + self._write_reg(_FEAT_PAGE, page) + buf = self._read_reg(_FEATURES + (word * 2), 2) + # DEBUG print('_f_read_word: {:1d}, {:2d}, {}'.format(page, word, buf)) + return (0x100 * buf[1]) + buf[0] + + def _f_get_bit(self, page, word, startbit, bits): + """get specific bit(s) in a feature page register""" + reg = self._f_read_word(page, word) + return (reg >> startbit) & (2**bits - 1) + + def _f_write_value(self, value, page, word, startbit, bits): + """set feature page register to value""" + value = min(max(0, value), 2**bits - 1) + self._write_reg(_FEAT_PAGE, page) + if startbit != 0 or bits != 16: + buf = self._read_reg(_FEATURES + (word * 2), 2) + reg_masked = (0xFFFF ^ (2**bits - 1 << startbit)) & ((0x100 * buf[1]) + buf[0]) + value = reg_masked | (value << startbit) + self._write_reg(_FEATURES + (word * 2), bytes([value % 0x100, value // 0x100])) + # DEBUG print('_f_write_value: {:1d}, {:2d}, {}, {} = 0b{:b}'.format(page,word,int(startbit),int(bits),int(value))) + + def _f_enable_disable(self, state, *reg): + if state in (True, False): + self._f_write_value(state, *reg) + return self._f_get_bit(*reg) + + # + # Base Functions for all configs + # + + def sensor_settings(self, gyro_odr=None, gyro_scale=None, accel_odr=None, accel_scale=None): + """ + Set sensor Output Data Rares (ODR) and Scale + - see init() for values + """ + if gyro_odr is not None: + if gyro_odr not in self.ODR: + raise ValueError("Invalid gyro sampling rate: %d" % gyro_odr) + self.gyro_odr = gyro_odr + if gyro_scale is not None: + if gyro_scale not in self.GYRO_SCALE: + raise ValueError("Invalid gyro scaling: %d" % gyro_scale) + self.gyro_scale = gyro_scale + if accel_odr is not None: + if accel_odr not in self.ODR: + raise ValueError("Invalid accelerometer sampling rate: %d" % accel_odr) + self.accel_odr = accel_odr + if accel_scale is not None: + if accel_scale not in self.ACCEL_SCALE: + raise ValueError("Invalid accelerometer scaling: %d" % accel_scale) + self.accel_scale = accel_scale + # Set gyroscope scale and range. + self._write_reg(_GYR_RANGE, self.GYRO_SCALE.index(self.gyro_scale)) + # gyr_filter_perf | gyr_bwp normal mode | ODR + self._write_reg(_GYR_CONF, 0xA | (self.ODR.index(self.gyro_odr) + 1)) + # Set accelerometer scale and range. + self._write_reg(_ACC_RANGE, self.ACCEL_SCALE.index(self.accel_scale)) + # acc_filter_perf | acc_bwp normal mode | ODR + self._write_reg(_ACC_CONF, 0xA | (self.ODR.index(self.accel_odr) + 1)) + # scale factors + self.accel_scale_factor = 32768 / self.accel_scale + self.gyro_scale_factor = 32768 / self.gyro_scale + + def power( + self, + powersave=None, + accel=None, + gyro=None, + acc_filter=None, + gyr_filter=None, + gyr_noise=None, + ): + """ + Set power modes; + - See base datasheet section 4.5 'Power Modes' (page 26) + - disabling the gyroscope produces greatest savings + - gesture detection features do not use the gyroscope + + powersave = device 'advanced power save' mode, default False + accel = enable/disable accellerometer, default True + gyro = enable/disable gyroscope, default True + acc_filter = powresave mode for accel filter, default True + gyr_filter = powresave mode for gyro filter, default True + gyr_noise = powersave mode for gyro noise filter, default False + """ + if powersave in (True, False): + self._set_bit(powersave, _PWR_CONF, 0) + self.powersave = powersave + if gyro in (True, False): + self._set_bit(gyro, _PWR_CTRL, 1) + if accel in (True, False): + self._set_bit(accel, _PWR_CTRL, 2) + if acc_filter in (True, False): + self._set_bit(acc_filter, _ACC_CONF, 7) + if gyr_filter in (True, False): + self._set_bit(gyr_filter, _GYR_CONF, 7) + if gyr_noise in (True, False): + self._set_bit(gyr_noise, _GYR_CONF, 6) + + def gyro(self): + """Returns gyroscope vector in degrees/sec.""" + f = self.gyro_scale_factor + self._read_reg_into(_DATA_14, self._scratch) + return (self._scratch[0] / f, self._scratch[1] / f, self._scratch[2] / f) + + def accel(self): + """Returns acceleration vector in gravity units (9.81m/s^2).""" + f = self.accel_scale_factor + self._read_reg_into(_DATA_8, self._scratch) + return (self._scratch[0] / f, self._scratch[1] / f, self._scratch[2] / f) + + def magnet(self): + """Returns magnetometer vector if a magnetomter attached and supplied.""" + if self.bmm_magnet is not None: + return self.bmm_magnet.magnet() + return (0.0, 0.0, 0.0) + + def temperature(self): + """Returns temperature value in degrees C.""" + self._read_reg_into(_TEMP_REG, self._word) + return (self._word[0] / 512) + 23.0 + + # + # Sensor Features for legacy config + # + # Look at section 3.6 in the BMI270 Legacy – Application Note + # to fully understand settings values + # https://codeberg.org/easytarget/bmi270-micropython/raw/branch/main/reference/bmi270-legacy-appication-note.pdf + # + + # Interrupt setup and handling + + # Interrupt status flags + FEATURE_INT_FLAGS = ( + "sig_motion", + "step_act", + "high_low_g", + "tap", + "flat", + "no_motion", + "any_motion", + "orientation", + ) + DEVICE_INT_FLAGS = ( + "fifo_full", + "fifo_watermark", + "error", + None, + None, + "aux_data_ready", + "gyro_data_ready", + "accel_data_ready", + ) + + def device_interrupt_mask(self, pin1=0b1000, pin2=0b1000): + """ + Enable/disable device interrupts (4 bits) + Default is to enable device error interrupts on both pins + - data ready and fifo full interrupts are suppressed + 0 - fifo full + 1 - fifo watermark + 2 - data ready + 3 - error + """ + self._write_reg(_INT_MAP_DATA, (pin2 << 4) | pin1) + + def feature_interrupt_mask(self, pin1=0b11111111, pin2=0b11111111): + """ + Enable/disable feature interrupts (8 bits) + Default is to enable all feature interrupts on both interrupt output pins + - Enable/Disable features to control which interrupts will be flagged + 0 - significant motion + 1 - step activity + 2 - high and low G + 3 - tap + 4 - flat + 5 - no motion + 6 - any motion + 7 - orientation + """ + self._write_reg(_INT1_MAP_FEAT, pin1) + self._write_reg(_INT2_MAP_FEAT, pin2) + + def int1_pin_setup(self, enable, active_low=True, pushpull=True): + """Set interrupt pin 1 electrical characteristics""" + self._set_bit(active_low, _INT1_IO_CTRL, 1) + self._set_bit(pushpull, _INT1_IO_CTRL, 2) + self._set_bit(enable, _INT1_IO_CTRL, 3) + self._set_bit(False, _INT1_IO_CTRL, 4) + + def int2_pin_setup(self, enable, active_low=True, pushpull=True): + """Set interrupt pin 2 electrical characteristics""" + self._set_bit(active_low, _INT2_IO_CTRL, 1) + self._set_bit(pushpull, _INT2_IO_CTRL, 2) + self._set_bit(enable, _INT2_IO_CTRL, 3) + self._set_bit(False, _INT2_IO_CTRL, 4) + + def int_pin_latch(self, latching=False): + """Set the interrupt non-latching / latching flag""" + self._set_bit(latching, _INT_LATCH, 0) + + def interrupts(self): + """Report all active interrupt flags and values as appropriate""" + flags = [] + feature_reg = self._read_reg(_INT_STATUS_0) + device_reg = self._read_reg(_INT_STATUS_1) + for bit in range(8): + if (feature_reg >> bit) & 0b1: + flags.append(self.FEATURE_INT_FLAGS[bit]) + for bit in range(8): + if (device_reg >> bit) & 0b1: + flags.append(self.DEVICE_INT_FLAGS[bit]) + return flags + + # Tap gestures + + def taps(self): + """Return last recorded tap.""" + taps = self._read_reg(_ORIENT_ACT) >> 5 + for tap in range(3): + if taps & 1 << tap: + return tap + 1 + return 0 # no taps registered + + def tap_enable(self, *taps): + """Enable the requested taps (list), and set wait_timeout true as needed""" + self._f_write_value(True if 1 in taps else False, *_F_TAP_1_EN) + self._f_write_value(True if 2 in taps else False, *_F_TAP_2_EN) + self._f_write_value(True if 3 in taps else False, *_F_TAP_3_EN) + # enable 'wait timeout' when multiple taps selected + wait_timeout = True if len(taps) > 1 else False + self._f_write_value(wait_timeout, *_F_TAP_WAIT_TIMEOUT) + + def tap_settings( + self, sense_threshold=9, maximum_duration=130, quiet_time=80, wait_timeout=False, axis=2 + ): + """ + Tap detection settings: + threshold in units of ~78mg + duration and quiet_time in units of 5ms + wait_timeout guards against the 'first' tap of a double/triple tap + causing an immediate interrupt + axis is one of 0(X), 1(Y) or 2(Z) + """ + self._f_write_value(sense_threshold, *_F_TAP_SENS_THRES) + self._f_write_value(maximum_duration, *_F_TAP_MAX_GEST_DUR) + self._f_write_value(quiet_time, *_F_TAP_QUIET_TIME) + self._f_write_value(wait_timeout, *_F_TAP_WAIT_TIMEOUT) + self._f_write_value(axis, *_F_TAP_AXIS) + + # Signifigent motion detection (android standard profile) + + def sigmotion_enable(self, enable=None): + """Enable as required and return status""" + return self._f_enable_disable(enable, *_F_SIGMOTION_EN) + + def sigmotion_settings(self, block_time=250): + """ + Signifigent motion settings: + block_time (trigger delay) in units of 20ms + """ + self._f_write_value(block_time, *_F_SIGMOTION_BLOCK) + + # No motion + + def nomotion_enable(self, enable=None): + """Enable as required and return status""" + return self._f_enable_disable(enable, *_F_NOMOTION_EN) + + def nomotion_settings(self, duration=5, threshold=144, axes=0b111): + """ + No motion settings: + duration in units of 20ms + threshold in units of 0.5mg + per-axis select, 3 bits; x=bit0, y=bit1, z=bit2 + """ + self._f_write_value(duration, *_F_NOMOTION_DURATION) + self._f_write_value(threshold, *_F_NOMOTION_THRESHOLD) + self._f_write_value(axes, *_F_NOMOTION_AXES) + + # Any motion + + def anymotion_enable(self, enable=None): + """Enable as required and return status""" + return self._f_enable_disable(enable, *_F_ANYMOTION_EN) + + def anymotion_settings(self, duration=5, threshold=170, axes=0b111): + """ + Any motion settings: + duration in units of 20ms + threshold in units of 0.5mg + per-axis select, 3 bits; x=bit0, y=bit1, z=bit2 + """ + self._f_write_value(duration, *_F_ANYMOTION_DURATION) + self._f_write_value(threshold, *_F_ANYMOTION_THRESHOLD) + self._f_write_value(axes, *_F_ANYMOTION_AXES) + + # Flat detection + + def flat_enable(self, enable=None): + """Enable as required and return status""" + return self._f_enable_disable(enable, *_F_FLAT_EN) + + def flat_settings(self, hold_time=32, theta=8, hysteresis=9, blocking=2): + """ + Flat detection settings: + hold_time in units of 20ms + """ + self._f_write_value(hold, *_F_FLAT_HOLD) + self._f_write_value(theta, *_F_FLAT_THETA) + self._f_write_value(hysteresis, *_F_FLAT_HYSTERESIS) + self._f_write_value(blocking, *_F_FLAT_BLOCKING) + + # High-Low-G detection + + def high_g_enable(self, enable=None): + """Enable high-G detection""" + return self._f_enable_disable(enable, *_F_HIGH_G_EN) + + def high_g_settings(self, duration=4, hysteresis=1000, threshold=10000, axes=0b111): + """ + High G settings: + duration in units of 20ms + per-axis select, 3 bits; x=bit0, y=bit1, z=bit2 + """ + self._f_write_value(duration, *_F_HIGH_G_DURATION) + self._f_write_value(hysteresis, *_F_HIGH_G_HYSTERESIS) + self._f_write_value(threshold, *_F_HIGH_G_THRESHOLD) + self._f_write_value(axes, *_F_HIGH_G_AXES) + + def low_g_enable(self, enable=None): + """Enable low-G detection""" + return self._f_enable_disable(enable, *_F_LOW_G_EN) + + def low_g_settings(self, duration=0, hysteresis=256, threshold=512): + """ + Low G settings: + duration in units of 20ms + """ + self._f_write_value(duration, *_F_LOW_G_DURATION) + self._f_write_value(hysteresis, *_F_LOW_G_HYSTERESIS) + self._f_write_value(threshold, *_F_LOW_G_THRESHOLD) + + # Orientation (and face up/down) + # - Can be localised by setting ORIENTATIONS and FACEDIRECTIONS + + ORIENTATIONS = [ + "portrait_upright", + "landscape_left", + "portrait_upside_down", + "landscape_right", + ] + + FACEDIRECTIONS = ["face_down", "face_up"] + + def orientation(self): + """Returns the orientation and face up/down status""" + orient = self._f_get_bit(*_F_ORIENT_OUT) + face = self._f_get_bit(*_F_FACEUP_OUT) + return self.ORIENTATIONS[orient], self.FACEDIRECTIONS[face] + + def orientation_enable(self, enable=None): + """Enable orientation and faceupdown detections""" + self._f_enable_disable(enable, *_F_UPDOWN_EN) + return self._f_enable_disable(enable, *_F_ORIENT_EN) + + def orientation_settings(self, mode=0, blocking=3, theta=40, hysteresis=128): + """ + Orientation settings, see the application note! + mode = 0 (normal), 1 (high asymmetric) or 2 (low asymmetric) + """ + self._f_write_value(mode, *_F_ORIENT_MODE) + self._f_write_value(blocking, *_F_ORIENT_BLOCKING) + self._f_write_value(theta, *_F_ORIENT_THETA) + self._f_write_value(hysteresis, *_F_ORIENT_HYSTERESIS) + + # Step Counter and Activity tracking, always enabled + # - interrupt is shared between step and activity events + # - steps may be buffered (up to 8) before being counted + + ACTIVITIES = ["still", "walking", "running", "unknown"] + + def step_count(self): + """Return step counter value""" + dword_low = self._f_read_word(_F_SC_OUT_0_1[0], _F_SC_OUT_0_1[1]) + dword_high = self._f_read_word(_F_SC_OUT_2_3[0], _F_SC_OUT_2_3[1]) + return (dword_high * 0xFFFF) + dword_low + + def activity(self): + """Return current activity status""" + act = self._f_get_bit(*_F_ACT_OUT) + return self.ACTIVITIES[act] + + def step_count_enable(self, enable=None): + """Enable step counting""" + return self._f_enable_disable(enable, *_F_STEP_COUNT_EN) + + def activity_enable(self, enable=None): + """Enable activity detection""" + return self._f_enable_disable(enable, *_F_ACTIVITY_EN) + + def step_count_reset(self): + """Reset the step counter""" + self._f_write_value(True, *_F_STEP_COUNT_RST) + + def step_settings(self, watermark=None): + """ + If watermark is None, no step interrupts + If watermark = 0 enable step_detect (interrupt on every step) + If positive integer disable step_detect and interrupt on (watermark * 20) steps + """ + if watermark is None: + self._f_write_value(False, *_F_STEP_DETECT_EN) + self._f_write_value(0, *_F_STEP_WATERMARK) + elif watermark == 0: + self._f_write_value(True, *_F_STEP_DETECT_EN) + self._f_write_value(0, *_F_STEP_WATERMARK) + else: + self._f_write_value(False, *_F_STEP_DETECT_EN) + self._f_write_value(watermark, *_F_STEP_WATERMARK) diff --git a/micropython/drivers/imu/bmi270_legacy/examples/demo_legacy.py b/micropython/drivers/imu/bmi270_legacy/examples/demo_legacy.py new file mode 100644 index 000000000..6e1961755 --- /dev/null +++ b/micropython/drivers/imu/bmi270_legacy/examples/demo_legacy.py @@ -0,0 +1,213 @@ +""" +Demo of BMI270 Legacy Configuration features + +Owen Carter, Jan 2026 +""" + +from bmi270_legacy import BMI270_LEGACY + +from machine import I2C, Pin +from time import sleep_ms, ticks_ms, ticks_diff + +# This lets us sense user input in the repl console +from sys import stdin +from select import poll, POLLIN + +stdinpoll = poll() +stdinpoll.register(stdin, POLLIN) + + +def helptxt(): + print(' "p" to pause, "?" for this help, "x" to exit') + print(' "+" / "-" to increase / decrease frequency') + print("Enable/Disable Features:") + print(' "1", "2", "3": single/double/triple tap detection') + print(' "a": Any motion detection') + print(' "s": Significant motion detection') + print(' "n": No motion detection') + print(' "f": Flat detection') + print(' "h": High-G detection') + print(' "l": Low-G detection') + print(' "v": Activity') + print(' "c": Step Counting') + print(' "w": Interrupt every step') + print(' "W": Interrupt every 40 steps') + print("Orientation is pre-enabled") + print(' "o": Orientation') + print("Device Control:") + print(' "R" / "r": Dump feature / device registers') + print(' "!": Reset the device') + + +def dump_regs(): + """Debug function, use as needed. Gives formatted feature register dump""" + for register in range(0x80): + reg = imu._read_reg(register) + print("0x{0:02X} :: 0b{1:08b} :: 0x{1:02X}".format(register, reg)) + + +def dump_f_regs(): + """Debug function, use as needed. Gives formatted feature register dump""" + for page in range(8): + for word in range(8): + reg = imu._f_read_word(page, word) + print( + "{0} 0x{1:02X} :: 0b{2:016b} :: 0x{2:04X}".format( + "{}::".format(page) if word == 0 else " ", 0x30 + (word * 2), reg + ) + ) + + +def show_data(): + """Dump a dataline with formatted info""" + interrupts = imu.interrupts() # get interrupts before status + orient, face = imu.orientation() + steps = imu.step_count() + activity = imu.activity() + ms = ticks_ms() % 1000 + s = ticks_ms() // 1000 + m = s // 60 + print("{:02d}:{:02d}.{:03d}: ".format(m % 60, s % 60, ms), end="") + print("{} steps ({}), {} ({})".format(steps, activity, orient, face), end="") + if "tap" in interrupts: + interrupts[interrupts.index("tap")] = "tap_{}".format(imu.taps()) + if len(interrupts) != 0: + print(", Int: {}".format(interrupts), end="") + print() + + +def data_loop(freq=float(2)): + "Simply loop until repl input detected" + + def toggle(enabler): + return bool(enabler(not enabler(None))) + + paused = False + taps = [] + start = ticks_ms() + while True: + cmd = "" + while len(stdinpoll.poll(0)) == 0 and ticks_diff(ticks_ms(), start) < (1000 / freq): + pass + start = ticks_ms() + while len(stdinpoll.poll(0)) > 0: + cmd += stdin.read(1) + if len(cmd) > 0: + c = cmd[0] + show_data() + if c in ("x", "X"): + break + elif c == "?": + helptxt() + elif c in ("p", "P"): + paused = not paused + print("Paused :: {}".format(paused)) + elif c == "r": + dump_regs() + elif c == "R": + dump_f_regs() + elif c in ("-", "_"): + freq = freq / 2 + print("freq- (~{} Hz)".format(freq)) + elif c in ("+", "="): + freq = min(4096, freq * 2) + print("freq+ ({} Hz)".format(freq)) + elif c in ("1", "2", "3"): + if int(c) in taps: + taps.remove(int(c)) + else: + taps.append(int(c)) + imu.tap_enable(*taps) + print("Taps set to: {}".format(taps)) + elif c == "o": + print("Orientation enable: {}".format(toggle(imu.orientation_enable))) + elif c == "v": + print("Activity tracking enable: {}".format(toggle(imu.activity_enable))) + elif c == "c": + print("Step count enable: {}".format(toggle(imu.step_count_enable))) + elif c == "w": + imu.step_settings(watermark=0) + imu.step_count_reset() + print("single step detect enabled, counter reset") + elif c == "W": + imu.step_settings(watermark=2) + print("step watermark enabled at 2 (40 steps)") + elif c == "h": + print("High G enable: {}".format(toggle(imu.high_g_enable))) + elif c == "l": + print("Low G enable: {}".format(toggle(imu.low_g_enable))) + elif c == "a": + print("Any motion enable: {}".format(toggle(imu.anymotion_enable))) + elif c == "s": + print("Significant motion enable: {}".format(toggle(imu.sigmotion_enable))) + elif c == "n": + print("No motion enable: {}".format(toggle(imu.nomotion_enable))) + elif c == "f": + print("Flat enable: {}".format(toggle(imu.flat_enable))) + elif c == "!": + print("\n## Reset ##\n") + imu.reset() + # all settings will be lost; re-apply + imu.sensor_settings() # defaults to previous settings.. + imu.feature_interrupt_mask() + imu.device_interrupt_mask() + else: + start = ticks_ms() + if not paused: + show_data() + + +print("Init: ", end="") + +# Set up the I2C bus +# - Adjust pins here to suit your system + +i2c = I2C(sda=Pin(31), scl=Pin(32)) + +# The bmi270 defaults to address 0x68 +imu_address = 0x68 + +# Init in I2C mode +imu = BMI270_LEGACY(i2c, address=imu_address) + +print(" Complete") + +# Inform if device needed a reset and configuration load +if imu.reset_flag: + print("warning: Device was reset during init") + +sleep_ms(100) # it pays to wait for an initial reading. + +# Interrupt setup (interrupts must be 'unmasked' on at least one pin to flagged) +# Unmask all feature interrupts on both pins +imu.feature_interrupt_mask() +# Unmask device error interrupts on both pins +imu.device_interrupt_mask() + +# No electrical signal will be seen on the interrupt output pins +# until they are enabled and drive properties defined +# imu.int1_pin_setup(False) +# imu.int2_pin_setup(False) + +# You can change feature report output attributes (eg to use integers, or localize the text) +# imu.ORIENTATIONS = [0, 1, 2, 3] +# imu.FACEDIRECTIONS = [True, False] +# imu.ACTIVITIES= ['stil', 'lopend', 'rennend', 'onbekend'] + +# make taps more sensitive +imu.tap_settings(sense_threshold=2) + +# orientation can be made low asymmetric, and blocking lowered. +# - to understand these options refer to the bmi270-legacy application notes` +imu.orientation_settings(mode=2, blocking=2) + +# Show any pending interrupts. +print("\nStartup interrupts: {}".format(imu.interrupts())) + +# Enable orientation tracking by default +imu.orientation_enable(True) +# other features will be off (after reset) or in their previous state + +# Now start loop +helptxt() +data_loop() diff --git a/micropython/drivers/imu/bmi270_legacy/manifest.py b/micropython/drivers/imu/bmi270_legacy/manifest.py new file mode 100644 index 000000000..e11144931 --- /dev/null +++ b/micropython/drivers/imu/bmi270_legacy/manifest.py @@ -0,0 +1,2 @@ +metadata(description="BOSCH BMI270 IMU driver with Legacy featureset.", version="1.0.0") +module("bmi270_legacy.py", opt=3)