klippy_connection: track connection state with an enum

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2023-11-16 14:44:20 -05:00
parent b18e9cc222
commit 7de61eb113
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
9 changed files with 104 additions and 63 deletions

View File

@ -28,7 +28,8 @@ from .common import (
APIDefinition,
APITransport,
TransportType,
RequestType
RequestType,
KlippyState
)
from .utils import json_wrapper as jsonw
from .websockets import (
@ -1133,11 +1134,10 @@ class WelcomeHandler(tornado.web.RequestHandler):
"The [authorization] section in moonraker.conf must be "
"configured to enable CORS."
)
kstate = self.server.get_klippy_state()
if kstate != "disconnected":
kinfo = self.server.get_klippy_info()
kmsg = kinfo.get("state_message", kstate)
summary.append(f"Klipper reports {kmsg.lower()}")
kconn: Klippy = self.server.lookup_component("klippy_connection")
kstate = kconn.state
if kstate != KlippyState.DISCONNECTED:
summary.append(f"Klipper reports {kstate.message.lower()}")
else:
summary.append(
"Moonraker is not currently connected to Klipper. Make sure "

View File

@ -123,6 +123,37 @@ class JobEvent(ExtendedEnum):
def is_printing(self) -> bool:
return self.value in [2, 4]
class KlippyState(ExtendedEnum):
DISCONNECTED = 1
STARTUP = 2
READY = 3
ERROR = 4
SHUTDOWN = 5
@classmethod
def from_string(cls, enum_name: str, msg: str = ""):
str_name = enum_name.upper()
for name, member in cls.__members__.items():
if name == str_name:
instance = cls(member.value)
if msg:
instance.set_message(msg)
return instance
raise ValueError(f"No enum member named {enum_name}")
def set_message(self, msg: str) -> None:
self._state_message: str = msg
@property
def message(self) -> str:
if hasattr(self, "_state_message"):
return self._state_message
return ""
def startup_complete(self) -> bool:
return self.value > 2
class Subscribable:
def send_status(
self, status: Dict[str, Any], eventtime: float

View File

@ -15,7 +15,7 @@ from typing import (
Dict,
List,
)
from ..common import JobEvent
from ..common import JobEvent, KlippyState
if TYPE_CHECKING:
from ..confighelper import ConfigHelper
from .klippy_apis import KlippyAPI
@ -27,8 +27,8 @@ class JobState:
self.server.register_event_handler(
"server:klippy_started", self._handle_started)
async def _handle_started(self, state: str) -> None:
if state != "ready":
async def _handle_started(self, state: KlippyState) -> None:
if state != KlippyState.READY:
return
kapis: KlippyAPI = self.server.lookup_component('klippy_apis')
sub: Dict[str, Optional[List[str]]] = {"print_stats": None}

View File

@ -18,7 +18,8 @@ from ..common import (
Subscribable,
WebRequest,
APITransport,
JsonRPC
JsonRPC,
KlippyState
)
from ..utils import json_wrapper as jsonw
@ -372,7 +373,7 @@ class MQTTClient(APITransport, Subscribable):
self._do_reconnect(first=True)
)
async def _handle_klippy_started(self, state: str) -> None:
async def _handle_klippy_started(self, state: KlippyState) -> None:
if self.status_objs:
args = {'objects': self.status_objs}
try:

View File

@ -6,7 +6,7 @@
from __future__ import annotations
import logging
from ..common import RequestType, TransportType
from ..common import RequestType, TransportType, KlippyState
# Annotation imports
from typing import (
@ -16,6 +16,7 @@ from typing import (
List,
)
if TYPE_CHECKING:
from ..klippy_connection import KlippyConnection
from ..confighelper import ConfigHelper
from ..common import WebRequest
from .klippy_apis import KlippyAPI as APIComp
@ -153,10 +154,11 @@ class OctoPrintCompat:
data.update(status[heater_name])
def printer_state(self) -> str:
klippy_state = self.server.get_klippy_state()
if klippy_state in ["disconnected", "startup"]:
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
klippy_state = kconn.state
if not klippy_state.startup_complete():
return 'Offline'
elif klippy_state != 'ready':
elif klippy_state != KlippyState.READY:
return 'Error'
return {
'standby': 'Operational',
@ -202,11 +204,11 @@ class OctoPrintCompat:
"""
Server status
"""
klippy_state = self.server.get_klippy_state()
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
klippy_state = kconn.state
return {
'server': OCTO_VERSION,
'safemode': (
None if klippy_state == 'ready' else 'settings')
'safemode': None if klippy_state == KlippyState.READY else 'settings'
}
async def _post_login_user(self,

View File

@ -12,7 +12,7 @@ import asyncio
import time
from urllib.parse import quote, urlencode
from ..utils import json_wrapper as jsonw
from ..common import RequestType
from ..common import RequestType, KlippyState
# Annotation imports
from typing import (
@ -262,11 +262,11 @@ class PowerDevice:
'initial_state', None
)
def _schedule_firmware_restart(self, state: str = "") -> None:
def _schedule_firmware_restart(self, state: KlippyState) -> None:
if not self.need_scheduled_restart:
return
self.need_scheduled_restart = False
if state == "ready":
if state == KlippyState.READY:
logging.info(
f"Power Device {self.name}: Klipper reports 'ready', "
"aborting FIRMWARE_RESTART"
@ -304,8 +304,9 @@ class PowerDevice:
await self.process_bound_services()
if self.state == "on" and self.klipper_restart:
self.need_scheduled_restart = True
klippy_state = self.server.get_klippy_state()
if klippy_state in ["disconnected", "startup"]:
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
klippy_state = kconn.state
if not klippy_state.startup_complete():
# If klippy is currently disconnected or hasn't proceeded past
# the startup state, schedule the restart in the
# "klippy_started" event callback.
@ -338,7 +339,8 @@ class PowerDevice:
self.off_when_shutdown_delay, self._power_off_on_shutdown)
def _power_off_on_shutdown(self) -> None:
if self.server.get_klippy_state() != "shutdown":
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
if kconn.state != KlippyState.SHUTDOWN:
return
logging.info(
f"Powering off device '{self.name}' due to klippy shutdown")

View File

@ -17,7 +17,7 @@ import logging.handlers
import tempfile
from queue import SimpleQueue
from ..loghelper import LocalQueueHandler
from ..common import Subscribable, WebRequest, JobEvent
from ..common import Subscribable, WebRequest, JobEvent, KlippyState
from ..utils import json_wrapper as jsonw
from typing import (
@ -640,12 +640,12 @@ class SimplyPrint(Subscribable):
self.cache.firmware_info.update(ui_data)
self.send_sp("machine_data", ui_data)
def _on_klippy_startup(self, state: str) -> None:
if state != "ready":
def _on_klippy_startup(self, state: KlippyState) -> None:
if state != KlippyState.READY:
self._update_state("error")
kconn: KlippyConnection
kconn = self.server.lookup_component("klippy_connection")
self.send_sp("printer_error", {"error": kconn.state_message})
self.send_sp("printer_error", {"error": kconn.state.message})
self.send_sp("connection", {"new": "connected"})
self._send_firmware_data()
@ -653,7 +653,7 @@ class SimplyPrint(Subscribable):
self._update_state("error")
kconn: KlippyConnection
kconn = self.server.lookup_component("klippy_connection")
self.send_sp("printer_error", {"error": kconn.state_message})
self.send_sp("printer_error", {"error": kconn.state.message})
def _on_klippy_disconnected(self) -> None:
self._update_state("offline")
@ -927,10 +927,11 @@ class SimplyPrint(Subscribable):
self.send_sp("temps", temp_data)
def _update_state_from_klippy(self) -> None:
kstate = self.server.get_klippy_state()
if kstate == "ready":
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
klippy_state = kconn.state
if klippy_state == KlippyState.READY:
sp_state = "operational"
elif kstate in ["error", "shutdown"]:
elif klippy_state in [KlippyState.ERROR, KlippyState.SHUTDOWN]:
sp_state = "error"
else:
sp_state = "offline"
@ -1613,7 +1614,8 @@ class PrintHandler:
self.simplyprint.send_sp("file_progress", data)
async def _check_can_print(self) -> bool:
if self.server.get_klippy_state() != "ready":
kconn: KlippyConnection = self.server.lookup_component("klippy_connection")
if kconn.state != KlippyState.READY:
return False
kapi: KlippyAPI = self.server.lookup_component("klippy_apis")
try:

View File

@ -14,6 +14,7 @@ import asyncio
import pathlib
from .utils import ServerError, get_unix_peer_credentials
from .utils import json_wrapper as jsonw
from .common import KlippyState
# Annotation imports
from typing import (
@ -78,8 +79,8 @@ class KlippyConnection:
self._peer_cred: Dict[str, int] = {}
self._service_info: Dict[str, Any] = {}
self.init_attempts: int = 0
self._state: str = "disconnected"
self._state_message: str = "Klippy Disconnected"
self._state: KlippyState = KlippyState.DISCONNECTED
self._state.set_message("Klippy Disconnected")
self.subscriptions: Dict[Subscribable, Subscription] = {}
self.subscription_cache: Dict[str, Dict[str, Any]] = {}
# Setup remote methods accessable to Klippy. Note that all
@ -106,14 +107,14 @@ class KlippyConnection:
return self.server.lookup_component("klippy_apis")
@property
def state(self) -> str:
def state(self) -> KlippyState:
if self.is_connected() and not self._klippy_started:
return "startup"
return KlippyState.STARTUP
return self._state
@property
def state_message(self) -> str:
return self._state_message
return self._state.message
@property
def klippy_info(self) -> Dict[str, Any]:
@ -241,7 +242,7 @@ class KlippyConnection:
connection.call_method(method_name, kwargs)
self.remote_methods[method_name] = _on_agent_method_received
self.klippy_reg_methods.append(method_name)
if self._methods_registered and self._state != "disconnected":
if self._methods_registered and self._state != KlippyState.DISCONNECTED:
coro = self.klippy_apis.register_method(method_name)
return self.event_loop.create_task(coro)
return None
@ -331,7 +332,7 @@ class KlippyConnection:
self._methods_registered = False
self._missing_reqs.clear()
self.init_attempts = 0
self._state = "startup"
self._state = KlippyState.STARTUP
while self.server.is_running():
await asyncio.sleep(INIT_TIME)
await self._check_ready()
@ -391,8 +392,10 @@ class KlippyConnection:
msg = f"Klipper Version: {version}"
self.server.add_log_rollover_item("klipper_version", msg)
self._klippy_info = dict(result)
state_message: str = self._state.message
if "state_message" in self._klippy_info:
self._state_message = self._klippy_info["state_message"]
state_message = self._klippy_info["state_message"]
self._state.set_message(state_message)
if "state" not in result:
return
if send_id:
@ -400,19 +403,20 @@ class KlippyConnection:
await self.server.send_event("server:klippy_identified")
# Request initial endpoints to register info, emergency stop APIs
await self._request_endpoints()
self._state = result["state"]
if self._state != "startup":
self._state = KlippyState.from_string(result["state"], state_message)
if self._state != KlippyState.STARTUP:
await self._request_initial_subscriptions()
# Register remaining endpoints available
await self._request_endpoints()
startup_state = self._state
await self.server.send_event(
"server:klippy_started", startup_state
)
await self.server.send_event("server:klippy_started", startup_state)
self._klippy_started = True
if self._state != "ready":
logging.info("\n" + self._state_message)
if self._state == "shutdown" and startup_state != "shutdown":
if self._state != KlippyState.READY:
logging.info("\n" + self._state.message)
if (
self._state == KlippyState.SHUTDOWN and
startup_state != KlippyState.SHUTDOWN
):
# Klippy shutdown during startup event
self.server.send_event("server:klippy_shutdown")
else:
@ -425,10 +429,10 @@ class KlippyConnection:
logging.exception(
f"Unable to register method '{method}'")
self._methods_registered = True
if self._state == "ready":
if self._state == KlippyState.READY:
logging.info("Klippy ready")
await self.server.send_event("server:klippy_ready")
if self._state == "shutdown":
if self._state == KlippyState.SHUTDOWN:
# Klippy shutdown during ready event
self.server.send_event("server:klippy_shutdown")
else:
@ -520,21 +524,23 @@ class KlippyConnection:
self.subscription_cache.setdefault(field, {}).update(item)
if 'webhooks' in status:
wh: Dict[str, str] = status['webhooks']
state_message: str = self._state.message
if "state_message" in wh:
self._state_message = wh["state_message"]
state_message = wh["state_message"]
self._state.set_message(state_message)
# XXX - process other states (startup, ready, error, etc)?
if "state" in wh:
state = wh["state"]
new_state = KlippyState.from_string(wh["state"], state_message)
if (
state == "shutdown" and
new_state == KlippyState.SHUTDOWN and
not self._klippy_initializing and
self._state != "shutdown"
self._state != KlippyState.SHUTDOWN
):
# If the shutdown state is received during initialization
# defer the event, the init routine will handle it.
logging.info("Klippy has shutdown")
self.server.send_event("server:klippy_shutdown")
self._state = state
self._state = new_state
for conn, sub in self.subscriptions.items():
conn_status: Dict[str, Any] = {}
for name, fields in sub.items():
@ -657,7 +663,7 @@ class KlippyConnection:
return self.writer is not None and not self.closing
def is_ready(self) -> bool:
return self._state == "ready"
return self._state == KlippyState.READY
def is_printing(self) -> bool:
if not self.is_ready():
@ -705,8 +711,8 @@ class KlippyConnection:
self._klippy_initializing = False
self._klippy_started = False
self._methods_registered = False
self._state = "disconnected"
self._state_message = "Klippy Disconnected"
self._state = KlippyState.DISCONNECTED
self._state.set_message("Klippy Disconnected")
for request in self.pending_requests.values():
request.set_exception(ServerError("Klippy Disconnected", 503))
self.pending_requests = {}

View File

@ -368,9 +368,6 @@ class Server:
def get_klippy_info(self) -> Dict[str, Any]:
return self.klippy_connection.klippy_info
def get_klippy_state(self) -> str:
return self.klippy_connection.state
def _handle_term_signal(self) -> None:
logging.info("Exiting with signal SIGTERM")
self.event_loop.register_callback(self._stop_server, "terminate")
@ -447,7 +444,7 @@ class Server:
]
return {
'klippy_connected': self.klippy_connection.is_connected(),
'klippy_state': self.klippy_connection.state,
'klippy_state': str(self.klippy_connection.state),
'components': list(self.components.keys()),
'failed_components': self.failed_components,
'registered_directories': reg_dirs,