Compare commits

...

2 Commits

Author SHA1 Message Date
dw-0
bb769fdf6d fix: hitting 'b' or 'h' in main menu raises exception
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-10 22:49:52 +02:00
dw-0
409aa3da25 refactor: extend firmware flashing functionalities
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-04-10 21:10:01 +02:00
10 changed files with 526 additions and 188 deletions

View File

@@ -0,0 +1,12 @@
# ======================================================================= #
# 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 #
# ======================================================================= #
from components.klipper import KLIPPER_DIR
SD_FLASH_SCRIPT = KLIPPER_DIR.joinpath("scripts/flash-sdcard.sh")

View File

@@ -13,8 +13,8 @@ from typing import Union, List
class FlashMethod(Enum):
REGULAR = "REGULAR"
SD_CARD = "SD_CARD"
REGULAR = "Regular"
SD_CARD = "SD Card"
class FlashCommand(Enum):
@@ -24,7 +24,7 @@ class FlashCommand(Enum):
class ConnectionType(Enum):
USB = "USB"
USB_DFU = "USB_DFU"
USB_DFU = "USB (DFU)"
UART = "UART"
@@ -36,6 +36,7 @@ class FlashOptions:
_mcu_list: List[str] = field(default_factory=list)
_selected_mcu: str = ""
_selected_board: str = ""
_selected_baudrate: int = 250000
def __new__(cls, *args, **kwargs):
if not cls._instance:
@@ -93,3 +94,11 @@ class FlashOptions:
@selected_board.setter
def selected_board(self, value: str) -> None:
self._selected_board = value
@property
def selected_baudrate(self) -> int:
return self._selected_baudrate
@selected_baudrate.setter
def selected_baudrate(self, value: int) -> None:
self._selected_baudrate = value

View File

