External sensors (#153)
* proof of principle, a DS18B20 is read and its value is transferred via MQTT, also discovery message works * added names for ext sensors * bugfix for names: there was a problem with the topics & added SHT21 support * catch exceptions if a problem during readout occurs, shrink comments in config-file * include ext_sensors in README file * work in progress * bugfix, also for ID of ds18b20 sensor * Revert "bugfix, also for ID of ds18b20 sensor" This reverts commit 1353b840b0b6142670ec59438db4b33fab764b8e. file was intended for another branch * Revert "work in progress" This reverts commit b3e884f79f5356b79f64931cb4d45a150bfce2c7. these changes were intended for another branch * bugfix for ID of ds18b20 sensor
This commit is contained in:
0
ext_sensor_lib/__init__.py
Normal file
0
ext_sensor_lib/__init__.py
Normal file
61
ext_sensor_lib/ds18b20.py
Executable file
61
ext_sensor_lib/ds18b20.py
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/python3
|
||||
import time
|
||||
import os
|
||||
"""
|
||||
This reads the temp value of a DS18B20 sensor
|
||||
"""
|
||||
|
||||
|
||||
def sensor_DS18B20(sensor_id, verbose=False):
|
||||
"""
|
||||
:param sensor_id: Name of the sensor id, can be found in /sys/bus/w1/devices
|
||||
:type sensor_id: string
|
||||
"""
|
||||
# read the file
|
||||
try:
|
||||
file = open('/sys/bus/w1/devices/28-%s/w1_slave' % sensor_id)
|
||||
filecontent = file.read()
|
||||
file.close()
|
||||
# read temperature value and convert it
|
||||
stringvalue = filecontent.split("\n")[1].split(" ")[9]
|
||||
temperature = float(stringvalue[2:]) / 1000
|
||||
except IOError as e:
|
||||
# if an error occurs, we return -300
|
||||
temperature = float(-300)
|
||||
|
||||
# format the temperature
|
||||
temp = '%6.1f' % temperature
|
||||
temp = round(temperature, 1)
|
||||
# if we set the verbose, we print the current temperature
|
||||
if verbose:
|
||||
print ("1-wire %s: temp=%.1f" % (sensor_id, temp))
|
||||
return temp
|
||||
|
||||
|
||||
def get_available_sensors():
|
||||
"""Returns all available sensors"""
|
||||
# https://github.com/rgbkrk/ds18b20/blob/master/ds18b20/__init__.py
|
||||
sensors = []
|
||||
for sensor in os.listdir("/sys/bus/w1/devices"):
|
||||
if sensor.startswith("28-"):
|
||||
# we put all sensor ids into the list (except the "28-" part)
|
||||
sensors.append(sensor[3:])
|
||||
return sensors
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# call the function to get all sensor_ids as a list
|
||||
sensors_ids = get_available_sensors()
|
||||
while True:
|
||||
for sensor_id in sensors_ids:
|
||||
sensor_DS18B20(sensor_id=sensor_id, verbose=True)
|
||||
#sensor_DS18B20("0014531448ff")
|
||||
time.sleep(2)
|
||||
except IOError as e:
|
||||
errno, strerror = e.args
|
||||
print("I/O error({0}): {1}".format(errno,strerror))
|
||||
except KeyboardInterrupt:
|
||||
print ("keyboard interrupt")
|
||||
|
||||
|
||||
174
ext_sensor_lib/sht21.py
Normal file
174
ext_sensor_lib/sht21.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
"""
|
||||
from https://raw.githubusercontent.com/kif/sht21_python/refs/heads/python3/sht21.py
|
||||
from https://github.com/kif/sht21_python/tree/python3
|
||||
forked from jaques/sht21_python
|
||||
with
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Richard Jaques
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
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"""
|
||||
self.i2c = open('/dev/i2c-%s' % device_number, 'rb+', 0)
|
||||
fcntl.ioctl(self.i2c, self.I2C_SLAVE, 0x40)
|
||||
self.i2c.write(bytes([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(bytes([self._TRIGGER_TEMPERATURE_NO_HOLD]))
|
||||
time.sleep(self._TEMPERATURE_WAIT_TIME)
|
||||
data = self.i2c.read(3)
|
||||
if self._calculate_checksum(data, 2) == 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(bytes([self._TRIGGER_HUMIDITY_NO_HOLD]))
|
||||
time.sleep(self._HUMIDITY_WAIT_TIME)
|
||||
data = self.i2c.read(3)
|
||||
if self._calculate_checksum(data, 2) == 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 ^= 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 = (data[0] << 8) + 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 = (data[0] << 8) + 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(1) as sht21:
|
||||
print("Temperature: %s" % sht21.read_temperature())
|
||||
print("Humidity: %s" % sht21.read_humidity())
|
||||
except IOError as e:
|
||||
print(type(e), e)
|
||||
print("Error creating connection to i2c. This must be run as root")
|
||||
|
||||
Reference in New Issue
Block a user