| @@ -20,6 +20,7 @@ from colorama import Style | |||||
| from subprocess import check_call | from subprocess import check_call | ||||
| from signal import pause | from signal import pause | ||||
| import smtplib, ssl | import smtplib, ssl | ||||
| from collections import deque | |||||
| picdir = os.path.realpath("/home/firestone/pi-zero-tank/pic") | picdir = os.path.realpath("/home/firestone/pi-zero-tank/pic") | ||||
| libdir = os.path.realpath("/home/firestone/pi-zero-tank/lib") | libdir = os.path.realpath("/home/firestone/pi-zero-tank/lib") | ||||
| @@ -37,6 +38,7 @@ import traceback | |||||
| logging.basicConfig(level=logging.DEBUG) | logging.basicConfig(level=logging.DEBUG) | ||||
| NUM_SAMPLES = 100 | NUM_SAMPLES = 100 | ||||
| AVG_WINDOWSIZE = 20 | |||||
| FILE = Path("config.ini") | FILE = Path("config.ini") | ||||
| ALARM_LEVEL_CM = 5.0 | ALARM_LEVEL_CM = 5.0 | ||||
| @@ -128,6 +130,15 @@ class Buttons: | |||||
| def none_event(self): | def none_event(self): | ||||
| pass | pass | ||||
| class MovingAverage: | |||||
| def __init__(self, size: int): | |||||
| self.size = size | |||||
| self.buffer = deque(maxlen=size) | |||||
| def add_value(self, value: float): | |||||
| self.buffer.append(value) | |||||
| return sum(self.buffer) / len(self.buffer), len(self.buffer) == self.size | |||||
| def shutdown(epd): | def shutdown(epd): | ||||
| print("System wird heruntergefahren:") | print("System wird heruntergefahren:") | ||||
| print("Erstelle Backup...") | print("Erstelle Backup...") | ||||
| @@ -148,8 +159,7 @@ def shutdown(epd): | |||||
| epd.sleep() | epd.sleep() | ||||
| epd2in13_V4.epdconfig.module_exit(cleanup=True) | epd2in13_V4.epdconfig.module_exit(cleanup=True) | ||||
| print("Linux shutdown...") | print("Linux shutdown...") | ||||
| #check_call(['sudo', 'poweroff']) | |||||
| #TODO | |||||
| #check_call(['sudo', 'poweroff']) #TODO | |||||
| sys.exit(1) | sys.exit(1) | ||||
| pause() | pause() | ||||
| @@ -162,7 +172,6 @@ def measure(sensor): | |||||
| except RuntimeError: | except RuntimeError: | ||||
| fails += 1 | fails += 1 | ||||
| time.sleep(0.01) | time.sleep(0.01) | ||||
| #print(len(data), fails) | |||||
| if fails < (NUM_SAMPLES / 2): | if fails < (NUM_SAMPLES / 2): | ||||
| mid_count = NUM_SAMPLES // 2 | mid_count = NUM_SAMPLES // 2 | ||||
| start_index = (NUM_SAMPLES - mid_count) // 2 | start_index = (NUM_SAMPLES - mid_count) // 2 | ||||
| @@ -173,7 +182,6 @@ def measure(sensor): | |||||
| val = 0 | val = 0 | ||||
| else: | else: | ||||
| val = 0 | val = 0 | ||||
| #val = random.uniform(5.0, 40.0) | |||||
| return val | return val | ||||
| def store_value(config, val, meas, lvl, date): | def store_value(config, val, meas, lvl, date): | ||||
| @@ -211,12 +219,11 @@ Die aktuelle Hoehe betraegt {3:.0f} cm. | |||||
| Gruesse vom Tank-Computer""".format(meas, sender_email, receiver_email, val) | Gruesse vom Tank-Computer""".format(meas, sender_email, receiver_email, val) | ||||
| context = ssl.create_default_context() | context = ssl.create_default_context() | ||||
| with smtplib.SMTP(smtp_server, smtp_port) as server: | with smtplib.SMTP(smtp_server, smtp_port) as server: | ||||
| #TODO | |||||
| #server.set_debuglevel(1) | #server.set_debuglevel(1) | ||||
| #server.ehlo() | |||||
| server.ehlo() | |||||
| server.starttls(context=context) | server.starttls(context=context) | ||||
| #server.login(sender_email, smtp_password.strip()) | |||||
| #server.sendmail(sender_email, receiver_email, message) | |||||
| server.login(sender_email, smtp_password.strip()) | |||||
| server.sendmail(sender_email, receiver_email, message) | |||||
| def display_site_measure(epd, titel, titel_pos, val): | def display_site_measure(epd, titel, titel_pos, val): | ||||
| dp = Image.new('1', (epd.height, epd.width), 255) | dp = Image.new('1', (epd.height, epd.width), 255) | ||||
| @@ -231,35 +238,39 @@ def display_site_measure(epd, titel, titel_pos, val): | |||||
| draw.text((156, 110), "Abbrechen", font = font_label, fill = 0) | draw.text((156, 110), "Abbrechen", font = font_label, fill = 0) | ||||
| epd.display(epd.getbuffer(dp)) | epd.display(epd.getbuffer(dp)) | ||||
| def display_site_main(epd, cl, ph): | |||||
| def display_site_main(epd, cl, ph, clm, phm): | |||||
| dp = Image.new('1', (epd.height, epd.width), 255) | dp = Image.new('1', (epd.height, epd.width), 255) | ||||
| newimage = Image.open(os.path.join(picdir, 'tankgrafik.bmp')) | newimage = Image.open(os.path.join(picdir, 'tankgrafik.bmp')) | ||||
| dp.paste(newimage, (0,0)) | dp.paste(newimage, (0,0)) | ||||
| draw = ImageDraw.Draw(dp) | draw = ImageDraw.Draw(dp) | ||||
| draw.text((19, 31), f"Chlor: {cl:.0f} %", font = font_label, fill = 0) | draw.text((19, 31), f"Chlor: {cl:.0f} %", font = font_label, fill = 0) | ||||
| draw.text((149, 31), f"pH-: {ph:.0f} %", font = font_label, fill = 0) | draw.text((149, 31), f"pH-: {ph:.0f} %", font = font_label, fill = 0) | ||||
| if cl < 80: | |||||
| draw.rectangle((10, 54, 114, 61), outline = 1, fill = 1) | |||||
| if cl < 60: | |||||
| draw.rectangle((10, 66, 114, 73), outline = 1, fill = 1) | |||||
| if cl < 40: | |||||
| draw.rectangle((10, 78, 114, 85), outline = 1, fill = 1) | |||||
| if cl < 20: | |||||
| draw.rectangle((10, 90, 114, 97), outline = 1, fill = 1) | |||||
| if cl < ALARM_LEVEL_CM: #todo and email | |||||
| if clm == 1: # wenn mail gesendet (cl < alarm) | |||||
| # alle Balken ausblenden | |||||
| draw.rectangle((10, 54, 114, 109), outline = 1, fill = 1) | draw.rectangle((10, 54, 114, 109), outline = 1, fill = 1) | ||||
| draw.text((37, 53), "X", font = font_huge, fill = 0) | draw.text((37, 53), "X", font = font_huge, fill = 0) | ||||
| if ph < 80: | |||||
| draw.rectangle((135, 54, 239, 61), outline = 1, fill = 1) | |||||
| if ph < 60: | |||||
| draw.rectangle((135, 66, 239, 73), outline = 1, fill = 1) | |||||
| if ph < 40: | |||||
| draw.rectangle((135, 78, 239, 85), outline = 1, fill = 1) | |||||
| if ph < 20: | |||||
| draw.rectangle((135, 90, 239, 97), outline = 1, fill = 1) | |||||
| if ph < ALARM_LEVEL_CM: #todo and email | |||||
| else: | |||||
| if cl < 80: #100-80 | |||||
| draw.rectangle((10, 54, 114, 61), outline = 1, fill = 1) | |||||
| if cl < 60: #80-60 | |||||
| draw.rectangle((10, 66, 114, 73), outline = 1, fill = 1) | |||||
| if cl < 40: #60-40 | |||||
| draw.rectangle((10, 78, 114, 85), outline = 1, fill = 1) | |||||
| if cl < 20: #40-20 | |||||
| draw.rectangle((10, 90, 114, 97), outline = 1, fill = 1) | |||||
| if phm == 1: # wenn mail gesendet (ph- < alarm) | |||||
| # alle Balken ausblenden | |||||
| draw.rectangle((135, 54, 239, 109), outline = 1, fill = 1) | draw.rectangle((135, 54, 239, 109), outline = 1, fill = 1) | ||||
| draw.text((164, 53), "X", font = font_huge, fill = 0) | draw.text((164, 53), "X", font = font_huge, fill = 0) | ||||
| else: | |||||
| if ph < 80: #100-80 | |||||
| draw.rectangle((135, 54, 239, 61), outline = 1, fill = 1) | |||||
| if ph < 60: #80-60 | |||||
| draw.rectangle((135, 66, 239, 73), outline = 1, fill = 1) | |||||
| if ph < 40: #60-40 | |||||
| draw.rectangle((135, 78, 239, 85), outline = 1, fill = 1) | |||||
| if ph < 20: #40-20 | |||||
| draw.rectangle((135, 90, 239, 97), outline = 1, fill = 1) | |||||
| epd.display(epd.getbuffer(dp)) | epd.display(epd.getbuffer(dp)) | ||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||
| @@ -274,7 +285,7 @@ if __name__ == '__main__': | |||||
| font_label = ImageFont.truetype(os.path.join(picdir, 'squarea.ttf'), 15) | font_label = ImageFont.truetype(os.path.join(picdir, 'squarea.ttf'), 15) | ||||
| font_huge = ImageFont.truetype(os.path.join(picdir, 'squarea.ttf'), 74) | font_huge = ImageFont.truetype(os.path.join(picdir, 'squarea.ttf'), 74) | ||||
| display_site_main(epd, 0, 0) | |||||
| display_site_main(epd, 0, 0, 0, 0) | |||||
| print("Lese config.ini...") | print("Lese config.ini...") | ||||
| config = configparser.ConfigParser() | config = configparser.ConfigParser() | ||||
| @@ -307,35 +318,13 @@ if __name__ == '__main__': | |||||
| buttons = Buttons(button_cl, button_ph) | buttons = Buttons(button_cl, button_ph) | ||||
| print("Starte Hauptprogramm...") | print("Starte Hauptprogramm...") | ||||
| mavg_cl = MovingAverage(AVG_WINDOWSIZE) | |||||
| mavg_ph = MovingAverage(AVG_WINDOWSIZE) | |||||
| clp_prev = -1 | |||||
| php_prev = -1 | |||||
| force_update = False | |||||
| while True: | while True: | ||||
| try: | try: | ||||
| cl = measure(sensor_cl) | |||||
| ph = measure(sensor_ph) | |||||
| clp = get_percent(cl_leer, cl_voll, cl) | |||||
| php = get_percent(ph_leer, ph_voll, ph) | |||||
| display_site_main(epd, clp, php) | |||||
| print("Chlor: {:10.2f} cm, pH-Minus: {:10.2f} cm".format(cl, ph)) | |||||
| print("Chlor: {:10.2f} %, pH-Minus: {:10.2f} %".format(clp, php)) | |||||
| if abs(cl_leer - ALARM_LEVEL_CM) < cl: | |||||
| print(f"{Fore.RED}Chlor-Tank fast leer!{Style.RESET_ALL}") | |||||
| if cl_mail == 0: | |||||
| print("Sende E-Mail...") | |||||
| send_email("Chlor", abs(cl_leer - cl)) | |||||
| cl_mail = 1 | |||||
| config.set('Chlor', 'mail', str(cl_mail)) | |||||
| config.set('Chlor', 'datum_mail', str(datetime.datetime.now())) | |||||
| with open(FILE, 'w') as configfile: | |||||
| config.write(configfile) | |||||
| if abs(ph_leer - ALARM_LEVEL_CM) < ph: | |||||
| print(f"{Fore.RED}pH-Minus-Tank fast leer!{Style.RESET_ALL}") | |||||
| if ph_mail == 0: | |||||
| print("Sende E-Mail...") | |||||
| send_email("pH-Minus", abs(ph_leer - ph)) | |||||
| ph_mail = 1 | |||||
| config.set('pH-', 'mail', str(ph_mail)) | |||||
| config.set('pH-', 'datum_mail', str(datetime.datetime.now())) | |||||
| with open(FILE, 'w') as configfile: | |||||
| config.write(configfile) | |||||
| state = buttons.get_state() | state = buttons.get_state() | ||||
| if state == States.MEASURE_CL_FULL: | if state == States.MEASURE_CL_FULL: | ||||
| buttons.deactivate_events() | buttons.deactivate_events() | ||||
| @@ -348,11 +337,18 @@ if __name__ == '__main__': | |||||
| if button_cl.is_pressed: | if button_cl.is_pressed: | ||||
| store_value(config, val, "Chlor", "voll", "datum_voll") | store_value(config, val, "Chlor", "voll", "datum_voll") | ||||
| cl_voll = val | cl_voll = val | ||||
| break; | |||||
| cl_mail = 0 | |||||
| mavg_cl = MovingAverage(AVG_WINDOWSIZE) | |||||
| config.set('Chlor', 'mail', str(cl_mail)) | |||||
| config.set('Chlor', 'datum_mail', "") | |||||
| with open(FILE, 'w') as configfile: | |||||
| config.write(configfile) | |||||
| break | |||||
| elif button_ph.is_pressed: | elif button_ph.is_pressed: | ||||
| print("Abbruch!") | print("Abbruch!") | ||||
| break | break | ||||
| buttons.set_state(States.MAIN_MENU) | buttons.set_state(States.MAIN_MENU) | ||||
| force_update = True | |||||
| buttons.activate_events() | buttons.activate_events() | ||||
| elif state == States.MEASURE_CL_EMPTY: | elif state == States.MEASURE_CL_EMPTY: | ||||
| buttons.deactivate_events() | buttons.deactivate_events() | ||||
| @@ -370,6 +366,7 @@ if __name__ == '__main__': | |||||
| print("Abbruch!") | print("Abbruch!") | ||||
| break | break | ||||
| buttons.set_state(States.MAIN_MENU) | buttons.set_state(States.MAIN_MENU) | ||||
| force_update = True | |||||
| buttons.activate_events() | buttons.activate_events() | ||||
| elif state == States.MEASURE_PH_FULL: | elif state == States.MEASURE_PH_FULL: | ||||
| buttons.deactivate_events() | buttons.deactivate_events() | ||||
| @@ -382,11 +379,18 @@ if __name__ == '__main__': | |||||
| if button_cl.is_pressed: | if button_cl.is_pressed: | ||||
| store_value(config, val, "pH-", "voll", "datum_voll") | store_value(config, val, "pH-", "voll", "datum_voll") | ||||
| ph_voll = val | ph_voll = val | ||||
| break; | |||||
| ph_mail = 0 | |||||
| mavg_ph = MovingAverage(AVG_WINDOWSIZE) | |||||
| config.set('pH-', 'mail', str(ph_mail)) | |||||
| config.set('pH-', 'datum_mail', "") | |||||
| with open(FILE, 'w') as configfile: | |||||
| config.write(configfile) | |||||
| break | |||||
| elif button_ph.is_pressed: | elif button_ph.is_pressed: | ||||
| print("Abbruch!") | print("Abbruch!") | ||||
| break | break | ||||
| buttons.set_state(States.MAIN_MENU) | buttons.set_state(States.MAIN_MENU) | ||||
| force_update = True | |||||
| buttons.activate_events() | buttons.activate_events() | ||||
| elif state == States.MEASURE_PH_EMPTY: | elif state == States.MEASURE_PH_EMPTY: | ||||
| buttons.deactivate_events() | buttons.deactivate_events() | ||||
| @@ -399,14 +403,61 @@ if __name__ == '__main__': | |||||
| if button_cl.is_pressed: | if button_cl.is_pressed: | ||||
| store_value(config, val, "pH-", "leer", "datum_leer") | store_value(config, val, "pH-", "leer", "datum_leer") | ||||
| ph_leer = val | ph_leer = val | ||||
| break; | |||||
| break | |||||
| elif button_ph.is_pressed: | elif button_ph.is_pressed: | ||||
| print("Abbruch!") | print("Abbruch!") | ||||
| break | break | ||||
| buttons.set_state(States.MAIN_MENU) | buttons.set_state(States.MAIN_MENU) | ||||
| force_update = True | |||||
| buttons.activate_events() | buttons.activate_events() | ||||
| elif state == States.SHUTDOWN: | elif state == States.SHUTDOWN: | ||||
| shutdown(epd) | shutdown(epd) | ||||
| else: | |||||
| tmp = measure(sensor_cl) | |||||
| if tmp != 0: | |||||
| cl, cl_status = mavg_cl.add_value(tmp) | |||||
| else: | |||||
| cl = 0 | |||||
| cl_status = False | |||||
| tmp = measure(sensor_ph) | |||||
| if tmp != 0: | |||||
| ph, ph_status = mavg_ph.add_value(tmp) | |||||
| else: | |||||
| ph = 0 | |||||
| ph_status = False | |||||
| clp = get_percent(cl_leer, cl_voll, cl) | |||||
| php = get_percent(ph_leer, ph_voll, ph) | |||||
| print("Chlor: {:10.2f} cm, pH-Minus: {:10.2f} cm".format(cl, ph)) | |||||
| print("Chlor: {:10.2f} %, pH-Minus: {:10.2f} %".format(clp, php)) | |||||
| clp_now = round(clp, 0) | |||||
| php_now = round(php, 0) | |||||
| if clp_now != clp_prev or php_now != php_prev or force_update: | |||||
| display_site_main(epd, clp, php, cl_mail, ph_mail) | |||||
| force_update = False | |||||
| clp_prev = clp_now | |||||
| php_prev = php_now | |||||
| if cl_status and abs(cl_leer - ALARM_LEVEL_CM) < cl: | |||||
| print(f"{Fore.RED}Chlor-Tank fast leer!{Style.RESET_ALL}") | |||||
| if cl_mail == 0: | |||||
| print("Sende E-Mail...") | |||||
| send_email("Chlor", abs(cl_leer - cl)) | |||||
| cl_mail = 1 | |||||
| config.set('Chlor', 'mail', str(cl_mail)) | |||||
| config.set('Chlor', 'datum_mail', str(datetime.datetime.now())) | |||||
| with open(FILE, 'w') as configfile: | |||||
| config.write(configfile) | |||||
| force_update = True | |||||
| if ph_status and abs(ph_leer - ALARM_LEVEL_CM) < ph: | |||||
| print(f"{Fore.RED}pH-Minus-Tank fast leer!{Style.RESET_ALL}") | |||||
| if ph_mail == 0: | |||||
| print("Sende E-Mail...") | |||||
| send_email("pH-Minus", abs(ph_leer - ph)) | |||||
| ph_mail = 1 | |||||
| config.set('pH-', 'mail', str(ph_mail)) | |||||
| config.set('pH-', 'datum_mail', str(datetime.datetime.now())) | |||||
| with open(FILE, 'w') as configfile: | |||||
| config.write(configfile) | |||||
| force_update = True | |||||
| time.sleep(0.5) | time.sleep(0.5) | ||||
| except KeyboardInterrupt: | except KeyboardInterrupt: | ||||
| shutdown(epd) | shutdown(epd) | ||||