Compare commits

...

7 Commits

Author SHA1 Message Date
CODeRUS
e95af1d998 Merge 2aefd86040 into 4978f22101 2025-02-08 13:34:02 +01:00
Mathijs Groothuis
4978f22101 fix(typo): Successfull > Successful
Co-authored-by: dw-0 <th33xitus@gmail.com>
Co-authored-by: dw-0 <domwil1091+github@gmail.com>
2025-02-08 13:30:37 +01:00
Mathijs Groothuis
8330f90b56 Fix typo: tyoing > typing
Fix typo: tyoing > typing

Co-authored-by: dw-0 <th33xitus@gmail.com>
Co-authored-by: dw-0 <domwil1091+github@gmail.com>
2025-02-05 19:07:19 +01:00
Andrey Kozhevnikov
2aefd86040 feature: save and select kconfig
Signed-off-by: Andrey Kozhevnikov <coderusinbox@gmail.com>
2025-02-02 22:44:58 +07:00
dw-0
2a08e3eb15 refactor: omit port 80 for IP in success message after webclient installation (#618)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2025-01-18 17:38:40 +01:00
dw-0
a2a3e92b50 refactor: remove BASE_USER argument from crowsnest install command (#617)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2025-01-18 17:38:00 +01:00
dw-0
3852464ab7 fix: use raw strings for regex parameter in get_string_input (#612)
fixes #602

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2025-01-03 22:10:39 +01:00
11 changed files with 191 additions and 25 deletions

View File

@@ -27,7 +27,6 @@ from components.crowsnest import (
) )
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
from core.constants import CURRENT_USER
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from core.types.component_status import ComponentStatus from core.types.component_status import ComponentStatus
@@ -73,7 +72,7 @@ def install_crowsnest() -> None:
Logger.print_info("Installer will prompt you for sudo password!") Logger.print_info("Installer will prompt you for sudo password!")
try: try:
run( run(
f"sudo make install BASE_USER={CURRENT_USER}", f"sudo make install",
cwd=CROWSNEST_DIR, cwd=CROWSNEST_DIR,
shell=True, shell=True,
check=True, check=True,

View File

@@ -25,6 +25,7 @@ KLIPPER_SERVICE_NAME = "klipper.service"
# directories # directories
KLIPPER_DIR = Path.home().joinpath("klipper") KLIPPER_DIR = Path.home().joinpath("klipper")
KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs")
KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env")
KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups")

View File

@@ -138,6 +138,7 @@ def start_flash_process(flash_options: FlashOptions) -> None:
if flash_options.flash_method is FlashMethod.REGULAR: if flash_options.flash_method is FlashMethod.REGULAR:
cmd = [ cmd = [
"make", "make",
f"KCONFIG_CONFIG={flash_options.selected_kconfig}",
flash_options.flash_command.value, flash_options.flash_command.value,
f"FLASH_DEVICE={flash_options.selected_mcu}", f"FLASH_DEVICE={flash_options.selected_mcu}",
] ]
@@ -165,17 +166,17 @@ def start_flash_process(flash_options: FlashOptions) -> None:
if rc != 0: if rc != 0:
raise Exception(f"Flashing failed with returncode: {rc}") raise Exception(f"Flashing failed with returncode: {rc}")
else: else:
Logger.print_ok("Flashing successfull!", start="\n", end="\n\n") Logger.print_ok("Flashing successful!", start="\n", end="\n\n")
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: def run_make_clean(kconfig = '.config') -> None:
try: try:
run( run(
"make clean", f"make KCONFIG_CONFIG={kconfig} clean",
cwd=KLIPPER_DIR, cwd=KLIPPER_DIR,
shell=True, shell=True,
check=True, check=True,
@@ -185,10 +186,10 @@ def run_make_clean() -> None:
raise raise
def run_make_menuconfig() -> None: def run_make_menuconfig(kconfig = '.config') -> None:
try: try:
run( run(
"make PYTHON=python3 menuconfig", f"make PYTHON=python3 KCONFIG_CONFIG={kconfig} menuconfig",
cwd=KLIPPER_DIR, cwd=KLIPPER_DIR,
shell=True, shell=True,
check=True, check=True,
@@ -198,10 +199,10 @@ def run_make_menuconfig() -> None:
raise raise
def run_make() -> None: def run_make(kconfig = '.config') -> None:
try: try:
run( run(
"make PYTHON=python3", f"make PYTHON=python3 KCONFIG_CONFIG={kconfig}",
cwd=KLIPPER_DIR, cwd=KLIPPER_DIR,
shell=True, shell=True,
check=True, check=True,

View File

@@ -39,6 +39,7 @@ class FlashOptions:
_selected_mcu: str = "" _selected_mcu: str = ""
_selected_board: str = "" _selected_board: str = ""
_selected_baudrate: int = 250000 _selected_baudrate: int = 250000
_selected_kconfig: str = ".config"
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if not cls._instance: if not cls._instance:
@@ -104,3 +105,11 @@ class FlashOptions:
@selected_baudrate.setter @selected_baudrate.setter
def selected_baudrate(self, value: int) -> None: def selected_baudrate(self, value: int) -> None:
self._selected_baudrate = value self._selected_baudrate = value
@property
def selected_kconfig(self) -> str:
return self._selected_kconfig
@selected_kconfig.setter
def selected_kconfig(self, value: str) -> None:
self._selected_kconfig = value

View File

@@ -10,34 +10,121 @@ from __future__ import annotations
import textwrap import textwrap
from typing import List, Set, Type from typing import List, Set, Type
from os import path, listdir, mkdir
from shutil import copyfile
from components.klipper import KLIPPER_DIR from components.klipper import KLIPPER_DIR, KLIPPER_KCONFIGS_DIR
from components.klipper_firmware.firmware_utils import ( from components.klipper_firmware.firmware_utils import (
run_make, run_make,
run_make_clean, run_make_clean,
run_make_menuconfig, run_make_menuconfig,
) )
from core.logger import Logger from components.klipper_firmware.flash_options import FlashOptions
from core.logger import DialogType, Logger
from core.menus import Option from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu, print_back_footer
from core.types.color import Color from core.types.color import Color
from utils.sys_utils import ( from utils.sys_utils import (
check_package_install, check_package_install,
install_system_packages, install_system_packages,
update_system_package_lists, update_system_package_lists,
) )
from utils.input_utils import get_confirm, get_string_input, get_selection_input
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperBuildFirmwareMenu(BaseMenu): class KlipperKConfigMenu(BaseMenu):
def __init__(self, previous_menu: Type[BaseMenu] | None = None): def __init__(self, previous_menu: Type[BaseMenu] | None = None):
super().__init__()
self.title = "Firmware Config Menu"
self.title_color = Color.CYAN
self.previous_menu: Type[BaseMenu] | None = previous_menu
self.flash_options = FlashOptions()
self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR
self.kconfig_default = path.join(KLIPPER_DIR, ".config")
self.kconfig = self.kconfig_default if not path.isdir(self.kconfigs_dirname) else None
def run(self) -> None:
if not self.kconfig:
super().run()
else:
self.flash_options.selected_kconfig = self.kconfig
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
from core.menus.advanced_menu import AdvancedMenu
self.previous_menu = (
previous_menu if previous_menu is not None else AdvancedMenu
)
def set_options(self) -> None:
if not path.isdir(self.kconfigs_dirname):
return
self.input_label_txt = "Select config or action to continue (default=n)"
self.default_option = Option(method=self.select_config, opt_data=self.kconfig_default)
self.configs = []
option_index = 1
for kconfig in listdir(self.kconfigs_dirname):
if not kconfig.endswith(".config"):
continue
kconfig_path = path.join(self.kconfigs_dirname, kconfig)
if path.isfile(kconfig_path):
self.configs += [kconfig]
self.options[str(option_index)] = Option(method=self.select_config, opt_data=kconfig_path)
option_index += 1
self.options['n'] = Option(method=self.select_config, opt_data=self.kconfig_default)
def print_menu(self) -> None:
menu = textwrap.dedent(
"""
╟───────────────────────────────────────────────────────╢
║ Found previously saved firmware configs ║
║ ║
║ You can select existing firmware config or create a ║
║ new one. ║
║ ║
"""
)[1:]
start_index = 1
for i, s in enumerate(self.configs):
line = f"{start_index + i}) {s}"
menu += f"{line:<54}\n"
new_config = Color.apply("n) New firmware config", Color.YELLOW)
menu += f"{new_config:<63}\n"
menu += "║ ║\n"
menu += "╟───────────────────────────────────────────────────────╢\n"
print(menu, end="")
def select_config(self, **kwargs) -> None:
selection: str | None = kwargs.get("opt_data", None)
if selection is None:
raise Exception("opt_data is None")
if not path.isfile(selection):
raise Exception("opt_data does not exists")
self.kconfig = selection
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperBuildFirmwareMenu(BaseMenu):
def __init__(self, kconfig: str | None = None, previous_menu: Type[BaseMenu] | None = None):
super().__init__() super().__init__()
self.title = "Build Firmware Menu" self.title = "Build Firmware Menu"
self.title_color = Color.CYAN self.title_color = Color.CYAN
self.previous_menu: Type[BaseMenu] | None = previous_menu self.previous_menu: Type[BaseMenu] | None = previous_menu
self.deps: Set[str] = {"build-essential", "dpkg-dev", "make"} self.deps: Set[str] = {"build-essential", "dpkg-dev", "make"}
self.missing_deps: List[str] = check_package_install(self.deps) self.missing_deps: List[str] = check_package_install(self.deps)
self.flash_options = FlashOptions()
self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR
self.kconfig_default = path.join(KLIPPER_DIR, ".config")
self.kconfig = self.flash_options.selected_kconfig
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
from core.menus.advanced_menu import AdvancedMenu from core.menus.advanced_menu import AdvancedMenu
@@ -67,7 +154,7 @@ class KlipperBuildFirmwareMenu(BaseMenu):
status_ok = Color.apply("*INSTALLED*", Color.GREEN) status_ok = Color.apply("*INSTALLED*", Color.GREEN)
status_missing = Color.apply("*MISSING*", Color.RED) status_missing = Color.apply("*MISSING*", Color.RED)
status = status_missing if d in self.missing_deps else status_ok status = status_missing if d in self.missing_deps else status_ok
padding = 39 - len(d) + len(status) + (len(status_ok) - len(status)) padding = 40 - len(d) + len(status) + (len(status_ok) - len(status))
d = Color.apply(f"{d}", Color.CYAN) d = Color.apply(f"{d}", Color.CYAN)
menu += f"{d}{status:>{padding}}\n" menu += f"{d}{status:>{padding}}\n"
menu += "║ ║\n" menu += "║ ║\n"
@@ -98,13 +185,16 @@ class KlipperBuildFirmwareMenu(BaseMenu):
def start_build_process(self, **kwargs) -> None: def start_build_process(self, **kwargs) -> None:
try: try:
run_make_clean() run_make_clean(self.kconfig)
run_make_menuconfig() run_make_menuconfig(self.kconfig)
run_make() run_make(self.kconfig)
Logger.print_ok("Firmware successfully built!") Logger.print_ok("Firmware successfully built!")
Logger.print_ok(f"Firmware file located in '{KLIPPER_DIR}/out'!") Logger.print_ok(f"Firmware file located in '{KLIPPER_DIR}/out'!")
if self.kconfig == self.kconfig_default:
self.save_firmware_config()
except Exception as e: except Exception as e:
Logger.print_error(e) Logger.print_error(e)
Logger.print_error("Building Klipper Firmware failed!") Logger.print_error("Building Klipper Firmware failed!")
@@ -112,3 +202,57 @@ class KlipperBuildFirmwareMenu(BaseMenu):
finally: finally:
if self.previous_menu is not None: if self.previous_menu is not None:
self.previous_menu().run() self.previous_menu().run()
def save_firmware_config(self) -> None:
Logger.print_dialog(
DialogType.CUSTOM,
[
"You can save the firmware build configs for multiple MCUs,"
" and use them to update the firmware after a Klipper version upgrade"
],
custom_title="Save firmware config",
)
if not get_confirm("Do you want to save firmware config?", default_choice=False):
return
filename = self.kconfig_default
while True:
Logger.print_dialog(
DialogType.CUSTOM,
[
"Allowed characters: a-z, 0-9 and '-'",
"The name must not contain the following:",
"\n\n",
"● Any special characters",
"● No leading or trailing '-'",
],
)
input_name = get_string_input(
"Enter the new firmware config name",
regex=r"^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$",
)
filename = path.join(self.kconfigs_dirname, f"{input_name}.config")
if path.isfile(filename):
if get_confirm(f"Firmware config {input_name} already exists, overwrite?", default_choice=False):
break
if path.isdir(filename):
Logger.print_error(f"Path {filename} exists and it's a directory")
if not path.exists(filename):
break
if not get_confirm(f"Save firmware config to '{filename}'?", default_choice=True):
Logger.print_info("Aborted saving firmware config ...")
return
if not path.exists(self.kconfigs_dirname):
mkdir(self.kconfigs_dirname)
copyfile(self.kconfig_default, filename)
Logger.print_ok()
Logger.print_ok(f"Firmware config successfully saved to {filename}")

View File

@@ -11,6 +11,7 @@ from __future__ import annotations
import textwrap import textwrap
import time import time
from typing import Type from typing import Type
from os.path import basename
from components.klipper_firmware.firmware_utils import ( from components.klipper_firmware.firmware_utils import (
find_firmware_file, find_firmware_file,
@@ -36,6 +37,7 @@ from components.klipper_firmware.menus.klipper_flash_help_menu import (
KlipperFlashMethodHelpMenu, KlipperFlashMethodHelpMenu,
KlipperMcuConnectionHelpMenu, KlipperMcuConnectionHelpMenu,
) )
from components.klipper_firmware.menus.klipper_build_menu import KlipperKConfigMenu
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.menus import FooterType, Option from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu, MenuTitleStyle from core.menus.base_menu import BaseMenu, MenuTitleStyle
@@ -420,6 +422,7 @@ class KlipperFlashOverviewMenu(BaseMenu):
mcu = self.flash_options.selected_mcu.split("/")[-1] mcu = self.flash_options.selected_mcu.split("/")[-1]
board = self.flash_options.selected_board board = self.flash_options.selected_board
baudrate = self.flash_options.selected_baudrate baudrate = self.flash_options.selected_baudrate
kconfig = basename(self.flash_options.selected_kconfig)
color = Color.CYAN color = Color.CYAN
subheader = f"[{Color.apply('Overview', color)}]" subheader = f"[{Color.apply('Overview', color)}]"
menu = textwrap.dedent( menu = textwrap.dedent(
@@ -452,6 +455,13 @@ class KlipperFlashOverviewMenu(BaseMenu):
""" """
)[1:] )[1:]
if self.flash_options.flash_method is FlashMethod.REGULAR:
menu += textwrap.dedent(
f"""
║ Firmware config: {Color.apply(f"{kconfig:<36}", color)}
"""
)[1:]
menu += textwrap.dedent( menu += textwrap.dedent(
""" """
║ ║ ║ ║

View File

@@ -144,7 +144,7 @@ def install_client(
custom_color=Color.GREEN, custom_color=Color.GREEN,
center_content=True, center_content=True,
content=[ content=[
f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{port}", f"Open {client.display_name} now on: http://{get_ipv4_addr()}{'' if port == 80 else f':{port}'}",
], ],
) )

View File

@@ -14,6 +14,7 @@ from typing import Type
from components.klipper import KLIPPER_DIR from components.klipper import KLIPPER_DIR
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.klipper_firmware.menus.klipper_build_menu import ( from components.klipper_firmware.menus.klipper_build_menu import (
KlipperKConfigMenu,
KlipperBuildFirmwareMenu, KlipperBuildFirmwareMenu,
) )
from components.klipper_firmware.menus.klipper_flash_menu import ( from components.klipper_firmware.menus.klipper_flash_menu import (
@@ -76,12 +77,15 @@ class AdvancedMenu(BaseMenu):
rollback_repository(MOONRAKER_DIR, Moonraker) rollback_repository(MOONRAKER_DIR, Moonraker)
def build(self, **kwargs) -> None: def build(self, **kwargs) -> None:
KlipperKConfigMenu().run()
KlipperBuildFirmwareMenu(previous_menu=self.__class__).run() KlipperBuildFirmwareMenu(previous_menu=self.__class__).run()
def flash(self, **kwargs) -> None: def flash(self, **kwargs) -> None:
KlipperKConfigMenu().run()
KlipperFlashMethodMenu(previous_menu=self.__class__).run() KlipperFlashMethodMenu(previous_menu=self.__class__).run()
def build_flash(self, **kwargs) -> None: def build_flash(self, **kwargs) -> None:
KlipperKConfigMenu().run()
KlipperBuildFirmwareMenu(previous_menu=KlipperFlashMethodMenu).run() KlipperBuildFirmwareMenu(previous_menu=KlipperFlashMethodMenu).run()
KlipperFlashMethodMenu(previous_menu=self.__class__).run() KlipperFlashMethodMenu(previous_menu=self.__class__).run()

View File

@@ -21,9 +21,7 @@ from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from core.settings.kiauh_settings import KiauhSettings, RepoSettings from core.settings.kiauh_settings import KiauhSettings, RepoSettings
from core.types.color import Color from core.types.color import Color
from core.types.component_status import ComponentStatus
from procedures.switch_repo import run_switch_repo_routine from procedures.switch_repo import run_switch_repo_routine
from utils.git_utils import get_repo_name
from utils.input_utils import get_confirm, get_string_input from utils.input_utils import get_confirm, get_string_input
@@ -141,12 +139,12 @@ class SettingsMenu(BaseMenu):
repo = get_string_input( repo = get_string_input(
"Enter new repository URL", "Enter new repository URL",
regex="^[\w/.:-]+$", regex=r"^[\w/.:-]+$",
default=KLIPPER_REPO_URL if repo_name == "klipper" else MOONRAKER_REPO_URL, default=KLIPPER_REPO_URL if repo_name == "klipper" else MOONRAKER_REPO_URL,
) )
branch = get_string_input( branch = get_string_input(
"Enter new branch name", "Enter new branch name",
regex="^.+$", regex=r"^.+$",
default="master" default="master"
) )

View File

@@ -31,7 +31,7 @@ def change_system_hostname() -> None:
"http://<hostname>.local", "http://<hostname>.local",
"\n\n", "\n\n",
"Example: If you set your hostname to 'my-printer', you can access an " "Example: If you set your hostname to 'my-printer', you can access an "
"installed webinterface by tyoing 'http://my-printer.local' in the " "installed webinterface by typing 'http://my-printer.local' in the "
"browser.", "browser.",
], ],
custom_title="CHANGE SYSTEM HOSTNAME", custom_title="CHANGE SYSTEM HOSTNAME",
@@ -51,7 +51,7 @@ def change_system_hostname() -> None:
) )
hostname = get_string_input( hostname = get_string_input(
"Enter the new hostname", "Enter the new hostname",
regex="^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$", regex=r"^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$",
) )
if not get_confirm(f"Change the hostname to '{hostname}'?", default_choice=False): if not get_confirm(f"Change the hostname to '{hostname}'?", default_choice=False):
Logger.print_info("Aborting hostname change ...") Logger.print_info("Aborting hostname change ...")

View File

@@ -105,7 +105,7 @@ function install_crowsnest(){
pushd "${HOME}/crowsnest" &> /dev/null || exit 1 pushd "${HOME}/crowsnest" &> /dev/null || exit 1
title_msg "Installer will prompt you for sudo password!" title_msg "Installer will prompt you for sudo password!"
status_msg "Launching crowsnest installer ..." status_msg "Launching crowsnest installer ..."
if ! sudo make install BASE_USER=$USER; then if ! sudo make install; then
error_msg "Something went wrong! Please try again..." error_msg "Something went wrong! Please try again..."
exit 1 exit 1
fi fi