b417ccf1f74e0f81bc4a73a38fa9dd5848a94987
8 # Transitional fix for breaking change in LTR559
9 from ltr559
import LTR559
14 from bme280
import BME280
15 from pms5003
import PMS5003
, ReadTimeoutError
as pmsReadTimeoutError
, SerialTimeoutError
16 from enviroplus
import gas
17 from subprocess
import PIPE
, Popen
19 from PIL
import ImageDraw
20 from PIL
import ImageFont
21 from fonts
.ttf
import RobotoMedium
as UserFont
25 format
='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
27 datefmt
='%Y-%m-%d %H:%M:%S')
29 logging
.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors
35 # BME280 temperature/pressure/humidity sensor
38 # PMS5003 particulate sensor
42 # Create ST7735 LCD display class
43 st7735
= ST7735
.ST7735(
56 HEIGHT
= st7735
.height
58 # Set up canvas and font
59 img
= Image
.new('RGB', (WIDTH
, HEIGHT
), color
=(0, 0, 0))
60 draw
= ImageDraw
.Draw(img
)
63 font
= ImageFont
.truetype(UserFont
, font_size_large
)
64 smallfont
= ImageFont
.truetype(UserFont
, font_size_small
)
70 # The position of the top bar
73 # Create a values dict to store the data
74 variables
= ["temperature",
96 # Define your own warning limits
97 # The limits definition follows the order of the variables array
98 # Example limits explanation for temperature:
100 # [-273.15 .. 4] -> Dangerously Low
102 # (18 .. 28] -> Normal
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.
109 limits
= [[4, 18, 28, 35],
110 [250, 650, 1013.25, 1015],
112 [-1, -1, 30000, 100000],
120 # RGB palette for values on the combined screen
121 palette
= [(0, 0, 255), # Dangerously Low
123 (0, 255, 0), # Normal
124 (255, 255, 0), # High
125 (255, 0, 0)] # Dangerously High
130 # Displays data and text on the 0.96" LCD
131 def 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
135 vmin
= min(values
[variable
])
136 vmax
= max(values
[variable
])
137 colours
= [(v
- vmin
+ 1) / (vmax
- vmin
+ 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
, 1.0, 1.0)]
146 # Draw a 1-pixel wide rectangle of colour
147 draw
.rectangle((i
, top_pos
, i
+ 1, HEIGHT
), (r
, g
, b
))
148 # Draw a line graph in black
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))
151 # Write the text at the top in black
152 draw
.text((0, 0), message
, font
=font
, fill
=(0, 0, 0))
156 # Saves the data to be used in the graphs later and prints to the log
157 def save_data(idx
, data
):
158 variable
= variables
[idx
]
159 # Maintain length of list
160 values
[variable
] = values
[variable
][1:] + [data
]
162 message
= "{}: {:.1f} {}".format(variable
[:4], data
, unit
)
163 logging
.info(message
)
166 # Displays all the text on the 0.96" LCD
167 def display_everything():
168 draw
.rectangle((0, 0, WIDTH
, HEIGHT
), (0, 0, 0))
170 row_count
= (len(variables
) / column_count
)
171 for i
in range(len(variables
)):
172 variable
= variables
[i
]
173 data_value
= values
[variable
][-1]
175 x
= x_offset
+ ((WIDTH
/ column_count
) * (i
/ row_count
))
176 y
= y_offset
+ ((HEIGHT
/ row_count
) * (i
% row_count
))
177 message
= "{}: {:.1f} {}".format(variable
[:4], data_value
, unit
)
180 for j
in range(len(lim
)):
181 if data_value
> lim
[j
]:
183 draw
.text((x
, y
), message
, font
=smallfont
, fill
=rgb
)
187 # Get the temperature of the CPU for compensation
188 def 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("'")])
195 # Tuning factor for compensation. Decrease this number to adjust the
196 # temperature down, and increase to adjust up
199 cpu_temps
= [get_cpu_temperature()] * 5
201 delay
= 0.5 # Debounce the proximity tap
202 mode
= 10 # The starting mode
206 values
[v
] = [1] * WIDTH
211 proximity
= ltr559
.get_proximity()
213 # If the proximity crosses the threshold, toggle the mode
214 if proximity
> 1500 and time
.time() - last_page
> delay
:
216 mode
%= (len(variables
) + 1)
217 last_page
= time
.time()
219 # One mode for each variable
221 # variable = "temperature"
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
)
232 # variable = "pressure"
234 data
= bme280
.get_pressure()
235 display_text(variables
[mode
], data
, unit
)
238 # variable = "humidity"
240 data
= bme280
.get_humidity()
241 display_text(variables
[mode
], data
, unit
)
247 data
= ltr559
.get_lux()
250 display_text(variables
[mode
], data
, unit
)
253 # variable = "oxidised"
255 data
= gas
.read_all()
256 data
= data
.oxidising
/ 1000
257 display_text(variables
[mode
], data
, unit
)
260 # variable = "reduced"
262 data
= gas
.read_all()
263 data
= data
.reducing
/ 1000
264 display_text(variables
[mode
], data
, unit
)
269 data
= gas
.read_all()
270 data
= data
.nh3
/ 1000
271 display_text(variables
[mode
], data
, unit
)
277 data
= pms5003
.read()
278 except pmsReadTimeoutError
:
279 logging
.warning("Failed to read PMS5003")
281 data
= float(data
.pm_ug_per_m3(1.0))
282 display_text(variables
[mode
], data
, unit
)
288 data
= pms5003
.read()
289 except pmsReadTimeoutError
:
290 logging
.warning("Failed to read PMS5003")
292 data
= float(data
.pm_ug_per_m3(2.5))
293 display_text(variables
[mode
], data
, unit
)
299 data
= pms5003
.read()
300 except pmsReadTimeoutError
:
301 logging
.warning("Failed to read PMS5003")
303 data
= float(data
.pm_ug_per_m3(10))
304 display_text(variables
[mode
], data
, unit
)
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
)
315 raw_data
= bme280
.get_pressure()
316 save_data(1, raw_data
)
318 raw_data
= bme280
.get_humidity()
319 save_data(2, raw_data
)
321 raw_data
= ltr559
.get_lux()
324 save_data(3, raw_data
)
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)
333 pms_data
= pms5003
.read()
334 except (SerialTimeoutError
, pmsReadTimeoutError
):
335 logging
.warning("Failed to read PMS5003")
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)))
343 except KeyboardInterrupt:
347 if __name__
== "__main__":