1
0
mirror of https://github.com/cmur2/python-bme680.git synced 2024-06-30 14:34:47 +02:00

Compare commits

..

No commits in common. "master" and "v1.0.4" have entirely different histories.

30 changed files with 236 additions and 891 deletions

View File

@ -1,36 +0,0 @@
---
name: ci
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
schedule:
- cron: '47 4 * * 4' # weekly on thursday morning
jobs:
build:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
python-version:
- '3.7'
- '3.9'
- '3.10'
- '3.11'
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade setuptools tox
- name: Test
working-directory: library
run: |
tox -e py

4
.gitignore vendored
View File

@ -19,7 +19,3 @@ library/debian/
pip-log.txt pip-log.txt
pip-delete-this-directory.txt pip-delete-this-directory.txt
.DS_Store .DS_Store
.vscode/
.coverage
.tox/
.pytest_cache/

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Pimoroni Ltd
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.

View File

@ -1,44 +0,0 @@
.PHONY: usage install uninstall
usage:
@echo "Usage: make <target>, where target is one of:\n"
@echo "install: install the library locally from source"
@echo "uninstall: uninstall the local library"
@echo "python-readme: generate library/README.rst from README.md"
@echo "python-wheels: build python .whl files for distribution"
@echo "python-sdist: build python source distribution"
@echo "python-clean: clean python build and dist directories"
@echo "python-dist: build all python distribution files"
install:
./install.sh
uninstall:
./uninstall.sh
python-readme: library/README.rst
python-license: library/LICENSE.txt
library/README.rst: README.md
pandoc --from=markdown --to=rst -o library/README.rst README.md
library/LICENSE.txt: LICENSE
cp LICENSE library/LICENSE.txt
python-wheels: python-readme python-license
cd library; python3 setup.py bdist_wheel
cd library; python setup.py bdist_wheel
python-sdist: python-readme python-license
cd library; python setup.py sdist
python-clean:
-rm -r library/dist
-rm -r library/build
-rm -r library/*.egg-info
python-dist: python-clean python-wheels python-sdist
ls library/dist
python-deploy: python-dist
twine upload library/dist/*

View File

@ -1,20 +1,40 @@
# BME680 # BME680
![ci](https://github.com/cmur2/python-bme680/workflows/ci/badge.svg?branch=master)
https://shop.pimoroni.com/products/bme680 https://shop.pimoroni.com/products/bme680
The state-of-the-art BME680 breakout lets you measure temperature, pressure, humidity, and indoor air quality. The state-of-the-art BME680 breakout lets you measure temperature, pressure, humidity, and indoor air quality.
## About this Fork ## Installing
In general this fork tries to improve some details of the [original pimoroni/bme680-python repo](https://github.com/pimoroni/bme680-python): ### Full install (recommended):
- use standard unit for pressure measurement (Pascal instead Hectopascal, user can convert if he likes) We've created an easy installation script that will install all pre-requisites and get your BME680
- floating point precision for measurement compensations (user has all precision available to choose *based on measurement settings and datasheet* how much is trustable) up and running with minimal efforts. To run it, fire up Terminal which you'll find in Menu -> Accessories -> Terminal
- no redundant tweaking of measurement settings after soft reset on your Raspberry Pi desktop, as illustrated below:
## Development ![Finding the terminal](http://get.pimoroni.com/resources/github-repo-terminal.png)
In the new terminal window type the command exactly as it appears below (check for typos) and follow the on-screen instructions:
```bash
curl https://get.pimoroni.com/bme680 | bash
```
### Manual install:
#### Library install for Python 3:
```bash
sudo pip3 install bme680
```
#### Library install for Python 2:
```bash
sudo pip2 install bme680
```
### Development:
If you want to contribute, or like living on the edge of your seat by having the latest code, you should clone this repository, `cd` to the library directory, and run: If you want to contribute, or like living on the edge of your seat by having the latest code, you should clone this repository, `cd` to the library directory, and run:
@ -27,5 +47,5 @@ In all cases you will have to enable the i2c bus.
## Documentation & Support ## Documentation & Support
* Guides and tutorials - https://learn.pimoroni.com/bme680-breakout * Guides and tutorials - https://learn.pimoroni.com/bme680
* Get help - http://forums.pimoroni.com/c/support * Get help - http://forums.pimoroni.com/c/support

View File

@ -1,57 +0,0 @@
#!/usr/bin/env python
import time
import bme680
from subprocess import PIPE, Popen
print("""compensated-temperature.py - Use the CPU temperature to compensate temperature
readings from the BME680 sensor. Method adapted from Initial State's Enviro pHAT
review: https://medium.com/@InitialState/tutorial-review-enviro-phat-for-raspberry-pi-4cd6d8c63441
Press Ctrl+C to exit!
""")
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
# These oversampling settings can be tweaked to
# change the balance between accuracy and noise in
# the data.
sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
# Gets the CPU temperature in degrees C
def get_cpu_temperature():
process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
output, _error = process.communicate()
return float(output[output.index(b'=') + 1:output.rindex(b"'")])
factor = 1.0 # Smaller numbers adjust temp down, vice versa
smooth_size = 10 # Dampens jitter due to rapid CPU temp changes
cpu_temps = []
while True:
if sensor.get_sensor_data():
cpu_temp = get_cpu_temperature()
cpu_temps.append(cpu_temp)
if len(cpu_temps) > smooth_size:
cpu_temps = cpu_temps[1:]
smoothed_cpu_temp = sum(cpu_temps) / float(len(cpu_temps))
raw_temp = sensor.data.temperature
comp_temp = raw_temp - ((smoothed_cpu_temp - raw_temp) / factor)
print("Compensated temperature: {:05.2f} *C".format(comp_temp))
time.sleep(1.0)

View File

@ -3,22 +3,19 @@
import bme680 import bme680
import time import time
print("""indoor-air-quality.py - Estimates indoor air quality. print("""Estimate indoor air quality
Runs the sensor for a burn-in period, then uses a Runs the sensor for a burn-in period, then uses a
combination of relative humidity and gas resistance combination of relative humidity and gas resistance
to estimate indoor air quality as a percentage. to estimate indoor air quality as a percentage.
Press Ctrl+C to exit! Press Ctrl+C to exit
""") """)
try: sensor = bme680.BME680()
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
# These oversampling settings can be tweaked to # These oversampling settings can be tweaked to
# change the balance between accuracy and noise in # change the balance between accuracy and noise in
# the data. # the data.
@ -32,7 +29,7 @@ sensor.set_gas_heater_temperature(320)
sensor.set_gas_heater_duration(150) sensor.set_gas_heater_duration(150)
sensor.select_gas_heater_profile(0) sensor.select_gas_heater_profile(0)
# start_time and curr_time ensure that the # start_time and curr_time ensure that the
# burn_in_time (in seconds) is kept track of. # burn_in_time (in seconds) is kept track of.
start_time = time.time() start_time = time.time()
@ -45,13 +42,13 @@ try:
# Collect gas resistance burn-in values, then use the average # Collect gas resistance burn-in values, then use the average
# of the last 50 values to set the upper limit for calculating # of the last 50 values to set the upper limit for calculating
# gas_baseline. # gas_baseline.
print('Collecting gas resistance burn-in data for 5 mins\n') print("Collecting gas resistance burn-in data for 5 mins\n")
while curr_time - start_time < burn_in_time: while curr_time - start_time < burn_in_time:
curr_time = time.time() curr_time = time.time()
if sensor.get_sensor_data() and sensor.data.heat_stable: if sensor.get_sensor_data() and sensor.data.heat_stable:
gas = sensor.data.gas_resistance gas = sensor.data.gas_resistance
burn_in_data.append(gas) burn_in_data.append(gas)
print('Gas: {0} Ohms'.format(gas)) print("Gas: {0} Ohms".format(gas))
time.sleep(1) time.sleep(1)
gas_baseline = sum(burn_in_data[-50:]) / 50.0 gas_baseline = sum(burn_in_data[-50:]) / 50.0
@ -59,13 +56,11 @@ try:
# Set the humidity baseline to 40%, an optimal indoor humidity. # Set the humidity baseline to 40%, an optimal indoor humidity.
hum_baseline = 40.0 hum_baseline = 40.0
# This sets the balance between humidity and gas reading in the # This sets the balance between humidity and gas reading in the
# calculation of air_quality_score (25:75, humidity:gas) # calculation of air_quality_score (25:75, humidity:gas)
hum_weighting = 0.25 hum_weighting = 0.25
print('Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n'.format( print("Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n".format(gas_baseline, hum_baseline))
gas_baseline,
hum_baseline))
while True: while True:
if sensor.get_sensor_data() and sensor.data.heat_stable: if sensor.get_sensor_data() and sensor.data.heat_stable:
@ -77,31 +72,22 @@ try:
# Calculate hum_score as the distance from the hum_baseline. # Calculate hum_score as the distance from the hum_baseline.
if hum_offset > 0: if hum_offset > 0:
hum_score = (100 - hum_baseline - hum_offset) hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)
hum_score /= (100 - hum_baseline)
hum_score *= (hum_weighting * 100)
else: else:
hum_score = (hum_baseline + hum_offset) hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)
hum_score /= hum_baseline
hum_score *= (hum_weighting * 100)
# Calculate gas_score as the distance from the gas_baseline. # Calculate gas_score as the distance from the gas_baseline.
if gas_offset > 0: if gas_offset > 0:
gas_score = (gas / gas_baseline) gas_score = (gas / gas_baseline) * (100 - (hum_weighting * 100))
gas_score *= (100 - (hum_weighting * 100))
else: else:
gas_score = 100 - (hum_weighting * 100) gas_score = 100 - (hum_weighting * 100)
# Calculate air_quality_score. # Calculate air_quality_score.
air_quality_score = hum_score + gas_score air_quality_score = hum_score + gas_score
print('Gas: {0:.2f} Ohms,humidity: {1:.2f} %RH,air quality: {2:.2f}'.format( print("Gas: {0:.2f} Ohms,humidity: {1:.2f} %RH,air quality: {2:.2f}".format(gas, hum, air_quality_score))
gas,
hum,
air_quality_score))
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -3,30 +3,21 @@
import bme680 import bme680
import time import time
print("""read-all.py - Displays temperature, pressure, humidity, and gas. sensor = bme680.BME680()
Press Ctrl+C to exit!
""")
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
# These calibration data can safely be commented # These calibration data can safely be commented
# out, if desired. # out, if desired.
print('Calibration data:') print("Calibration data:")
for name in dir(sensor.calibration_data): for name in dir(sensor.calibration_data):
if not name.startswith('_'): if not name.startswith('_'):
value = getattr(sensor.calibration_data, name) value = getattr(sensor.calibration_data, name)
if isinstance(value, int): if isinstance(value, int):
print('{}: {}'.format(name, value)) print("{}: {}".format(name, value))
# These oversampling settings can be tweaked to # These oversampling settings can be tweaked to
# change the balance between accuracy and noise in # change the balance between accuracy and noise in
# the data. # the data.
@ -36,12 +27,12 @@ sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3) sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_gas_status(bme680.ENABLE_GAS_MEAS) sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
print('\n\nInitial reading:') print("\n\nInitial reading:")
for name in dir(sensor.data): for name in dir(sensor.data):
value = getattr(sensor.data, name) value = getattr(sensor.data, name)
if not name.startswith('_'): if not name.startswith('_'):
print('{}: {}'.format(name, value)) print("{}: {}".format(name, value))
sensor.set_gas_heater_temperature(320) sensor.set_gas_heater_temperature(320)
sensor.set_gas_heater_duration(150) sensor.set_gas_heater_duration(150)
@ -52,19 +43,14 @@ sensor.select_gas_heater_profile(0)
# sensor.set_gas_heater_profile(200, 150, nb_profile=1) # sensor.set_gas_heater_profile(200, 150, nb_profile=1)
# sensor.select_gas_heater_profile(1) # sensor.select_gas_heater_profile(1)
print('\n\nPolling:') print("\n\nPolling:")
try: try:
while True: while True:
if sensor.get_sensor_data(): if sensor.get_sensor_data():
output = '{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH'.format( output = "{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH".format(sensor.data.temperature, sensor.data.pressure, sensor.data.humidity)
sensor.data.temperature,
sensor.data.pressure / 100.0,
sensor.data.humidity)
if sensor.data.heat_stable: if sensor.data.heat_stable:
print('{0},{1} Ohms'.format( print("{0},{1} Ohms".format(output, sensor.data.gas_resistance))
output,
sensor.data.gas_resistance))
else: else:
print(output) print(output)

View File

@ -1,8 +0,0 @@
[flake8]
ignore =
E501 # line too long
F403
F405
# Don't require docstrings in example code
D100 # Missing docstring in public module
D103 # Missing docstring in public function

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
import bme680 import bme680
import time
print("""temperature-pressure-humidity.py - Displays temperature, pressure, and humidity. print("""Display Temperature, Pressure and Humidity
If you don't need gas readings, then you can read temperature, If you don't need gas readings, then you can read temperature,
pressure and humidity quickly. pressure and humidity quickly.
@ -11,10 +11,7 @@ Press Ctrl+C to exit
""") """)
try: sensor = bme680.BME680()
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
# These oversampling settings can be tweaked to # These oversampling settings can be tweaked to
# change the balance between accuracy and noise in # change the balance between accuracy and noise in
@ -25,15 +22,15 @@ sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X) sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3) sensor.set_filter(bme680.FILTER_SIZE_3)
print('Polling:') print("Polling:")
try: try:
while True: while True:
if sensor.get_sensor_data(): if sensor.get_sensor_data():
output = '{0:.2f} C,{1:.2f} hPa,{2:.3f} %RH'.format(
sensor.data.temperature, output = "{0:.2f} C,{1:.2f} hPa,{2:.3f} %RH".format(sensor.data.temperature, sensor.data.pressure, sensor.data.humidity)
sensor.data.pressure / 100.0,
sensor.data.humidity)
print(output) print(output)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

View File

@ -1,50 +0,0 @@
#!/usr/bin/env python
import bme680
print("""temperature-offset.py - Displays temperature, pressure, and humidity with different offsets.
Press Ctrl+C to exit!
""")
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
# These oversampling settings can be tweaked to
# change the balance between accuracy and noise in
# the data.
sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
def display_data(offset=0):
sensor.set_temp_offset(offset)
sensor.get_sensor_data()
output = '{0:.2f} C, {1:.2f} hPa, {2:.3f} %RH'.format(
sensor.data.temperature,
sensor.data.pressure / 100.0,
sensor.data.humidity)
print(output)
print('')
print('Initial readings')
display_data()
print('SET offset 4 degrees celsius')
display_data(4)
print('SET offset -1.87 degrees celsius')
display_data(-1.87)
print('SET offset -100 degrees celsius')
display_data(-100)
print('SET offset 0 degrees celsius')
display_data(0)

View File

@ -1,22 +0,0 @@
#!/bin/bash
printf "BME680 Python Library: Installer\n\n"
if [ $(id -u) -ne 0 ]; then
printf "Script must be run as root. Try 'sudo ./install.sh'\n"
exit 1
fi
cd library
printf "Installing for Python 2..\n"
python setup.py install
if [ -f "/usr/bin/python3" ]; then
printf "Installing for Python 3..\n"
python3 setup.py install
fi
cd ..
printf "Done!\n"

View File

@ -1,4 +0,0 @@
[run]
source = bme680
omit =
.tox/*

View File

@ -1,8 +1,3 @@
1.0.5
-----
* New: set_temp_offset to calibrate temperature offset in degrees C
1.0.4 1.0.4
----- -----

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Pimoroni Ltd
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.

View File

@ -1,5 +1,5 @@
include CHANGELOG.txt include CHANGELOG.txt
include LICENSE.txt include LICENSE.txt
include README.rst include README.txt
include setup.py include setup.py
recursive-include bme680 *.py include bme680.py

View File

@ -1,80 +0,0 @@
BME680
======
|Build Status| |Coverage Status| |PyPi Package| |Python Versions|
https://shop.pimoroni.com/products/bme680
The state-of-the-art BME680 breakout lets you measure temperature,
pressure, humidity, and indoor air quality.
Installing
----------
Full install (recommended):
~~~~~~~~~~~~~~~~~~~~~~~~~~~
We've created an easy installation script that will install all
pre-requisites and get your BME680 up and running with minimal efforts.
To run it, fire up Terminal which you'll find in Menu -> Accessories ->
Terminal on your Raspberry Pi desktop, as illustrated below:
.. figure:: http://get.pimoroni.com/resources/github-repo-terminal.png
:alt: Finding the terminal
Finding the terminal
In the new terminal window type the command exactly as it appears below
(check for typos) and follow the on-screen instructions:
.. code:: bash
curl https://get.pimoroni.com/bme680 | bash
Manual install:
~~~~~~~~~~~~~~~
Library install for Python 3:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: bash
sudo pip3 install bme680
Library install for Python 2:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code:: bash
sudo pip2 install bme680
Development:
~~~~~~~~~~~~
If you want to contribute, or like living on the edge of your seat by
having the latest code, you should clone this repository, ``cd`` to the
library directory, and run:
.. code:: bash
sudo python3 setup.py install
(or ``sudo python setup.py install`` whichever your primary Python
environment may be)
In all cases you will have to enable the i2c bus.
Documentation & Support
-----------------------
- Guides and tutorials - https://learn.pimoroni.com/bme680
- Get help - http://forums.pimoroni.com/c/support
.. |Build Status| image:: https://travis-ci.org/pimoroni/bme680-python.svg?branch=master
:target: https://travis-ci.org/pimoroni/bme680-python
.. |Coverage Status| image:: https://coveralls.io/repos/github/pimoroni/bme680-python/badge.svg?branch=master
:target: https://coveralls.io/github/pimoroni/bme680-python?branch=master
.. |PyPi Package| image:: https://img.shields.io/pypi/v/bme680.svg
:target: https://pypi.python.org/pypi/bme680
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/bme680.svg
:target: https://pypi.python.org/pypi/bme680

Before

Width:  |  Height:  |  Size: 2.2 KiB

0
library/README.txt Normal file
View File

View File

@ -1,24 +1,11 @@
"""BME680 Temperature, Pressure, Humidity & Gas Sensor.""" from .constants import *
from .constants import lookupTable1, lookupTable2
from .constants import BME680Data
from . import constants
import math import math
import time import time
__version__ = '1.0.5' __version__ = '1.0.4'
# Export constants to global namespace
# so end-users can "from BME680 import NAME"
if hasattr(constants, '__dict__'):
for key in constants.__dict__:
value = constants.__dict__[key]
if key not in globals():
globals()[key] = value
class BME680(BME680Data): class BME680(BME680Data):
"""BOSCH BME680. """BOSCH BME680
Gas, pressure, temperature and humidity sensor. Gas, pressure, temperature and humidity sensor.
@ -26,14 +13,7 @@ class BME680(BME680Data):
:param i2c_device: Optional smbus or compatible instance for facilitating i2c communications. :param i2c_device: Optional smbus or compatible instance for facilitating i2c communications.
""" """
def __init__(self, i2c_addr=I2C_ADDR_PRIMARY, i2c_device=None):
def __init__(self, i2c_addr=constants.I2C_ADDR_PRIMARY, i2c_device=None):
"""Initialise BME680 sensor instance and verify device presence.
:param i2c_addr: i2c address of BME680
:param i2c_device: Optional SMBus-compatible instance for i2c transport
"""
BME680Data.__init__(self) BME680Data.__init__(self)
self.i2c_addr = i2c_addr self.i2c_addr = i2c_addr
@ -42,50 +22,43 @@ class BME680(BME680Data):
import smbus import smbus
self._i2c = smbus.SMBus(1) self._i2c = smbus.SMBus(1)
self.chip_id = self._get_regs(constants.CHIP_ID_ADDR, 1) self.chip_id = self._get_regs(CHIP_ID_ADDR, 1)
if self.chip_id != constants.CHIP_ID: if self.chip_id != CHIP_ID:
raise RuntimeError('BME680 Not Found. Invalid CHIP ID: 0x{0:02x}'.format(self.chip_id)) raise RuntimeError("BME680 Not Found. Invalid CHIP ID: 0x{0:02x}".format(self.chip_id))
self.soft_reset() self.soft_reset()
self.set_power_mode(constants.SLEEP_MODE) self.set_power_mode(SLEEP_MODE)
self._get_calibration_data() self._get_calibration_data()
self.set_temp_offset(0) self.set_humidity_oversample(OS_2X)
self.ambient_temperature = 25 # only for gas heater temperature, nearly irrelevant self.set_pressure_oversample(OS_4X)
self.set_temperature_oversample(OS_8X)
self.set_filter(FILTER_SIZE_3)
self.set_gas_status(ENABLE_GAS_MEAS)
self.get_sensor_data()
def _get_calibration_data(self): def _get_calibration_data(self):
"""Retrieve the sensor calibration data and store it in .calibration_data.""" """Retrieves the sensor calibration data and stores it in .calibration_data"""
calibration = self._get_regs(constants.COEFF_ADDR1, constants.COEFF_ADDR1_LEN) calibration = self._get_regs(COEFF_ADDR1, COEFF_ADDR1_LEN)
calibration += self._get_regs(constants.COEFF_ADDR2, constants.COEFF_ADDR2_LEN) calibration += self._get_regs(COEFF_ADDR2, COEFF_ADDR2_LEN)
heat_range = self._get_regs(constants.ADDR_RES_HEAT_RANGE_ADDR, 1) heat_range = self._get_regs(ADDR_RES_HEAT_RANGE_ADDR, 1)
heat_value = constants.twos_comp(self._get_regs(constants.ADDR_RES_HEAT_VAL_ADDR, 1), bits=8) heat_value = twos_comp(self._get_regs(ADDR_RES_HEAT_VAL_ADDR, 1), bits=8)
sw_error = constants.twos_comp(self._get_regs(constants.ADDR_RANGE_SW_ERR_ADDR, 1), bits=8) sw_error = twos_comp(self._get_regs(ADDR_RANGE_SW_ERR_ADDR, 1), bits=8)
self.calibration_data.set_from_array(calibration) self.calibration_data.set_from_array(calibration)
self.calibration_data.set_other(heat_range, heat_value, sw_error) self.calibration_data.set_other(heat_range, heat_value, sw_error)
def soft_reset(self): def soft_reset(self):
"""Trigger a soft reset.""" """Initiate a soft reset"""
self._set_regs(constants.SOFT_RESET_ADDR, constants.SOFT_RESET_CMD) self._set_regs(SOFT_RESET_ADDR, SOFT_RESET_CMD)
time.sleep(constants.RESET_PERIOD / 1000.0) time.sleep(RESET_PERIOD / 1000.0)
def set_temp_offset(self, value):
"""Set temperature offset in celsius.
If set, the temperature t_fine will be increased by given value in celsius.
:param value: Temperature offset in Celsius, eg. 4, -8, 1.25
"""
if value == 0:
self.offset_temp_in_t_fine = 0
else:
self.offset_temp_in_t_fine = int(math.copysign((((int(abs(value) * 100)) << 8) - 128) / 5, value))
def set_humidity_oversample(self, value): def set_humidity_oversample(self, value):
"""Set humidity oversampling. """Set humidity oversampling
A higher oversampling value means more stable sensor readings, A higher oversampling value means more stable sensor readings,
with less noise and jitter. with less noise and jitter.
@ -96,15 +69,15 @@ class BME680(BME680Data):
""" """
self.tph_settings.os_hum = value self.tph_settings.os_hum = value
self._set_bits(constants.CONF_OS_H_ADDR, constants.OSH_MSK, constants.OSH_POS, value) self._set_bits(CONF_OS_H_ADDR, OSH_MSK, OSH_POS, value)
def get_humidity_oversample(self): def get_humidity_oversample(self):
"""Get humidity oversampling.""" """Get humidity oversampling"""
return (self._get_regs(constants.CONF_OS_H_ADDR, 1) & constants.OSH_MSK) >> constants.OSH_POS return (self._get_regs(CONF_OS_H_ADDR, 1) & OSH_MSK) >> OSH_POS
def set_pressure_oversample(self, value): def set_pressure_oversample(self, value):
"""Set temperature oversampling. """Set temperature oversampling
A higher oversampling value means more stable sensor readings, A higher oversampling value means more stable sensor readings,
with less noise and jitter. with less noise and jitter.
@ -112,18 +85,18 @@ class BME680(BME680Data):
causing a slower response time to fast transients. causing a slower response time to fast transients.
:param value: Oversampling value, one of: OS_NONE, OS_1X, OS_2X, OS_4X, OS_8X, OS_16X :param value: Oversampling value, one of: OS_NONE, OS_1X, OS_2X, OS_4X, OS_8X, OS_16X
""" """
self.tph_settings.os_pres = value self.tph_settings.os_pres = value
self._set_bits(constants.CONF_T_P_MODE_ADDR, constants.OSP_MSK, constants.OSP_POS, value) self._set_bits(CONF_T_P_MODE_ADDR, OSP_MSK, OSP_POS, value)
def get_pressure_oversample(self): def get_pressure_oversample(self):
"""Get pressure oversampling.""" """Get pressure oversampling"""
return (self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) & constants.OSP_MSK) >> constants.OSP_POS return (self._get_regs(CONF_T_P_MODE_ADDR, 1) & OSP_MSK) >> OSP_POS
def set_temperature_oversample(self, value): def set_temperature_oversample(self, value):
"""Set pressure oversampling. """Set pressure oversampling
A higher oversampling value means more stable sensor readings, A higher oversampling value means more stable sensor readings,
with less noise and jitter. with less noise and jitter.
@ -131,18 +104,18 @@ class BME680(BME680Data):
causing a slower response time to fast transients. causing a slower response time to fast transients.
:param value: Oversampling value, one of: OS_NONE, OS_1X, OS_2X, OS_4X, OS_8X, OS_16X :param value: Oversampling value, one of: OS_NONE, OS_1X, OS_2X, OS_4X, OS_8X, OS_16X
""" """
self.tph_settings.os_temp = value self.tph_settings.os_temp = value
self._set_bits(constants.CONF_T_P_MODE_ADDR, constants.OST_MSK, constants.OST_POS, value) self._set_bits(CONF_T_P_MODE_ADDR, OST_MSK, OST_POS, value)
def get_temperature_oversample(self): def get_temperature_oversample(self):
"""Get temperature oversampling.""" """Get temperature oversampling"""
return (self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) & constants.OST_MSK) >> constants.OST_POS return (self._get_regs(CONF_T_P_MODE_ADDR, 1) & OST_MSK) >> OST_POS
def set_filter(self, value): def set_filter(self, value):
"""Set IIR filter size. """Set IIR filter size
Optionally remove short term fluctuations from the temperature and pressure readings, Optionally remove short term fluctuations from the temperature and pressure readings,
increasing their resolution but reducing their bandwidth. increasing their resolution but reducing their bandwidth.
@ -154,42 +127,40 @@ class BME680(BME680Data):
""" """
self.tph_settings.filter = value self.tph_settings.filter = value
self._set_bits(constants.CONF_ODR_FILT_ADDR, constants.FILTER_MSK, constants.FILTER_POS, value) self._set_bits(CONF_ODR_FILT_ADDR, FILTER_MSK, FILTER_POS, value)
def get_filter(self): def get_filter(self):
"""Get filter size.""" """Get filter size"""
return (self._get_regs(constants.CONF_ODR_FILT_ADDR, 1) & constants.FILTER_MSK) >> constants.FILTER_POS return (self._get_regs(CONF_ODR_FILT_ADDR, 1) & FILTER_MSK) >> FILTER_POS
def select_gas_heater_profile(self, value): def select_gas_heater_profile(self, value):
"""Set current gas sensor conversion profile. """Set current gas sensor conversion profile: 0 to 9
Select one of the 10 configured heating durations/set points. Select one of the 10 configured heating durations/set points.
:param value: Profile index from 0 to 9
""" """
if value > constants.NBCONV_MAX or value < constants.NBCONV_MIN: if value > NBCONV_MAX or value < NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(value, constants.NBCONV_MIN, constants.NBCONV_MAX)) raise ValueError("Profile '{}' should be between {} and {}".format(value, NBCONV_MIN, NBCONV_MAX))
self.gas_settings.nb_conv = value self.gas_settings.nb_conv = value
self._set_bits(constants.CONF_ODR_RUN_GAS_NBC_ADDR, constants.NBCONV_MSK, constants.NBCONV_POS, value) self._set_bits(CONF_ODR_RUN_GAS_NBC_ADDR, NBCONV_MSK, NBCONV_POS, value)
def get_gas_heater_profile(self): def get_gas_heater_profile(self):
"""Get gas sensor conversion profile: 0 to 9.""" """Get gas sensor conversion profile: 0 to 9"""
return self._get_regs(constants.CONF_ODR_RUN_GAS_NBC_ADDR, 1) & constants.NBCONV_MSK return self._get_regs(CONF_ODR_RUN_GAS_NBC_ADDR, 1) & NBCONV_MSK
def set_gas_status(self, value): def set_gas_status(self, value):
"""Enable/disable gas sensor.""" """Enable/disable gas sensor"""
self.gas_settings.run_gas = value self.gas_settings.run_gas = value
self._set_bits(constants.CONF_ODR_RUN_GAS_NBC_ADDR, constants.RUN_GAS_MSK, constants.RUN_GAS_POS, value) self._set_bits(CONF_ODR_RUN_GAS_NBC_ADDR, RUN_GAS_MSK, RUN_GAS_POS, value)
def get_gas_status(self): def get_gas_status(self):
"""Get the current gas status.""" """Get the current gas status"""
return (self._get_regs(constants.CONF_ODR_RUN_GAS_NBC_ADDR, 1) & constants.RUN_GAS_MSK) >> constants.RUN_GAS_POS return (self._get_regs(CONF_ODR_RUN_GAS_NBC_ADDR, 1) & RUN_GAS_MSK) >> RUN_GAS_POS
def set_gas_heater_profile(self, temperature, duration, nb_profile=0): def set_gas_heater_profile(self, temperature, duration, nb_profile=0):
"""Set temperature and duration of gas sensor heater. """Set temperature and duration of gas sensor heater
:param temperature: Target temperature in degrees celsius, between 200 and 400 :param temperature: Target temperature in degrees celsius, between 200 and 400
:param durarion: Target duration in milliseconds, between 1 and 4032 :param durarion: Target duration in milliseconds, between 1 and 4032
:param nb_profile: Target profile, between 0 and 9 :param nb_profile: Target profile, between 0 and 9
@ -199,23 +170,23 @@ class BME680(BME680Data):
self.set_gas_heater_duration(duration, nb_profile=nb_profile) self.set_gas_heater_duration(duration, nb_profile=nb_profile)
def set_gas_heater_temperature(self, value, nb_profile=0): def set_gas_heater_temperature(self, value, nb_profile=0):
"""Set gas sensor heater temperature. """Set gas sensor heater temperature
:param value: Target temperature in degrees celsius, between 200 and 400 :param value: Target temperature in degrees celsius, between 200 and 400
When setting an nb_profile other than 0, When setting an nb_profile other than 0,
make sure to select it with select_gas_heater_profile. make sure to select it with select_gas_heater_profile.
""" """
if nb_profile > constants.NBCONV_MAX or value < constants.NBCONV_MIN: if nb_profile > NBCONV_MAX or value < NBCONV_MIN:
raise ValueError('Profile "{}" should be between {} and {}'.format(nb_profile, constants.NBCONV_MIN, constants.NBCONV_MAX)) raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, NBCONV_MIN, NBCONV_MAX))
self.gas_settings.heatr_temp = value self.gas_settings.heatr_temp = value
temp = int(self._calc_heater_resistance(self.gas_settings.heatr_temp)) temp = int(self._calc_heater_resistance(self.gas_settings.heatr_temp))
self._set_regs(constants.RES_HEAT0_ADDR + nb_profile, temp) self._set_regs(RES_HEAT0_ADDR + nb_profile, temp)
def set_gas_heater_duration(self, value, nb_profile=0): def set_gas_heater_duration(self, value, nb_profile=0):
"""Set gas sensor heater duration. """Set gas sensor heater duration
Heating durations between 1 ms and 4032 ms can be configured. Heating durations between 1 ms and 4032 ms can be configured.
Approximately 20-30 ms are necessary for the heater to reach the intended target temperature. Approximately 20-30 ms are necessary for the heater to reach the intended target temperature.
@ -224,70 +195,70 @@ class BME680(BME680Data):
When setting an nb_profile other than 0, When setting an nb_profile other than 0,
make sure to select it with select_gas_heater_profile. make sure to select it with select_gas_heater_profile.
""" """
if nb_profile > constants.NBCONV_MAX or value < constants.NBCONV_MIN: if nb_profile > NBCONV_MAX or value < NBCONV_MIN:
raise ValueError('Profile "{}" should be between {} and {}'.format(nb_profile, constants.NBCONV_MIN, constants.NBCONV_MAX)) raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, NBCONV_MIN, NBCONV_MAX))
self.gas_settings.heatr_dur = value self.gas_settings.heatr_dur = value
temp = self._calc_heater_duration(self.gas_settings.heatr_dur) temp = self._calc_heater_duration(self.gas_settings.heatr_dur)
self._set_regs(constants.GAS_WAIT0_ADDR + nb_profile, temp) self._set_regs(GAS_WAIT0_ADDR + nb_profile, temp)
def set_power_mode(self, value, blocking=True): def set_power_mode(self, value, blocking=True):
"""Set power mode.""" """Set power mode"""
if value not in (constants.SLEEP_MODE, constants.FORCED_MODE): if value not in (SLEEP_MODE, FORCED_MODE):
raise ValueError('Power mode should be one of SLEEP_MODE or FORCED_MODE') print("Power mode should be one of SLEEP_MODE or FORCED_MODE")
self.power_mode = value self.power_mode = value
self._set_bits(constants.CONF_T_P_MODE_ADDR, constants.MODE_MSK, constants.MODE_POS, value) self._set_bits(CONF_T_P_MODE_ADDR, MODE_MSK, MODE_POS, value)
while blocking and self.get_power_mode() != self.power_mode: while blocking and self.get_power_mode() != self.power_mode:
time.sleep(constants.POLL_PERIOD_MS / 1000.0) time.sleep(POLL_PERIOD_MS / 1000.0)
def get_power_mode(self): def get_power_mode(self):
"""Get power mode.""" """Get power mode"""
self.power_mode = self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) self.power_mode = self._get_regs(CONF_T_P_MODE_ADDR, 1)
return self.power_mode return self.power_mode
def get_sensor_data(self): def get_sensor_data(self):
"""Get sensor data. """Get sensor data.
Stores data in .data and returns True upon success. Stores data in .data and returns True upon success.
""" """
self.set_power_mode(constants.FORCED_MODE) self.set_power_mode(FORCED_MODE)
for attempt in range(10): for attempt in range(10):
status = self._get_regs(constants.FIELD0_ADDR, 1) status = self._get_regs(FIELD0_ADDR, 1)
if (status & constants.NEW_DATA_MSK) == 0: if (status & NEW_DATA_MSK) == 0:
time.sleep(constants.POLL_PERIOD_MS / 1000.0) time.sleep(POLL_PERIOD_MS / 1000.0)
continue continue
regs = self._get_regs(constants.FIELD0_ADDR, constants.FIELD_LENGTH) regs = self._get_regs(FIELD0_ADDR, FIELD_LENGTH)
self.data.status = regs[0] & constants.NEW_DATA_MSK self.data.status = regs[0] & NEW_DATA_MSK
# Contains the nb_profile used to obtain the current measurement # Contains the nb_profile used to obtain the current measurement
self.data.gas_index = regs[0] & constants.GAS_INDEX_MSK self.data.gas_index = regs[0] & GAS_INDEX_MSK
self.data.meas_index = regs[1] self.data.meas_index = regs[1]
adc_pres = (regs[2] << 12) | (regs[3] << 4) | (regs[4] >> 4) adc_pres = (regs[2] << 12) | (regs[3] << 4) | (regs[4] >> 4)
adc_temp = (regs[5] << 12) | (regs[6] << 4) | (regs[7] >> 4) adc_temp = (regs[5] << 12) | (regs[6] << 4) | (regs[7] >> 4)
adc_hum = (regs[8] << 8) | regs[9] adc_hum = (regs[8] << 8) | regs[9]
adc_gas_res = (regs[13] << 2) | (regs[14] >> 6) adc_gas_res = (regs[13] << 2) | (regs[14] >> 6)
gas_range = regs[14] & constants.GAS_RANGE_MSK gas_range = regs[14] & GAS_RANGE_MSK
self.data.status |= regs[14] & constants.GASM_VALID_MSK self.data.status |= regs[14] & GASM_VALID_MSK
self.data.status |= regs[14] & constants.HEAT_STAB_MSK self.data.status |= regs[14] & HEAT_STAB_MSK
self.data.heat_stable = (self.data.status & constants.HEAT_STAB_MSK) > 0 self.data.heat_stable = (self.data.status & HEAT_STAB_MSK) > 0
temperature = self._calc_temperature(adc_temp) temperature = self._calc_temperature(adc_temp)
self.data.temperature = temperature / 100.0 self.data.temperature = temperature / 100.0
self.ambient_temperature = temperature # Saved for heater calc self.ambient_temperature = temperature # Saved for heater calc
self.data.pressure = self._calc_pressure(adc_pres) self.data.pressure = self._calc_pressure(adc_pres) / 100.0
self.data.humidity = self._calc_humidity(adc_hum) / 1000.0 self.data.humidity = self._calc_humidity(adc_hum) / 1000.0
self.data.gas_resistance = self._calc_gas_resistance(adc_gas_res, gas_range) self.data.gas_resistance = self._calc_gas_resistance(adc_gas_res, gas_range)
return True return True
@ -295,106 +266,101 @@ class BME680(BME680Data):
return False return False
def _set_bits(self, register, mask, position, value): def _set_bits(self, register, mask, position, value):
"""Mask out and set one or more bits in a register.""" """Mask out and set one or more bits in a register"""
temp = self._get_regs(register, 1) temp = self._get_regs(register, 1)
temp &= ~mask temp &= ~mask
temp |= value << position temp |= value << position
self._set_regs(register, temp) self._set_regs(register, temp)
def _set_regs(self, register, value): def _set_regs(self, register, value):
"""Set one or more registers.""" """Set one or more registers"""
if isinstance(value, int): if isinstance(value, int):
self._i2c.write_byte_data(self.i2c_addr, register, value) self._i2c.write_byte_data(self.i2c_addr, register, value)
else: else:
self._i2c.write_i2c_block_data(self.i2c_addr, register, value) self._i2c.write_i2c_block_data(self.i2c_addr, register, value)
def _get_regs(self, register, length): def _get_regs(self, register, length):
"""Get one or more registers.""" """Get one or more registers"""
if length == 1: if length == 1:
return self._i2c.read_byte_data(self.i2c_addr, register) return self._i2c.read_byte_data(self.i2c_addr, register)
else: else:
return self._i2c.read_i2c_block_data(self.i2c_addr, register, length) return self._i2c.read_i2c_block_data(self.i2c_addr, register, length)
def _calc_temperature(self, temperature_adc): def _calc_temperature(self, temperature_adc):
"""Convert the raw temperature to degrees C using calibration_data.""" var1 = (temperature_adc >> 3) - (self.calibration_data.par_t1 << 1)
var1 = (temperature_adc / 8.0) - (self.calibration_data.par_t1 * 2.0) var2 = (var1 * self.calibration_data.par_t2) >> 11
var2 = (var1 * self.calibration_data.par_t2) / 2048.0 var3 = ((var1 >> 1) * (var1 >> 1)) >> 12
var3 = ((var1 / 2.0) * (var1 / 2.0)) / 4096.0 var3 = ((var3) * (self.calibration_data.par_t3 << 4)) >> 14
var3 = ((var3) * (self.calibration_data.par_t3 * 16.0)) / 16384.0
# Save teperature data for pressure calculations # Save teperature data for pressure calculations
self.calibration_data.t_fine = (var2 + var3) + self.offset_temp_in_t_fine self.calibration_data.t_fine = (var2 + var3)
calc_temp = (((self.calibration_data.t_fine * 5.0) + 128.0) / 256.0) calc_temp = (((self.calibration_data.t_fine * 5) + 128) >> 8)
return calc_temp return calc_temp
def _calc_pressure(self, pressure_adc): def _calc_pressure(self, pressure_adc):
"""Convert the raw pressure using calibration data.""" var1 = ((self.calibration_data.t_fine) >> 1) - 64000
var1 = (self.calibration_data.t_fine / 2.0) - 64000.0 var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) *
var2 = ((((var1 / 4.0) * (var1 / 4.0)) / 2048.0) * self.calibration_data.par_p6) >> 2
self.calibration_data.par_p6) / 4.0 var2 = var2 + ((var1 * self.calibration_data.par_p5) << 1)
var2 = var2 + ((var1 * self.calibration_data.par_p5) * 2.0) var2 = (var2 >> 2) + (self.calibration_data.par_p4 << 16)
var2 = (var2 / 4.0) + (self.calibration_data.par_p4 * 65536.0) var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13 ) *
var1 = (((((var1 / 4.0) * (var1 / 4.0)) / 8192.0) * ((self.calibration_data.par_p3 << 5)) >> 3) +
((self.calibration_data.par_p3 * 32.0)) / 8.0) + ((self.calibration_data.par_p2 * var1) >> 1))
((self.calibration_data.par_p2 * var1) / 2.0)) var1 = var1 >> 18
var1 = var1 / 262144.0
var1 = ((32768.0 + var1) * self.calibration_data.par_p1) / 32768.0 var1 = ((32768 + var1) * self.calibration_data.par_p1) >> 15
calc_pressure = 1048576.0 - pressure_adc calc_pressure = 1048576 - pressure_adc
calc_pressure = ((calc_pressure - (var2 / 4096.0)) * (3125.0)) calc_pressure = ((calc_pressure - (var2 >> 12)) * (3125))
if calc_pressure >= (1 << 31): if calc_pressure >= (1 << 31):
calc_pressure = ((calc_pressure / var1) * 2.0) calc_pressure = ((calc_pressure // var1) << 1)
else: else:
calc_pressure = ((calc_pressure * 2.0) / var1) calc_pressure = ((calc_pressure << 1) // var1)
var1 = (self.calibration_data.par_p9 * (((calc_pressure / 8.0) * var1 = (self.calibration_data.par_p9 * (((calc_pressure >> 3) *
(calc_pressure / 8.0)) / 8192.0)) / 4096.0 (calc_pressure >> 3)) >> 13)) >> 12
var2 = ((calc_pressure / 4.0) * var2 = ((calc_pressure >> 2) *
self.calibration_data.par_p8) / 8192.0 self.calibration_data.par_p8) >> 13
var3 = ((calc_pressure / 256.0) * (calc_pressure / 256.0) * var3 = ((calc_pressure >> 8) * (calc_pressure >> 8) *
(calc_pressure / 256.0) * (calc_pressure >> 8) *
self.calibration_data.par_p10) / 131072.0 self.calibration_data.par_p10) >> 17
calc_pressure = (calc_pressure) + ((var1 + var2 + var3 + calc_pressure = (calc_pressure) + ((var1 + var2 + var3 +
(self.calibration_data.par_p7 * 128.0)) / 16.0) (self.calibration_data.par_p7 << 7)) >> 4)
return calc_pressure return calc_pressure
def _calc_humidity(self, humidity_adc): def _calc_humidity(self, humidity_adc):
"""Convert the raw humidity using calibration data.""" temp_scaled = ((self.calibration_data.t_fine * 5) + 128) >> 8
temp_scaled = ((self.calibration_data.t_fine * 5.0) + 128.0) / 256.0 var1 = (humidity_adc - ((self.calibration_data.par_h1 * 16))) \
var1 = (humidity_adc - ((self.calibration_data.par_h1 * 16.0))) -\ - (((temp_scaled * self.calibration_data.par_h3) // (100)) >> 1)
(((temp_scaled * self.calibration_data.par_h3) / (100.0)) / 2.0) var2 = (self.calibration_data.par_h2
var2 = (self.calibration_data.par_h2 * * (((temp_scaled * self.calibration_data.par_h4) // (100))
(((temp_scaled * self.calibration_data.par_h4) / (100.0)) + + (((temp_scaled * ((temp_scaled * self.calibration_data.par_h5) // (100))) >> 6)
(((temp_scaled * ((temp_scaled * self.calibration_data.par_h5) / (100.0))) / 64.0) / // (100)) + (1 * 16384))) >> 10
(100.0)) + (1.0 * 16384.0))) / 1024.0
var3 = var1 * var2 var3 = var1 * var2
var4 = self.calibration_data.par_h6 * 128.0 var4 = self.calibration_data.par_h6 << 7
var4 = ((var4) + ((temp_scaled * self.calibration_data.par_h7) / (100.0))) / 16.0 var4 = ((var4) + ((temp_scaled * self.calibration_data.par_h7) // (100))) >> 4
var5 = ((var3 / 16384.0) * (var3 / 16384.0)) / 1024.0 var5 = ((var3 >> 14) * (var3 >> 14)) >> 10
var6 = (var4 * var5) / 2.0 var6 = (var4 * var5) >> 1
calc_hum = (((var3 + var6) / 1024.0) * (1000.0)) / 4096.0 calc_hum = (((var3 + var6) >> 10) * (1000)) >> 12
return min(max(calc_hum, 0.0), 100000.0) return min(max(calc_hum,0),100000)
def _calc_gas_resistance(self, gas_res_adc, gas_range): def _calc_gas_resistance(self, gas_res_adc, gas_range):
"""Convert the raw gas resistance using calibration data."""
var1 = ((1340 + (5 * self.calibration_data.range_sw_err)) * (lookupTable1[gas_range])) >> 16 var1 = ((1340 + (5 * self.calibration_data.range_sw_err)) * (lookupTable1[gas_range])) >> 16
var2 = (((gas_res_adc << 15) - (16777216)) + var1) var2 = (((gas_res_adc << 15) - (16777216)) + var1)
var3 = ((lookupTable2[gas_range] * var1) >> 9) var3 = ((lookupTable2[gas_range] * var1) >> 9)
calc_gas_res = ((var3 + (var2 >> 1)) / var2) calc_gas_res = ((var3 + (var2 >> 1)) / var2)
if calc_gas_res < 0: if calc_gas_res < 0:
calc_gas_res = (1 << 32) + calc_gas_res calc_gas_res = (1<<32) + calc_gas_res
return calc_gas_res return calc_gas_res
def _calc_heater_resistance(self, temperature): def _calc_heater_resistance(self, temperature):
"""Convert raw heater resistance using calibration data.""" temperature = min(max(temperature,200),400)
temperature = min(max(temperature, 200), 400)
var1 = ((self.ambient_temperature * self.calibration_data.par_gh3) / 1000) * 256 var1 = ((self.ambient_temperature * self.calibration_data.par_gh3) / 1000) * 256
var2 = (self.calibration_data.par_gh1 + 784) * (((((self.calibration_data.par_gh2 + 154009) * temperature * 5) / 100) + 3276800) / 10) var2 = (self.calibration_data.par_gh1 + 784) * (((((self.calibration_data.par_gh2 + 154009) * temperature * 5) / 100) + 3276800) / 10)
@ -407,7 +373,6 @@ class BME680(BME680Data):
return heatr_res return heatr_res
def _calc_heater_duration(self, duration): def _calc_heater_duration(self, duration):
"""Calculate correct value for heater duration setting from milliseconds."""
if duration < 0xfc0: if duration < 0xfc0:
factor = 0 factor = 0

View File

@ -1,7 +1,5 @@
"""BME680 constants, structures and utilities."""
# BME680 General config # BME680 General config
POLL_PERIOD_MS = 50 POLL_PERIOD_MS = 10
# BME680 I2C addresses # BME680 I2C addresses
I2C_ADDR_PRIMARY = 0x76 I2C_ADDR_PRIMARY = 0x76
@ -120,7 +118,7 @@ TMP_BUFFER_LENGTH = 40
REG_BUFFER_LENGTH = 6 REG_BUFFER_LENGTH = 6
FIELD_DATA_LENGTH = 3 FIELD_DATA_LENGTH = 3
GAS_REG_BUF_LENGTH = 20 GAS_REG_BUF_LENGTH = 20
GAS_HEATER_PROF_LEN_MAX = 10 GAS_HEATER_PROF_LEN_MAX = 10
# Settings selector # Settings selector
OST_SEL = 1 OST_SEL = 1
@ -135,7 +133,7 @@ GAS_SENSOR_SEL = GAS_MEAS_SEL | RUN_GAS_SEL | NBCONV_SEL
# Number of conversion settings # Number of conversion settings
NBCONV_MIN = 0 NBCONV_MIN = 0
NBCONV_MAX = 9 # Was 10, but there are only 10 settings: 0 1 2 ... 8 9 NBCONV_MAX = 9 # Was 10, but there are only 10 settings: 0 1 2 ... 8 9
# Mask definitions # Mask definitions
GAS_MEAS_MSK = 0x30 GAS_MEAS_MSK = 0x30
@ -216,37 +214,30 @@ REG_HCTRL_INDEX = 0
# Look up tables for the possible gas range values # Look up tables for the possible gas range values
lookupTable1 = [2147483647, 2147483647, 2147483647, 2147483647, lookupTable1 = [2147483647, 2147483647, 2147483647, 2147483647,
2147483647, 2126008810, 2147483647, 2130303777, 2147483647, 2147483647, 2126008810, 2147483647, 2130303777, 2147483647,
2147483647, 2143188679, 2136746228, 2147483647, 2126008810, 2147483647, 2143188679, 2136746228, 2147483647, 2126008810,
2147483647, 2147483647] 2147483647, 2147483647]
lookupTable2 = [4096000000, 2048000000, 1024000000, 512000000, lookupTable2 = [4096000000, 2048000000, 1024000000, 512000000,
255744255, 127110228, 64000000, 32258064, 255744255, 127110228, 64000000, 32258064,
16016016, 8000000, 4000000, 2000000, 16016016, 8000000, 4000000, 2000000,
1000000, 500000, 250000, 125000] 1000000, 500000, 250000, 125000]
def bytes_to_word(msb, lsb, bits=16, signed=False): def bytes_to_word(msb, lsb, bits=16, signed=False):
"""Convert a most and least significant byte into a word."""
# TODO: Reimpliment with struct
word = (msb << 8) | lsb word = (msb << 8) | lsb
if signed: if signed:
word = twos_comp(word, bits) word = twos_comp(word, bits)
return word return word
def twos_comp(val, bits=16): def twos_comp(val, bits=16):
"""Convert two bytes into a two's compliment signed word."""
# TODO: Reimpliment with struct
if val & (1 << (bits - 1)) != 0: if val & (1 << (bits - 1)) != 0:
val = val - (1 << bits) val = val - (1 << bits)
return val return val
# Sensor field data structure
class FieldData: class FieldData:
"""Structure for storing BME680 sensor data.""" def __init__(self):
def __init__(self): # noqa D107
# Contains new_data, gasm_valid & heat_stab # Contains new_data, gasm_valid & heat_stab
self.status = None self.status = None
self.heat_stable = False self.heat_stable = False
@ -263,11 +254,10 @@ class FieldData:
# Gas resistance in Ohms # Gas resistance in Ohms
self.gas_resistance = None self.gas_resistance = None
# Structure to hold the Calibration data
class CalibrationData: class CalibrationData:
"""Structure for storing BME680 calibration data.""" def __init__(self):
def __init__(self): # noqa D107
self.par_h1 = None self.par_h1 = None
self.par_h2 = None self.par_h2 = None
self.par_h3 = None self.par_h3 = None
@ -301,7 +291,6 @@ class CalibrationData:
self.range_sw_err = None self.range_sw_err = None
def set_from_array(self, calibration): def set_from_array(self, calibration):
"""Set paramaters from an array of bytes."""
# Temperature related coefficients # Temperature related coefficients
self.par_t1 = bytes_to_word(calibration[T1_MSB_REG], calibration[T1_LSB_REG]) self.par_t1 = bytes_to_word(calibration[T1_MSB_REG], calibration[T1_LSB_REG])
self.par_t2 = bytes_to_word(calibration[T2_MSB_REG], calibration[T2_LSB_REG], bits=16, signed=True) self.par_t2 = bytes_to_word(calibration[T2_MSB_REG], calibration[T2_LSB_REG], bits=16, signed=True)
@ -334,20 +323,15 @@ class CalibrationData:
self.par_gh3 = twos_comp(calibration[GH3_REG], bits=8) self.par_gh3 = twos_comp(calibration[GH3_REG], bits=8)
def set_other(self, heat_range, heat_value, sw_error): def set_other(self, heat_range, heat_value, sw_error):
"""Set other values."""
self.res_heat_range = (heat_range & RHRANGE_MSK) // 16 self.res_heat_range = (heat_range & RHRANGE_MSK) // 16
self.res_heat_val = heat_value self.res_heat_val = heat_value
self.range_sw_err = (sw_error & RSERROR_MSK) // 16 self.range_sw_err = (sw_error & RSERROR_MSK) // 16
# BME680 sensor settings structure which comprises of ODR,
# over-sampling and filter settings.
class TPHSettings: class TPHSettings:
"""Structure for storing BME680 sensor settings. def __init__(self):
Comprises of output data rate, over-sampling and filter settings.
"""
def __init__(self): # noqa D107
# Humidity oversampling # Humidity oversampling
self.os_hum = None self.os_hum = None
# Temperature oversampling # Temperature oversampling
@ -357,11 +341,11 @@ class TPHSettings:
# Filter coefficient # Filter coefficient
self.filter = None self.filter = None
# BME680 gas sensor which comprises of gas settings
## and status parameters
class GasSettings: class GasSettings:
"""Structure for storing BME680 gas settings and status.""" def __init__(self):
def __init__(self): # noqa D107
# Variable to store nb conversion # Variable to store nb conversion
self.nb_conv = None self.nb_conv = None
# Variable to store heater control # Variable to store heater control
@ -373,11 +357,10 @@ class GasSettings:
# Pointer to store duration profile # Pointer to store duration profile
self.heatr_dur = None self.heatr_dur = None
# BME680 device structure
class BME680Data: class BME680Data:
"""Structure to represent BME680 device.""" def __init__(self):
def __init__(self): # noqa D107
# Chip Id # Chip Id
self.chip_id = None self.chip_id = None
# Device Id # Device Id

View File

@ -1,15 +0,0 @@
[flake8]
exclude =
test.py
.tox
.eggs
.git
__pycache__
build
dist
tests
ignore =
E501
F403
F405
W504

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
""" """
Copyright (c) 2016 Pimoroni. Copyright (c) 2016 Pimoroni
Permission is hereby granted, free of charge, to any person obtaining a copy of 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 this software and associated documentation files (the "Software"), to deal in
@ -38,17 +38,17 @@ classifiers = ['Development Status :: 5 - Production/Stable',
'Topic :: System :: Hardware'] 'Topic :: System :: Hardware']
setup( setup(
name='bme680', name = 'bme680',
version='1.0.5', version = '1.0.4',
author='Philip Howard', author = 'Philip Howard',
author_email='phil@pimoroni.com', author_email = 'phil@pimoroni.com',
description="""Python library for driving the Pimoroni BME680 Breakout""", description = """Python library for driving the Pimoroni BME680 Breakout""",
long_description=open('README.rst').read() + '\n' + open('CHANGELOG.txt').read(), long_description= open('README.txt').read() + open('CHANGELOG.txt').read(),
license='MIT', license = 'MIT',
keywords='Raspberry Pi', keywords = 'Raspberry Pi',
url='http://www.pimoroni.com', url = 'http://www.pimoroni.com',
classifiers=classifiers, classifiers = classifiers,
packages=['bme680'], packages = ['bme680'],
py_modules=[], py_modules = [],
install_requires=['smbus'] # preferably: install `python3-smbus` instead of relying on this install_requires= []
) )

View File

@ -1,101 +0,0 @@
import sys
import mock
import pytest
import bme680
from bme680.constants import CalibrationData
class MockSMBus:
"""Mock a basic non-presence SMBus device to cause BME680 to fail.
Returns 0 in all cases, so that CHIP_ID will never match.
"""
def __init__(self, bus): # noqa D107
pass
def read_byte_data(self, addr, register):
"""Return 0 for all read attempts."""
return 0
class MockSMBusPresent:
"""Mock enough of the BME680 for the library to initialise and test."""
def __init__(self, bus):
"""Initialise with test data."""
self.regs = [0 for _ in range(256)]
self.regs[bme680.CHIP_ID_ADDR] = bme680.CHIP_ID
def read_byte_data(self, addr, register):
"""Read a single byte from fake registers."""
return self.regs[register]
def write_byte_data(self, addr, register, value):
"""Write a single byte to fake registers."""
self.regs[register] = value
def read_i2c_block_data(self, addr, register, length):
"""Read up to length bytes from register."""
return self.regs[register:register + length]
@pytest.fixture(scope='function', autouse=False)
def smbus_notpresent():
"""Mock smbus module."""
smbus = mock.MagicMock()
smbus.SMBus = MockSMBus
sys.modules['smbus'] = smbus
yield smbus
del sys.modules['smbus']
@pytest.fixture(scope='function', autouse=False)
def smbus():
"""Mock smbus module."""
smbus = mock.MagicMock()
smbus.SMBus = MockSMBusPresent
sys.modules['smbus'] = smbus
yield smbus
del sys.modules['smbus']
@pytest.fixture(scope='function', autouse=False)
def calibration():
"""Mock bme680 calibration."""
calibration = CalibrationData()
# Dump of calibration data borrowed from:
# https://github.com/pimoroni/bme680-python/issues/11
data = {
'par_gh1': -30,
'par_gh2': -24754,
'par_gh3': 18,
'par_h1': 676,
'par_h2': 1029,
'par_h3': 0,
'par_h4': 45,
'par_h5': 20,
'par_h6': 120,
'par_h7': -100,
'par_p1': 36673,
'par_p10': 30,
'par_p2': -10515,
'par_p3': 88,
'par_p4': 7310,
'par_p5': -129,
'par_p6': 30,
'par_p7': 46,
'par_p8': -3177,
'par_p9': -2379,
'par_t1': 26041,
'par_t2': 26469,
'par_t3': 3,
'range_sw_err': 0,
'res_heat_range': 1,
'res_heat_val': 48,
't_fine': 136667
}
for k, v in data.items():
setattr(calibration, k, v)
return calibration

View File

@ -1,8 +0,0 @@
[flake8]
exclude =
__pycache__
ignore =
D100 # Do not require docstrings
E501
F403
F405

View File

@ -1,42 +0,0 @@
import pytest
import bme680
def test_calc_temperature(smbus, calibration):
"""Validate temperature calculation against mock calibration data."""
sensor = bme680.BME680()
sensor.calibration_data = calibration
assert sensor._calc_temperature(501240) == pytest.approx(2669.81, 0.01)
assert sensor.calibration_data.t_fine == pytest.approx(136668.78, 0.01)
def test_calc_pressure(smbus, calibration):
"""Validate pressure calculation against mock calibration data."""
sensor = bme680.BME680()
sensor.calibration_data = calibration
sensor._calc_temperature(501240)
assert sensor._calc_pressure(353485) == pytest.approx(98716.92, 0.01)
def test_calc_humidity(smbus, calibration):
"""Validate humidity calculation against mock calibration data."""
sensor = bme680.BME680()
sensor.calibration_data = calibration
sensor._calc_temperature(501240)
assert sensor._calc_humidity(19019) == pytest.approx(42410.28, 0.01)
def test_calc_gas_resistance(smbus, calibration):
"""Validate gas calculation against mock calibration data."""
sensor = bme680.BME680()
sensor.calibration_data = calibration
assert int(sensor._calc_gas_resistance(0, 0)) == 12946860
def test_temp_offset(smbus, calibration):
"""Validate temperature calculation with offset against mock calibration data."""
sensor = bme680.BME680()
sensor.calibration_data = calibration
sensor.set_temp_offset(1.99)
assert sensor._calc_temperature(501240) == pytest.approx(2868.30, 0.01)
assert sensor.calibration_data.t_fine == pytest.approx(146831.78, 0.01)

View File

@ -1,13 +0,0 @@
import pytest
import bme680
def test_setup_not_present(smbus_notpresent):
"""Mock the adbsence of a BME680 and test initialisation."""
with pytest.raises(RuntimeError):
sensor = bme680.BME680() # noqa F841
def test_setup_mock_present(smbus):
"""Mock the presence of a BME680 and test initialisation."""
sensor = bme680.BME680() # noqa F841

View File

@ -1,25 +0,0 @@
[tox]
envlist = py{37,38,39},qa
skip_missing_interpreters = True
[testenv]
commands =
python setup.py install
coverage run -m pytest -v -r wsx
coverage report
deps =
mock
pytest>=3.1
pytest-cov
[testenv:qa]
commands =
flake8
flake8 tests/
flake8 ../examples/
rstcheck README.rst
deps =
flake8
flake8-docstrings
flake8-quotes
rstcheck

View File

@ -1,9 +1,3 @@
bme680 (1.0.5) stable; urgency=low
* New: set_temp_offset to calibrate temperature offset in degrees C
-- Phil Howard <phil@pimoroni.com> Fri, 01 Jun 2018 00:00:00 +0000
bme680 (1.0.4) stable; urgency=low bme680 (1.0.4) stable; urgency=low
* Fix to range_sw_err for extremely high gas readings * Fix to range_sw_err for extremely high gas readings

View File

@ -1,9 +1,3 @@
bme680 (1.0.5) stable; urgency=low
* New: set_temp_offset to calibrate temperature offset in degrees C
-- Phil Howard <phil@pimoroni.com> Fri, 01 Jun 2018 00:00:00 +0000
bme680 (1.0.4) stable; urgency=low bme680 (1.0.4) stable; urgency=low
* Fix to range_sw_err for extremely high gas readings * Fix to range_sw_err for extremely high gas readings

View File

@ -1,24 +0,0 @@
#!/bin/bash
PACKAGE="bme680"
printf "BME680 Python Library: Uninstaller\n\n"
if [ $(id -u) -ne 0 ]; then
printf "Script must be run as root. Try 'sudo ./uninstall.sh'\n"
exit 1
fi
cd library
printf "Unnstalling for Python 2..\n"
pip uninstall $PACKAGE
if [ -f "/usr/bin/pip3" ]; then
printf "Uninstalling for Python 3..\n"
pip3 uninstall $PACKAGE
fi
cd ..
printf "Done!\n"