diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..5211bea --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ + +# python-bme280 + +A Python library for accessing the [BME280 combined humidity and pressure](https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf) from Bosch via `python-smbus` using the I2C interface. + +Default settings are suitable for Raspberry Pi 2 and 3 and was successfully tested using a [breakout](https://github.com/watterott/BME280-Breakout). + +I created this Python library in style of e.g. [python-veml6070](https://github.com/cmur2/python-veml6070) or [python-tsl2591](https://github.com/maxlklaxl/python-tsl2591) since I found only [python scripts](https://github.com/SWITCHSCIENCE/BME280) with limited functionality and minor bugs and the official driver from Bosch is only in C(++). + +Although, it is heavily based on existing code from Bosch translated to Python and from [SWITCHSCIENCE](https://github.com/SWITCHSCIENCE/BME280). + +## Usage + +Consult the data sheet and see [demo.py](demo.py) for clues how to use this library. + +Not all functions of the chip are supported since I focused on the *forced mode* but data readout and parameter setting should work in *normal mode*, too. Please send pull requests for improvements and bug fixes! + +## License + +Python files in this repository are released under the [MIT license](LICENSE) except those parts from other sources which are indicated where appropriate in the files. diff --git a/bme280/__init__.py b/bme280/__init__.py new file mode 100644 index 0000000..afd3295 --- /dev/null +++ b/bme280/__init__.py @@ -0,0 +1,4 @@ + +from bme280 import Bme280 +#from bme280 import HO_SKIPPED, HO_1, HO_2, HO_4, HO_8, HO_16, PO_SKIPPED, PO_1, PO_2, PO_4, PO_8, PO_16, TO_SKIPPED, TO_1, TO_2, TO_4, TO_8, TO_16 +#from bme280 import MODE_SLEEP, MODE_FORCED, MODE_NORMAL diff --git a/bme280/bme280.py b/bme280/bme280.py new file mode 100644 index 0000000..d7ee07c --- /dev/null +++ b/bme280/bme280.py @@ -0,0 +1,259 @@ + +import smbus +import time + +ADDR=0x76 # 7bit address of the BME280 for SDO=0, else 0x77 + +REGISTER_ID=0xD0 +REGISTER_RESET=0xE0 +REGISTER_CTRL_HUM=0xF2 +REGISTER_STATUS=0xF3 +REGISTER_CTRL_MEAS=0xF4 +REGISTER_CONFIG=0xF5 + +HO_SKIPPED=0x00 +HO_1=0x01 +HO_2=0x02 +HO_4=0x03 +HO_8=0x04 +HO_16=0x05 # and all higher + +PO_SKIPPED=0x00 +PO_1=0x01 +PO_2=0x02 +PO_4=0x03 +PO_8=0x04 +PO_16=0x05 # and all higher + +TO_SKIPPED=0x00 +TO_1=0x01 +TO_2=0x02 +TO_4=0x03 +TO_8=0x04 +TO_16=0x05 # and all higher + +MODE_SLEEP=0x00 +MODE_FORCED=0x01 # and 0x02 +MODE_NORMAL=0x03 + +TSTANDBY_0_5=0x00 +TSTANDBY_62_5=0x01 +TSTANDBY_125=0x02 +TSTANDBY_250=0x03 +TSTANDBY_500=0x04 +TSTANDBY_1000=0x05 +TSTANDBY_10=0x06 +TSTANDBY_20=0x07 + +FILTER_OFF=0x00 +FILTER_2=0x01 +FILTER_4=0x02 +FILTER_8=0x03 +FILTER_16=0x04 # and all higher + +class Bme280: + + def __init__(self, i2c_bus=1, sensor_address=ADDR): + self.bus = smbus.SMBus(i2c_bus) + self.sensor_address = sensor_address + self.ho = HO_1 + self.po = PO_1 + self.to = TO_1 + self.mode = MODE_SLEEP + self.tstandy = TSTANDBY_1000 + self.filter = FILTER_OFF + + self.read_calibration_parameters() + + # initialize once + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_HUM, self.get_reg_ctrl_hum()) + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_MEAS, self.get_reg_ctrl_meas()) + self.bus.write_byte_data(self.sensor_address, REGISTER_CONFIG, self.get_reg_config()) + + def get_chip_id(self): + return self.bus.read_byte_data(self.sensor_address, REGISTER_ID) + + def reset(self): + self.bus.write_byte_data(self.sensor_address, REGISTER_RESET, 0xB6) + + def is_status_measuring(self): + return (self.bus.read_byte_data(self.sensor_address, REGISTER_STATUS) & 0x08) != 0x00 + + def is_status_image_register_updating(self): + return (self.bus.read_byte_data(self.sensor_address, REGISTER_STATUS) & 0x01) != 0x00 + + def set_humidity_oversampling(self, ho): + self.ho = ho + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_HUM, self.get_reg_ctrl_hum()) + # flush (unchanged) CTRL_MEAS to make CTRL_HUM effective! + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_MEAS, self.get_reg_ctrl_meas()) + + def get_humidity_oversampling(self): + return self.ho + + def set_temperature_oversampling(self, to): + self.to = to + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_MEAS, self.get_reg_ctrl_meas()) + + def get_temperature_oversampling(self): + return self.to + + def set_pressure_oversampling(self, po): + self.po = po + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_MEAS, self.get_reg_ctrl_meas()) + + def get_pressure_oversampling(self): + return self.po + + def set_mode(self, mode): + self.mode = mode + self.bus.write_byte_data(self.sensor_address, REGISTER_CTRL_MEAS, self.get_reg_ctrl_meas()) + + def get_mode(self): + return self.mode + + def set_tstandy(self, tstandy): + self.tstandy = tstandy + self.bus.write_byte_data(self.sensor_address, REGISTER_CONFIG, self.get_reg_config()) + + def get_tstandy(self): + return self.tstandy + + def set_filter(self, fil): + self.filter = fil + self.bus.write_byte_data(self.sensor_address, REGISTER_CONFIG, self.get_reg_config()) + + def get_filter(self): + return self.filter + + def get_data(self): + if self.get_mode() == MODE_FORCED: + t_measure_max = 1.25 + (2.3 * self.to) + (2.3 * self.po + 0.575) + (2.3 * self.ho + 0.575) + time.sleep(t_measure_max/1000.0) + + data = [] + for i in range(0xF7, 0xF7+8): + data.append(self.bus.read_byte_data(self.sensor_address, i)) + + pressure_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4) + temperature_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4) + humidity_raw = (data[6] << 8) | data[7] + t_fine = self.calc_t_fine(temperature_raw) + t = self.calc_compensated_temperature(t_fine) + p = self.calc_compensated_pressure(t_fine, pressure_raw) + h = self.calc_compensated_humidity(t_fine, humidity_raw) + + if self.get_mode() == MODE_FORCED: + # chip returns to sleep after data readout automatically, mirror it + self.mode = MODE_SLEEP + + return (t, p, h) + + def get_reg_ctrl_hum(self): + """ + returns the bit pattern for CTRL_HUM corresponding to the desired state of this class + """ + return (self.ho & 0x07) + + def get_reg_ctrl_meas(self): + """ + returns the bit pattern for CTRL_MEAS corresponding to the desired state of this class + """ + return ((self.to & 0x07) << 5) | ((self.po & 0x07) << 2) | self.mode + + def get_reg_config(self): + """ + returns the bit pattern for CONFIG corresponding to the desired state of this class + """ + # SPI permanently disabled + return ((self.tstandy & 0x07) << 5) | ((self.filter & 0x07) << 2) | 0x00 + + # Bug-fixed code, originally from https://github.com/SWITCHSCIENCE/BME280 + + def read_calibration_parameters(self): + # read all calibration registers from chip NVM + calibration_regs = [] + for i in range(0x88, 0x88+24): + calibration_regs.append(self.bus.read_byte_data(self.sensor_address, i)) + calibration_regs.append(self.bus.read_byte_data(self.sensor_address, 0xA1)) + for i in range(0xE1, 0xE1+7): + calibration_regs.append(self.bus.read_byte_data(self.sensor_address, i)) + + # reorganize 8-bit words into compensation words (without correct sign) + self.digT = [] + self.digT.append((calibration_regs[1] << 8) | calibration_regs[0]) + self.digT.append((calibration_regs[3] << 8) | calibration_regs[2]) + self.digT.append((calibration_regs[5] << 8) | calibration_regs[4]) + + self.digP = [] + self.digP.append((calibration_regs[7] << 8) | calibration_regs[6]) + self.digP.append((calibration_regs[9] << 8) | calibration_regs[8]) + self.digP.append((calibration_regs[11]<< 8) | calibration_regs[10]) + self.digP.append((calibration_regs[13]<< 8) | calibration_regs[12]) + self.digP.append((calibration_regs[15]<< 8) | calibration_regs[14]) + self.digP.append((calibration_regs[17]<< 8) | calibration_regs[16]) + self.digP.append((calibration_regs[19]<< 8) | calibration_regs[18]) + self.digP.append((calibration_regs[21]<< 8) | calibration_regs[20]) + self.digP.append((calibration_regs[23]<< 8) | calibration_regs[22]) + + self.digH = [] + self.digH.append( calibration_regs[24] ) + self.digH.append((calibration_regs[26]<< 8) | calibration_regs[25]) + self.digH.append( calibration_regs[27] ) + self.digH.append((calibration_regs[28]<< 4) | (0x0F & calibration_regs[29])) + self.digH.append((calibration_regs[30]<< 4) | ((calibration_regs[29] >> 4) & 0x0F)) + self.digH.append( calibration_regs[31] ) + + # fix sign for integers in two's complement + for i in [1,2]: + if self.digT[i] & 0x8000: + self.digT[i] = (-self.digT[i] ^ 0xFFFF) + 1 + + for i in [1,2,3,4,5,6,7,8]: + if self.digP[i] & 0x8000: + self.digP[i] = (-self.digP[i] ^ 0xFFFF) + 1 + + for i in [1]: + if self.digH[i] & 0x8000: + self.digH[i] = (-self.digH[i] ^ 0xFFFF) + 1 + for i in [3,4]: + if self.digH[i] & 0x0800: + self.digH[i] = (-self.digH[i] ^ 0x0FFF) + 1 + for i in [5]: + if self.digH[i] & 0x0080: + self.digH[i] = (-self.digH[i] ^ 0x00FF) + 1 + + # Code from Bosch datasheet translated to Python + + def calc_t_fine(self, adc_T): + var1 = (adc_T / 16384.0 - self.digT[0] / 1024.0) * self.digT[1] + var2 = (adc_T / 131072.0 - self.digT[0] / 8192.0) * (adc_T / 131072.0 - self.digT[0] / 8192.0) * self.digT[2] + return var1 + var2 + + def calc_compensated_temperature(self, t_fine): + return t_fine / 5120.0 + + def calc_compensated_pressure(self, t_fine, adc_P): + var1 = (t_fine/2.0) - 64000.0 + var2 = var1 * var1 * (self.digP[5]) / 32768.0 + var2 = var2 + var1 * (self.digP[4]) * 2.0 + var2 = (var2/4.0)+(self.digP[3] * 65536.0) + var1 = (self.digP[2] * var1 * var1 / 524288.0 + self.digP[1] * var1) / 524288.0 + var1 = (1.0 + var1 / 32768.0)*self.digP[0] + if var1 == 0.0: + return 0 # avoid exception caused by division by zero + p = 1048576.0 - adc_P + p = (p - (var2 / 4096.0)) * 6250.0 / var1 + var1 = self.digP[8] * p * p / 2147483648.0 + var2 = p * self.digP[7] / 32768.0 + return p + (var1 + var2 + self.digP[6]) / 16.0 + + def calc_compensated_humidity(self, t_fine, adc_H): + var_H = t_fine - 76800.0 + var_H = (adc_H - (self.digH[3] * 64.0 + self.digH[4] / 16384.0 * var_H)) * (self.digH[1] / 65536.0 * (1.0 + self.digH[5] / 67108864.0 * var_H * (1.0 + self.digH[2] / 67108864.0 * var_H))) + var_H = var_H * (1.0 - self.digH[0] * var_H / 524288.0) + if var_H > 100.0: + var_H = 100.0 + elif var_H < 0.0: + var_H = 0.0 + return var_H \ No newline at end of file diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..065d6b2 --- /dev/null +++ b/demo.py @@ -0,0 +1,10 @@ + +import bme280 + +if __name__ == '__main__': + bme = bme280.Bme280() + bme.set_mode(bme280.MODE_FORCED) + t, p, h = bme.get_data() + print "Temperature: %f °C" % t + print "Pressure: %f P" % p + print "Humidity: %f %" % h diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3ffc573 --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup(name='bme280', + version='1.0', + url='http://github.com/cmur2/python-bme280', + author='Christian Nicolai', + description='A python library for accessing the BME280 combined humidity and pressure sensor from Bosch.', + packages=['bme280'], + long_description=open('README.md').read(), + requires=['python (>= 2.7)', 'smbus (>= 0.4.1)'], + install_requires=['smbus-cffi']) \ No newline at end of file