Fix launch bug in all-in-one-no-pm.py
[EVA-2020-02-2.git] / examples / all-in-one.py
1 #!/usr/bin/env python3
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 from fonts.ttf import RobotoMedium as UserFont
23 import logging
24
25 logging.basicConfig(
26 format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
27 level=logging.INFO,
28 datefmt='%Y-%m-%d %H:%M:%S')
29
30 logging.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors
31
32 Press Ctrl+C to exit!
33
34 """)
35
36 # BME280 temperature/pressure/humidity sensor
37 bme280 = BME280()
38
39 # PMS5003 particulate sensor
40 pms5003 = PMS5003()
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 font_size = 20
62 font = ImageFont.truetype(UserFont, font_size)
63
64 message = ""
65
66 # The position of the top bar
67 top_pos = 25
68
69
70 # Displays data and text on the 0.96" LCD
71 def display_text(variable, data, unit):
72 # Maintain length of list
73 values[variable] = values[variable][1:] + [data]
74 # Scale the values for the variable between 0 and 1
75 colours = [(v - min(values[variable]) + 1) / (max(values[variable])
76 - min(values[variable]) + 1) for v in values[variable]]
77 # Format the variable name and value
78 message = "{}: {:.1f} {}".format(variable[:4], data, unit)
79 logging.info(message)
80 draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255))
81 for i in range(len(colours)):
82 # Convert the values to colours from red to blue
83 colour = (1.0 - colours[i]) * 0.6
84 r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour,
85 1.0, 1.0)]
86 # Draw a 1-pixel wide rectangle of colour
87 draw.rectangle((i, top_pos, i+1, HEIGHT), (r, g, b))
88 # Draw a line graph in black
89 line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos)))\
90 + top_pos
91 draw.rectangle((i, line_y, i+1, line_y+1), (0, 0, 0))
92 # Write the text at the top in black
93 draw.text((0, 0), message, font=font, fill=(0, 0, 0))
94 st7735.display(img)
95
96
97 # Get the temperature of the CPU for compensation
98 def get_cpu_temperature():
99 process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True)
100 output, _error = process.communicate()
101 return float(output[output.index('=') + 1:output.rindex("'")])
102
103
104 # Tuning factor for compensation. Decrease this number to adjust the
105 # temperature down, and increase to adjust up
106 factor = 2.25
107
108 cpu_temps = [get_cpu_temperature()] * 5
109
110 delay = 0.5 # Debounce the proximity tap
111 mode = 0 # The starting mode
112 last_page = 0
113 light = 1
114
115 # Create a values dict to store the data
116 variables = ["temperature",
117 "pressure",
118 "humidity",
119 "light",
120 "oxidised",
121 "reduced",
122 "nh3",
123 "pm1",
124 "pm25",
125 "pm10"]
126
127 values = {}
128
129 for v in variables:
130 values[v] = [1] * WIDTH
131
132 # The main loop
133 try:
134 while True:
135 proximity = ltr559.get_proximity()
136
137 # If the proximity crosses the threshold, toggle the mode
138 if proximity > 1500 and time.time() - last_page > delay:
139 mode += 1
140 mode %= len(variables)
141 last_page = time.time()
142
143 # One mode for each variable
144 if mode == 0:
145 # variable = "temperature"
146 unit = "C"
147 cpu_temp = get_cpu_temperature()
148 # Smooth out with some averaging to decrease jitter
149 cpu_temps = cpu_temps[1:] + [cpu_temp]
150 avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps))
151 raw_temp = bme280.get_temperature()
152 data = raw_temp - ((avg_cpu_temp - raw_temp) / factor)
153 display_text(variables[mode], data, unit)
154
155 if mode == 1:
156 # variable = "pressure"
157 unit = "hPa"
158 data = bme280.get_pressure()
159 display_text(variables[mode], data, unit)
160
161 if mode == 2:
162 # variable = "humidity"
163 unit = "%"
164 data = bme280.get_humidity()
165 display_text(variables[mode], data, unit)
166
167 if mode == 3:
168 # variable = "light"
169 unit = "Lux"
170 if proximity < 10:
171 data = ltr559.get_lux()
172 else:
173 data = 1
174 display_text(variables[mode], data, unit)
175
176 if mode == 4:
177 # variable = "oxidised"
178 unit = "kO"
179 data = gas.read_all()
180 data = data.oxidising / 1000
181 display_text(variables[mode], data, unit)
182
183 if mode == 5:
184 # variable = "reduced"
185 unit = "kO"
186 data = gas.read_all()
187 data = data.reducing / 1000
188 display_text(variables[mode], data, unit)
189
190 if mode == 6:
191 # variable = "nh3"
192 unit = "kO"
193 data = gas.read_all()
194 data = data.nh3 / 1000
195 display_text(variables[mode], data, unit)
196
197 if mode == 7:
198 # variable = "pm1"
199 unit = "ug/m3"
200 try:
201 data = pms5003.read()
202 except pmsReadTimeoutError:
203 logging.warn("Failed to read PMS5003")
204 else:
205 data = float(data.pm_ug_per_m3(1.0))
206 display_text(variables[mode], data, unit)
207
208 if mode == 8:
209 # variable = "pm25"
210 unit = "ug/m3"
211 try:
212 data = pms5003.read()
213 except pmsReadTimeoutError:
214 logging.warn("Failed to read PMS5003")
215 else:
216 data = float(data.pm_ug_per_m3(2.5))
217 display_text(variables[mode], data, unit)
218
219 if mode == 9:
220 # variable = "pm10"
221 unit = "ug/m3"
222 try:
223 data = pms5003.read()
224 except pmsReadTimeoutError:
225 logging.warn("Failed to read PMS5003")
226 else:
227 data = float(data.pm_ug_per_m3(10))
228 display_text(variables[mode], data, unit)
229
230 # Exit cleanly
231 except KeyboardInterrupt:
232 sys.exit(0)