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.
« Previous 1 2 3 4 Next »
Buy this article as PDF
Pages: 7
(incl. VAT)