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:
parent
019c5fc416
commit
ecf7fb9267
|
@ -38,10 +38,12 @@ if TYPE_CHECKING:
|
||||||
from inotify_simple import Event as InotifyEvent
|
from inotify_simple import Event as InotifyEvent
|
||||||
from confighelper import ConfigHelper
|
from confighelper import ConfigHelper
|
||||||
from websockets import WebRequest
|
from websockets import WebRequest
|
||||||
|
from klippy_connection import KlippyConnection
|
||||||
from components import database
|
from components import database
|
||||||
from components import klippy_apis
|
from components import klippy_apis
|
||||||
from components import shell_command
|
from components import shell_command
|
||||||
from components.job_queue import JobQueue
|
from components.job_queue import JobQueue
|
||||||
|
from components.job_state import JobState
|
||||||
StrOrPath = Union[str, pathlib.Path]
|
StrOrPath = Union[str, pathlib.Path]
|
||||||
DBComp = database.MoonrakerDatabase
|
DBComp = database.MoonrakerDatabase
|
||||||
APIComp = klippy_apis.KlippyAPI
|
APIComp = klippy_apis.KlippyAPI
|
||||||
|
@ -403,7 +405,7 @@ class FileManager:
|
||||||
if force:
|
if force:
|
||||||
# Make sure that the directory does not contain a file
|
# Make sure that the directory does not contain a file
|
||||||
# loaded by the virtual_sdcard
|
# 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)
|
self.notify_sync_lock = NotifySyncLock(dir_path)
|
||||||
try:
|
try:
|
||||||
await self.event_loop.run_in_thread(
|
await self.event_loop.run_in_thread(
|
||||||
|
@ -423,20 +425,19 @@ class FileManager:
|
||||||
raise self.server.error("Operation Not Supported", 405)
|
raise self.server.error("Operation Not Supported", 405)
|
||||||
return result
|
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):
|
if not self.get_relative_path("gcodes", requested_path):
|
||||||
# Path not in the gcodes path
|
# Path not in the gcodes path
|
||||||
return True
|
return True
|
||||||
# Get virtual_sdcard status
|
kconn: KlippyConnection
|
||||||
kapis: APIComp = self.server.lookup_component('klippy_apis')
|
kconn = self.server.lookup_component("klippy_connection")
|
||||||
result: Dict[str, Any]
|
job_state: JobState = self.server.lookup_component("job_state")
|
||||||
result = await kapis.query_objects({'print_stats': None}, {})
|
last_stats = job_state.get_last_stats()
|
||||||
pstats = result.get('print_stats', {})
|
loaded_file: str = last_stats.get('filename', "")
|
||||||
loaded_file: str = pstats.get('filename', "")
|
state: str = last_stats.get('state', "")
|
||||||
state: str = pstats.get('state', "")
|
|
||||||
gc_path = self.file_paths.get('gcodes', "")
|
gc_path = self.file_paths.get('gcodes', "")
|
||||||
full_path = os.path.join(gc_path, loaded_file)
|
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 loaded_file and is_printing:
|
||||||
if os.path.isdir(requested_path):
|
if os.path.isdir(requested_path):
|
||||||
# Check to see of the loaded file is in the request
|
# 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")
|
raise self.server.error(f"File {source_path} does not exist")
|
||||||
# make sure the destination is not in use
|
# make sure the destination is not in use
|
||||||
if os.path.exists(dest_path):
|
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 ep == "/server/files/move":
|
||||||
if source_root not in self.full_access_roots:
|
if source_root not in self.full_access_roots:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"Source path is read-only, cannot move: {source_root}")
|
f"Source path is read-only, cannot move: {source_root}")
|
||||||
# if moving the file, make sure the source is not in use
|
# 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
|
op_func: Callable[..., str] = shutil.move
|
||||||
result['source_item'] = {
|
result['source_item'] = {
|
||||||
'path': source,
|
'path': source,
|
||||||
|
@ -665,7 +666,7 @@ class FileManager:
|
||||||
can_start: bool = False
|
can_start: bool = False
|
||||||
try:
|
try:
|
||||||
check_path: str = upload_info['dest_path']
|
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:
|
except self.server.error as e:
|
||||||
if e.status_code == 403:
|
if e.status_code == 403:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
|
@ -857,7 +858,7 @@ class FileManager:
|
||||||
if not os.path.isfile(full_path):
|
if not os.path.isfile(full_path):
|
||||||
raise self.server.error(f"Invalid file path: {path}")
|
raise self.server.error(f"Invalid file path: {path}")
|
||||||
try:
|
try:
|
||||||
await self._handle_operation_check(full_path)
|
self._handle_operation_check(full_path)
|
||||||
except self.server.error as e:
|
except self.server.error as e:
|
||||||
if e.status_code == 403:
|
if e.status_code == 403:
|
||||||
raise
|
raise
|
||||||
|
@ -1683,7 +1684,7 @@ class MetadataStorage:
|
||||||
|
|
||||||
def get(self,
|
def get(self,
|
||||||
key: str,
|
key: str,
|
||||||
default: _T = None
|
default: Optional[_T] = None
|
||||||
) -> Union[_T, Dict[str, Any]]:
|
) -> Union[_T, Dict[str, Any]]:
|
||||||
return deepcopy(self.metadata.get(key, default))
|
return deepcopy(self.metadata.get(key, default))
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ if TYPE_CHECKING:
|
||||||
from .mqtt import MQTTClient
|
from .mqtt import MQTTClient
|
||||||
from .template import JinjaTemplate
|
from .template import JinjaTemplate
|
||||||
from .http_client import HttpClient
|
from .http_client import HttpClient
|
||||||
|
from klippy_connection import KlippyConnection
|
||||||
APIComp = klippy_apis.KlippyAPI
|
APIComp = klippy_apis.KlippyAPI
|
||||||
|
|
||||||
class PrinterPower:
|
class PrinterPower:
|
||||||
|
@ -94,13 +95,6 @@ class PrinterPower:
|
||||||
"job_queue:job_queue_changed", self._handle_job_queued)
|
"job_queue:job_queue_changed", self._handle_job_queued)
|
||||||
self.server.register_notification("power:power_changed")
|
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:
|
async def component_init(self) -> None:
|
||||||
for dev in self.devices.values():
|
for dev in self.devices.values():
|
||||||
if not dev.initialize():
|
if not dev.initialize():
|
||||||
|
@ -261,13 +255,6 @@ class PowerDevice:
|
||||||
'initial_state', None
|
'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:
|
def _schedule_firmware_restart(self, state: str = "") -> None:
|
||||||
if not self.need_scheduled_restart:
|
if not self.need_scheduled_restart:
|
||||||
return
|
return
|
||||||
|
@ -399,8 +386,9 @@ class PowerDevice:
|
||||||
self.notify_power_changed()
|
self.notify_power_changed()
|
||||||
return cur_state
|
return cur_state
|
||||||
if not force:
|
if not force:
|
||||||
printing = await self._check_klippy_printing()
|
kconn: KlippyConnection
|
||||||
if self.locked_while_printing and printing:
|
kconn = self.server.lookup_component("klippy_connection")
|
||||||
|
if self.locked_while_printing and kconn.is_printing():
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"Unable to change power for {self.name} "
|
f"Unable to change power for {self.name} "
|
||||||
"while printing")
|
"while printing")
|
||||||
|
|
|
@ -40,7 +40,7 @@ if TYPE_CHECKING:
|
||||||
from moonraker import Server
|
from moonraker import Server
|
||||||
from confighelper import ConfigHelper
|
from confighelper import ConfigHelper
|
||||||
from websockets import WebRequest
|
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.shell_command import ShellCommandFactory as SCMDComp
|
||||||
from components.database import MoonrakerDatabase as DBComp
|
from components.database import MoonrakerDatabase as DBComp
|
||||||
from components.database import NamespaceWrapper
|
from components.database import NamespaceWrapper
|
||||||
|
@ -68,6 +68,8 @@ class UpdateManager:
|
||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
self.server = config.get_server()
|
self.server = config.get_server()
|
||||||
self.event_loop = self.server.get_event_loop()
|
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")
|
self.channel = config.get('channel', "dev")
|
||||||
if self.channel not in ["dev", "beta"]:
|
if self.channel not in ["dev", "beta"]:
|
||||||
raise config.error(
|
raise config.error(
|
||||||
|
@ -224,20 +226,13 @@ class UpdateManager:
|
||||||
if notify:
|
if notify:
|
||||||
self.cmd_helper.notify_update_refreshed()
|
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:
|
async def _handle_auto_refresh(self, eventtime: float) -> float:
|
||||||
cur_hour = time.localtime(time.time()).tm_hour
|
cur_hour = time.localtime(time.time()).tm_hour
|
||||||
if self.initial_refresh_complete:
|
if self.initial_refresh_complete:
|
||||||
# Update when the local time is between 12AM and 5AM
|
# Update when the local time is between 12AM and 5AM
|
||||||
if cur_hour >= MAX_UPDATE_HOUR:
|
if cur_hour >= MAX_UPDATE_HOUR:
|
||||||
return eventtime + UPDATE_REFRESH_INTERVAL
|
return eventtime + UPDATE_REFRESH_INTERVAL
|
||||||
if await self._check_klippy_printing():
|
if self.kconn.is_printing():
|
||||||
# Don't Refresh during a print
|
# Don't Refresh during a print
|
||||||
logging.info("Klippy is printing, auto refresh aborted")
|
logging.info("Klippy is printing, auto refresh aborted")
|
||||||
return eventtime + UPDATE_REFRESH_INTERVAL
|
return eventtime + UPDATE_REFRESH_INTERVAL
|
||||||
|
@ -268,7 +263,7 @@ class UpdateManager:
|
||||||
async def _handle_update_request(self,
|
async def _handle_update_request(self,
|
||||||
web_request: WebRequest
|
web_request: WebRequest
|
||||||
) -> str:
|
) -> str:
|
||||||
if await self._check_klippy_printing():
|
if self.kconn.is_printing():
|
||||||
raise self.server.error("Update Refused: Klippy is printing")
|
raise self.server.error("Update Refused: Klippy is printing")
|
||||||
app: str = web_request.get_endpoint().split("/")[-1]
|
app: str = web_request.get_endpoint().split("/")[-1]
|
||||||
if app == "client":
|
if app == "client":
|
||||||
|
@ -391,7 +386,7 @@ class UpdateManager:
|
||||||
if (
|
if (
|
||||||
machine.validation_enabled() or
|
machine.validation_enabled() or
|
||||||
self.cmd_helper.is_update_busy() or
|
self.cmd_helper.is_update_busy() or
|
||||||
await self._check_klippy_printing() or
|
self.kconn.is_printing() or
|
||||||
not self.initial_refresh_complete
|
not self.initial_refresh_complete
|
||||||
):
|
):
|
||||||
if check_refresh:
|
if check_refresh:
|
||||||
|
@ -433,7 +428,7 @@ class UpdateManager:
|
||||||
async def _handle_repo_recovery(self,
|
async def _handle_repo_recovery(self,
|
||||||
web_request: WebRequest
|
web_request: WebRequest
|
||||||
) -> str:
|
) -> str:
|
||||||
if await self._check_klippy_printing():
|
if self.kconn.is_printing():
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
"Recovery Attempt Refused: Klippy is printing")
|
"Recovery Attempt Refused: Klippy is printing")
|
||||||
app: str = web_request.get_str('name')
|
app: str = web_request.get_str('name')
|
||||||
|
|
|
@ -35,6 +35,7 @@ if TYPE_CHECKING:
|
||||||
from components.klippy_apis import KlippyAPI
|
from components.klippy_apis import KlippyAPI
|
||||||
from components.file_manager.file_manager import FileManager
|
from components.file_manager.file_manager import FileManager
|
||||||
from components.machine import Machine
|
from components.machine import Machine
|
||||||
|
from components.job_state import JobState
|
||||||
FlexCallback = Callable[..., Optional[Coroutine]]
|
FlexCallback = Callable[..., Optional[Coroutine]]
|
||||||
|
|
||||||
# These endpoints are reserved for klippy/moonraker communication only and are
|
# These endpoints are reserved for klippy/moonraker communication only and are
|
||||||
|
@ -550,6 +551,16 @@ class KlippyConnection:
|
||||||
def is_connected(self) -> bool:
|
def is_connected(self) -> bool:
|
||||||
return self.writer is not None and not self.closing
|
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:
|
async def _on_connection_closed(self) -> None:
|
||||||
self.init_list = []
|
self.init_list = []
|
||||||
self._state = "disconnected"
|
self._state = "disconnected"
|
||||||
|
|
Loading…
Reference in New Issue