#!/usr/bin/python
|
#-*- coding: utf-8 -*-
|
|
import fcntl
|
import time
|
import unittest
|
|
|
class SHT21:
|
"""Class to read temperature and humidity from SHT21, much of class was
|
derived from:
|
http://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/Humidity/Sensirion_Humidity_SHT21_Datasheet_V3.pdf
|
and Martin Steppuhn's code from http://www.emsystech.de/raspi-sht21"""
|
|
# control constants
|
_SOFTRESET = 0xFE
|
_I2C_ADDRESS = 0x40
|
_TRIGGER_TEMPERATURE_NO_HOLD = 0xF3
|
_TRIGGER_HUMIDITY_NO_HOLD = 0xF5
|
_STATUS_BITS_MASK = 0xFFFC
|
|
# From: /linux/i2c-dev.h
|
I2C_SLAVE = 0x0703
|
I2C_SLAVE_FORCE = 0x0706
|
|
# datasheet (v4), page 9, table 7, thanks to Martin Milata
|
# for suggesting the use of these better values
|
# code copied from https://github.com/mmilata/growd
|
_TEMPERATURE_WAIT_TIME = 0.086 # (datasheet: typ=66, max=85)
|
_HUMIDITY_WAIT_TIME = 0.030 # (datasheet: typ=22, max=29)
|
|
def __init__(self, device_number=0):
|
"""Opens the i2c device (assuming that the kernel modules have been
|
loaded). Note that this has only been tested on first revision
|
raspberry pi where the device_number = 0, but it should work
|
where device_number=1"""
|
device_number = 1
|
self.i2c = open('/dev/i2c-%s' % device_number, 'r+', 0)
|
fcntl.ioctl(self.i2c, self.I2C_SLAVE, 0x40)
|
self.i2c.write(chr(self._SOFTRESET))
|
time.sleep(0.050)
|
|
def read_temperature(self):
|
"""Reads the temperature from the sensor. Not that this call blocks
|
for ~86ms to allow the sensor to return the data"""
|
self.i2c.write(chr(self._TRIGGER_TEMPERATURE_NO_HOLD))
|
time.sleep(self._TEMPERATURE_WAIT_TIME)
|
data = self.i2c.read(3)
|
if self._calculate_checksum(data, 2) == ord(data[2]):
|
return self._get_temperature_from_buffer(data)
|
|
def read_humidity(self):
|
"""Reads the humidity from the sensor. Not that this call blocks
|
for ~30ms to allow the sensor to return the data"""
|
self.i2c.write(chr(self._TRIGGER_HUMIDITY_NO_HOLD))
|
time.sleep(self._HUMIDITY_WAIT_TIME)
|
data = self.i2c.read(3)
|
if self._calculate_checksum(data, 2) == ord(data[2]):
|
return self._get_humidity_from_buffer(data)
|
|
def close(self):
|
"""Closes the i2c connection"""
|
self.i2c.close()
|
|
def __enter__(self):
|
"""used to enable python's with statement support"""
|
return self
|
|
def __exit__(self, type, value, traceback):
|
"""with support"""
|
self.close()
|
|
@staticmethod
|
def _calculate_checksum(data, number_of_bytes):
|
"""5.7 CRC Checksum using the polynomial given in the datasheet"""
|
# CRC
|
POLYNOMIAL = 0x131 # //P(x)=x^8+x^5+x^4+1 = 100110001
|
crc = 0
|
# calculates 8-Bit checksum with given polynomial
|
for byteCtr in range(number_of_bytes):
|
crc ^= (ord(data[byteCtr]))
|
for bit in range(8, 0, -1):
|
if crc & 0x80:
|
crc = (crc << 1) ^ POLYNOMIAL
|
else:
|
crc = (crc << 1)
|
return crc
|
|
@staticmethod
|
def _get_temperature_from_buffer(data):
|
"""This function reads the first two bytes of data and
|
returns the temperature in C by using the following function:
|
T = -46.85 + (175.72 * (ST/2^16))
|
where ST is the value from the sensor
|
"""
|
unadjusted = (ord(data[0]) << 8) + ord(data[1])
|
unadjusted &= SHT21._STATUS_BITS_MASK # zero the status bits
|
unadjusted *= 175.72
|
unadjusted /= 1 << 16 # divide by 2^16
|
unadjusted -= 46.85
|
return unadjusted
|
|
@staticmethod
|
def _get_humidity_from_buffer(data):
|
"""This function reads the first two bytes of data and returns
|
the relative humidity in percent by using the following function:
|
RH = -6 + (125 * (SRH / 2 ^16))
|
where SRH is the value read from the sensor
|
"""
|
unadjusted = (ord(data[0]) << 8) + ord(data[1])
|
unadjusted &= SHT21._STATUS_BITS_MASK # zero the status bits
|
unadjusted *= 125.0
|
unadjusted /= 1 << 16 # divide by 2^16
|
unadjusted -= 6
|
return unadjusted
|
|
|
class SHT21Test(unittest.TestCase):
|
"""simple sanity test. Run from the command line with
|
python -m unittest sht21 to check they are still good"""
|
|
def test_temperature(self):
|
"""Unit test to check the checksum method"""
|
calc_temp = SHT21._get_temperature_from_buffer([chr(99), chr(172)])
|
self.failUnless(abs(calc_temp - 21.5653979492) < 0.1)
|
|
def test_humidity(self):
|
"""Unit test to check the humidity computation using example
|
from the v4 datasheet"""
|
calc_temp = SHT21._get_humidity_from_buffer([chr(99), chr(82)])
|
self.failUnless(abs(calc_temp - 42.4924) < 0.001)
|
|
def test_checksum(self):
|
"""Unit test to check the checksum method. Uses values read"""
|
self.failUnless(SHT21._calculate_checksum([chr(99), chr(172)], 2) == 249)
|
self.failUnless(SHT21._calculate_checksum([chr(99), chr(160)], 2) == 132)
|
|
if __name__ == "__main__":
|
try:
|
with SHT21(0) as sht21:
|
print "Temperature: %s" % sht21.read_temperature()
|
print "Humidity: %s" % sht21.read_humidity()
|
except IOError, e:
|
print e
|
print "Error creating connection to i2c. This must be run as root"
|