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)