feat(examples/a): Add absolute dew point temperature display mode
[EVA-2020-02-2.git] / examples / all-in-one-enviro-mini-bk.py
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,
48 rotation=270,
49 #rotation=90, # flip upside down wrt enviro+ default orientation
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
102 #print('DEBUG:len(values[' + str(variable) + ']):' + str(len(values[variable])))
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
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
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;
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",
342 "humidity_abs",
343 "dewpoint_temperature",
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 (%)
372 # now_humidity_abs_gkg_tuple (g water vapor / kg dry air)
373 # now_illuminance_tuple (lux)
374 # Depends: time, bme280, ltr559, get_cpu_temperature(), rel_to_abs(), rel_to_dpt()
375
376 # Tell function to modify these global variables
377 global now_temp_tuple
378 global now_pressure_tuple
379 global now_humidity_tuple
380 global now_humidity_abs_gkg_tuple
381 global now_humidity_dpt_c_tuple
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)
398 # Get relative humidity reading
399 now_humidity = bme280.get_humidity() # get % relative humidity from BME280 sensor
400 now_humidity_tuple = (time.time_ns(), '%', now_humidity)
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
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);
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);
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
508 global now_humidity_abs_gkg_tuple
509 global now_humidity_dpt_c_tuple
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))
523 #print('DEBUG:now_humidity_abs_gkg_tuple:' + str(now_humidity_abs_gkg_tuple))
524 #print('DEBUG:now_humidity_dpt_c_tuple:' + str(now_humidity_dpt_c_tuple))
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)
532 ## Relative Humidity
533 varLenBuffer[variables[2]].append(now_humidity_tuple)
534 ## Absolute Humidity
535 varLenBuffer[variables[3]].append(now_humidity_abs_gkg_tuple)
536 ## Dew Point Temperature
537 varLenBuffer[variables[4]].append(now_humidity_dpt_c_tuple)
538 ## Illuminance
539 varLenBuffer[variables[5]].append(now_illuminance_tuple)
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)
609 pollSensors() # initial run to start up sensors
610 time.sleep(1) # pause to give sensors time to initialize
611 updateBuffer() # initial run
612
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)
652
653 if mode == 3:
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
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));
669 # print('DEBUG:now_humidity_abs_gkg:' + str(now_humidity_abs_gkg));
670 display_text2(variables[mode],data,unit,fixLenBuffer)
671
672 if mode == 4:
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:
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)