Skip to content

Commit 7265dec

Browse files
Include support for periodic data acquisition mode and tuning the device's behaviour.
1 parent 40d5609 commit 7265dec

File tree

1 file changed

+270
-41
lines changed

1 file changed

+270
-41
lines changed

adafruit_sht31d.py

+270-41
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# The MIT License (MIT)
22
#
33
# Copyright (c) 2017 Jerry Needell
4+
# Copyright (c) 2019 Llewelyn Trahaearn
45
#
56
# Permission is hereby granted, free of charge, to any person obtaining a copy
67
# of this software and associated documentation files (the "Software"), to deal
@@ -25,7 +26,7 @@
2526
2627
This is a CircuitPython driver for the SHT31-D temperature and humidity sensor.
2728
28-
* Author(s): Jerry Needell
29+
* Author(s): Jerry Needell, Llewelyn Trahaearn
2930
3031
Implementation Notes
3132
--------------------
@@ -57,18 +58,67 @@
5758
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SHT31D.git"
5859

5960

60-
SHT31_DEFAULT_ADDR = const(0x44)
61-
SHT31_MEAS_HIGHREP_STRETCH = const(0x2C06)
62-
SHT31_MEAS_MEDREP_STRETCH = const(0x2C0D)
63-
SHT31_MEAS_LOWREP_STRETCH = const(0x2C10)
64-
SHT31_MEAS_HIGHREP = const(0x2400)
65-
SHT31_MEAS_MEDREP = const(0x240B)
66-
SHT31_MEAS_LOWREP = const(0x2416)
67-
SHT31_READSTATUS = const(0xF32D)
68-
SHT31_CLEARSTATUS = const(0x3041)
69-
SHT31_SOFTRESET = const(0x30A2)
70-
SHT31_HEATEREN = const(0x306D)
71-
SHT31_HEATERDIS = const(0x3066)
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_PDA_FETCH = const(0xE000)
74+
_SHT31_PDA_BREAK = const(0x3093)
75+
76+
MODE_SSDA = 'Single'
77+
MODE_PDA = 'Periodic'
78+
79+
_SHT31_MODES = (MODE_SSDA, MODE_PDA)
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 = (FREQUENCY_0_5, FREQUENCY_1, FREQUENCY_2, FREQUENCY_4, FREQUENCY_10)
94+
95+
_SSDA_COMMANDS = ((REP_LOW, const(False), const(0x2416)),
96+
(REP_MED, const(False), const(0x240B)),
97+
(REP_HIGH, const(False), const(0x2400)),
98+
(REP_LOW, const(True), const(0x2C10)),
99+
(REP_MED, const(True), const(0x2C0D)),
100+
(REP_HIGH, const(True), const(0x2C06)))
101+
102+
_PDA_COMMANDS = ((True, None, const(0x2B32)),
103+
(REP_LOW, FREQUENCY_0_5, const(0x202F)),
104+
(REP_MED, FREQUENCY_0_5, const(0x2024)),
105+
(REP_HIGH, FREQUENCY_0_5, const(0x2032)),
106+
(REP_LOW, FREQUENCY_1, const(0x212D)),
107+
(REP_MED, FREQUENCY_1, const(0x2126)),
108+
(REP_HIGH, FREQUENCY_1, const(0x2130)),
109+
(REP_LOW, FREQUENCY_2, const(0x222B)),
110+
(REP_MED, FREQUENCY_2, const(0x2220)),
111+
(REP_HIGH, FREQUENCY_2, const(0x2236)),
112+
(REP_LOW, FREQUENCY_4, const(0x2329)),
113+
(REP_MED, FREQUENCY_4, const(0x2322)),
114+
(REP_HIGH, FREQUENCY_4, const(0x2334)),
115+
(REP_LOW, FREQUENCY_10, const(0x272A)),
116+
(REP_MED, FREQUENCY_10, const(0x2721)),
117+
(REP_HIGH, FREQUENCY_10, const(0x2737)))
118+
119+
_DELAY = ((REP_LOW, .0045),
120+
(REP_MED, .0065),
121+
(REP_HIGH, .0155))
72122

73123

74124
def _crc(data):
@@ -83,6 +133,19 @@ def _crc(data):
83133
crc <<= 1
84134
return crc
85135

