Skip to content

Commit 71dc296

Browse files
author
Kostadin
committed
Added a new combined mode example
This is a modification of all-in-one.py. It adds another mode where all EnviroPlus and PMS5003 sensor readings are combined on one screen. Each variable that is displayed can have custom warning limits assigned which change the color of the text according to a predefined RGB palette. It allows for a quick glance of all sensor readings at once in order to jugde if everything is OK in the air or to quickly pinpoint a sensor reading that requires attention. In addition, the new combined mode saves each reading as soon as it is received for graphing later. As in all-in-one.py, moving your finger close to the proximity sensor switches the mode.
1 parent bca0449 commit 71dc296

File tree

1 file changed

+345
-0
lines changed

1 file changed

+345
-0
lines changed

examples/combined.py

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
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+
41+
# Create ST7735 LCD display class
42+
st7735 = 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
52+
st7735.begin()
53+
54+
WIDTH = st7735.width
55+
HEIGHT = st7735.height
56+
57+
# Set up canvas and font
58+
img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
59+
draw = ImageDraw.Draw(img)
60+
path = os.path.dirname(os.path.realpath(__file__))
61+
font = ImageFont.truetype(path + "/fonts/Asap/Asap-Bold.ttf", 20)
62+
smallfont = ImageFont.truetype(path + "/fonts/Asap/Asap-Bold.ttf", 10)
63+
x_offset = 2
64+
y_offset = 2
65+
66+
message = ""
67+
68+
# The position of the top bar
69+
top_pos = 25
70+
71+
# Create a values dict to store the data
72+
variables = ["temperature",
73+
"pressure",
74+
"humidity",
75+
"light",
76+
"oxidised",
77+
"reduced",
78+
"nh3",
79+
"pm1",
80+
"pm25",
81+
"pm10"]
82+
83+
units = ["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.
107+
limits = [[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
119+
palette = [(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+
125+
values = {}
126+
127+
128+
# Displays data and text on the 0.96" LCD
129+
def 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
155+
def 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
165+
def 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
187+
def 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
195+
factor = 1.95
196+
197+
cpu_temps = [get_cpu_temperature()] * 5
198+
199+
delay = 0.5 # Debounce the proximity tap
200+
mode = 10 # The starting mode
201+
last_page = 0
202+
light = 1
203+
204+
for v in variables:
205+
values[v] = [1] * WIDTH
206+
207+
# The main loop
208+
try:
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
344+
except KeyboardInterrupt:
345+
sys.exit(0)

0 commit comments

Comments
 (0)