From 042a5cb75be88701c6c080579c49dd783273bfa4 Mon Sep 17 00:00:00 2001 From: Stefan Huber <stefan.huber@stusta.de> Date: Fri, 29 Nov 2019 13:22:32 +0100 Subject: [PATCH] for branch maxbill --- BME280.py | 292 +++++++++++++++++++++++++++++++++++++++++++++++ main.py | 58 ++++++++++ tempermon.ini | 42 +++++-- tempermonitor.py | 2 +- 4 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 BME280.py create mode 100644 main.py diff --git a/BME280.py b/BME280.py new file mode 100644 index 0000000..bfaa060 --- /dev/null +++ b/BME280.py @@ -0,0 +1,292 @@ +""" +This code is designed to run on a ESP32 or ESP8266. It will +control one single BME280 via I2C. +""" +from machine import I2C +import time + +# BME280 default address. +BME280_I2CADDR = 0x76 + +# Operating Modes +BME280_OSAMPLE_1 = 1 +BME280_OSAMPLE_2 = 2 +BME280_OSAMPLE_4 = 3 +BME280_OSAMPLE_8 = 4 +BME280_OSAMPLE_16 = 5 + +# BME280 Registers + +BME280_REGISTER_DIG_T1 = 0x88 # Trimming parameter registers +BME280_REGISTER_DIG_T2 = 0x8A +BME280_REGISTER_DIG_T3 = 0x8C + +BME280_REGISTER_DIG_P1 = 0x8E +BME280_REGISTER_DIG_P2 = 0x90 +BME280_REGISTER_DIG_P3 = 0x92 +BME280_REGISTER_DIG_P4 = 0x94 +BME280_REGISTER_DIG_P5 = 0x96 +BME280_REGISTER_DIG_P6 = 0x98 +BME280_REGISTER_DIG_P7 = 0x9A +BME280_REGISTER_DIG_P8 = 0x9C +BME280_REGISTER_DIG_P9 = 0x9E + +BME280_REGISTER_DIG_H1 = 0xA1 +BME280_REGISTER_DIG_H2 = 0xE1 +BME280_REGISTER_DIG_H3 = 0xE3 +BME280_REGISTER_DIG_H4 = 0xE4 +BME280_REGISTER_DIG_H5 = 0xE5 +BME280_REGISTER_DIG_H6 = 0xE6 +BME280_REGISTER_DIG_H7 = 0xE7 + +BME280_REGISTER_CHIPID = 0xD0 +BME280_REGISTER_VERSION = 0xD1 +BME280_REGISTER_SOFTRESET = 0xE0 + +BME280_REGISTER_CONTROL_HUM = 0xF2 +BME280_REGISTER_CONTROL = 0xF4 +BME280_REGISTER_CONFIG = 0xF5 +BME280_REGISTER_PRESSURE_DATA = 0xF7 +BME280_REGISTER_TEMP_DATA = 0xFA +BME280_REGISTER_HUMIDITY_DATA = 0xFD + + +class Device: + """Class for communicating with an I2C device. + + Allows reading and writing 8-bit, 16-bit, and byte array values to + registers on the device.""" + + def __init__(self, address, i2c): + """Create an instance of the I2C device at the specified address using + the specified I2C interface object.""" + self._address = address + self._i2c = i2c + + def writeRaw8(self, value): + """Write an 8-bit value on the bus (without register).""" + value = value & 0xFF + self._i2c.writeto(self._address, value) + + def write8(self, register, value): + """Write an 8-bit value to the specified register.""" + b=bytearray(1) + b[0]=value & 0xFF + self._i2c.writeto_mem(self._address, register, b) + + def write16(self, register, value): + """Write a 16-bit value to the specified register.""" + value = value & 0xFFFF + b=bytearray(2) + b[0]= value & 0xFF + b[1]= (value>>8) & 0xFF + self.i2c.writeto_mem(self._address, register, value) + + def readRaw8(self): + """Read an 8-bit value on the bus (without register).""" + return int.from_bytes(self._i2c.readfrom(self._address, 1),'little') & 0xFF + + def readU8(self, register): + """Read an unsigned byte from the specified register.""" + return int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 1),'little') & 0xFF + + def readS8(self, register): + """Read a signed byte from the specified register.""" + result = self.readU8(register) + if result > 127: + result -= 256 + return result + + def readU16(self, register, little_endian=True): + """Read an unsigned 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = int.from_bytes( + self._i2c.readfrom_mem(self._address, register, 2),'little') & 0xFFFF + if not little_endian: + result = ((result << 8) & 0xFF00) + (result >> 8) + return result + + def readS16(self, register, little_endian=True): + """Read a signed 16-bit value from the specified register, with the + specified endianness (default little endian, or least significant byte + first).""" + result = self.readU16(register, little_endian) + if result > 32767: + result -= 65536 + return result + + def readU16LE(self, register): + """Read an unsigned 16-bit value from the specified register, in little + endian byte order.""" + return self.readU16(register, little_endian=True) + + def readU16BE(self, register): + """Read an unsigned 16-bit value from the specified register, in big + endian byte order.""" + return self.readU16(register, little_endian=False) + + def readS16LE(self, register): + """Read a signed 16-bit value from the specified register, in little + endian byte order.""" + return self.readS16(register, little_endian=True) + + def readS16BE(self, register): + """Read a signed 16-bit value from the specified register, in big + endian byte order.""" + return self.readS16(register, little_endian=False) + + +class BME280: + def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None, + **kwargs): + # Check that mode is valid. + if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4, + BME280_OSAMPLE_8, BME280_OSAMPLE_16]: + raise ValueError( + 'Unexpected mode value {0}. Set mode to one of ' + 'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or ' + 'BME280_ULTRAHIGHRES'.format(mode)) + self._mode = mode + # Create I2C device. + if i2c is None: + raise ValueError('An I2C object is required.') + self._device = Device(address, i2c) + # Load calibration values. + self._load_calibration() + self._device.write8(BME280_REGISTER_CONTROL, 0x3F) + self.t_fine = 0 + + def _load_calibration(self): + + self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1) + self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2) + self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3) + + self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1) + self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2) + self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3) + self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4) + self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5) + self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6) + self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7) + self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8) + self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9) + + self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1) + self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2) + self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3) + self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7) + + h4 = self._device.readS8(BME280_REGISTER_DIG_H4) + h4 = (h4 << 24) >> 20 + self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F) + + h5 = self._device.readS8(BME280_REGISTER_DIG_H6) + h5 = (h5 << 24) >> 20 + self.dig_H5 = h5 | ( + self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F) + + def read_raw_temp(self): + """Reads the raw (uncompensated) temperature from the sensor.""" + meas = self._mode + self._device.write8(BME280_REGISTER_CONTROL_HUM, meas) + meas = self._mode << 5 | self._mode << 2 | 1 + self._device.write8(BME280_REGISTER_CONTROL, meas) + sleep_time = 1250 + 2300 * (1 << self._mode) + + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + sleep_time = sleep_time + 2300 * (1 << self._mode) + 575 + time.sleep_us(sleep_time) # Wait the required time + msb = self._device.readU8(BME280_REGISTER_TEMP_DATA) + lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_pressure(self): + """Reads the raw (uncompensated) pressure level from the sensor.""" + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA) + lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1) + xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2) + raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4 + return raw + + def read_raw_humidity(self): + """Assumes that the temperature has already been read """ + """i.e. that enough delay has been provided""" + msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA) + lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1) + raw = (msb << 8) | lsb + return raw + + def read_temperature(self): + """Get the compensated temperature in 0.01 of a degree celsius.""" + adc = self.read_raw_temp() + var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11) + var2 = (( + (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) * + self.dig_T3) >> 14 + self.t_fine = var1 + var2 + return (self.t_fine * 5 + 128) >> 8 + + def read_pressure(self): + """Gets the compensated pressure in Pascals.""" + adc = self.read_raw_pressure() + var1 = self.t_fine - 128000 + var2 = var1 * var1 * self.dig_P6 + var2 = var2 + ((var1 * self.dig_P5) << 17) + var2 = var2 + (self.dig_P4 << 35) + var1 = (((var1 * var1 * self.dig_P3) >> 8) + + ((var1 * self.dig_P2) >> 12)) + var1 = (((1 << 47) + var1) * self.dig_P1) >> 33 + if var1 == 0: + return 0 + p = 1048576 - adc + p = (((p << 31) - var2) * 3125) // var1 + var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25 + var2 = (self.dig_P8 * p) >> 19 + return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4) + + def read_humidity(self): + adc = self.read_raw_humidity() + # print 'Raw humidity = {0:d}'.format (adc) + h = self.t_fine - 76800 + h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) + + 16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h * + self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) * + self.dig_H2 + 8192) >> 14)) + h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4) + h = 0 if h < 0 else h + h = 419430400 if h > 419430400 else h + return h >> 12 + + @property + def temperature(self): + "Return the temperature in degrees." + t = self.read_temperature() + ti = t // 100 + td = t - ti * 100 + #return "{}.{:02d}C".format(ti, td) + return "{}.{:02d}".format(ti, td) + + @property + def pressure(self): + "Return the temperature in hPa." + p = self.read_pressure() // 256 + pi = p // 100 + pd = p - pi * 100 + #return "{}.{:02d}hPa".format(pi, pd) + return "{}.{:02d}".format(pi, pd) + + @property + def humidity(self): + "Return the humidity in percent." + h = self.read_humidity() + hi = h // 1024 + hd = h * 100 // 1024 - hi * 100 + #return "{}.{:02d}%".format(hi, hd) + return "{}.{:02d}".format(hi, hd) diff --git a/main.py b/main.py new file mode 100644 index 0000000..ae8ed04 --- /dev/null +++ b/main.py @@ -0,0 +1,58 @@ +""" +This code is designed to run on an ESP8266 for grabbing temperature data from +ds18x20 sensors via the onewire protocol and sending them via serial to the +connected host. It should be trivial to port it to different platforms. +It also grabs data from one BME280 via I2C. + +The Protocol is dead simple: + +ONEWIRE-ID1 TEMPERATURE1 (in degree C) +ONEWIRE-ID2 TEMPERATURE2 (in degree C) +BME280-TEMPERATURE TEMPERATUREBME (in degree C) +BME280-PRESSURE PRESSUREBME (in hPa) +BME280-HUMIDITY HUMIDITYBME (in %rH) +... +<Empty Line> + +When a sensor has problems reading, it sends as temperature 9001. + +New sensors are only detected upon powerup, so you have to reboot in order to +extend the sensor network. + +The sensors have a parasitic-power-mode which is NOT TO BE USED here. +Please connect all three pins, and multiplex as you please. +""" + +import machine +import time +import onewire, ds18x20 +import ubinascii +import BME280 + +di = machine.Pin(13) +ds = ds18x20.DS18X20(onewire.OneWire(di)) + +scl = machine.Pin(5) +sda = machine.Pin(4) + +i2c = machine.I2C(scl=scl, sda=sda, freq=10000) +bme = BME280.BME280(i2c=i2c) + +# scan for onewire sensors +roms = ds.scan() + +while 1: + time.sleep_ms(240) + ds.convert_temp() + time.sleep_ms(750) + for rom in roms: + print(ubinascii.hexlify(rom).decode('utf-8'), end=' ') + try: + print(ds.read_temp(rom)) + except: + print(9001) + print("BME280_temperature",bme.temperature) + print("BME280_pressure", bme.pressure) + print("BME280_humidity", bme.humidity) + print() + diff --git a/tempermon.ini b/tempermon.ini index 6e98a68..244725f 100644 --- a/tempermon.ini +++ b/tempermon.ini @@ -1,6 +1,5 @@ [serial] -#port=/dev/ttyUSB0 -port=/tmp/temperature_pts +port=/dev/ttyUSB0 baudrate=115200 timeout=100 @@ -12,23 +11,42 @@ interval=1 [mail] from=Temperman <root@temperator.stusta.de> -to=jw@stusta.de,markus.hefele@stusta.de -to_urgent=jw@stusta.de,markus.hefele@stusta.de +to=jw@stusta.de,markus.hefele@stusta.de,suhu@stusta.de +to_urgent=vorstand@stustanet.de min_delay_between_messages=3600 [warning] -floor_sensors=Test -ceiling_sensors=Test2 +floor_sensors=temp0 +ceiling_sensors=temp1 min_ceiling_warning=35 floor_ceiling_diff=15 ceiling_warning_level=40 +[28aaabf6531401a0] +name=temp0 +calibration=0 -[testsensor] -name=Test -calibration=5 +[28aab9ee5314017b] +name=temp1 +calibration=0 -[sensortest] -name=Test2 -calibration=5 +[28aa82ec53140188] +name=temp2 +calibration=0 + +[28aaec455414017b] +name=temp3 +calibration=0 + +[BME280_temperature] +name=temp4 +calibration=0 + +[BME280_pressure] +name=pressure +calibration=0 + +[BME280_humidity] +name=humidity +calibration=0 diff --git a/tempermonitor.py b/tempermonitor.py index 3c26e86..0e24c8c 100755 --- a/tempermonitor.py +++ b/tempermonitor.py @@ -174,7 +174,7 @@ class TempMonitor: config=self._configname, owid=owid, temp=temp) - elif temp > 1000 or temp < -1000: + elif temp > 9000 or temp < -1000: sensor.valid = False # if the sensor is giving bullshit data - notify the operators await self.call_plugin("err_problem_sensor", -- GitLab