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