Compare commits

..

9 Commits

Author SHA1 Message Date
dw-0
df92ae25b2 Merge aa1b435da5 into f2691f33d3 2024-04-17 20:05:43 +02:00
dw-0
aa1b435da5 feat: implement build + flash process
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-17 19:58:40 +02:00
dw-0
449317b118 fix: fix sd flash process
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-17 19:48:31 +02:00
dw-0
336414c43c fix: init previous_menu in menus
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-15 22:12:14 +02:00
dw-0
cd63034b74 fix: ignore flash method when checking for firmware files
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-15 21:52:32 +02:00
dw-0
8de7ab7e11 fix: wrong default previous menu for KlipperFlashMethodMenu
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-15 21:37:25 +02:00
dw-0
c2b0ca5b19 fix: typo
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-15 21:31:54 +02:00
dw-0
ecb673a088 feat: implement firmware build
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-15 21:29:13 +02:00
dw-0
da4c5fe109 refactor: rework of menu lifecycle and option handling
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-14 22:11:40 +02:00
20 changed files with 658 additions and 276 deletions

View File

@@ -8,34 +8,42 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper import klipper_remove from components.klipper import klipper_remove
from core.menus import FooterType from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class KlipperRemoveMenu(BaseMenu): class KlipperRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu
self.options = {
"0": self.toggle_all,
"1": self.toggle_remove_klipper_service,
"2": self.toggle_remove_klipper_dir,
"3": self.toggle_remove_klipper_env,
"4": self.toggle_delete_klipper_logs,
"c": self.run_removal_process,
}
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
self.remove_klipper_service = False self.remove_klipper_service = False
self.remove_klipper_dir = False self.remove_klipper_dir = False
self.remove_klipper_env = False self.remove_klipper_env = False
self.delete_klipper_logs = False self.delete_klipper_logs = False
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.remove_menu import RemoveMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else RemoveMenu
)
def set_options(self) -> None:
self.options = {
"0": Option(method=self.toggle_all, menu=False),
"1": Option(method=self.toggle_remove_klipper_service, menu=False),
"2": Option(method=self.toggle_remove_klipper_dir, menu=False),
"3": Option(method=self.toggle_remove_klipper_env, menu=False),
"4": Option(method=self.toggle_delete_klipper_logs, menu=False),
"c": Option(method=self.run_removal_process, menu=False),
}
def print_menu(self) -> None: def print_menu(self) -> None:
header = " [ Remove Klipper ] " header = " [ Remove Klipper ] "
color = COLOR_RED color = COLOR_RED

View File

@@ -7,31 +7,31 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import subprocess from subprocess import CalledProcessError, check_output, Popen, PIPE, STDOUT, run
from subprocess import CalledProcessError, check_output, Popen, PIPE, STDOUT
from typing import List from typing import List
from components.klipper import KLIPPER_DIR from components.klipper import KLIPPER_DIR
from components.klipper.klipper import Klipper
from components.klipper_firmware import SD_FLASH_SCRIPT from components.klipper_firmware import SD_FLASH_SCRIPT
from components.klipper_firmware.flash_options import ( from components.klipper_firmware.flash_options import (
FlashOptions, FlashOptions,
FlashMethod, FlashMethod,
) )
from core.instance_manager.instance_manager import InstanceManager
from utils.logger import Logger from utils.logger import Logger
from utils.system_utils import log_process from utils.system_utils import log_process
def find_firmware_file(method: FlashMethod) -> bool: def find_firmware_file() -> bool:
target = KLIPPER_DIR.joinpath("out") target = KLIPPER_DIR.joinpath("out")
target_exists = target.exists() target_exists = target.exists()
if method is FlashMethod.REGULAR:
f1 = "klipper.elf.hex" f1 = "klipper.elf.hex"
f2 = "klipper.elf" f2 = "klipper.elf"
fw_file_exists = target.joinpath(f1).exists() and target.joinpath(f2).exists() f3 = "klipper.bin"
elif method is FlashMethod.SD_CARD: fw_file_exists = (
fw_file_exists = target.joinpath("klipper.bin").exists() target.joinpath(f1).exists() and target.joinpath(f2).exists()
else: ) or target.joinpath(f3).exists()
raise Exception("Unknown flash method")
return target_exists and fw_file_exists return target_exists and fw_file_exists
@@ -75,9 +75,9 @@ def get_sd_flash_board_list() -> List[str]:
try: try:
cmd = f"{SD_FLASH_SCRIPT} -l" cmd = f"{SD_FLASH_SCRIPT} -l"
blist = subprocess.check_output(cmd, shell=True, text=True) blist = check_output(cmd, shell=True, text=True)
return blist.splitlines()[1:] return blist.splitlines()[1:]
except subprocess.CalledProcessError as e: except CalledProcessError as e:
Logger.print_error(f"An unexpected error occured:\n{e}") Logger.print_error(f"An unexpected error occured:\n{e}")
@@ -108,18 +108,22 @@ def start_flash_process(flash_options: FlashOptions) -> None:
if not SD_FLASH_SCRIPT.exists(): if not SD_FLASH_SCRIPT.exists():
raise Exception("Unable to find Klippers sdcard flash script!") raise Exception("Unable to find Klippers sdcard flash script!")
cmd = [ cmd = [
SD_FLASH_SCRIPT, SD_FLASH_SCRIPT.as_posix(),
"-b", f"-b {flash_options.selected_baudrate}",
flash_options.selected_baudrate,
flash_options.selected_mcu, flash_options.selected_mcu,
flash_options.selected_board, flash_options.selected_board,
] ]
else: else:
raise Exception("Invalid value for flash_method!") raise Exception("Invalid value for flash_method!")
instance_manager = InstanceManager(Klipper)
instance_manager.stop_all_instance()
process = Popen(cmd, cwd=KLIPPER_DIR, stdout=PIPE, stderr=STDOUT, text=True) process = Popen(cmd, cwd=KLIPPER_DIR, stdout=PIPE, stderr=STDOUT, text=True)
log_process(process) log_process(process)
instance_manager.start_all_instance()
rc = process.returncode rc = process.returncode
if rc != 0: if rc != 0:
raise Exception(f"Flashing failed with returncode: {rc}") raise Exception(f"Flashing failed with returncode: {rc}")
@@ -129,3 +133,42 @@ def start_flash_process(flash_options: FlashOptions) -> None:
except (Exception, CalledProcessError): except (Exception, CalledProcessError):
Logger.print_error("Flashing failed!", start="\n") Logger.print_error("Flashing failed!", start="\n")
Logger.print_error("See the console output above!", end="\n\n") Logger.print_error("See the console output above!", end="\n\n")
def run_make_clean() -> None:
try:
run(
"make clean",
cwd=KLIPPER_DIR,
shell=True,
check=True,
)
except CalledProcessError as e:
Logger.print_error(f"Unexpected error:\n{e}")
raise
def run_make_menuconfig() -> None:
try:
run(
"make PYTHON=python3 menuconfig",
cwd=KLIPPER_DIR,
shell=True,
check=True,
)
except CalledProcessError as e:
Logger.print_error(f"Unexpected error:\n{e}")
raise
def run_make() -> None:
try:
run(
"make PYTHON=python3",
cwd=KLIPPER_DIR,
shell=True,
check=True,
)
except CalledProcessError as e:
Logger.print_error(f"Unexpected error:\n{e}")
raise

