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 <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2023-01-06 08:09:42 -05:00
parent 019c5fc416
commit ecf7fb9267
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
4 changed files with 38 additions and 43 deletions

View File

@ -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))

View File

@ -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")

View File

@ -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')

View File

@ -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"