From f92fa6c1448bb9860f8bc08317b3e2c1ab350ddc Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 31 Oct 2017 10:57:23 -0700 Subject: [PATCH] Fix up the docs. --- adafruit_bme680.py | 361 +++++++++++++++++++++++++-------------------- conf.py | 2 + 2 files changed, 200 insertions(+), 163 deletions(-) diff --git a/adafruit_bme680.py b/adafruit_bme680.py index 38ff2a6..60d8b77 100644 --- a/adafruit_bme680.py +++ b/adafruit_bme680.py @@ -1,3 +1,34 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 ladyada for Adafruit Industries +# +# 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. + +""" +`adafruit_bme680` +==================================================== + +CircuitPython driver from BME680 air quality sensor + +* Author(s): ladyada +""" + import time, math from micropython import const try: @@ -7,8 +38,7 @@ # I2C ADDRESS/BITS/SETTINGS # ----------------------------------------------------------------------- -_BME680_ADDRESS = const(0x77) -_BME680_CHIPID = const(0x61) +_BME680_CHIPID = const(0x61) _BME680_REG_CHIPID = const(0xD0) _BME680_BME680_COEFF_ADDR1 = const(0x89) @@ -38,57 +68,59 @@ class Adafruit_BME680: + """Driver from BME680 air quality sensor""" def __init__(self): - """Check the BME680 was found, read the coefficients and enable the sensor for continuous reads""" - self._write(_BME680_REG_SOFTRESET, [0xB6]) - time.sleep(0.5) - - # Check device ID. - id = self._read_byte(_BME680_REG_CHIPID) - if _BME680_CHIPID != id: - raise RuntimeError('Failed to find BME680! Chip ID 0x%x' % id) - - self._read_coefficients() - - # set up heater - self._write(_BME680_BME680_RES_WAIT_0, [0x73, 0x64, 0x65]) - self.seaLevelhPa = 1013.25 - self.pres_oversample = 4 - self.temp_oversample = 8 - self.hum_oversample = 2 - self.filter = 2 - + """Check the BME680 was found, read the coefficients and enable the sensor for continuous reads""" + self._write(_BME680_REG_SOFTRESET, [0xB6]) + time.sleep(0.5) + + # Check device ID. + id = self._read_byte(_BME680_REG_CHIPID) + if _BME680_CHIPID != id: + raise RuntimeError('Failed to find BME680! Chip ID 0x%x' % id) + + self._read_coefficients() + + # set up heater + self._write(_BME680_BME680_RES_WAIT_0, [0x73, 0x64, 0x65]) + self.seaLevelhPa = 1013.25 + """Pressure in hectoPascals at sea level. Used to calibrate `altitude`.""" + self.pres_oversample = 4 + self.temp_oversample = 8 + self.hum_oversample = 2 + self.filter = 2 + @property - def pres_oversample(self): + def pressure_oversample(self): + """The oversampling for pressure sensor""" return _BME680_SAMPLERATES[self.osrs_p] - - @pres_oversample.setter - def pres_oversample(self, os): - """Set the oversampling for pressure sensor""" + + @pressure_oversample.setter + def pressure_oversample(self, os): if os in _BME680_SAMPLERATES: self.osrs_p = _BME680_SAMPLERATES.index(os) else: raise RuntimeError("Invalid oversample") @property - def hum_oversample(self): + def humidity_oversample(self): + """The oversampling for humidity sensor""" return _BME680_SAMPLERATES[self.osrs_h] - - @hum_oversample.setter - def hum_oversample(self, os): - """Set the oversampling for humidity sensor""" + + @humidity_oversample.setter + def humidity_oversample(self, os): if os in _BME680_SAMPLERATES: self.osrs_h = _BME680_SAMPLERATES.index(os) else: raise RuntimeError("Invalid oversample") @property - def temp_oversample(self): + def temperature_oversample(self): + """The oversampling for temperature sensor""" return _BME680_SAMPLERATES[self.osrs_p] - - @temp_oversample.setter - def temp_oversample(self, os): - """Set the oversampling for temperature sensor""" + + @temperature_oversample.setter + def temperature_oversample(self, os): if os in _BME680_SAMPLERATES: self.osrs_t = _BME680_SAMPLERATES.index(os) else: @@ -96,11 +128,11 @@ def temp_oversample(self, os): @property def filter_size(self): + """The filter size for the built in IIR filter""" return _BME680_FILTERSIZES[self.filter] - + @filter_size.setter def filter_size(self, fs): - """Set the filter size for the built in IIR filter""" if fs in _BME680_FILTERSIZES: self.filter = _BME680_FILTERSIZES(fs) else: @@ -108,166 +140,169 @@ def filter_size(self, fs): @property def temperature(self): - """The compensated temperature in degrees celsius.""" - self._perform_reading() - var1 = (self._adc_temp / 8) - (self._T1 * 2) - var2 = (var1 * self._T2) / 2048 - var3 = ((var1 / 2) * (var1 / 2)) / 4096 - var3 = (var3 * self._T3 * 16) / 16384 - self.t_fine = int(var2 + var3) - calc_temp = (((self.t_fine * 5) + 128) / 256) - return calc_temp / 100 + """The compensated temperature in degrees celsius.""" + self._perform_reading() + var1 = (self._adc_temp / 8) - (self._T1 * 2) + var2 = (var1 * self._T2) / 2048 + var3 = ((var1 / 2) * (var1 / 2)) / 4096 + var3 = (var3 * self._T3 * 16) / 16384 + self.t_fine = int(var2 + var3) + calc_temp = (((self.t_fine * 5) + 128) / 256) + return calc_temp / 100 @property def pressure(self): """The barometric pressure in hectoPascals""" - self.temperature - var1 = (self.t_fine / 2) - 64000 - var2 = ((var1 / 4) * (var1 / 4)) / 2048 - var2 = (var2 * self._P6) / 4 - var2 = var2 + (var1 * self._P5 * 2) - var2 = (var2 / 4) + (self._P4 * 65536) - var1 = ((var1 / 4) * (var1 / 4)) / 8192 - var1 = ((var1 * self._P3 * 32) / 8) + ((self._P2 * var1) / 2) - var1 = var1 / 262144 - var1 = ((32768 + var1) * self._P1) / 32768 - calc_pres = 1048576 - self._adc_pres - calc_pres = (calc_pres - (var2 / 4096)) * 3125 - calc_pres = (calc_pres / var1) * 2 - var1 = (self._P9 * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096 - var2 = ((calc_pres / 4) * self._P8) / 8192 - var3 = ((calc_pres / 256) * (calc_pres / 256) * (calc_pres / 256) * self._P10) / 131072 - calc_pres += ((var1 + var2 + var3 + (self._P7 * 128)) / 16) - return calc_pres/100 + self.temperature + var1 = (self.t_fine / 2) - 64000 + var2 = ((var1 / 4) * (var1 / 4)) / 2048 + var2 = (var2 * self._P6) / 4 + var2 = var2 + (var1 * self._P5 * 2) + var2 = (var2 / 4) + (self._P4 * 65536) + var1 = ((var1 / 4) * (var1 / 4)) / 8192 + var1 = ((var1 * self._P3 * 32) / 8) + ((self._P2 * var1) / 2) + var1 = var1 / 262144 + var1 = ((32768 + var1) * self._P1) / 32768 + calc_pres = 1048576 - self._adc_pres + calc_pres = (calc_pres - (var2 / 4096)) * 3125 + calc_pres = (calc_pres / var1) * 2 + var1 = (self._P9 * (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096 + var2 = ((calc_pres / 4) * self._P8) / 8192 + var3 = ((calc_pres / 256) * (calc_pres / 256) * (calc_pres / 256) * self._P10) / 131072 + calc_pres += ((var1 + var2 + var3 + (self._P7 * 128)) / 16) + return calc_pres/100 @property def humidity(self): """The relative humidity in RH %""" - self.temperature - temp_scaled = ((self.t_fine * 5) + 128) / 256 - var1 = (self._adc_hum - (self._H1 * 16)) - ((temp_scaled * self._H3) / 200) - var2 = (self._H2 * (((temp_scaled * self._H4) / 100) + (((temp_scaled * ((temp_scaled * self._H5) / 100)) / 64) / 100) + 16384)) / 1024 - var3 = var1 * var2 - var4 = self._H6 * 128 - var4 = (var4 + ((temp_scaled * self._H7) / 100)) / 16 - var5 = ((var3 / 16384) * (var3 / 16384)) / 1024 - var6 = (var4 * var5) / 2 - calc_hum = (((var3 + var6) / 1024) * 1000) / 4096 - calc_hum /= 1000 # get back to RH - - if calc_hum > 100: calc_hum = 100 - if calc_hum < 0: calc_hum = 0 - return calc_hum - + self.temperature # Trigger a read + temp_scaled = ((self.t_fine * 5) + 128) / 256 + var1 = (self._adc_hum - (self._H1 * 16)) - ((temp_scaled * self._H3) / 200) + var2 = (self._H2 * (((temp_scaled * self._H4) / 100) + (((temp_scaled * ((temp_scaled * self._H5) / 100)) / 64) / 100) + 16384)) / 1024 + var3 = var1 * var2 + var4 = self._H6 * 128 + var4 = (var4 + ((temp_scaled * self._H7) / 100)) / 16 + var5 = ((var3 / 16384) * (var3 / 16384)) / 1024 + var6 = (var4 * var5) / 2 + calc_hum = (((var3 + var6) / 1024) * 1000) / 4096 + calc_hum /= 1000 # get back to RH + + if calc_hum > 100: + calc_hum = 100 + if calc_hum < 0: + calc_hum = 0 + return calc_hum + @property def altitude(self): - """calculate the altitude based on the sea level pressure (seaLevelhPa) - which you must enter ahead of time)""" + """The altitude based on current `pressure` vs the sea level pressure (`seaLevelhPa`) - which you must enter ahead of time)""" p = self.pressure # in Si units for hPascal return 44330 * (1.0 - math.pow(p / self.seaLevelhPa, 0.1903)); @property def gas(self): - """Return the gas resistance in ohms""" - var1 = ((1340 + (5 * self._sw_err)) * (lookupTable1[self._gas_range])) / 65536 - var2 = ((self._adc_gas * 32768) - 16777216) + var1 - var3 = (lookupTable2[self._gas_range] * var1) / 512 - calc_gas_res = (var3 + (var2 / 2)) / var2 - return int(calc_gas_res) + """The gas resistance in ohms""" + var1 = ((1340 + (5 * self._sw_err)) * (lookupTable1[self._gas_range])) / 65536 + var2 = ((self._adc_gas * 32768) - 16777216) + var1 + var3 = (lookupTable2[self._gas_range] * var1) / 512 + calc_gas_res = (var3 + (var2 / 2)) / var2 + return int(calc_gas_res) def _perform_reading(self): """Perform a single-shot reading from the sensor and fill internal data structure for calculations""" - # set filter - self._write(_BME680_REG_CONFIG, [self.filter << 2]) - # turn on temp oversample & pressure oversample - self._write(_BME680_REG_CTRL_MEAS, [(self.osrs_t << 5)|(self.osrs_p << 2)]) - # turn on humidity oversample - self._write(_BME680_REG_CTRL_HUM, [self.osrs_h]) - # gas measurements enabled - self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS]) - - v = self._read(_BME680_REG_CTRL_MEAS, 1)[0] - v = (v & 0xFC) | 0x01 # enable single shot! - self._write(_BME680_REG_CTRL_MEAS, [v]) - time.sleep(0.5) - data = self._read(_BME680_REG_STATUS, 15) - self._status = data[0] & 0x80 - gas_idx = data[0] & 0x0F - meas_idx = data[1] - #print("status 0x%x gas_idx %d meas_idx %d" % (status, gas_idx, meas_idx)) - - self._adc_pres = self._read24(data[2:5]) / 16 - self._adc_temp = self._read24(data[5:8]) / 16 - self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0] - self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64) + # set filter + self._write(_BME680_REG_CONFIG, [self.filter << 2]) + # turn on temp oversample & pressure oversample + self._write(_BME680_REG_CTRL_MEAS, [(self.osrs_t << 5)|(self.osrs_p << 2)]) + # turn on humidity oversample + self._write(_BME680_REG_CTRL_HUM, [self.osrs_h]) + # gas measurements enabled + self._write(_BME680_REG_CTRL_GAS, [_BME680_RUNGAS]) + + v = self._read(_BME680_REG_CTRL_MEAS, 1)[0] + v = (v & 0xFC) | 0x01 # enable single shot! + self._write(_BME680_REG_CTRL_MEAS, [v]) + time.sleep(0.5) + data = self._read(_BME680_REG_STATUS, 15) + self._status = data[0] & 0x80 + gas_idx = data[0] & 0x0F + meas_idx = data[1] + #print("status 0x%x gas_idx %d meas_idx %d" % (status, gas_idx, meas_idx)) + + self._adc_pres = self._read24(data[2:5]) / 16 + self._adc_temp = self._read24(data[5:8]) / 16 + self._adc_hum = struct.unpack('>H', bytes(data[8:10]))[0] + self._adc_gas = int(struct.unpack('>H', bytes(data[13:15]))[0] / 64) self._gas_range = data[14] & 0x0F - #print(self._adc_hum) - #print(self._adc_gas) - self._status |= data[14] & 0x30 # VALID + STABILITY mask + #print(self._adc_hum) + #print(self._adc_gas) + self._status |= data[14] & 0x30 # VALID + STABILITY mask def _read24(self, arr): - """Parse an unsigned 24-bit value as a floating point and return it.""" - ret = 0.0 - #print([hex(i) for i in arr]) - for b in arr: - ret *= 256.0 - ret += float(b & 0xFF) - return ret + """Parse an unsigned 24-bit value as a floating point and return it.""" + ret = 0.0 + #print([hex(i) for i in arr]) + for b in arr: + ret *= 256.0 + ret += float(b & 0xFF) + return ret def _read_coefficients(self): - """Read & save the calibration coefficients""" - coeff = self._read(_BME680_BME680_COEFF_ADDR1, 25) - coeff += self._read(_BME680_BME680_COEFF_ADDR2, 16) - - coeff = list(struct.unpack(' %s" % (register, [hex(i) for i in result])) - return result + return result def _write(self, register, values): """Writes an array of 'length' bytes to the 'register'""" - with self._i2c as i2c: - values = [(v & 0xFF) for v in [register]+values] - i2c.write(bytes(values)) + with self._i2c as i2c: + values = [(v & 0xFF) for v in [register]+values] + i2c.write(bytes(values)) if self._debug: print("\t$%02X <= %s" % (values[0], [hex(i) for i in values[1:]])) diff --git a/conf.py b/conf.py index 3acc0c8..ff2d555 100644 --- a/conf.py +++ b/conf.py @@ -15,6 +15,8 @@ 'sphinx.ext.viewcode', ] +autodoc_mock_imports = ['micropython'] + intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'BusDevice': ('https://circuitpython.readthedocs.io/projects/bus_device/en/latest/', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} # Add any paths that contain templates here, relative to this directory.