View File

@@ -0,0 +1,112 @@
# ======================================================================= #
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
import textwrap
from typing import Type, Optional
from components.klipper import KLIPPER_DIR
from components.klipper_firmware.firmware_utils import (
run_make_clean,
run_make_menuconfig,
run_make,
)
from core.menus import Option
from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_GREEN, COLOR_RED
from utils.logger import Logger
from utils.system_utils import (
check_package_install,
update_system_package_lists,
install_system_packages,
)
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperBuildFirmwareMenu(BaseMenu):
def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__()
self.previous_menu = previous_menu
self.deps = ["build-essential", "dpkg-dev", "make"]
self.missing_deps = check_package_install(self.deps)
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.advanced_menu import AdvancedMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else AdvancedMenu
)
def set_options(self) -> None:
if len(self.missing_deps) == 0:
self.input_label_txt = "Press ENTER to continue"
self.default_option = Option(method=self.start_build_process, menu=False)
else:
self.input_label_txt = "Press ENTER to install dependencies"
self.default_option = Option(method=self.install_missing_deps, menu=False)
def print_menu(self) -> None:
header = " [ Build Firmware Menu ] "
color = COLOR_CYAN
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| The following dependencies are required: |
| |
"""
)[1:]
for d in self.deps:
status_ok = f"{COLOR_GREEN}*INSTALLED*{RESET_FORMAT}"
status_missing = f"{COLOR_RED}*MISSING*{RESET_FORMAT}"
status = status_missing if d in self.missing_deps else status_ok
padding = 39 - len(d) + len(status) + (len(status_ok) - len(status))
d = f" {COLOR_CYAN}{d}{RESET_FORMAT}"
menu += f"| {d}{status:>{padding}} |\n"
menu += "| |\n"
if len(self.missing_deps) == 0:
line = f"{COLOR_GREEN}All dependencies are met!{RESET_FORMAT}"
else:
line = f"{COLOR_RED}Dependencies are missing!{RESET_FORMAT}"
menu += f"| {line:<62} |\n"
print(menu, end="")
def install_missing_deps(self, **kwargs) -> None:
try:
update_system_package_lists(silent=False)
Logger.print_status("Installing system packages...")
install_system_packages(self.missing_deps)
except Exception as e:
Logger.print_error(e)
Logger.print_error("Installding dependencies failed!")
finally:
# restart this menu
KlipperBuildFirmwareMenu().run()
def start_build_process(self, **kwargs) -> None:
try:
run_make_clean()
run_make_menuconfig()
run_make()
Logger.print_ok("Firmware successfully built!")
Logger.print_ok(f"Firmware file located in '{KLIPPER_DIR}/out'!")
except Exception as e:
Logger.print_error(e)
Logger.print_error("Building Klipper Firmware failed!")
finally:
self.previous_menu().run()

View File

@@ -7,9 +7,10 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Optional, Type
from components.klipper_firmware.flash_options import FlashOptions, FlashMethod from components.klipper_firmware.flash_options import FlashOptions, FlashMethod
from core.menus import FooterType from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_RED, RESET_FORMAT from utils.constants import COLOR_RED, RESET_FORMAT
@@ -17,14 +18,20 @@ from utils.constants import COLOR_RED, RESET_FORMAT
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperNoFirmwareErrorMenu(BaseMenu): class KlipperNoFirmwareErrorMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
self.default_option = self.go_back
self.footer_type = FooterType.BLANK self.footer_type = FooterType.BLANK
self.input_label_txt = "Press ENTER to go back to [Advanced Menu]" self.input_label_txt = "Press ENTER to go back to [Advanced Menu]"
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu = previous_menu
def set_options(self) -> None:
self.default_option = Option(self.go_back, False)
def print_menu(self) -> None: def print_menu(self) -> None:
header = "!!! NO FIRMWARE FILE FOUND !!!" header = "!!! NO FIRMWARE FILE FOUND !!!"
color = COLOR_RED color = COLOR_RED
@@ -60,13 +67,18 @@ class KlipperNoFirmwareErrorMenu(BaseMenu):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperNoBoardTypesErrorMenu(BaseMenu): class KlipperNoBoardTypesErrorMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.default_option = self.go_back
self.footer_type = FooterType.BLANK self.footer_type = FooterType.BLANK
self.input_label_txt = "Press ENTER to go back to [Main Menu]" self.input_label_txt = "Press ENTER to go back to [Main Menu]"
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu = previous_menu
def set_options(self) -> None:
self.default_option = Option(self.go_back, False)
def print_menu(self) -> None: def print_menu(self) -> None:
header = "!!! ERROR GETTING BOARD LIST !!!" header = "!!! ERROR GETTING BOARD LIST !!!"
color = COLOR_RED color = COLOR_RED

View File

@@ -7,16 +7,29 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
# noinspection DuplicatedCode
class KlipperFlashMethodHelpMenu(BaseMenu): class KlipperFlashMethodHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from components.klipper_firmware.menus.klipper_flash_menu import (
KlipperFlashMethodMenu,
)
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else KlipperFlashMethodMenu
)
def set_options(self) -> None:
pass
def print_menu(self) -> None: def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > " header = " < ? > Help: Flash MCU < ? > "
@@ -57,11 +70,23 @@ class KlipperFlashMethodHelpMenu(BaseMenu):
print(menu, end="") print(menu, end="")
# noinspection DuplicatedCode
class KlipperFlashCommandHelpMenu(BaseMenu): class KlipperFlashCommandHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from components.klipper_firmware.menus.klipper_flash_menu import (
KlipperFlashCommandMenu,
)
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else KlipperFlashCommandMenu
)
def set_options(self) -> None:
pass
def print_menu(self) -> None: def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > " header = " < ? > Help: Flash MCU < ? > "
@@ -89,11 +114,25 @@ class KlipperFlashCommandHelpMenu(BaseMenu):
print(menu, end="") print(menu, end="")
# noinspection DuplicatedCode
class KlipperMcuConnectionHelpMenu(BaseMenu): class KlipperMcuConnectionHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from components.klipper_firmware.menus.klipper_flash_menu import (
KlipperSelectMcuConnectionMenu,
)
self.previous_menu: Type[BaseMenu] = (
previous_menu
if previous_menu is not None
else KlipperSelectMcuConnectionMenu
)
def set_options(self) -> None:
pass
def print_menu(self) -> None: def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > " header = " < ? > Help: Flash MCU < ? > "

View File

@@ -9,6 +9,7 @@
import textwrap import textwrap
import time import time
from typing import Type, Optional
from components.klipper_firmware.flash_options import ( from components.klipper_firmware.flash_options import (
FlashOptions, FlashOptions,
@@ -16,7 +17,7 @@ from components.klipper_firmware.flash_options import (
FlashCommand, FlashCommand,
ConnectionType, ConnectionType,
) )
from components.klipper_firmware.flash_utils import ( from components.klipper_firmware.firmware_utils import (
find_usb_device_by_id, find_usb_device_by_id,
find_uart_device, find_uart_device,
find_usb_dfu_device, find_usb_dfu_device,
@@ -33,7 +34,7 @@ from components.klipper_firmware.menus.klipper_flash_help_menu import (
KlipperFlashCommandHelpMenu, KlipperFlashCommandHelpMenu,
KlipperFlashMethodHelpMenu, KlipperFlashMethodHelpMenu,
) )
from core.menus import FooterType from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW, COLOR_RED from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW, COLOR_RED
@@ -44,20 +45,26 @@ from utils.logger import Logger
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperFlashMethodMenu(BaseMenu): class KlipperFlashMethodMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.help_menu = KlipperFlashMethodHelpMenu self.help_menu = KlipperFlashMethodHelpMenu
self.options = {
"1": self.select_regular,
"2": self.select_sdcard,
}
self.input_label_txt = "Select flash method" self.input_label_txt = "Select flash method"
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.advanced_menu import AdvancedMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else AdvancedMenu
)
def set_options(self) -> None:
self.options = {
"1": Option(self.select_regular, menu=False),
"2": Option(self.select_sdcard, menu=False),
}
def print_menu(self) -> None: def print_menu(self) -> None:
header = " [ MCU Flash Menu ] " header = " [ MCU Flash Menu ] "
subheader = f"{COLOR_YELLOW}ATTENTION:{RESET_FORMAT}" subheader = f"{COLOR_YELLOW}ATTENTION:{RESET_FORMAT}"
@@ -94,8 +101,8 @@ class KlipperFlashMethodMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
if find_firmware_file(self.flash_options.flash_method): if find_firmware_file():
KlipperFlashCommandMenu(previous_menu=self).run() KlipperFlashCommandMenu(previous_menu=self.__class__).run()
else: else:
KlipperNoFirmwareErrorMenu().run() KlipperNoFirmwareErrorMenu().run()
@@ -103,21 +110,25 @@ class KlipperFlashMethodMenu(BaseMenu):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperFlashCommandMenu(BaseMenu): class KlipperFlashCommandMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.help_menu = KlipperFlashCommandHelpMenu self.help_menu = KlipperFlashCommandHelpMenu
self.options = {
"1": self.select_flash,
"2": self.select_serialflash,
}
self.default_option = self.select_flash
self.input_label_txt = "Select flash command" self.input_label_txt = "Select flash command"
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else KlipperFlashMethodMenu
)
def set_options(self) -> None:
self.options = {
"1": Option(self.select_flash, menu=False),
"2": Option(self.select_serialflash, menu=False),
}
self.default_option = Option(self.select_flash, menu=False)
def print_menu(self) -> None: def print_menu(self) -> None:
menu = textwrap.dedent( menu = textwrap.dedent(
""" """
@@ -140,28 +151,35 @@ class KlipperFlashCommandMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
KlipperSelectMcuConnectionMenu(previous_menu=self).run() KlipperSelectMcuConnectionMenu(previous_menu=self.__class__).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperSelectMcuConnectionMenu(BaseMenu): class KlipperSelectMcuConnectionMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu, standalone: bool = False): def __init__(
self, previous_menu: Optional[Type[BaseMenu]] = None, standalone: bool = False
):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.__standalone = standalone self.__standalone = standalone
self.previous_menu: BaseMenu = previous_menu
self.help_menu = KlipperMcuConnectionHelpMenu self.help_menu = KlipperMcuConnectionHelpMenu
self.options = {
"1": self.select_usb,
"2": self.select_dfu,
"3": self.select_usb_dfu,
}
self.input_label_txt = "Select connection type" self.input_label_txt = "Select connection type"
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else KlipperFlashCommandMenu
)
def set_options(self) -> None:
self.options = {
"1": Option(method=self.select_usb, menu=False),
"2": Option(method=self.select_dfu, menu=False),
"3": Option(method=self.select_usb_dfu, menu=False),
}
def print_menu(self) -> None: def print_menu(self) -> None:
header = "Make sure that the controller board is connected now!" header = "Make sure that the controller board is connected now!"
color = COLOR_YELLOW color = COLOR_YELLOW
@@ -221,23 +239,32 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
self.goto_next_menu() self.goto_next_menu()
def goto_next_menu(self, **kwargs): def goto_next_menu(self, **kwargs):
KlipperSelectMcuIdMenu(previous_menu=self).run() KlipperSelectMcuIdMenu(previous_menu=self.__class__).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperSelectMcuIdMenu(BaseMenu): class KlipperSelectMcuIdMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
self.mcu_list = self.flash_options.mcu_list self.mcu_list = self.flash_options.mcu_list
options = {f"{index}": self.flash_mcu for index in range(len(self.mcu_list))}
self.options = options
self.input_label_txt = "Select MCU to flash" self.input_label_txt = "Select MCU to flash"
self.footer_type = FooterType.BACK_HELP self.footer_type = FooterType.BACK_HELP
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu: Type[BaseMenu] = (
previous_menu
if previous_menu is not None
else KlipperSelectMcuConnectionMenu
)
def set_options(self) -> None:
self.options = {
f"{i}": Option(self.flash_mcu, False, f"{i}")
for i in range(len(self.mcu_list))
}
def print_menu(self) -> None: def print_menu(self) -> None:
header = "!!! ATTENTION !!!" header = "!!! ATTENTION !!!"
header2 = f"[{COLOR_CYAN}List of available MCUs{RESET_FORMAT}]" header2 = f"[{COLOR_CYAN}List of available MCUs{RESET_FORMAT}]"
@@ -268,24 +295,30 @@ class KlipperSelectMcuIdMenu(BaseMenu):
self.flash_options.selected_mcu = selected_mcu self.flash_options.selected_mcu = selected_mcu
if self.flash_options.flash_method == FlashMethod.SD_CARD: if self.flash_options.flash_method == FlashMethod.SD_CARD:
KlipperSelectSDFlashBoardMenu(previous_menu=self).run() KlipperSelectSDFlashBoardMenu(previous_menu=self.__class__).run()
elif self.flash_options.flash_method == FlashMethod.REGULAR: elif self.flash_options.flash_method == FlashMethod.REGULAR:
KlipperFlashOverviewMenu(previous_menu=self).run() KlipperFlashOverviewMenu(previous_menu=self.__class__).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperSelectSDFlashBoardMenu(BaseMenu): class KlipperSelectSDFlashBoardMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
self.available_boards = get_sd_flash_board_list() self.available_boards = get_sd_flash_board_list()
self.input_label_txt = "Select board type" self.input_label_txt = "Select board type"
options = {f"{i}": self.board_select for i in range(len(self.available_boards))} def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.options = options self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else KlipperSelectMcuIdMenu
)
def set_options(self) -> None:
self.options = {
f"{i}": Option(self.board_select, False, f"{i}")
for i in range(len(self.available_boards))
}
def print_menu(self) -> None: def print_menu(self) -> None:
if len(self.available_boards) < 1: if len(self.available_boards) < 1:
@@ -331,20 +364,27 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
min_count=0, min_count=0,
allow_go_back=True, allow_go_back=True,
) )
KlipperFlashOverviewMenu(previous_menu=self).run() KlipperFlashOverviewMenu(previous_menu=self.__class__).run()
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperFlashOverviewMenu(BaseMenu): class KlipperFlashOverviewMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.flash_options = FlashOptions() self.flash_options = FlashOptions()
self.options = {"Y": self.execute_flash, "N": self.abort_process}
self.input_label_txt = "Perform action (default=Y)" self.input_label_txt = "Perform action (default=Y)"
self.default_option = self.execute_flash
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu: Type[BaseMenu] = previous_menu
def set_options(self) -> None:
self.options = {
"Y": Option(self.execute_flash, menu=False),
"N": Option(self.abort_process, menu=False),
}
self.default_option = Option(self.execute_flash, menu=False)
def print_menu(self) -> None: def print_menu(self) -> None:
header = "!!! ATTENTION !!!" header = "!!! ATTENTION !!!"
@@ -392,12 +432,10 @@ class KlipperFlashOverviewMenu(BaseMenu):
print(menu, end="") print(menu, end="")
def execute_flash(self, **kwargs): def execute_flash(self, **kwargs):
from core.menus.advanced_menu import AdvancedMenu
start_flash_process(self.flash_options) start_flash_process(self.flash_options)
Logger.print_info("Returning to MCU Flash Menu in 5 seconds ...") Logger.print_info("Returning to MCU Flash Menu in 5 seconds ...")
time.sleep(5) time.sleep(5)
KlipperFlashMethodMenu(previous_menu=AdvancedMenu()).run() KlipperFlashMethodMenu().run()
def abort_process(self, **kwargs): def abort_process(self, **kwargs):
from core.menus.advanced_menu import AdvancedMenu from core.menus.advanced_menu import AdvancedMenu

View File

@@ -8,22 +8,34 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.log_uploads.log_upload_utils import get_logfile_list from components.log_uploads.log_upload_utils import get_logfile_list
from components.log_uploads.log_upload_utils import upload_logfile from components.log_uploads.log_upload_utils import upload_logfile
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_YELLOW from utils.constants import RESET_FORMAT, COLOR_YELLOW
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class LogUploadMenu(BaseMenu): class LogUploadMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu
self.logfile_list = get_logfile_list() self.logfile_list = get_logfile_list()
options = {f"{index}": self.upload for index in range(len(self.logfile_list))}
self.options = options def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self) -> None:
self.options = {
f"{index}": Option(self.upload, False, opt_index=f"{index}")
for index in range(len(self.logfile_list))
}
def print_menu(self): def print_menu(self):
header = " [ Log Upload ] " header = " [ Log Upload ] "

View File

@@ -8,34 +8,43 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.moonraker import moonraker_remove from components.moonraker import moonraker_remove
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class MoonrakerRemoveMenu(BaseMenu): class MoonrakerRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu
self.options = {
"0": self.toggle_all,
"1": self.toggle_remove_moonraker_service,
"2": self.toggle_remove_moonraker_dir,
"3": self.toggle_remove_moonraker_env,
"4": self.toggle_remove_moonraker_polkit,
"5": self.toggle_delete_moonraker_logs,
"c": self.run_removal_process,
}
self.remove_moonraker_service = False self.remove_moonraker_service = False
self.remove_moonraker_dir = False self.remove_moonraker_dir = False
self.remove_moonraker_env = False self.remove_moonraker_env = False
self.remove_moonraker_polkit = False self.remove_moonraker_polkit = False
self.delete_moonraker_logs = False self.delete_moonraker_logs = False
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.remove_menu import RemoveMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else RemoveMenu
)
def set_options(self) -> None:
self.options = {
"0": Option(method=self.toggle_all, menu=False),
"1": Option(method=self.toggle_remove_moonraker_service, menu=False),
"2": Option(method=self.toggle_remove_moonraker_dir, menu=False),
"3": Option(method=self.toggle_remove_moonraker_env, menu=False),
"4": Option(method=self.toggle_remove_moonraker_polkit, menu=False),
"5": Option(method=self.toggle_delete_moonraker_logs, menu=False),
"c": Option(method=self.run_removal_process, menu=False),
}
def print_menu(self) -> None: def print_menu(self) -> None:
header = " [ Remove Moonraker ] " header = " [ Remove Moonraker ] "
color = COLOR_RED color = COLOR_RED

View File

@@ -8,35 +8,43 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Callable, Dict from typing import Dict, Type, Optional
from components.webui_client import client_remove from components.webui_client import client_remove
from components.webui_client.base_data import BaseWebClient, WebClientType from components.webui_client.base_data import BaseWebClient, WebClientType
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN from utils.constants import RESET_FORMAT, COLOR_RED, COLOR_CYAN
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class ClientRemoveMenu(BaseMenu): class ClientRemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu, client: BaseWebClient): def __init__(
self, client: BaseWebClient, previous_menu: Optional[Type[BaseMenu]] = None
):
super().__init__() super().__init__()
self.previous_menu = previous_menu self.previous_menu = previous_menu
self.options = self.get_options(client)
self.client = client self.client = client
self.rm_client = False self.rm_client = False
self.rm_client_config = False self.rm_client_config = False
self.backup_mainsail_config_json = False self.backup_mainsail_config_json = False
def get_options(self, client: BaseWebClient) -> Dict[str, Callable]: def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.remove_menu import RemoveMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else RemoveMenu
)
def set_options(self) -> Dict[str, Option]:
options = { options = {
"0": self.toggle_all, "0": Option(method=self.toggle_all, menu=False),
"1": self.toggle_rm_client, "1": Option(method=self.toggle_rm_client, menu=False),
"2": self.toggle_rm_client_config, "2": Option(method=self.toggle_rm_client_config, menu=False),
"c": self.run_removal_process, "c": Option(method=self.run_removal_process, menu=False),
} }
if client.client == WebClientType.MAINSAIL: if self.client.client == WebClientType.MAINSAIL:
options["3"] = self.toggle_backup_mainsail_config_json options["3"] = Option(self.toggle_backup_mainsail_config_json, False)
return options return options

View File

@@ -7,7 +7,25 @@
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Callable, Any, Union
@dataclass
class Option:
"""
Represents a menu option.
:param method: Method that will be used to call the menu option
:param menu: Flag for singaling that another menu will be opened
:param opt_index: Can be used to pass the user input to the menu option
:param opt_data: Can be used to pass any additional data to the menu option
"""
method: Union[Callable, None] = None
menu: bool = False
opt_index: str = ""
opt_data: Any = None
class FooterType(Enum): class FooterType(Enum):
@@ -15,11 +33,3 @@ class FooterType(Enum):
BACK = "BACK" BACK = "BACK"
BACK_HELP = "BACK_HELP" BACK_HELP = "BACK_HELP"
BLANK = "BLANK" BLANK = "BLANK"
class ExitAppException(Exception):
pass
class GoBackException(Exception):
pass

View File

@@ -8,30 +8,39 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper_firmware.menus.klipper_build_menu import (
KlipperBuildFirmwareMenu,
)
from components.klipper_firmware.menus.klipper_flash_menu import ( from components.klipper_firmware.menus.klipper_flash_menu import (
KlipperFlashMethodMenu, KlipperFlashMethodMenu,
KlipperSelectMcuConnectionMenu, KlipperSelectMcuConnectionMenu,
) )
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_YELLOW, RESET_FORMAT from utils.constants import COLOR_YELLOW, RESET_FORMAT
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
class AdvancedMenu(BaseMenu): class AdvancedMenu(BaseMenu):
def __init__(self): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu from core.menus.main_menu import MainMenu
self.previous_menu: BaseMenu = MainMenu() self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self):
self.options = { self.options = {
"1": None, "3": Option(method=self.build, menu=True),
"2": None, "4": Option(method=self.flash, menu=False),
"3": None, "5": Option(method=self.build_flash, menu=False),
"4": self.flash, "6": Option(method=self.get_id, menu=False),
"5": None,
"6": self.get_id,
} }
def print_menu(self): def print_menu(self):
@@ -56,8 +65,18 @@ class AdvancedMenu(BaseMenu):
)[1:] )[1:]
print(menu, end="") print(menu, end="")
def build(self, **kwargs):
KlipperBuildFirmwareMenu(previous_menu=self.__class__).run()
def flash(self, **kwargs): def flash(self, **kwargs):
KlipperFlashMethodMenu(previous_menu=self).run() KlipperFlashMethodMenu(previous_menu=self.__class__).run()
def build_flash(self, **kwargs):
KlipperBuildFirmwareMenu(previous_menu=KlipperFlashMethodMenu).run()
KlipperFlashMethodMenu(previous_menu=self.__class__).run()
def get_id(self, **kwargs): def get_id(self, **kwargs):
KlipperSelectMcuConnectionMenu(previous_menu=self, standalone=True).run() KlipperSelectMcuConnectionMenu(
previous_menu=self.__class__,
standalone=True,
).run()

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.klipper_utils import backup_klipper_dir from components.klipper.klipper_utils import backup_klipper_dir
from components.moonraker.moonraker_utils import ( from components.moonraker.moonraker_utils import (
@@ -20,6 +21,7 @@ from components.webui_client.client_utils import (
) )
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.common import backup_printer_config_dir from utils.common import backup_printer_config_dir
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
@@ -28,20 +30,28 @@ from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class BackupMenu(BaseMenu): class BackupMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self) -> None:
self.options = { self.options = {
"1": self.backup_klipper, "1": Option(method=self.backup_klipper, menu=False),
"2": self.backup_moonraker, "2": Option(method=self.backup_moonraker, menu=False),
"3": self.backup_printer_config, "3": Option(method=self.backup_printer_config, menu=False),
"4": self.backup_moonraker_db, "4": Option(method=self.backup_moonraker_db, menu=False),
"5": self.backup_mainsail, "5": Option(method=self.backup_mainsail, menu=False),
"6": self.backup_fluidd, "6": Option(method=self.backup_fluidd, menu=False),
"7": self.backup_mainsail_config, "7": Option(method=self.backup_mainsail_config, menu=False),
"8": self.backup_fluidd_config, "8": Option(method=self.backup_fluidd_config, menu=False),
"9": self.backup_klipperscreen, "9": Option(method=self.backup_klipperscreen, menu=False),
} }
def print_menu(self): def print_menu(self):
@@ -82,16 +92,16 @@ class BackupMenu(BaseMenu):
backup_moonraker_db_dir() backup_moonraker_db_dir()
def backup_mainsail(self, **kwargs): def backup_mainsail(self, **kwargs):
backup_client_data(MainsailData().get()) backup_client_data(MainsailData())
def backup_fluidd(self, **kwargs): def backup_fluidd(self, **kwargs):
backup_client_data(FluiddData().get()) backup_client_data(FluiddData())
def backup_mainsail_config(self, **kwargs): def backup_mainsail_config(self, **kwargs):
backup_client_config_data(MainsailData().get()) backup_client_config_data(MainsailData())
def backup_fluidd_config(self, **kwargs): def backup_fluidd_config(self, **kwargs):
backup_client_config_data(FluiddData().get()) backup_client_config_data(FluiddData())
def backup_klipperscreen(self, **kwargs): def backup_klipperscreen(self, **kwargs):
pass pass

View File

@@ -13,9 +13,9 @@ import subprocess
import sys import sys
import textwrap import textwrap
from abc import abstractmethod from abc import abstractmethod
from typing import Dict, Union, Callable, Type, Tuple from typing import Type, Dict, Optional
from core.menus import FooterType, ExitAppException, GoBackException from core.menus import FooterType, Option
from utils.constants import ( from utils.constants import (
COLOR_GREEN, COLOR_GREEN,
COLOR_YELLOW, COLOR_YELLOW,
@@ -106,12 +106,12 @@ class PostInitCaller(type):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class BaseMenu(metaclass=PostInitCaller): class BaseMenu(metaclass=PostInitCaller):
options: Dict[str, Callable] = {} options: Dict[str, Option] = {}
options_offset: int = 0 options_offset: int = 0
default_option: Union[Callable, None] = None default_option: Option = None
input_label_txt: str = "Perform action" input_label_txt: str = "Perform action"
header: bool = False header: bool = False
previous_menu: Union[Type[BaseMenu], BaseMenu] = None previous_menu: Type[BaseMenu] = None
help_menu: Type[BaseMenu] = None help_menu: Type[BaseMenu] = None
footer_type: FooterType = FooterType.BACK footer_type: FooterType = FooterType.BACK
@@ -120,30 +120,42 @@ class BaseMenu(metaclass=PostInitCaller):
raise NotImplementedError("BaseMenu cannot be instantiated directly.") raise NotImplementedError("BaseMenu cannot be instantiated directly.")
def __post_init__(self): def __post_init__(self):
self.set_previous_menu(self.previous_menu)
self.set_options()
# conditionally add options based on footer type # conditionally add options based on footer type
if self.footer_type is FooterType.QUIT: if self.footer_type is FooterType.QUIT:
self.options["q"] = self.__exit self.options["q"] = Option(method=self.__exit, menu=False)
if self.footer_type is FooterType.BACK: if self.footer_type is FooterType.BACK:
self.options["b"] = self.__go_back self.options["b"] = Option(method=self.__go_back, menu=False)
if self.footer_type is FooterType.BACK_HELP: if self.footer_type is FooterType.BACK_HELP:
self.options["b"] = self.__go_back self.options["b"] = Option(method=self.__go_back, menu=False)
self.options["h"] = self.__go_to_help self.options["h"] = Option(method=self.__go_to_help, menu=False)
# if defined, add the default option to the options dict # if defined, add the default option to the options dict
if self.default_option is not None: if self.default_option is not None:
self.options[""] = self.default_option self.options[""] = self.default_option
def __go_back(self, **kwargs): def __go_back(self, **kwargs):
raise GoBackException() self.previous_menu().run()
def __go_to_help(self, **kwargs): def __go_to_help(self, **kwargs):
self.help_menu(previous_menu=self).run() self.help_menu(previous_menu=self).run()
def __exit(self, **kwargs): def __exit(self, **kwargs):
raise ExitAppException() Logger.print_ok("###### Happy printing!", False)
sys.exit(0)
@abstractmethod
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
raise NotImplementedError
@abstractmethod
def set_options(self) -> None:
raise NotImplementedError
@abstractmethod @abstractmethod
def print_menu(self) -> None: def print_menu(self) -> None:
raise NotImplementedError("Subclasses must implement the print_menu method") raise NotImplementedError
def print_footer(self) -> None: def print_footer(self) -> None:
if self.footer_type is FooterType.QUIT: if self.footer_type is FooterType.QUIT:
@@ -155,53 +167,50 @@ class BaseMenu(metaclass=PostInitCaller):
elif self.footer_type is FooterType.BLANK: elif self.footer_type is FooterType.BLANK:
print_blank_footer() print_blank_footer()
else: else:
raise NotImplementedError("Method for printing footer not implemented.") raise NotImplementedError
def display_menu(self) -> None: def display_menu(self) -> None:
# clear()
if self.header: if self.header:
print_header() print_header()
self.print_menu() self.print_menu()
self.print_footer() self.print_footer()
def validate_user_input(self, usr_input: str) -> Tuple[Callable, str]: def validate_user_input(self, usr_input: str) -> Option:
""" """
Validate the user input and either return an Option, a string or None Validate the user input and either return an Option, a string or None
:param usr_input: The user input in form of a string :param usr_input: The user input in form of a string
:return: Option, str or None :return: Option, str or None
""" """
usr_input = usr_input.lower() usr_input = usr_input.lower()
option = self.options.get(usr_input, None) option = self.options.get(usr_input, Option(None, False, "", None))
# if option/usr_input is None/empty string, we execute the menus default option if specified # if option/usr_input is None/empty string, we execute the menus default option if specified
if (option is None or usr_input == "") and self.default_option is not None: if (option is None or usr_input == "") and self.default_option is not None:
return self.default_option, usr_input self.default_option.opt_index = usr_input
return self.default_option
# user selected a regular option # user selected a regular option
return option, usr_input option.opt_index = usr_input
return option
def handle_user_input(self) -> Tuple[Callable, str]: def handle_user_input(self) -> Option:
"""Handle the user input, return the validated input or print an error.""" """Handle the user input, return the validated input or print an error."""
while True: while True:
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="") print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
usr_input = input().lower() usr_input = input().lower()
validated_input = self.validate_user_input(usr_input) validated_input = self.validate_user_input(usr_input)
method_to_call = validated_input[0]
if method_to_call is not None: if validated_input.method is not None:
return validated_input return validated_input
else: else:
Logger.print_error("Invalid input!", False) Logger.print_error("Invalid input!", False)
def run(self) -> None: def run(self) -> None:
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends.""" """Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
while True: try:
try: self.display_menu()
self.display_menu() option = self.handle_user_input()
option = self.handle_user_input() option.method(opt_index=option.opt_index, opt_data=option.opt_data)
option[0](opt_index=option[1]) self.run()
except GoBackException: except Exception as e:
return Logger.print_error(f"An unexpected error occured:\n{e}")
except ExitAppException:
Logger.print_ok("###### Happy printing!", False)
sys.exit(0)

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper import klipper_setup from components.klipper import klipper_setup
from components.moonraker import moonraker_setup from components.moonraker import moonraker_setup
@@ -15,6 +16,7 @@ from components.webui_client import client_setup
from components.webui_client.client_config import client_config_setup from components.webui_client.client_config import client_config_setup
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_GREEN, RESET_FORMAT from utils.constants import COLOR_GREEN, RESET_FORMAT
@@ -23,20 +25,25 @@ from utils.constants import COLOR_GREEN, RESET_FORMAT
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class InstallMenu(BaseMenu): class InstallMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self) -> None:
self.options = { self.options = {
"1": self.install_klipper, "1": Option(method=self.install_klipper, menu=False),
"2": self.install_moonraker, "2": Option(method=self.install_moonraker, menu=False),
"3": self.install_mainsail, "3": Option(method=self.install_mainsail, menu=False),
"4": self.install_fluidd, "4": Option(method=self.install_fluidd, menu=False),
"5": self.install_mainsail_config, "5": Option(method=self.install_mainsail_config, menu=False),
"6": self.install_fluidd_config, "6": Option(method=self.install_fluidd_config, menu=False),
"7": None,
"8": None,
"9": None,
} }
def print_menu(self): def print_menu(self):

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.klipper_utils import get_klipper_status from components.klipper.klipper_utils import get_klipper_status
from components.log_uploads.menus.log_upload_menu import LogUploadMenu from components.log_uploads.menus.log_upload_menu import LogUploadMenu
@@ -21,7 +22,7 @@ from components.webui_client.mainsail_data import MainsailData
from core.menus import FooterType from core.menus import FooterType
from core.menus.advanced_menu import AdvancedMenu from core.menus.advanced_menu import AdvancedMenu
from core.menus.backup_menu import BackupMenu from core.menus.backup_menu import BackupMenu
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu, Option
from extensions.extensions_menu import ExtensionsMenu from extensions.extensions_menu import ExtensionsMenu
from core.menus.install_menu import InstallMenu from core.menus.install_menu import InstallMenu
from core.menus.remove_menu import RemoveMenu from core.menus.remove_menu import RemoveMenu
@@ -43,16 +44,6 @@ class MainMenu(BaseMenu):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.options = {
"0": self.log_upload_menu,
"1": self.install_menu,
"2": self.update_menu,
"3": self.remove_menu,
"4": self.advanced_menu,
"5": self.backup_menu,
"e": self.extension_menu,
"s": self.settings_menu,
}
self.header = True self.header = True
self.footer_type = FooterType.QUIT self.footer_type = FooterType.QUIT
@@ -68,6 +59,22 @@ class MainMenu(BaseMenu):
self.cc_status = "" self.cc_status = ""
self.init_status() self.init_status()
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
"""MainMenu does not have a previous menu"""
pass
def set_options(self) -> None:
self.options = {
"0": Option(method=self.log_upload_menu, menu=True),
"1": Option(method=self.install_menu, menu=True),
"2": Option(method=self.update_menu, menu=True),
"3": Option(method=self.remove_menu, menu=True),
"4": Option(method=self.advanced_menu, menu=True),
"5": Option(method=self.backup_menu, menu=True),
"e": Option(method=self.extension_menu, menu=True),
"s": Option(method=self.settings_menu, menu=True),
}
def init_status(self) -> None: def init_status(self) -> None:
status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"] status_vars = ["kl", "mr", "ms", "fl", "ks", "mb", "cn"]
for var in status_vars: for var in status_vars:
@@ -140,25 +147,25 @@ class MainMenu(BaseMenu):
print(menu, end="") print(menu, end="")
def log_upload_menu(self, **kwargs): def log_upload_menu(self, **kwargs):
LogUploadMenu(previous_menu=self).run() LogUploadMenu().run()
def install_menu(self, **kwargs): def install_menu(self, **kwargs):
InstallMenu(previous_menu=self).run() InstallMenu(previous_menu=self.__class__).run()
def update_menu(self, **kwargs): def update_menu(self, **kwargs):
UpdateMenu(previous_menu=self).run() UpdateMenu(previous_menu=self.__class__).run()
def remove_menu(self, **kwargs): def remove_menu(self, **kwargs):
RemoveMenu(previous_menu=self).run() RemoveMenu(previous_menu=self.__class__).run()
def advanced_menu(self, **kwargs): def advanced_menu(self, **kwargs):
AdvancedMenu().run() AdvancedMenu(previous_menu=self.__class__).run()
def backup_menu(self, **kwargs): def backup_menu(self, **kwargs):
BackupMenu(previous_menu=self).run() BackupMenu(previous_menu=self.__class__).run()
def settings_menu(self, **kwargs): def settings_menu(self, **kwargs):
SettingsMenu(previous_menu=self).run() SettingsMenu().run()
def extension_menu(self, **kwargs): def extension_menu(self, **kwargs):
ExtensionsMenu(previous_menu=self).run() ExtensionsMenu(previous_menu=self.__class__).run()

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu
from components.moonraker.menus.moonraker_remove_menu import ( from components.moonraker.menus.moonraker_remove_menu import (
@@ -16,6 +17,7 @@ from components.moonraker.menus.moonraker_remove_menu import (
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from components.webui_client.menus.client_remove_menu import ClientRemoveMenu from components.webui_client.menus.client_remove_menu import ClientRemoveMenu
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_RED, RESET_FORMAT from utils.constants import COLOR_RED, RESET_FORMAT
@@ -23,24 +25,23 @@ from utils.constants import COLOR_RED, RESET_FORMAT
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class RemoveMenu(BaseMenu): class RemoveMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self):
self.options = { self.options = {
"1": self.remove_klipper, "1": Option(method=self.remove_klipper, menu=True),
"2": self.remove_moonraker, "2": Option(method=self.remove_moonraker, menu=True),
"3": self.remove_mainsail, "3": Option(method=self.remove_mainsail, menu=True),
"4": self.remove_fluidd, "4": Option(method=self.remove_fluidd, menu=True),
"5": None,
"6": None,
"7": None,
"8": None,
"9": None,
"10": None,
"11": None,
"12": None,
"13": None,
} }
def print_menu(self): def print_menu(self):
@@ -71,13 +72,13 @@ class RemoveMenu(BaseMenu):
print(menu, end="") print(menu, end="")
def remove_klipper(self, **kwargs): def remove_klipper(self, **kwargs):
KlipperRemoveMenu(previous_menu=self).run() KlipperRemoveMenu(previous_menu=self.__class__).run()
def remove_moonraker(self, **kwargs): def remove_moonraker(self, **kwargs):
MoonrakerRemoveMenu(previous_menu=self).run() MoonrakerRemoveMenu(previous_menu=self.__class__).run()
def remove_mainsail(self, **kwargs): def remove_mainsail(self, **kwargs):
ClientRemoveMenu(previous_menu=self, client=MainsailData()).run() ClientRemoveMenu(previous_menu=self.__class__, client=MainsailData()).run()
def remove_fluidd(self, **kwargs): def remove_fluidd(self, **kwargs):
ClientRemoveMenu(previous_menu=self, client=FluiddData()).run() ClientRemoveMenu(previous_menu=self.__class__, client=FluiddData()).run()

View File

@@ -6,16 +6,26 @@
# # # #
# This file may be distributed under the terms of the GNU GPLv3 license # # This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= # # ======================================================================= #
from typing import Type, Optional
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class SettingsMenu(BaseMenu): class SettingsMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self) -> None:
pass
def print_menu(self): def print_menu(self):
print("self") print("self")

View File

@@ -8,6 +8,7 @@
# ======================================================================= # # ======================================================================= #
import textwrap import textwrap
from typing import Type, Optional
from components.klipper.klipper_setup import update_klipper from components.klipper.klipper_setup import update_klipper
from components.klipper.klipper_utils import ( from components.klipper.klipper_utils import (
@@ -26,6 +27,7 @@ from components.webui_client.client_utils import (
) )
from components.webui_client.fluidd_data import FluiddData from components.webui_client.fluidd_data import FluiddData
from components.webui_client.mainsail_data import MainsailData from components.webui_client.mainsail_data import MainsailData
from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from utils.constants import ( from utils.constants import (
COLOR_GREEN, COLOR_GREEN,
@@ -39,23 +41,9 @@ from utils.constants import (
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class UpdateMenu(BaseMenu): class UpdateMenu(BaseMenu):
def __init__(self, previous_menu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.previous_menu: BaseMenu = previous_menu
self.options = {
"0": self.update_all,
"1": self.update_klipper,
"2": self.update_moonraker,
"3": self.update_mainsail,
"4": self.update_fluidd,
"5": self.update_mainsail_config,
"6": self.update_fluidd_config,
"7": self.update_klipperscreen,
"8": self.update_mobileraker,
"9": self.update_crowsnest,
"10": self.upgrade_system_packages,
}
self.kl_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.kl_local = f"{COLOR_WHITE}{RESET_FORMAT}"
self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.kl_remote = f"{COLOR_WHITE}{RESET_FORMAT}"
@@ -73,6 +61,28 @@ class UpdateMenu(BaseMenu):
self.mainsail_client = MainsailData() self.mainsail_client = MainsailData()
self.fluidd_client = FluiddData() self.fluidd_client = FluiddData()
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
from core.menus.main_menu import MainMenu
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self) -> None:
self.options = {
"0": Option(self.update_all, menu=False),
"1": Option(self.update_klipper, menu=False),
"2": Option(self.update_moonraker, menu=False),
"3": Option(self.update_mainsail, menu=False),
"4": Option(self.update_fluidd, menu=False),
"5": Option(self.update_mainsail_config, menu=False),
"6": Option(self.update_fluidd_config, menu=False),
"7": Option(self.update_klipperscreen, menu=False),
"8": Option(self.update_mobileraker, menu=False),
"9": Option(self.update_crowsnest, menu=False),
"10": Option(self.upgrade_system_packages, menu=False),
}
def print_menu(self): def print_menu(self):
self.fetch_update_status() self.fetch_update_status()

View File

@@ -12,8 +12,9 @@ import inspect
import json import json
import textwrap import textwrap
from pathlib import Path from pathlib import Path
from typing import Type, Dict from typing import Type, Dict, Optional
from core.menus import Option
from extensions import EXTENSION_ROOT from extensions import EXTENSION_ROOT
from extensions.base_extension import BaseExtension from extensions.base_extension import BaseExtension
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
@@ -23,12 +24,25 @@ from utils.constants import RESET_FORMAT, COLOR_CYAN, COLOR_YELLOW
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class ExtensionsMenu(BaseMenu): class ExtensionsMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu): def __init__(self, previous_menu: Optional[Type[BaseMenu]] = None):
super().__init__() super().__init__()
self.previous_menu = previous_menu
self.extensions: Dict[str, BaseExtension] = self.discover_extensions()
self.previous_menu: BaseMenu = previous_menu def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.extensions = self.discover_extensions() from core.menus.main_menu import MainMenu
self.options = {ext: self.extension_submenu for ext in self.extensions}
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else MainMenu
)
def set_options(self) -> None:
self.options = {
i: Option(
self.extension_submenu, menu=True, opt_data=self.extensions.get(i)
)
for i in self.extensions
}
def discover_extensions(self) -> Dict[str, BaseExtension]: def discover_extensions(self) -> Dict[str, BaseExtension]:
ext_dict = {} ext_dict = {}
@@ -63,8 +77,7 @@ class ExtensionsMenu(BaseMenu):
return ext_dict return ext_dict
def extension_submenu(self, **kwargs): def extension_submenu(self, **kwargs):
extension = self.extensions.get(kwargs.get("opt_index")) ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run()
ExtensionSubmenu(self, extension).run()
def print_menu(self): def print_menu(self):
header = " [ Extensions Menu ] " header = " [ Extensions Menu ] "
@@ -92,28 +105,33 @@ class ExtensionsMenu(BaseMenu):
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class ExtensionSubmenu(BaseMenu): class ExtensionSubmenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu, extension: BaseExtension): def __init__(
self, extension: BaseExtension, previous_menu: Optional[Type[BaseMenu]] = None
):
super().__init__() super().__init__()
self.extension = extension self.extension = extension
self.extension_name = extension.metadata.get("display_name")
self.extension_desc = extension.metadata.get("description")
self.previous_menu = previous_menu self.previous_menu = previous_menu
self.options["1"] = extension.install_extension
def set_previous_menu(self, previous_menu: Optional[Type[BaseMenu]]) -> None:
self.previous_menu: Type[BaseMenu] = (
previous_menu if previous_menu is not None else ExtensionsMenu
)
def set_options(self) -> None:
self.options["1"] = Option(self.extension.install_extension, menu=False)
if self.extension.metadata.get("updates"): if self.extension.metadata.get("updates"):
self.options["2"] = extension.update_extension self.options["2"] = Option(self.extension.update_extension, menu=False)
self.options["3"] = extension.remove_extension self.options["3"] = Option(self.extension.remove_extension, menu=False)
else: else:
self.options["2"] = extension.remove_extension self.options["2"] = Option(self.extension.remove_extension, menu=False)
def print_menu(self) -> None: def print_menu(self) -> None:
header = f" [ {self.extension_name} ] " header = f" [ {self.extension.metadata.get('display_name')} ] "
color = COLOR_YELLOW color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT) count = 62 - len(color) - len(RESET_FORMAT)
wrapper = textwrap.TextWrapper(55, initial_indent="| ", subsequent_indent="| ") wrapper = textwrap.TextWrapper(55, initial_indent="| ", subsequent_indent="| ")
lines = wrapper.wrap(self.extension_desc) lines = wrapper.wrap(self.extension.metadata.get("description"))
formatted_lines = [f"{line:<55} |" for line in lines] formatted_lines = [f"{line:<55} |" for line in lines]
description_text = "\n".join(formatted_lines) description_text = "\n".join(formatted_lines)

View File

@@ -190,7 +190,8 @@ def update_system_package_lists(silent: bool, rls_info_change=False) -> None:
Logger.print_ok("System package list update successful!") Logger.print_ok("System package list update successful!")
except CalledProcessError as e: except CalledProcessError as e:
kill(f"Error updating system package list:\n{e.stderr.decode()}") Logger.print_error(f"Error updating system package list:\n{e.stderr.decode()}")
raise
def check_package_install(packages: List[str]) -> List[str]: def check_package_install(packages: List[str]) -> List[str]:
@@ -210,8 +211,6 @@ def check_package_install(packages: List[str]) -> List[str]:
) )
if "installed" not in result.stdout.strip("'").split(): if "installed" not in result.stdout.strip("'").split():
not_installed.append(package) not_installed.append(package)
else:
Logger.print_ok(f"{package} already installed.")
return not_installed return not_installed
@@ -230,7 +229,8 @@ def install_system_packages(packages: List[str]) -> None:
Logger.print_ok("Packages installed successfully.") Logger.print_ok("Packages installed successfully.")
except CalledProcessError as e: except CalledProcessError as e:
kill(f"Error installing packages:\n{e.stderr.decode()}") Logger.print_error(f"Error installing packages:\n{e.stderr.decode()}")
raise
def mask_system_service(service_name: str) -> None: def mask_system_service(service_name: str) -> None: