/ adafruit_dht.py
adafruit_dht.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Mike McWethy for Adafruit Industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  :mod:`adafruit_dhtlib`
 24  ======================
 25  
 26  CircuitPython support for the DHT11 and DHT22 temperature and humidity devices.
 27  
 28  * Author(s): Mike McWethy
 29  """
 30  
 31  import array
 32  import time
 33  try:
 34      import pulseio
 35  except ImportError as excpt:
 36      print("adafruit_dht requires the pulseio library, but it failed to load."+
 37          "  Note that CircuitPython does not support pulseio on all boards.")
 38      raise excpt
 39  
 40  class DHTBase:
 41      """ base support for DHT11 and DHT22 devices
 42      """
 43  
 44      __hiLevel = 51
 45  
 46      def __init__(self, dht11, pin, trig_wait):
 47          """
 48          :param boolean dht11: True if device is DHT11, otherwise DHT22.
 49          :param ~board.Pin pin: digital pin used for communication
 50          :param int trig_wait: length of time to hold trigger in LOW state (microseconds)
 51          """
 52          self._dht11 = dht11
 53          self._pin = pin
 54          self._trig_wait = trig_wait
 55          self._last_called = 0
 56          self._humidity = None
 57          self._temperature = None
 58  
 59  
 60      def _pulses_to_binary(self, pulses, start, stop):
 61          """Takes pulses, a list of transition times, and converts
 62          them to a 1's or 0's.  The pulses array contains the transition times.
 63          pulses starts with a low transition time followed by a high transistion time.
 64          then a low followed by a high and so on.  The low transition times are
 65          ignored.  Only the high transition times are used.  If the high
 66          transition time is greater than __hiLevel, that counts as a bit=1, if the
 67          high transition time is less that __hiLevel, that counts as a bit=0.
 68  
 69          start is the starting index in pulses to start converting
 70  
 71          stop is the index to convert upto but not including
 72  
 73          Returns an integer containing the converted 1 and 0 bits
 74          """
 75  
 76          binary = 0
 77          hi_sig = False
 78          for bit_inx in range(start, stop):
 79              if hi_sig:
 80                  bit = 0
 81                  if pulses[bit_inx] > self.__hiLevel:
 82                      bit = 1
 83                  binary = binary<<1 | bit
 84  
 85              hi_sig = not hi_sig
 86  
 87          return binary
 88  
 89      def _get_pulses(self):
 90          """ _get_pulses implements the communication protcol for
 91          DHT11 and DHT22 type devices.  It sends a start signal
 92          of a specific length and listens and measures the
 93          return signal lengths.
 94  
 95          return pulses (array.array uint16) contains alternating high and low
 96          transition times starting with a low transition time.  Normally
 97          pulses will have 81 elements for the DHT11/22 type devices.
 98          """
 99          pulses = array.array('H')
100          tmono = time.monotonic()
101  
102          # create the PulseIn object using context manager
103          with pulseio.PulseIn(self._pin, 81, True) as pulse_in:
104  
105              # The DHT type device use a specialize 1-wire protocol
106              # The microprocessor first sends a LOW signal for a
107              # specific length of time.  Then the device sends back a
108              # series HIGH and LOW signals.  The length the HIGH signals
109              # represents the device values.
110              pulse_in.pause()
111              pulse_in.clear()
112              pulse_in.resume(self._trig_wait)
113  
114              # loop until we get the return pulse we need or
115              # time out after 1/2 seconds
116              while True:
117                  if len(pulse_in) >= 80:
118                      break
119                  if time.monotonic()-tmono > 0.5: # time out after 1/2 seconds
120                      break
121  
122              pulse_in.pause()
123              while len(pulse_in):
124                  pulses.append(pulse_in.popleft())
125              pulse_in.resume()
126  
127          return pulses
128  
129      def measure(self):
130          """ measure runs the communications to the DHT11/22 type device.
131              if successful, the class properties temperature and humidity will
132              return the reading returned from the device.
133  
134              Raises RuntimeError exception for checksum failure and for insuffcient
135              data returned from the device (try again)
136          """
137          if time.monotonic()-self._last_called > 0.5:
138              self._last_called = time.monotonic()
139  
140              pulses = self._get_pulses()
141              ##print(pulses)
142  
143              if len(pulses) >= 80:
144                  buf = array.array('B')
145                  for byte_start in range(0, 80, 16):
146                      buf.append(self._pulses_to_binary(pulses, byte_start, byte_start+16))
147                  #print(buf)
148  
149                  # humidity is 2 bytes
150                  if self._dht11:
151                      self._humidity = buf[0]
152                  else:
153                      self._humidity = ((buf[0]<<8) | buf[1]) / 10
154  
155                  # tempature is 2 bytes
156                  if self._dht11:
157                      self._temperature = buf[2]
158                  else:
159                      self._temperature = ((buf[2]<<8) | buf[3]) / 10
160  
161                  # calc checksum
162                  chk_sum = 0
163                  for b in buf[0:4]:
164                      chk_sum += b
165  
166                  # checksum is the last byte
167                  if chk_sum & 0xff != buf[4]:
168                      # check sum failed to validate
169                      raise RuntimeError("Checksum did not validate. Try again.")
170                      #print("checksum did not match. Temp: {} Humidity: {} Checksum:{}".format(self._temperature,self._humidity,bites[4]))
171  
172                  # checksum matches
173                  #print("Temp: {} C Humidity: {}% ".format(self._temperature, self._humidity))
174  
175              else:
176                  raise RuntimeError("A full buffer was not returned.  Try again.")
177                  #print("did not get a full return.  number returned was: {}".format(len(r)))
178  
179      @property
180      def temperature(self):
181          """ temperature current reading.  It makes sure a reading is available
182  
183              Raises RuntimeError exception for checksum failure and for insuffcient
184              data returned from the device (try again)
185          """
186          self.measure()
187          return self._temperature
188  
189      @property
190      def humidity(self):
191          """ humidity current reading. It makes sure a reading is available
192  
193              Raises RuntimeError exception for checksum failure and for insuffcient
194              data returned from the device (try again)
195          """
196          self.measure()
197          return self._humidity
198  
199  class DHT11(DHTBase):
200      """ Support for DHT11 device.
201  
202          :param ~board.Pin pin: digital pin used for communication
203      """
204      def __init__(self, pin):
205          super().__init__(True, pin, 18000)
206  
207  
208  class DHT22(DHTBase):
209      """ Support for DHT22 device.
210  
211          :param ~board.Pin pin: digital pin used for communication
212      """
213      def __init__(self, pin):
214          super().__init__(False, pin, 1000)