@@ -7,15 +7,35 @@
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
import subprocess
from subprocess import CalledProcessError, check_output, Popen, PIPE, STDOUT
from typing import List
from components.klipper import KLIPPER_DIR
from components.klipper_firmware.flash_options import FlashOptions, FlashCommand
from components.klipper_firmware import SD_FLASH_SCRIPT
from components.klipper_firmware.flash_options import (
FlashOptions,
FlashMethod,
)
from utils.logger import Logger
from utils.system_utils import log_process
def find_firmware_file(method: FlashMethod) -> bool:
target = KLIPPER_DIR.joinpath("out")
target_exists = target.exists()
if method is FlashMethod.REGULAR:
f1 = "klipper.elf.hex"
f2 = "klipper.elf"
fw_file_exists = target.joinpath(f1).exists() and target.joinpath(f2).exists()
elif method is FlashMethod.SD_CARD:
fw_file_exists = target.joinpath("klipper.bin").exists()
else:
raise Exception("Unknown flash method")
return target_exists and fw_file_exists
def find_usb_device_by_id() -> List[str]:
try:
command = "find /dev/serial/by-id/* 2>/dev/null"
@@ -49,28 +69,62 @@ def find_usb_dfu_device() -> List[str]:
return []
def flash_device(flash_options: FlashOptions) -> None:
def get_sd_flash_board_list() -> List[str]:
if not KLIPPER_DIR.exists() or not SD_FLASH_SCRIPT.exists():
return []
try:
cmd = f"{SD_FLASH_SCRIPT} -l"
blist = subprocess.check_output(cmd, shell=True, text=True)
return blist.splitlines()[1:]
except subprocess.CalledProcessError as e:
Logger.print_error(f"An unexpected error occured:\n{e}")
def start_flash_process(flash_options: FlashOptions) -> None:
Logger.print_status(f"Flashing '{flash_options.selected_mcu}' ...")
try:
if not flash_options.flash_method:
raise Exception("Missing value for flash_method!")
if not flash_options.flash_command:
raise Exception("Missing value for flash_command!")
if not flash_options.selected_mcu:
raise Exception("Missing value for selected_mcu!")
if not flash_options.connection_type:
raise Exception("Missing value for connection_type!")
if (
flash_options.flash_method == FlashMethod.SD_CARD
and not flash_options.selected_board
):
raise Exception("Missing value for selected_board!")
if flash_options.flash_command is FlashCommand.FLASH:
command = [
if flash_options.flash_method is FlashMethod.REGULAR:
cmd = [
"make",
flash_options.flash_command.value,
f"FLASH_DEVICE={flash_options.selected_mcu}",
]
process = Popen(
command, cwd=KLIPPER_DIR, stdout=PIPE, stderr=STDOUT, text=True
)
elif flash_options.flash_method is FlashMethod.SD_CARD:
if not SD_FLASH_SCRIPT.exists():
raise Exception("Unable to find Klippers sdcard flash script!")
cmd = [
SD_FLASH_SCRIPT,
"-b",
flash_options.selected_baudrate,
flash_options.selected_mcu,
flash_options.selected_board,
]
else:
raise Exception("Invalid value for flash_method!")
log_process(process)
process = Popen(cmd, cwd=KLIPPER_DIR, stdout=PIPE, stderr=STDOUT, text=True)
log_process(process)
rc = process.returncode
if rc != 0:
raise Exception(f"Flashing failed with returncode: {rc}")
else:
Logger.print_ok("Flashing successfull!", start="\n", end="\n\n")
rc = process.returncode
if rc != 0:
raise Exception(f"Flashing failed with returncode: {rc}")
else:
Logger.print_ok("Flashing successfull!", start="\n", end="\n\n")
except (Exception, CalledProcessError):
Logger.print_error("Flashing failed!", start="\n")

View File

@@ -0,0 +1,97 @@
# ======================================================================= #
# 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 components.klipper_firmware.flash_options import FlashOptions, FlashMethod
from core.menus import FooterType
from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_RED, RESET_FORMAT
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperNoFirmwareErrorMenu(BaseMenu):
def __init__(self):
super().__init__()
self.flash_options = FlashOptions()
self.default_option = self.go_back
self.footer_type = FooterType.BLANK
self.input_label_txt = "Press ENTER to go back to [Advanced Menu]"
def print_menu(self) -> None:
header = "!!! NO FIRMWARE FILE FOUND !!!"
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
line1 = f"{color}Unable to find a compiled firmware file!{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {line1:<62} |
| |
| Make sure, that: |
| ● the folder '~/klipper/out' and its content exist |
| ● the folder contains the following file: |
"""
)[1:]
if self.flash_options.flash_method is FlashMethod.REGULAR:
menu += "| ● 'klipper.elf' |\n"
menu += "| ● 'klipper.elf.hex' |\n"
else:
menu += "| ● 'klipper.bin' |\n"
print(menu, end="")
def go_back(self, **kwargs) -> None:
from core.menus.advanced_menu import AdvancedMenu
AdvancedMenu().run()
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperNoBoardTypesErrorMenu(BaseMenu):
def __init__(self):
super().__init__()
self.default_option = self.go_back
self.footer_type = FooterType.BLANK
self.input_label_txt = "Press ENTER to go back to [Main Menu]"
def print_menu(self) -> None:
header = "!!! ERROR GETTING BOARD LIST !!!"
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
line1 = f"{color}Reading the list of supported boards failed!{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {line1:<62} |
| |
| Make sure, that: |
| ● the folder '~/klipper' and all its content exist |
| ● the content of folder '~/klipper' is not currupted |
| ● the file '~/klipper/scripts/flash-sd.py' exist |
| ● your current user has access to those files/folders |
| |
| If in doubt or this process continues to fail, please |
| consider to download Klipper again. |
"""
)[1:]
print(menu, end="")
def go_back(self, **kwargs) -> None:
from core.menus.main_menu import MainMenu
MainMenu().run()

View File

