1
0
mirror of https://github.com/cmur2/python-bme680.git synced 2024-12-22 12:54:29 +01:00

Test suites and code QA fixes

This commit is contained in:
Phil Howard 2018-09-02 11:26:04 +01:00
parent c97791d720
commit 6374bc4241
15 changed files with 384 additions and 180 deletions

4
.gitignore vendored
View File

@ -19,3 +19,7 @@ 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/

View File

@ -1,10 +1,24 @@
language: python language: python
sudo: false sudo: false
cache: pip
git:
submodules: true
matrix: matrix:
include: include:
python: "2.7" - python: "2.7"
env: TOXENV=py27
- python: "3.5"
env: TOXENV=py35
- python: "2.7"
env: TOXENV=py27
install:
- pip install --ignore-installed --upgrade setuptools pip tox coveralls
script: script:
- pip install --ignore-installed --upgrade flake8 - cd library
- flake8 --ignore F403,F405,E501 - tox -vv
after_success: if [ "$TOXENV" == "py35" ]; then coveralls; fi

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
import bme680 import bme680
import time import time
@ -42,13 +41,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
@ -60,7 +59,9 @@ try:
# 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(gas_baseline, hum_baseline)) print('Gas baseline: {0} Ohms, humidity baseline: {1:.2f} %RH\n'.format(
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:
@ -72,14 +73,19 @@ 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) / (100 - hum_baseline) * (hum_weighting * 100) hum_score = (100 - hum_baseline - hum_offset)
hum_score /= (100 - hum_baseline)
hum_score *= (hum_weighting * 100)
else: else:
hum_score = (hum_baseline + hum_offset) / hum_baseline * (hum_weighting * 100) hum_score = (hum_baseline + hum_offset)
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) * (100 - (hum_weighting * 100)) gas_score = (gas / gas_baseline)
gas_score *= (100 - (hum_weighting * 100))
else: else:
gas_score = 100 - (hum_weighting * 100) gas_score = 100 - (hum_weighting * 100)
@ -87,7 +93,11 @@ try:
# 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(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) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:

View File

