-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathadafruit_sht4x.py
244 lines (181 loc) · 7.4 KB
/
adafruit_sht4x.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2021 ladyada for Adafruit
#
# SPDX-License-Identifier: MIT
"""
`adafruit_sht4x`
================================================================================
Python library for Sensirion SHT4x temperature and humidity sensors
* Author(s): ladyada
Implementation Notes
--------------------
**Hardware:**
* `Adafruit SHT40 Temperature & Humidity Sensor
<https://www.adafruit.com/product/4885>`_ (Product ID: 4885)
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://circuitpython.org/downloads
* Adafruit's Bus Device library:
https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""
import time
import struct
from adafruit_bus_device import i2c_device
from micropython import const
try:
from typing import Tuple
from busio import I2C
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SHT4x.git"
_SHT4X_DEFAULT_ADDR = const(0x44) # SHT4X I2C Address
_SHT4X_READSERIAL = const(0x89) # Read Out of Serial Register
_SHT4X_SOFTRESET = const(0x94) # Soft Reset
class CV:
"""struct helper"""
@classmethod
def add_values(cls, value_tuples: Tuple[str, int, str, float]) -> None:
"""Add CV values to the class"""
cls.string = {}
cls.delay = {}
for value_tuple in value_tuples:
name, value, string, delay = value_tuple
setattr(cls, name, value)
cls.string[value] = string
cls.delay[value] = delay
@classmethod
def is_valid(cls, value: int) -> bool:
"""Validate that a given value is a member"""
return value in cls.string
class Mode(CV):
"""Options for ``power_mode``"""
pass # pylint: disable=unnecessary-pass
Mode.add_values(
(
("NOHEAT_HIGHPRECISION", 0xFD, "No heater, high precision", 0.01),
("NOHEAT_MEDPRECISION", 0xF6, "No heater, med precision", 0.005),
("NOHEAT_LOWPRECISION", 0xE0, "No heater, low precision", 0.002),
("HIGHHEAT_1S", 0x39, "High heat, 1 second", 1.1),
("HIGHHEAT_100MS", 0x32, "High heat, 0.1 second", 0.11),
("MEDHEAT_1S", 0x2F, "Med heat, 1 second", 1.1),
("MEDHEAT_100MS", 0x24, "Med heat, 0.1 second", 0.11),
("LOWHEAT_1S", 0x1E, "Low heat, 1 second", 1.1),
("LOWHEAT_100MS", 0x15, "Low heat, 0.1 second", 0.11),
)
)
class SHT4x:
"""
A driver for the SHT4x temperature and humidity sensor.
:param ~busio.I2C i2c_bus: The I2C bus the SHT4x is connected to.
:param int address: The I2C device address. Default is :const:`0x44`
**Quickstart: Importing and using the SHT4x temperature and humidity sensor**
Here is an example of using the :class:`SHT4x`.
First you will need to import the libraries to use the sensor
.. code-block:: python
import board
import adafruit_sht4x
Once this is done you can define your `board.I2C` object and define your sensor object
.. code-block:: python
i2c = board.I2C() # uses board.SCL and board.SDA
sht = adafruit_sht4x.SHT4x(i2c)
You can now make some initial settings on the sensor
.. code-block:: python
sht.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION
Now you have access to the temperature and humidity using the :attr:`measurements`.
It will return a tuple with the :attr:`temperature` and :attr:`relative_humidity`
measurements
.. code-block:: python
temperature, relative_humidity = sht.measurements
"""
def __init__(self, i2c_bus: I2C, address: int = _SHT4X_DEFAULT_ADDR) -> None:
self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
self._buffer = bytearray(6)
self.reset()
self._mode = Mode.NOHEAT_HIGHPRECISION # pylint: disable=no-member
@property
def serial_number(self) -> int:
"""The unique 32-bit serial number"""
self._buffer[0] = _SHT4X_READSERIAL
with self.i2c_device as i2c:
i2c.write(self._buffer, end=1)
time.sleep(0.01)
i2c.readinto(self._buffer)
ser1 = self._buffer[0:2]
ser1_crc = self._buffer[2]
ser2 = self._buffer[3:5]
ser2_crc = self._buffer[5]
# check CRC of bytes
if ser1_crc != self._crc8(ser1) or ser2_crc != self._crc8(ser2):
raise RuntimeError("Invalid CRC calculated")
serial = (ser1[0] << 24) + (ser1[1] << 16) + (ser2[0] << 8) + ser2[1]
return serial
def reset(self) -> None:
"""Perform a soft reset of the sensor, resetting all settings to their power-on defaults"""
self._buffer[0] = _SHT4X_SOFTRESET
with self.i2c_device as i2c:
i2c.write(self._buffer, end=1)
time.sleep(0.001)
@property
def mode(self) -> int:
"""The current sensor reading mode (heater and precision)"""
return self._mode
@mode.setter
def mode(self, new_mode: int) -> None:
if not Mode.is_valid(new_mode):
raise AttributeError("mode must be a Mode")
self._mode = new_mode
@property
def relative_humidity(self) -> float:
"""The current relative humidity in % rH. This is a value from 0-100%."""
return self.measurements[1]
@property
def temperature(self) -> float:
"""The current temperature in degrees Celsius"""
return self.measurements[0]
@property
def measurements(self) -> Tuple[float, float]:
"""both `temperature` and `relative_humidity`, read simultaneously"""
temperature = None
humidity = None
command = self._mode
with self.i2c_device as i2c:
self._buffer[0] = command
i2c.write(self._buffer, end=1)
time.sleep(Mode.delay[self._mode])
i2c.readinto(self._buffer)
# separate the read data
temp_data = self._buffer[0:2]
temp_crc = self._buffer[2]
humidity_data = self._buffer[3:5]
humidity_crc = self._buffer[5]
# check CRC of bytes
if temp_crc != self._crc8(temp_data) or humidity_crc != self._crc8(
humidity_data
):
raise RuntimeError("Invalid CRC calculated")
# decode data into human values:
# convert bytes into 16-bit signed integer
# convert the LSB value to a human value according to the datasheet
temperature = struct.unpack_from(">H", temp_data)[0]
temperature = -45.0 + 175.0 * temperature / 65535.0
# repeat above steps for humidity data
humidity = struct.unpack_from(">H", humidity_data)[0]
humidity = -6.0 + 125.0 * humidity / 65535.0
humidity = max(min(humidity, 100), 0)
return (temperature, humidity)
## CRC-8 formula from page 14 of SHTC3 datasheet
# https://media.digikey.com/pdf/Data%20Sheets/Sensirion%20PDFs/HT_DS_SHTC3_D1.pdf
# Test data [0xBE, 0xEF] should yield 0x92
@staticmethod
def _crc8(buffer) -> int:
"""verify the crc8 checksum"""
crc = 0xFF
for byte in buffer:
crc ^= byte
for _ in range(8):
if crc & 0x80:
crc = (crc << 1) ^ 0x31
else:
crc = crc << 1
return crc & 0xFF # return the bottom 8 bits