├── .gitignore ├── LICENSE ├── README.md └── bme280_i2c.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *.cover 45 | .hypothesis/ 46 | .pytest_cache/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | db.sqlite3 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # Environments 83 | .env 84 | .venv 85 | env/ 86 | venv/ 87 | ENV/ 88 | env.bak/ 89 | venv.bak/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | 105 | 106 | *.pem 107 | *.bin 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jonathan Hanson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is furnished 10 | to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | This Micropython module enables I2C communication with a Bosch BME280 temperature, humidity, and pressure sensor. 3 | 4 | # Usage 5 | This module pretty closely follows the Bosch reference library's behavior (see the references below). 6 | 7 | The basic operation of the module requires initialization of an `BME280_I2C` instance, followed by sensor configuration, and finally acquiring a measurement. 8 | 9 | Note that the BME280 has a couple of different operating modes (FORCED, and NORMAL), as well as several oversampling and filtering options, and it's a good idea to understand these specifics in order to get the most out of the part. See the data sheet, specifically section `3. Functional description` to get an understanding of how this sensor works. 10 | 11 | Also, in the examples below, the I2C address is supplied as `bme280_i2c.BME280_I2C_ADDR_SEC` (`0x77`). Be aware that `bme280_i2c.BME280_I2C_ADDR_PRIM` (`0x76`) is also available. 12 | 13 | ## Available Methods 14 | ### `get_measurement_settings()` 15 | Returns a dict with the sensor's currently-configured settings for the filter coefficient, standby time, and the oversampling settings for each of humidity, pressure, and temperature. The result would look like: 16 | ``` python 17 | sensor.get_measurement_settings() 18 | 19 | { 20 | 'filter': 0, 21 | 'standby_time': 0, 22 | 'osr_h': 1, 23 | 'osr_p': 1, 24 | 'osr_t': 1 25 | } 26 | ``` 27 | Where the values are constants that represent the values of the various settings. See the various `BME280_OVERSAMPLING_*`, `BME280_STANDBY_TIME_*`, and `BME280_FILTER_COEFF_*` defines at the top of `bme280_i2c.py` and listed below. 28 | 29 | ### `set_measurement_settings(settings: dict)` 30 | Sets the sensor configuration with a dict similar to the one returned by `get_measurement_settings()` above. Note that all the keys are optional, and leaving one out will retain the currently-set value. 31 | 32 | ### `get_power_mode()` 33 | Returns the currently set sensor power mode, where the value is one of the `BME280_*_MODE` constants. 34 | 35 | ### `set_power_mode(mode: int)` 36 | Set the sensor power mode, where the value is one of the `BME280_*_MODE` constants. 37 | 38 | Be sure to read section 3.3.1 of the data sheet, and understand that setting the power mode to FORCED will immediately return the sensor to the SLEEP mode, after taking a single sample. 39 | 40 | ### `get_measurement()` 41 | Acquire and return a dict of the sensor's values, like: 42 | ``` python 43 | sensor.get_measurement() 44 | 45 | { 46 | 'temperature': 27.86, 47 | 'pressure': 101412.0, 48 | 'humidity': 39.5 49 | } 50 | ``` 51 | Where the values are in Degrees Celsius, Pascals, and % Relative Humidity, respectively. 52 | 53 | ## Configuration Constants 54 | ``` python 55 | # BME280 default address 56 | BME280_I2C_ADDR_PRIM : 0x76 57 | BME280_I2C_ADDR_SEC : 0x77 58 | 59 | # Sensor Power Mode Options 60 | BME280_SLEEP_MODE : 0x00 61 | BME280_FORCED_MODE : 0x01 62 | BME280_NORMAL_MODE : 0x03 63 | 64 | # Oversampling Options 65 | BME280_NO_OVERSAMPLING : 0x00 66 | BME280_OVERSAMPLING_1X : 0x01 67 | BME280_OVERSAMPLING_2X : 0x02 68 | BME280_OVERSAMPLING_4X : 0x03 69 | BME280_OVERSAMPLING_8X : 0x04 70 | BME280_OVERSAMPLING_16X : 0x05 71 | 72 | # Standby Duration Options 73 | BME280_STANDBY_TIME_500_US : 0x00 # Note this is microseconds, so 0.5 ms 74 | BME280_STANDBY_TIME_62_5_MS : 0x01 75 | BME280_STANDBY_TIME_125_MS : 0x02 76 | BME280_STANDBY_TIME_250_MS : 0x03 77 | BME280_STANDBY_TIME_500_MS : 0x04 78 | BME280_STANDBY_TIME_1000_MS : 0x05 79 | BME280_STANDBY_TIME_10_MS : 0x06 80 | BME280_STANDBY_TIME_20_MS : 0x07 81 | 82 | # Filter Coefficient Options 83 | BME280_FILTER_COEFF_OFF : 0x00 84 | BME280_FILTER_COEFF_2 : 0x01 85 | BME280_FILTER_COEFF_4 : 0x02 86 | BME280_FILTER_COEFF_8 : 0x03 87 | BME280_FILTER_COEFF_16 : 0x04 88 | ``` 89 | 90 | ## "Normal" Power Mode Example 91 | Once enabled, Normal mode samples automatically at a given rate for as long as the sensor has power. This mode makes sense for high sample-rate applications. 92 | 93 | This example implements the advised settings from the data sheet section `3.5.3 Indoor navigation`: 94 | 95 | ``` python 96 | import machine 97 | import bme280_i2c 98 | import time 99 | 100 | # Create a micropython I2C object with the appropriate device pins 101 | i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4)) 102 | 103 | # Create a sensor object to represent the BME280 104 | # Note that this will error if the device can't be reached over I2C. 105 | sensor = bme280_i2c.BME280_I2C(address=bme280_i2c.BME280_I2C_ADDR_SEC, i2c=i2c) 106 | 107 | # Configure the sensor for the application in question. 108 | sensor.set_measurement_settings({ 109 | 'filter': bme280_i2c.BME280_FILTER_COEFF_16, 110 | 'standby_time': bme280_i2c.BME280_STANDBY_TIME_500_US, 111 | 'osr_h': bme280_i2c.BME280_OVERSAMPLING_1X, 112 | 'osr_p': bme280_i2c.BME280_OVERSAMPLING_16X, 113 | 'osr_t': bme280_i2c.BME280_OVERSAMPLING_2X}) 114 | 115 | # Start the sensor automatically sensing 116 | sensor.set_power_mode(bme280_i2c.BME280_NORMAL_MODE) 117 | 118 | # Wait for the measurement settle time, print the measurement, and repeat 119 | while 1: 120 | time.sleep_ms(70) 121 | print( sensor.get_measurement() ) 122 | 123 | # The above code repeatedly prints a line like: 124 | # {'pressure': 101412.0, 'humidity': 39.5, 'temperature': 27.86} 125 | ``` 126 | 127 | ## "Forced" Power Mode Example 128 | Once enabled, forced mode takes a single measurement and then returns the sensor to sleep mode. Acquiring a new sample requires another set to forced mode. This mode is convenient to conserve power for low sample rate applications. 129 | 130 | This example implements the advised settings from the data sheet section `3.5.1 Weather monitoring`: 131 | 132 | ``` python 133 | import machine 134 | import bme280_i2c 135 | import time 136 | 137 | i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4)) 138 | 139 | sensor = bme280_i2c.BME280_I2C(address=bme280_i2c.BME280_I2C_ADDR_SEC, i2c=i2c) 140 | 141 | sensor.set_measurement_settings({ 142 | 'filter': bme280_i2c.BME280_FILTER_COEFF_OFF, 143 | 'osr_h': bme280_i2c.BME280_OVERSAMPLING_1X, 144 | 'osr_p': bme280_i2c.BME280_OVERSAMPLING_1X, 145 | 'osr_t': bme280_i2c.BME280_OVERSAMPLING_1X}) 146 | 147 | while 1: 148 | sensor.set_power_mode(bme280_i2c.BME280_FORCED_MODE) 149 | time.sleep_ms(40) 150 | print( sensor.get_measurement() ) 151 | 152 | # {'pressure': 101412.0, 'humidity': 39.5, 'temperature': 27.86} 153 | ``` 154 | 155 | ## A Note About Measurement Duration 156 | In the above examples there are sleep commands issued prior to each measurement to pause a given number of milliseconds before acquiring a new sample. These pauses are defined by the data sheet in section `9. Appendix B: Measurement time and current calculation`. 157 | 158 | You should read the whole thing to properly understand the determination of delays before sampling, but the basic idea is that the measurement itself has a typical and maximum amount of time to complete, depending on the 3 different oversampling configuration values. 159 | 160 | In addition, the Normal power mode has an additional standby time which you can configure, and which adds to the measurement duration. 161 | 162 | See the data sheet for the details. Note that the weird factor of 1000 in their calculations is just to deal with the milliseconds vs seconds conversion. 163 | 164 | # Reference Material 165 | - [Bosch BME280 Product Information](https://www.bosch-sensortec.com/bst/products/all_products/bme280) 166 | - [Bosch BME280 Datasheet](https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-12.pdf) 167 | - [Bosch Sensortec Reference Driver (in C)](https://github.com/BoschSensortec/BME280_driver) 168 | -------------------------------------------------------------------------------- /bme280_i2c.py: -------------------------------------------------------------------------------- 1 | # Author(s): Jonathan Hanson 2018 2 | # 3 | # This is more or less a straight read of the Bosch data sheet at: 4 | # https://www.bosch-sensortec.com/bst/products/all_products/bme280 5 | # and specifically: 6 | # https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-12.pdf 7 | # 8 | # Modeled on the reference library Bosch Sensortec C library at: 9 | # https://github.com/BoschSensortec/BME280_driver 10 | # 11 | # The development of this module was heavily guided by the prior work done 12 | # by Peter Dahlberg et al at: 13 | # https://github.com/catdog2/mpy_bme280_esp8266 14 | 15 | from micropython import const 16 | from ustruct import unpack, unpack_from 17 | from utime import sleep_ms 18 | 19 | 20 | # BME280 default address 21 | BME280_I2C_ADDR_PRIM = const(0x76) 22 | BME280_I2C_ADDR_SEC = const(0x77) 23 | 24 | # Sensor Power Mode Options 25 | BME280_SLEEP_MODE = const(0x00) 26 | BME280_FORCED_MODE = const(0x01) 27 | BME280_NORMAL_MODE = const(0x03) 28 | 29 | # Oversampling Options 30 | BME280_NO_OVERSAMPLING = const(0x00) 31 | BME280_OVERSAMPLING_1X = const(0x01) 32 | BME280_OVERSAMPLING_2X = const(0x02) 33 | BME280_OVERSAMPLING_4X = const(0x03) 34 | BME280_OVERSAMPLING_8X = const(0x04) 35 | BME280_OVERSAMPLING_16X = const(0x05) 36 | 37 | # Standby Duration Options 38 | BME280_STANDBY_TIME_500_US = const(0x00) # Note this is microseconds, so 0.5 ms 39 | BME280_STANDBY_TIME_62_5_MS = const(0x01) 40 | BME280_STANDBY_TIME_125_MS = const(0x02) 41 | BME280_STANDBY_TIME_250_MS = const(0x03) 42 | BME280_STANDBY_TIME_500_MS = const(0x04) 43 | BME280_STANDBY_TIME_1000_MS = const(0x05) 44 | BME280_STANDBY_TIME_10_MS = const(0x06) 45 | BME280_STANDBY_TIME_20_MS = const(0x07) 46 | 47 | # Filter Coefficient Options 48 | BME280_FILTER_COEFF_OFF = const(0x00) 49 | BME280_FILTER_COEFF_2 = const(0x01) 50 | BME280_FILTER_COEFF_4 = const(0x02) 51 | BME280_FILTER_COEFF_8 = const(0x03) 52 | BME280_FILTER_COEFF_16 = const(0x04) 53 | 54 | # BME280 Chip ID 55 | _BME280_CHIP_ID = const(0x60) 56 | 57 | # Register Addresses 58 | _BME280_CHIP_ID_ADDR = const(0xD0) 59 | _BME280_RESET_ADDR = const(0xE0) 60 | _BME280_TEMP_PRESS_CALIB_DATA_ADDR = const(0x88) 61 | _BME280_HUMIDITY_CALIB_DATA_ADDR = const(0xE1) 62 | _BME280_PWR_CTRL_ADDR = const(0xF4) 63 | _BME280_CTRL_HUM_ADDR = const(0xF2) 64 | _BME280_CTRL_MEAS_ADDR = const(0xF4) 65 | _BME280_CONFIG_ADDR = const(0xF5) 66 | _BME280_DATA_ADDR = const(0xF7) 67 | 68 | # Register range sizes 69 | _BME280_TEMP_PRESS_CALIB_DATA_LEN = const(26) 70 | _BME280_HUMIDITY_CALIB_DATA_LEN = const(7) 71 | _BME280_P_T_H_DATA_LEN = const(8) 72 | 73 | 74 | class BME280_I2C: 75 | def __init__(self, address: int = BME280_I2C_ADDR_PRIM, i2c=None): 76 | """ 77 | Ensure I2C communication with the sensor is working, reset the sensor, 78 | and load its calibration data into memory. 79 | """ 80 | self.address = address 81 | 82 | if i2c is None: 83 | raise ValueError('A configured I2C object is required.') 84 | self.i2c = i2c 85 | 86 | self._read_chip_id() 87 | self._soft_reset() 88 | self._load_calibration_data() 89 | 90 | def _read_chip_id(self): 91 | """ 92 | Read the chip ID from the sensor and verify it's correct. 93 | If the value isn't correct, wait 1ms and try again. 94 | If 5 tries don't work, raise an exception. 95 | """ 96 | for x in range(5): 97 | mem = self.i2c.readfrom_mem(self.address, _BME280_CHIP_ID_ADDR, 1) 98 | if mem[0] == _BME280_CHIP_ID: 99 | return 100 | sleep_ms(1) 101 | raise Exception("Couldn't read BME280 chip ID after 5 attempts.") 102 | 103 | def _soft_reset(self): 104 | """ 105 | Write the reset command to the sensor's reset address. 106 | Wait 2ms, per the reference library's example. 107 | """ 108 | self.i2c.writeto_mem(self.address, _BME280_RESET_ADDR, bytearray([0xB6])) 109 | sleep_ms(2) 110 | 111 | def _load_calibration_data(self): 112 | """ 113 | Load the read-only calibration values out of the sensor's memory, to be 114 | used later in calibrating the raw reads. These get stored in various 115 | self.cal_dig_* object properties. 116 | 117 | See https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L1192 118 | See https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L1216 119 | See https://github.com/catdog2/mpy_bme280_esp8266/blob/master/bme280.py#L73 120 | """ 121 | # Load the temperature and pressure calibration data 122 | # (note that the first value of the humidity data is stuffed in here) 123 | tp_cal_mem = self.i2c.readfrom_mem(self.address, 124 | _BME280_TEMP_PRESS_CALIB_DATA_ADDR, 125 | _BME280_TEMP_PRESS_CALIB_DATA_LEN) 126 | 127 | (self.cal_dig_T1, self.cal_dig_T2, self.cal_dig_T3, 128 | self.cal_dig_P1, self.cal_dig_P2, self.cal_dig_P3, 129 | self.cal_dig_P4, self.cal_dig_P5, self.cal_dig_P6, 130 | self.cal_dig_P7, self.cal_dig_P8, self.cal_dig_P9, 131 | _, 132 | self.cal_dig_H1) = unpack("> 4) 146 | 147 | self.cal_dig_H6 = unpack_from("> 2) & 0b00000111, 166 | "osr_t": (ctrl_meas >> 5) & 0b00000111, 167 | "filter": (config >> 2) & 0b00000111, 168 | "standby_time": (config >> 5) & 0b00000111, 169 | } 170 | 171 | def set_measurement_settings(self, settings: dict): 172 | """ 173 | Set the sensor's settings for each measurement's oversampling, 174 | the pressure IIR filter coefficient, and standby duration 175 | during normal power mode. 176 | 177 | The settings dict can have keys osr_h, osr_p, osr_t, filter, and 178 | standby_time. All values are optional, and omitting any will retain 179 | the pre-existing value. 180 | 181 | See the data sheet, section 3 and 5 182 | """ 183 | self._validate_settings(settings) 184 | self._ensure_sensor_is_asleep() 185 | self._write_measurement_settings(settings) 186 | 187 | def _validate_settings(self, settings: dict): 188 | oversampling_options = [ 189 | BME280_NO_OVERSAMPLING, BME280_OVERSAMPLING_1X, 190 | BME280_OVERSAMPLING_2X, BME280_OVERSAMPLING_4X, 191 | BME280_OVERSAMPLING_8X, BME280_OVERSAMPLING_16X] 192 | 193 | filter_options = [ 194 | BME280_FILTER_COEFF_OFF, BME280_FILTER_COEFF_2, 195 | BME280_FILTER_COEFF_4, BME280_FILTER_COEFF_8, 196 | BME280_FILTER_COEFF_16] 197 | 198 | standby_time_options = [ 199 | BME280_STANDBY_TIME_500_US, 200 | BME280_STANDBY_TIME_62_5_MS, BME280_STANDBY_TIME_125_MS, 201 | BME280_STANDBY_TIME_250_MS, BME280_STANDBY_TIME_500_MS, 202 | BME280_STANDBY_TIME_1000_MS, BME280_STANDBY_TIME_10_MS, 203 | BME280_STANDBY_TIME_20_MS] 204 | 205 | if 'osr_h' in settings: 206 | if settings['osr_h'] not in oversampling_options: 207 | raise ValueError("osr_h must be one of the oversampling defines") 208 | if 'osr_p' in settings: 209 | if settings['osr_h'] not in oversampling_options: 210 | raise ValueError("osr_p must be one of the oversampling defines") 211 | if 'osr_t' in settings: 212 | if settings['osr_h'] not in oversampling_options: 213 | raise ValueError("osr_t must be one of the oversampling defines") 214 | if 'filter' in settings: 215 | if settings['filter'] not in filter_options: 216 | raise ValueError("filter filter coefficient defines") 217 | if 'standby_time' in settings: 218 | if settings['standby_time'] not in standby_time_options: 219 | raise ValueError("standby_time must be one of the standby time duration defines") 220 | 221 | def _write_measurement_settings(self, settings: dict): 222 | # Read in the existing configuration, to modify 223 | mem = self.i2c.readfrom_mem(self.address, _BME280_CTRL_HUM_ADDR, 4) 224 | ctrl_hum, _, ctrl_meas, config = unpack("> 4), 324 | "pressure": (press_msb << 12) | (press_lsb << 4) | (press_xlsb >> 4), 325 | "humidity": (hum_msb << 8) | (hum_lsb), 326 | } 327 | 328 | ## 329 | # Float Implementations 330 | ## 331 | 332 | # def _compensate_temperature(self, adc_T: int) -> float: 333 | # """ 334 | # Output value of “25.0” equals 25.0 DegC. 335 | # 336 | # See the floating-point implementation in the reference library: 337 | # https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L884 338 | # """ 339 | # temperature_min = -40 340 | # temperature_max = 85 341 | # 342 | # var1 = (adc_T / 16384.0) - (self.cal_dig_T1 / 1024.0) 343 | # var1 = var1 * self.cal_dig_T2 344 | # 345 | # var2 = (adc_T / 131072.0) - (self.cal_dig_T1 / 8192.0) 346 | # var2 = var2 * var2 * self.cal_dig_T3 347 | # 348 | # self.cal_t_fine = int(var1 + var2) 349 | # 350 | # temperature = (var1 + var2) / 5120.0 351 | # 352 | # if temperature < temperature_min: 353 | # temperature = temperature_min 354 | # elif temperature > temperature_max: 355 | # temperature = temperature_max 356 | # 357 | # return temperature 358 | 359 | # def _compensate_pressure(self, adc_P: int) -> float: 360 | # """ 361 | # Output value of “96386.0” equals 96386 Pa = 963.86 hPa 362 | # 363 | # See the floating-point implementation in the reference library: 364 | # https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L912 365 | # """ 366 | # pressure_min = 30000.0 367 | # pressure_max = 110000.0 368 | # 369 | # var1 = (self.cal_t_fine / 2.0) - 64000.0 370 | # 371 | # var2 = var1 * var1 * self.cal_dig_P6 / 32768.0 372 | # var2 = var2 + (var1 * self.cal_dig_P5 * 2.0) 373 | # var2 = (var2 / 4.0) + (self.cal_dig_P4 * 65536.0) 374 | # 375 | # var3 = self.cal_dig_P3 * var1 * var1 / 524288.0 376 | # 377 | # var1 = (var3 + self.cal_dig_P2 * var1) / 524288.0 378 | # var1 = (1.0 + var1 / 32768.0) * self.cal_dig_P1 379 | # 380 | # # avoid exception caused by division by zero 381 | # if var1: 382 | # pressure = 1048576.0 - adc_P 383 | # pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1 384 | # var1 = self.cal_dig_P9 * pressure * pressure / 2147483648.0 385 | # var2 = pressure * self.cal_dig_P8 / 32768.0 386 | # pressure = pressure + (var1 + var2 + self.cal_dig_P7) / 16.0 387 | # 388 | # if pressure < pressure_min: 389 | # pressure = pressure_min 390 | # elif pressure > pressure_max: 391 | # pressure = pressure_max 392 | # 393 | # else: 394 | # # Invalid case 395 | # pressure = pressure_min 396 | # 397 | # return pressure 398 | 399 | # def _compensate_humidity(self, adc_H: int) -> float: 400 | # """ 401 | # Output value between 0.0 and 100.0, where 100.0 is 100%RH 402 | # 403 | # See the floating-point implementation in the reference library: 404 | # https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L952 405 | # """ 406 | # humidity_min = 0.0 407 | # humidity_max = 100.0 408 | # 409 | # var1 = self.cal_t_fine - 76800.0 410 | # 411 | # var2 = self.cal_dig_H4 * 64.0 + (self.cal_dig_H5 / 16384.0) * var1 412 | # 413 | # var3 = adc_H - var2 414 | # 415 | # var4 = self.cal_dig_H2 / 65536.0 416 | # 417 | # var5 = 1.0 + (self.cal_dig_H3 / 67108864.0) * var1 418 | # 419 | # var6 = 1.0 + (self.cal_dig_H6 / 67108864.0) * var1 * var5 420 | # var6 = var3 * var4 * (var5 * var6) 421 | # 422 | # humidity = var6 * (1.0 - self.cal_dig_H1 * var6 / 524288.0) 423 | # 424 | # if humidity > humidity_max: 425 | # humidity = humidity_max 426 | # elif humidity < humidity_min: 427 | # humidity = humidity_min 428 | # 429 | # return humidity 430 | 431 | ## 432 | # 32-Bit Integer Implementations 433 | ## 434 | 435 | def _compensate_temperature(self, adc_T: int) -> float: 436 | """ 437 | Output value of “25.0” equals 25.0 DegC. 438 | 439 | See the integer implementation in the data sheet, section 4.2.3 440 | And the reference library: 441 | https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L987 442 | """ 443 | temperature_min = -4000 444 | temperature_max = 8500 445 | 446 | var1 = (((adc_T // 8) - (self.cal_dig_T1 * 2)) * self.cal_dig_T2) // 2048 447 | 448 | var2 = (((((adc_T // 16) - self.cal_dig_T1) * ((adc_T // 16) - self.cal_dig_T1)) // 4096) * self.cal_dig_T3) // 16384 449 | 450 | self.cal_t_fine = var1 + var2 451 | 452 | temperature = (self.cal_t_fine * 5 + 128) // 256 453 | 454 | if temperature < temperature_min: 455 | temperature = temperature_min 456 | elif temperature > temperature_max: 457 | temperature = temperature_max 458 | 459 | return temperature / 100 460 | 461 | def _compensate_pressure(self, adc_P: int) -> float: 462 | """ 463 | Output value of “96386.0” equals 96386 Pa = 963.86 hPa 464 | 465 | See the 32-bit integer implementation in the data sheet, section 4.2.3 466 | And the reference library: 467 | https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#L1059 468 | 469 | Note that there's a 64-bit version of this function in the reference 470 | library on line 1016 that we're leaving unimplemented. 471 | """ 472 | pressure_min = 30000 473 | pressure_max = 110000 474 | 475 | var1 = (self.cal_t_fine // 2) - 64000 476 | 477 | var2 = (((var1 // 4) * (var1 // 4)) // 2048) * self.cal_dig_P6 478 | var2 = var2 + ((var1 * self.cal_dig_P5) * 2) 479 | var2 = (var2 // 4) + (self.cal_dig_P4 * 65536) 480 | 481 | var3 = (self.cal_dig_P3 * (((var1 // 4) * (var1 // 4)) // 8192)) // 8 482 | 483 | var4 = (self.cal_dig_P2 * var1) // 2 484 | 485 | var1 = (var3 + var4) // 262144 486 | var1 = ((32768 + var1) * self.cal_dig_P1) // 32768 487 | 488 | # avoid exception caused by division by zero 489 | if var1: 490 | var5 = 1048576 - adc_P 491 | 492 | pressure = (var5 - (var2 // 4096)) * 3125 493 | 494 | if pressure < 0x80000000: 495 | pressure = (pressure << 1) // var1 496 | else: 497 | pressure = (pressure // var1) * 2 498 | 499 | var1 = (self.cal_dig_P9 * (((pressure // 8) * (pressure // 8)) // 8192)) // 4096 500 | 501 | var2 = (((pressure // 4)) * self.cal_dig_P8) // 8192 502 | 503 | pressure = pressure + ((var1 + var2 + self.cal_dig_P7) // 16) 504 | 505 | if pressure < pressure_min: 506 | pressure = pressure_min 507 | elif pressure > pressure_max: 508 | pressure = pressure_max 509 | 510 | else: 511 | # Invalid case 512 | pressure = pressure_min 513 | 514 | return pressure 515 | 516 | def _compensate_humidity(self, adc_H: int) -> float: 517 | """ 518 | Output value between 0.0 and 100.0, where 100.0 is 100%RH 519 | 520 | See the floating-point implementation in the reference library: 521 | https://github.com/BoschSensortec/BME280_driver/blob/bme280_v3.3.4/bme280.c#1108 522 | """ 523 | 524 | humidity_max = 102400 525 | 526 | var1 = self.cal_t_fine - 76800 527 | 528 | var2 = adc_H * 16384 529 | 530 | var3 = self.cal_dig_H4 * 1048576 531 | 532 | var4 = self.cal_dig_H5 * var1 533 | 534 | var5 = (((var2 - var3) - var4) + 16384) // 32768 535 | 536 | var2 = (var1 * self.cal_dig_H6) // 1024 537 | 538 | var3 = (var1 * self.cal_dig_H3) // 2048 539 | 540 | var4 = ((var2 * (var3 + 32768)) // 1024) + 2097152 541 | 542 | var2 = ((var4 * self.cal_dig_H2) + 8192) // 16384 543 | 544 | var3 = var5 * var2 545 | 546 | var4 = ((var3 // 32768) * (var3 // 32768)) // 128 547 | 548 | var5 = var3 - ((var4 * self.cal_dig_H1) // 16) 549 | if var5 < 0: 550 | var5 = 0 551 | if var5 > 419430400: 552 | var5 = 419430400 553 | 554 | humidity = var5 // 4096 555 | 556 | if (humidity > humidity_max): 557 | humidity = humidity_max 558 | 559 | return humidity / 1024 560 | --------------------------------------------------------------------------------