""" uv-belichter-software - The main program run at the Pi Picos startup Copyright (C) 2024 Benjamin Burkhardt This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ import utils from lcdMenu import lcdMenu from WelcomeScreen import WelcomeScreen from time import sleep, time_ns from gc import collect # garbage collector for better memory performance config = utils.Config() btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus # extra functions to access the garbage collector def manual(): config.LCD.clear() set_value = config.PIN_OUT_RELAIS.value() config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") while True: if set_value != config.PIN_IN_SWITCH.value(): config.PIN_OUT_RELAIS.value(config.PIN_IN_SWITCH.value()) set_value = config.PIN_OUT_RELAIS.value() config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") if config.PIN_IN_BTN_1.value() == 1 or config.PIN_IN_BTN_2.value() == 1: return True # exit on press of these buttons; True to disable the Quitting message from lcdMenu def timer(): timer_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="TIMERS") timer_values_original = [config.TIMER_1_DURATION, config.TIMER_2_DURATION, config.TIMER_3_DURATION] timer_values = timer_values_original.copy() # here, the current timers time is stored when interrupting via the interrupt_pin (see below) timer_splits = [lambda: divmod(round(timer_values[0]), 60), lambda: divmod(round(timer_values[1]), 60), lambda: divmod(round(timer_values[2]), 60)] interrupt_pin = config.PIN_IN_BTN_1 # the interrupt btn stops the timer, saves the current time and goes back to the menu reset_pin = config.PIN_IN_BTN_2 # the reset btn restores the default value start_stop_pin = config.PIN_IN_SWITCH # the start_stop switch starts/stops the timer # timer_number is the number of the timer that will be run, starting at 1 # the _timer variable is needed because otherwise python will throw crazy errors regarding "variable accessed before assignment"... # ...just see it as a copy of the timer_number-1'th elemnt of the timer_values list (see above) def run_timer(timer_number: int, _timer: int): config.LCD.clear() config.LCD.putstr(f"Timer {timer_number}".center(16)) last_start_stop_value = start_stop_pin.value() config.PIN_OUT_RELAIS.value(last_start_stop_value) last_time_ns = time_ns() while True: # now run the timer (if the switch is high) config.LCD.move_to(0,1) _timer_div = divmod(round(_timer), 60) config.LCD.putstr(f"{_timer_div[0]:02d}:{_timer_div[1]:02d}".center(16)) if interrupt_pin.value() == 1: timer_values[timer_number-1] = _timer # save the current state last_start_stop_value = 0 # turn the LEDs off! config.PIN_OUT_RELAIS.value(last_start_stop_value) return None if reset_pin.value() == 1: _timer = timer_values_original[timer_number-1] # reset the timers if _timer <= 0: config.PIN_OUT_RELAIS.off() return True sleep(0.05) if last_start_stop_value != (new_value := start_stop_pin.value()): last_start_stop_value = new_value config.PIN_OUT_RELAIS.value(new_value) last_time_ns = time_ns() if start_stop_pin.value() == 1: _timer -= (time_ns() - last_time_ns) / 1000**3 last_time_ns = time_ns() timer_programs = [(f"T1 - {timer_splits[0]()[0]:02d}:{timer_splits[0]()[1]:02d}", lambda: run_timer(1, timer_values[0])), (f"T2 - {timer_splits[1]()[0]:02d}:{timer_splits[1]()[1]:02d}", lambda: run_timer(2, timer_values[1])), (f"T3 - {timer_splits[2]()[0]:02d}:{timer_splits[2]()[1]:02d}", lambda: run_timer(3, timer_values[2])), ("Exit", timer_menu.stop)] timer_menu.setup(timer_programs) # give it the callback list timer_menu.run() del timer_menu, timer_programs collect() return True # disable the "Quitting" message from lcdMenu def lcd_big_hello(): import lcd_big_hello as lbh lbh.run(config.LCD) del lbh collect() return True def input_tests(): import input_tests as input_tests input_tests.run(serial_output=False) collect() return True def settings(): settings_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="SETTINGS") def swap_welcome(): config.STARTUP_WELCOME_SHOW = not config.STARTUP_WELCOME_SHOW config.LCD.clear() config.LCD.putstr(f" Welcome Screen ") if config.STARTUP_WELCOME_SHOW: config.LCD.putstr("--- Enabled! ---") else: config.LCD.putstr("--- Disabled ---") sleep(1) def welcome_cycles(): config.LCD.clear() current_cycles = config.STARTUP_WELCOME_CYCLES option_down = [" ", "v"][current_cycles>1] config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^") btn_left = config.PIN_IN_BTN_1 btn_right = config.PIN_IN_BTN_2 while True: if btn_left.value() == 1: sleep(0.1) # this value is a good compromise between being able to press both buttons and a fast up/down speed if btn_right.value() == 1: config.STARTUP_WELCOME_CYCLES = current_cycles config.LCD.move_to(0,1) # move to the second row config.LCD.putstr(f"Now set to {current_cycles}".center(16)) # show a little info that it is now set while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released pass return True current_cycles -= 1 if current_cycles < 1: current_cycles = 1 option_down = [" ", "v"][current_cycles>1] config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^") if btn_right.value() == 1: sleep(0.1) if btn_left.value() == 1: config.STARTUP_WELCOME_CYCLES = current_cycles config.LCD.move_to(0,1) # move to the second row config.LCD.putstr(f"Now set to {current_cycles}".center(16)) # show a little info that it is now set while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released pass return True current_cycles += 1 option_down = [" ", "v"][current_cycles>1] config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^") # with n being the number of the timer (starting at 0) def set_n_timer(n: int): # a small helper function to save the value of the n'th timer... # as you can't programatically access TIMER_n_DURATION def set_timer_helper(n, value): if n == 0: config.TIMER_1_DURATION = value elif n == 1: config.TIMER_2_DURATION = value elif n == 2: config.TIMER_3_DURATION = value else: utils.log(0, "There are only 3 timers at all. Trying to set the timer number {n} failed.") config.LCD.clear() timer_values = [config.TIMER_1_DURATION, config.TIMER_2_DURATION, config.TIMER_3_DURATION] current_timer = timer_values[n] # get the n'th timer current_timer_div = lambda: divmod(current_timer, 60) config.LCD.putstr(f"Timer {n+1}".center(16)) config.LCD.putstr(f"{'v' if current_timer > 1 else ' '} " + f"{current_timer_div()[0]:02d}:{current_timer_div()[1]:02d}".center(12) + " ^") btn_left = config.PIN_IN_BTN_1 btn_right = config.PIN_IN_BTN_2 left_was_released = True right_was_released = True while True: if btn_left.value() == 1: if left_was_released: time_press_start = time_ns() left_was_released = False sleep(0.1) # this value is a good compromise between being able to press both buttons and a fast up/down speed if btn_right.value() == 1: # exit if both btns are pressed set_timer_helper(n, current_timer) config.LCD.move_to(0,1) # move to the second row config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released pass return True # define the step width time_now = time_ns() if (time_now - time_press_start) <= 1*(10**9): # max. 1 seconds pressed current_timer -= 1 elif (time_now - time_press_start) <= 2*(10**9): # max. 2 seconds pressed current_timer -= 5 elif (time_now - time_press_start) <= 3*(10**9): # max. 3 seconds pressed current_timer -= 10 elif (time_now - time_press_start) <= 4*(10**9): # max. 4 seconds pressed current_timer -= 30 elif (time_now - time_press_start) <= 5*(10**9): # max. 5 seconds pressed current_timer -= 60 else: # longer than 5s pressed current_timer -= 300 if current_timer < 1: current_timer = 5999 config.LCD.move_to(0,1) config.LCD.putstr("v " + f"{current_timer_div()[0]:02d}:{current_timer_div()[1]:02d}".center(12) + " ^") else: left_was_released = True if btn_right.value() == 1: if right_was_released: time_press_start = time_ns() right_was_released = False sleep(0.1) if btn_left.value() == 1: # exit if both btns are pressed set_timer_helper(n, current_timer) config.LCD.move_to(0,1) # move to the second row config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released pass return True # define the step width time_now = time_ns() if (time_now - time_press_start) <= 1*(10**9): # max. 1 seconds pressed current_timer += 1 elif (time_now - time_press_start) <= 2*(10**9): # max. 2 seconds pressed current_timer += 5 elif (time_now - time_press_start) <= 3*(10**9): # max. 3 seconds pressed current_timer += 10 elif (time_now - time_press_start) <= 4*(10**9): # max. 4 seconds pressed current_timer += 30 elif (time_now - time_press_start) <= 5*(10**9): # max. 5 seconds pressed current_timer += 60 else: # longer than 5s pressed current_timer += 300 if current_timer > 5999: current_timer = 1 config.LCD.move_to(0,1) config.LCD.putstr("v " + f"{current_timer_div()[0]:02d}:{current_timer_div()[1]:02d}".center(12) + " ^") else: right_was_released = True settings_programs = [("Show welcome", swap_welcome), ("Welcome cycles", welcome_cycles), ("Timer 1", lambda: set_n_timer(0)), ("Timer 2", lambda: set_n_timer(1)), ("Timer 3", lambda: set_n_timer(2)), ("Exit", settings_menu.stop)] settings_menu.setup(settings_programs) # give it the callback list settings_menu.run() # run the menu until it's closed del settings_menu, settings_programs collect() return True def run_demo_menu(): demo_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="DEMOS") demo_programs = [("LCD Demo", lcd_big_hello), ("Input tests", input_tests), ("Exit", demo_menu.stop)] demo_menu.setup(demo_programs) # give it the callback list demo_menu.run() del demo_menu, demo_programs collect() return True if config.STARTUP_WELCOME_SHOW: ws = WelcomeScreen(config.LCD, interrupt_pins=[config.PIN_IN_BTN_1, config.PIN_IN_BTN_2, config.PIN_IN_SWITCH], subtitle=config.STARTUP_PROJECT_NAME, starting_msg=config.STARTUP_MESSAGE_STARTING, started_msg=config.STARTUP_MESSAGE_FINISHED) ws.show(cycles=config.STARTUP_WELCOME_CYCLES) del ws collect() # create the menus main_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="PROGRAMS") main_programs = [("Timer", timer), ("Manual", manual), ("Demos", run_demo_menu), ("Settings", settings)] main_menu.setup(main_programs) # give it the callback list # and run the main menu (will be an endless loop) main_menu.run()