1
0
mirror of https://github.com/cmur2/python-bme680.git synced 2024-06-28 12: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-delete-this-directory.txt
.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
![ci](https://github.com/cmur2/python-bme680/workflows/ci/badge.svg?branch=master)
https://shop.pimoroni.com/products/bme680
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)
- floating point precision for measurement compensations (user has all precision available to choose *based on measurement settings and datasheet* how much is trustable)
- no redundant tweaking of measurement settings after soft reset
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:
## 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:
@ -27,5 +47,5 @@ In all cases you will have to enable the i2c bus.
## 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

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 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
to estimate indoor air quality as a percentage.
Press Ctrl+C to exit!
Press Ctrl+C to exit
""")
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
sensor = bme680.BME680()
# These oversampling settings can be tweaked to
# These oversampling settings can be tweaked to
# change the balance between accuracy and noise in
# the data.
@ -32,7 +29,7 @@ sensor.set_gas_heater_temperature(320)
sensor.set_gas_heater_duration(150)
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.
start_time = time.time()
@ -45,13 +42,13 @@ try:
# Collect gas resistance burn-in values, then use the average
# of the last 50 values to set the upper limit for calculating
# 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:
curr_time = time.time()
if sensor.get_sensor_data() and sensor.data.heat_stable:
gas = sensor.data.gas_resistance
burn_in_data.append(gas)
print('Gas: {0} Ohms'.format(gas))
print("Gas: {0} Ohms".format(gas))
time.sleep(1)
gas_baseline = sum(burn_in_data[-50:]) / 50.0
@ -59,13 +56,11 @@ try:
# Set the humidity baseline to 40%, an optimal indoor humidity.
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)
hum_weighting = 0.25
print('Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n'.format(
gas_baseline,
hum_baseline))
print("Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n".format(gas_baseline, hum_baseline))
while True:
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.
if hum_offset > 0:
hum_score = (100 - hum_baseline - hum_offset)
hum_score /= (100 - hum_baseline)
hum_score *= (hum_weighting * 100)
hum_score = (100 - hum_baseline - hum_offset) / (100 - hum_baseline) * (hum_weighting * 100)
else:
hum_score = (hum_baseline + hum_offset)
hum_score /= hum_baseline
hum_score *= (hum_weighting * 100)
hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100)
# Calculate gas_score as the distance from the gas_baseline.
if gas_offset > 0:
gas_score = (gas / gas_baseline)
gas_score *= (100 - (hum_weighting * 100))
gas_score = (gas / gas_baseline) * (100 - (hum_weighting * 100))
else:
gas_score = 100 - (hum_weighting * 100)
# Calculate air_quality_score.
# Calculate air_quality_score.
air_quality_score = hum_score + gas_score
print('Gas: {0:.2f} Ohms,humidity: {1:.2f} %RH,air quality: {2:.2f}'.format(
gas,
hum,
air_quality_score))
print("Gas: {0:.2f} Ohms,humidity: {1:.2f} %RH,air quality: {2:.2f}".format(gas, hum, air_quality_score))
time.sleep(1)
except KeyboardInterrupt:

View File

@ -3,30 +3,21 @@
import bme680
import time
print("""read-all.py - Displays temperature, pressure, humidity, and gas.
Press Ctrl+C to exit!
""")
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
sensor = bme680.BME680()
# These calibration data can safely be commented
# out, if desired.
print('Calibration data:')
print("Calibration data:")
for name in dir(sensor.calibration_data):
if not name.startswith('_'):
value = getattr(sensor.calibration_data, name)
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
# the data.
@ -36,12 +27,12 @@ sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
print('\n\nInitial reading:')
print("\n\nInitial reading:")
for name in dir(sensor.data):
value = getattr(sensor.data, name)
if not name.startswith('_'):
print('{}: {}'.format(name, value))
print("{}: {}".format(name, value))
sensor.set_gas_heater_temperature(320)
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.select_gas_heater_profile(1)
print('\n\nPolling:')
print("\n\nPolling:")
try:
while True:
if sensor.get_sensor_data():
output = '{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH'.format(
sensor.data.temperature,
sensor.data.pressure / 100.0,
sensor.data.humidity)
output = "{0:.2f} C,{1:.2f} hPa,{2:.2f} %RH".format(sensor.data.temperature, sensor.data.pressure, sensor.data.humidity)
if sensor.data.heat_stable:
print('{0},{1} Ohms'.format(
output,
sensor.data.gas_resistance))
print("{0},{1} Ohms".format(output, sensor.data.gas_resistance))
else:
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
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,
pressure and humidity quickly.
@ -11,10 +11,7 @@ Press Ctrl+C to exit
""")
try:
sensor = bme680.BME680(bme680.I2C_ADDR_PRIMARY)
except IOError:
sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
sensor = bme680.BME680()
# These oversampling settings can be tweaked to
# 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_filter(bme680.FILTER_SIZE_3)
print('Polling:')
print("Polling:")
try:
while True:
if 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)
output = "{0:.2f} C,{1:.2f} hPa,{2:.3f} %RH".format(sensor.data.temperature, sensor.data.pressure, sensor.data.humidity)
print(output)
except KeyboardInterrupt:
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
-----

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 LICENSE.txt
include README.rst
include README.txt
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 lookupTable1, lookupTable2
from .constants import BME680Data
from . import constants
from .constants import *
import math
import time
__version__ = '1.0.5'
# 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
__version__ = '1.0.4'
class BME680(BME680Data):
"""BOSCH BME680.
"""BOSCH BME680
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.
"""
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
"""
def __init__(self, i2c_addr=I2C_ADDR_PRIMARY, i2c_device=None):
BME680Data.__init__(self)
self.i2c_addr = i2c_addr
@ -42,50 +22,43 @@ class BME680(BME680Data):
import smbus
self._i2c = smbus.SMBus(1)
self.chip_id = self._get_regs(constants.CHIP_ID_ADDR, 1)
if self.chip_id != constants.CHIP_ID:
raise RuntimeError('BME680 Not Found. Invalid CHIP ID: 0x{0:02x}'.format(self.chip_id))
self.chip_id = self._get_regs(CHIP_ID_ADDR, 1)
if self.chip_id != CHIP_ID:
raise RuntimeError("BME680 Not Found. Invalid CHIP ID: 0x{0:02x}".format(self.chip_id))
self.soft_reset()
self.set_power_mode(constants.SLEEP_MODE)
self.set_power_mode(SLEEP_MODE)
self._get_calibration_data()
self.set_temp_offset(0)
self.ambient_temperature = 25 # only for gas heater temperature, nearly irrelevant
self.set_humidity_oversample(OS_2X)
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):
"""Retrieve the sensor calibration data and store it in .calibration_data."""
calibration = self._get_regs(constants.COEFF_ADDR1, constants.COEFF_ADDR1_LEN)
calibration += self._get_regs(constants.COEFF_ADDR2, constants.COEFF_ADDR2_LEN)
"""Retrieves the sensor calibration data and stores it in .calibration_data"""
calibration = self._get_regs(COEFF_ADDR1, COEFF_ADDR1_LEN)
calibration += self._get_regs(COEFF_ADDR2, COEFF_ADDR2_LEN)
heat_range = self._get_regs(constants.ADDR_RES_HEAT_RANGE_ADDR, 1)
heat_value = constants.twos_comp(self._get_regs(constants.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)
heat_range = self._get_regs(ADDR_RES_HEAT_RANGE_ADDR, 1)
heat_value = twos_comp(self._get_regs(ADDR_RES_HEAT_VAL_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_other(heat_range, heat_value, sw_error)
def soft_reset(self):
"""Trigger a soft reset."""
self._set_regs(constants.SOFT_RESET_ADDR, constants.SOFT_RESET_CMD)
time.sleep(constants.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))
"""Initiate a soft reset"""
self._set_regs(SOFT_RESET_ADDR, SOFT_RESET_CMD)
time.sleep(RESET_PERIOD / 1000.0)
def set_humidity_oversample(self, value):
"""Set humidity oversampling.
"""Set humidity oversampling
A higher oversampling value means more stable sensor readings,
with less noise and jitter.
@ -96,15 +69,15 @@ class BME680(BME680Data):
"""
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):
"""Get humidity oversampling."""
return (self._get_regs(constants.CONF_OS_H_ADDR, 1) & constants.OSH_MSK) >> constants.OSH_POS
"""Get humidity oversampling"""
return (self._get_regs(CONF_OS_H_ADDR, 1) & OSH_MSK) >> OSH_POS
def set_pressure_oversample(self, value):
"""Set temperature oversampling.
"""Set temperature oversampling
A higher oversampling value means more stable sensor readings,
with less noise and jitter.
@ -112,18 +85,18 @@ class BME680(BME680Data):
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
"""
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):
"""Get pressure oversampling."""
return (self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) & constants.OSP_MSK) >> constants.OSP_POS
"""Get pressure oversampling"""
return (self._get_regs(CONF_T_P_MODE_ADDR, 1) & OSP_MSK) >> OSP_POS
def set_temperature_oversample(self, value):
"""Set pressure oversampling.
"""Set pressure oversampling
A higher oversampling value means more stable sensor readings,
with less noise and jitter.
@ -131,18 +104,18 @@ class BME680(BME680Data):
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
"""
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):
"""Get temperature oversampling."""
return (self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) & constants.OST_MSK) >> constants.OST_POS
"""Get temperature oversampling"""
return (self._get_regs(CONF_T_P_MODE_ADDR, 1) & OST_MSK) >> OST_POS
def set_filter(self, value):
"""Set IIR filter size.
"""Set IIR filter size
Optionally remove short term fluctuations from the temperature and pressure readings,
increasing their resolution but reducing their bandwidth.
@ -154,42 +127,40 @@ class BME680(BME680Data):
"""
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):
"""Get filter size."""
return (self._get_regs(constants.CONF_ODR_FILT_ADDR, 1) & constants.FILTER_MSK) >> constants.FILTER_POS
"""Get filter size"""
return (self._get_regs(CONF_ODR_FILT_ADDR, 1) & FILTER_MSK) >> FILTER_POS
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.
:param value: Profile index from 0 to 9
"""
if value > constants.NBCONV_MAX or value < constants.NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(value, constants.NBCONV_MIN, constants.NBCONV_MAX))
if value > NBCONV_MAX or value < NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(value, NBCONV_MIN, NBCONV_MAX))
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):
"""Get gas sensor conversion profile: 0 to 9."""
return self._get_regs(constants.CONF_ODR_RUN_GAS_NBC_ADDR, 1) & constants.NBCONV_MSK
"""Get gas sensor conversion profile: 0 to 9"""
return self._get_regs(CONF_ODR_RUN_GAS_NBC_ADDR, 1) & NBCONV_MSK
def set_gas_status(self, value):
"""Enable/disable gas sensor."""
"""Enable/disable gas sensor"""
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):
"""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
"""Get the current gas status"""
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):
"""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 durarion: Target duration in milliseconds, between 1 and 4032
: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)
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
When setting an nb_profile other than 0,
make sure to select it with select_gas_heater_profile.
"""
if nb_profile > constants.NBCONV_MAX or value < constants.NBCONV_MIN:
raise ValueError('Profile "{}" should be between {} and {}'.format(nb_profile, constants.NBCONV_MIN, constants.NBCONV_MAX))
if nb_profile > NBCONV_MAX or value < NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, NBCONV_MIN, NBCONV_MAX))
self.gas_settings.heatr_temp = value
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):
"""Set gas sensor heater duration.
"""Set gas sensor heater duration
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.
@ -224,70 +195,70 @@ class BME680(BME680Data):
When setting an nb_profile other than 0,
make sure to select it with select_gas_heater_profile.
"""
if nb_profile > constants.NBCONV_MAX or value < constants.NBCONV_MIN:
raise ValueError('Profile "{}" should be between {} and {}'.format(nb_profile, constants.NBCONV_MIN, constants.NBCONV_MAX))
if nb_profile > NBCONV_MAX or value < NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, NBCONV_MIN, NBCONV_MAX))
self.gas_settings.heatr_dur = value
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):
"""Set power mode."""
if value not in (constants.SLEEP_MODE, constants.FORCED_MODE):
raise ValueError('Power mode should be one of SLEEP_MODE or FORCED_MODE')
"""Set power mode"""
if value not in (SLEEP_MODE, FORCED_MODE):
print("Power mode should be one of SLEEP_MODE or FORCED_MODE")
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:
time.sleep(constants.POLL_PERIOD_MS / 1000.0)
time.sleep(POLL_PERIOD_MS / 1000.0)
def get_power_mode(self):
"""Get power mode."""
self.power_mode = self._get_regs(constants.CONF_T_P_MODE_ADDR, 1)
"""Get power mode"""
self.power_mode = self._get_regs(CONF_T_P_MODE_ADDR, 1)
return self.power_mode
def get_sensor_data(self):
"""Get sensor data.
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):
status = self._get_regs(constants.FIELD0_ADDR, 1)
status = self._get_regs(FIELD0_ADDR, 1)
if (status & constants.NEW_DATA_MSK) == 0:
time.sleep(constants.POLL_PERIOD_MS / 1000.0)
if (status & NEW_DATA_MSK) == 0:
time.sleep(POLL_PERIOD_MS / 1000.0)
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
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]
adc_pres = (regs[2] << 12) | (regs[3] << 4) | (regs[4] >> 4)
adc_temp = (regs[5] << 12) | (regs[6] << 4) | (regs[7] >> 4)
adc_hum = (regs[8] << 8) | regs[9]
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] & constants.HEAT_STAB_MSK
self.data.status |= regs[14] & GASM_VALID_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)
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.gas_resistance = self._calc_gas_resistance(adc_gas_res, gas_range)
return True
@ -295,106 +266,101 @@ class BME680(BME680Data):
return False
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 &= ~mask
temp |= value << position
self._set_regs(register, temp)
def _set_regs(self, register, value):
"""Set one or more registers."""
"""Set one or more registers"""
if isinstance(value, int):
self._i2c.write_byte_data(self.i2c_addr, register, value)
else:
self._i2c.write_i2c_block_data(self.i2c_addr, register, value)
def _get_regs(self, register, length):
"""Get one or more registers."""
"""Get one or more registers"""
if length == 1:
return self._i2c.read_byte_data(self.i2c_addr, register)
else:
return self._i2c.read_i2c_block_data(self.i2c_addr, register, length)
def _calc_temperature(self, temperature_adc):
"""Convert the raw temperature to degrees C using calibration_data."""
var1 = (temperature_adc / 8.0) - (self.calibration_data.par_t1 * 2.0)
var2 = (var1 * self.calibration_data.par_t2) / 2048.0
var3 = ((var1 / 2.0) * (var1 / 2.0)) / 4096.0
var3 = ((var3) * (self.calibration_data.par_t3 * 16.0)) / 16384.0
var1 = (temperature_adc >> 3) - (self.calibration_data.par_t1 << 1)
var2 = (var1 * self.calibration_data.par_t2) >> 11
var3 = ((var1 >> 1) * (var1 >> 1)) >> 12
var3 = ((var3) * (self.calibration_data.par_t3 << 4)) >> 14
# Save teperature data for pressure calculations
self.calibration_data.t_fine = (var2 + var3) + self.offset_temp_in_t_fine
calc_temp = (((self.calibration_data.t_fine * 5.0) + 128.0) / 256.0)
self.calibration_data.t_fine = (var2 + var3)
calc_temp = (((self.calibration_data.t_fine * 5) + 128) >> 8)
return calc_temp
def _calc_pressure(self, pressure_adc):
"""Convert the raw pressure using calibration data."""
var1 = (self.calibration_data.t_fine / 2.0) - 64000.0
var2 = ((((var1 / 4.0) * (var1 / 4.0)) / 2048.0) *
self.calibration_data.par_p6) / 4.0
var2 = var2 + ((var1 * self.calibration_data.par_p5) * 2.0)
var2 = (var2 / 4.0) + (self.calibration_data.par_p4 * 65536.0)
var1 = (((((var1 / 4.0) * (var1 / 4.0)) / 8192.0) *
((self.calibration_data.par_p3 * 32.0)) / 8.0) +
((self.calibration_data.par_p2 * var1) / 2.0))
var1 = var1 / 262144.0
var1 = ((self.calibration_data.t_fine) >> 1) - 64000
var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) *
self.calibration_data.par_p6) >> 2
var2 = var2 + ((var1 * self.calibration_data.par_p5) << 1)
var2 = (var2 >> 2) + (self.calibration_data.par_p4 << 16)
var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13 ) *
((self.calibration_data.par_p3 << 5)) >> 3) +
((self.calibration_data.par_p2 * var1) >> 1))
var1 = var1 >> 18
var1 = ((32768.0 + var1) * self.calibration_data.par_p1) / 32768.0
calc_pressure = 1048576.0 - pressure_adc
calc_pressure = ((calc_pressure - (var2 / 4096.0)) * (3125.0))
var1 = ((32768 + var1) * self.calibration_data.par_p1) >> 15
calc_pressure = 1048576 - pressure_adc
calc_pressure = ((calc_pressure - (var2 >> 12)) * (3125))
if calc_pressure >= (1 << 31):
calc_pressure = ((calc_pressure / var1) * 2.0)
calc_pressure = ((calc_pressure // var1) << 1)
else:
calc_pressure = ((calc_pressure * 2.0) / var1)
calc_pressure = ((calc_pressure << 1) // var1)
var1 = (self.calibration_data.par_p9 * (((calc_pressure / 8.0) *
(calc_pressure / 8.0)) / 8192.0)) / 4096.0
var2 = ((calc_pressure / 4.0) *
self.calibration_data.par_p8) / 8192.0
var3 = ((calc_pressure / 256.0) * (calc_pressure / 256.0) *
(calc_pressure / 256.0) *
self.calibration_data.par_p10) / 131072.0
var1 = (self.calibration_data.par_p9 * (((calc_pressure >> 3) *
(calc_pressure >> 3)) >> 13)) >> 12
var2 = ((calc_pressure >> 2) *
self.calibration_data.par_p8) >> 13
var3 = ((calc_pressure >> 8) * (calc_pressure >> 8) *
(calc_pressure >> 8) *
self.calibration_data.par_p10) >> 17
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
def _calc_humidity(self, humidity_adc):
"""Convert the raw humidity using calibration data."""
temp_scaled = ((self.calibration_data.t_fine * 5.0) + 128.0) / 256.0
var1 = (humidity_adc - ((self.calibration_data.par_h1 * 16.0))) -\
(((temp_scaled * self.calibration_data.par_h3) / (100.0)) / 2.0)
var2 = (self.calibration_data.par_h2 *
(((temp_scaled * self.calibration_data.par_h4) / (100.0)) +
(((temp_scaled * ((temp_scaled * self.calibration_data.par_h5) / (100.0))) / 64.0) /
(100.0)) + (1.0 * 16384.0))) / 1024.0
temp_scaled = ((self.calibration_data.t_fine * 5) + 128) >> 8
var1 = (humidity_adc - ((self.calibration_data.par_h1 * 16))) \
- (((temp_scaled * self.calibration_data.par_h3) // (100)) >> 1)
var2 = (self.calibration_data.par_h2
* (((temp_scaled * self.calibration_data.par_h4) // (100))
+ (((temp_scaled * ((temp_scaled * self.calibration_data.par_h5) // (100))) >> 6)
// (100)) + (1 * 16384))) >> 10
var3 = var1 * var2
var4 = self.calibration_data.par_h6 * 128.0
var4 = ((var4) + ((temp_scaled * self.calibration_data.par_h7) / (100.0))) / 16.0
var5 = ((var3 / 16384.0) * (var3 / 16384.0)) / 1024.0
var6 = (var4 * var5) / 2.0
calc_hum = (((var3 + var6) / 1024.0) * (1000.0)) / 4096.0
var4 = self.calibration_data.par_h6 << 7
var4 = ((var4) + ((temp_scaled * self.calibration_data.par_h7) // (100))) >> 4
var5 = ((var3 >> 14) * (var3 >> 14)) >> 10
var6 = (var4 * var5) >> 1
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):
"""Convert the raw gas resistance using calibration data."""
var1 = ((1340 + (5 * self.calibration_data.range_sw_err)) * (lookupTable1[gas_range])) >> 16
var2 = (((gas_res_adc << 15) - (16777216)) + var1)
var3 = ((lookupTable2[gas_range] * var1) >> 9)
calc_gas_res = ((var3 + (var2 >> 1)) / var2)
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
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
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
def _calc_heater_duration(self, duration):
"""Calculate correct value for heater duration setting from milliseconds."""
if duration < 0xfc0:
factor = 0

View File

@ -1,7 +1,5 @@
"""BME680 constants, structures and utilities."""
# BME680 General config
POLL_PERIOD_MS = 50
POLL_PERIOD_MS = 10
# BME680 I2C addresses
I2C_ADDR_PRIMARY = 0x76
@ -120,7 +118,7 @@ TMP_BUFFER_LENGTH = 40
REG_BUFFER_LENGTH = 6
FIELD_DATA_LENGTH = 3
GAS_REG_BUF_LENGTH = 20
GAS_HEATER_PROF_LEN_MAX = 10
GAS_HEATER_PROF_LEN_MAX = 10
# Settings selector
OST_SEL = 1
@ -135,7 +133,7 @@ GAS_SENSOR_SEL = GAS_MEAS_SEL | RUN_GAS_SEL | NBCONV_SEL
# Number of conversion settings
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
GAS_MEAS_MSK = 0x30
@ -216,37 +214,30 @@ REG_HCTRL_INDEX = 0
# Look up tables for the possible gas range values
lookupTable1 = [2147483647, 2147483647, 2147483647, 2147483647,
2147483647, 2126008810, 2147483647, 2130303777, 2147483647,
2147483647, 2143188679, 2136746228, 2147483647, 2126008810,
2147483647, 2147483647]
2147483647, 2126008810, 2147483647, 2130303777, 2147483647,
2147483647, 2143188679, 2136746228, 2147483647, 2126008810,
2147483647, 2147483647]
lookupTable2 = [4096000000, 2048000000, 1024000000, 512000000,
255744255, 127110228, 64000000, 32258064,
16016016, 8000000, 4000000, 2000000,
1000000, 500000, 250000, 125000]
255744255, 127110228, 64000000, 32258064,
16016016, 8000000, 4000000, 2000000,
1000000, 500000, 250000, 125000]
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
if signed:
word = twos_comp(word, bits)
return word
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:
val = val - (1 << bits)
return val
# Sensor field data structure
class FieldData:
"""Structure for storing BME680 sensor data."""
def __init__(self): # noqa D107
def __init__(self):
# Contains new_data, gasm_valid & heat_stab
self.status = None
self.heat_stable = False
@ -263,11 +254,10 @@ class FieldData:
# Gas resistance in Ohms
self.gas_resistance = None
# Structure to hold the Calibration data
class CalibrationData:
"""Structure for storing BME680 calibration data."""
def __init__(self): # noqa D107
def __init__(self):
self.par_h1 = None
self.par_h2 = None
self.par_h3 = None
@ -301,7 +291,6 @@ class CalibrationData:
self.range_sw_err = None
def set_from_array(self, calibration):
"""Set paramaters from an array of bytes."""
# Temperature related coefficients
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)
@ -334,20 +323,15 @@ class CalibrationData:
self.par_gh3 = twos_comp(calibration[GH3_REG], bits=8)
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_val = heat_value
self.range_sw_err = (sw_error & RSERROR_MSK) // 16
# BME680 sensor settings structure which comprises of ODR,
# over-sampling and filter settings.
class TPHSettings:
"""Structure for storing BME680 sensor settings.
Comprises of output data rate, over-sampling and filter settings.
"""
def __init__(self): # noqa D107
def __init__(self):
# Humidity oversampling
self.os_hum = None
# Temperature oversampling
@ -357,11 +341,11 @@ class TPHSettings:
# Filter coefficient
self.filter = None
# BME680 gas sensor which comprises of gas settings
## and status parameters
class GasSettings:
"""Structure for storing BME680 gas settings and status."""
def __init__(self): # noqa D107
def __init__(self):
# Variable to store nb conversion
self.nb_conv = None
# Variable to store heater control
@ -373,11 +357,10 @@ class GasSettings:
# Pointer to store duration profile
self.heatr_dur = None
# BME680 device structure
class BME680Data:
"""Structure to represent BME680 device."""
def __init__(self): # noqa D107
def __init__(self):
# Chip Id
self.chip_id = None
# 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
"""
Copyright (c) 2016 Pimoroni.
Copyright (c) 2016 Pimoroni
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
@ -38,17 +38,17 @@ classifiers = ['Development Status :: 5 - Production/Stable',
'Topic :: System :: Hardware']
setup(
name='bme680',
version='1.0.5',
author='Philip Howard',
author_email='phil@pimoroni.com',
description="""Python library for driving the Pimoroni BME680 Breakout""",
long_description=open('README.rst').read() + '\n' + open('CHANGELOG.txt').read(),
license='MIT',
keywords='Raspberry Pi',
url='http://www.pimoroni.com',
classifiers=classifiers,
packages=['bme680'],
py_modules=[],
install_requires=['smbus'] # preferably: install `python3-smbus` instead of relying on this
name = 'bme680',
version = '1.0.4',
author = 'Philip Howard',
author_email = 'phil@pimoroni.com',
description = """Python library for driving the Pimoroni BME680 Breakout""",
long_description= open('README.txt').read() + open('CHANGELOG.txt').read(),
license = 'MIT',
keywords = 'Raspberry Pi',
url = 'http://www.pimoroni.com',
classifiers = classifiers,
packages = ['bme680'],
py_modules = [],
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
* 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
* 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"