Compare commits

...

6 Commits
v0.7 ... main

10 changed files with 447 additions and 331 deletions

View File

@ -2,10 +2,12 @@
A collection of programs run on a Raspberry Pi Pico to control a uv exposure unit (mainly used for exposing PCBs)
![The front of the exposure unit](cover.png)
## Hardware
This software is strongly customized to my needs, and my DIY exposure unit has **two buttons** and **one switch** to interact with the software (and a power switch FWIW). Also, a **16x2 display** (maybe 20x4 or others do also work, but these are not tested) can show information to the user.
This software is strongly customized to my needs, and my DIY exposure unit has **two buttons** and **one switch** to interact with the software (and a power switch FWIW). Also, a **16x2 display** can show information to the user.
A **relais** is used for switching all the LEDs.
| Device Pin | Pi Pico Pin |

BIN
cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

View File

@ -21,10 +21,12 @@ from utils import log
def run(lcd):
# Show off basic functionality of the lcd driver
log(2, "Running the lcd driver demo")
lcd.putstr("Driver demo".center(16))
lcd.clear()
lcd.putstr("Driver demo".center(16)+"running...".center(16))
sleep(1)
lcd.clear()
lcd.putstr("Lorem ipsum dolor sit amet")
lcd.putstr("Lorem ipsum dolor sit amet")
sleep(1)
lcd.show_cursor()
sleep(1)

361
main.py
View File

