From 533deb283311ff104fb1fbce8b49f94b7989454d Mon Sep 17 00:00:00 2001 From: BlueFox Date: Fri, 15 Nov 2024 13:23:20 +0100 Subject: [PATCH] Now using lcdMenu as the ProgramChooser library has been retired --- config.py | 2 +- input-tests.py => input_tests.py | 21 +- lcd_big_hello.py | 2 +- lib/{PCF8574T.py => PCF8574T/__init__.py} | 0 lib/ProgramChooserAdapted.py | 93 ------- .../__init__.py} | 0 lib/lcdMenu/__init__.py | 241 ++++++++++++++++++ main.py | 100 +++++--- 8 files changed, 320 insertions(+), 139 deletions(-) rename input-tests.py => input_tests.py (54%) rename lib/{PCF8574T.py => PCF8574T/__init__.py} (100%) delete mode 100644 lib/ProgramChooserAdapted.py rename lib/{WelcomeScreen.py => WelcomeScreen/__init__.py} (100%) create mode 100644 lib/lcdMenu/__init__.py diff --git a/config.py b/config.py index 378bbcf..e1a870e 100644 --- a/config.py +++ b/config.py @@ -62,4 +62,4 @@ LCD_I2C_NUM_COLS = 16 # and how many characters can it display per row? LCD = I2C_LCD(I2C(0, sda=LCD_SDA, scl=LCD_SCL, freq=400000), LCD_I2C_ADDR, LCD_I2C_NUM_ROWS, - LCD_I2C_NUM_COLS) + LCD_I2C_NUM_COLS) \ No newline at end of file diff --git a/input-tests.py b/input_tests.py similarity index 54% rename from input-tests.py rename to input_tests.py index c332781..1450fad 100644 --- a/input-tests.py +++ b/input_tests.py @@ -9,11 +9,24 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see . """ - import config as cfg from time import sleep + +def run(endless_loop: bool = True, serial_output: bool = True): + while endless_loop: + if cfg.BTN_1.value() and cfg.BTN_2.value() and cfg.SWITCH.value(): + cfg.LCD.move_to(0,0) + cfg.LCD.putstr("In: Y1 | G1 | S1 Exiting! ") + if serial_output: + print("All configured inputs pressed! Exiting...") + sleep(0.2) + break + cfg.LCD.move_to(0,0) + cfg.LCD.putstr(f"In: Y{cfg.BTN_1.value()} | G{cfg.BTN_2.value()} | S{cfg.SWITCH.value()}Push all to exit") + if serial_output: + print(f"Y_BTN: {cfg.BTN_1.value()}; G_BTN: {cfg.BTN_2.value()}; Lever: {cfg.SWITCH.value()}") + sleep(0.05) + if __name__ == "__main__": - while True: - print(f"Y_BTN: {cfg.BTN_1.value()}; G_BTN: {cfg.BTN_2.value()}; Lever: {cfg.SWITCH.value()}") - sleep(0.1) + run() # run the program \ No newline at end of file diff --git a/lcd_big_hello.py b/lcd_big_hello.py index 262dc40..eac1c99 100644 --- a/lcd_big_hello.py +++ b/lcd_big_hello.py @@ -1,5 +1,5 @@ """ -An example "program" which can be used with the ProgramChooser library, see also main.py +An example "program" which can be used with the lcdMenu library, see also main.py 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. diff --git a/lib/PCF8574T.py b/lib/PCF8574T/__init__.py similarity index 100% rename from lib/PCF8574T.py rename to lib/PCF8574T/__init__.py diff --git a/lib/ProgramChooserAdapted.py b/lib/ProgramChooserAdapted.py deleted file mode 100644 index 5f1eea1..0000000 --- a/lib/ProgramChooserAdapted.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -ProgramChooser - a program launcher for microPython (adapted to the UV Belichter needs) -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, config -from PCF8574T import I2C_LCD -import time - -class ProgramChooser: - def __init__(self, programs, debug=False, run_directly=False): - self.lcd = config.LCD - self.ok_btn = config.BTN_1 - self.next_btn = config.BTN_2 - - self.lcd.move_to(0,0) - self.lcd.putstr("[ PROGRAMS ]< >") - - self.current_selection = None # no selection - self.programs = programs # a dictionary of programs and it's callbacks e.g. {"lora_test": some_callback} - - self.show_selection() - - if run_directly: self.run() - - def log(self, msg, is_debug=False): - if is_debug: - utils.log(2, f"[ProgramChooser] {msg}") - else: - utils.log(1, f"[ProgramChooser] {msg}") - - - def show_selection(self): - self.lcd.move_to(1,1) - - if len(self.programs) == 0: - self.lcd.putstr(" No programs!") - return True - if self.current_selection == None: # set it initially - self.current_selection = 0 - - # the actual displaying process - to_show = list(self.programs.keys())[self.current_selection] - if len(to_show) > 14: - to_show = to_show[:13] + chr(0) - else: - to_show = to_show[:14] - to_show = to_show.center(14) - self.lcd.putstr(to_show) - return True - - - def run(self): - while True: - if self.next_btn.value() == 1: - former_program_name = list(self.programs.keys())[self.current_selection] - self.current_selection = (self.current_selection+1)%len(list(self.programs.keys())) - self.show_selection() - now_program_name = list(self.programs.keys())[self.current_selection] - self.log(f"Selected next program (\"{former_program_name}\" -> \"{now_program_name}\")") - while self.next_btn.value() == 1: time.sleep(0.01) # wait till release - if self.ok_btn.value() == 1: - program_name = list(self.programs.keys())[self.current_selection] - self.log(f"Running selected program! (\"{program_name}\")") - # shorten the name for displaying (if too long) - if len(program_name) > 14: - program_name = program_name[:13] + chr(0) - else: - program_name = program_name[:14] - program_name = program_name.center(14) - self.lcd.move_to(0,0) - self.lcd.putstr(f" {program_name} Executing... ") - self.execute_selection() - while self.ok_btn.value() == 1: time.sleep(0.01) # wait till release (e.g. if the "program" is a simple send action) - self.lcd.putstr(f" {program_name} Closing... ") - time.sleep(1) - self.lcd.move_to(0,0) - self.lcd.putstr("[ PROGRAMS ]< >") - self.show_selection() - - time.sleep(0.01) - - - def execute_selection(self): # execute the current selected program's callback - self.programs[list(self.programs.keys())[self.current_selection]]() - diff --git a/lib/WelcomeScreen.py b/lib/WelcomeScreen/__init__.py similarity index 100% rename from lib/WelcomeScreen.py rename to lib/WelcomeScreen/__init__.py diff --git a/lib/lcdMenu/__init__.py b/lib/lcdMenu/__init__.py new file mode 100644 index 0000000..f03b045 --- /dev/null +++ b/lib/lcdMenu/__init__.py @@ -0,0 +1,241 @@ +""" +lcdMenu - A micropython library, which supports vertical and horizontal scrolling through menu items on both 2x16 and 4x20 LCDs +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 . +""" + +from time import sleep + + +class lcdMenu: + # lcd: I2C_LCD - an object of the I2C_LCD class (https://git.privacynerd.de/BlueFox/micropython-libraries/src/branch/main/PCF8574T) + # buttons: dict - a dictionary with machine.Pin objects as items with following keys + # - "prev_btn" - OPTIONAL - when pressed, select the previous menu item + # - "next_btn" - REQUIRED - when pressed, select the next menu item + # - "ok_btn" - REQUIRED - when pressed, call the callback function of the menu item + # menu_items: list - a list (-> maintains order!) containing tuples with the following format: (,) + # scroll_direction: bool - if true, the scrolling direction is horizontal, if false, vertical + # cycle: bool - if true, start again with the first menu entry after the last one (and show the last before the first) + # hide_menu_name: bool - OPTIONAL - if true, hide the menu's name (won't work in combination with a vertical scrolling direction) + # name: str - OPTIONAL - a string with the name of the menu (can be hidden under certain circumstances) + # debounce_time: float - OPTIONAL - the debounce time used by the library to debounce button presses + def __init__(self, lcd, buttons: dict, scroll_direction: bool, cycle: bool, hide_menu_name: bool = False, name: str = "CHOOSE", debounce_time: float = 0.15): + # save the argument variables + self.lcd = lcd + if "prev_btn" in buttons.keys(): + self.prev_btn = buttons["prev_btn"] + else: + self.prev_btn = None + self.next_btn = buttons["next_btn"] + self.ok_btn = buttons["ok_btn"] + self.scroll_direction = scroll_direction + self.cycle = cycle + self.hide_menu_name = hide_menu_name + self.name = name + self.debounce_time = debounce_time + + self.current_selection = 0 # set a standard value (can/most of the time will be changed directly after __init__ by a call to self.setup()) + self.menu_items = [] # set a standard empty (can/most of the time will be changed directly after __init__ by a call to self.setup()) + + # variables that are very unlikely the user want's to set them (but can be set via ., of course) + self.start_execution_msg = "Selected..." # the string displayed when an menu item is selected + self.end_execution_msg = "Closing..." # the string displayed when the callback function of a selected menu item returns self.end_execution_wait = 1 # the time (in seconds) to wait after the callback function of a selected menu item returns + self.start_execution_wait = 0.5 # the time (in seconds) to wait before the callback function of a selected menu item is called + self.end_execution_wait = 1 # the time (in seconds) to wait after the callback function of a selected menu item returns + self.fill_char = '-' # the character used to fill up space (used only on 4x20 displays); MUST BE 1 character long + + + # menu_items: list - a list (-> maintains order!) containing tuples with the following format: (,) + # start_selection: int - OPTIONAL - the index of the item selected by default (starting with 0) - DON'T USE NEGATIVE INDEXES + def setup(self, menu_items: list, start_selection: int = 0): + self.menu_items = menu_items + self.current_selection = start_selection + + + def show_selection(self): + # some checks: + # 1. if you scrolling vertically, I found no elegant way to hide the name (there just need's to be something up there!) + if self.scroll_direction and self.hide_menu_name: + raise TypeError("Hiding the menu name whilst having the scroll direction set to horizontal is impossible!") + # 2. if there are no menu items to display... + if len(self.menu_items) == 0: + raise TypeError("Can't show empty menus! Maybe you forgot calling self.setup() after initializing me?") + + # get some often used values into local variables + selection_name = self.menu_items[self.current_selection][0] + lw = self.lcd.num_columns + + # fill the custom character fields in the displays memory + self.lcd.custom_char(0, bytearray([0x04,0x0A,0x11,0x00,0x00,0x00,0x00,0x00])) # arrow up + self.lcd.custom_char(1, bytearray([0x00,0x00,0x00,0x00,0x00,0x11,0x0A,0x04])) # arrow down + self.lcd.custom_char(2, bytearray([0x04,0x0A,0x11,0x00,0x00,0x11,0x0A,0x04])) # arrow up and down + self.lcd.custom_char(3, bytearray([0x11,0x0A,0x04,0x00,0x00,0x04,0x0A,0x11])) # no options + self.lcd.custom_char(4, bytearray([0x08,0x08,0x08,0x0F,0x08,0x08,0x08,0x08])) # line with a fork (to show the current selection - v scrolling) + self.lcd.custom_char(5, bytearray([0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08])) # line without a fork (to show unselected items - v scrolling) + self.lcd.custom_char(6, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x00])) # three dots in a row + + # now show it off! + # Horizontal scrolling: + if self.scroll_direction: + self.lcd.move_to(0,0) + if self.lcd.num_lines == 4: + self.lcd.putstr(f"{self.fill_char*lw}{' '*lw*2}{self.fill_char*lw}") # fill the first and last line with 'fill_char's + self.lcd.move_to(0,1) # move to the second line for the starting message below (takes two lines) + self.lcd.putstr(f"[{self.name[0:lw-2].center(lw-2)}]") # the menu name (cannot be hidden in this mode) + self.lcd.putstr(f"<{selection_name[0:lw-2].center(lw-2)}>") # the current selected menu item's name + # Vertical scrolling: + else: + lines_for_display = self.lcd.num_lines + items_before_selection = self.current_selection # e.g. if current selection is the second -> 1; one item is before this + items_after_selection = len(self.menu_items) - self.current_selection - 1 + self.lcd.move_to(0,0) + if not self.hide_menu_name: + self.lcd.putstr(f"[{self.name[0:lw-2].center(lw-2)}]") # print the menu name surrounded by sqare brackets [] + lines_for_display -= 1 # decrease the amount of lines available for displaying menu items by 1 (as it's used for the menu name) + + # now we want to display the selections + # where the currently selected item should be the uppermost if possible + # only at the end of the item list, the current item has to be in a lower line... + # ... to avoid emtpy lines at the end as this would seem unprofessional/unclean to me + # but it adds an noticeable amount of extra work / a complex algorithm + if items_after_selection < (lines_for_display - 1): # if there aren't enough items to fill the display after the current selection + # maybe the following could be done with a crazy math formula - but I want to keep it simpler! + # as there aren't enough menu items after the current selection to fill the display, we have to... + # ... calculate where to place the current selection, how many items there are before it and how many after it + menu_items_cut = self.menu_items[::-1][:lines_for_display][::-1] # cut the menu_items list to the relevant last n ones maintaining order (n = number of lines for display) + current_pos_in_cut = -len(self.menu_items) + self.current_selection + lines_for_display # calculate the current index of the selection in the new cut + # draw all the lines + for i in range(lines_for_display): + if i == current_pos_in_cut: # if drawing the currently selected item + self.lcd.putstr(f"{chr(4)} {menu_items_cut[i][0][0:lw-4]}") + self.lcd.putstr(" " * ((lw-4)-len(menu_items_cut[i][0][0:lw-4]))) # fit the line + else: + self.lcd.putstr(f"{chr(5)} {menu_items_cut[i][0][0:lw-4]}") + self.lcd.putstr(" " * ((lw-4)-len(menu_items_cut[i][0][0:lw-4]))) # fit the line + # now the arrow + if i == 0: # if the first element is drawn, think about printing or not printing the up arrow + if self.current_selection == 0 and not self.cycle: # first item selected and no cycling + self.lcd.putstr(" ") # leave the line with spaces + else: + self.lcd.putstr(" " + chr(0)) + elif i == lines_for_display-1: # if the last element is drawn, print a down arrow? + if self.current_selection == (len(self.menu_items)-1) and not self.cycle: # first item selected and no cycling + self.lcd.putstr(" ") # leave the line with spaces + else: + self.lcd.putstr(" " + chr(1)) # arrow down + + else: # there are enough items to fill the display after the current selection + # the first line + self.lcd.putstr(f"{chr(4)} {selection_name[0:lw-4]}") + self.lcd.putstr(" " * ((lw-4)-len(selection_name[0:lw-4]))) # fill the line with spaces up to 2 before the end of the line + + if len(self.menu_items) <= 1: + self.lcd.putstr(" " + chr(3)) # no options icon (as there's only one menu item!) + elif lines_for_display == 1: # if there is exactly one line to display the menu entries... + if self.current_selection == 0 and not self.cycle: # ...and the first element is selected and cycling is disabled so you can't go back + self.lcd.putstr(" "+chr(1)) + elif self.current_selection == (len(self.menu_items)-1) and not self.cycle: # ...as before but with the last element -> you can't go further + self.lcd.putstr(" "+chr(0)) + else: # ...or anything else (cycling or in the middle -> you can go in both directions + self.lcd.putstr(" "+chr(2)) + elif self.current_selection == 0 and not self.cycle: # first item selected and no cycling + self.lcd.putstr(" ") # leave the line with spaces + else: + self.lcd.putstr(" " + chr(0)) + + # the other lines... (if existing!) + for i in range(lines_for_display-1): + self.lcd.putstr(f"{chr(5)} {self.menu_items[self.current_selection+i+1][0][0:lw-4]}") + self.lcd.putstr(" " * ((lw-4)-len(self.menu_items[self.current_selection+i+1][0][0:lw-4]))) # fit the line + # check if it's the last line being drawn... + if (i+1) == (lines_for_display-1): + if self.current_selection == (len(self.menu_items)-1) and not self.cycle: # last item selected and no cycling + self.lcd.putstr(" ") + else: + self.lcd.putstr(" " + chr(1)) + else: # else: if it's not the last line, leave the last two columns of the line empty + self.lcd.putstr(" ") + + + + def previous_selection(self): + self.current_selection -= 1 + if self.current_selection < 0: # after the last element: + if self.cycle: # if cycling is enabled, set it to the index of the last element + self.current_selection = len(self.menu_items)-1 + else: # else, go to first element + self.current_selection = 0 + + + def next_selection(self): + self.current_selection += 1 + if self.current_selection >= len(self.menu_items): # after the last element: + if self.cycle: # if cycling is enabled, go to first element + self.current_selection = 0 + else: # else, set it to the index of the last element + self.current_selection = len(self.menu_items)-1 + + + def execute_selection(self): + selection = self.menu_items[self.current_selection] + lw = self.lcd.num_columns + + # if the program executed had no display (so the user notices something happens!) + self.lcd.move_to(0,0) + if self.lcd.num_lines == 4: + self.lcd.putstr(f"{self.fill_char*lw}{' '*lw*2}{self.fill_char*lw}") # fill the first and last line with 'fill_char's + self.lcd.move_to(0,1) # move to the second line for the starting message below (takes two lines) + self.lcd.putstr(f"[{selection[0][0:lw-2].center(lw-2)}]{self.start_execution_msg[0:lw].center(lw)}") + sleep(self.start_execution_wait) # wait some time before execution (so that the text can be read) + + # run the program + return_value = selection[1]() + + # show a exit when there's no specific return value + if not return_value: # if the return value is None / nothing was returned -> show a closing message + while self.ok_btn.value() == 1: time.sleep(self.debounce_time) # wait till ok_btn release (e.g. if the "program" is a simple send action) + self.lcd.move_to(0,0) + if self.lcd.num_lines == 4: + self.lcd.putstr(f"{self.fill_char*lw}{' '*lw*2}{self.fill_char*lw}") # fill the first and last line with 'fill_char's + self.lcd.move_to(0,1) # move to the second line for the starting message below (takes two lines) + self.lcd.putstr(f"[{selection[0][0:lw].center(lw-2)}]{self.end_execution_msg[0:lw].center(lw)}") + sleep(self.end_execution_wait) + self.show_selection() + else: # -> show no message and quit directly back into the lcdMenu + self.show_selection() + + + # listen for button presses (this method should be called in an endless loop, see method run) + def loop(self): + if self.prev_btn: + if self.prev_btn.value() == 1: + self.previous_selection() + self.show_selection() + while self.prev_btn.value() == 1: sleep(self.debounce_time) # wait till release + if self.next_btn.value() == 1: + self.next_selection() + self.show_selection() + while self.next_btn.value() == 1: sleep(self.debounce_time) # wait till release + if self.ok_btn.value() == 1: + while self.ok_btn.value() == 1: sleep(self.debounce_time) # wait till release + self.execute_selection() + + def stop(self): # here to act as a callback for a menu entry (if the user wants to ofc!) + self.running = False + return True + + def run(self): + # show the selection first + self.show_selection() + self.running = True + + # then listen on button presses in a loop... + while self.running: + self.loop() + + return True # to prevent a "Closing menu ..." in submenu-situations diff --git a/main.py b/main.py index 7f78a35..e6d5d51 100644 --- a/main.py +++ b/main.py @@ -10,69 +10,89 @@ You should have received a copy of the GNU General Public License along with thi """ -import config, utils -from lib.ProgramChooserAdapted import ProgramChooser -from lib.WelcomeScreen import WelcomeScreen +import utils +from lcdMenu import lcdMenu +from WelcomeScreen import WelcomeScreen from time import sleep import gc # garbage collector for better memory performance # extra functions to access the garbage collector def manual(): - config.LCD.clear() - set_value = config.RELAIS.value() - config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") + utils.cfg.LCD.clear() + set_value = utils.cfg.RELAIS.value() + utils.cfg.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") while True: - if set_value != config.SWITCH.value(): - config.RELAIS.value(config.SWITCH.value()) - set_value = config.RELAIS.value() - config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") - if config.BTN_1.value() == 1 or config.BTN_2.value() == 1: - return # exit on press of these buttons + if set_value != utils.cfg.SWITCH.value(): + utils.cfg.RELAIS.value(utils.cfg.SWITCH.value()) + set_value = utils.cfg.RELAIS.value() + utils.cfg.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") + if utils.cfg.BTN_1.value() == 1 or utils.cfg.BTN_2.value() == 1: + return True # exit on press of these buttons def timer(): # display WIP - config.LCD.clear() - config.LCD.putstr(" Still work-in-progress") + utils.cfg.LCD.clear() + utils.cfg.LCD.putstr(" Still work-in-progress") sleep(3) + return True # disable the "Quitting" message from lcdMenu def uv_on(): - config.RELAIS.value(1) - config.LCD.clear() - config.LCD.putstr(" UV turned on ") + utils.cfg.RELAIS.value(1) + utils.cfg.LCD.clear() + utils.cfg.LCD.putstr("------ UV ------ turned on ") sleep(1) + return True # disable the "Quitting" message from lcdMenu def uv_off(): - config.RELAIS.value(0) - config.LCD.clear() - config.LCD.putstr("------ UV ------ turned off ") + utils.cfg.RELAIS.value(0) + utils.cfg.LCD.clear() + utils.cfg.LCD.putstr("------ UV ------ turned off ") sleep(1) + return True # disable the "Quitting" message from lcdMenu def lcd_big_hello(): import lcd_big_hello lcd_big_hello.run() gc.collect() + return True +def input_tests(): + import input_tests as input_tests + input_tests.run(serial_output=False) + gc.collect() + return True def settings(): # display WIP - config.LCD.clear() - config.LCD.putstr(" Still work-in-progress") + utils.cfg.LCD.clear() + utils.cfg.LCD.putstr(" Still work-in-progress") sleep(3) + return True -# create a programs dict, where the items are callables (functions) -programs = { - "Settings": settings, - "LCD Demo": lcd_big_hello, - "UV off": uv_off, - "UV on": uv_on, - "Timer": timer, - "Manual": manual, -} -if config.STARTUP_WELCOME_SHOW: - ws = WelcomeScreen(config.LCD, - interrupt_pins=[config.BTN_1, config.BTN_2, config.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) +if utils.cfg.STARTUP_WELCOME_SHOW: + ws = WelcomeScreen(utils.cfg.LCD, + interrupt_pins=[utils.cfg.BTN_1, utils.cfg.BTN_2, utils.cfg.SWITCH], + subtitle=utils.cfg.STARTUP_PROJECT_NAME, + starting_msg=utils.cfg.STARTUP_MESSAGE_STARTING, + started_msg=utils.cfg.STARTUP_MESSAGE_FINISHED) + ws.show(cycles=utils.cfg.STARTUP_WELCOME_CYCLES) del ws gc.collect() -pc = ProgramChooser(programs) # initialize the ProgramChooser -pc.run() # and run it (will be an endless loop) + +# create the menus +btn_mapping = {"ok_btn": utils.cfg.BTN_1, "next_btn": utils.cfg.BTN_2} # the btn mapping for all menus + +demo_menu = lcdMenu(utils.cfg.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 + +main_menu = lcdMenu(utils.cfg.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="PROGRAMS") +main_programs = [("Timer", timer), + ("Manual", manual), + ("UV off", uv_off), + ("UV on", uv_on), + ("Demos", demo_menu.run), + ("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()