9 # Transitional fix for breaking change in LTR559
10 from ltr559
import LTR559
15 from bme280
import BME280
16 from pms5003
import PMS5003
, ReadTimeoutError
as pmsReadTimeoutError
17 from enviroplus
import gas
18 from subprocess
import PIPE
, Popen
20 from PIL
import ImageDraw
21 from PIL
import ImageFont
22 from fonts
.ttf
import RobotoMedium
as UserFont
26 format
='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
28 datefmt
='%Y-%m-%d %H:%M:%S')
30 logging
.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors
36 # BME280 temperature/pressure/humidity sensor
39 # PMS5003 particulate sensor
43 # Create ST7735 LCD display class
44 st7735
= ST7735
.ST7735(
57 HEIGHT
= st7735
.height
59 # Set up canvas and font
60 img
= Image
.new('RGB', (WIDTH
, HEIGHT
), color
=(0, 0, 0))
61 draw
= ImageDraw
.Draw(img
)
64 font
= ImageFont
.truetype(UserFont
, font_size_large
)
65 smallfont
= ImageFont
.truetype(UserFont
, font_size_small
)
71 # The position of the top bar
74 # Create a values dict to store the data
75 variables
= ["temperature",
97 # Define your own warning limits
98 # The limits definition follows the order of the variables array
99 # Example limits explanation for temperature:
101 # [-273.15 .. 4] -> Dangerously Low
103 # (18 .. 28] -> Normal
105 # (35 .. MAX] -> Dangerously High
106 # DISCLAIMER: The limits provided here are just examples and come
107 # with NO WARRANTY. The authors of this example code claim
108 # NO RESPONSIBILITY if reliance on the following values or this
109 # code in general leads to ANY DAMAGES or DEATH.
110 limits
= [[4,18,28,35],
111 [250,650,1013.25,1015],
113 [-1,-1,30000,100000],
121 # RGB palette for values on the combined screen
122 palette
= [(0,0,255), # Dangerously Low
126 (255,0,0)] # Dangerously High
131 # Displays data and text on the 0.96" LCD
132 def display_text(variable
, data
, unit
):
133 # Maintain length of list
134 values
[variable
] = values
[variable
][1:] + [data
]
135 # Scale the values for the variable between 0 and 1
136 colours
= [(v
- min(values
[variable
]) + 1) / (max(values
[variable
])
137 - min(values
[variable
]) + 1) for v
in values
[variable
]]
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
145 r
, g
, b
= [int(x
* 255.0) for x
in colorsys
.hsv_to_rgb(colour
,
147 # Draw a 1-pixel wide rectangle of colour
148 draw
.rectangle((i
, top_pos
, i
+1, HEIGHT
), (r
, g
, b
))
149 # Draw a line graph in black
150 line_y
= HEIGHT
- (top_pos
+ (colours
[i
] * (HEIGHT
- top_pos
)))\
152 draw
.rectangle((i
, line_y
, i
+1, line_y
+1), (0, 0, 0))
153 # Write the text at the top in black
154 draw
.text((0, 0), message
, font
=font
, fill
=(0, 0, 0))
157 # Saves the data to be used in the graphs later and prints to the log
158 def save_data(idx
, data
):
159 variable
= variables
[idx
]
160 # Maintain length of list
161 values
[variable
] = values
[variable
][1:] + [data
]
163 message
= "{}: {:.1f} {}".format(variable
[:4], data
, unit
)
164 logging
.info(message
)
167 # Displays all the text on the 0.96" LCD
168 def display_everything():
169 draw
.rectangle((0, 0, WIDTH
, HEIGHT
), (0, 0, 0))
171 row_count
= (len(variables
)/column_count
)
172 for i
in range(len(variables
)):
173 variable
= variables
[i
]
174 data_value
= values
[variable
][-1]
176 x
= x_offset
+ ((WIDTH
/column_count
) * (i
/ row_count
))
177 y
= y_offset
+ ((HEIGHT
/row_count
) * (i
% row_count
))
178 message
= "{}: {:.1f} {}".format(variable
[:4], data_value
, unit
)
181 for j
in range(len(lim
)):
182 if data_value
> lim
[j
]:
184 draw
.text((x
, y
), message
, font
=smallfont
, fill
=rgb
)
189 # Get the temperature of the CPU for compensation
190 def get_cpu_temperature():
191 process
= Popen(['vcgencmd', 'measure_temp'], stdout
=PIPE
, universal_newlines
=True)
192 output
, _error
= process
.communicate()
193 return float(output
[output
.index('=') + 1:output
.rindex("'")])
196 # Tuning factor for compensation. Decrease this number to adjust the
197 # temperature down, and increase to adjust up
200 cpu_temps
= [get_cpu_temperature()] * 5
202 delay
= 0.5 # Debounce the proximity tap
203 mode
= 10 # The starting mode
208 values
[v
] = [1] * WIDTH
213 proximity
= ltr559
.get_proximity()
215 # If the proximity crosses the threshold, toggle the mode
216 if proximity
> 1500 and time
.time() - last_page
> delay
:
218 mode
%= (len(variables
)+1)
219 last_page
= time
.time()
221 # One mode for each variable
223 # variable = "temperature"
225 cpu_temp
= get_cpu_temperature()
226 # Smooth out with some averaging to decrease jitter
227 cpu_temps
= cpu_temps
[1:] + [cpu_temp
]
228 avg_cpu_temp
= sum(cpu_temps
) / float(len(cpu_temps
))
229 raw_temp
= bme280
.get_temperature()
230 data
= raw_temp
- ((avg_cpu_temp
- raw_temp
) / factor
)
231 display_text(variables
[mode
], data
, unit
)
234 # variable = "pressure"
236 data
= bme280
.get_pressure()
237 display_text(variables
[mode
], data
, unit
)
240 # variable = "humidity"
242 data
= bme280
.get_humidity()
243 display_text(variables
[mode
], data
, unit
)
249 data
= ltr559
.get_lux()
252 display_text(variables
[mode
], data
, unit
)
255 # variable = "oxidised"
257 data
= gas
.read_all()
258 data
= data
.oxidising
/ 1000
259 display_text(variables
[mode
], data
, unit
)
262 # variable = "reduced"
264 data
= gas
.read_all()
265 data
= data
.reducing
/ 1000
266 display_text(variables
[mode
], data
, unit
)
271 data
= gas
.read_all()
272 data
= data
.nh3
/ 1000
273 display_text(variables
[mode
], data
, unit
)
279 data
= pms5003
.read()
280 except pmsReadTimeoutError
:
281 logging
.warn("Failed to read PMS5003")
283 data
= float(data
.pm_ug_per_m3(1.0))
284 display_text(variables
[mode
], data
, unit
)
290 data
= pms5003
.read()
291 except pmsReadTimeoutError
:
292 logging
.warn("Failed to read PMS5003")
294 data
= float(data
.pm_ug_per_m3(2.5))
295 display_text(variables
[mode
], data
, unit
)
301 data
= pms5003
.read()
302 except pmsReadTimeoutError
:
303 logging
.warn("Failed to read PMS5003")
305 data
= float(data
.pm_ug_per_m3(10))
306 display_text(variables
[mode
], data
, unit
)
308 # Everything on one screen
309 cpu_temp
= get_cpu_temperature()
310 # Smooth out with some averaging to decrease jitter
311 cpu_temps
= cpu_temps
[1:] + [cpu_temp
]
312 avg_cpu_temp
= sum(cpu_temps
) / float(len(cpu_temps
))
313 raw_temp
= bme280
.get_temperature()
314 raw_data
= raw_temp
- ((avg_cpu_temp
- raw_temp
) / factor
)
315 save_data(0, raw_data
)
317 raw_data
= bme280
.get_pressure()
318 save_data(1, raw_data
)
320 raw_data
= bme280
.get_humidity()
321 save_data(2, raw_data
)
323 raw_data
= ltr559
.get_lux()
326 save_data(3, raw_data
)
328 gas_data
= gas
.read_all()
329 save_data(4, gas_data
.oxidising
/ 1000)
330 save_data(5, gas_data
.reducing
/ 1000)
331 save_data(6, gas_data
.nh3
/ 1000)
335 pms_data
= pms5003
.read()
336 except pmsReadTimeoutError
:
337 logging
.warn("Failed to read PMS5003")
339 save_data(7, float(pms_data
.pm_ug_per_m3(1.0)))
340 save_data(8, float(pms_data
.pm_ug_per_m3(2.5)))
341 save_data(9, float(pms_data
.pm_ug_per_m3(10)))
347 except KeyboardInterrupt: