Compare commits

...

7 Commits

Author SHA1 Message Date
dw-0
3734ef0568 feat(obico): add obico extension (#474)
* feat(obico): add obico extension

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* refactor: add obico to moonraker suffix blacklist

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* fix: correctly recognize the suffix of the instance

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* fix: fix logic of asking for linking

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* Squashed 'kiauh/core/submodules/simple_config_parser/' changes from 2698f60..7aa6586

7aa6586 fix: sections can have hyphens in their second word
44cedf5 fix(tests): fix whitespaces in expected output

git-subtree-dir: kiauh/core/submodules/simple_config_parser
git-subtree-split: 7aa658654eeb08fd53831effbfba4503a61e0eff

* refactor: use SimpleConfigParser and finalize the code

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* fix: wrong condition in _load_config

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* Squashed 'kiauh/core/submodules/simple_config_parser/' changes from 7aa6586..47c353f

47c353f refactor: improve section regex
dd904bc test: add more test cases

git-subtree-dir: kiauh/core/submodules/simple_config_parser
git-subtree-split: 47c353f4e91e6be9605394b174834e1f34c9cfdf

* Squashed 'kiauh/core/submodules/simple_config_parser/' changes from 47c353f..3655330

3655330 refactor: use pop() for removing elements from lists and dicts
99733f1 refactor: add empty options dict to _all_options on section parsing

git-subtree-dir: kiauh/core/submodules/simple_config_parser
git-subtree-split: 3655330d2156e13acffc56fac070ab8716444c85

* refactor: improve config creations and patching

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 18:08:00 +02:00
dw-0
08c10fdded refactor: rework some moonraker dialogs
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 15:57:13 +02:00
dw-0
cfc45a9746 refactor: rework some klipper dialogs
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 15:50:31 +02:00
dw-0
205c84b3c3 refactor: make menus more visually appealing
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 12:30:29 +02:00
dw-0
e63eb47ee9 refactor: extract config filenames into constants
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 10:58:43 +02:00
dw-0
af57b9670d fix: wrong condition in _load_config
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 10:56:02 +02:00
dw-0
b758b3887b refactor: improve error logging on missing kiauh config file
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-06-22 10:55:11 +02:00
39 changed files with 1096 additions and 497 deletions

View File

@@ -13,7 +13,12 @@ from typing import List
from core.instance_manager.base_instance import BaseInstance
from core.menus.base_menu import print_back_footer
from utils.constants import COLOR_CYAN, COLOR_GREEN, COLOR_YELLOW, RESET_FORMAT
from utils.constants import (
COLOR_CYAN,
COLOR_GREEN,
COLOR_YELLOW,
RESET_FORMAT,
)
@unique
@@ -29,7 +34,7 @@ def print_instance_overview(
show_index=False,
show_select_all=False,
):
dialog = "/=======================================================\\\n"
dialog = "╔═══════════════════════════════════════════════════════╗\n"
if show_headline:
d_type = (
"Klipper instances"
@@ -37,13 +42,13 @@ def print_instance_overview(
else "printer directories"
)
headline = f"{COLOR_GREEN}The following {d_type} were found:{RESET_FORMAT}"
dialog += f"|{headline:^64}|\n"
dialog += "|-------------------------------------------------------|\n"
dialog += f"{headline:^64}\n"
dialog += "╟───────────────────────────────────────────────────────╢\n"
if show_select_all:
select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}"
dialog += f"| {select_all:<63}|\n"
dialog += "| |\n"
dialog += f" {select_all:<63}\n"
dialog += " \n"
for i, s in enumerate(instances):
if display_type is DisplayType.SERVICE_NAME:
@@ -51,7 +56,8 @@ def print_instance_overview(
else:
name = s.data_dir
line = f"{COLOR_CYAN}{f'{i})' if show_index else ''} {name}{RESET_FORMAT}"
dialog += f"| {line:<63}|\n"
dialog += f" {line:<63}\n"
dialog += "╟───────────────────────────────────────────────────────╢\n"
print(dialog, end="")
print_back_footer()
@@ -62,13 +68,14 @@ def print_select_instance_count_dialog():
line2 = f"{COLOR_YELLOW}Setting up too many instances may crash your system.{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| Please select the number of Klipper instances to set |
| up. The number of Klipper instances will determine |
| the amount of printers you can run from this host. |
| |
| {line1:<63}|
| {line2:<63}|
╔═══════════════════════════════════════════════════════╗
Please select the number of Klipper instances to set
up. The number of Klipper instances will determine
the amount of printers you can run from this host.
{line1:<63}
{line2:<63}
╟───────────────────────────────────────────────────────╢
"""
)[1:]
@@ -81,71 +88,16 @@ def print_select_custom_name_dialog():
line2 = f"{COLOR_YELLOW}Only alphanumeric characters are allowed!{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| You can now assign a custom name to each instance. |
| If skipped, each instance will get an index assigned |
| in ascending order, starting at index '1'. |
| |
| {line1:<63}|
| {line2:<63}|
╔═══════════════════════════════════════════════════════╗
You can now assign a custom name to each instance.
If skipped, each instance will get an index assigned
in ascending order, starting at index '1'.
{line1:<63}
{line2:<63}
╟───────────────────────────────────────────────────────╢
"""
)[1:]
print(dialog, end="")
print_back_footer()
def print_missing_usergroup_dialog(missing_groups) -> None:
line1 = f"{COLOR_YELLOW}WARNING: Your current user is not in group:{RESET_FORMAT}"
line2 = f"{COLOR_CYAN}● tty{RESET_FORMAT}"
line3 = f"{COLOR_CYAN}● dialout{RESET_FORMAT}"
line4 = f"{COLOR_YELLOW}INFO:{RESET_FORMAT}"
line5 = f"{COLOR_YELLOW}Relog required for group assignments to take effect!{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| {line1:<63}|
"""
)[1:]
if "tty" in missing_groups:
dialog += f"| {line2:<63}|\n"
if "dialout" in missing_groups:
dialog += f"| {line3:<63}|\n"
dialog += textwrap.dedent(
f"""
| |
| It is possible that you won't be able to successfully |
| connect and/or flash the controller board without |
| your user being a member of that group. |
| If you want to add the current user to the group(s) |
| listed above, answer with 'Y'. Else skip with 'n'. |
| |
| {line4:<63}|
| {line5:<63}|
\\=======================================================/
"""
)[1:]
print(dialog, end="")
def print_update_warn_dialog() -> None:
line1 = f"{COLOR_YELLOW}WARNING:{RESET_FORMAT}"
line2 = f"{COLOR_YELLOW}Do NOT continue if there are ongoing prints running!{RESET_FORMAT}"
line3 = f"{COLOR_YELLOW}All Klipper instances will be restarted during the {RESET_FORMAT}"
line4 = f"{COLOR_YELLOW}update process and ongoing prints WILL FAIL.{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
| {line1:<63}|
| {line2:<63}|
| {line3:<63}|
| {line4:<63}|
\\=======================================================/
"""
)[1:]
print(dialog, end="")

View File

@@ -16,7 +16,6 @@ from components.klipper import (
KLIPPER_REQUIREMENTS_TXT,
)
from components.klipper.klipper import Klipper
from components.klipper.klipper_dialogs import print_update_warn_dialog
from components.klipper.klipper_utils import (
add_to_existing,
backup_klipper_dir,
@@ -39,7 +38,7 @@ from core.settings.kiauh_settings import KiauhSettings
from utils.common import check_install_dependencies
from utils.git_utils import git_clone_wrapper, git_pull_wrapper
from utils.input_utils import get_confirm
from utils.logger import Logger
from utils.logger import DialogType, Logger
from utils.sys_utils import (
cmd_sysctl_manage,
create_python_venv,
@@ -139,7 +138,16 @@ def install_klipper_packages(klipper_dir: Path) -> None:
def update_klipper() -> None:
print_update_warn_dialog()
Logger.print_dialog(
DialogType.WARNING,
[
"Do NOT continue if there are ongoing prints running!",
"All Klipper instances will be restarted during the update process and "
"ongoing prints WILL FAIL.",
],
end="",
)
if not get_confirm("Update Klipper now?"):
return

View File

@@ -23,7 +23,6 @@ from components.klipper import (
from components.klipper.klipper import Klipper
from components.klipper.klipper_dialogs import (
print_instance_overview,
print_missing_usergroup_dialog,
print_select_custom_name_dialog,
print_select_instance_count_dialog,
)
@@ -201,18 +200,29 @@ def klipper_to_multi_conversion(new_name: str) -> None:
def check_user_groups():
current_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()]
missing_groups = []
if "tty" not in current_groups:
missing_groups.append("tty")
if "dialout" not in current_groups:
missing_groups.append("dialout")
user_groups = [grp.getgrgid(gid).gr_name for gid in os.getgroups()]
missing_groups = [g for g in user_groups if g == "tty" or g == "dialout"]
if not missing_groups:
return
print_missing_usergroup_dialog(missing_groups)
Logger.print_dialog(
DialogType.ATTENTION,
[
"Your current user is not in group:",
*[f"{g}" for g in missing_groups],
"\n\n",
"It is possible that you won't be able to successfully connect and/or "
"flash the controller board without your user being a member of that "
"group. If you want to add the current user to the group(s) listed above, "
"answer with 'Y'. Else skip with 'n'.",
"\n\n",
"INFO:",
"Relog required for group assignments to take effect!",
],
end="",
)
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
log = "Skipped adding user to required groups. You might encounter issues."
Logger.warn(log)

View File

@@ -56,20 +56,21 @@ class KlipperRemoveMenu(BaseMenu):
o4 = checked if self.delete_klipper_logs else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Enter a number and hit enter to select / deselect |
| the specific option for removal. |
|-------------------------------------------------------|
| 0) Select everything |
|-------------------------------------------------------|
| 1) {o1} Remove Service |
| 2) {o2} Remove Local Repository |
| 3) {o3} Remove Python Environment |
| 4) {o4} Delete all Log-Files |
|-------------------------------------------------------|
| C) Continue |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
Enter a number and hit enter to select / deselect
the specific option for removal.
╟───────────────────────────────────────────────────────╢
0) Select everything
╟───────────────────────────────────────────────────────╢
1) {o1} Remove Service
2) {o2} Remove Local Repository
3) {o3} Remove Python Environment
4) {o4} Delete all Log-Files
╟───────────────────────────────────────────────────────╢
C) Continue
╟───────────────────────────────────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -57,11 +57,11 @@ class KlipperBuildFirmwareMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| The following dependencies are required: |
| |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
The following dependencies are required:
"""
)[1:]
@@ -71,15 +71,15 @@ class KlipperBuildFirmwareMenu(BaseMenu):
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 += f" {d}{status:>{padding}} \n"
menu += "║ ║\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"
menu += f" {line:<62} \n"
print(menu, end="")

View File

@@ -39,22 +39,22 @@ class KlipperNoFirmwareErrorMenu(BaseMenu):
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: |
╔═══════════════════════════════════════════════════════╗
{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"
menu += "'klipper.elf' \n"
menu += "'klipper.elf.hex' \n"
else:
menu += "|'klipper.bin' |\n"
menu += "'klipper.bin' \n"
print(menu, end="")
@@ -86,19 +86,19 @@ class KlipperNoBoardTypesErrorMenu(BaseMenu):
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. |
╔═══════════════════════════════════════════════════════╗
{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="")

View File

@@ -39,32 +39,33 @@ class KlipperFlashMethodHelpMenu(BaseMenu):
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 |
| |
╔═══════════════════════════════════════════════════════╗
{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="")
@@ -96,19 +97,19 @@ class KlipperFlashCommandHelpMenu(BaseMenu):
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. |
| |
╔═══════════════════════════════════════════════════════╗
{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="")
@@ -142,25 +143,26 @@ class KlipperMcuConnectionHelpMenu(BaseMenu):
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. |
| |
╔═══════════════════════════════════════════════════════╗
{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

@@ -38,7 +38,7 @@ from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu
from utils.constants import COLOR_CYAN, COLOR_RED, COLOR_YELLOW, RESET_FORMAT
from utils.input_utils import get_number_input
from utils.logger import Logger
from utils.logger import DialogType, Logger
# noinspection PyUnusedLocal
@@ -74,19 +74,18 @@ class KlipperFlashMethodMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Select the flash method for flashing the MCU. |
| |
| {subheader:<62} |
| {subline1:<62} |
| {subline2:<62} |
|-------------------------------------------------------|
| |
| 1) Regular flashing method |
| 2) Updating via SD-Card Update |
| |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
Select the flash method for flashing the MCU.
{subheader:<62}
{subline1:<62}
{subline2:<62}
╟───────────────────────────────────────────────────────╢
║ 1) Regular flashing method
║ 2) Updating via SD-Card Update
╟───────────────────────────┬───────────────────────────╢
"""
)[1:]
print(menu, end="")
@@ -131,12 +130,12 @@ class KlipperFlashCommandMenu(BaseMenu):
def print_menu(self) -> None:
menu = textwrap.dedent(
"""
/=======================================================\\
| |
| Which flash command to use for flashing the MCU? |
| 1) make flash (default) |
| 2) make serialflash (stm32flash) |
| |
╔═══════════════════════════════════════════════════════╗
║ Which flash command to use for flashing the MCU?
╟───────────────────────────────────────────────────────╢
1) make flash (default)
2) make serialflash (stm32flash)
╟───────────────────────────┬───────────────────────────╢
"""
)[1:]
print(menu, end="")
@@ -185,15 +184,15 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| |
| How is the controller board connected to the host? |
| 1) USB |
| 2) UART |
| 3) USB (DFU mode) |
| |
╔═══════════════════════════════════════════════════════╗
{color}{header:^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
║ How is the controller board connected to the host?
╟───────────────────────────────────────────────────────╢
1) USB
2) UART
3) USB (DFU mode)
╟───────────────────────────┬───────────────────────────╢
"""
)[1:]
print(menu, end="")
@@ -271,20 +270,20 @@ class KlipperSelectMcuIdMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Make sure, to select the correct MCU! |
| ONLY flash a firmware created for the respective MCU! |
| |
|{header2:-^64}|
╔═══════════════════════════════════════════════════════╗
{color}{header:^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
Make sure, to select the correct MCU!
ONLY flash a firmware created for the respective MCU!
{header2:^64}
"""
)[1:]
for i, mcu in enumerate(self.mcu_list):
mcu = mcu.split("/")[-1]
menu += f" ● MCU #{i}: {COLOR_CYAN}{mcu}{RESET_FORMAT}\n"
menu += "╟───────────────────────────┬───────────────────────────╢"
print(menu, end="\n")
@@ -325,12 +324,12 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
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: |
|-------------------------------------------------------|
╔═══════════════════════════════════════════════════════╗
Please select the type of board that corresponds to
the currently selected MCU ID you chose before.
The following boards are currently supported:
╟───────────────────────────────────────────────────────╢
"""
)[1:]
@@ -346,17 +345,16 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
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="")
Logger.print_dialog(
DialogType.CUSTOM,
[
"If your board is flashed with firmware that connects "
"at a custom baud rate, please change it now.",
"\n\n",
"If you are unsure, stick to the default 250000!",
],
end="",
)
self.flash_options.selected_baudrate = get_number_input(
question="Please set the baud rate",
default=250000,
@@ -399,16 +397,15 @@ class KlipperFlashOverviewMenu(BaseMenu):
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}|
╔═══════════════════════════════════════════════════════╗
{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:]
@@ -423,9 +420,9 @@ class KlipperFlashOverviewMenu(BaseMenu):
menu += textwrap.dedent(
"""
|-------------------------------------------------------|
| Y) Start flash process |
| N) Abort - Return to Advanced Menu |
╟───────────────────────────────────────────────────────╢
Y) Start flash process
N) Abort - Return to Advanced Menu
"""
)
print(menu, end="")

View File

@@ -42,17 +42,18 @@ class LogUploadMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| You can select the following logfiles for uploading: |
| |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
You can select the following logfiles for uploading:
"""
)[1:]
for logfile in enumerate(self.logfile_list):
line = f"{logfile[0]}) {logfile[1].get('display_name')}"
menu += f"| {line:<54}|\n"
menu += f" {line:<54}\n"
menu += "╟───────────────────────────────────────────────────────╢\n"
print(menu, end="")

View File

@@ -58,21 +58,22 @@ class MoonrakerRemoveMenu(BaseMenu):
o5 = checked if self.delete_moonraker_logs else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Enter a number and hit enter to select / deselect |
| the specific option for removal. |
|-------------------------------------------------------|
| 0) Select everything |
|-------------------------------------------------------|
| 1) {o1} Remove Service |
| 2) {o2} Remove Local Repository |
| 3) {o3} Remove Python Environment |
| 4) {o4} Remove Policy Kit Rules |
| 5) {o5} Delete all Log-Files |
|-------------------------------------------------------|
| C) Continue |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
Enter a number and hit enter to select / deselect
the specific option for removal.
╟───────────────────────────────────────────────────────╢
0) Select everything
╟───────────────────────────────────────────────────────╢
1) {o1} Remove Service
2) {o2} Remove Local Repository
3) {o3} Remove Python Environment
4) {o4} Remove Policy Kit Rules
5) {o5} Delete all Log-Files
╟───────────────────────────────────────────────────────╢
C) Continue
╟───────────────────────────────────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -25,7 +25,7 @@ from utils.logger import Logger
class Moonraker(BaseInstance):
@classmethod
def blacklist(cls) -> List[str]:
return ["None", "mcu"]
return ["None", "mcu", "obico"]
def __init__(self, suffix: str = ""):
super().__init__(instance_type=self, suffix=suffix)

View File

@@ -25,16 +25,16 @@ def print_moonraker_overview(
headline = f"{COLOR_GREEN}The following instances were found:{RESET_FORMAT}"
dialog = textwrap.dedent(
f"""
/=======================================================\\
|{headline:^64}|
|-------------------------------------------------------|
╔═══════════════════════════════════════════════════════╗
{headline:^64}
╟───────────────────────────────────────────────────────╢
"""
)[1:]
if show_select_all:
select_all = f"{COLOR_YELLOW}a) Select all{RESET_FORMAT}"
dialog += f"| {select_all:<63}|\n"
dialog += "| |\n"
dialog += f" {select_all:<63}\n"
dialog += " \n"
instance_map = {
k.get_service_file_name(): (
@@ -49,18 +49,19 @@ def print_moonraker_overview(
mr_name = instance_map.get(k)
m = f"<-> {mr_name}" if mr_name != "" else ""
line = f"{COLOR_CYAN}{f'{i})' if show_index else ''} {k} {m} {RESET_FORMAT}"
dialog += f"| {line:<63}|\n"
dialog += f" {line:<63}\n"
warn_l1 = f"{COLOR_YELLOW}PLEASE NOTE: {RESET_FORMAT}"
warn_l2 = f"{COLOR_YELLOW}If you select an instance with an existing Moonraker{RESET_FORMAT}"
warn_l3 = f"{COLOR_YELLOW}instance, that Moonraker instance will be re-created!{RESET_FORMAT}"
warning = textwrap.dedent(
f"""
| |
|-------------------------------------------------------|
| {warn_l1:<63}|
| {warn_l2:<63}|
| {warn_l3:<63}|
╟───────────────────────────────────────────────────────╢
{warn_l1:<63}
{warn_l2:<63}
{warn_l3:<63}
╟───────────────────────────────────────────────────────╢
"""
)[1:]

View File

@@ -60,16 +60,16 @@ class ClientRemoveMenu(BaseMenu):
o2 = checked if self.rm_client_config else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Enter a number and hit enter to select / deselect |
| the specific option for removal. |
|-------------------------------------------------------|
| 0) Select everything |
|-------------------------------------------------------|
| 1) {o1} Remove {client_name:16} |
| 2) {o2} Remove {client_config_name:24} |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
Enter a number and hit enter to select / deselect
the specific option for removal.
╟───────────────────────────────────────────────────────╢
0) Select everything
╟───────────────────────────────────────────────────────╢
1) {o1} Remove {client_name:16}
2) {o2} Remove {client_config_name:24}
"""
)[1:]
@@ -77,14 +77,15 @@ class ClientRemoveMenu(BaseMenu):
o3 = checked if self.backup_mainsail_config_json else unchecked
menu += textwrap.dedent(
f"""
| 3) {o3} Backup config.json |
3) {o3} Backup config.json
"""
)[1:]
menu += textwrap.dedent(
"""
|-------------------------------------------------------|
| C) Continue |
╟───────────────────────────────────────────────────────╢
C) Continue
╟───────────────────────────────────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -172,14 +172,18 @@ class InstanceManager:
]
instance_list = [
self.instance_type(suffix=self._get_instance_suffix(service))
self.instance_type(suffix=self._get_instance_suffix(name, service))
for service in service_list
]
return sorted(instance_list, key=lambda x: self._sort_instance_list(x.suffix))
def _get_instance_suffix(self, file_path: Path) -> str:
return file_path.stem.split("-")[-1] if "-" in file_path.stem else ""
def _get_instance_suffix(self, name: str, file_path: Path) -> str:
# to get the suffix of the instance, we remove the name of the instance from
# the file name, if the remaining part an empty string we return it
# otherwise there is and hyphen left, and we return the part after the hyphen
suffix = file_path.stem[len(name) :]
return suffix[1:] if suffix else ""
def _sort_instance_list(self, s: Union[int, str, None]):
if s is None:

View File

