Test noise, fix gas
[EVA-2020-02-2.git] / examples / combined.py
CommitLineData
20442c9a 1#!/usr/bin/env python3
71dc2962
K
2
3import time
4import colorsys
71dc2962
K
5import sys
6import ST7735
7try:
8 # Transitional fix for breaking change in LTR559
9 from ltr559 import LTR559
10 ltr559 = LTR559()
11except ImportError:
12 import ltr559
13
14from bme280 import BME280
15from pms5003 import PMS5003, ReadTimeoutError as pmsReadTimeoutError
16from enviroplus import gas
17from subprocess import PIPE, Popen
18from PIL import Image
19from PIL import ImageDraw
20from PIL import ImageFont
20442c9a 21from fonts.ttf import RobotoMedium as UserFont
71dc2962
K
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()
7c3404f8 40time.sleep(1.0)
71dc2962
K
41
42# Create ST7735 LCD display class
43st7735 = 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
53st7735.begin()
54
55WIDTH = st7735.width
56HEIGHT = st7735.height
57
58# Set up canvas and font
59img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
60draw = ImageDraw.Draw(img)
20442c9a 61font_size_small = 10
62font_size_large = 20
63font = ImageFont.truetype(UserFont, font_size_large)
64smallfont = ImageFont.truetype(UserFont, font_size_small)
71dc2962
K
65x_offset = 2
66y_offset = 2
67
68message = ""
69
70# The position of the top bar
71top_pos = 25
72
73# Create a values dict to store the data
74variables = ["temperature",
75 "pressure",
76 "humidity",
77 "light",
78 "oxidised",
79 "reduced",
80 "nh3",
81 "pm1",
82 "pm25",
83 "pm10"]
84
85units = ["C",
86 "hPa",
87 "%",
88 "Lux",
89 "kO",
90 "kO",
91 "kO",
92 "ug/m3",
93 "ug/m3",
94 "ug/m3"]
95
96# Define your own warning limits
97# The limits definition follows the order of the variables array
98# Example limits explanation for temperature:
99# [4,18,28,35] means
100# [-273.15 .. 4] -> Dangerously Low
101# (4 .. 18] -> Low
102# (18 .. 28] -> Normal
103# (28 .. 35] -> High
104# (35 .. MAX] -> Dangerously High
105# DISCLAIMER: The limits provided here are just examples and come
106# with NO WARRANTY. The authors of this example code claim
107# NO RESPONSIBILITY if reliance on the following values or this
108# code in general leads to ANY DAMAGES or DEATH.
be4d0fc9
PH
109limits = [[4, 18, 28, 35],
110 [250, 650, 1013.25, 1015],
111 [20, 30, 60, 70],
112 [-1, -1, 30000, 100000],
113 [-1, -1, 40, 50],
114 [-1, -1, 450, 550],
115 [-1, -1, 200, 300],
116 [-1, -1, 50, 100],
117 [-1, -1, 50, 100],
118 [-1, -1, 50, 100]]
71dc2962
K
119
120# RGB palette for values on the combined screen
be4d0fc9
PH
121palette = [(0, 0, 255), # Dangerously Low
122 (0, 255, 255), # Low
123 (0, 255, 0), # Normal
124 (255, 255, 0), # High
125 (255, 0, 0)] # Dangerously High
71dc2962
K
126
127values = {}
128
129
130# Displays data and text on the 0.96" LCD
131def display_text(variable, data, unit):
132 # Maintain length of list
133 values[variable] = values[variable][1:] + [data]
134 # Scale the values for the variable between 0 and 1
be4d0fc9
PH
135 vmin = min(values[variable])
136 vmax = max(values[variable])
137 colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
71dc2962
K
138 # Format the variable name and value
139 message = "{}: {:.1f} {}".format(variable[:4], data, unit)
140 logging.info(message)
141 draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
142 for i in range(len(colours)):
143 # Convert the values to colours from red to blue
144 colour = (1.0 - colours[i]) * 0.6
be4d0fc9 145 r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
71dc2962 146 # Draw a 1-pixel wide rectangle of colour
be4d0fc9 147 draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b))
71dc2962 148 # Draw a line graph in black
be4d0fc9
PH
149 line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos
150 draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))
71dc2962
K
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
be4d0fc9 155
71dc2962
K
156# Saves the data to be used in the graphs later and prints to the log
157def save_data(idx, data):
158 variable = variables[idx]
159 # Maintain length of list
160 values[variable] = values[variable][1:] + [data]
161 unit = units[idx]
162 message = "{}: {:.1f} {}".format(variable[:4], data, unit)
163 logging.info(message)
164
165
166# Displays all the text on the 0.96" LCD
167def display_everything():
168 draw.rectangle((0, 0, WIDTH, HEIGHT), (0, 0, 0))
169 column_count = 2
be4d0fc9 170 row_count = (len(variables) / column_count)
20442c9a 171 for i in range(len(variables)):
71dc2962
K
172 variable = variables[i]
173 data_value = values[variable][-1]
174 unit = units[i]
be4d0fc9
PH
175 x = x_offset + ((WIDTH / column_count) * (i / row_count))
176 y = y_offset + ((HEIGHT / row_count) * (i % row_count))
71dc2962
K
177 message = "{}: {:.1f} {}".format(variable[:4], data_value, unit)
178 lim = limits[i]
179 rgb = palette[0]
20442c9a 180 for j in range(len(lim)):
71dc2962 181 if data_value > lim[j]:
be4d0fc9 182 rgb = palette[j + 1]
71dc2962
K
183 draw.text((x, y), message, font=smallfont, fill=rgb)
184 st7735.display(img)
185
186
71dc2962
K
187# Get the temperature of the CPU for compensation
188def 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
be4d0fc9
PH
194def main():
195 # Tuning factor for compensation. Decrease this number to adjust the
196 # temperature down, and increase to adjust up
197 factor = 2.25
198
199 cpu_temps = [get_cpu_temperature()] * 5
200
201 delay = 0.5 # Debounce the proximity tap
202 mode = 10 # The starting mode
203 last_page = 0
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)
71dc2962 230
be4d0fc9
PH
231 if mode == 1:
232 # variable = "pressure"
233 unit = "hPa"
234 data = bme280.get_pressure()
235 display_text(variables[mode], data, unit)
71dc2962 236
be4d0fc9
PH
237 if mode == 2:
238 # variable = "humidity"
239 unit = "%"
240 data = bme280.get_humidity()
71dc2962
K
241 display_text(variables[mode], data, unit)
242
be4d0fc9
PH
243 if mode == 3:
244 # variable = "light"
245 unit = "Lux"
246 if proximity < 10:
247 data = ltr559.get_lux()
248 else:
249 data = 1
71dc2962
K
250 display_text(variables[mode], data, unit)
251
be4d0fc9
PH
252 if mode == 4:
253 # variable = "oxidised"
254 unit = "kO"
255 data = gas.read_all()
256 data = data.oxidising / 1000
71dc2962 257 display_text(variables[mode], data, unit)
71dc2962 258
be4d0fc9
PH
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)
71dc2962 265
be4d0fc9
PH
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)
71dc2962 272
be4d0fc9
PH
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 # Exit cleanly
343 except KeyboardInterrupt:
344 sys.exit(0)
345
346
347if __name__ == "__main__":
348 main()