Using a temperature and humidity sensor to monitor a terrarium

Code Breakdown

Listing 1 shows the monitoring software for the sensor [3]. The first line tells the shell that, when executed, this file should be interpreted by the file /usr/bin/python; that is, it tells the operating system to use Python to run this program. The following commented lines (#) spell out the license agreement. The Adafruit library for the DHT series of sensors is offered for free use, provided this license statement is included with all source code.

Listing 1

DHT11 Monitoring Software

001 #!/usr/bin/python
002
003 # Copyright (c) 2014 Adafruit Industries
004 # Author: Tony DiCola
005
006 # Permission is hereby granted, free of charge, to any person obtaining a copy
007 # of this software and associated documentation files (the "Software"), to deal
008 # in the Software without restriction, including without limitation the rights
009 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 # copies of the Software, and to permit persons to whom the Software is
011 # furnished to do so, subject to the following conditions:
012
013 # The above copyright notice and this permission notice shall be included in all
014 # copies or substantial portions of the Software.
015
016 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 # SOFTWARE.
023 import Adafruit_DHT
024 import pygame
025 import datetime
026 from dateutil import tz
027
028 sensor = Adafruit_DHT.DHT11
029 pin = 2
030
031 class logger:
032    def __init__ ( self , screen , sensor , pin ):
033       self.screen = screen
034       self.sensor = sensor
035       self.pin = pin
036
037       self.tempMin = None
038       self.tempMax = None
039       self.humidityMin = None
040       self.humidityMax = None
041       self.temperatureLog = list()
042       self.humidityLog = list()
043       self.timestamps = list()
044
045       self.tempBox = None
046       self.humiBox = None
047
048       pygame.font.init()
049       self.currentFont = pygame.font.SysFont ( "" , 100 )
050       self.statsFont = pygame.font.SysFont ( "" , 40 )
051
052    def sense ( self ):
053       humidity , temperature = Adafruit_DHT.read_retry
            ( self.sensor , self.pin )
054       if humidity is not None and temperature is not None:
055          return ( humidity , temperature )
056       else:
057          return None
058
059    def plotCurrent ( self ):
060       if self.tempBox != None: pygame.draw.rect
            ( self.screen , ( 0 , 0 , 0 ) , self.tempBox )
061       if self.humiBox != None: pygame.draw.rect
            ( self.screen , ( 0 , 0 , 0 ) , self.humiBox )
062       tempSurf = self.currentFont.render
            ( "{0:5.1f} F".format ( self.temperature ) ,
              True , ( 0 , 255 , 0 ) )
063       humiSurf = self.currentFont.render
            ( "{0}%".format ( self.humidity ) , True ,
              ( 0 , 255 , 0 ) )
064       self.tempBox = self.screen.blit ( tempSurf ,
                                            ( 1060 , 100 ) )
065       self.humiBox = self.screen.blit ( humiSurf ,
                                            ( 1440 , 100 ) )
066
067    def plotStats ( self ):
068       tempLo = self.statsFont.render
            ( "Lowest seen {0:5.1f} F".format
              ( self.tempMin ) , True , ( 0 , 255 , 0 ) )
069       tempHi = self.statsFont.render
            ( "Highest seen {0:5.1f} F".format
              ( self.tempMax ) , True , ( 0 , 255 , 0 ) )
070       tempAvg = self.statsFont.render
            ( "Average {0:5.1f} F".format
              ( self.tempAverage ) , True , ( 0 , 255 , 0 ) )
071       humidityLo = self.statsFont.render
            ( "Lowest seen {0}%".format ( self.humidityMin ) ,
              True , ( 0 , 255 , 0 ) )
072       humidityHi = self.statsFont.render
            ( "Highest seen {0}%".format ( self.humidityMax ) ,
              True , ( 0 , 255 , 0 ) )
073       humidityAvg = self.statsFont.render
            ( "Average {0:5.1f}%".format
              ( self.humidityAverage ) , True , ( 0 , 255 , 0 ) )
074
075       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) ,
                             ( ( 960 , 200 , 800 , 200 ) ) )
076       self.screen.blit ( tempLo , ( 1060 , 200 ) )
077       self.screen.blit ( tempHi , ( 1060 , 250 ) )
078       self.screen.blit ( tempAvg , ( 1060 , 300 ) )
079       self.screen.blit ( humidityLo , ( 1440 , 200 ) )
080       self.screen.blit ( humidityHi , ( 1440 , 250 ) )
081       self.screen.blit ( humidityAvg , ( 1440 , 300 ) )
082
083    def keyMap ( self ):
084       resetStats = self.statsFont.render
            ( "S -- Reset stats" , True , ( 0 , 255 , 0 ) )
