Sleep before first PMS5003 reading
[EVA-2020-02-2.git] / examples / combined.py
1 #!/usr/bin/env python
2
3 import time
4 import colorsys
5 import os
6 import sys
7 import ST7735
8 try:
9 # Transitional fix for breaking change in LTR559
10 from ltr559 import LTR559
11 ltr559 = LTR559()
12 except ImportError:
13 import ltr559
14
15 from bme280 import BME280
16 from pms5003 import PMS5003, ReadTimeoutError as pmsReadTimeoutError
17 from enviroplus import gas
18 from subprocess import PIPE, Popen
19 from PIL import Image
20 from PIL import ImageDraw
21 from PIL import ImageFont
22 import logging
23
24 logging.basicConfig(
25 format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
26 level=logging.INFO,
27 datefmt='%Y-%m-%d %H:%M:%S')
28
29 logging.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors
30
31 Press Ctrl+C to exit!
32
33 """)
34
35 # BME280 temperature/pressure/humidity sensor
36 bme280 = BME280()
37
38 # PMS5003 particulate sensor
39 pms5003 = PMS5003()
40 time.sleep(1.0)
41
42 # Create ST7735 LCD display class
43 st7735 = ST7735.ST7735(
44 port=0,
45 cs=1,
46 dc=9,
47 backlight=12,
48 rotation=270,
49 spi_speed_hz=10000000
50 )
51
52 # Initialize display
53 st7735.begin()
54
55 WIDTH = st7735.width
56 HEIGHT = st7735.height
57
58 # Set up canvas and font
59 img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
60 draw = ImageDraw.Draw(img)
61 path = os.path.dirname(os.path.realpath(__file__))
62 font = ImageFont.truetype(path + "/fonts/Asap/Asap-Bold.ttf", 20)
63 smallfont = ImageFont.truetype(path + "/fonts/Asap/Asap-Bold.ttf", 10)
64 x_offset = 2
65 y_offset = 2
66
67 message = ""
68
69 # The position of the top bar
70 top_pos = 25
71
72 # Create a values dict to store the data
73 variables = ["temperature",
74 "pressure",
75 "humidity",
76 "light",
77 "oxidised",
78 "reduced",
79 "nh3",
80 "pm1",
81 "pm25",
82 "pm10"]
83
84 units = ["C",
85 "hPa",
86 "%",
87 "Lux",
88 "kO",
89 "kO",
90 "kO",
91 "ug/m3",
92 "ug/m3",
93 "ug/m3"]
94
95 # Define your own warning limits
96 # The limits definition follows the order of the variables array
97 # Example limits explanation for temperature:
98 # [4,18,28,35] means
99 # [-273.15 .. 4] -> Dangerously Low
100 # (4 .. 18] -> Low
101 # (18 .. 28] -> Normal
102 # (28 .. 35] -> High
103 # (35 .. MAX] -> Dangerously High
104 # DISCLAIMER: The limits provided here are just examples and come
105 # with NO WARRANTY. The authors of this example code claim
106 # NO RESPONSIBILITY if reliance on the following values or this
107 # code in general leads to ANY DAMAGES or DEATH.
108 limits = [[4,18,28,35],
109 [250,650,1013.25,1015],
110 [20,30,60,70],
111 [-1,-1,30000,100000],
112 [-1,-1,40,50],
113 [-1,-1,450,550],
114 [-1,-1,200,300],
115 [-1,-1,50,100],
116 [-1,-1,50,100],
117 [-1,-1,50,100]]
118
119 # RGB palette for values on the combined screen
120 palette = [(0,0,255), # Dangerously Low
121 (0,255,255), # Low
122 (0,255,0), # Normal
123 (255,255,0), # High
124 (255,0,0)] # Dangerously High
125
126 values = {}
127
128
129 # Displays data and text on the 0.96" LCD
130 def display_text(variable, data, unit):
131 # Maintain length of list
132 values[variable] = values[variable][1:] + [data]
133 # Scale the values for the variable between 0 and 1
134 colours = [(v - min(values[variable]) + 1) / (max(values[variable])
135 - min(values[variable]) + 1) for v in values[variable]]
136 # Format the variable name and value
137 message = "{}: {:.1f} {}".format(variable[:4], data, unit)
138 logging.info(message)
139 draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
140 for i in range(len(colours)):
141 # Convert the values to colours from red to blue
142 colour = (1.0 - colours[i]) * 0.6
143 r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour,
144 1.0, 1.0)]
145 # Draw a 1-pixel wide rectangle of colour
146 draw.rectangle((i, top_pos, i+1, HEIGHT), (r, g, b))
147 # Draw a line graph in black
148 line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos)))\
149 + top_pos
150 draw.rectangle((i, line_y, i+1, line_y+1), (0, 0, 0))
151 # Write the text at the top in black
152 draw.text((0, 0), message, font=font, fill=(0, 0, 0))
153 st7735.display(img)
154
155 # Saves the data to be used in the graphs later and prints to the log
156 def save_data(idx, data):
157 variable = variables[idx]
158 # Maintain length of list
159 values[variable] = values[variable][1:] + [data]
160 unit = units[idx]
161 message = "{}: {:.1f} {}".format(variable[:4], data, unit)
162 logging.info(message)
163
164
165 # Displays all the text on the 0.96" LCD
166 def display_everything():
167 draw.rectangle((0, 0, WIDTH, HEIGHT), (0, 0, 0))
168 column_count = 2
169 row_count = (len(variables)/column_count)
170 for i in xrange(len(variables)):
171 variable = variables[i]
172 data_value = values[variable][-1]
173 unit = units[i]
174 x = x_offset + ((WIDTH/column_count) * (i / row_count))
175 y = y_offset + ((HEIGHT/row_count) * (i % row_count))
176 message = "{}: {:.1f} {}".format(variable[:4], data_value, unit)
177 lim = limits[i]
178 rgb = palette[0]
179 for j in xrange(len(lim)):
180 if data_value > lim[j]:
181 rgb = palette[j+1]
182 draw.text((x, y), message, font=smallfont, fill=rgb)
183 st7735.display(img)
184
185
186
187 # Get the temperature of the CPU for compensation
188 def get_cpu_temperature():
189 process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True)
190 output, _error = process.communicate()
191 return float(output[output.index('=') + 1:output.rindex("'")])
192
193
194 # Tuning factor for compensation. Decrease this number to adjust the
195 # temperature down, and increase to adjust up
196 factor = 1.95
197
198 cpu_temps = [get_cpu_temperature()] * 5
199
200 delay = 0.5 # Debounce the proximity tap
201 mode = 10 # The starting mode
202 last_page = 0
203 light = 1
204
205 for v in variables:
206 values[v] = [1] * WIDTH
207
208 # The main loop
209 try:
210 while True:
211 proximity = ltr559.get_proximity()
212
213 # If the proximity crosses the threshold, toggle the mode
214 if proximity > 1500 and time.time() - last_page > delay:
215 mode += 1
216 mode %= (len(variables)+1)
217 last_page = time.time()
218
219 # One mode for each variable
220 if mode == 0:
221 # variable = "temperature"
222 unit = "C"
223 cpu_temp = get_cpu_temperature()
224 # Smooth out with some averaging to decrease jitter
225 cpu_temps = cpu_temps[1:] + [cpu_temp]
226 avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps))
227 raw_temp = bme280.get_temperature()
228 data = raw_temp - ((avg_cpu_temp - raw_temp) / factor)
229 display_text(variables[mode], data, unit)
230
231 if mode == 1:
232 # variable = "pressure"
233 unit = "hPa"
234 data = bme280.get_pressure()
235 display_text(variables[mode], data, unit)
236
237 if mode == 2:
238 # variable = "humidity"
239 unit = "%"
240 data = bme280.get_humidity()
241 display_text(variables[mode], data, unit)
242
243 if mode == 3:
244 # variable = "light"
245 unit = "Lux"
246 if proximity < 10:
247 data = ltr559.get_lux()
248 else:
249 data = 1
250 display_text(variables[mode], data, unit)
251
252 if mode == 4:
253 # variable = "oxidised"
254 unit = "kO"
255 data = gas.read_all()
256 data = data.oxidising / 1000
257 display_text(variables[mode], data, unit)
258
259 if mode == 5:
260 # variable = "reduced"
261 unit = "kO"
262 data = gas.read_all()
263 data = data.reducing / 1000
264 display_text(variables[mode], data, unit)
265
266 if mode == 6:
267 # variable = "nh3"
268 unit = "kO"
269 data = gas.read_all()
270 data = data.nh3 / 1000
271 display_text(variables[mode], data, unit)
272
273 if mode == 7:
274 # variable = "pm1"
275 unit = "ug/m3"
276 try:
277 data = pms5003.read()
278 except pmsReadTimeoutError:
279 logging.warn("Failed to read PMS5003")
280 else:
281 data = float(data.pm_ug_per_m3(1.0))
282 display_text(variables[mode], data, unit)
283
284 if mode == 8:
285 # variable = "pm25"
286 unit = "ug/m3"
287 try:
288 data = pms5003.read()
289 except pmsReadTimeoutError:
290 logging.warn("Failed to read PMS5003")
291 else:
292 data = float(data.pm_ug_per_m3(2.5))
293 display_text(variables[mode], data, unit)
294
295 if mode == 9:
296 # variable = "pm10"
297 unit = "ug/m3"
298 try:
299 data = pms5003.read()
300 except pmsReadTimeoutError:
301 logging.warn("Failed to read PMS5003")
302 else:
303 data = float(data.pm_ug_per_m3(10))
304 display_text(variables[mode], data, unit)
305 if mode == 10:
306 # Everything on one screen
307 cpu_temp = get_cpu_temperature()
308 # Smooth out with some averaging to decrease jitter
309 cpu_temps = cpu_temps[1:] + [cpu_temp]
310 avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps))
311 raw_temp = bme280.get_temperature()
312 raw_data = raw_temp - ((avg_cpu_temp - raw_temp) / factor)
313 save_data(0, raw_data)
314 display_everything()
315 raw_data = bme280.get_pressure()
316 save_data(1, raw_data)
317 display_everything()
318 raw_data = bme280.get_humidity()
319 save_data(2, raw_data)
320 if proximity < 10:
321 raw_data = ltr559.get_lux()
322 else:
323 raw_data = 1
324 save_data(3, raw_data)
325 display_everything()
326 gas_data = gas.read_all()
327 save_data(4, gas_data.oxidising / 1000)
328 save_data(5, gas_data.reducing / 1000)
329 save_data(6, gas_data.nh3 / 1000)
330 display_everything()
331 pms_data = None
332 try:
333 pms_data = pms5003.read()
334 except pmsReadTimeoutError:
335 logging.warn("Failed to read PMS5003")
336 else:
337 save_data(7, float(pms_data.pm_ug_per_m3(1.0)))
338 save_data(8, float(pms_data.pm_ug_per_m3(2.5)))
339 save_data(9, float(pms_data.pm_ug_per_m3(10)))
340 display_everything()
341
342
343
344 # Exit cleanly
345 except KeyboardInterrupt:
346 sys.exit(0)