|
| 1 | +# ------------------------------------------------------------ |
| 2 | +# Copyright (c) All rights reserved |
| 3 | +# SiLab, Institute of Physics, University of Bonn |
| 4 | +# ------------------------------------------------------------ |
| 5 | +# |
| 6 | +import logging |
| 7 | +import struct |
| 8 | + |
| 9 | +from basil.HL.SensirionBridgeDevice import SensirionBridgeI2CDevice |
| 10 | + |
| 11 | +logger = logging.getLogger(__name__) |
| 12 | +logging.getLogger("sensirion_shdlc_driver.connection").setLevel(logging.ERROR) |
| 13 | + |
| 14 | + |
| 15 | +class sensirionSHT45(SensirionBridgeI2CDevice): |
| 16 | + """ |
| 17 | + Driver for the Sensirion SHT45 temperature and humidity sensor (SHT4x family). |
| 18 | +
|
| 19 | + Unlike the SHT3x/SHT85, the SHT4x only supports single-shot measurements |
| 20 | + (no periodic/ART mode and no status register). |
| 21 | +
|
| 22 | + Measurements can be performed in three repeatability modes: |
| 23 | + low (default) (0.2°C, 0.21%RH), medium (0.12°C, 0.21%RH), high (0.1°C, 0.08%RH) |
| 24 | + with respective drawbacks in readout speed and power consumption. |
| 25 | +
|
| 26 | + The dew point can be estimated using humidity and temperature. |
| 27 | +
|
| 28 | + The sensor has an integrated heater (20/110/200 mW) that can be activated |
| 29 | + for 0.1s or 1s. A heater command always performs a high-repeatability |
| 30 | + measurement right before the heater is switched off again and returns |
| 31 | + that reading; there is no separate heater on/off command. |
| 32 | + """ |
| 33 | + |
| 34 | + def __init__(self, intf, conf): |
| 35 | + super(sensirionSHT45, self).__init__(intf, conf) |
| 36 | + |
| 37 | + def init(self): |
| 38 | + super(sensirionSHT45, self).init(0x44) |
| 39 | + |
| 40 | + try: |
| 41 | + import crcmod |
| 42 | + |
| 43 | + self.crc_func = crcmod.mkCrcFun(0x131, initCrc=0xFF, rev=False, xorOut=0x00) |
| 44 | + except ImportError: |
| 45 | + logger.warning("You have to install the package 'crcmod'! Transmission errors will not be caught.") |
| 46 | + self.crc_func = lambda x: 0 |
| 47 | + |
| 48 | + self.repeatability = self._init.get("repeatability", "low") |
| 49 | + |
| 50 | + def _read(self, command, read_n_words=0, timeout_us=20e3, n_tries=10): |
| 51 | + for _ in range(n_tries): |
| 52 | + rx_data = super(sensirionSHT45, self)._read(command, read_n_words * 3, timeout_us) |
| 53 | + data = [0] * read_n_words |
| 54 | + for i in range(read_n_words): |
| 55 | + if self.crc_func(rx_data[i * 3 : (i + 1) * 3]): |
| 56 | + break |
| 57 | + else: |
| 58 | + data[i] = struct.unpack(">H", rx_data[i * 3 : i * 3 + 2])[0] |
| 59 | + else: |
| 60 | + return data |
| 61 | + continue |
| 62 | + raise Exception("Checksum repeatedly ({0}x) wrong".format(n_tries), rx_data) |
| 63 | + |
| 64 | + def _write(self, command): |
| 65 | + super(sensirionSHT45, self)._write(command) |
| 66 | + |
| 67 | + def _perform_measurement(self, read_n_words=2): |
| 68 | + # command byte, max measurement duration in us (see datasheet Table 4) |
| 69 | + params = { |
| 70 | + "low": ([0xE0], 1700), |
| 71 | + "medium": ([0xF6], 4500), |
| 72 | + "high": ([0xFD], 8200), |
| 73 | + }[self.repeatability] |
| 74 | + return self._read(params[0], read_n_words=read_n_words, timeout_us=params[1]) |
| 75 | + |
| 76 | + def get_temperature(self): |
| 77 | + data = self._perform_measurement(read_n_words=2) |
| 78 | + return self._to_temperature(data) |
| 79 | + |
| 80 | + def get_humidity(self): |
| 81 | + data = self._perform_measurement(read_n_words=2) |
| 82 | + return self._to_humidity(data) |
| 83 | + |
| 84 | + def get_temperature_and_humidity(self): |
| 85 | + data = self._perform_measurement(read_n_words=2) |
| 86 | + return self._to_temperature(data), self._to_humidity(data) |
| 87 | + |
| 88 | + def get_dew_point(self): |
| 89 | + T, RH = self.get_temperature_and_humidity() |
| 90 | + return self.to_dew_point(T, RH) |
| 91 | + |
| 92 | + def measure_with_heater(self, power="high", duration="short"): |
| 93 | + """ |
| 94 | + Fire the integrated heater and return the T/RH reading taken with |
| 95 | + high repeatability just before the heater switches off again. |
| 96 | +
|
| 97 | + power: "low" (20mW), "medium" (110mW) or "high" (200mW) |
| 98 | + duration: "short" (0.1s) or "long" (1s) |
| 99 | +
|
| 100 | + Note: the heater is rated for a maximum on-time of 1s and should not |
| 101 | + be fired back-to-back without a cool-down; see the datasheet. |
| 102 | + """ |
| 103 | + cmd, timeout_us = { |
| 104 | + "high": {"long": ([0x39], 1.1e6), "short": ([0x32], 0.11e6)}, |
| 105 | + "medium": {"long": ([0x2F], 1.1e6), "short": ([0x24], 0.11e6)}, |
| 106 | + "low": {"long": ([0x1E], 1.1e6), "short": ([0x15], 0.11e6)}, |
| 107 | + }[power][duration] |
| 108 | + |
| 109 | + data = self._read(cmd, read_n_words=2, timeout_us=timeout_us) |
| 110 | + return self._to_temperature(data), self._to_humidity(data) |
| 111 | + |
| 112 | + def get_serial_number(self): |
| 113 | + data = self._read([0x89], read_n_words=2, timeout_us=1e3) |
| 114 | + return (data[0] << 16) | data[1] |
| 115 | + |
| 116 | + # This soft-reset re-initializes all registers. |
| 117 | + # A general call reset (not implemented here) also resets the sensor. |
| 118 | + def reset_sensor(self): |
| 119 | + self._write([0x94]) |
| 120 | + |
| 121 | + def _to_temperature(self, data): |
| 122 | + return -45 + 175 * (float(data[0]) / (2**16 - 1)) |
| 123 | + |
| 124 | + def _to_humidity(self, data): |
| 125 | + RH = -6 + 125 * (float(data[1]) / (2**16 - 1)) |
| 126 | + return min(max(RH, 0), 100) |
| 127 | + |
| 128 | + def to_dew_point(self, T, RH): |
| 129 | + """returns the dew point using an approximation |
| 130 | + approximation specified by Sensirion: |
| 131 | + http://irtfweb.ifa.hawaii.edu/~tcs3/tcs3/Misc/Dewpoint_Calculation_Humidity_Sensor_E.pdf |
| 132 | + """ |
| 133 | + import numpy as np |
| 134 | + |
| 135 | + if RH == 0: |
| 136 | + RH = self._to_humidity((0, 1)) # lowest non-zero rel. humidity |
| 137 | + H = (np.log10(RH) - 2) / 0.4343 + (17.62 * T) / (243.12 + T) |
| 138 | + Dp = 243.12 * H / (17.62 - H) |
| 139 | + return Dp |
0 commit comments