klippy_apis: support subscription callbacks
This callback will only fire after a component has requested a subscription, preventing early updates while the component is waiting for the subscription request to complete. It is still valid for components to register the "server:status_update" event handler if this behavior is not a concern. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
@@ -42,9 +42,6 @@ class DataStore:
|
|||||||
self.temp_update_timer = eventloop.register_timer(
|
self.temp_update_timer = eventloop.register_timer(
|
||||||
self._update_temperature_store)
|
self._update_temperature_store)
|
||||||
|
|
||||||
# Register status update event
|
|
||||||
self.server.register_event_handler(
|
|
||||||
"server:status_update", self._set_current_temps)
|
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:gcode_response", self._update_gcode_store)
|
"server:gcode_response", self._update_gcode_store)
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
@@ -78,7 +75,9 @@ class DataStore:
|
|||||||
sub: Dict[str, Optional[List[str]]] = {s: None for s in sensors}
|
sub: Dict[str, Optional[List[str]]] = {s: None for s in sensors}
|
||||||
try:
|
try:
|
||||||
status: Dict[str, Any]
|
status: Dict[str, Any]
|
||||||
status = await klippy_apis.subscribe_objects(sub)
|
status = await klippy_apis.subscribe_objects(
|
||||||
|
sub, self._set_current_temps
|
||||||
|
)
|
||||||
except self.server.error as e:
|
except self.server.error as e:
|
||||||
logging.info(f"Error subscribing to sensors: {e}")
|
logging.info(f"Error subscribing to sensors: {e}")
|
||||||
return
|
return
|
||||||
@@ -111,7 +110,7 @@ class DataStore:
|
|||||||
self.temperature_store = {}
|
self.temperature_store = {}
|
||||||
self.temp_update_timer.stop()
|
self.temp_update_timer.stop()
|
||||||
|
|
||||||
def _set_current_temps(self, data: Dict[str, Any]) -> None:
|
def _set_current_temps(self, data: Dict[str, Any], _: float = 0.) -> None:
|
||||||
for sensor in self.temperature_store:
|
for sensor in self.temperature_store:
|
||||||
if sensor in data:
|
if sensor in data:
|
||||||
last_val = self.last_temps[sensor]
|
last_val = self.last_temps[sensor]
|
||||||
|
@@ -25,8 +25,6 @@ class JobState:
|
|||||||
self.last_print_stats: Dict[str, Any] = {}
|
self.last_print_stats: Dict[str, Any] = {}
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:klippy_started", self._handle_started)
|
"server:klippy_started", self._handle_started)
|
||||||
self.server.register_event_handler(
|
|
||||||
"server:status_update", self._status_update)
|
|
||||||
|
|
||||||
async def _handle_started(self, state: str) -> None:
|
async def _handle_started(self, state: str) -> None:
|
||||||
if state != "ready":
|
if state != "ready":
|
||||||
@@ -34,7 +32,7 @@ class JobState:
|
|||||||
kapis: KlippyAPI = self.server.lookup_component('klippy_apis')
|
kapis: KlippyAPI = self.server.lookup_component('klippy_apis')
|
||||||
sub: Dict[str, Optional[List[str]]] = {"print_stats": None}
|
sub: Dict[str, Optional[List[str]]] = {"print_stats": None}
|
||||||
try:
|
try:
|
||||||
result = await kapis.subscribe_objects(sub)
|
result = await kapis.subscribe_objects(sub, self._status_update)
|
||||||
except self.server.error as e:
|
except self.server.error as e:
|
||||||
logging.info(f"Error subscribing to print_stats")
|
logging.info(f"Error subscribing to print_stats")
|
||||||
self.last_print_stats = result.get("print_stats", {})
|
self.last_print_stats = result.get("print_stats", {})
|
||||||
@@ -42,7 +40,7 @@ class JobState:
|
|||||||
state = self.last_print_stats["state"]
|
state = self.last_print_stats["state"]
|
||||||
logging.info(f"Job state initialized: {state}")
|
logging.info(f"Job state initialized: {state}")
|
||||||
|
|
||||||
async def _status_update(self, data: Dict[str, Any]) -> None:
|
async def _status_update(self, data: Dict[str, Any], _: float) -> None:
|
||||||
if 'print_stats' not in data:
|
if 'print_stats' not in data:
|
||||||
return
|
return
|
||||||
ps = data['print_stats']
|
ps = data['print_stats']
|
||||||
|
@@ -18,11 +18,14 @@ from typing import (
|
|||||||
List,
|
List,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Mapping,
|
Mapping,
|
||||||
|
Callable,
|
||||||
|
Coroutine
|
||||||
)
|
)
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..confighelper import ConfigHelper
|
from ..confighelper import ConfigHelper
|
||||||
from ..klippy_connection import KlippyConnection as Klippy
|
from ..klippy_connection import KlippyConnection as Klippy
|
||||||
Subscription = Dict[str, Optional[List[Any]]]
|
Subscription = Dict[str, Optional[List[Any]]]
|
||||||
|
SubCallback = Callable[[Dict[str, Dict[str, Any]], float], Optional[Coroutine]]
|
||||||
_T = TypeVar("_T")
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
INFO_ENDPOINT = "info"
|
INFO_ENDPOINT = "info"
|
||||||
@@ -39,11 +42,13 @@ class KlippyAPI(Subscribable):
|
|||||||
def __init__(self, config: ConfigHelper) -> None:
|
def __init__(self, config: ConfigHelper) -> None:
|
||||||
self.server = config.get_server()
|
self.server = config.get_server()
|
||||||
self.klippy: Klippy = self.server.lookup_component("klippy_connection")
|
self.klippy: Klippy = self.server.lookup_component("klippy_connection")
|
||||||
|
self.eventloop = self.server.get_event_loop()
|
||||||
app_args = self.server.get_app_args()
|
app_args = self.server.get_app_args()
|
||||||
self.version = app_args.get('software_version')
|
self.version = app_args.get('software_version')
|
||||||
# Maintain a subscription for all moonraker requests, as
|
# Maintain a subscription for all moonraker requests, as
|
||||||
# we do not want to overwrite them
|
# we do not want to overwrite them
|
||||||
self.host_subscription: Subscription = {}
|
self.host_subscription: Subscription = {}
|
||||||
|
self.subscription_callbacks: List[SubCallback] = []
|
||||||
|
|
||||||
# Register GCode Aliases
|
# Register GCode Aliases
|
||||||
self.server.register_endpoint(
|
self.server.register_endpoint(
|
||||||
@@ -58,6 +63,13 @@ class KlippyAPI(Subscribable):
|
|||||||
"/printer/restart", ['POST'], self._gcode_restart)
|
"/printer/restart", ['POST'], self._gcode_restart)
|
||||||
self.server.register_endpoint(
|
self.server.register_endpoint(
|
||||||
"/printer/firmware_restart", ['POST'], self._gcode_firmware_restart)
|
"/printer/firmware_restart", ['POST'], self._gcode_firmware_restart)
|
||||||
|
self.server.register_event_handler(
|
||||||
|
"server:klippy_disconnect", self._on_klippy_disconnect
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_klippy_disconnect(self) -> None:
|
||||||
|
self.host_subscription.clear()
|
||||||
|
self.subscription_callbacks.clear()
|
||||||
|
|
||||||
async def _gcode_pause(self, web_request: WebRequest) -> str:
|
async def _gcode_pause(self, web_request: WebRequest) -> str:
|
||||||
return await self.pause_print()
|
return await self.pause_print()
|
||||||
@@ -197,14 +209,16 @@ class KlippyAPI(Subscribable):
|
|||||||
params = {'objects': objects}
|
params = {'objects': objects}
|
||||||
result = await self._send_klippy_request(
|
result = await self._send_klippy_request(
|
||||||
STATUS_ENDPOINT, params, default)
|
STATUS_ENDPOINT, params, default)
|
||||||
if isinstance(result, dict) and 'status' in result:
|
if isinstance(result, dict) and "status" in result:
|
||||||
return result['status']
|
return result["status"]
|
||||||
if default is not Sentinel.MISSING:
|
if default is not Sentinel.MISSING:
|
||||||
return default
|
return default
|
||||||
raise self.server.error("Invalid response received from Klippy", 500)
|
raise self.server.error("Invalid response received from Klippy", 500)
|
||||||
|
|
||||||
async def subscribe_objects(self,
|
async def subscribe_objects(
|
||||||
|
self,
|
||||||
objects: Mapping[str, Optional[List[str]]],
|
objects: Mapping[str, Optional[List[str]]],
|
||||||
|
callback: Optional[SubCallback] = None,
|
||||||
default: Union[Sentinel, _T] = Sentinel.MISSING
|
default: Union[Sentinel, _T] = Sentinel.MISSING
|
||||||
) -> Union[_T, Dict[str, Any]]:
|
) -> Union[_T, Dict[str, Any]]:
|
||||||
for obj, items in objects.items():
|
for obj, items in objects.items():
|
||||||
@@ -217,11 +231,13 @@ class KlippyAPI(Subscribable):
|
|||||||
self.host_subscription[obj] = uitems
|
self.host_subscription[obj] = uitems
|
||||||
else:
|
else:
|
||||||
self.host_subscription[obj] = items
|
self.host_subscription[obj] = items
|
||||||
params = {'objects': self.host_subscription}
|
params = {'objects': dict(self.host_subscription)}
|
||||||
result = await self._send_klippy_request(
|
result = await self._send_klippy_request(
|
||||||
SUBSCRIPTION_ENDPOINT, params, default)
|
SUBSCRIPTION_ENDPOINT, params, default)
|
||||||
if isinstance(result, dict) and 'status' in result:
|
if isinstance(result, dict) and "status" in result:
|
||||||
return result['status']
|
if callback is not None:
|
||||||
|
self.subscription_callbacks.append(callback)
|
||||||
|
return result["status"]
|
||||||
if default is not Sentinel.MISSING:
|
if default is not Sentinel.MISSING:
|
||||||
return default
|
return default
|
||||||
raise self.server.error("Invalid response received from Klippy", 500)
|
raise self.server.error("Invalid response received from Klippy", 500)
|
||||||
@@ -237,10 +253,11 @@ class KlippyAPI(Subscribable):
|
|||||||
{'response_template': {"method": method_name},
|
{'response_template': {"method": method_name},
|
||||||
'remote_method': method_name})
|
'remote_method': method_name})
|
||||||
|
|
||||||
def send_status(self,
|
def send_status(
|
||||||
status: Dict[str, Any],
|
self, status: Dict[str, Any], eventtime: float
|
||||||
eventtime: float
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
for cb in self.subscription_callbacks:
|
||||||
|
self.eventloop.register_callback(cb, status, eventtime)
|
||||||
self.server.send_event("server:status_update", status)
|
self.server.send_event("server:status_update", status)
|
||||||
|
|
||||||
def load_component(config: ConfigHelper) -> KlippyAPI:
|
def load_component(config: ConfigHelper) -> KlippyAPI:
|
||||||
|
@@ -229,8 +229,6 @@ class PanelDue:
|
|||||||
"server:klippy_shutdown", self._process_klippy_shutdown)
|
"server:klippy_shutdown", self._process_klippy_shutdown)
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:klippy_disconnect", self._process_klippy_disconnect)
|
"server:klippy_disconnect", self._process_klippy_disconnect)
|
||||||
self.server.register_event_handler(
|
|
||||||
"server:status_update", self.handle_status_update)
|
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:gcode_response", self.handle_gcode_response)
|
"server:gcode_response", self.handle_gcode_response)
|
||||||
|
|
||||||
@@ -320,7 +318,9 @@ class PanelDue:
|
|||||||
self.heaters.extend(extruders)
|
self.heaters.extend(extruders)
|
||||||
try:
|
try:
|
||||||
status: Dict[str, Any]
|
status: Dict[str, Any]
|
||||||
status = await self.klippy_apis.subscribe_objects(sub_args)
|
status = await self.klippy_apis.subscribe_objects(
|
||||||
|
sub_args, self.handle_status_update
|
||||||
|
)
|
||||||
except self.server.error:
|
except self.server.error:
|
||||||
logging.exception("Unable to complete subscription request")
|
logging.exception("Unable to complete subscription request")
|
||||||
else:
|
else:
|
||||||
@@ -337,7 +337,7 @@ class PanelDue:
|
|||||||
self.last_printer_state = 'O'
|
self.last_printer_state = 'O'
|
||||||
self.is_shutdown = self.is_shutdown = False
|
self.is_shutdown = self.is_shutdown = False
|
||||||
|
|
||||||
def handle_status_update(self, status: Dict[str, Any]) -> None:
|
def handle_status_update(self, status: Dict[str, Any], _: float) -> None:
|
||||||
for obj, items in status.items():
|
for obj, items in status.items():
|
||||||
if obj in self.printer_state:
|
if obj in self.printer_state:
|
||||||
self.printer_state[obj].update(items)
|
self.printer_state[obj].update(items)
|
||||||
|
@@ -593,14 +593,12 @@ class KlipperDevice(PowerDevice):
|
|||||||
"Klipper object must be either 'output_pin' or 'gcode_macro' "
|
"Klipper object must be either 'output_pin' or 'gcode_macro' "
|
||||||
f"for option 'object_name' in section [{config.get_name()}]")
|
f"for option 'object_name' in section [{config.get_name()}]")
|
||||||
|
|
||||||
self.server.register_event_handler(
|
|
||||||
"server:status_update", self._status_update)
|
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:klippy_ready", self._handle_ready)
|
"server:klippy_ready", self._handle_ready)
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"server:klippy_disconnect", self._handle_disconnect)
|
"server:klippy_disconnect", self._handle_disconnect)
|
||||||
|
|
||||||
def _status_update(self, data: Dict[str, Any]) -> None:
|
def _status_update(self, data: Dict[str, Any], _: float) -> None:
|
||||||
self._set_state_from_data(data)
|
self._set_state_from_data(data)
|
||||||
|
|
||||||
def get_device_info(self) -> Dict[str, Any]:
|
def get_device_info(self) -> Dict[str, Any]:
|
||||||
@@ -611,7 +609,7 @@ class KlipperDevice(PowerDevice):
|
|||||||
async def _handle_ready(self) -> None:
|
async def _handle_ready(self) -> None:
|
||||||
kapis: APIComp = self.server.lookup_component('klippy_apis')
|
kapis: APIComp = self.server.lookup_component('klippy_apis')
|
||||||
sub: Dict[str, Optional[List[str]]] = {self.object_name: None}
|
sub: Dict[str, Optional[List[str]]] = {self.object_name: None}
|
||||||
data = await kapis.subscribe_objects(sub, None)
|
data = await kapis.subscribe_objects(sub, self._status_update, None)
|
||||||
if not self._validate_data(data):
|
if not self._validate_data(data):
|
||||||
self.state == "error"
|
self.state == "error"
|
||||||
else:
|
else:
|
||||||
|
@@ -77,16 +77,11 @@ class SpoolManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _handle_server_ready(self):
|
async def _handle_server_ready(self):
|
||||||
self.server.register_event_handler(
|
|
||||||
"server:status_update", self._handle_status_update
|
|
||||||
)
|
|
||||||
result = await self.klippy_apis.subscribe_objects(
|
result = await self.klippy_apis.subscribe_objects(
|
||||||
{"toolhead": ["position"]}
|
{"toolhead": ["position"]}, self._handle_status_update, {}
|
||||||
)
|
)
|
||||||
initial_e_pos = self._eposition_from_status(result)
|
initial_e_pos = self._eposition_from_status(result)
|
||||||
|
|
||||||
logging.debug(f"Initial epos: {initial_e_pos}")
|
logging.debug(f"Initial epos: {initial_e_pos}")
|
||||||
|
|
||||||
if initial_e_pos is not None:
|
if initial_e_pos is not None:
|
||||||
self.highest_e_pos = initial_e_pos
|
self.highest_e_pos = initial_e_pos
|
||||||
else:
|
else:
|
||||||
@@ -97,7 +92,7 @@ class SpoolManager:
|
|||||||
position = status.get("toolhead", {}).get("position", [])
|
position = status.get("toolhead", {}).get("position", [])
|
||||||
return position[3] if len(position) > 3 else None
|
return position[3] if len(position) > 3 else None
|
||||||
|
|
||||||
async def _handle_status_update(self, status: Dict[str, Any]) -> None:
|
async def _handle_status_update(self, status: Dict[str, Any], _: float) -> None:
|
||||||
epos = self._eposition_from_status(status)
|
epos = self._eposition_from_status(status)
|
||||||
if epos and epos > self.highest_e_pos:
|
if epos and epos > self.highest_e_pos:
|
||||||
async with self.extruded_lock:
|
async with self.extruded_lock:
|
||||||
|
Reference in New Issue
Block a user