Experimental fix to communicate Py version reqs for #78
[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
6a89566f 15from pms5003 import PMS5003, ReadTimeoutError as pmsReadTimeoutError, SerialTimeoutError
71dc2962
K
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
c161c52b 29logging.info("""combined.py - Displays readings from all of Enviro plus' sensors
71dc2962
K
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]
c161c52b 175 x = x_offset + ((WIDTH // column_count) * (i // row_count))
be4d0fc9 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:
6a89566f 279 logging.warning("Failed to read PMS5003")
be4d0fc9
PH
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:
6a89566f 290 logging.warning("Failed to read PMS5003")
be4d0fc9
PH
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:
6a89566f 301 logging.warning("Failed to read PMS5003")
be4d0fc9
PH
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()
6a89566f
PM
334 except (SerialTimeoutError, pmsReadTimeoutError):
335 logging.warning("Failed to read PMS5003")
be4d0fc9
PH
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()