├── sht ├── MANIFEST.in ├── .gitignore ├── sht_sensor ├── __main__.py ├── __init__.py ├── gpio.py └── sensor.py ├── COPYING ├── setup.py └── README.rst /sht: -------------------------------------------------------------------------------- 1 | sht_sensor/sensor.py -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYING README.rst 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.egg-info 2 | /build 3 | /dist 4 | /README.txt 5 | *.pyc 6 | *.pyo 7 | -------------------------------------------------------------------------------- /sht_sensor/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function 3 | 4 | from sht_sensor.sensor import main 5 | import os, sys 6 | 7 | if __name__ == '__main__': sys.exit(main()) 8 | -------------------------------------------------------------------------------- /sht_sensor/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function 3 | 4 | from sht_sensor.sensor import Sht, ShtVDDLevel 5 | from sht_sensor.sensor import ShtFailure, ShtCommFailure, ShtCRCCheckError 6 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2012 Mike Kazantsev 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages 5 | import os, sys 6 | 7 | # Error-handling here is to allow package to be built w/o README included 8 | try: 9 | readme = open(os.path.join( 10 | os.path.dirname(__file__), 'README.rst' )).read() 11 | except IOError: readme = '' 12 | 13 | setup( 14 | 15 | name = 'sht-sensor', 16 | version = '18.3.2', 17 | author = 'Mike Kazantsev', 18 | author_email = 'mk.fraggod@gmail.com', 19 | license = 'WTFPL', 20 | keywords = ( 'sht sensor sensirion ic' 21 | ' sht1x sht7x sht10 sht11 sht15 sht71 sht75' 22 | ' T temperature RH relative humidity dew point celsius' 23 | ' environment conditioning measurement' 24 | ' gpio hardware driver serial 2-wire crc8' ), 25 | 26 | url = 'http://github.com/mk-fg/sht-sensor', 27 | 28 | description = 'Driver and command-line tool' 29 | ' for Sensirion SHT1x and SHT7x sensors connected to GPIO pins.', 30 | long_description = readme, 31 | 32 | classifiers = [ 33 | 'Development Status :: 4 - Beta', 34 | 'Environment :: Console', 35 | 'Intended Audience :: Developers', 36 | 'Intended Audience :: End Users/Desktop', 37 | 'Intended Audience :: Manufacturing', 38 | 'Intended Audience :: System Administrators', 39 | 'License :: Public Domain', 40 | 'Operating System :: POSIX :: Linux', 41 | 'Programming Language :: Python', 42 | 'Programming Language :: Python :: 2.7', 43 | 'Programming Language :: Python :: 2 :: Only', 44 | 'Topic :: Home Automation', 45 | 'Topic :: Scientific/Engineering :: Atmospheric Science', 46 | 'Topic :: System :: Hardware :: Hardware Drivers', 47 | 'Topic :: System :: Monitoring', 48 | 'Topic :: System :: Operating System Kernels :: Linux', 49 | 'Topic :: Utilities' ], 50 | 51 | packages=find_packages(), 52 | include_package_data=True, 53 | package_data={'': ['README.txt']}, 54 | exclude_package_data={'': ['README.*']}, 55 | 56 | entry_points = { 57 | 'console_scripts': ['sht = sht_sensor.sensor:main'] }) 58 | -------------------------------------------------------------------------------- /sht_sensor/gpio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function 3 | 4 | import itertools as it, operator as op, functools as ft 5 | from os.path import join, exists 6 | import os, sys, logging, glob, time, select 7 | 8 | 9 | class OnDemandLogger(object): 10 | log = None 11 | def __getattr__(self, k): 12 | if not self.log: self.log = logging.getLogger('sht.gpio') 13 | return getattr(self.log, k) 14 | log = OnDemandLogger() 15 | 16 | 17 | path_gpio = '/sys/class/gpio' 18 | 19 | class GPIOAccessFailure(Exception): pass 20 | 21 | def gpio_access_wrap(func, checks=12, timeout=1.0): 22 | for n in xrange(checks, -1, -1): 23 | try: return func() 24 | except (IOError, OSError): pass 25 | if checks <= 0: break 26 | if n: time.sleep(timeout / checks) 27 | else: 28 | raise GPIOAccessFailure(func, timeout) 29 | # log.warn('gpio access failed (func: %s, timeout: %s)', func, timeout) 30 | 31 | def get_pin_path(n, sub=None, _cache=dict()): 32 | n = int(n) 33 | if n not in _cache: 34 | for try_export in [True, False]: 35 | try: 36 | path = join(path_gpio, 'gpio{}'.format(n)) 37 | if not exists(path): path, = glob.glob(path + '_*') 38 | except: 39 | if not try_export: 40 | raise OSError('Failed to find sysfs control path for pin: {}'.format(n)) 41 | else: break 42 | log.debug('Exporting pin: %s', n) 43 | with open(join(path_gpio, 'export'), 'wb', 0) as dst: 44 | gpio_access_wrap(ft.partial(dst.write, bytes(n))) 45 | _cache[n] = path 46 | else: path = _cache[n] 47 | return path if not sub else os.path.join(path, sub) 48 | 49 | def get_pin_value(n, k='value'): 50 | with gpio_access_wrap( 51 | ft.partial(open, get_pin_path(n, k), 'rb', 0) ) as src: 52 | val = src.read().strip() 53 | if k == 'value': 54 | try: val = int(val) 55 | except ValueError as err: 56 | log.warn('Failed to read/decode pin (n: %s) value %r: %s', n, val, err) 57 | val = None 58 | return val 59 | 60 | def set_pin_value(n, v, k='value', force=False, _pin_state=dict()): 61 | if k == 'value' and isinstance(v, bool): v = int(v) 62 | if not force and _pin_state.get(n) == v: return 63 | if _pin_state.get(n) == v: return 64 | # log.debug('Setting parameter of pin-%s: %s = %r ', n, k, v) 65 | with gpio_access_wrap( 66 | ft.partial(open, get_pin_path(n, k), 'wb', 0) ) as dst: 67 | gpio_access_wrap(ft.partial(dst.write, bytes(v))) 68 | _pin_state[n] = v 69 | 70 | 71 | class PollTimeout(Exception): pass 72 | 73 | def poll_pin(n, timeout=1.0, edge='both', _poller_cache=dict()): 74 | if edge: set_pin_value(n, k='edge', v=edge) 75 | try: 76 | if n not in _poller_cache: 77 | _poller_cache[n] = select.poll() 78 | poller = _poller_cache[n] 79 | with gpio_access_wrap( 80 | ft.partial(open, get_pin_path(n, 'value'), 'rb', 0) ) as src: 81 | poller.register(src.fileno(), select.POLLPRI | select.POLLERR) 82 | res = poller.poll(timeout * 1000) 83 | if not res or res[0][1] & select.POLLERR == select.POLLERR: 84 | raise PollTimeout(n, timeout, edge, res) 85 | return get_pin_value(n) 86 | finally: 87 | if edge: set_pin_value(n, k='edge', v='none') 88 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | **DEPRECATION NOTICE**: 2 | this is an obsolete Python-2.7-only version of the module, 3 | kept here for historical purposes only, for any new projects 4 | use `kizniche/sht-sensor `_ instead. 5 | 6 | 7 | sht-sensor 8 | ========== 9 | 10 | Python driver and command-line tool for Sensirion SHT1x and SHT7x sensors 11 | connected to GPIO pins. 12 | 13 | 14 | .. contents:: 15 | :backlinks: none 16 | 17 | 18 | 19 | Description 20 | ----------- 21 | 22 | This is a pure-python module that only requires /sys/class/gpio interface, 23 | provided by the Linux kernel and should work on any device that has it 24 | (including RPi, Beaglebone boards, Cubieboard, etc - any linux). 25 | 26 | Its main purpose is reading temperature (in degrees Celsius) and humidity (%RH) 27 | values from these devices, checking CRC8 checksums for received data to make 28 | sure it was not corrupted in transfer. 29 | 30 | SHT1x (SHT10, SHT11, SHT15) and SHT7x (SHT71, SHT75) are fairly popular and 31 | accurate capacitive/band-gap relative humidity and temperature sensor IC's, with 32 | digital output via custom 2-wire serial interface. 33 | 34 | SHT1x differs from SHT7x in packaging, with SHT1x being surface-mountable one 35 | and latter having pluggable FR4 package. 36 | 37 | Sensors include additional functionality available via the status register (like 38 | VDD level check, enabling internal heating element, resolution, OTP reload, etc) 39 | which may or may not also be implemented here, see "Stuff that is not 40 | implemented" section at the end. 41 | 42 | 43 | 44 | Usage 45 | ----- 46 | 47 | Module can be imported from the python code or used via included command-line 48 | tool, which should be installed along with the module (or can be used via ./sht 49 | symlink in the repo root without installation). 50 | 51 | See "Installation" section below on how to install the module. 52 | 53 | GPIO numbers (to which SCK and DATA sensor pins are connected) must be specified 54 | either on command-line (for cli tool) or on class init (when using as a python 55 | module). 56 | 57 | Example, for SCK connected to gpio 21 and DATA to gpio 17:: 58 | 59 | % sht -v -trd 21 17 60 | temperature: 25.07 61 | rh: 26.502119362 62 | dew_point: 4.4847911176 63 | 64 | GPIO "pin" numbers here (and in python module) use whichever numbering scheme 65 | kernel has in /sys/class/gpio, which is likely be totally different from the 66 | actual (physical) pin numbers on the board headers, and can potentially change 67 | between board revisions (e.g. RPi rev 1.0 -> 2.0) or even kernel updates, so be 68 | sure to check up-to-date docs on these. 69 | 70 | For both the tool and module, also be sure to check/specify correct voltage 71 | (default is '3.5V', value is from the datasheet table, not free-form!) that the 72 | sensor's VDD pin is connected to:: 73 | 74 | % sht --voltage=5V --temperature 21 17 75 | 25.08 76 | 77 | This voltage value is used to pick coefficient (as presented in datasheet table) 78 | for temperature calculation, and incorrect setting here should result in less 79 | precise output values (these values add/subtract 0.1th of degree, while sensor's 80 | typical precision is +/- 0.4 degree, so mostly irrelevant). 81 | 82 | If you're using non-SHT1x/SHT7x, but a similar sensor (e.g. some later model), 83 | it might be a good idea to look at the Sht class in the code and make sure all 84 | coefficients (taken from SHT1x/SHT7x datasheet - google it, sensirion.com URL 85 | for it changed like 4 times in 2y) there match your model's datasheet exactly. 86 | 87 | See `sht --help` output for the full list of options for command-line tool. 88 | 89 | Example usage from python code:: 90 | 91 | from sht_sensor import Sht 92 | sht = Sht(21, 17) 93 | print 'Temperature', sht.read_t() 94 | print 'Relative Humidity', sht.read_rh() 95 | 96 | Voltage value (see note on it above) on sensor's VDD pin can be specified for 97 | calculations exactly as it is presented in datasheet table (either as a string 98 | or ShtVDDLevel enum value), if it's not module-default '3.5V', for example: 99 | ``sht = Sht(21, 17, voltage=ShtVDDLevel.vdd_5v)``. 100 | 101 | It might be preferrable to use ``ShtVDDLevel.vdd_5v`` value over simple '5V' 102 | string as it should catch typos and similar bugs in some cases, but makes no 103 | difference otherwise. 104 | 105 | Some calculations (e.g. for RH) use other sensor-provided values, so it's 106 | possible to pass these to the corresponding read_* methods, to avoid heating-up 107 | sensor with unnecessary extra measurements:: 108 | 109 | t = sht.read_t() 110 | rh = sht.read_rh(t) 111 | dew_point = sht.read_dew_point(t, rh) 112 | 113 | If included ``sht_sensor.gpio`` module (accessing /sys/class/gpio directly) 114 | should not be used (e.g. on non-linux or with different gpio interface), its 115 | interface ("get_pin_value" and "set_pin_value" attrs/functions) can be 116 | re-implemented and passed as a "gpio" keyword argument on Sht class init. 117 | 118 | ShtComms class is an implementation of 2-wire protocol that sensor uses and 119 | probably should not be used directly. 120 | All the coefficients, calculations and such high-level logic is defined in Sht 121 | class, extending ShtComms. 122 | 123 | Installed python module can also be used from cli via the usual ``python -m 124 | sht_sensor ...`` convention. 125 | 126 | 127 | 128 | Installation 129 | ------------ 130 | 131 | **DEPRECATION NOTICE**: 132 | this is an obsolete Python-2.7-only version of the module, 133 | kept here for historical purposes only, for any new projects 134 | use `kizniche/sht-sensor `_ 135 | instead, see installation instructions there. 136 | 137 | "sht-sensor" on PyPI is packaged from there too. 138 | 139 | 140 | Misc features / quirks 141 | ---------------------- 142 | 143 | Description of minor things that might be useful in some less common cases. 144 | 145 | 146 | ShtCommFailure: Command ACK failed on step-1 147 | ```````````````````````````````````````````` 148 | 149 | Very common error indicating that there's no response from the sensor at all. 150 | 151 | Basically, command gets sent on a wire and at the very first step where there 152 | should be response (acknowledgement) from the sensor, there is none. 153 | 154 | This would happen if specified pins are not connected to anything for example, 155 | which is the most likely issue here - probably worth double-checking 156 | GPIO-line/pin numbering scheme (usually GPIO numbers are NOT the same as 157 | physical pin numbers, and their wiring may vary between board revisions) and 158 | whether `controlling specified pins via /sys/class/gpio`_ can be measured - 159 | e.g. lights up the LED connected to the pin/gnd or shows up on the multimeter 160 | display. 161 | 162 | For example, to control voltage on GPIO line number 17 (again, note that it can 163 | be connected to any physical pin number, check device docs):: 164 | 165 | # cd /sys/class/gpio 166 | # echo 17 > export 167 | # echo high > gpio17/direction 168 | # echo low > gpio17/direction 169 | 170 | Another simple thing to check is whether used sensor package needs a pull-up 171 | resistor, and whether that is connected properly. 172 | 173 | Might also be some issue with the sensor of course, but that should be extremely 174 | unlikely compared to aforementioned trivial issues. 175 | 176 | .. _controlling specified pins via /sys/class/gpio: https://www.kernel.org/doc/Documentation/gpio/sysfs.txt 177 | 178 | 179 | Max bit-banging frequency control 180 | ````````````````````````````````` 181 | 182 | Max frequency value Can be passed either on command-line with --max-freq or when 183 | creating an Sht instance, with separate values for SCK and DATA pins, if necessary. 184 | 185 | Sensor can work just fine with very low frequencies like 20Hz - 186 | e.g. ``sht --max-freq 20 -trv 30 60`` - though that'd obviously slow things down a bit. 187 | 188 | Separate SCK:DATA frequencies (in that order): ``sht --max-freq 100:200 -trv 30 60`` 189 | 190 | Same from python module: ``sht = Sht(21, 17, freq_sck=100, freq_data=200)`` 191 | 192 | 193 | 194 | Stuff that is not implemented 195 | ----------------------------- 196 | 197 | - Everything related to the Status Register. 198 | 199 | In particular, commands like VDD level check, enabling internal heating 200 | element, resolution, OTP reload, etc. 201 | 202 | - Temerature measurements in degrees Fahrenheit. 203 | 204 | These just use different calculation coefficients, which can be overidden in 205 | the Sht class. 206 | Or degrees-Celsius value can easily be converted to F after the fact. 207 | 208 | Metric system is used here, so I just had no need for these. 209 | 210 | - Lower-resolution measurements. 211 | 212 | Sensor supports returning these after changing the value in the Status 213 | Register, so interface to that one should probably be implemented/tested 214 | first. 215 | 216 | - Skipping CRC8 checksum validation. 217 | 218 | Code is there, as ShtComms._skip_crc() method, but no idea why it might be 219 | preferrable to skip this check. 220 | 221 | 222 | 223 | Links 224 | ----- 225 | 226 | Other drivers for these sensors that I know of and might be more suitable for 227 | some particular case: 228 | 229 | * `rpiSht1x `_ (python package) 230 | 231 | Uses RaspberryPi-specific RPi.GPIO module. 232 | 233 | As of 2015-01-12, did not check CRC8 checksums for received data, 234 | used hard-coded 5V temperature conversion coefficients, 235 | returned invalid values even if ack's were incorrect, 236 | looked more like proof-of-concept overall. 237 | 238 | * `Pi-Sht1x `_ (python package) 239 | 240 | Python-3.x module based on rpiSht1x, also uses RPi.GPIO, and rather similar to 241 | this one, but with more extensive functionality - has most/all stuff from "not 242 | implemented" list above, addresses all of the rpiSht1x shortcomings. 243 | 244 | Probably wouldn't have bothered writing this module if it was around at the time. 245 | 246 | * sht1x module in `Linux kernel `_ 247 | 248 | Looks very mature and feature-complete, probably used a lot for various 249 | platforms' hardware monitoring drivers. 250 | 251 | Seem to be only for internal use (i.e. from other kernel modules) at the 252 | moment (3.17.x), but should be possible (and easy) to add Device Tree hooks 253 | there, which would allow to specify how it is connected (gpio pins) via Device 254 | Tree. 255 | 256 | * `SHT1x module for Arduino `_ 257 | 258 | C++ code, rpiSht1x above is based on this one. 259 | -------------------------------------------------------------------------------- /sht_sensor/sensor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function 4 | 5 | import itertools as it, operator as op, functools as ft 6 | import os, sys, logging, time, math 7 | 8 | try: import sht_sensor 9 | except ImportError: 10 | # Make sure tool works from a checkout 11 | if __name__ != '__main__': raise 12 | from os.path import dirname, exists, isdir, join, abspath 13 | pkg_root = abspath(dirname(__file__)) 14 | for pkg_root in pkg_root, dirname(pkg_root): 15 | if isdir(join(pkg_root, 'sht_sensor'))\ 16 | and exists(join(pkg_root, 'setup.py')): 17 | sys.path.insert(0, pkg_root) 18 | try: import sht_sensor 19 | except ImportError: pass 20 | else: break 21 | else: raise ImportError('Failed to find/import "sht_sensor" module') 22 | 23 | 24 | @ft.total_ordering 25 | class EnumValue(object): 26 | 'String-based enum value, comparable to native strings.' 27 | __slots__ = '_t', '_value', '_c_val' 28 | def __init__(self, t, value, c_value=None): 29 | self._t, self._value, self._c_val = t, value, c_value 30 | def __repr__(self): return ''.format(self._t, self._value) 31 | def __eq__(self, val): 32 | print(val, self._value) 33 | if isinstance(val, EnumValue): val = val._value 34 | return self._value == val 35 | def __ne__(self, val): return not (self == val) 36 | def __lt__(self, val): 37 | if isinstance(val, EnumValue): val = val._value 38 | return self._value < val 39 | def __hash__(self): return hash(self._value) 40 | 41 | class Enum(object): 42 | 43 | def __init__(self, name, vals): 44 | self._name, self._values, self._c_vals = name, dict(), dict() 45 | for k, v in vals.items(): 46 | v = EnumValue(name, v) 47 | setattr(self, k, v) 48 | 49 | def __getitem__(self, k, *default): 50 | if isinstance(k, EnumValue): 51 | t, k, v = k._t, k._value, k 52 | if t != self._name: raise KeyError(v) 53 | try: return getattr(self, k, *default) 54 | except AttributeError: raise KeyError(k) 55 | 56 | def _get(self, k, default=None): return self.__getitem__(k, default) 57 | def __contains__(self, k): return self._get(k) is not None 58 | 59 | def __repr__(self): 60 | return ''.format(self._name, ' '.join(sorted(self._values.keys()))) 61 | 62 | 63 | try: import ctypes as ct 64 | except ImportError: mono_time = None 65 | else: 66 | def mono_time(): 67 | if not hasattr(mono_time, 'ts'): 68 | class timespec(ct.Structure): 69 | _fields_ = [('tv_sec', ct.c_long), ('tv_nsec', ct.c_long)] 70 | librt = ct.CDLL('librt.so.1', use_errno=True) 71 | mono_time.get = librt.clock_gettime 72 | mono_time.get.argtypes = [ct.c_int, ct.POINTER(timespec)] 73 | mono_time.ts = timespec 74 | ts = mono_time.ts() 75 | if mono_time.get(4, ct.pointer(ts)) != 0: 76 | err = ct.get_errno() 77 | raise OSError(err, os.strerror(err)) 78 | return ts.tv_sec + ts.tv_nsec * 1e-9 79 | 80 | 81 | ShtVDDLevel = Enum('sht-vdd-level', dict( 82 | vdd_5='5V', vdd_4='4V', vdd_3_5='3.5V', vdd_3='3V', vdd_2_5='2.5V' )) 83 | 84 | 85 | class ShtFailure(Exception): pass 86 | class ShtCommFailure(ShtFailure): pass 87 | class ShtCRCCheckError(ShtFailure): pass 88 | 89 | class ShtComms(object): 90 | 91 | bitbang_delay_min = 0 92 | 93 | def _crc8(self, cmd, v0, v1, _crc_table=[ 94 | 0x00, 0x31, 0x62, 0x53, 0xc4, 0xf5, 0xa6, 0x97, 0xb9, 0x88, 0xdb, 0xea, 95 | 0x7d, 0x4c, 0x1f, 0x2e, 0x43, 0x72, 0x21, 0x10, 0x87, 0xb6, 0xe5, 0xd4, 96 | 0xfa, 0xcb, 0x98, 0xa9, 0x3e, 0x0f, 0x5c, 0x6d, 0x86, 0xb7, 0xe4, 0xd5, 97 | 0x42, 0x73, 0x20, 0x11, 0x3f, 0x0e, 0x5d, 0x6c, 0xfb, 0xca, 0x99, 0xa8, 98 | 0xc5, 0xf4, 0xa7, 0x96, 0x01, 0x30, 0x63, 0x52, 0x7c, 0x4d, 0x1e, 0x2f, 99 | 0xb8, 0x89, 0xda, 0xeb, 0x3d, 0x0c, 0x5f, 0x6e, 0xf9, 0xc8, 0x9b, 0xaa, 100 | 0x84, 0xb5, 0xe6, 0xd7, 0x40, 0x71, 0x22, 0x13, 0x7e, 0x4f, 0x1c, 0x2d, 101 | 0xba, 0x8b, 0xd8, 0xe9, 0xc7, 0xf6, 0xa5, 0x94, 0x03, 0x32, 0x61, 0x50, 102 | 0xbb, 0x8a, 0xd9, 0xe8, 0x7f, 0x4e, 0x1d, 0x2c, 0x02, 0x33, 0x60, 0x51, 103 | 0xc6, 0xf7, 0xa4, 0x95, 0xf8, 0xc9, 0x9a, 0xab, 0x3c, 0x0d, 0x5e, 0x6f, 104 | 0x41, 0x70, 0x23, 0x12, 0x85, 0xb4, 0xe7, 0xd6, 0x7a, 0x4b, 0x18, 0x29, 105 | 0xbe, 0x8f, 0xdc, 0xed, 0xc3, 0xf2, 0xa1, 0x90, 0x07, 0x36, 0x65, 0x54, 106 | 0x39, 0x08, 0x5b, 0x6a, 0xfd, 0xcc, 0x9f, 0xae, 0x80, 0xb1, 0xe2, 0xd3, 107 | 0x44, 0x75, 0x26, 0x17, 0xfc, 0xcd, 0x9e, 0xaf, 0x38, 0x09, 0x5a, 0x6b, 108 | 0x45, 0x74, 0x27, 0x16, 0x81, 0xb0, 0xe3, 0xd2, 0xbf, 0x8e, 0xdd, 0xec, 109 | 0x7b, 0x4a, 0x19, 0x28, 0x06, 0x37, 0x64, 0x55, 0xc2, 0xf3, 0xa0, 0x91, 110 | 0x47, 0x76, 0x25, 0x14, 0x83, 0xb2, 0xe1, 0xd0, 0xfe, 0xcf, 0x9c, 0xad, 111 | 0x3a, 0x0b, 0x58, 0x69, 0x04, 0x35, 0x66, 0x57, 0xc0, 0xf1, 0xa2, 0x93, 112 | 0xbd, 0x8c, 0xdf, 0xee, 0x79, 0x48, 0x1b, 0x2a, 0xc1, 0xf0, 0xa3, 0x92, 113 | 0x05, 0x34, 0x67, 0x56, 0x78, 0x49, 0x1a, 0x2b, 0xbc, 0x8d, 0xde, 0xef, 114 | 0x82, 0xb3, 0xe0, 0xd1, 0x46, 0x77, 0x24, 0x15, 0x3b, 0x0a, 0x59, 0x68, 115 | 0xff, 0xce, 0x9d, 0xac ]): 116 | # See: http://www.sensirion.com/nc/en/products/\ 117 | # humidity-temperature/download-center/?cid=884&did=124&sechash=5c5f91f6 118 | crc = _crc_table[cmd] 119 | crc = _crc_table[crc ^ v0] 120 | crc = _crc_table[crc ^ v1] 121 | # Reverse bit order 122 | # See: http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv 123 | return (crc * 0x0202020202 & 0x010884422010) % 1023 124 | 125 | def __init__(self, pin_sck, pin_data, gpio=None, freq_sck=None, freq_data=None): 126 | if gpio is None: 127 | from sht_sensor import gpio 128 | self.pin_sck, self.pin_data, self.gpio = pin_sck, pin_data, gpio 129 | self.freq_sck, self.freq_data = map(self._freq_iter, [freq_sck, freq_data]) 130 | self.log = logging.getLogger('sht') 131 | self._init() 132 | 133 | def _init(self): 134 | for pin in self.pin_sck, self.pin_data: 135 | # self.gpio.set_pin_value(pin, k='edge', v='none') 136 | self.gpio.set_pin_value(pin, k='direction', v='low') 137 | self.pin_data_mode = 'out' 138 | _cleanup = _init 139 | 140 | 141 | def _freq_iter(self, freq): 142 | if not freq: 143 | while True: 144 | time.sleep(self.bitbang_delay_min) 145 | yield 146 | if not mono_time: 147 | raise ShtFailure( 'Unable to use frequency settings' 148 | ' due to failure to import monotonic timer func via ctypes' ) 149 | delay = 1.0 / freq 150 | while True: 151 | ts_next = mono_time() + delay 152 | yield 153 | delta = ts_next - mono_time() 154 | if delta > 0: time.sleep(delta) 155 | 156 | def _data_mode(self, mode): 157 | if self.pin_data_mode != mode: 158 | self.gpio.set_pin_value(self.pin_data, k='direction', v=mode) 159 | self.pin_data_mode = mode 160 | 161 | def _data_set(self, v): 162 | self._data_mode('out') 163 | self.gpio.set_pin_value(self.pin_data, v, force=True) 164 | next(self.freq_data) 165 | 166 | def _data_get(self): 167 | self._data_mode('in') 168 | return self.gpio.get_pin_value(self.pin_data) 169 | 170 | def _sck_tick(self, v): 171 | self.gpio.set_pin_value(self.pin_sck, v) 172 | next(self.freq_data) 173 | 174 | 175 | def _send(self, cmd): 176 | tick, data = self._sck_tick, self._data_set 177 | 178 | tick(0) 179 | data(1) 180 | tick(1) 181 | data(0) 182 | tick(0) 183 | tick(1) 184 | data(1) 185 | 186 | tick(0) 187 | for n in xrange(8): 188 | data(cmd & (1 << 7 - n)) 189 | tick(1) 190 | tick(0) 191 | 192 | tick(1) 193 | if self._data_get(): 194 | raise ShtCommFailure( 195 | 'Command ACK failed on step-1.' 196 | ' This indicates lack of response from the start,' 197 | ' as if sensor is not connected to specified pins.' 198 | ' Make sure it is connected and pin numbers are' 199 | ' specified correctly, and/or has/needs a pull-up resistor.' 200 | ' See README file for more information.') 201 | tick(0) 202 | if not self._data_get(): 203 | raise ShtCommFailure('Command ACK failed on step-2') 204 | 205 | def _wait(self, timeout=1.0, poll_interval=0.01): 206 | self._data_mode('in') 207 | ## Proper edge-poll seem to always return POLLERR on BBB 208 | ## Also seem unreliable in general case, and not super-necessary here 209 | # ack = self.gpio.poll_pin(self.pin_data, edge='falling') 210 | for i in xrange(int(timeout / poll_interval) + 1): 211 | time.sleep(poll_interval) 212 | ack = self.gpio.get_pin_value(self.pin_data) 213 | if not ack: break 214 | else: 215 | raise ShtCommFailure('Measurement timeout: {:.2f}s'.format(timeout)) 216 | 217 | def _read_bits(self, bits, v=0): 218 | tick = self._sck_tick 219 | self._data_mode('in') 220 | for n in xrange(bits): 221 | tick(1) 222 | v = v * 2 + self._data_get() 223 | tick(0) 224 | return v 225 | 226 | def _read_meas_16bit(self): 227 | # Most significant bits (upper nibble is always zeroes) 228 | v0 = self._read_bits(8) 229 | # Send ack 230 | tick, data = self._sck_tick, self._data_set 231 | data(1) 232 | data(0) 233 | tick(1) 234 | tick(0) 235 | # Least significant bits 236 | v1 = self._read_bits(8) 237 | return v0, v1 238 | 239 | def _get_meas_result(self, cmd): 240 | self._send(cmd) 241 | self._wait() 242 | v0, v1 = self._read_meas_16bit() 243 | # self._skip_crc() 244 | crc0, crc1 = self._crc8(cmd, v0, v1), self._read_crc() 245 | if crc0 != crc1: raise ShtCRCCheckError(crc0, crc1) 246 | return v0 * 256 | v1 247 | 248 | def _read_crc(self): 249 | self._data_set(1) 250 | self._data_set(0) 251 | self._sck_tick(1) 252 | self._sck_tick(0) 253 | return self._read_bits(8) 254 | 255 | def _skip_crc(self): 256 | self._data_set(1) 257 | self._sck_tick(1) 258 | self._sck_tick(0) 259 | 260 | def _conn_reset(self): 261 | self._data_set(1) 262 | for n in xrange(10): 263 | self._sck_tick(1) 264 | self._sck_tick(0) 265 | 266 | 267 | class Sht(ShtComms): 268 | # All table/chapter refs here point to: 269 | # Sensirion_Humidity_and_Temperature_Sensors_SHT7x_Datasheet_V5.pdf 270 | 271 | voltage_default = '3.5V' 272 | 273 | class c: 274 | d1 = { # Table 8, C 275 | '5V': -40.1, 276 | '4V': -39.8, 277 | '3.5V': -39.7, 278 | '3V': -39.6, 279 | '2.5V': -39.4 } 280 | d2 = 0.01 # Table 8, C/14b 281 | c1, c2, c3 = -2.0468, 0.0367, -1.5955e-6 # Table 6, 12b 282 | t1, t2 = 0.01, 0.00008 # Table 7, 12b 283 | tn = dict(water=243.12, ice=272.62) # Table 9 284 | m = dict(water=17.62, ice=22.46) # Table 9 285 | 286 | class cmd: 287 | t = 0b00000011 288 | rh = 0b00000101 289 | 290 | def __init__(self, pin_sck, pin_data, voltage=None, **sht_comms_kws): 291 | '''"voltage" setting is important, 292 | as it influences temperature conversion coefficients!!! 293 | Unless you're using SHT1x/SHT7x, please make 294 | sure all coefficients match your sensor's datasheet.''' 295 | self.voltage = voltage or self.voltage_default 296 | assert self.voltage in self.c.d1, [self.voltage, self.c.d1.keys()] 297 | super(Sht, self).__init__(pin_sck, pin_data, **sht_comms_kws) 298 | 299 | def read_t(self): 300 | t_raw = self._get_meas_result(self.cmd.t) 301 | return t_raw * self.c.d2 + self.c.d1[self.voltage] 302 | 303 | def read_rh(self, t=None): 304 | if t is None: t = self.read_t() 305 | return self._read_rh(t) 306 | 307 | def _read_rh(self, t): 308 | rh_raw = self._get_meas_result(self.cmd.rh) 309 | self._cleanup() 310 | rh_linear = self.c.c1 + self.c.c2 * rh_raw + self.c.c3 * rh_raw**2 # ch 4.1 311 | return (t - 25.0) * (self.c.t1 + self.c.t2 * rh_raw) + rh_linear # ch 4.2 312 | 313 | def read_dew_point(self, t=None, rh=None): 314 | 'With t and rh provided, does not access the hardware.' 315 | if t is None: t, rh = self.read_t(), None 316 | if rh is None: rh = self.read_rh(t) 317 | t_range = 'water' if t >= 0 else 'ice' 318 | tn, m = self.c.tn[t_range], self.c.m[t_range] 319 | return ( # ch 4.4 320 | tn * (math.log(rh / 100.0) + (m * t) / (tn + t)) 321 | / (m - math.log(rh / 100.0) - m * t / (tn + t)) ) 322 | 323 | 324 | def main(args=None): 325 | import argparse 326 | parser = argparse.ArgumentParser(description='Read data from SHTxx-type sensor.') 327 | parser.add_argument('pin_sck', type=int, 328 | help='Number of SCK pin, specified using kernel sysfs numbering scheme.') 329 | parser.add_argument('pin_data', type=int, 330 | help='Number of DATA pin, specified using kernel sysfs numbering scheme.') 331 | 332 | parser.add_argument('--voltage', default='3.5V', metavar='label_from_table', 333 | help='Voltage value (exactly as presented' 334 | ' in datasheet table) that is used. Default: %(default)s') 335 | parser.add_argument('--max-freq', metavar='hz[:hz]', 336 | help='Max frequency for sending SCK/DATA bits, in Hz (times/s).' 337 | ' Two colon-separated values can be specified' 338 | ' to use separate frequency for SCK and DATA pins, in that order.' 339 | ' For example, specifying "20" will introduce ~50ms delays between each DATA/SCK change.' 340 | ' Default is unlimited - i.e. not introduce any additional delays.') 341 | 342 | parser.add_argument('-t', '--temperature', action='store_true', 343 | help='Print temperature value to stdout. Default if no other values were specified.') 344 | parser.add_argument('-r', '--rel-humidity', 345 | action='store_true', help='Print RH value to stdout.') 346 | parser.add_argument('-d', '--dew-point', 347 | action='store_true', help='Print "dew point" value to stdout.') 348 | 349 | parser.add_argument('-v', '--verbose', action='store_true', help='Print labels before values.') 350 | parser.add_argument('--debug', action='store_true', help='Verbose operation mode.') 351 | opts = parser.parse_args(sys.argv[1:] if args is None else args) 352 | 353 | logging.basicConfig(level=logging.DEBUG if opts.debug else logging.WARNING) 354 | if not (opts.temperature or opts.rel_humidity or opts.dew_point): opts.temperature = True 355 | 356 | freq_kws = dict() 357 | if opts.max_freq: 358 | try: 359 | freq_kws.update(zip(['freq_sck', 'freq_data'], map( float, 360 | opts.max_freq.split(':', 1) if ':' in opts.max_freq else [opts.max_freq]*2 ))) 361 | except ValueError as err: 362 | parser.error('Invalid frequency "hz[:hz]" spec {!r}: {}'.format(opts.max_freq, err)) 363 | 364 | sht = Sht(opts.pin_sck, opts.pin_data, **freq_kws) 365 | 366 | p = '{name}: {val}' if opts.verbose else '{val}' 367 | p = lambda name, val, fmt=p: print(fmt.format(name=name, val=val)) 368 | t = rh = dp = None 369 | if opts.temperature: t = sht.read_t() 370 | if opts.rel_humidity: rh = sht.read_rh(t) 371 | if opts.dew_point: dp = sht.read_dew_point(t, rh) 372 | 373 | for name, v in zip(['temperature', 'rh', 'dew_point'], [t, rh, dp]): 374 | if v is not None: p(name, v) 375 | 376 | 377 | if __name__ == '__main__': sys.exit(main()) 378 | --------------------------------------------------------------------------------