@@ -0,0 +1,127 @@
# ======================================================================= #
# 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 core.menus.base_menu import BaseMenu
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW
class KlipperFlashMethodHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > "
color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}Regular flashing method:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}Updating via SD-Card Update:{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {subheader1:<62} |
| The default method to flash controller boards which |
| are connected and updated over USB and not by placing |
| a compiled firmware file onto an internal SD-Card. |
| |
| Common controllers that get flashed that way are: |
| - Arduino Mega 2560 |
| - Fysetc F6 / S6 (used without a Display + SD-Slot) |
| |
| {subheader2:<62} |
| Many popular controller boards ship with a bootloader |
| capable of updating the firmware via SD-Card. |
| Choose this method if your controller board supports |
| this way of updating. This method ONLY works for up- |
| grading firmware. The initial flashing procedure must |
| be done manually per the instructions that apply to |
| your controller board. |
| |
| Common controllers that can be flashed that way are: |
| - BigTreeTech SKR 1.3 / 1.4 (Turbo) / E3 / Mini E3 |
| - Fysetc F6 / S6 (used with a Display + SD-Slot) |
| - Fysetc Spider |
| |
"""
)[1:]
print(menu, end="")
class KlipperFlashCommandHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > "
color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}make flash:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}make serialflash:{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {subheader1:<62} |
| The default command to flash controller board, it |
| will detect selected microcontroller and use suitable |
| tool for flashing it. |
| |
| {subheader2:<62} |
| Special command to flash STM32 microcontrollers in |
| DFU mode but connected via serial. stm32flash command |
| will be used internally. |
| |
"""
)[1:]
print(menu, end="")
class KlipperMcuConnectionHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > "
color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}USB:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}UART:{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {subheader1:<62} |
| Selecting USB as the connection method will scan the |
| USB ports for connected controller boards. This will |
| be similar to the 'ls /dev/serial/by-id/*' command |
| suggested by the official Klipper documentation for |
| determining successfull USB connections! |
| |
| {subheader2:<62} |
| Selecting UART as the connection method will list all |
| possible UART serial ports. Note: This method ALWAYS |
| returns something as it seems impossible to determine |
| if a valid Klipper controller board is connected or |
| not. Because of that, you MUST know which UART serial |
| port your controller board is connected to when using |
| this connection method. |
| |
"""
)[1:]
print(menu, end="")

View File

@@ -20,13 +20,24 @@ from components.klipper_firmware.flash_utils import (
find_usb_device_by_id,
find_uart_device,
find_usb_dfu_device,
flash_device,
get_sd_flash_board_list,
start_flash_process,
find_firmware_file,
)
from components.klipper_firmware.menus.klipper_flash_error_menu import (
KlipperNoBoardTypesErrorMenu,
KlipperNoFirmwareErrorMenu,
)
from components.klipper_firmware.menus.klipper_flash_help_menu import (
KlipperMcuConnectionHelpMenu,
KlipperFlashCommandHelpMenu,
KlipperFlashMethodHelpMenu,
)
from core.menus import FooterType
from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_CYAN, RESET_FORMAT, COLOR_YELLOW, COLOR_RED
from utils.input_utils import get_confirm
from utils.input_utils import get_number_input
from utils.logger import Logger
@@ -37,10 +48,10 @@ class KlipperFlashMethodMenu(BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.help_menu = KlipperFlashMethodHelpMenu
self.options = {
"1": self.select_regular,
"2": self.select_sdcard,
"h": self.help_menu,
}
self.input_label_txt = "Select flash method"
self.footer_type = FooterType.BACK_HELP
@@ -48,7 +59,11 @@ class KlipperFlashMethodMenu(BaseMenu):
self.flash_options = FlashOptions()
def print_menu(self) -> None:
header = " [ Flash MCU ] "
header = " [ MCU Flash Menu ] "
subheader = f"{COLOR_YELLOW}ATTENTION:{RESET_FORMAT}"
subline1 = f"{COLOR_YELLOW}Make sure to select the correct method for the MCU!{RESET_FORMAT}"
subline2 = f"{COLOR_YELLOW}Not all MCUs support both methods!{RESET_FORMAT}"
color = COLOR_CYAN
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
@@ -56,9 +71,11 @@ class KlipperFlashMethodMenu(BaseMenu):
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Please select the flashing method to flash your MCU. |
| Make sure to only select a method your MCU supports. |
| Not all MCUs support both methods! |
| Select the flash method for flashing the MCU. |
| |
| {subheader:<62} |
| {subline1:<62} |
| {subline2:<62} |
|-------------------------------------------------------|
| |
| 1) Regular flashing method |
@@ -77,10 +94,10 @@ class KlipperFlashMethodMenu(BaseMenu):
self.goto_next_menu()
def goto_next_menu(self, **kwargs):
KlipperFlashCommandMenu(previous_menu=self).run()
def help_menu(self, **kwargs):
KlipperFlashMethodHelpMenu(previous_menu=self).run()
if find_firmware_file(self.flash_options.flash_method):
KlipperFlashCommandMenu(previous_menu=self).run()
else:
KlipperNoFirmwareErrorMenu().run()
# noinspection PyUnusedLocal
@@ -90,10 +107,10 @@ class KlipperFlashCommandMenu(BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.help_menu = KlipperFlashCommandHelpMenu
self.options = {
"1": self.select_flash,
"2": self.select_serialflash,
"h": self.help_menu,
}
self.default_option = self.select_flash
self.input_label_txt = "Select flash command"
@@ -125,9 +142,6 @@ class KlipperFlashCommandMenu(BaseMenu):
def goto_next_menu(self, **kwargs):
KlipperSelectMcuConnectionMenu(previous_menu=self).run()
def help_menu(self, **kwargs):
KlipperFlashCommandHelpMenu(previous_menu=self).run()
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
@@ -137,11 +151,11 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
self.__standalone = standalone
self.previous_menu: BaseMenu = previous_menu
self.help_menu = KlipperMcuConnectionHelpMenu
self.options = {
"1": self.select_usb,
"2": self.select_dfu,
"3": self.select_usb_dfu,
"h": self.help_menu,
}
self.input_label_txt = "Select connection type"
self.footer_type = FooterType.BACK_HELP
@@ -209,9 +223,6 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
def goto_next_menu(self, **kwargs):
KlipperSelectMcuIdMenu(previous_menu=self).run()
def help_menu(self, **kwargs):
KlipperMcuConnectionHelpMenu(previous_menu=self).run()
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
@@ -256,132 +267,139 @@ class KlipperSelectMcuIdMenu(BaseMenu):
selected_mcu = self.mcu_list[index]
self.flash_options.selected_mcu = selected_mcu
print(f"{COLOR_CYAN}###### You selected:{RESET_FORMAT}")
print(f"● MCU #{index}: {selected_mcu}\n")
if self.flash_options.flash_method == FlashMethod.SD_CARD:
KlipperSelectSDFlashBoardMenu(previous_menu=self).run()
elif self.flash_options.flash_method == FlashMethod.REGULAR:
KlipperFlashOverviewMenu(previous_menu=self).run()
if get_confirm("Continue", allow_go_back=True):
Logger.print_status(f"Flashing '{selected_mcu}' ...")
flash_device(self.flash_options)
self.goto_next_menu()
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperSelectSDFlashBoardMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
def goto_next_menu(self, **kwargs):
from core.menus.main_menu import MainMenu
self.previous_menu: BaseMenu = previous_menu
self.flash_options = FlashOptions()
self.available_boards = get_sd_flash_board_list()
self.input_label_txt = "Select board type"
options = {f"{i}": self.board_select for i in range(len(self.available_boards))}
self.options = options
def print_menu(self) -> None:
if len(self.available_boards) < 1:
KlipperNoBoardTypesErrorMenu().run()
else:
menu = textwrap.dedent(
"""
/=======================================================\\
| Please select the type of board that corresponds to |
| the currently selected MCU ID you chose before. |
| |
| The following boards are currently supported: |
|-------------------------------------------------------|
"""
)[1:]
for i, board in enumerate(self.available_boards):
line = f" {i}) {board}"
menu += f"|{line:<55}|\n"
print(menu, end="")
def board_select(self, **kwargs):
board = int(kwargs.get("opt_index"))
self.flash_options.selected_board = self.available_boards[board]
self.baudrate_select()
def baudrate_select(self, **kwargs):
menu = textwrap.dedent(
"""
/=======================================================\\
| If your board is flashed with firmware that connects |
| at a custom baud rate, please change it now. |
| |
| If you are unsure, stick to the default 250000! |
\\=======================================================/
"""
)[1:]
print(menu, end="")
self.flash_options.selected_baudrate = get_number_input(
question="Please set the baud rate",
default=250000,
min_count=0,
allow_go_back=True,
)
KlipperFlashOverviewMenu(previous_menu=self).run()
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperFlashOverviewMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
self.flash_options = FlashOptions()
self.options = {"Y": self.execute_flash, "N": self.abort_process}
self.input_label_txt = "Perform action (default=Y)"
self.default_option = self.execute_flash
def print_menu(self) -> None:
header = "!!! ATTENTION !!!"
color = COLOR_RED
count = 62 - len(color) - len(RESET_FORMAT)
method = self.flash_options.flash_method.value
command = self.flash_options.flash_command.value
conn_type = self.flash_options.connection_type.value
mcu = self.flash_options.selected_mcu
board = self.flash_options.selected_board
baudrate = self.flash_options.selected_baudrate
subheader = f"[{COLOR_CYAN}Overview{RESET_FORMAT}]"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Before contuining the flashing process, please check |
| if all parameters were set correctly! Once you made |
| sure everything is correct, start the process. If any |
| parameter needs to be changed, you can go back (B) |
| step by step or abort and start from the beginning. |
|{subheader:-^64}|
"""
)[1:]
menu += f" ● MCU: {COLOR_CYAN}{mcu}{RESET_FORMAT}\n"
menu += f" ● Connection: {COLOR_CYAN}{conn_type}{RESET_FORMAT}\n"
menu += f" ● Flash method: {COLOR_CYAN}{method}{RESET_FORMAT}\n"
menu += f" ● Flash command: {COLOR_CYAN}{command}{RESET_FORMAT}\n"
if self.flash_options.flash_method is FlashMethod.SD_CARD:
menu += f" ● Board type: {COLOR_CYAN}{board}{RESET_FORMAT}\n"
menu += f" ● Baudrate: {COLOR_CYAN}{baudrate}{RESET_FORMAT}\n"
menu += textwrap.dedent(
"""
|-------------------------------------------------------|
| Y) Start flash process |
| N) Abort - Return to Advanced Menu |
"""
)
print(menu, end="")
def execute_flash(self, **kwargs):
from core.menus.advanced_menu import AdvancedMenu
AdvancedMenu(previous_menu=MainMenu()).run()
start_flash_process(self.flash_options)
Logger.print_info("Returning to MCU Flash Menu in 5 seconds ...")
time.sleep(5)
KlipperFlashMethodMenu(previous_menu=AdvancedMenu()).run()
def abort_process(self, **kwargs):
from core.menus.advanced_menu import AdvancedMenu
class KlipperFlashMethodHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > "
color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}Regular flashing method:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}Updating via SD-Card Update:{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {subheader1:<62} |
| The default method to flash controller boards which |
| are connected and updated over USB and not by placing |
| a compiled firmware file onto an internal SD-Card. |
| |
| Common controllers that get flashed that way are: |
| - Arduino Mega 2560 |
| - Fysetc F6 / S6 (used without a Display + SD-Slot) |
| |
| {subheader2:<62} |
| Many popular controller boards ship with a bootloader |
| capable of updating the firmware via SD-Card. |
| Choose this method if your controller board supports |
| this way of updating. This method ONLY works for up- |
| grading firmware. The initial flashing procedure must |
| be done manually per the instructions that apply to |
| your controller board. |
| |
| Common controllers that can be flashed that way are: |
| - BigTreeTech SKR 1.3 / 1.4 (Turbo) / E3 / Mini E3 |
| - Fysetc F6 / S6 (used with a Display + SD-Slot) |
| - Fysetc Spider |
| |
"""
)[1:]
print(menu, end="")
class KlipperFlashCommandHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > "
color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}make flash:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}make serialflash:{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {subheader1:<62} |
| The default command to flash controller board, it |
| will detect selected microcontroller and use suitable |
| tool for flashing it. |
| |
| {subheader2:<62} |
| Special command to flash STM32 microcontrollers in |
| DFU mode but connected via serial. stm32flash command |
| will be used internally. |
| |
"""
)[1:]
print(menu, end="")
class KlipperMcuConnectionHelpMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
def print_menu(self) -> None:
header = " < ? > Help: Flash MCU < ? > "
color = COLOR_YELLOW
count = 62 - len(color) - len(RESET_FORMAT)
subheader1 = f"{COLOR_CYAN}USB:{RESET_FORMAT}"
subheader2 = f"{COLOR_CYAN}UART:{RESET_FORMAT}"
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {subheader1:<62} |
| Selecting USB as the connection method will scan the |
| USB ports for connected controller boards. This will |
| be similar to the 'ls /dev/serial/by-id/*' command |
| suggested by the official Klipper documentation for |
| determining successfull USB connections! |
| |
| {subheader2:<62} |
| Selecting UART as the connection method will list all |
| possible UART serial ports. Note: This method ALWAYS |
| returns something as it seems impossible to determine |
| if a valid Klipper controller board is connected or |
| not. Because of that, you MUST know which UART serial |
| port your controller board is connected to when using |
| this connection method. |
| |
"""
)[1:]
print(menu, end="")
AdvancedMenu().run()

View File

@@ -14,13 +14,7 @@ class FooterType(Enum):
QUIT = "QUIT"
BACK = "BACK"
BACK_HELP = "BACK_HELP"
NAVI_OPTIONS = {
FooterType.QUIT: ["q"],
FooterType.BACK: ["b"],
FooterType.BACK_HELP: ["b", "h"],
}
BLANK = "BLANK"
class ExitAppException(Exception):

View File

@@ -19,10 +19,12 @@ from utils.constants import COLOR_YELLOW, RESET_FORMAT
# noinspection PyUnusedLocal
class AdvancedMenu(BaseMenu):
def __init__(self, previous_menu: BaseMenu):
def __init__(self):
super().__init__()
self.previous_menu: BaseMenu = previous_menu
from core.menus.main_menu import MainMenu
self.previous_menu: BaseMenu = MainMenu()
self.options = {
"1": None,
"2": None,

View File

@@ -12,10 +12,10 @@ from __future__ import annotations
import subprocess
import sys
import textwrap
from abc import abstractmethod, ABC
from abc import abstractmethod
from typing import Dict, Union, Callable, Type, Tuple
from core.menus import FooterType, NAVI_OPTIONS, ExitAppException, GoBackException
from core.menus import FooterType, ExitAppException, GoBackException
from utils.constants import (
COLOR_GREEN,
COLOR_YELLOW,
@@ -92,22 +92,55 @@ def print_back_help_footer():
print(footer, end="")
Options = Dict[str, Callable]
def print_blank_footer():
print("\=======================================================/")
class BaseMenu(ABC):
options: Options = {}
class PostInitCaller(type):
def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
obj.__post_init__()
return obj
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class BaseMenu(metaclass=PostInitCaller):
options: Dict[str, Callable] = {}
options_offset: int = 0
default_option: Union[Callable, None] = None
input_label_txt: str = "Perform action"
header: bool = False
previous_menu: Union[Type[BaseMenu], BaseMenu] = None
help_menu: Type[BaseMenu] = None
footer_type: FooterType = FooterType.BACK
def __init__(self):
def __init__(self, **kwargs):
if type(self) is BaseMenu:
raise NotImplementedError("BaseMenu cannot be instantiated directly.")
def __post_init__(self):
# conditionally add options based on footer type
if self.footer_type is FooterType.QUIT:
self.options["q"] = self.__exit
if self.footer_type is FooterType.BACK:
self.options["b"] = self.__go_back
if self.footer_type is FooterType.BACK_HELP:
self.options["b"] = self.__go_back
self.options["h"] = self.__go_to_help
# if defined, add the default option to the options dict
if self.default_option is not None:
self.options[""] = self.default_option
def __go_back(self, **kwargs):
raise GoBackException()
def __go_to_help(self, **kwargs):
self.help_menu(previous_menu=self).run()
def __exit(self, **kwargs):
raise ExitAppException()
@abstractmethod
def print_menu(self) -> None:
raise NotImplementedError("Subclasses must implement the print_menu method")
@@ -119,6 +152,8 @@ class BaseMenu(ABC):
print_back_footer()
elif self.footer_type is FooterType.BACK_HELP:
print_back_help_footer()
elif self.footer_type is FooterType.BLANK:
print_blank_footer()
else:
raise NotImplementedError("Method for printing footer not implemented.")
@@ -138,20 +173,8 @@ class BaseMenu(ABC):
usr_input = usr_input.lower()
option = self.options.get(usr_input, None)
# check if usr_input contains a character used for basic navigation, e.g. b, h or q
# and if the current menu has the appropriate footer to allow for that action
is_valid_navigation = self.footer_type in NAVI_OPTIONS
user_navigated = usr_input in NAVI_OPTIONS[self.footer_type]
if is_valid_navigation and user_navigated:
if usr_input == "q":
raise ExitAppException()
elif usr_input == "b":
raise GoBackException()
elif usr_input == "h":
return option, usr_input
# if usr_input is None or an empty string, we execute the menues default option if specified
if usr_input == "" and self.default_option is not None:
# 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:
return self.default_option, usr_input
# user selected a regular option
@@ -162,8 +185,10 @@ class BaseMenu(ABC):
while True:
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
usr_input = input().lower()
validated_input = self.validate_user_input(usr_input)
method_to_call = validated_input[0]
if (validated_input := self.validate_user_input(usr_input)) is not None:
if method_to_call is not None:
return validated_input
else:
Logger.print_error("Invalid input!", False)

View File

@@ -152,7 +152,7 @@ class MainMenu(BaseMenu):
RemoveMenu(previous_menu=self).run()
def advanced_menu(self, **kwargs):
AdvancedMenu(previous_menu=self).run()
AdvancedMenu().run()
def backup_menu(self, **kwargs):
BackupMenu(previous_menu=self).run()