@ -1,21 +1,26 @@
#!/usr/bin/env python #!/usr/bin/env python
import bme680 import bme680
import time import time
print("""Display Temperature, Pressure, Humidity and Gas
Press Ctrl+C to exit
""")
sensor = bme680.BME680() sensor = bme680.BME680()
# 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
@ -27,12 +32,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)
@ -43,14 +48,19 @@ 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(sensor.data.temperature, sensor.data.pressure, 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: 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: else:
print(output) print(output)

8
examples/setup.cfg Normal file
View File

@ -0,0 +1,8 @@
[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

@ -15,25 +15,29 @@ 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)
def display_data(offset=0): def display_data(offset=0):
sensor.set_temp_offset(offset) sensor.set_temp_offset(offset)
sensor.get_sensor_data() sensor.get_sensor_data()
output = "{0:.2f} C, {1:.2f} hPa, {2:.3f} %RH".format(sensor.data.temperature, sensor.data.pressure, 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) print(output)
print("") print('')
print("Initial readings")
print('Initial readings')
display_data() display_data()
print("SET offset 4 degrees celsius") print('SET offset 4 degrees celsius')
display_data(4) display_data(4)
print("SET offset -1.87 degrees celsius") print('SET offset -1.87 degrees celsius')
display_data(-1.87) display_data(-1.87)
print("SET offset -100 degrees celsius") print('SET offset -100 degrees celsius')
display_data(-100) display_data(-100)
print("SET offset 0 degrees celsius") print('SET offset 0 degrees celsius')
display_data(0) display_data(0)

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
import bme680 import bme680
import time
print("""Display Temperature, Pressure and Humidity print("""Display Temperature, Pressure and Humidity
@ -22,15 +21,17 @@ 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, sensor.data.pressure, 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) print(output)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass

4
library/.coveragerc Normal file
View File

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

View File

@ -1,11 +1,23 @@
from .constants import * """BME680 Temperature, Pressure, Humidity & Gas Sensor."""
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.5'
# Export constants to global namespace
# so end-users can "from BME680 import NAME"
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.
@ -13,7 +25,14 @@ 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
@ -22,45 +41,46 @@ class BME680(BME680Data):
import smbus import smbus
self._i2c = smbus.SMBus(1) self._i2c = smbus.SMBus(1)
self.chip_id = self._get_regs(CHIP_ID_ADDR, 1) self.chip_id = self._get_regs(constants.CHIP_ID_ADDR, 1)
if self.chip_id != CHIP_ID: if self.chip_id != constants.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(SLEEP_MODE) self.set_power_mode(constants.SLEEP_MODE)
self._get_calibration_data() self._get_calibration_data()
self.set_humidity_oversample(OS_2X) self.set_humidity_oversample(constants.OS_2X)
self.set_pressure_oversample(OS_4X) self.set_pressure_oversample(constants.OS_4X)
self.set_temperature_oversample(OS_8X) self.set_temperature_oversample(constants.OS_8X)
self.set_filter(FILTER_SIZE_3) self.set_filter(constants.FILTER_SIZE_3)
self.set_gas_status(ENABLE_GAS_MEAS) self.set_gas_status(constants.ENABLE_GAS_MEAS)
self.set_temp_offset(0) self.set_temp_offset(0)
self.get_sensor_data() self.get_sensor_data()
def _get_calibration_data(self): def _get_calibration_data(self):
"""Retrieves the sensor calibration data and stores it in .calibration_data""" """Retrieve the sensor calibration data and store it in .calibration_data."""
calibration = self._get_regs(COEFF_ADDR1, COEFF_ADDR1_LEN) calibration = self._get_regs(constants.COEFF_ADDR1, constants.COEFF_ADDR1_LEN)
calibration += self._get_regs(COEFF_ADDR2, COEFF_ADDR2_LEN) calibration += self._get_regs(constants.COEFF_ADDR2, constants.COEFF_ADDR2_LEN)
heat_range = self._get_regs(ADDR_RES_HEAT_RANGE_ADDR, 1) heat_range = self._get_regs(constants.ADDR_RES_HEAT_RANGE_ADDR, 1)
heat_value = twos_comp(self._get_regs(ADDR_RES_HEAT_VAL_ADDR, 1), bits=8) heat_value = constants.twos_comp(self._get_regs(constants.ADDR_RES_HEAT_VAL_ADDR, 1), bits=8)
sw_error = twos_comp(self._get_regs(ADDR_RANGE_SW_ERR_ADDR, 1), bits=8) sw_error = constants.twos_comp(self._get_regs(constants.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):
"""Initiate a soft reset""" """Trigger a soft reset."""
self._set_regs(SOFT_RESET_ADDR, SOFT_RESET_CMD) self._set_regs(constants.SOFT_RESET_ADDR, constants.SOFT_RESET_CMD)
time.sleep(RESET_PERIOD / 1000.0) time.sleep(constants.RESET_PERIOD / 1000.0)
def set_temp_offset(self, value): def set_temp_offset(self, value):
"""Set temperature offset in celsius """Set temperature offset in celsius.
If set, the temperature t_fine will be increased by given value 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 :param value: Temperature offset in Celsius, eg. 4, -8, 1.25
""" """
if value == 0: if value == 0:
self.offset_temp_in_t_fine = 0 self.offset_temp_in_t_fine = 0
@ -68,7 +88,7 @@ class BME680(BME680Data):
self.offset_temp_in_t_fine = int(math.copysign((((int(abs(value) * 100)) << 8) - 128) / 5, value)) 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.
@ -80,14 +100,14 @@ class BME680(BME680Data):
""" """
self.tph_settings.os_hum = value self.tph_settings.os_hum = value
self._set_bits(CONF_OS_H_ADDR, OSH_MSK, OSH_POS, value) self._set_bits(constants.CONF_OS_H_ADDR, constants.OSH_MSK, constants.OSH_POS, value)
def get_humidity_oversample(self): def get_humidity_oversample(self):
"""Get humidity oversampling""" """Get humidity oversampling."""
return (self._get_regs(CONF_OS_H_ADDR, 1) & OSH_MSK) >> OSH_POS return (self._get_regs(constants.CONF_OS_H_ADDR, 1) & constants.OSH_MSK) >> constants.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.
@ -99,14 +119,14 @@ class BME680(BME680Data):
""" """
self.tph_settings.os_pres = value self.tph_settings.os_pres = value
self._set_bits(CONF_T_P_MODE_ADDR, OSP_MSK, OSP_POS, value) self._set_bits(constants.CONF_T_P_MODE_ADDR, constants.OSP_MSK, constants.OSP_POS, value)
def get_pressure_oversample(self): def get_pressure_oversample(self):
"""Get pressure oversampling""" """Get pressure oversampling."""
return (self._get_regs(CONF_T_P_MODE_ADDR, 1) & OSP_MSK) >> OSP_POS return (self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) & constants.OSP_MSK) >> constants.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.
@ -118,14 +138,14 @@ class BME680(BME680Data):
""" """
self.tph_settings.os_temp = value self.tph_settings.os_temp = value
self._set_bits(CONF_T_P_MODE_ADDR, OST_MSK, OST_POS, value) self._set_bits(constants.CONF_T_P_MODE_ADDR, constants.OST_MSK, constants.OST_POS, value)
def get_temperature_oversample(self): def get_temperature_oversample(self):
"""Get temperature oversampling""" """Get temperature oversampling."""
return (self._get_regs(CONF_T_P_MODE_ADDR, 1) & OST_MSK) >> OST_POS return (self._get_regs(constants.CONF_T_P_MODE_ADDR, 1) & constants.OST_MSK) >> constants.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.
@ -138,39 +158,41 @@ class BME680(BME680Data):
""" """
self.tph_settings.filter = value self.tph_settings.filter = value
self._set_bits(CONF_ODR_FILT_ADDR, FILTER_MSK, FILTER_POS, value) self._set_bits(constants.CONF_ODR_FILT_ADDR, constants.FILTER_MSK, constants.FILTER_POS, value)
def get_filter(self): def get_filter(self):
"""Get filter size""" """Get filter size."""
return (self._get_regs(CONF_ODR_FILT_ADDR, 1) & FILTER_MSK) >> FILTER_POS return (self._get_regs(constants.CONF_ODR_FILT_ADDR, 1) & constants.FILTER_MSK) >> constants.FILTER_POS
def select_gas_heater_profile(self, value): def select_gas_heater_profile(self, value):
"""Set current gas sensor conversion profile: 0 to 9 """Set current gas sensor conversion profile.
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 > NBCONV_MAX or value < NBCONV_MIN: if value > constants.NBCONV_MAX or value < constants.NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(value, NBCONV_MIN, NBCONV_MAX)) raise ValueError("Profile '{}' should be between {} and {}".format(value, constants.NBCONV_MIN, constants.NBCONV_MAX))
self.gas_settings.nb_conv = value self.gas_settings.nb_conv = value
self._set_bits(CONF_ODR_RUN_GAS_NBC_ADDR, NBCONV_MSK, NBCONV_POS, value) self._set_bits(constants.CONF_ODR_RUN_GAS_NBC_ADDR, constants.NBCONV_MSK, constants.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(CONF_ODR_RUN_GAS_NBC_ADDR, 1) & NBCONV_MSK return self._get_regs(constants.CONF_ODR_RUN_GAS_NBC_ADDR, 1) & constants.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(CONF_ODR_RUN_GAS_NBC_ADDR, RUN_GAS_MSK, RUN_GAS_POS, value) self._set_bits(constants.CONF_ODR_RUN_GAS_NBC_ADDR, constants.RUN_GAS_MSK, constants.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(CONF_ODR_RUN_GAS_NBC_ADDR, 1) & RUN_GAS_MSK) >> RUN_GAS_POS return (self._get_regs(constants.CONF_ODR_RUN_GAS_NBC_ADDR, 1) & constants.RUN_GAS_MSK) >> constants.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
@ -181,7 +203,7 @@ 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
@ -189,15 +211,15 @@ class BME680(BME680Data):
make sure to select it with select_gas_heater_profile. make sure to select it with select_gas_heater_profile.
""" """
if nb_profile > NBCONV_MAX or value < NBCONV_MIN: if nb_profile > constants.NBCONV_MAX or value < constants.NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, NBCONV_MIN, NBCONV_MAX)) raise ValueError('Profile "{}" should be between {} and {}'.format(nb_profile, constants.NBCONV_MIN, constants.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(RES_HEAT0_ADDR + nb_profile, temp) self._set_regs(constants.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.
@ -208,28 +230,28 @@ class BME680(BME680Data):
make sure to select it with select_gas_heater_profile. make sure to select it with select_gas_heater_profile.
""" """
if nb_profile > NBCONV_MAX or value < NBCONV_MIN: if nb_profile > constants.NBCONV_MAX or value < constants.NBCONV_MIN:
raise ValueError("Profile '{}' should be between {} and {}".format(nb_profile, NBCONV_MIN, NBCONV_MAX)) raise ValueError('Profile "{}" should be between {} and {}'.format(nb_profile, constants.NBCONV_MIN, constants.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(GAS_WAIT0_ADDR + nb_profile, temp) self._set_regs(constants.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 (SLEEP_MODE, FORCED_MODE): if value not in (constants.SLEEP_MODE, constants.FORCED_MODE):
print("Power mode should be one of SLEEP_MODE or FORCED_MODE") raise ValueError('Power mode should be one of SLEEP_MODE or FORCED_MODE')
self.power_mode = value self.power_mode = value
self._set_bits(CONF_T_P_MODE_ADDR, MODE_MSK, MODE_POS, value) self._set_bits(constants.CONF_T_P_MODE_ADDR, constants.MODE_MSK, constants.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(POLL_PERIOD_MS / 1000.0) time.sleep(constants.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(CONF_T_P_MODE_ADDR, 1) self.power_mode = self._get_regs(constants.CONF_T_P_MODE_ADDR, 1)
return self.power_mode return self.power_mode
def get_sensor_data(self): def get_sensor_data(self):
@ -238,32 +260,32 @@ class BME680(BME680Data):
Stores data in .data and returns True upon success. Stores data in .data and returns True upon success.
""" """
self.set_power_mode(FORCED_MODE) self.set_power_mode(constants.FORCED_MODE)
for attempt in range(10): for attempt in range(10):
status = self._get_regs(FIELD0_ADDR, 1) status = self._get_regs(constants.FIELD0_ADDR, 1)
if (status & NEW_DATA_MSK) == 0: if (status & constants.NEW_DATA_MSK) == 0:
time.sleep(POLL_PERIOD_MS / 1000.0) time.sleep(constants.POLL_PERIOD_MS / 1000.0)
continue continue
regs = self._get_regs(FIELD0_ADDR, FIELD_LENGTH) regs = self._get_regs(constants.FIELD0_ADDR, constants.FIELD_LENGTH)
self.data.status = regs[0] & NEW_DATA_MSK self.data.status = regs[0] & constants.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] & GAS_INDEX_MSK self.data.gas_index = regs[0] & constants.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] & GAS_RANGE_MSK gas_range = regs[14] & constants.GAS_RANGE_MSK
self.data.status |= regs[14] & GASM_VALID_MSK self.data.status |= regs[14] & constants.GASM_VALID_MSK
self.data.status |= regs[14] & HEAT_STAB_MSK self.data.status |= regs[14] & constants.HEAT_STAB_MSK
self.data.heat_stable = (self.data.status & HEAT_STAB_MSK) > 0 self.data.heat_stable = (self.data.status & constants.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
@ -277,27 +299,28 @@ 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 >> 3) - (self.calibration_data.par_t1 << 1)
var2 = (var1 * self.calibration_data.par_t2) >> 11 var2 = (var1 * self.calibration_data.par_t2) >> 11
var3 = ((var1 >> 1) * (var1 >> 1)) >> 12 var3 = ((var1 >> 1) * (var1 >> 1)) >> 12
@ -310,6 +333,7 @@ class BME680(BME680Data):
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) >> 1) - 64000
var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) *
self.calibration_data.par_p6) >> 2 self.calibration_data.par_p6) >> 2
@ -343,13 +367,14 @@ class BME680(BME680Data):
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) + 128) >> 8
var1 = (humidity_adc - ((self.calibration_data.par_h1 * 16))) \ var1 = (humidity_adc - ((self.calibration_data.par_h1 * 16))) -\
- (((temp_scaled * self.calibration_data.par_h3) // (100)) >> 1) (((temp_scaled * self.calibration_data.par_h3) // (100)) >> 1)
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)) +
+ (((temp_scaled * ((temp_scaled * self.calibration_data.par_h5) // (100))) >> 6) (((temp_scaled * ((temp_scaled * self.calibration_data.par_h5) // (100))) >> 6) //
// (100)) + (1 * 16384))) >> 10 (100)) + (1 * 16384))) >> 10
var3 = var1 * var2 var3 = var1 * var2
var4 = self.calibration_data.par_h6 << 7 var4 = self.calibration_data.par_h6 << 7
var4 = ((var4) + ((temp_scaled * self.calibration_data.par_h7) // (100))) >> 4 var4 = ((var4) + ((temp_scaled * self.calibration_data.par_h7) // (100))) >> 4
@ -360,6 +385,7 @@ class BME680(BME680Data):
return min(max(calc_hum, 0), 100000) 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)
@ -371,6 +397,7 @@ class BME680(BME680Data):
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
@ -384,6 +411,7 @@ 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,3 +1,5 @@
"""BME680 constants, structures and utilities."""
# BME680 General config # BME680 General config
POLL_PERIOD_MS = 10 POLL_PERIOD_MS = 10
@ -223,21 +225,28 @@ lookupTable2 = [4096000000, 2048000000, 1024000000, 512000000,
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:
def __init__(self): """Structure for storing BME680 sensor data."""
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
@ -254,10 +263,11 @@ 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:
def __init__(self): """Structure for storing BME680 calibration data."""
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
@ -291,6 +301,7 @@ 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)
@ -323,15 +334,20 @@ 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:
def __init__(self): """Structure for storing BME680 sensor settings.
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
@ -341,11 +357,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:
def __init__(self): """Structure for storing BME680 gas settings and status."""
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
@ -357,10 +373,11 @@ 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:
def __init__(self): """Structure to represent BME680 device."""
def __init__(self): # noqa D107
# Chip Id # Chip Id
self.chip_id = None self.chip_id = None
# Device Id # Device Id

14
library/setup.cfg Normal file
View File

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

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
@ -43,7 +43,7 @@ setup(
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.rst').read() + '\n' + open('CHANGELOG.txt').read(),
license='MIT', license='MIT',
keywords='Raspberry Pi', keywords='Raspberry Pi',
url='http://www.pimoroni.com', url='http://www.pimoroni.com',

8
library/tests/setup.cfg Normal file
View File

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

View File

@ -0,0 +1,57 @@
import sys
import mock
import pytest
import bme680
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]
def test_setup_not_present():
"""Mock the adbsence of a BME680 and test initialisation."""
sys.modules['smbus'] = mock.MagicMock()
sys.modules['smbus'].SMBus = MockSMBus
with pytest.raises(RuntimeError):
sensor = bme680.BME680() # noqa F841
def test_setup_mock_present():
"""Mock the presence of a BME680 and test initialisation."""
sys.modules['smbus'] = mock.MagicMock()
sys.modules['smbus'].SMBus = MockSMBusPresent
sensor = bme680.BME680() # noqa F841

25
library/tox.ini Normal file
View File

@ -0,0 +1,25 @@
[tox]
envlist = py{27,35},qa
skip_missing_interpreters = True
[testenv]
commands =
python setup.py install
coverage run -m py.test -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