From ecf7fb926760820988fb7fed76d99d83a2b457c1 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Fri, 6 Jan 2023 08:09:42 -0500 Subject: [PATCH] klippy_connection: add is_printing() and is_ready() methods Several components throughout Moonraker determine whether or not Klipper is printing or is ready before taking action. This centralizes queries in one area. The checks do not query Klipper directly but rather rely on subscriptions to push state to Moonraker. Signed-off-by: Eric Callahan --- .../components/file_manager/file_manager.py | 31 ++++++++++--------- moonraker/components/power.py | 20 +++--------- .../update_manager/update_manager.py | 19 +++++------- moonraker/klippy_connection.py | 11 +++++++ 4 files changed, 38 insertions(+), 43 deletions(-) diff --git a/moonraker/components/file_manager/file_manager.py b/moonraker/components/file_manager/file_manager.py index 5fc1c13..71b375b 100644 --- a/moonraker/components/file_manager/file_manager.py +++ b/moonraker/components/file_manager/file_manager.py @@ -38,10 +38,12 @@ if TYPE_CHECKING: from inotify_simple import Event as InotifyEvent from confighelper import ConfigHelper from websockets import WebRequest + from klippy_connection import KlippyConnection from components import database from components import klippy_apis from components import shell_command from components.job_queue import JobQueue + from components.job_state import JobState StrOrPath = Union[str, pathlib.Path] DBComp = database.MoonrakerDatabase APIComp = klippy_apis.KlippyAPI @@ -403,7 +405,7 @@ class FileManager: if force: # Make sure that the directory does not contain a file # loaded by the virtual_sdcard - await self._handle_operation_check(dir_path) + self._handle_operation_check(dir_path) self.notify_sync_lock = NotifySyncLock(dir_path) try: await self.event_loop.run_in_thread( @@ -423,20 +425,19 @@ class FileManager: raise self.server.error("Operation Not Supported", 405) return result - async def _handle_operation_check(self, requested_path: str) -> bool: + def _handle_operation_check(self, requested_path: str) -> bool: if not self.get_relative_path("gcodes", requested_path): # Path not in the gcodes path return True - # Get virtual_sdcard status - kapis: APIComp = self.server.lookup_component('klippy_apis') - result: Dict[str, Any] - result = await kapis.query_objects({'print_stats': None}, {}) - pstats = result.get('print_stats', {}) - loaded_file: str = pstats.get('filename', "") - state: str = pstats.get('state', "") + kconn: KlippyConnection + kconn = self.server.lookup_component("klippy_connection") + job_state: JobState = self.server.lookup_component("job_state") + last_stats = job_state.get_last_stats() + loaded_file: str = last_stats.get('filename', "") + state: str = last_stats.get('state', "") gc_path = self.file_paths.get('gcodes', "") full_path = os.path.join(gc_path, loaded_file) - is_printing = state in ["printing", "paused"] + is_printing = kconn.is_ready() and state in ["printing", "paused"] if loaded_file and is_printing: if os.path.isdir(requested_path): # Check to see of the loaded file is in the request @@ -482,13 +483,13 @@ class FileManager: raise self.server.error(f"File {source_path} does not exist") # make sure the destination is not in use if os.path.exists(dest_path): - await self._handle_operation_check(dest_path) + self._handle_operation_check(dest_path) if ep == "/server/files/move": if source_root not in self.full_access_roots: raise self.server.error( f"Source path is read-only, cannot move: {source_root}") # if moving the file, make sure the source is not in use - await self._handle_operation_check(source_path) + self._handle_operation_check(source_path) op_func: Callable[..., str] = shutil.move result['source_item'] = { 'path': source, @@ -665,7 +666,7 @@ class FileManager: can_start: bool = False try: check_path: str = upload_info['dest_path'] - can_start = await self._handle_operation_check(check_path) + can_start = self._handle_operation_check(check_path) except self.server.error as e: if e.status_code == 403: raise self.server.error( @@ -857,7 +858,7 @@ class FileManager: if not os.path.isfile(full_path): raise self.server.error(f"Invalid file path: {path}") try: - await self._handle_operation_check(full_path) + self._handle_operation_check(full_path) except self.server.error as e: if e.status_code == 403: raise @@ -1683,7 +1684,7 @@ class MetadataStorage: def get(self, key: str, - default: _T = None + default: Optional[_T] = None ) -> Union[_T, Dict[str, Any]]: return deepcopy(self.metadata.get(key, default)) diff --git a/moonraker/components/power.py b/moonraker/components/power.py index 72a127d..695fbc6 100644 --- a/moonraker/components/power.py +++ b/moonraker/components/power.py @@ -33,6 +33,7 @@ if TYPE_CHECKING: from .mqtt import MQTTClient from .template import JinjaTemplate from .http_client import HttpClient + from klippy_connection import KlippyConnection APIComp = klippy_apis.KlippyAPI class PrinterPower: @@ -94,13 +95,6 @@ class PrinterPower: "job_queue:job_queue_changed", self._handle_job_queued) self.server.register_notification("power:power_changed") - async def _check_klippy_printing(self) -> bool: - kapis: APIComp = self.server.lookup_component('klippy_apis') - result: Dict[str, Any] = await kapis.query_objects( - {'print_stats': None}, default={}) - pstate = result.get('print_stats', {}).get('state', "").lower() - return pstate == "printing" - async def component_init(self) -> None: for dev in self.devices.values(): if not dev.initialize(): @@ -261,13 +255,6 @@ class PowerDevice: 'initial_state', None ) - async def _check_klippy_printing(self) -> bool: - kapis: APIComp = self.server.lookup_component('klippy_apis') - result: Dict[str, Any] = await kapis.query_objects( - {'print_stats': None}, default={}) - pstate = result.get('print_stats', {}).get('state', "").lower() - return pstate == "printing" - def _schedule_firmware_restart(self, state: str = "") -> None: if not self.need_scheduled_restart: return @@ -399,8 +386,9 @@ class PowerDevice: self.notify_power_changed() return cur_state if not force: - printing = await self._check_klippy_printing() - if self.locked_while_printing and printing: + kconn: KlippyConnection + kconn = self.server.lookup_component("klippy_connection") + if self.locked_while_printing and kconn.is_printing(): raise self.server.error( f"Unable to change power for {self.name} " "while printing") diff --git a/moonraker/components/update_manager/update_manager.py b/moonraker/components/update_manager/update_manager.py index 0dca628..70a4f75 100644 --- a/moonraker/components/update_manager/update_manager.py +++ b/moonraker/components/update_manager/update_manager.py @@ -40,7 +40,7 @@ if TYPE_CHECKING: from moonraker import Server from confighelper import ConfigHelper from websockets import WebRequest - from components.klippy_apis import KlippyAPI as APIComp + from klippy_connection import KlippyConnection from components.shell_command import ShellCommandFactory as SCMDComp from components.database import MoonrakerDatabase as DBComp from components.database import NamespaceWrapper @@ -68,6 +68,8 @@ class UpdateManager: def __init__(self, config: ConfigHelper) -> None: self.server = config.get_server() self.event_loop = self.server.get_event_loop() + self.kconn: KlippyConnection + self.kconn = self.server.lookup_component("klippy_connection") self.channel = config.get('channel', "dev") if self.channel not in ["dev", "beta"]: raise config.error( @@ -224,20 +226,13 @@ class UpdateManager: if notify: self.cmd_helper.notify_update_refreshed() - async def _check_klippy_printing(self) -> bool: - kapi: APIComp = self.server.lookup_component('klippy_apis') - result: Dict[str, Any] = await kapi.query_objects( - {'print_stats': None}, default={}) - pstate: str = result.get('print_stats', {}).get('state', "") - return pstate.lower() == "printing" - async def _handle_auto_refresh(self, eventtime: float) -> float: cur_hour = time.localtime(time.time()).tm_hour if self.initial_refresh_complete: # Update when the local time is between 12AM and 5AM if cur_hour >= MAX_UPDATE_HOUR: return eventtime + UPDATE_REFRESH_INTERVAL - if await self._check_klippy_printing(): + if self.kconn.is_printing(): # Don't Refresh during a print logging.info("Klippy is printing, auto refresh aborted") return eventtime + UPDATE_REFRESH_INTERVAL @@ -268,7 +263,7 @@ class UpdateManager: async def _handle_update_request(self, web_request: WebRequest ) -> str: - if await self._check_klippy_printing(): + if self.kconn.is_printing(): raise self.server.error("Update Refused: Klippy is printing") app: str = web_request.get_endpoint().split("/")[-1] if app == "client": @@ -391,7 +386,7 @@ class UpdateManager: if ( machine.validation_enabled() or self.cmd_helper.is_update_busy() or - await self._check_klippy_printing() or + self.kconn.is_printing() or not self.initial_refresh_complete ): if check_refresh: @@ -433,7 +428,7 @@ class UpdateManager: async def _handle_repo_recovery(self, web_request: WebRequest ) -> str: - if await self._check_klippy_printing(): + if self.kconn.is_printing(): raise self.server.error( "Recovery Attempt Refused: Klippy is printing") app: str = web_request.get_str('name') diff --git a/moonraker/klippy_connection.py b/moonraker/klippy_connection.py index b05cddb..458751d 100644 --- a/moonraker/klippy_connection.py +++ b/moonraker/klippy_connection.py @@ -35,6 +35,7 @@ if TYPE_CHECKING: from components.klippy_apis import KlippyAPI from components.file_manager.file_manager import FileManager from components.machine import Machine + from components.job_state import JobState FlexCallback = Callable[..., Optional[Coroutine]] # These endpoints are reserved for klippy/moonraker communication only and are @@ -550,6 +551,16 @@ class KlippyConnection: def is_connected(self) -> bool: return self.writer is not None and not self.closing + def is_ready(self) -> bool: + return self.state == "ready" + + def is_printing(self) -> bool: + if not self.is_ready(): + return False + job_state: JobState = self.server.lookup_component("job_state") + stats = job_state.get_last_stats() + return stats.get("state", "") == "printing" + async def _on_connection_closed(self) -> None: self.init_list = [] self._state = "disconnected"