136+
def _unpack(data):
137+
length = len(data)
138+
crc = [None] * (length//3)
139+
word = [None] * (length//3)
140+
for i in range(length//6):
141+
word[i*2], crc[i*2], word[(i*2)+1], crc[(i*2)+1] = struct.unpack('>HBHB', data[i*6:(i*6)+6])
142+
if crc[i*2] == _crc(data[i*6:(i*6)+2]):
143+
length = (i+1)*6
144+
for i in range(length//3):
145+
if crc[i] != _crc(data[i*3:(i*3)+2]):
146+
raise RuntimeError("CRC mismatch")
147+
return word[:length//3]
148+
86149

87150
class SHT31D:
88151
"""
@@ -91,64 +154,230 @@ class SHT31D:
91154
:param i2c_bus: The `busio.I2C` object to use. This is the only required parameter.
92155
:param int address: (optional) The I2C address of the device.
93156
"""
94-
def __init__(self, i2c_bus, address=SHT31_DEFAULT_ADDR):
157+
def __init__(self, i2c_bus, address=_SHT31_DEFAULT_ADDRESS):
158+
if address not in _SHT31_ADDRESSES:
159+
raise ValueError('Invalid address: 0x%x' % (address))
95160
self.i2c_device = I2CDevice(i2c_bus, address)
96-
self._command(SHT31_SOFTRESET)
97-
time.sleep(.010)
161+
self._mode = MODE_SSDA
162+
self._repeatability = REP_HIGH
163+
self._frequency = FREQUENCY_4
164+
self._clock_stretching = False
165+
self._art = False
166+
self._last_read = 0
167+
self._cached_temperature = None
168+
self._cached_humidity = None
169+
self._reset()
98170

99171
def _command(self, command):
100172
with self.i2c_device as i2c:
101173
i2c.write(struct.pack('>H', command))
102174

175+
def _reset(self):
176+
"""
177+
Soft reset the device
178+
The reset command is preceded by a break command as the
179+
device will not respond to a soft reset when in PDA mode.
180+
"""
181+
self._command(_SHT31_PDA_BREAK)
182+
time.sleep(.001)
183+
self._command(_SHT31_SOFTRESET)
184+
time.sleep(.0015)
185+
186+
def _pda(self):
187+
for command in _PDA_COMMANDS:
188+
if self.art == command[0] or \
189+
(self.repeatability == command[0] and self.frequency == command[1]):
190+
self._command(command[2])
191+
time.sleep(.001)
192+
self._last_read = 0
193+
103194
def _data(self):
104-
data = bytearray(6)
105-
data[0] = 0xff
106-
self._command(SHT31_MEAS_HIGHREP)
107-
time.sleep(.5)
195+
if self.mode == MODE_PDA:
196+
data = bytearray(48)
197+
data[0] = 0xff
198+
self._command(_SHT31_PDA_FETCH)
199+
time.sleep(.001)
200+
elif self.mode == MODE_SSDA:
201+
data = bytearray(6)
202+
data[0] = 0xff
203+
for command in _SSDA_COMMANDS:
204+
if self.repeatability == command[0] and self.clock_stretching == command[1]:
205+
self._command(command[2])
206+
if not self.clock_stretching:
207+
for delay in _DELAY:
208+
if self.repeatability == delay[0]:
209+
time.sleep(delay[1])
210+
else:
211+
time.sleep(.001)
108212
with self.i2c_device as i2c:
109213
i2c.readinto(data)
110-
temperature, tcheck, humidity, hcheck = struct.unpack('>HBHB', data)
111-
if tcheck != _crc(data[:2]):
112-
raise RuntimeError("temperature CRC mismatch")
113-
if hcheck != _crc(data[3:5]):
114-
raise RuntimeError("humidity CRC mismatch")
214+
word = _unpack(data)
215+
length = len(word)
216+
temperature = [None] * (length//2)
217+
humidity = [None] * (length//2)
218+
for i in range(length//2):
219+
temperature[i] = -45 + (175 * (word[i*2] / 65535))
220+
humidity[i] = 100 * (word[(i*2)+1] / 65523)
221+
if (len(temperature) == 1) and (len(humidity) == 1):
222+
return temperature[0], humidity[0]
115223
return temperature, humidity
116224

225+
def _read(self):
226+
if self.mode == MODE_PDA and time.time() > self._last_read+1/self.frequency:
227+
self._cached_temperature, self._cached_humidity = self._data()
228+
self._last_read = time.time()
229+
elif self.mode == MODE_SSDA:
230+
self._cached_temperature, self._cached_humidity = self._data()
231+
return self._cached_temperature, self._cached_humidity
232+
233+
@property
234+
def mode(self):
235+
"""
236+
Operation mode
237+
Allowed values are the constants MODE_*
238+
Return the device to 'Single' mode to stop periodic data acquisition and allow it to sleep.
239+
"""
240+
return self._mode
241+
242+
@mode.setter
243+
def mode(self, value):
244+
if not value in _SHT31_MODES:
245+
raise ValueError("Mode '%s' not supported" % (value))
246+
if self._mode == MODE_PDA and value != MODE_PDA:
247+
self._command(_SHT31_PDA_BREAK)
248+
time.sleep(.001)
249+
if value == MODE_PDA and self._mode != MODE_PDA:
250+
self._pda()
251+
self._mode = value
252+
253+
@property
254+
def repeatability(self):
255+
"""
256+
Repeatability
257+
Allowed values are the constants REP_*
258+
"""
259+
return self._repeatability
260+
261+
@repeatability.setter
262+
def repeatability(self, value):
263+
if not value in _SHT31_REP:
264+
raise ValueError("Repeatability '%s' not supported" % (value))
265+
if self.mode == MODE_PDA and not self._repeatability == value:
266+
self._repeatability = value
267+
self._pda()
268+
else:
269+
self._repeatability = value
270+
271+
@property
272+
def clock_stretching(self):
273+
"""
274+
Control clock stretching.
275+
This feature only affects SSDA mode.
276+
"""
277+
return self._clock_stretching
278+
279+
@clock_stretching.setter
280+
def clock_stretching(self, value):
281+
self._clock_stretching = bool(value)
282+
283+
@property
284+
def art(self):
285+
"""
286+
Control accelerated response time
287+
This feature only affects PDA mode
288+
"""
289+
return self._art
290+
291+
@art.setter
292+
def art(self, value):
293+
if value:
294+
self.frequency = FREQUENCY_4
295+
if self.mode == MODE_PDA and not self._art == value:
296+
self._art = bool(value)
297+
self._pda()
298+
else:
299+
self._art = bool(value)
300+
301+
@property
302+
def frequency(self):
303+
"""
304+
Periodic data acquisition frequency
305+
Allowed values are the constants FREQUENCY_*
306+
Frequency can not be modified when ART is enabled
307+
"""
308+
return self._frequency
309+
310+
@frequency.setter
311+
def frequency(self, value):
312+
if self.art:
313+
raise RuntimeError("Frequency locked to '4 Hz' when ART enabled")
314+
if not value in _SHT31_FREQUENCIES:
315+
raise ValueError("Data acquisition frequency '%s Hz' not supported" % (value))
316+
if self.mode == MODE_PDA and not self._frequency == value:
317+
self._frequency = value
318+
self._pda()
319+
else:
320+
self._frequency = value
321+
117322
@property
118323
def temperature(self):
119-
"""The measured temperature in degrees celsius."""
120-
raw_temperature, _ = self._data()
121-
return -45 + (175 * (raw_temperature / 65535))
324+
"""
325+
The measured temperature in degrees celsius.
326+
SSDA mode reads and returns the current temperature as a float.
327+
PDA mode returns the most recent readings available from the sensor's cache
328+
in a FIFO list of seven floats. This list is backfilled with with the
329+
sensor's maximum output of 130.0 when the sensor is read before the
330+
cache is full.
331+
"""
332+
temperature, _ = self._read()
333+
return temperature
122334

123335
@property
124336
def relative_humidity(self):
125-
"""The measured relative humidity in percent."""
126-
_, raw_humidity = self._data()
127-
return 100 * (raw_humidity / 65523)
128-
129-
def reset(self):
130-
"""Execute a Soft RESET of the sensor."""
131-
self._command(SHT31_SOFTRESET)
132-
time.sleep(.010)
337+
"""
338+
The measured relative humidity in percent.
339+
SSDA mode reads and returns the current humidity as a float.
340+
PDA mode returns the most recent readings available from the sensor's cache
341+
in a FIFO list of seven floats. This list is backfilled with with the
342+
sensor's maximum output of 100.01831417975366 when the sensor is read
343+
before the cache is full.
344+
"""
345+
_, humidity = self._read()
346+
return humidity
133347

134348
@property
135349
def heater(self):
136-
"""Control the sensor internal heater."""
350+
"""Control device's internal heater."""
137351
return (self.status & 0x2000) != 0
138352

139353
@heater.setter
140354
def heater(self, value=False):
141355
if value:
142-
self._command(SHT31_HEATEREN)
356+
self._command(_SHT31_HEATER_ENABLE)
357+
time.sleep(.001)
143358
else:
144-
self._command(SHT31_HEATERDIS)
359+
self._command(_SHT31_HEATER_DISABLE)
360+
time.sleep(.001)
145361

146362
@property
147363
def status(self):
148-
"""The Sensor status."""
364+
"""Device status."""
149365
data = bytearray(2)
150-
self._command(SHT31_READSTATUS)
366+
self._command(_SHT31_READSTATUS)
367+
time.sleep(.001)
151368
with self.i2c_device as i2c:
152369
i2c.readinto(data)
153370
status = data[0] << 8 | data[1]
154371
return status
372+
373+
@property
374+
def serial_number(self):
375+
"""Device serial number."""
376+
data = bytearray(6)
377+
data[0] = 0xff
378+
self._command(_SHT31_READSERIALNBR)
379+
time.sleep(.001)
380+
with self.i2c_device as i2c:
381+
i2c.readinto(data)
382+
word = _unpack(data)
383+
return (word[0] << 16) | word[1]

0 commit comments

Comments
 (0)