085       resetGraph = self.statsFont.render
            ( "G -- Reset graph" , True , ( 0 , 255 , 0 ) )
086       resetAll = self.statsFont.render
            ( "A -- Reset all" , True , ( 0 , 255 , 0 ) )
087
088       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) ,
                             ( ( 960 , 500 , 800 , 200 ) ) )
089       self.screen.blit ( resetStats , ( 1060 , 500 ) )
090       self.screen.blit ( resetGraph , ( 1060 , 550 ) )
091       self.screen.blit ( resetAll , ( 1060 , 600 ) )
092
093    def graph ( self ):
094       scaleFont = pygame.font.SysFont ( "" , 40 )
095       pygame.draw.rect ( self.screen , ( 0 , 0 , 0 ) ,
                             ( 0 , 0 , 960 , 1080 ) )
096       pygame.draw.line ( self.screen , ( 255 , 255 , 255 ) ,
                             ( 100 , 0 ) , ( 100 , 980 ) , 1 )
097       pygame.draw.line ( self.screen , ( 255 , 255 , 255 ) ,
                             ( 100 , 980 ) , ( 960 , 980 ) , 1 )
098
099       startTime = "{0}-{1}-{2} {3}:{4:02d}:{5:02d}".format
           ( self.timestamps [ 0 ].month , self.timestamps
           [ 0 ].day , self.timestamps [ 0 ].year ,
           self.timestamps [ 0 ].hour , self.timestamps
           [ 0 ].minute , self.timestamps [ 0 ].second )
100       endTime = "{0}-{1}-{2} {3}:{4:02d}:{5:02d}".format
           ( self.timestamps [ -1 ].month , self.timestamps
           [ -1 ].day , self.timestamps [ -1 ].year ,
           self.timestamps [ -1 ].hour , self.timestamps
           [ -1 ].minute , self.timestamps [ -1 ].second )
101
102       start = scaleFont.render ( startTime , True ,
                                     ( 255 , 255 , 255 ) )
103       end = scaleFont.render ( endTime , True ,
                                   ( 255 , 255 , 255 ) )
104       self.screen.blit ( start , ( 100 , 1000 ) )
105       self.screen.blit ( end , ( 960 - end.get_width()
                             , 1000 ) )
106
107       value = 0
108       for i in range ( 0 , 950 , 86 ):
109          scale = scaleFont.render
              ( str ( value ) , True , ( 255 , 255 , 255 ) )
110          self.screen.blit ( scale ,
                                ( 50 , 950 -  i + 10 ) )
111          if i > 0:
112             pygame.draw.line ( self.screen ,
                                   ( 50 , 50 , 50 ) ,
                                   ( 100 , 950 - i + 30 ) ,
                                   ( 980 , 950 - i + 30 ) )
113          value += 10
114       for i in range ( 1 , len ( self.temperatureLog ) ):
115          tempHeight = ( self.temperatureLog [ i ] / 110 ) * 950
116          pygame.draw.circle ( self.screen , ( 0 , 0 ,
                                  255 ) , ( i + 100 , 980 -
                                  int ( tempHeight ) ) , 1 , 0 )
117       for i in range ( 1 , len ( self.humidityLog ) ):
118          humidityHeight = ( self.humidityLog [ i ] / 110 ) * 950
119          pygame.draw.circle ( self.screen ,
                                  ( 255 , 255 , 0 ) ,
                                  ( i + 100 , 980 - int
                                  ( humidityHeight ) ) , 1 , 0 )
120
121    def update ( self ):
122       reading = self.sense()
123       if reading != None:
124          self.humidity = reading [ 0 ]
125          self.temperature = float
              ( reading [ 1 ] ) * 1.8 + 32
126       else:
127          self.humidity = None
128          self.temperature = None
129
130       if self.humidityMin == None or self.humidityMin >
           self.humidity: self.humidityMin = self.humidity
131       if self.humidityMax == None or self.humidityMax <
           self.humidity: self.humidityMax = self.humidity
132       if self.tempMin == None or self.tempMin >
           self.temperature: self.tempMin = self.temperature
133       if self.tempMax == None or self.tempMax <
           self.temperature: self.tempMax = self.temperature
