Commit | Line | Data |
---|---|---|
f0613e57 SBS |
1 | #!/usr/bin/env python3 |
2 | ||
3 | import time | |
4 | import colorsys | |
5 | import os | |
6 | import sys | |
7 | import ST7735 | |
8 | import schedule # https://pypi.org/project/schedule/ via https://stackoverflow.com/a/16786600 | |
9 | import datetime # http://stackoverflow.com/questions/2150739/ddg#28147286 | |
10 | try: | |
11 | # Transitional fix for breaking change in LTR559 | |
12 | from ltr559 import LTR559 | |
13 | ltr559 = LTR559() | |
14 | except ImportError: | |
15 | import ltr559 | |
16 | ||
17 | from bme280 import BME280 | |
18 | from enviroplus import gas | |
19 | from subprocess import PIPE, Popen | |
20 | from PIL import Image | |
21 | from PIL import ImageDraw | |
22 | from PIL import ImageFont | |
23 | from fonts.ttf import RobotoMedium as UserFont | |
24 | import logging | |
25 | import numpy as np | |
26 | ||
27 | logging.basicConfig( | |
28 | format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s', | |
29 | level=logging.INFO, | |
30 | datefmt='%Y-%m-%d %H:%M:%S') | |
31 | ||
32 | logging.info("""all-in-one.py - Displays readings from all of Enviro plus' sensors | |
33 | Press Ctrl+C to exit! | |
34 | """) | |
35 | ||
36 | # Script Constants | |
37 | varLenBufferTTL = int((2*24*60*60)*10**9) # time-to-live in nanoseconds | |
38 | ||
39 | # BME280 temperature/pressure/humidity sensor | |
40 | bme280 = BME280() | |
41 | ||
42 | # Create ST7735 LCD display class | |
43 | st7735 = ST7735.ST7735( | |
44 | port=0, | |
45 | cs=1, | |
46 | dc=9, | |
47 | backlight=12, | |
4cbbc360 SBS |
48 | rotation=270, |
49 | #rotation=90, # flip upside down wrt enviro+ default orientation | |
f0613e57 SBS |
50 | spi_speed_hz=10000000 |
51 | ) | |
52 | ||
53 | # Initialize display | |
54 | st7735.begin() | |
55 | ||
56 | WIDTH = st7735.width | |
57 | HEIGHT = st7735.height | |
58 | ||
59 | # Set up canvas and font | |
60 | img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0)) | |
61 | draw = ImageDraw.Draw(img) | |
62 | path = os.path.dirname(os.path.realpath(__file__)) | |
63 | font_size = 20 | |
64 | font = ImageFont.truetype(UserFont, font_size) | |
65 | font2_size = 15 | |
66 | font2 = ImageFont.truetype(UserFont, font2_size) | |
67 | ||
68 | message = "" | |
69 | ||
70 | # The position of the top bar | |
71 | top_pos = 25 | |
72 | ||
73 | ||
74 | # Displays data and text on the 0.96" LCD | |
75 | def display_text(variable, data, unit): | |
76 | # Maintain length of list | |
77 | values[variable] = values[variable][1:] + [data] | |
78 | # Scale the values for the variable between 0 and 1 | |
79 | vmin = min(values[variable]) | |
80 | vmax = max(values[variable]) | |
81 | colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]] | |
82 | # Format the variable name and value | |
83 | message = "{}: {:.1f} {}".format(variable[:4], data, unit) | |
84 | logging.info(message) | |
85 | draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) | |
86 | for i in range(len(colours)): | |
87 | # Convert the values to colours from red to blue | |
88 | colour = (1.0 - colours[i]) * 0.6 | |
89 | r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)] | |
90 | # Draw a 1-pixel wide rectangle of colour | |
91 | draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b)) | |
92 | # Draw a line graph in black | |
93 | line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos | |
94 | draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0)) | |
95 | # Write the text at the top in black | |
96 | draw.text((0, 0), message, font=font, fill=(0, 0, 0)) | |
97 | st7735.display(img) | |
98 | ||
99 | # Displays data and text on the 0.96" LCD | |
100 | def display_text2(variable, data, unit, values): | |
101 | # Scale the values for the variable between 0 and 1 | |
eba03e3f | 102 | #print('DEBUG:len(values[' + str(variable) + ']):' + str(len(values[variable]))) |
f0613e57 SBS |
103 | #print('DEBUG:values[' + str(variable) + ']:' + str(values[variable])) |
104 | vmin = min(values[variable]) | |
105 | vmax = max(values[variable]) | |
106 | colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]] | |
107 | # Format the variable name and value | |
108 | message = "{}: {:.1f} {}".format(variable[:4], data, unit) | |
109 | #message = "{}: {:.1f} {}".format(variable[:4], values[variable][-1], unit) | |
110 | logging.info(message) | |
111 | draw.rectangle((0, 0, WIDTH, HEIGHT), (255, 255, 255)) | |
112 | for i in range(len(colours)): | |
113 | # Convert the values to colours from red to blue | |
114 | colour = (1.0 - colours[i]) * 0.6 | |
115 | r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)] | |
116 | # Draw a 1-pixel wide rectangle of colour | |
117 | draw.rectangle((i, top_pos, i + 1, HEIGHT), (r, g, b)) | |
118 | # Draw a line graph in black | |
119 | line_y = HEIGHT - (top_pos + (colours[i] * (HEIGHT - top_pos))) + top_pos | |
120 | draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0)) | |
121 | # Write the text at the top in black | |
122 | draw.text((0, 0), message, font=font, fill=(0, 0, 0)) | |
123 | # Write text (test) | |
124 | maxMsg = "MAX:{:.1f}".format(vmax) | |
125 | durMsg = "HR:{:.1f}".format(span_time_h) | |
126 | minMsg = "MIN:{:.1f}".format(vmin) | |
127 | maxMsg_y = int( ((HEIGHT - top_pos)/(HEIGHT))*(HEIGHT*(1/4)) + top_pos - (font2_size/2) ) | |
128 | durMsg_y = int( ((HEIGHT - top_pos)/(HEIGHT))*(HEIGHT*(2/4)) + top_pos - (font2_size/2) ) | |
129 | minMsg_y = int( ((HEIGHT - top_pos)/(HEIGHT))*(HEIGHT*(3/4)) + top_pos - (font2_size/2) ) | |
130 | maxMsg_x = int( WIDTH*(3/100) ) | |
131 | durMsg_x = int( WIDTH*(3/100) ) | |
132 | minMsg_x = int( WIDTH*(3/100) ) | |
133 | draw.text((maxMsg_x, maxMsg_y), maxMsg, font=font2, fill=(0, 0, 0)) | |
134 | draw.text((durMsg_x, durMsg_y), durMsg, font=font2, fill=(0, 0, 0)) | |
135 | draw.text((minMsg_x, minMsg_y), minMsg, font=font2, fill=(0, 0, 0)) | |
136 | st7735.display(img) | |
137 | ||
138 | ||
139 | # Get the temperature of the CPU for compensation | |
140 | def get_cpu_temperature(): | |
141 | process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE, universal_newlines=True) | |
142 | output, _error = process.communicate() | |
143 | return float(output[output.index('=') + 1:output.rindex("'")]) | |
144 | ||
f4b2db2d SBS |
145 | def rel_to_abs(T,P,RH): |
146 | """Returns absolute humidity given relative humidity. | |
147 | ||
148 | Inputs: | |
149 | -------- | |
150 | T : float | |
151 | Absolute temperature in units Kelvin (K). | |
152 | P : float | |
153 | Total pressure in units Pascals (Pa). | |
154 | RH : float | |
155 | Relative humidity in units percent (%). | |
156 | ||
157 | Output: | |
158 | -------- | |
159 | absolute_humidity : float | |
160 | Absolute humidity in units [kg water vapor / kg dry air]. | |
161 | ||
162 | References: | |
163 | -------- | |
164 | 1. Sonntag, D. "Advancements in the field of hygrometry". 1994. https://doi.org/10.1127/metz/3/1994/51 | |
165 | 2. Green, D. "Perry's Chemical Engineers' Handbook" (8th Edition). Page "12-4". McGraw-Hill Professional Publishing. 2007. | |
166 | ||
167 | Version: 0.0.1 | |
168 | Author: Steven Baltakatei Sandoval | |
169 | License: GPLv3+ | |
170 | """ | |
171 | ||
172 | import math; | |
173 | ||
174 | # Check input types | |
175 | T = float(T); | |
176 | P = float(P); | |
177 | RH = float(RH); | |
178 | ||
179 | #debug | |
180 | # print('DEBUG:Input Temperature (K) :' + str(T)); | |
181 | # print('DEBUG:Input Pressure (Pa) :' + str(P)); | |
182 | # print('DEBUG:Input Rel. Humidity (%) :' + str(RH)); | |
183 | ||
184 | # Set constants and initial conversions | |
185 | epsilon = 0.62198 # (molar mass of water vapor) / (molar mass of dry air) | |
186 | t = T - 273.15; # Celsius from Kelvin | |
187 | P_hpa = P / 100; # hectoPascals (hPa) from Pascals (Pa) | |
188 | ||
189 | # Calculate e_w(T), saturation vapor pressure of water in a pure phase, in Pascals | |
190 | ln_e_w = -6096*T**-1 + 21.2409642 - 2.711193*10**-2*T + 1.673952*10**-5*T**2 + 2.433502*math.log(T); # Sonntag-1994 eq 7; e_w in Pascals | |
191 | e_w = math.exp(ln_e_w); | |
192 | e_w_hpa = e_w / 100; # also save e_w in hectoPascals (hPa) | |
193 | # print('DEBUG:ln_e_w:' + str(ln_e_w)); # debug | |
194 | # print('DEBUG:e_w:' + str(e_w)); # debug | |
195 | ||
196 | # Calculate f_w(P,T), enhancement factor for water | |
197 | f_w = 1 + (10**-4*e_w_hpa)/(273 + t)*(((38 + 173*math.exp(-t/43))*(1 - (e_w_hpa / P_hpa))) + ((6.39 + 4.28*math.exp(-t / 107))*((P_hpa / e_w_hpa) - 1))); # Sonntag-1994 eq 22. | |
198 | # print('DEBUG:f_w:' + str(f_w)); # debug | |
199 | ||
200 | # Calculate e_prime_w(P,T), saturation vapor pressure of water in air-water mixture, in Pascals | |
201 | e_prime_w = f_w * e_w; # Sonntag-1994 eq 18 | |
202 | # print('DEBUG:e_prime_w:' + str(e_prime_w)); # debug | |
203 | ||
204 | # Calculate e_prime, vapor pressure of water in air, in Pascals | |
205 | e_prime = (RH / 100) * e_prime_w; | |
206 | # print('DEBUG:e_prime:' + str(e_prime)); # debug | |
207 | ||
208 | # Calculate r, the absolute humidity, in [kg water vapor / kg dry air] | |
209 | r = (epsilon * e_prime) / (P - e_prime); | |
210 | # print('DEBUG:r:' + str(r)); # debug | |
211 | ||
212 | return float(r); | |
213 | ||
3905a56d SBS |
214 | def rel_to_dpt(T,P,RH): |
215 | """Returns dew point temperature given relative humidity. | |
216 | ||
217 | Inputs: | |
218 | -------- | |
219 | T : float | |
220 | Absolute temperature in units Kelvin (K). | |
221 | P : float | |
222 | Total pressure in units Pascals (Pa). | |
223 | RH : float | |
224 | Relative humidity in units percent (%). | |
225 | ||
226 | Output: | |
227 | -------- | |
228 | T_d : float | |
229 | Dew point temperature in units Kelvin (K). | |
230 | ||
231 | References: | |
232 | -------- | |
233 | 1. Sonntag, D. "Advancements in the field of hygrometry". 1994. https://doi.org/10.1127/metz/3/1994/51 | |
234 | 2. Green, D. "Perry's Chemical Engineers' Handbook" (8th Edition). Page "12-4". McGraw-Hill Professional Publishing. 2007. | |
235 | ||
236 | Version: 0.0.1 | |
237 | Author: Steven Baltakatei Sandoval | |
238 | License: GPLv3+ | |
239 | """ | |
240 | ||
241 | import math; | |
242 | ||
243 | # Check input types | |
244 | T = float(T); | |
245 | P = float(P); | |
246 | RH = float(RH); | |
247 | ||
248 | #debug | |
249 | # print('DEBUG:Input Temperature (K) :' + str(T)); | |
250 | # print('DEBUG:Input Pressure (Pa) :' + str(P)); | |
251 | # print('DEBUG:Input Rel. Humidity (%) :' + str(RH)); | |
252 | ||
253 | # Set constants and initial conversions | |
254 | epsilon = 0.62198 # (molar mass of water vapor) / (molar mass of dry air) | |
255 | t = T - 273.15; # Celsius from Kelvin | |
256 | P_hpa = P / 100; # hectoPascals (hPa) from Pascals (Pa) | |
257 | ||
258 | # Calculate e_w(T), saturation vapor pressure of water in a pure phase, in Pascals | |
259 | ln_e_w = -6096*T**-1 + 21.2409642 - 2.711193*10**-2*T + 1.673952*10**-5*T**2 + 2.433502*math.log(T); # Sonntag-1994 eq 7; e_w in Pascals | |
260 | e_w = math.exp(ln_e_w); | |
261 | e_w_hpa = e_w / 100; # also save e_w in hectoPascals (hPa) | |
262 | # print('DEBUG:ln_e_w:' + str(ln_e_w)); # debug | |
263 | # print('DEBUG:e_w:' + str(e_w)); # debug | |
264 | ||
265 | # Calculate f_w(P,T), enhancement factor for water | |
266 | f_w = 1 + (10**-4*e_w_hpa)/(273 + t)*(((38 + 173*math.exp(-t/43))*(1 - (e_w_hpa / P_hpa))) + ((6.39 + 4.28*math.exp(-t / 107))*((P_hpa / e_w_hpa) - 1))); # Sonntag-1994 eq 22. | |
267 | # print('DEBUG:f_w:' + str(f_w)); # debug | |
268 | ||
269 | # Calculate e_prime_w(P,T), saturation vapor pressure of water in air-water mixture, in Pascals | |
270 | e_prime_w = f_w * e_w; # Sonntag-1994 eq 18 | |
271 | # print('DEBUG:e_prime_w:' + str(e_prime_w)); # debug | |
272 | ||
273 | # Calculate e_prime, vapor pressure of water in air, in Pascals | |
274 | e_prime = (RH / 100) * e_prime_w; | |
275 | # print('DEBUG:e_prime:' + str(e_prime)); # debug | |
276 | ||
277 | n = 0; repeat_flag = True; | |
278 | while repeat_flag == True: | |
279 | # print('DEBUG:n:' + str(n)); # debug | |
280 | ||
281 | # Calculate f_w_td, the enhancement factor for water at dew point temperature. | |
282 | if n == 0: | |
283 | f = 1.0016 + 3.15*10**-6*P_hpa - (0.074 / P_hpa); # Sonntag-1994 eq 24 | |
284 | f_w_td = f; # initial approximation | |
285 | elif n > 0: | |
286 | t_d_prev = float(t_d); # save previous t_d value for later comparison | |
287 | f_w_td = 1 + (10**-4*e_w_hpa)/(273 + t_d)*(((38 + 173*math.exp(-t_d/43))*(1 - (e_w_hpa / P_hpa))) + ((6.39 + 4.28*math.exp(-t_d / 107))*((P_hpa / e_w_hpa) - 1))); # Sonntag-1994 eq 22. | |
288 | # print('DEBUG:f_w_td:' + str(f_w_td)); # debug | |
289 | ||
290 | # Calculate e, the vapor pressure of water in the pure phase, in Pascals | |
291 | e = (e_prime / f_w_td); # Sonntag-1994 eq 9 and 20 | |
292 | # print('DEBUG:e:' + str(e)); # debug | |
293 | ||
294 | # Calculate y, an intermediate dew point calculation variable | |
295 | y = math.log(e / 611.213); | |
296 | # print('DEBUG:y:' + str(y)); # debug | |
297 | ||
298 | # Calculate t_d, the dew point temperature in degrees Celsius | |
299 | t_d = 13.715*y + 8.4262*10**-1*y**2 + 1.9048*10**-2*y**3 + 7.8158*10**-3*y**4;# Sonntag-1994 eq 10 | |
300 | # print('DEBUG:t_d:' + str(t_d)); # debug | |
301 | ||
302 | if n == 0: | |
303 | # First run | |
304 | repeat_flag = True; | |
305 | else: | |
306 | # Test t_d accuracy | |
307 | t_d_diff = math.fabs(t_d - t_d_prev); | |
308 | # print('DEBUG:t_d :' + str(t_d)); # debug | |
309 | # print('DEBUG:t_d_prev:' + str(t_d_prev)); # debug | |
310 | # print('DEBUG:t_d_diff:' + str(t_d_diff)); # debug | |
311 | if t_d_diff < 0.01: | |
312 | repeat_flag = False; | |
313 | else: | |
314 | repeat_flag = True; | |
315 | ||
316 | # Calculate T_d, the dew point temperature in Kelvin | |
317 | T_d = 273.15 + t_d; | |
318 | # print('DEBUG:T_d:' + str(T_d)); # debug | |
319 | ||
320 | if n > 100: | |
321 | return T_d; # good enough | |
322 | ||
323 | # update loop counter | |
324 | n += 1; | |
325 | return T_d; | |
f0613e57 SBS |
326 | |
327 | # Tuning factor for compensation. Decrease this number to adjust the | |
328 | # temperature down, and increase to adjust up | |
329 | factor = 2.25 | |
330 | ||
331 | cpu_temps = [get_cpu_temperature()] * 5 | |
332 | ||
333 | delay = 0.5 # Debounce the proximity tap | |
334 | mode = 0 # The starting mode | |
335 | last_page = 0 | |
336 | light = 1 | |
337 | ||
338 | # Create a values dict to store the data | |
339 | variables = ["temperature", | |
340 | "pressure", | |
341 | "humidity", | |
f4b2db2d | 342 | "humidity_abs", |
3905a56d | 343 | "dewpoint_temperature", |
f0613e57 SBS |
344 | "light"] |
345 | values = {} # Initialize values dictionary | |
346 | for v in variables: | |
347 | values[v] = [1] * WIDTH # Init a WIDTH-length list as value for each string in variables | |
348 | ||
349 | # Create a varLenBuffer dict to store recent data | |
350 | varLenBuffer = {} | |
351 | for v in variables: | |
352 | varLenBuffer[v] = [] # Init an empty list for each string in variables | |
353 | ||
354 | # Create a varLenBufferFlt dict to store recent data as floats only | |
355 | varLenBufferFlt = {} | |
356 | for v in variables: | |
357 | varLenBufferFlt[v] = [] # Init an empty list for each string in variables | |
358 | ||
359 | # Create a fixLenBuffer dict to store data for displaying | |
360 | fixLenBuffer = {} | |
361 | for v in variables: | |
362 | fixLenBuffer[v] = [] # Init an empty list for each string in variables | |
363 | ||
364 | pollDelay = 5.0 | |
365 | ||
366 | def pollSensors(): | |
367 | # Desc: Update variables containing latest sensor tuples | |
368 | # Output: (time [ns], unit, float) | |
369 | # now_temp_tuple (°C) | |
370 | # now_pressure_tuple (hPa) | |
371 | # now_humidity_tuple (%) | |
eba03e3f | 372 | # now_humidity_abs_gkg_tuple (g water vapor / kg dry air) |
f0613e57 | 373 | # now_illuminance_tuple (lux) |
3905a56d | 374 | # Depends: time, bme280, ltr559, get_cpu_temperature(), rel_to_abs(), rel_to_dpt() |
f0613e57 SBS |
375 | |
376 | # Tell function to modify these global variables | |
377 | global now_temp_tuple | |
378 | global now_pressure_tuple | |
379 | global now_humidity_tuple | |
eba03e3f | 380 | global now_humidity_abs_gkg_tuple |
3905a56d | 381 | global now_humidity_dpt_c_tuple |
f0613e57 SBS |
382 | global now_illuminance_tuple |
383 | # Initialize | |
384 | cpu_temps = [] | |
385 | poll_time_ns_start = time.time_ns() # Get time reading (unix spoech, nanoseconds) | |
386 | # Get temperature reading | |
387 | cpu_temp = get_cpu_temperature() # get °C from CPU | |
388 | # Smooth out with some averaging to decrease jitter | |
389 | cpu_temps = cpu_temps[1:] + [cpu_temp] | |
390 | avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps)) | |
391 | raw_temp = bme280.get_temperature() # get °C from BME280 sensor | |
392 | now_temp = raw_temp - ((avg_cpu_temp - raw_temp) / factor) | |
393 | now_temp_tuple = (time.time_ns(), '°C', now_temp) | |
394 | # Get pressure reading | |
395 | now_time_ns = time.time_ns() # Get time reading (unix epoch, nanoseconds) | |
396 | now_pressure = bme280.get_pressure() # get hPa from BME280 sensor | |
397 | now_pressure_tuple = (time.time_ns(), 'hPa', now_pressure) | |
f4b2db2d SBS |
398 | # Get relative humidity reading |
399 | now_humidity = bme280.get_humidity() # get % relative humidity from BME280 sensor | |
f0613e57 | 400 | now_humidity_tuple = (time.time_ns(), '%', now_humidity) |
f4b2db2d SBS |
401 | # Calculate absolute humidity reading |
402 | raw_temp_k = 273.15 + raw_temp; # convert sensor temp from degC to K | |
403 | now_pressure_pa = now_pressure * 100; # convert sensor pressure from hPa to Pa | |
eba03e3f SBS |
404 | now_humidity_abs = rel_to_abs(raw_temp_k, now_pressure_pa, now_humidity); # calc kg/kg abs humidity |
405 | now_humidity_abs_gkg = now_humidity_abs * 1000; | |
406 | now_humidity_abs_gkg_tuple = (time.time_ns(), 'g/kg', now_humidity_abs_gkg); | |
3905a56d SBS |
407 | # Calculate dew point temperature |
408 | now_humidity_dpt = rel_to_dpt(raw_temp_k, now_pressure_pa, now_humidity); # calc K dpt | |
409 | now_humidity_dpt_c = now_humidity_dpt - 273.15; | |
410 | now_humidity_dpt_c_tuple = (time.time_ns(), '°C', now_humidity_dpt_c); | |
f0613e57 SBS |
411 | # Get light reading |
412 | proximity = ltr559.get_proximity() # get proximity reading | |
413 | if proximity < 10: | |
414 | now_illuminance = ltr559.get_lux() # get lux reading from LTR-559 sensor if nothing is nearby | |
415 | now_illuminance_tuple = (time.time_ns(), 'lux', now_illuminance) | |
416 | poll_time_ns_end = time.time_ns() | |
417 | #print('DEBUG:poll time (s):' + str((poll_time_ns_end - poll_time_ns_start)/1000000000)) | |
418 | ||
419 | ||
420 | def dateIso8601Str(): | |
421 | nowUTC = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() | |
422 | return str(nowUTC) | |
423 | ||
424 | def medianSubset(listIn: list = [], listOutLen: int = 0) -> list: | |
425 | # Input: int: listOutLen: quantity of elements in output list | |
426 | # list: listIn: input list consisting of integers or floats | |
427 | # Output: list: ints/floats of specified size | |
428 | # Ref/Attrib: PEP 3107 typing https://stackoverflow.com/a/21384492 | |
429 | # Version: 0.1.0 | |
430 | #print('DEBUG:listOutLen:' + str(listOutLen)) | |
431 | #print('DEBUG:listIn:' + str(listIn)) | |
432 | ||
433 | # Exit for invalid input | |
434 | if not isinstance(listOutLen, int): | |
435 | raise ValueError('ERROR:Not a valid int:' + str(listOutLen)) | |
436 | else: | |
437 | if not listOutLen > 0: | |
438 | raise ValueError('ERROR:Invalid value:' + str(listOutLen)) | |
439 | if not isinstance(listIn, list): | |
440 | raise ValueError('ERROR:Not a valid list:' + str(listOutLen)) | |
441 | if not all([( (isinstance(x,int)) or (isinstance(x,float)) ) for x in listIn]): | |
442 | raise ValueError('ERROR:Input list contains something besides integers or floating point numbers.') | |
443 | ||
444 | # Initialize listOut | |
445 | listOut = [None] * listOutLen | |
446 | #print('DEBUG:listOut:' + str(listOut)) | |
447 | ||
448 | # Calc listIn length | |
449 | listInLen = len(listIn) | |
450 | #print('DEBUG:listInLen:' + str(listInLen)) | |
451 | ||
452 | # Calc subset length float | |
453 | subsetLenFloat = ( (max([listInLen,listOutLen]) - 1) /min([listInLen,listOutLen])) | |
454 | subsetIndRatio = ( (listInLen)/(listOutLen) ) | |
455 | #print('DEBUG:subsetLenFloat: %.5f' % subsetLenFloat) | |
456 | #print('DEBUG:subsetLenFloat2: %.5f' % subsetIndRatio) | |
457 | ||
458 | # Iterate for each element in listOut | |
459 | for i_out in range(listOutLen): | |
460 | #print('DEBUG:i_out:' + str(i_out)) | |
461 | ## Decide to expand or reduce listIn to produce listOut | |
462 | if listInLen > listOutLen: | |
463 | ### reduce listIn to listOut | |
464 | #print('DEBUG:listOutLen:' + str(listOutLen)) | |
465 | #print('DEBUG:listInLen:' + str(listInLen)) | |
466 | if i_out == 0: | |
467 | #### Initialize subsetIndLo in first loop | |
468 | subsetIndLo = int(0) | |
469 | #print('DEBUG:subsetIndLo:' + str(subsetIndLo)) | |
470 | #print('DEBUG:i_out:' + str(i_out)) | |
471 | #### Calc indices of i_out'th subset of listIn | |
472 | subsetIndHi = (listInLen - 1) * (i_out + 1) // listOutLen | |
473 | subsetLen = subsetIndHi - subsetIndLo + 1 | |
474 | #print('DEBUG:subsetIndLo:' + str(subsetIndLo)) | |
475 | #print('DEBUG:subsetIndHi:' + str(subsetIndHi)) | |
476 | #print('DEBUG:subsetLen:' + str(subsetLen)) | |
477 | #### Extract subset from listIn using indices inclusively | |
478 | subset = listIn[ int(subsetIndLo) : int(subsetIndHi)+1 ] | |
479 | #print('DEBUG:subset:' + str(subset)) | |
480 | #### Calculate median for subset | |
481 | subsetMedian = np.median(subset) | |
482 | #print('DEBUG:subset median:' + str(subsetMedian)) | |
483 | #### Set listOut element | |
484 | listOut[i_out] = subsetMedian | |
485 | #### Housekeeping | |
486 | ##### Update subsetIndLo for next loop | |
487 | subsetIndLo = subsetIndHi + 1 | |
488 | #print('DEBUG:Updated subsetIndLo:' + str(subsetIndLo)) | |
489 | elif listOutLen > listInLen: | |
490 | ### Expand listIn to listOut | |
491 | #print('DEBUG:listOutLen:' + str(listOutLen)) | |
492 | #print('DEBUG:listInLen:' + str(listInLen)) | |
493 | #### Identify index list of lists mapping listIn to ListOut | |
494 | expandIndex = int(i_out / subsetLenFloat) | |
495 | expandIndex = min([expandIndex,(listInLen - 1)]) | |
496 | #print('DEBUG:expandIndex:' + str(expandIndex)) | |
497 | listOut[i_out] = listIn[expandIndex] | |
498 | #print('DEBUG:listOut[i_out]:' + str(listOut[i_out])) | |
499 | elif listOutLen == listInLen: | |
500 | listOut = listIn | |
501 | #print('DEBUG:end for loop===========') | |
502 | return listOut | |
503 | ||
504 | def updateBuffer(): | |
505 | global now_temp_tuple | |
506 | global now_pressure_tuple | |
507 | global now_humidity_tuple | |
eba03e3f | 508 | global now_humidity_abs_gkg_tuple |
3905a56d | 509 | global now_humidity_dpt_c_tuple |
f0613e57 SBS |
510 | global now_illuminance_tuple |
511 | global varLenBuffer | |
512 | global fixLenBuffer | |
513 | global fixLenBufferFlt | |
514 | global span_time_h | |
515 | #print('DEBUG:This is the updateBuffer() function.') | |
516 | #print('DEBUG:===========================================================') | |
517 | #print('DEBUG:===========================================================') | |
518 | # Capture new sensor tuples | |
519 | pollSensors() | |
520 | #print('DEBUG:now_temp_tuple:' + str(now_temp_tuple)) | |
521 | #print('DEBUG:now_pressure_tuple:' + str(now_pressure_tuple)) | |
522 | #print('DEBUG:now_humidity_tuple:' + str(now_humidity_tuple)) | |
eba03e3f | 523 | #print('DEBUG:now_humidity_abs_gkg_tuple:' + str(now_humidity_abs_gkg_tuple)) |
3905a56d | 524 | #print('DEBUG:now_humidity_dpt_c_tuple:' + str(now_humidity_dpt_c_tuple)) |
f0613e57 SBS |
525 | #print('DEBUG:now_illuminance_tuple:' + str(now_illuminance_tuple)) |
526 | ||
527 | # Append new sensor tuples to varying-length buffer | |
528 | ## Temperature | |
529 | varLenBuffer[variables[0]].append(now_temp_tuple) | |
530 | ## Pressure | |
531 | varLenBuffer[variables[1]].append(now_pressure_tuple) | |
f4b2db2d | 532 | ## Relative Humidity |
f0613e57 | 533 | varLenBuffer[variables[2]].append(now_humidity_tuple) |
f4b2db2d | 534 | ## Absolute Humidity |
eba03e3f | 535 | varLenBuffer[variables[3]].append(now_humidity_abs_gkg_tuple) |
3905a56d SBS |
536 | ## Dew Point Temperature |
537 | varLenBuffer[variables[4]].append(now_humidity_dpt_c_tuple) | |
f0613e57 | 538 | ## Illuminance |
3905a56d | 539 | varLenBuffer[variables[5]].append(now_illuminance_tuple) |
f0613e57 SBS |
540 | #print('DEBUG:varLenBuffer:' + str(varLenBuffer)) |
541 | ||
542 | # Trim outdated sensor tuples from varying-length buffer | |
543 | ## iterate through each tuple list and remove old tuples | |
544 | varLenBufferTemp = [] | |
545 | for v in variables: | |
546 | #varLenBufferTemp = varLenBuffer[v].copy() | |
547 | now_time_ns = time.time_ns() # get ns timestamp of now | |
548 | thn_time_ns = varLenBuffer[v][0][0] # get ns timestamp of earliest tuple | |
549 | dif_time_ns = now_time_ns - thn_time_ns # calc nanosecond difference | |
550 | #print('DEBUG:varLenBufferTTL:' + str(varLenBufferTTL)) | |
551 | #print('DEBUG:now:' + str(now_time_ns)) | |
552 | #print('DEBUG:thn:' + str(thn_time_ns)) | |
553 | #print('DEBUG:dif:' + str(dif_time_ns)) | |
554 | #print('DEBUG:dif(s):' + str(dif_time_ns / 1000000000)) | |
555 | if dif_time_ns > varLenBufferTTL: | |
556 | varLenBuffer[v].pop(0) # discard earliest tuple if age > varLenBufferTTL | |
557 | print('DEBUG:Len of varLenBuffer[' + str(v) + ']:' + str(len(varLenBuffer[v]))) | |
558 | #print('DEBUG:*******************************************') | |
559 | #print('DEBUG:varLenBuffer[variables[' + str(v) + ']]:' + str(varLenBuffer[v])) | |
560 | #print('DEBUG:*******************************************') | |
561 | ||
562 | # Calculate buffer time span in hours | |
563 | ## Get earliest timestamp (use temperature tuples) | |
564 | first_time_ns = varLenBuffer[variables[0]][0][0] | |
565 | last_time_ns = varLenBuffer[variables[0]][-1][0] | |
566 | span_time_ns = int(last_time_ns - first_time_ns) | |
567 | span_time_h = float(span_time_ns / (10**9*60*60)) # nanoseconds to hours | |
568 | ||
569 | # Convert tuple buffer into float buffer | |
570 | for v in variables: | |
571 | varLenBufferFlt[v].clear() # clear old float list | |
572 | #print('DEBUG:v:' + str(v)) | |
573 | for t in varLenBuffer[v]: | |
574 | #print('DEBUG:t:' + str(t)) | |
575 | #print('DEBUG:t[2]:' + str(t[2])) | |
576 | #print('DEBUG:------------------------------------------') | |
577 | #print('DEBUG:varLenBufferFlt[' + str(v) + ']:' + str(varLenBufferFlt[v])) | |
578 | #print('DEBUG:------------------------------------------') | |
579 | if isinstance(t[2], float): | |
580 | varLenBufferFlt[v].append(float(t[2])) # build new float list | |
581 | else: | |
582 | varLenBufferFlt[v].append(float(-273)) # add obvious zero otherwise | |
583 | #print('DEBUG:varLenBufferFlt[' + str(v) + ']:' + str(varLenBufferFlt[v])) | |
584 | ||
585 | # Compress/expand buffer to fixed-length buffer | |
586 | for v in variables: | |
587 | #print('DEBUG:varLenBufferFlt[0]:' + str(varLenBufferFlt[variables[0]])) | |
588 | fixLenBuffer[v] = medianSubset(varLenBufferFlt[v], WIDTH) | |
589 | print('DEBUG:Len of fixLenBuffer[' + str(v) + ']:' + str(len(fixLenBuffer[v]))) | |
590 | #print('DEBUG:fixLenBuffer[' + str(v) + ']:' + str(fixLenBuffer[v])) | |
591 | ||
592 | ||
593 | # The main loop | |
594 | try: | |
595 | # Schedule tasks | |
596 | # schedule.every(1).second.do(updateBuffer) | |
597 | schedule.every().minute.at(":00").do(updateBuffer) | |
598 | # schedule.every().minute.at(":05").do(updateBuffer) | |
599 | # schedule.every().minute.at(":10").do(updateBuffer) | |
600 | # schedule.every().minute.at(":15").do(updateBuffer) | |
601 | # schedule.every().minute.at(":20").do(updateBuffer) | |
602 | # schedule.every().minute.at(":25").do(updateBuffer) | |
603 | # schedule.every().minute.at(":30").do(updateBuffer) | |
604 | # schedule.every().minute.at(":35").do(updateBuffer) | |
605 | # schedule.every().minute.at(":40").do(updateBuffer) | |
606 | # schedule.every().minute.at(":45").do(updateBuffer) | |
607 | # schedule.every().minute.at(":50").do(updateBuffer) | |
608 | # schedule.every().minute.at(":55").do(updateBuffer) | |
699a70ff SBS |
609 | pollSensors() # initial run to start up sensors |
610 | time.sleep(1) # pause to give sensors time to initialize | |
f0613e57 | 611 | updateBuffer() # initial run |
699a70ff | 612 | |
f0613e57 SBS |
613 | while True: |
614 | proximity = ltr559.get_proximity() | |
615 | ||
616 | # If the proximity crosses the threshold, toggle the display mode | |
617 | if proximity > 1500 and time.time() - last_page > delay: | |
618 | mode += 1 | |
619 | mode %= len(variables) | |
620 | last_page = time.time() | |
621 | ||
622 | # Run scheduled tasks | |
623 | schedule.run_pending() | |
624 | ||
625 | # One display mode for each variable | |
626 | if mode == 0: | |
627 | # variable = "temperature" | |
628 | ## run function to display latest temp values in variables[mode] list | |
629 | unit = "°C" | |
630 | cpu_temp = get_cpu_temperature() | |
631 | # Smooth out with some averaging to decrease jitter | |
632 | cpu_temps = cpu_temps[1:] + [cpu_temp] | |
633 | avg_cpu_temp = sum(cpu_temps) / float(len(cpu_temps)) | |
634 | raw_temp = bme280.get_temperature() | |
635 | data = raw_temp - ((avg_cpu_temp - raw_temp) / factor) | |
636 | #display_text(variables[mode], data, unit) | |
637 | display_text2(variables[mode],data,unit,fixLenBuffer) | |
638 | ||
639 | if mode == 1: | |
640 | # variable = "pressure" | |
641 | unit = "hPa" | |
642 | data = bme280.get_pressure() | |
643 | #display_text(variables[mode], data, unit) | |
644 | display_text2(variables[mode],data,unit,fixLenBuffer) | |
645 | ||
646 | if mode == 2: | |
647 | # variable = "humidity" | |
648 | unit = "%" | |
649 | data = bme280.get_humidity() | |
650 | #display_text(variables[mode], data, unit) | |
651 | display_text2(variables[mode],data,unit,fixLenBuffer) | |
f4b2db2d | 652 | |
f0613e57 | 653 | if mode == 3: |
f4b2db2d SBS |
654 | # variable = "humidity_abs" |
655 | unit = "g/kg" | |
656 | raw_temp = bme280.get_temperature() # get °C from BME280 sensor | |
657 | raw_temp_k = 273.15 + raw_temp; # convert sensor temp from degC to K | |
658 | now_pressure = bme280.get_pressure() # get hPa from BME280 sensor | |
659 | now_pressure_pa = now_pressure * 100; # convert sensor pressure from hPa to Pa | |
660 | now_humidity = bme280.get_humidity() # get % relative humidity from BME280 sensor | |
eba03e3f SBS |
661 | now_humidity_abs = rel_to_abs(raw_temp_k,now_pressure_pa,now_humidity); # calc [kg water / kg dry air] abs humidity |
662 | now_humidity_abs_gkg = now_humidity_abs * 1000; # convert kg/kg to g/kg abs humidity | |
663 | data = now_humidity_abs_gkg; | |
664 | # print('DEBUG:raw_temp:' + str(raw_temp)); | |
665 | # print('DEBUG:raw_temp_k:' + str(raw_temp_k)); | |
666 | # print('DEBUG:now_pressure:' + str(now_pressure)); | |
667 | # print('DEBUG:now_pressure_pa:' + str(now_pressure_pa)); | |
668 | # print('DEBUG:now_humidity:' + str(now_humidity)); | |
3905a56d | 669 | # print('DEBUG:now_humidity_abs_gkg:' + str(now_humidity_abs_gkg)); |
f4b2db2d | 670 | display_text2(variables[mode],data,unit,fixLenBuffer) |
3905a56d | 671 | |
f4b2db2d | 672 | if mode == 4: |
3905a56d SBS |
673 | # variable = "humidity_abs" |
674 | unit = "°C" | |
675 | raw_temp = bme280.get_temperature() # get °C from BME280 sensor | |
676 | raw_temp_k = 273.15 + raw_temp; # convert sensor temp from degC to K | |
677 | now_pressure = bme280.get_pressure() # get hPa from BME280 sensor | |
678 | now_pressure_pa = now_pressure * 100; # convert sensor pressure from hPa to Pa | |
679 | now_humidity = bme280.get_humidity() # get % relative humidity from BME280 sensor | |
680 | now_humidity_dpt = rel_to_dpt(raw_temp_k,now_pressure_pa,now_humidity); # calc K dpt humidity | |
681 | now_humidity_dpt_c = now_humidity_dpt - 273.15; # convert K to °C dpt humidity | |
682 | data = now_humidity_dpt_c; | |
683 | # print('DEBUG:raw_temp:' + str(raw_temp)); | |
684 | # print('DEBUG:raw_temp_k:' + str(raw_temp_k)); | |
685 | # print('DEBUG:now_pressure:' + str(now_pressure)); | |
686 | # print('DEBUG:now_pressure_pa:' + str(now_pressure_pa)); | |
687 | # print('DEBUG:now_humidity:' + str(now_humidity)); | |
688 | # print('DEBUG:now_humidity_dpt_c:' + str(now_humidity_dpt_c)); | |
689 | display_text2(variables[mode],data,unit,fixLenBuffer) | |
690 | ||
691 | if mode == 5: | |
f0613e57 SBS |
692 | # variable = "light" |
693 | unit = "Lux" | |
694 | if proximity < 10: | |
695 | data = ltr559.get_lux() | |
696 | else: | |
697 | data = 1 | |
698 | #display_text(variables[mode], data, unit) | |
699 | display_text2(variables[mode],data,unit,fixLenBuffer) | |
700 | ||
701 | ||
702 | ||
703 | # Exit cleanly | |
704 | except KeyboardInterrupt: | |
705 | sys.exit(0) |