1
+ # SPDX-FileCopyrightText: 2017 Mike McWethy for Adafruit Industries
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ """
6
+ :mod:`adafruit_dhtlib`
7
+ ======================
8
+
9
+ CircuitPython support for the DHT11 and DHT22 temperature and humidity devices.
10
+
11
+ * Author(s): Mike McWethy
12
+
13
+ **Hardware:**
14
+
15
+ * Adafruit `DHT22 temperature-humidity sensor + extras
16
+ <https://www.adafruit.com/product/385>`_ (Product ID: 385)
17
+
18
+ * Adafruit `DHT11 basic temperature-humidity sensor + extras
19
+ <https://www.adafruit.com/product/386>`_ (Product ID: 386)
20
+
21
+
22
+ **Software and Dependencies:**
23
+
24
+ * Adafruit CircuitPython firmware for the supported boards:
25
+ https://circuitpython.org/downloads
26
+
27
+ """
28
+
29
+ import array
30
+ import time
31
+ from os import uname
32
+ from digitalio import DigitalInOut , Pull , Direction
33
+
34
+ _USE_PULSEIO = False
35
+ try :
36
+ from pulseio import PulseIn
37
+
38
+ _USE_PULSEIO = True
39
+ except (ImportError , NotImplementedError ):
40
+ pass # This is OK, we'll try to bitbang it!
41
+
42
+ try :
43
+ # Used only for typing
44
+ from typing import Union
45
+ from microcontroller import Pin
46
+ except ImportError :
47
+ pass
48
+
49
+ __version__ = "0.0.0+auto.0"
50
+ __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DHT.git"
51
+
52
+
53
+ class DHTBase :
54
+ """base support for DHT11 and DHT22 devices
55
+
56
+ :param bool dht11: True if device is DHT11, otherwise DHT22.
57
+ :param ~board.Pin pin: digital pin used for communication
58
+ :param int trig_wait: length of time to hold trigger in LOW state (microseconds)
59
+ :param bool use_pulseio: False to force bitbang when pulseio available (only with Blinka)
60
+ """
61
+
62
+ __hiLevel = 51
63
+
64
+ def __init__ (
65
+ self ,
66
+ dht11 : bool ,
67
+ pin : Pin ,
68
+ trig_wait : int ,
69
+ use_pulseio : bool ,
70
+ * ,
71
+ max_pulses : int = 81
72
+ ):
73
+ self ._dht11 = dht11
74
+ self ._pin = pin
75
+ self ._trig_wait = trig_wait
76
+ self ._max_pulses = max_pulses
77
+ self ._last_called = 0
78
+ self ._humidity = None
79
+ self ._temperature = None
80
+ self ._use_pulseio = use_pulseio
81
+ if "Linux" not in uname () and not self ._use_pulseio :
82
+ raise ValueError ("Bitbanging is not supported when using CircuitPython." )
83
+ # We don't use a context because linux-based systems are sluggish
84
+ # and we're better off having a running process
85
+ if self ._use_pulseio :
86
+ self .pulse_in = PulseIn (self ._pin , maxlen = self ._max_pulses , idle_state = True )
87
+ self .pulse_in .pause ()
88
+
89
+ def exit (self ) -> None :
90
+ """Cleans up the PulseIn process. Must be called explicitly"""
91
+ if self ._use_pulseio :
92
+ self .pulse_in .deinit ()
93
+
94
+ def _pulses_to_binary (self , pulses : array .array , start : int , stop : int ) -> int :
95
+ """Takes pulses, a list of transition times, and converts
96
+ them to a 1's or 0's. The pulses array contains the transition times.
97
+ pulses starts with a low transition time followed by a high transistion time.
98
+ then a low followed by a high and so on. The low transition times are
99
+ ignored. Only the high transition times are used. If the high
100
+ transition time is greater than __hiLevel, that counts as a bit=1, if the
101
+ high transition time is less that __hiLevel, that counts as a bit=0.
102
+
103
+ start is the starting index in pulses to start converting
104
+
105
+ stop is the index to convert upto but not including
106
+
107
+ Returns an integer containing the converted 1 and 0 bits
108
+ """
109
+
110
+ binary = 0
111
+ hi_sig = False
112
+ for bit_inx in range (start , stop ):
113
+ if hi_sig :
114
+ bit = 0
115
+ if pulses [bit_inx ] > self .__hiLevel :
116
+ bit = 1
117
+ binary = binary << 1 | bit
118
+
119
+ hi_sig = not hi_sig
120
+
121
+ return binary
122
+
123
+ def _get_pulses_pulseio (self ) -> array .array :
124
+ """_get_pulses implements the communication protocol for
125
+ DHT11 and DHT22 type devices. It sends a start signal
126
+ of a specific length and listens and measures the
127
+ return signal lengths.
128
+
129
+ return pulses (array.array uint16) contains alternating high and low
130
+ transition times starting with a low transition time. Normally
131
+ pulses will have 81 elements for the DHT11/22 type devices.
132
+ """
133
+ pulses = array .array ("H" )
134
+ if self ._use_pulseio :
135
+ # The DHT type device use a specialize 1-wire protocol
136
+ # The microprocessor first sends a LOW signal for a
137
+ # specific length of time. Then the device sends back a
138
+ # series HIGH and LOW signals. The length the HIGH signals
139
+ # represents the device values.
140
+ self .pulse_in .clear ()
141
+ self .pulse_in .resume (self ._trig_wait )
142
+
143
+ # loop until we get the return pulse we need or
144
+ # time out after 1/4 second
145
+ time .sleep (0.25 )
146
+ self .pulse_in .pause ()
147
+ while self .pulse_in :
148
+ pulses .append (self .pulse_in .popleft ())
149
+ return pulses
150
+
151
+ def _get_pulses_bitbang (self ) -> array .array :
152
+ """_get_pulses implements the communication protcol for
153
+ DHT11 and DHT22 type devices. It sends a start signal
154
+ of a specific length and listens and measures the
155
+ return signal lengths.
156
+
157
+ return pulses (array.array uint16) contains alternating high and low
158
+ transition times starting with a low transition time. Normally
159
+ pulses will have 81 elements for the DHT11/22 type devices.
160
+ """
161
+ pulses = array .array ("H" )
162
+ with DigitalInOut (self ._pin ) as dhtpin :
163
+ # we will bitbang if no pulsein capability
164
+ transitions = []
165
+ # Signal by setting pin high, then low, and releasing
166
+ dhtpin .direction = Direction .OUTPUT
167
+ dhtpin .value = True
168
+ time .sleep (0.1 )
169
+ dhtpin .value = False
170
+ # Using the time to pull-down the line according to DHT Model
171
+ time .sleep (self ._trig_wait / 1000000 )
172
+ timestamp = time .monotonic () # take timestamp
173
+ dhtval = True # start with dht pin true because its pulled up
174
+ dhtpin .direction = Direction .INPUT
175
+
176
+ try :
177
+ dhtpin .pull = Pull .UP
178
+ # Catch the NotImplementedError raised because
179
+ # blinka.microcontroller.generic_linux.libgpiod_pin does not support
180
+ # internal pull resistors.
181
+ except NotImplementedError :
182
+ dhtpin .pull = None
183
+
184
+ while time .monotonic () - timestamp < 0.25 :
185
+ if dhtval != dhtpin .value :
186
+ dhtval = not dhtval # we toggled
187
+ transitions .append (time .monotonic ()) # save the timestamp
188
+ # convert transtions to microsecond delta pulses:
189
+ # use last 81 pulses
190
+ transition_start = max (1 , len (transitions ) - self ._max_pulses )
191
+ for i in range (transition_start , len (transitions )):
192
+ pulses_micro_sec = int (1000000 * (transitions [i ] - transitions [i - 1 ]))
193
+ pulses .append (min (pulses_micro_sec , 65535 ))
194
+ return pulses
195
+
196
+ def measure (self ) -> None :
197
+ """measure runs the communications to the DHT11/22 type device.
198
+ if successful, the class properties temperature and humidity will
199
+ return the reading returned from the device.
200
+
201
+ Raises RuntimeError exception for checksum failure and for insufficient
202
+ data returned from the device (try again)
203
+ """
204
+ delay_between_readings = 2 # 2 seconds per read according to datasheet
205
+ # Initiate new reading if this is the first call or if sufficient delay
206
+ # If delay not sufficient - return previous reading.
207
+ # This allows back to back access for temperature and humidity for same reading
208
+ if (
209
+ self ._last_called == 0
210
+ or (time .monotonic () - self ._last_called ) > delay_between_readings
211
+ ):
212
+ self ._last_called = time .monotonic ()
213
+
214
+ new_temperature = 0
215
+ new_humidity = 0
216
+
217
+ if self ._use_pulseio :
218
+ pulses = self ._get_pulses_pulseio ()
219
+ else :
220
+ pulses = self ._get_pulses_bitbang ()
221
+ # print(len(pulses), "pulses:", [x for x in pulses])
222
+
223
+ if len (pulses ) < 10 :
224
+ # Probably a connection issue!
225
+ raise RuntimeError ("DHT sensor not found, check wiring" )
226
+
227
+ if len (pulses ) < 80 :
228
+ # We got *some* data just not 81 bits
229
+ raise RuntimeError ("A full buffer was not returned. Try again." )
230
+
231
+ buf = array .array ("B" )
232
+ for byte_start in range (0 , 80 , 16 ):
233
+ buf .append (self ._pulses_to_binary (pulses , byte_start , byte_start + 16 ))
234
+
235
+ if self ._dht11 :
236
+ # humidity is 1 byte
237
+ new_humidity = buf [0 ]
238
+ # temperature is 1 byte
239
+ new_temperature = buf [2 ]
240
+ else :
241
+ # humidity is 2 bytes
242
+ new_humidity = ((buf [0 ] << 8 ) | buf [1 ]) / 10
243
+ # temperature is 2 bytes
244
+ # MSB is sign, bits 0-14 are magnitude)
245
+ new_temperature = (((buf [2 ] & 0x7F ) << 8 ) | buf [3 ]) / 10
246
+ # set sign
247
+ if buf [2 ] & 0x80 :
248
+ new_temperature = - new_temperature
249
+ # calc checksum
250
+ chk_sum = 0
251
+ for b in buf [0 :4 ]:
252
+ chk_sum += b
253
+
254
+ # checksum is the last byte
255
+ if chk_sum & 0xFF != buf [4 ]:
256
+ # check sum failed to validate
257
+ raise RuntimeError ("Checksum did not validate. Try again." )
258
+
259
+ if new_humidity < 0 or new_humidity > 100 :
260
+ # We received unplausible data
261
+ raise RuntimeError ("Received unplausible data. Try again." )
262
+
263
+ self ._temperature = new_temperature
264
+ self ._humidity = new_humidity
265
+
266
+ @property
267
+ def temperature (self ) -> Union [int , float , None ]:
268
+ """temperature current reading. It makes sure a reading is available
269
+
270
+ Raises RuntimeError exception for checksum failure and for insufficient
271
+ data returned from the device (try again)
272
+ """
273
+ self .measure ()
274
+ return self ._temperature
275
+
276
+ @property
277
+ def humidity (self ) -> Union [int , float , None ]:
278
+ """humidity current reading. It makes sure a reading is available
279
+
280
+ Raises RuntimeError exception for checksum failure and for insufficient
281
+ data returned from the device (try again)
282
+ """
283
+ self .measure ()
284
+ return self ._humidity
285
+
286
+
287
+ class DHT11 (DHTBase ):
288
+ """Support for DHT11 device.
289
+
290
+ :param ~board.Pin pin: digital pin used for communication
291
+ """
292
+
293
+ def __init__ (self , pin : Pin , use_pulseio : bool = _USE_PULSEIO ):
294
+ super ().__init__ (True , pin , 18000 , use_pulseio )
295
+
296
+
297
+ class DHT22 (DHTBase ):
298
+ """Support for DHT22 device.
299
+
300
+ :param ~board.Pin pin: digital pin used for communication
301
+ """
302
+
303
+ def __init__ (self , pin : Pin , use_pulseio : bool = _USE_PULSEIO ):
304
+ super ().__init__ (False , pin , 1000 , use_pulseio )
305
+
306
+
307
+ class DHT21 (DHTBase ):
308
+ """Support for DHT21/AM2301 device.
309
+
310
+ :param ~board.Pin pin: digital pin used for communication
311
+ """
312
+
313
+ # DHT21/AM2301 is sending three more dummy bytes after the "official" protocol.
314
+ # Pulseio will take only the last pulses up to maxPulses.
315
+ # If that would be 81, the dummy pulses will be read and the real data would be truncated.
316
+ # Hence setting maxPulses to 129, taking both real data and dummy bytes into buffer.
317
+ def __init__ (self , pin : Pin , use_pulseio : bool = _USE_PULSEIO ):
318
+ super ().__init__ (False , pin , 1000 , use_pulseio , max_pulses = 129 )
0 commit comments