134       if self.humidity != None: self.humidityLog.append
           ( self.humidity )
135       if self.temperature != None:
136          self.temperatureLog.append ( self.temperature )
137          self.timestamps.append ( datetime.datetime.now
                                      ( tz.gettz (
                                      "America/Chicago" ) ) )
138
139       if len ( self.temperatureLog ) > 720:
           self.temperatureLog = self.temperatureLog [ -720: ]
140       if len ( self.humidityLog ) >
            720: self.humidityLog = self.humidityLog [ -720: ]
141       if len ( self.timestamps ) >
            720: self.timestamps = self.timestamps [ -720: ]
142
143       self.tempAverage = sum ( self.temperatureLog ) /
            float ( len ( self.temperatureLog ) )
144       self.humidityAverage = sum ( self.humidityLog ) /
            float ( len ( self.humidityLog ) )
145
146       self.plotCurrent()
147       self.plotStats()
148       self.graph()
149       self.keyMap()
150
151    def loop ( self ):
152       pygame.time.set_timer ( pygame.USEREVENT , 5000 )
153       looping = True
154       while looping:
155          for event in pygame.event.get():
156             if event.type == pygame.USEREVENT:
157                self.update()
158                pygame.display.flip()
159             elif event.type == pygame.KEYDOWN:
160                if event.key == ord ( "q" ):
161                   pygame.quit()
162                   looping = False
163                elif event.key == ord ( "s" ):
164                   self.tempMin = None
165                   self.tempMax = None
166                   self.humidityMin = None
167                   self.humidityMax = None
168                   self.update()
169                   pygame.display.flip()
170                elif event.key == ord ( "g" ):
171                   self.temperatureLog = list()
172                   self.humidityLog = list()
173                   self.timestamps = list()
174                   self.update()
175                   pygame.display.flip()
176                elif event.key == ord ( "a" ):
177                   self.tempMin = None
178                   self.tempMax = None
179                   self.humidityMin = None
180                   self.humidityMax = None
181                   self.temperatureLog = list()
182                   self.humidityLog = list()
183                   self.timestamps = list()
184                   self.update()
185                   pygame.display.flip()
186
187
188 pygame.display.init()
189 screen = pygame.display.set_mode ( ( 1920 , 1080 ) )
190
191 log = logger ( screen , sensor , pin )
192 log.loop()

The import lines that follow are Python's way of bringing in external libraries. The Adafruit_DHT library adds the ability to talk to the T/H sensor, pygame handles the graphics, and datetime provides dates and times. Line 26 imports time zone calculations. Before starting the main part of the program, I have to let the Adafruit library know which sensor I have connected to the Rasp Pi and which data pin to use to talk to the sensor.

The logger Class

The logger class (lines 31-185) monitors the temperature and humidity and plots it to a graph. I begin by initializing the class and continuing with a number of lines that start with self. The __init__ function runs automatically whenever the class is initialized. This lets you set everything up. Any arguments that __init__ asks for must be passed when the class is created. I then copy the arguments screen, sensor, and pin to class properties by adding self. in front of them. Lines 37-46 initialize the assorted statistics variables and graphics placeholders, and lines 48-50 create fonts that I can use to render text at different sizes.

The sense function in lines 52-57 reads the T/H sensor. The read_retry() continuously tries to read the sensor for 30 seconds and returns humidity and temperature as either a float value or None if it fails to read the sensor. Line 54 makes sure that both values are present and, if so, returns them as a tuple (line 55). If either value if missing, it returns None (line 57),

The plotCurrent function shows the most recent temperature and humidity readings in a large font at the top of the display. Lines 60 and 61 check to see that self.tempBox and self.humiBox exist and, if so, draw a black rectangle. The first time through the function, they don't exist (lines 64 and 65 create those variables).

Blitting is how Python transfers pixels to the screen; blit always returns the screen coordinates that it has affected as a PyGame rect object and stores them to clear the text easily the next time through the loop. Lines 62 and 63 render current temperature and humidity values onto surfaces ready for blit.

The plotStats (lines 67-81) and keyMap (lines 83-91) functions work exactly the same as plotCurrent, but they plot different variables at a smaller size. plotStats shows the minimum, maximum, and average values for both temperature and humidity, whereas keyMap draws the legend for which key press does what.

Buy this article as PDF

Express-Checkout as PDF

Pages: 7

Price $2.95
(incl. VAT)

Buy Raspberry Pi Geek

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content