@@ -43,12 +43,12 @@ class AdvancedMenu(BaseMenu):
def set_options(self):
self.options = {
"1": Option(method=self.klipper_rollback, menu=True),
"2": Option(method=self.moonraker_rollback, menu=True),
"3": Option(method=self.build, menu=True),
"4": Option(method=self.flash, menu=False),
"5": Option(method=self.build_flash, menu=False),
"6": Option(method=self.get_id, menu=False),
"1": Option(method=self.build, menu=True),
"2": Option(method=self.flash, menu=False),
"3": Option(method=self.build_flash, menu=False),
"4": Option(method=self.get_id, menu=False),
"5": Option(method=self.klipper_rollback, menu=True),
"6": Option(method=self.moonraker_rollback, menu=True),
}
def print_menu(self):
@@ -57,18 +57,15 @@ class AdvancedMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Repo Rollback: |
| 1) [Klipper] |
| 2) [Moonraker] |
| |
| Klipper Firmware: |
| 3) [Build] |
| 4) [Flash] |
| 5) [Build + Flash] |
| 6) [Get MCU ID] |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────┬───────────────────────────╢
║ Klipper Firmware: │ Repository Rollback: ║
1) [Build] 5) [Klipper]
2) [Flash] │ 6) [Moonraker]
║ 3) [Build + Flash] │
║ 4) [Get MCU ID] │
╟───────────────────────────┴───────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -62,20 +62,21 @@ class BackupMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {line1:^62} |
|-------------------------------------------------------|
| Klipper & Moonraker API: | Client-Config: |
| 1) [Klipper] | 7) [Mainsail-Config] |
| 2) [Moonraker] | 8) [Fluidd-Config] |
| 3) [Config Folder] | |
| 4) [Moonraker Database] | Touchscreen GUI: |
| | 9) [KlipperScreen] |
| Webinterface: | |
| 5) [Mainsail] | |
| 6) [Fluidd] | |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
{line1:^62}
╟───────────────────────────┬───────────────────────────╢
Klipper & Moonraker API: Client-Config:
1) [Klipper] 7) [Mainsail-Config]
2) [Moonraker] 8) [Fluidd-Config]
3) [Config Folder]
4) [Moonraker Database] Touchscreen GUI:
9) [KlipperScreen]
Webinterface:
5) [Mainsail]
6) [Fluidd]
╟───────────────────────────┴───────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -39,11 +39,11 @@ def print_header():
count = 62 - len(color) - len(RESET_FORMAT)
header = textwrap.dedent(
f"""
/=======================================================\\
| {color}{line1:~^{count}}{RESET_FORMAT} |
| {color}{line2:^{count}}{RESET_FORMAT} |
| {color}{line3:~^{count}}{RESET_FORMAT} |
\=======================================================/
╔═══════════════════════════════════════════════════════╗
{color}{line1:~^{count}}{RESET_FORMAT}
{color}{line2:^{count}}{RESET_FORMAT}
{color}{line3:~^{count}}{RESET_FORMAT}
╚═══════════════════════════════════════════════════════╝
"""
)[1:]
print(header, end="")
@@ -55,9 +55,8 @@ def print_quit_footer():
count = 62 - len(color) - len(RESET_FORMAT)
footer = textwrap.dedent(
f"""
|-------------------------------------------------------|
| {color}{text:^{count}}{RESET_FORMAT} |
\=======================================================/
{color}{text:^{count}}{RESET_FORMAT}
╚═══════════════════════════════════════════════════════╝
"""
)[1:]
print(footer, end="")
@@ -69,9 +68,8 @@ def print_back_footer():
count = 62 - len(color) - len(RESET_FORMAT)
footer = textwrap.dedent(
f"""
|-------------------------------------------------------|
| {color}{text:^{count}}{RESET_FORMAT} |
\=======================================================/
{color}{text:^{count}}{RESET_FORMAT}
╚═══════════════════════════════════════════════════════╝
"""
)[1:]
print(footer, end="")
@@ -85,16 +83,15 @@ def print_back_help_footer():
count = 34 - len(color1) - len(RESET_FORMAT)
footer = textwrap.dedent(
f"""
|-------------------------------------------------------|
| {color1}{text1:^{count}}{RESET_FORMAT} | {color2}{text2:^{count}}{RESET_FORMAT} |
\=======================================================/
{color1}{text1:^{count}}{RESET_FORMAT}{color2}{text2:^{count}}{RESET_FORMAT}
╚═══════════════════════════╧═══════════════════════════╝
"""
)[1:]
print(footer, end="")
def print_blank_footer():
print("\=======================================================/")
print("╚═══════════════════════════════════════════════════════╝")
class PostInitCaller(type):
@@ -168,7 +165,7 @@ class BaseMenu(metaclass=PostInitCaller):
elif self.footer_type is FooterType.BLANK:
print_blank_footer()
else:
raise NotImplementedError
raise NotImplementedError("FooterType not correctly implemented!")
def display_menu(self) -> None:
if self.header:

View File

@@ -57,21 +57,22 @@ class InstallMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Firmware & API: | Touchscreen GUI: |
| 1) [Klipper] | 7) [KlipperScreen] |
| 2) [Moonraker] | |
| | Android / iOS: |
| Webinterface: | 8) [Mobileraker] |
| 3) [Mainsail] | |
| 4) [Fluidd] | Webcam Streamer: |
| | 9) [Crowsnest] |
| Client-Config: | |
| 5) [Mainsail-Config] | |
| 6) [Fluidd-Config] | |
| | |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────┬───────────────────────────╢
Firmware & API: Touchscreen GUI:
1) [Klipper] 7) [KlipperScreen]
2) [Moonraker]
Android / iOS:
Webinterface: 8) [Mobileraker]
3) [Mainsail]
4) [Fluidd] Webcam Streamer:
9) [Crowsnest]
Client-Config:
5) [Mainsail-Config]
6) [Fluidd-Config]
╟───────────────────────────┴───────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -6,7 +6,7 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
import sys
import textwrap
from typing import Optional, Type
@@ -39,6 +39,7 @@ from utils.constants import (
COLOR_YELLOW,
RESET_FORMAT,
)
from utils.logger import Logger
from utils.types import ComponentStatus
@@ -117,7 +118,7 @@ class MainMenu(BaseMenu):
self.fetch_status()
header = " [ Main Menu ] "
footer1 = "KIAUH v6.0.0"
footer1 = f"{COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT}"
footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}"
color = COLOR_CYAN
count = 62 - len(color) - len(RESET_FORMAT)
@@ -125,28 +126,33 @@ class MainMenu(BaseMenu):
pad2 = 26
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| 0) [Log-Upload] | Klipper: {self.kl_status:<{pad1}} |
| | Repo: {self.kl_repo:<{pad1}} |
| 1) [Install] |------------------------------------|
| 2) [Update] | Moonraker: {self.mr_status:<{pad1}} |
| 3) [Remove] | Repo: {self.mr_repo:<{pad1}} |
| 4) [Advanced] |------------------------------------|
| 5) [Backup] | Mainsail: {self.ms_status:<{pad2}} |
| | Fluidd: {self.fl_status:<{pad2}} |
| S) [Settings] | Client-Config: {self.cc_status:<{pad2}} |
| | |
| Community: | KlipperScreen: {self.ks_status:<{pad2}} |
| E) [Extensions] | Mobileraker: {self.mb_status:<{pad2}} |
| | Crowsnest: {self.cn_status:<{pad2}} |
|-------------------------------------------------------|
| {COLOR_CYAN}{footer1:^16}{RESET_FORMAT} | {footer2:^43} |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟──────────────────┬────────────────────────────────────╢
0) [Log-Upload] Klipper: {self.kl_status:<{pad1}}
Repo: {self.kl_repo:<{pad1}}
1) [Install] ├────────────────────────────────────╢
2) [Update] Moonraker: {self.mr_status:<{pad1}}
3) [Remove] Repo: {self.mr_repo:<{pad1}}
4) [Advanced] ├────────────────────────────────────╢
5) [Backup] Mainsail: {self.ms_status:<{pad2}}
Fluidd: {self.fl_status:<{pad2}}
S) [Settings] Client-Config: {self.cc_status:<{pad2}}
Community: KlipperScreen: {self.ks_status:<{pad2}}
E) [Extensions] Mobileraker: {self.mb_status:<{pad2}}
Crowsnest: {self.cn_status:<{pad2}}
╟──────────────────┼────────────────────────────────────╢
{footer1:^25} {footer2:^43}
╟──────────────────┴────────────────────────────────────╢
"""
)[1:]
print(menu, end="")
def exit(self, **kwargs):
Logger.print_ok("###### Happy printing!", False)
sys.exit(0)
def log_upload_menu(self, **kwargs):
LogUploadMenu().run()

View File

@@ -56,19 +56,20 @@ class RemoveMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| INFO: Configurations and/or any backups will be kept! |
|-------------------------------------------------------|
| Firmware & API: | Touchscreen GUI: |
| 1) [Klipper] | 5) [KlipperScreen] |
| 2) [Moonraker] | |
| | Android / iOS: |
| Klipper Webinterface: | 6) [Mobileraker] |
| 3) [Mainsail] | |
| 4) [Fluidd] | Webcam Streamer: |
| | 7) [Crowsnest] |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
INFO: Configurations and/or any backups will be kept!
╟───────────────────────────┬───────────────────────────╢
Firmware & API: Touchscreen GUI:
1) [Klipper] 5) [KlipperScreen]
2) [Moonraker]
Android / iOS:
Klipper Webinterface: 6) [Mobileraker]
3) [Mainsail]
4) [Fluidd] Webcam Streamer:
7) [Crowsnest]
╟───────────────────────────┴───────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -65,30 +65,31 @@ class SettingsMenu(BaseMenu):
o3 = checked if self.auto_backups_enabled else unchecked
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| Klipper source repository: |
|{self.klipper_repo:<67} |
| |
| Moonraker source repository: |
|{self.moonraker_repo:<67} |
| |
| Install unstable Webinterface releases: |
| {o1} Mainsail |
| {o2} Fluidd |
| |
| Auto-Backup: |
| {o3} Automatic backup before update |
| |
|-------------------------------------------------------|
| 1) Set Klipper source repository |
| 2) Set Moonraker source repository |
| |
| 3) Toggle unstable Mainsail releases |
| 4) Toggle unstable Fluidd releases |
| |
| 5) Toggle automatic backups before updates |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
Klipper source repository:
{self.klipper_repo:<67}
Moonraker source repository:
{self.moonraker_repo:<67}
Install unstable Webinterface releases:
{o1} Mainsail
{o2} Fluidd
Auto-Backup:
{o3} Automatic backup before update
╟───────────────────────────────────────────────────────╢
1) Set Klipper source repository
2) Set Moonraker source repository
3) Toggle unstable Mainsail releases
4) Toggle unstable Fluidd releases
5) Toggle automatic backups before updates
╟───────────────────────────────────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -93,29 +93,30 @@ class UpdateMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| 0) Update all | | |
| | Current: | Latest: |
| Klipper & API: |---------------|---------------|
| 1) Klipper | {self.kl_local:<22} | {self.kl_remote:<22} |
| 2) Moonraker | {self.mr_local:<22} | {self.mr_remote:<22} |
| | | |
| Webinterface: |---------------|---------------|
| 3) Mainsail | {self.ms_local:<22} | {self.ms_remote:<22} |
| 4) Fluidd | {self.fl_local:<22} | {self.fl_remote:<22} |
| | | |
| Client-Config: |---------------|---------------|
| 5) Mainsail-Config | {self.mc_local:<22} | {self.mc_remote:<22} |
| 6) Fluidd-Config | {self.fc_local:<22} | {self.fc_remote:<22} |
| | | |
| Other: |---------------|---------------|
| 7) KlipperScreen | {self.ks_local:<22} | {self.ks_remote:<22} |
| 8) Mobileraker | {self.mb_local:<22} | {self.mb_remote:<22} |
| 9) Crowsnest | {self.cn_local:<22} | {self.cn_remote:<22} |
| |-------------------------------|
| 10) System | |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────┬───────────────┬───────────────╢
0) Update all
Current: Latest:
Klipper & API: ├───────────────┼───────────────╢
1) Klipper {self.kl_local:<22} {self.kl_remote:<22}
2) Moonraker {self.mr_local:<22} {self.mr_remote:<22}
Webinterface: ├───────────────┼───────────────╢
3) Mainsail {self.ms_local:<22} {self.ms_remote:<22}
4) Fluidd {self.fl_local:<22} {self.fl_remote:<22}
Client-Config: ├───────────────┼───────────────╢
5) Mainsail-Config {self.mc_local:<22} {self.mc_remote:<22}
6) Fluidd-Config {self.fc_local:<22} {self.fc_remote:<22}
Other: ├───────────────┼───────────────╢
7) KlipperScreen {self.ks_local:<22} {self.ks_remote:<22}
8) Mobileraker {self.mb_local:<22} {self.mb_remote:<22}
9) Crowsnest {self.cn_local:<22} {self.cn_remote:<22}
├───────────────┴───────────────╢
10) System
╟───────────────────────┴───────────────────────────────╢
"""
)[1:]
print(menu, end="")

