/ adafruit_sht31d.py
adafruit_sht31d.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Jerry Needell 4 # Copyright (c) 2019 Llewelyn Trahaearn 5 # 6 # Permission is hereby granted, free of charge, to any person obtaining a copy 7 # of this software and associated documentation files (the "Software"), to deal 8 # in the Software without restriction, including without limitation the rights 9 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 # copies of the Software, and to permit persons to whom the Software is 11 # furnished to do so, subject to the following conditions: 12 # 13 # The above copyright notice and this permission notice shall be included in 14 # all copies or substantial portions of the Software. 15 # 16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 # THE SOFTWARE. 23 """ 24 `adafruit_sht31d` 25 ==================================================== 26 27 This is a CircuitPython driver for the SHT31-D temperature and humidity sensor. 28 29 * Author(s): Jerry Needell, Llewelyn Trahaearn 30 31 Implementation Notes 32 -------------------- 33 34 **Hardware:** 35 36 * Adafruit `Sensiron SHT31-D Temperature & Humidity Sensor Breakout 37 <https://www.adafruit.com/product/2857>`_ (Product ID: 2857) 38 39 **Software and Dependencies:** 40 41 * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: 42 https://github.com/adafruit/circuitpython/releases 43 * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice 44 """ 45 46 # imports 47 try: 48 import struct 49 except ImportError: 50 import ustruct as struct 51 52 import time 53 54 from micropython import const 55 from adafruit_bus_device.i2c_device import I2CDevice 56 57 __version__ = "0.0.0-auto.0" 58 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SHT31D.git" 59 60 61 _SHT31_DEFAULT_ADDRESS = const(0x44) 62 _SHT31_SECONDARY_ADDRESS = const(0x45) 63 64 _SHT31_ADDRESSES = (_SHT31_DEFAULT_ADDRESS, _SHT31_SECONDARY_ADDRESS) 65 66 _SHT31_READSERIALNBR = const(0x3780) 67 _SHT31_READSTATUS = const(0xF32D) 68 _SHT31_CLEARSTATUS = const(0x3041) 69 _SHT31_HEATER_ENABLE = const(0x306D) 70 _SHT31_HEATER_DISABLE = const(0x3066) 71 _SHT31_SOFTRESET = const(0x30A2) 72 _SHT31_NOSLEEP = const(0x303E) 73 _SHT31_PERIODIC_FETCH = const(0xE000) 74 _SHT31_PERIODIC_BREAK = const(0x3093) 75 76 MODE_SINGLE = "Single" 77 MODE_PERIODIC = "Periodic" 78 79 _SHT31_MODES = (MODE_SINGLE, MODE_PERIODIC) 80 81 REP_HIGH = "High" 82 REP_MED = "Medium" 83 REP_LOW = "Low" 84 85 _SHT31_REP = (REP_HIGH, REP_MED, REP_LOW) 86 87 FREQUENCY_0_5 = 0.5 88 FREQUENCY_1 = 1 89 FREQUENCY_2 = 2 90 FREQUENCY_4 = 4 91 FREQUENCY_10 = 10 92 93 _SHT31_FREQUENCIES = ( 94 FREQUENCY_0_5, 95 FREQUENCY_1, 96 FREQUENCY_2, 97 FREQUENCY_4, 98 FREQUENCY_10, 99 ) 100 101 _SINGLE_COMMANDS = ( 102 (REP_LOW, const(False), const(0x2416)), 103 (REP_MED, const(False), const(0x240B)), 104 (REP_HIGH, const(False), const(0x2400)), 105 (REP_LOW, const(True), const(0x2C10)), 106 (REP_MED, const(True), const(0x2C0D)), 107 (REP_HIGH, const(True), const(0x2C06)), 108 ) 109 110 _PERIODIC_COMMANDS = ( 111 (True, None, const(0x2B32)), 112 (REP_LOW, FREQUENCY_0_5, const(0x202F)), 113 (REP_MED, FREQUENCY_0_5, const(0x2024)), 114 (REP_HIGH, FREQUENCY_0_5, const(0x2032)), 115 (REP_LOW, FREQUENCY_1, const(0x212D)), 116 (REP_MED, FREQUENCY_1, const(0x2126)), 117 (REP_HIGH, FREQUENCY_1, const(0x2130)), 118 (REP_LOW, FREQUENCY_2, const(0x222B)), 119 (REP_MED, FREQUENCY_2, const(0x2220)), 120 (REP_HIGH, FREQUENCY_2, const(0x2236)), 121 (REP_LOW, FREQUENCY_4, const(0x2329)), 122 (REP_MED, FREQUENCY_4, const(0x2322)), 123 (REP_HIGH, FREQUENCY_4, const(0x2334)), 124 (REP_LOW, FREQUENCY_10, const(0x272A)), 125 (REP_MED, FREQUENCY_10, const(0x2721)), 126 (REP_HIGH, FREQUENCY_10, const(0x2737)), 127 ) 128 129 _DELAY = ((REP_LOW, 0.0045), (REP_MED, 0.0065), (REP_HIGH, 0.0155)) 130 131 132 def _crc(data): 133 crc = 0xFF 134 for byte in data: 135 crc ^= byte 136 for _ in range(8): 137 if crc & 0x80: 138 crc <<= 1 139 crc ^= 0x131 140 else: 141 crc <<= 1 142 return crc 143 144 145 def _unpack(data): 146 length = len(data) 147 crc = [None] * (length // 3) 148 word = [None] * (length // 3) 149 for i in range(length // 6): 150 word[i * 2], crc[i * 2], word[(i * 2) + 1], crc[(i * 2) + 1] = struct.unpack( 151 ">HBHB", data[i * 6 : (i * 6) + 6] 152 ) 153 if crc[i * 2] == _crc(data[i * 6 : (i * 6) + 2]): 154 length = (i + 1) * 6 155 for i in range(length // 3): 156 if crc[i] != _crc(data[i * 3 : (i * 3) + 2]): 157 raise RuntimeError("CRC mismatch") 158 return word[: length // 3] 159 160 161 class SHT31D: 162 """ 163 A driver for the SHT31-D temperature and humidity sensor. 164 165 :param i2c_bus: The `busio.I2C` object to use. This is the only required parameter. 166 :param int address: (optional) The I2C address of the device. 167 """ 168 169 def __init__(self, i2c_bus, address=_SHT31_DEFAULT_ADDRESS): 170 if address not in _SHT31_ADDRESSES: 171 raise ValueError("Invalid address: 0x%x" % (address)) 172 self.i2c_device = I2CDevice(i2c_bus, address) 173 self._mode = MODE_SINGLE 174 self._repeatability = REP_HIGH 175 self._frequency = FREQUENCY_4 176 self._clock_stretching = False 177 self._art = False 178 self._last_read = 0 179 self._cached_temperature = None 180 self._cached_humidity = None 181 self._reset() 182 183 def _command(self, command): 184 with self.i2c_device as i2c: 185 i2c.write(struct.pack(">H", command)) 186 187 def _reset(self): 188 """ 189 Soft reset the device 190 The reset command is preceded by a break command as the 191 device will not respond to a soft reset when in 'Periodic' mode. 192 """ 193 self._command(_SHT31_PERIODIC_BREAK) 194 time.sleep(0.001) 195 self._command(_SHT31_SOFTRESET) 196 time.sleep(0.0015) 197 198 def _periodic(self): 199 for command in _PERIODIC_COMMANDS: 200 if self.art == command[0] or ( 201 self.repeatability == command[0] and self.frequency == command[1] 202 ): 203 self._command(command[2]) 204 time.sleep(0.001) 205 self._last_read = 0 206 207 def _data(self): 208 if self.mode == MODE_PERIODIC: 209 data = bytearray(48) 210 data[0] = 0xFF 211 self._command(_SHT31_PERIODIC_FETCH) 212 time.sleep(0.001) 213 elif self.mode == MODE_SINGLE: 214 data = bytearray(6) 215 data[0] = 0xFF 216 for command in _SINGLE_COMMANDS: 217 if ( 218 self.repeatability == command[0] 219 and self.clock_stretching == command[1] 220 ): 221 self._command(command[2]) 222 if not self.clock_stretching: 223 for delay in _DELAY: 224 if self.repeatability == delay[0]: 225 time.sleep(delay[1]) 226 else: 227 time.sleep(0.001) 228 with self.i2c_device as i2c: 229 i2c.readinto(data) 230 word = _unpack(data) 231 length = len(word) 232 temperature = [None] * (length // 2) 233 humidity = [None] * (length // 2) 234 for i in range(length // 2): 235 temperature[i] = -45 + (175 * (word[i * 2] / 65535)) 236 humidity[i] = 100 * (word[(i * 2) + 1] / 65535) 237 if (len(temperature) == 1) and (len(humidity) == 1): 238 return temperature[0], humidity[0] 239 return temperature, humidity 240 241 def _read(self): 242 if ( 243 self.mode == MODE_PERIODIC 244 and time.time() > self._last_read + 1 / self.frequency 245 ): 246 self._cached_temperature, self._cached_humidity = self._data() 247 self._last_read = time.time() 248 elif self.mode == MODE_SINGLE: 249 self._cached_temperature, self._cached_humidity = self._data() 250 return self._cached_temperature, self._cached_humidity 251 252 @property 253 def mode(self): 254 """ 255 Operation mode 256 Allowed values are the constants MODE_* 257 Return the device to 'Single' mode to stop periodic data acquisition and allow it to sleep. 258 """ 259 return self._mode 260 261 @mode.setter 262 def mode(self, value): 263 if not value in _SHT31_MODES: 264 raise ValueError("Mode '%s' not supported" % (value)) 265 if self._mode == MODE_PERIODIC and value != MODE_PERIODIC: 266 self._command(_SHT31_PERIODIC_BREAK) 267 time.sleep(0.001) 268 if value == MODE_PERIODIC and self._mode != MODE_PERIODIC: 269 self._periodic() 270 self._mode = value 271 272 @property 273 def repeatability(self): 274 """ 275 Repeatability 276 Allowed values are the constants REP_* 277 """ 278 return self._repeatability 279 280 @repeatability.setter 281 def repeatability(self, value): 282 if not value in _SHT31_REP: 283 raise ValueError("Repeatability '%s' not supported" % (value)) 284 if self.mode == MODE_PERIODIC and not self._repeatability == value: 285 self._repeatability = value 286 self._periodic() 287 else: 288 self._repeatability = value 289 290 @property 291 def clock_stretching(self): 292 """ 293 Control clock stretching. 294 This feature only affects 'Single' mode. 295 """ 296 return self._clock_stretching 297 298 @clock_stretching.setter 299 def clock_stretching(self, value): 300 self._clock_stretching = bool(value) 301 302 @property 303 def art(self): 304 """ 305 Control accelerated response time 306 This feature only affects 'Periodic' mode. 307 """ 308 return self._art 309 310 @art.setter 311 def art(self, value): 312 if value: 313 self.frequency = FREQUENCY_4 314 if self.mode == MODE_PERIODIC and not self._art == value: 315 self._art = bool(value) 316 self._periodic() 317 else: 318 self._art = bool(value) 319 320 @property 321 def frequency(self): 322 """ 323 Periodic data acquisition frequency 324 Allowed values are the constants FREQUENCY_* 325 Frequency can not be modified when ART is enabled 326 """ 327 return self._frequency 328 329 @frequency.setter 330 def frequency(self, value): 331 if self.art: 332 raise RuntimeError("Frequency locked to '4 Hz' when ART enabled") 333 if not value in _SHT31_FREQUENCIES: 334 raise ValueError( 335 "Data acquisition frequency '%s Hz' not supported" % (value) 336 ) 337 if self.mode == MODE_PERIODIC and not self._frequency == value: 338 self._frequency = value 339 self._periodic() 340 else: 341 self._frequency = value 342 343 @property 344 def temperature(self): 345 """ 346 The measured temperature in degrees celsius. 347 'Single' mode reads and returns the current temperature as a float. 348 'Periodic' mode returns the most recent readings available from the sensor's cache 349 in a FILO list of eight floats. This list is backfilled with with the 350 sensor's maximum output of 130.0 when the sensor is read before the 351 cache is full. 352 """ 353 temperature, _ = self._read() 354 return temperature 355 356 @property 357 def relative_humidity(self): 358 """ 359 The measured relative humidity in percent. 360 'Single' mode reads and returns the current humidity as a float. 361 'Periodic' mode returns the most recent readings available from the sensor's cache 362 in a FILO list of eight floats. This list is backfilled with with the 363 sensor's maximum output of 100.01831417975366 when the sensor is read 364 before the cache is full. 365 """ 366 _, humidity = self._read() 367 return humidity 368 369 @property 370 def heater(self): 371 """Control device's internal heater.""" 372 return (self.status & 0x2000) != 0 373 374 @heater.setter 375 def heater(self, value=False): 376 if value: 377 self._command(_SHT31_HEATER_ENABLE) 378 time.sleep(0.001) 379 else: 380 self._command(_SHT31_HEATER_DISABLE) 381 time.sleep(0.001) 382 383 @property 384 def status(self): 385 """Device status.""" 386 data = bytearray(2) 387 self._command(_SHT31_READSTATUS) 388 time.sleep(0.001) 389 with self.i2c_device as i2c: 390 i2c.readinto(data) 391 status = data[0] << 8 | data[1] 392 return status 393 394 @property 395 def serial_number(self): 396 """Device serial number.""" 397 data = bytearray(6) 398 data[0] = 0xFF 399 self._command(_SHT31_READSERIALNBR) 400 time.sleep(0.001) 401 with self.i2c_device as i2c: 402 i2c.readinto(data) 403 word = _unpack(data) 404 return (word[0] << 16) | word[1]