@ -13,336 +13,50 @@ You should have received a copy of the GNU General Public License along with thi
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 timers():
import programs.timers as t
t.run(config, btn_mapping, utils.log, lcdMenu)
del t
collect()
return True
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
import programs.manual as m
m.run(config)
del m
collect()
return True
def demos():
import programs.demos as d
d.run(config, btn_mapping, utils.log, lcdMenu)
del d
collect()
return True
def settings():
settings_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="SETTINGS")
def toggle_show_welcome():
current_value = config.STARTUP_WELCOME_SHOW
config.LCD.clear()
config.LCD.putstr(f"Currently {'on' if current_value else 'off'}".center(16))
config.LCD.putstr("< keep change >")
keep_btn = config.PIN_IN_BTN_1
change_btn = config.PIN_IN_BTN_2
while True:
if keep_btn.value() == 1:
config.LCD.move_to(0,1) # move to the second row
config.LCD.putstr(f"Stay {'on' if current_value else 'off'}!".center(16))
sleep(0.5)
return True
if change_btn.value() == 1:
config.STARTUP_WELCOME_SHOW = not current_value
config.LCD.move_to(0,1) # move to the second row
config.LCD.putstr(f"Turned {'on' if not current_value else 'off'}!".center(16))
sleep(0.5)
return True
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"Saved!".center(16)) # show a little info that it is now set
sleep(0.5)
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"Saved!".center(16)) # show a little info that it is now set
sleep(0.5)
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)} ^")
import programs.settings as s
s.run(config, btn_mapping, utils.log, lcdMenu)
del s
collect()
return True
# 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)
# create the main menu
main_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="PROGRAMS")
main_programs = [("Timers", timers),
("Manual", manual),
("Demos", demos),
("Settings", settings)]
main_menu.setup(main_programs) # give it the callback list
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
sleep(0.5)
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
sleep(0.5)
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
def set_log_level():
config.LCD.clear()
current_level = config.LOG_LEVEL
log_levels = ["WARN", "INFO", "DEBUG"]
config.LCD.putstr(f"Log level".center(16))
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
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.LOG_LEVEL = current_level
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
sleep(0.5)
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
pass
return True
current_level -= 1
if current_level < 0: current_level = 2
config.LCD.move_to(0,0)
config.LCD.putstr(f"Log level".center(16))
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
if btn_right.value() == 1:
sleep(0.1)
if btn_left.value() == 1:
config.LOG_LEVEL = current_level
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
sleep(0.5)
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
pass
return True
current_level += 1
if current_level > 2: current_level = 0
config.LCD.move_to(0,0)
config.LCD.putstr(f"Log level".center(16))
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
def reset(): # reset all user-settable configuration to the default values
config.LCD.clear()
config.LCD.putstr("Sure about that?")
config.LCD.putstr("< no yes >")
no_btn = config.PIN_IN_BTN_1
yes_btn = config.PIN_IN_BTN_2
while True:
if no_btn.value() == 1:
return None
if yes_btn.value() == 1:
config.LCD.putstr("Resetting...".center(16))
config.LCD.putstr("Welcome Screen".center(16))
config.STARTUP_WELCOME_SHOW = True
config.STARTUP_WELCOME_CYCLES = 1
sleep(0.5)
config.LCD.move_to(0,1)
config.LCD.putstr("Timers".center(16))
config.TIMER_1_DURATION = 60
config.TIMER_2_DURATION = 2400
config.TIMER_3_DURATION = 2700
sleep(0.5)
config.LCD.move_to(0,1)
config.LCD.putstr("Logging".center(16))
config.LOG_LEVEL = 1
sleep(0.5)
return True
settings_programs = [("Show welcome", toggle_show_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)),
("Log level", set_log_level),
("Reset", reset),
("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():
def lcd_driver_demo():
import lcd_driver_demo as ldd
ldd.run(config.LCD)
del ldd
collect()
return True
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)
del input_tests
collect()
return True
demo_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="DEMOS")
demo_programs = [("LCD Demo", lcd_driver_demo),
("Hello world", 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
# -------
# run the welcome screen as defined in the config file
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],
@ -353,14 +67,5 @@ if config.STARTUP_WELCOME_SHOW:
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()

52
programs/demos.py Normal file
View File

@ -0,0 +1,52 @@
"""
uv-belichter-software - the DEMOS submenu
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 <https://www.gnu.org/licenses/>.
"""
from gc import collect
# All the arguments this method takes are there for one reason: reduce the amount of libraries loaded into the picos memory and thus improving performance
# config: utils.Config object
# btn_mapping: a dict containing the btn mapping for the menu operation (given to the lcdMenu object)
# log: the utils.log function
# lcdMenu: the lcdMenu class (not an object of that class!)
def run(config, btn_mapping, log, lcdMenu):
def lcd_driver_demo():
import demos.lcd_driver_demo as ldd
ldd.run(config.LCD)
del ldd
collect()
return True
def lcd_big_hello():
import demos.lcd_big_hello as lbh
lbh.run(config.LCD)
del lbh
collect()
return True
def input_tests():
import demos.input_tests as it
it.run(serial_output=False)
del it
collect()
return True
demo_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="DEMOS")
demo_programs = [("LCD Demo", lcd_driver_demo),
("Hello world", lcd_big_hello),
("Input tests", input_tests),
("Exit", demo_menu.stop)]
demo_menu.setup(demo_programs) # give it the callback list
demo_menu.run()
return True
if __name__ == "__main__":
from utils import Config, log
from lcdMenu import lcdMenu
config = Config()
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
run(Config(), btn_mapping, log, lcdMenu)

28
programs/manual.py Normal file
View File

@ -0,0 +1,28 @@
"""
uv-belichter-software - the MANUAL program (started from the main menu)
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 <https://www.gnu.org/licenses/>.
"""
def run(config):
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
if __name__ == "__main__":
from utils import Config, log
config = Config()
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
run(Config(), btn_mapping, log)

254
programs/settings.py Normal file
View File

@ -0,0 +1,254 @@
"""
uv-belichter-software - the SETTINGS submenu
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 <https://www.gnu.org/licenses/>.
"""
from time import sleep, time_ns
# All the arguments this method takes are there for one reason: reduce the amount of libraries loaded into the picos memory and thus improving performance
# config: utils.Config object
# btn_mapping: a dict containing the btn mapping for the menu operation (given to the lcdMenu object)
# log: the utils.log function
# lcdMenu: the lcdMenu class (not an object of that class!)
def run(config, btn_mapping, log, lcdMenu):
settings_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="SETTINGS")
def toggle_show_welcome():
current_value = config.STARTUP_WELCOME_SHOW
config.LCD.clear()
config.LCD.putstr(f"Currently {'on' if current_value else 'off'}".center(16))
config.LCD.putstr("< keep change >")
keep_btn = config.PIN_IN_BTN_1
change_btn = config.PIN_IN_BTN_2
while True:
if keep_btn.value() == 1:
config.LCD.move_to(0,1) # move to the second row
config.LCD.putstr(f"Stay {'on' if current_value else 'off'}!".center(16))
sleep(0.5)
return True
if change_btn.value() == 1:
config.STARTUP_WELCOME_SHOW = not current_value
config.LCD.move_to(0,1) # move to the second row
config.LCD.putstr(f"Turned {'on' if not current_value else 'off'}!".center(16))
sleep(0.5)
return True
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"Saved!".center(16)) # show a little info that it is now set
sleep(0.5)
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"Saved!".center(16)) # show a little info that it is now set
sleep(0.5)
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:
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
sleep(0.5)
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
sleep(0.5)
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
def set_log_level():
config.LCD.clear()
current_level = config.LOG_LEVEL
log_levels = ["WARN", "INFO", "DEBUG"]
config.LCD.putstr(f"Log level".center(16))
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
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.LOG_LEVEL = current_level
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
sleep(0.5)
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
pass
return True
current_level -= 1
if current_level < 0: current_level = 2
config.LCD.move_to(0,0)
config.LCD.putstr(f"Log level".center(16))
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
if btn_right.value() == 1:
sleep(0.1)
if btn_left.value() == 1:
config.LOG_LEVEL = current_level
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
sleep(0.5)
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
pass
return True
current_level += 1
if current_level > 2: current_level = 0
config.LCD.move_to(0,0)
config.LCD.putstr(f"Log level".center(16))
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
def reset(): # reset all user-settable configuration to the default values
config.LCD.clear()
config.LCD.putstr("Sure about that?")
config.LCD.putstr("< no yes >")
no_btn = config.PIN_IN_BTN_1
yes_btn = config.PIN_IN_BTN_2
while True:
if no_btn.value() == 1:
return None
if yes_btn.value() == 1:
config.LCD.putstr("Resetting...".center(16))
config.LCD.putstr("Welcome Screen".center(16))
config.STARTUP_WELCOME_SHOW = True
config.STARTUP_WELCOME_CYCLES = 1
sleep(0.5)
config.LCD.move_to(0,1)
config.LCD.putstr("Timers".center(16))
config.TIMER_1_DURATION = 60
config.TIMER_2_DURATION = 2400
config.TIMER_3_DURATION = 2700
sleep(0.5)
config.LCD.move_to(0,1)
config.LCD.putstr("Logging".center(16))
config.LOG_LEVEL = 1
sleep(0.5)
return True
settings_programs = [("Show welcome", toggle_show_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)),
("Log level", set_log_level),
("Reset", reset),
("Exit", settings_menu.stop)]
settings_menu.setup(settings_programs) # give it the callback list
settings_menu.run() # run the menu until it's closed
if __name__ == "__main__":
from utils import Config, log
from lcdMenu import lcdMenu
config = Config()
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
run(Config(), btn_mapping, log, lcdMenu)

73
programs/timers.py Normal file
View File

@ -0,0 +1,73 @@
"""
uv-belichter-software - the TIMER submenu
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 <https://www.gnu.org/licenses/>.
"""
from time import sleep, time_ns
# All the arguments this method takes are there for one reason: reduce the amount of libraries loaded into the picos memory and thus improving performance
# config: utils.Config object
# btn_mapping: a dict containing the btn mapping for the menu operation (given to the lcdMenu object)
# log: the utils.log function
# lcdMenu: the lcdMenu class (not an object of that class!)
def run(config, btn_mapping, log, lcdMenu):
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()
return True # disable the "Quitting" message from lcdMenu
if __name__ == "__main__":
from utils import Config, log
from lcdMenu import lcdMenu
config = Config()
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
run(Config(), btn_mapping, log, lcdMenu)