View File

@@ -8,7 +8,6 @@
# ======================================================================= #
from __future__ import annotations
import textwrap
from typing import Union
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
@@ -16,12 +15,14 @@ from core.submodules.simple_config_parser.src.simple_config_parser.simple_config
NoSectionError,
SimpleConfigParser,
)
from utils.constants import COLOR_RED, RESET_FORMAT
from utils.logger import Logger
from utils.logger import DialogType, Logger
from utils.sys_utils import kill
from kiauh import PROJECT_ROOT
DEFAULT_CFG = PROJECT_ROOT.joinpath("default.kiauh.cfg")
CUSTOM_CFG = PROJECT_ROOT.joinpath("kiauh.cfg")
class AppSettings:
def __init__(self) -> None:
@@ -56,8 +57,6 @@ class FluiddSettings:
# noinspection PyMethodMayBeStatic
class KiauhSettings:
_instance = None
_default_cfg = PROJECT_ROOT.joinpath("default_kiauh.cfg")
_custom_cfg = PROJECT_ROOT.joinpath("kiauh.cfg")
def __new__(cls, *args, **kwargs) -> "KiauhSettings":
if cls._instance is None:
@@ -120,14 +119,14 @@ class KiauhSettings:
def save(self) -> None:
self._set_config_options()
self.config.write(self._custom_cfg)
self.config.write(CUSTOM_CFG)
self._load_config()
def _load_config(self) -> None:
if not self._custom_cfg.exists() or not self._default_cfg.exists():
if not CUSTOM_CFG.exists() and not DEFAULT_CFG.exists():
self._kill()
cfg = self._custom_cfg if self._custom_cfg.exists() else self._default_cfg
cfg = CUSTOM_CFG if CUSTOM_CFG.exists() else DEFAULT_CFG
self.config.read(cfg)
self._validate_cfg()
@@ -212,15 +211,14 @@ class KiauhSettings:
)
def _kill(self) -> None:
l1 = "!!! ERROR !!!"
l2 = "No KIAUH configuration file found!"
error = textwrap.dedent(
f"""
{COLOR_RED}/=======================================================\\
| {l1:^53} |
| {l2:^53} |
\=======================================================/{RESET_FORMAT}
"""
)[1:]
print(error, end="")
Logger.print_dialog(
DialogType.ERROR,
[
"No KIAUH configuration file found! Please make sure you have at least "
"one of the following configuration files in KIAUH's root directory:",
"● default.kiauh.cfg",
"● kiauh.cfg",
],
end="",
)
kill()

View File

@@ -85,7 +85,7 @@ class DuplicateOptionError(Exception):
class SimpleConfigParser:
"""A customized config parser targeted at handling Klipper style config files"""
_SECTION_RE = re.compile(r"\s*\[(\w+ ?\w+)]\s*([#;].*)?$")
_SECTION_RE = re.compile(r"\s*\[(\w+\s?.+)]\s*([#;].*)?$")
_OPTION_RE = re.compile(r"^\s*(\w+)\s*[:=]\s*([^=:].*)\s*([#;].*)?$")
_MLOPTION_RE = re.compile(r"^\s*(\w+)\s*[:=]\s*([#;].*)?$")
_COMMENT_RE = re.compile(r"^\s*([#;].*)?$")
@@ -192,9 +192,9 @@ class SimpleConfigParser:
if section not in self._all_sections:
raise NoSectionError(section)
del self._all_sections[self._all_sections.index(section)]
del self._all_options[section]
del self._config[section]
self._all_sections.pop(self._all_sections.index(section))
self._all_options.pop(section)
self._config.pop(section)
def options(self, section) -> List[str]:
"""Return a list of option names for the given section name"""
@@ -453,6 +453,7 @@ class SimpleConfigParser:
self.section_name = section
self._all_sections.append(section)
self._all_options[section] = {}
self._config[section]: Section = {"_raw": raw_value, "body": []}
def _parse_option(self, line: str) -> None:

View File

@@ -34,6 +34,7 @@ class TestInternalStateChanges:
parser._store_internal_state_section(given, given)
assert parser._all_sections == [given]
assert parser._all_options[given] == {}
assert parser._config[given]["body"] == []
assert parser._config[given]["_raw"] == given

View File

@@ -8,6 +8,11 @@ testcases = [
("option: value\n", "option", "value"),
("option: value # inline comment", "option", "value"),
("option: value # inline comment\n", "option", "value"),
(
"description: Helper: park toolhead used in PAUSE and CANCEL_PRINT",
"description",
"Helper: park toolhead used in PAUSE and CANCEL_PRINT",
),
("description: homing!", "description", "homing!"),
("description: inline macro :-)", "description", "inline macro :-)"),
("path: %GCODES_DIR%", "path", "%GCODES_DIR%"),

View File

@@ -3,4 +3,6 @@ testcases = [
("[test_section two]", "test_section two"),
("[section1] # inline comment", "section1"),
("[section2] ; second comment", "section2"),
("[include moonraker-obico-update.cfg]", "include moonraker-obico-update.cfg"),
("[include moonraker_obico_macros.cfg]", "include moonraker_obico_macros.cfg"),
]

View File

@@ -1,5 +1,11 @@
testcases = [
("[example_section]", True),
("[gcode_macro CANCEL_PRINT]", True),
("[gcode_macro SET_PAUSE_NEXT_LAYER]", True),
("[gcode_macro _TOOLHEAD_PARK_PAUSE_CANCEL]", True),
("[update_manager moonraker-obico]", True),
("[include moonraker_obico_macros.cfg]", True),
("[include moonraker-obico-update.cfg]", True),
("[example_section two]", True),
("not_a_valid_section", False),
("section: invalid", False),

View File

@@ -108,7 +108,7 @@ class TestPublicAPI:
assert parser._config[section]["body"][0]["option"] == option
values = ["value1", "value2", "value3"]
raw_values = [" value1\n", " value2\n", " value3\n"]
raw_values = [" value1\n", " value2\n", " value3\n"]
assert parser._config[section]["body"][0]["value"] == values
assert parser._config[section]["body"][0]["_raw"] == f"{option}:\n"
assert parser._config[section]["body"][0]["_raw_value"] == raw_values

View File

@@ -87,11 +87,11 @@ class ExtensionsMenu(BaseMenu):
count = 62 - len(color) - len(RESET_FORMAT)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
| {line1:<62} |
| |
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
{line1:<62}
"""
)[1:]
print(menu, end="")
@@ -100,7 +100,8 @@ class ExtensionsMenu(BaseMenu):
index = extension.metadata.get("index")
name = extension.metadata.get("display_name")
row = f"{index}) {name}"
print(f"| {row:<53} |")
print(f" {row:<53} ")
print("╟───────────────────────────────────────────────────────╢")
# noinspection PyUnusedLocal
@@ -135,29 +136,30 @@ class ExtensionSubmenu(BaseMenu):
description_text = Logger.format_content(
description,
line_width,
border_left="|",
border_right="|",
border_left="",
border_right="",
)
menu = textwrap.dedent(
f"""
/=======================================================\\
| {color}{header:~^{count}}{RESET_FORMAT} |
|-------------------------------------------------------|
╔═══════════════════════════════════════════════════════╗
{color}{header:~^{count}}{RESET_FORMAT}
╟───────────────────────────────────────────────────────╢
"""
)[1:]
menu += f"{description_text}\n"
menu += textwrap.dedent(
"""
|-------------------------------------------------------|
| 1) Install |
╟───────────────────────────────────────────────────────╢
1) Install
"""
)[1:]
if self.extension.metadata.get("updates"):
menu += "| 2) Update |\n"
menu += "| 3) Remove |\n"
menu += " 2) Update \n"
menu += " 3) Remove \n"
else:
menu += "| 2) Remove |\n"
menu += " 2) Remove \n"
menu += "╟───────────────────────────────────────────────────────╢\n"
print(menu, end="")

View File

View File

@@ -0,0 +1 @@
OBICO_ARGS="-m moonraker_obico.app -c %CFG%"

View File

@@ -0,0 +1,16 @@
#Systemd service file for moonraker-obico
[Unit]
Description=Moonraker-Obico
After=network-online.target moonraker.service
[Install]
WantedBy=multi-user.target
[Service]
Type=simple
User=%USER%
WorkingDirectory=%OBICO_DIR%
EnvironmentFile=%ENV_FILE%
ExecStart=%ENV%/bin/python3 $OBICO_ARGS
Restart=always
RestartSec=5

View File

@@ -0,0 +1,16 @@
{
"metadata": {
"index": 6,
"module": "moonraker_obico_extension",
"maintained_by": "Obico",
"display_name": "Obico for Klipper",
"description": [
"Open source 3D Printing cloud and AI",
"- AI-Powered Failure Detection",
"- Free Remote Monitoring and Access",
"- 25FPS High-Def Webcam Streaming",
"- Free 4.9-Star Mobile App"
],
"updates": true
}
}

View File

@@ -0,0 +1,178 @@
# ======================================================================= #
# 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 pathlib import Path
from subprocess import DEVNULL, CalledProcessError, run
from typing import List
from core.instance_manager.base_instance import BaseInstance
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from utils.constants import SYSTEMD
from utils.logger import Logger
MODULE_PATH = Path(__file__).resolve().parent
OBICO_DIR = Path.home().joinpath("moonraker-obico")
OBICO_ENV = Path.home().joinpath("moonraker-obico-env")
OBICO_REPO = "https://github.com/TheSpaghettiDetective/moonraker-obico.git"
OBICO_CFG = "moonraker-obico.cfg"
OBICO_CFG_SAMPLE = "moonraker-obico.cfg.sample"
OBICO_LOG = "moonraker-obico.log"
OBICO_UPDATE_CFG = "moonraker-obico-update.cfg"
OBICO_UPDATE_CFG_SAMPLE = "moonraker-obico-update.cfg.sample"
OBICO_MACROS_CFG = "moonraker_obico_macros.cfg"
# noinspection PyMethodMayBeStatic
class MoonrakerObico(BaseInstance):
@classmethod
def blacklist(cls) -> List[str]:
return ["None", "mcu"]
def __init__(self, suffix: str = ""):
super().__init__(instance_type=self, suffix=suffix)
self.dir: Path = OBICO_DIR
self.env_dir: Path = OBICO_ENV
self._cfg_file = self.cfg_dir.joinpath("moonraker-obico.cfg")
self._log = self.log_dir.joinpath("moonraker-obico.log")
self._is_linked: bool = self._check_link_status()
self._assets_dir = MODULE_PATH.joinpath("assets")
@property
def cfg_file(self) -> Path:
return self._cfg_file
@property
def log(self) -> Path:
return self._log
@property
def is_linked(self) -> bool:
return self._is_linked
def create(self) -> None:
Logger.print_status("Creating new Obico for Klipper Instance ...")
service_template_path = MODULE_PATH.joinpath("assets/moonraker-obico.service")
service_file_name = self.get_service_file_name(extension=True)
service_file_target = SYSTEMD.joinpath(service_file_name)
env_template_file_path = MODULE_PATH.joinpath("assets/moonraker-obico.env")
env_file_target = self.sysd_dir.joinpath("moonraker-obico.env")
try:
self.create_folders()
self.write_service_file(
service_template_path, service_file_target, env_file_target
)
self.write_env_file(env_template_file_path, env_file_target)
except CalledProcessError as e:
Logger.print_error(
f"Error creating service file {service_file_target}: {e}"
)
raise
except OSError as e:
Logger.print_error(f"Error creating env file {env_file_target}: {e}")
raise
def delete(self) -> None:
service_file = self.get_service_file_name(extension=True)
service_file_path = self.get_service_file_path()
Logger.print_status(f"Deleting Obico for Klipper Instance: {service_file}")
try:
command = ["sudo", "rm", "-f", service_file_path]
run(command, check=True)
Logger.print_ok(f"Service file deleted: {service_file_path}")
except CalledProcessError as e:
Logger.print_error(f"Error deleting service file: {e}")
raise
def write_service_file(
self,
service_template_path: Path,
service_file_target: Path,
env_file_target: Path,
) -> None:
service_content = self._prep_service_file(
service_template_path, env_file_target
)
command = ["sudo", "tee", service_file_target]
run(
command,
input=service_content.encode(),
stdout=DEVNULL,
check=True,
)
Logger.print_ok(f"Service file created: {service_file_target}")
def write_env_file(
self, env_template_file_path: Path, env_file_target: Path
) -> None:
env_file_content = self._prep_env_file(env_template_file_path)
with open(env_file_target, "w") as env_file:
env_file.write(env_file_content)
Logger.print_ok(f"Env file created: {env_file_target}")
def link(self) -> None:
Logger.print_status(
f"Linking instance for printer {self.data_dir_name} to the Obico server ..."
)
try:
script = OBICO_DIR.joinpath("scripts/link.sh")
cmd = [f"{script} -q -c {self.cfg_file}"]
if self.suffix:
cmd.append(f"-n {self.suffix}")
run(cmd, check=True, shell=True)
except CalledProcessError as e:
Logger.print_error(f"Error during Obico linking: {e}")
raise
def _prep_service_file(
self, service_template_path: Path, env_file_path: Path
) -> str:
try:
with open(service_template_path, "r") as template_file:
template_content = template_file.read()
except FileNotFoundError:
Logger.print_error(
f"Unable to open {service_template_path} - File not found"
)
raise
service_content = template_content.replace("%USER%", self.user)
service_content = service_content.replace("%OBICO_DIR%", str(self.dir))
service_content = service_content.replace("%ENV%", str(self.env_dir))
service_content = service_content.replace("%ENV_FILE%", str(env_file_path))
return service_content
def _prep_env_file(self, env_template_file_path: Path) -> str:
try:
with open(env_template_file_path, "r") as env_file:
env_template_file_content = env_file.read()
except FileNotFoundError:
Logger.print_error(
f"Unable to open {env_template_file_path} - File not found"
)
raise
env_file_content = env_template_file_content.replace(
"%CFG%",
f"{self.cfg_dir}/{self.cfg_file}",
)
return env_file_content
def _check_link_status(self) -> bool:
if not self.cfg_file.exists():
return False
scp = SimpleConfigParser()
scp.read(self.cfg_file)
return scp.get("server", "auth_token", None) is not None

View File

@@ -0,0 +1,386 @@
# ======================================================================= #
# 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 shutil
from typing import List
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from core.instance_manager.instance_manager import InstanceManager
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from extensions.base_extension import BaseExtension
from extensions.obico.moonraker_obico import (
OBICO_CFG_SAMPLE,
OBICO_DIR,
OBICO_ENV,
OBICO_LOG,
OBICO_MACROS_CFG,
OBICO_REPO,
OBICO_UPDATE_CFG,
OBICO_UPDATE_CFG_SAMPLE,
MoonrakerObico,
)
from utils.common import check_install_dependencies, moonraker_exists
from utils.config_utils import (
add_config_section,
remove_config_section,
)
from utils.fs_utils import remove_file
from utils.git_utils import git_clone_wrapper, git_pull_wrapper
from utils.input_utils import get_confirm, get_selection_input, get_string_input
from utils.logger import DialogType, Logger
from utils.sys_utils import (
cmd_sysctl_manage,
create_python_venv,
install_python_requirements,
parse_packages_from_file,
)
# noinspection PyMethodMayBeStatic
class ObicoExtension(BaseExtension):
server_url: str
def install_extension(self, **kwargs) -> None:
Logger.print_status("Installing Obico for Klipper ...")
# check if moonraker is installed. if not, notify the user and exit
if not moonraker_exists():
return
# if obico is already installed, ask if the user wants to repair an
# incomplete installation or link to the obico server
obico_im = InstanceManager(MoonrakerObico)
obico_instances: List[MoonrakerObico] = obico_im.instances
if obico_instances:
self._print_is_already_installed()
options = ["l", "L", "r", "R", "b", "B"]
action = get_selection_input("Perform action", option_list=options)
if action.lower() == "b":
Logger.print_info("Exiting Obico for Klipper installation ...")
return
elif action.lower() == "l":
unlinked_instances: List[MoonrakerObico] = [
obico for obico in obico_instances if not obico.is_linked
]
self._link_obico_instances(unlinked_instances)
return
else:
Logger.print_status("Re-Installing Obico for Klipper ...")
# let the user confirm installation
kl_im = InstanceManager(Klipper)
kl_instances: List[Klipper] = kl_im.instances
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
self._print_moonraker_instances(mr_instances)
if not get_confirm(
"Continue Obico for Klipper installation?",
default_choice=True,
allow_go_back=True,
):
return
try:
git_clone_wrapper(OBICO_REPO, OBICO_DIR)
self._install_dependencies()
# ask the user for the obico server url
self._get_server_url()
# create obico instances
for moonraker in mr_instances:
current_instance = MoonrakerObico(suffix=moonraker.suffix)
obico_im.current_instance = current_instance
obico_im.create_instance()
obico_im.enable_instance()
# create obico config
self._create_obico_cfg(current_instance, moonraker)
# create obico macros
self._create_obico_macros_cfg(moonraker)
# create obico update manager
self._create_obico_update_manager_cfg(moonraker)
obico_im.start_instance()
cmd_sysctl_manage("daemon-reload")
# add to klippers config
self._patch_printer_cfg(kl_instances)
kl_im.restart_all_instance()
# add to moonraker update manager
self._patch_moonraker_conf(mr_instances)
mr_im.restart_all_instance()
# check linking of / ask for linking instances
self._check_and_opt_link_instances()
Logger.print_dialog(
DialogType.SUCCESS,
["Obico for Klipper successfully installed!"],
center_content=True,
)
except Exception as e:
Logger.print_error(f"Error during Obico for Klipper installation:\n{e}")
def update_extension(self, **kwargs) -> None:
Logger.print_status("Updating Obico for Klipper ...")
try:
tb_im = InstanceManager(MoonrakerObico)
tb_im.stop_all_instance()
git_pull_wrapper(OBICO_REPO, OBICO_DIR)
self._install_dependencies()
tb_im.start_all_instance()
Logger.print_ok("Obico for Klipper successfully updated!")
except Exception as e:
Logger.print_error(f"Error during Obico for Klipper update:\n{e}")
def remove_extension(self, **kwargs) -> None:
Logger.print_status("Removing Obico for Klipper ...")
kl_im = InstanceManager(Klipper)
kl_instances: List[Klipper] = kl_im.instances
mr_im = InstanceManager(Moonraker)
mr_instances: List[Moonraker] = mr_im.instances
ob_im = InstanceManager(MoonrakerObico)
ob_instances: List[MoonrakerObico] = ob_im.instances
try:
self._remove_obico_instances(ob_im, ob_instances)
self._remove_obico_dir()
self._remove_obico_env()
remove_config_section(f"include {OBICO_MACROS_CFG}", kl_instances)
remove_config_section(f"include {OBICO_UPDATE_CFG}", mr_instances)
self._delete_obico_logs(ob_instances)
Logger.print_dialog(
DialogType.SUCCESS,
["Obico for Klipper successfully removed!"],
center_content=True,
)
except Exception as e:
Logger.print_error(f"Error during Obico for Klipper removal:\n{e}")
def _obico_server_url_prompt(self) -> None:
Logger.print_dialog(
DialogType.CUSTOM,
custom_title="Obico Server URL",
content=[
"You can use a self-hosted Obico Server or the Obico Cloud. "
"For more information, please visit:",
"https://obico.io.",
"\n\n",
"For the Obico Cloud, leave it as the default:",
"https://app.obico.io.",
"\n\n",
"For self-hosted server, specify:",
"http://server_ip:port",
"For instance, 'http://192.168.0.5:3334'.",
],
end="",
)
def _print_moonraker_instances(self, mr_instances) -> None:
mr_names = [f"{moonraker.data_dir_name}" for moonraker in mr_instances]
if len(mr_names) > 1:
Logger.print_dialog(
DialogType.INFO,
[
"The following Moonraker instances were found:",
*mr_names,
"\n\n",
"The setup will apply the same names to Obico!",
],
end="",
)
def _print_is_already_installed(self) -> None:
Logger.print_dialog(
DialogType.INFO,
[
"Obico is already installed!",
"It is save to run the installer again to link your "
"printer or repair any issues.",
"\n\n",
"You can perform the following actions:",
"L) Link printer to the Obico server",
"R) Repair installation",
],
end="",
)
def _get_server_url(self) -> None:
self._obico_server_url_prompt()
pattern = r"^(http|https)://[a-zA-Z0-9./?=_%:-]*$"
self.server_url = get_string_input(
"Obico Server URL",
regex=pattern,
default="https://app.obico.io",
)
def _install_dependencies(self) -> None:
# install dependencies
script = OBICO_DIR.joinpath("install.sh")
package_list = parse_packages_from_file(script)
check_install_dependencies(package_list)
# create virtualenv
create_python_venv(OBICO_ENV)
requirements = OBICO_DIR.joinpath("requirements.txt")
install_python_requirements(OBICO_ENV, requirements)
def _create_obico_macros_cfg(self, moonraker) -> None:
macros_cfg = OBICO_DIR.joinpath(f"include_cfgs/{OBICO_MACROS_CFG}")
macros_target = moonraker.cfg_dir.joinpath(OBICO_MACROS_CFG)
if not macros_target.exists():
shutil.copy(macros_cfg, macros_target)
else:
Logger.print_info(
f"Obico's '{OBICO_MACROS_CFG}' in {moonraker.cfg_dir} already exists! Skipped ..."
)
def _create_obico_update_manager_cfg(self, moonraker) -> None:
update_cfg = OBICO_DIR.joinpath(OBICO_UPDATE_CFG_SAMPLE)
update_cfg_target = moonraker.cfg_dir.joinpath(OBICO_UPDATE_CFG)
if not update_cfg_target.exists():
shutil.copy(update_cfg, update_cfg_target)
else:
Logger.print_info(
f"Obico's '{OBICO_UPDATE_CFG}' in {moonraker.cfg_dir} already exists! Skipped ..."
)
def _create_obico_cfg(
self, current_instance: MoonrakerObico, moonraker: Moonraker
) -> None:
cfg_template = OBICO_DIR.joinpath(OBICO_CFG_SAMPLE)
cfg_target_file = current_instance.cfg_file
if not cfg_template.exists():
Logger.print_error(
f"Obico config template file {cfg_target_file} does not exist!"
)
return
if not cfg_target_file.exists():
shutil.copy(cfg_template, cfg_target_file)
self._patch_obico_cfg(moonraker, current_instance)
else:
Logger.print_info(
f"Obico config in {current_instance.cfg_dir} already exists! Skipped ..."
)
def _patch_obico_cfg(self, moonraker: Moonraker, obico: MoonrakerObico) -> None:
scp = SimpleConfigParser()
scp.read(obico.cfg_file)
scp.set("server", "url", self.server_url)
scp.set("moonraker", "port", str(moonraker.port))
scp.set("logging", "path", str(obico.log))
scp.write(obico.cfg_file)
def _patch_printer_cfg(self, klipper: List[Klipper]) -> None:
add_config_section(section=f"include {OBICO_MACROS_CFG}", instances=klipper)
def _patch_moonraker_conf(self, instances: List[Moonraker]) -> None:
add_config_section(section=f"include {OBICO_UPDATE_CFG}", instances=instances)
def _link_obico_instances(self, unlinked_instances):
for obico in unlinked_instances:
obico.link()
def _check_and_opt_link_instances(self):
Logger.print_status("Checking link status of Obico instances ...")
ob_im = InstanceManager(MoonrakerObico)
ob_instances: List[MoonrakerObico] = ob_im.instances
unlinked_instances: List[MoonrakerObico] = [
obico for obico in ob_instances if not obico.is_linked
]
if unlinked_instances:
Logger.print_dialog(
DialogType.INFO,
[
"The Obico instances for the following printers are not "
"linked to the server:",
*[f"{obico.data_dir_name}" for obico in unlinked_instances],
"\n\n",
"It will take only 10 seconds to link the printer to the Obico server.",
"For more information visit:",
"https://www.obico.io/docs/user-guides/klipper-setup/",
"\n\n",
"If you don't want to link the printer now, you can restart the "
"linking process later by running this installer again.",
],
end="",
)
if not get_confirm("Do you want to link the printers now?"):
Logger.print_info("Linking to Obico server skipped ...")
return
self._link_obico_instances(unlinked_instances)
def _remove_obico_instances(
self,
instance_manager: InstanceManager,
instance_list: List[MoonrakerObico],
) -> None:
if not instance_list:
Logger.print_info("No Obico instances found. Skipped ...")
return
for instance in instance_list:
Logger.print_status(
f"Removing instance {instance.get_service_file_name()} ..."
)
instance_manager.current_instance = instance
instance_manager.stop_instance()
instance_manager.disable_instance()
instance_manager.delete_instance()
cmd_sysctl_manage("daemon-reload")
def _remove_obico_dir(self) -> None:
if not OBICO_DIR.exists():
Logger.print_info(f"'{OBICO_DIR}' does not exist. Skipped ...")
return
try:
shutil.rmtree(OBICO_DIR)
except OSError as e:
Logger.print_error(f"Unable to delete '{OBICO_DIR}':\n{e}")
def _remove_obico_env(self) -> None:
if not OBICO_ENV.exists():
Logger.print_info(f"'{OBICO_ENV}' does not exist. Skipped ...")
return
try:
shutil.rmtree(OBICO_ENV)
except OSError as e:
Logger.print_error(f"Unable to delete '{OBICO_ENV}':\n{e}")
def _delete_obico_logs(self, instances: List[MoonrakerObico]) -> None:
Logger.print_status("Removing Obico logs ...")
all_logfiles = []
for instance in instances:
all_logfiles = list(instance.log_dir.glob(f"{OBICO_LOG}*"))
if not all_logfiles:
Logger.print_info("No Obico logs found. Skipped ...")
return
for log in all_logfiles:
Logger.print_status(f"Remove '{log}'")
remove_file(log)

View File

@@ -43,7 +43,7 @@ def add_config_section(
scp.add_section(section)
if options is not None:
for option in options:
for option in reversed(options):
scp.set(section, option[0], option[1])
scp.write(cfg_file)

View File

@@ -120,15 +120,19 @@ class Logger:
@staticmethod
def _format_top_border(color: str) -> str:
return textwrap.dedent(f"""
return textwrap.dedent(
f"""
{color}┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
""")[:-1]
"""
)[1:-1]
@staticmethod
def _format_bottom_border() -> str:
return textwrap.dedent(f"""
return textwrap.dedent(
f"""
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
{RESET_FORMAT}""")
{RESET_FORMAT}"""
)
@staticmethod
def _format_dialog_title(title: str) -> str: