From 82cb73879c269619a4acec5854f082635e787b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Joz=C3=ADfek?= Date: Mon, 30 Oct 2023 18:42:33 +0100 Subject: [PATCH] Send keepalive to keep printer in PrusaLink mode --- ChangeLog | 1 + prusa/link/const.py | 1 + prusa/link/printer_adapter/keepalive.py | 64 ++++++++++++++++++++++++ prusa/link/printer_adapter/prusa_link.py | 11 +++- prusa/link/sdk_augmentation/printer.py | 6 ++- 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 prusa/link/printer_adapter/keepalive.py diff --git a/ChangeLog b/ChangeLog index 24754fba..78e509e9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,7 @@ * Bump the API version so it is the same as the xBuddy reported one * Support the new multi-level telemetry data structure * Don't send over serial when temperature calibration is running + * Periodically send a keepalive gcode to keep the printer in PrusaLink mode 0.7.1rc1 (2023-08-10) * Fixed HTTP response status on HEAD request for non-existing files diff --git a/prusa/link/const.py b/prusa/link/const.py index 16605215..d9396016 100644 --- a/prusa/link/const.py +++ b/prusa/link/const.py @@ -96,6 +96,7 @@ CAMERA_REGISTER_TIMEOUT = 5 TIME_FOR_SNAPSHOT = 1 PRINT_END_TIMEOUT = 11 +KEEPALIVE_INTERVAL = 12 # --- Lcd queue --- LCD_QUEUE_SIZE = 30 diff --git a/prusa/link/printer_adapter/keepalive.py b/prusa/link/printer_adapter/keepalive.py new file mode 100644 index 00000000..152700e5 --- /dev/null +++ b/prusa/link/printer_adapter/keepalive.py @@ -0,0 +1,64 @@ +"""Contains the keepalive implementation""" +from enum import Enum +from threading import Event, Thread +from time import monotonic + +from ..const import KEEPALIVE_INTERVAL +from ..serial.helpers import enqueue_instruction, wait_for_instruction +from ..serial.serial_queue import SerialQueue +from .structures.mc_singleton import MCSingleton + + +class KeepaliveMode(Enum): + """The modes the keepalive can be in""" + PL = "PL" # PrusaLink + PC = "PC" # PrusaConnect + + +class Keepalive(metaclass=MCSingleton): + """Its job is to keep the PrusaLink printer mode on""" + + def __init__(self, serial_queue: SerialQueue): + self.serial_queue: SerialQueue = serial_queue + + self.mode = KeepaliveMode.PL + + self.quit_evt = Event() + self.wait_evt = Event() + self.keepalive_thread: Thread = Thread(target=self._keepalive, + name="Keepalive") + + self.last_keepalive = monotonic() - KEEPALIVE_INTERVAL + + def start(self): + """Starts the module""" + self.keepalive_thread.start() + + def set_use_connect(self, use_connect: bool): + """Changes the mode of the keepalive""" + self.mode = KeepaliveMode.PC if use_connect else KeepaliveMode.PL + self.last_keepalive = monotonic() - KEEPALIVE_INTERVAL + self.wait_evt.set() + + def _keepalive(self): + """Keep sending out a signal, that PrusaLink is connected""" + while not self.quit_evt.is_set(): + instruction = enqueue_instruction( + self.serial_queue, f"M79 S\"{self.mode.value}\"", + to_front=True) + self.last_keepalive = monotonic() + wait_for_instruction(instruction, should_wait_evt=self.quit_evt) + to_wait = self.last_keepalive + KEEPALIVE_INTERVAL - monotonic() + if to_wait >= 0: + self.wait_evt.wait(to_wait) + if self.wait_evt.is_set(): + self.wait_evt.clear() + + def stop(self): + """Stops the keepalive sender""" + self.quit_evt.set() + self.wait_evt.set() + + def wait_stopped(self): + """Waits for Keepalive thread to stop""" + self.keepalive_thread.join() diff --git a/prusa/link/printer_adapter/prusa_link.py b/prusa/link/printer_adapter/prusa_link.py index f2037ea7..f2ee19bf 100644 --- a/prusa/link/printer_adapter/prusa_link.py +++ b/prusa/link/printer_adapter/prusa_link.py @@ -67,6 +67,7 @@ from .filesystem.storage_controller import StorageController from .ip_updater import IPUpdater from .job import Job, JobState +from .keepalive import Keepalive from .lcd_printer import LCDPrinter from .model import Model from .print_stat_doubler import PrintStatDoubler @@ -141,6 +142,9 @@ def __init__(self, cfg: Config, settings: Settings) -> None: threshold_path=self.cfg.daemon.threshold_file) # ----- + self.keepalive = Keepalive(self.serial_queue) + self.keepalive.set_use_connect(self.settings.use_connect()) + self.printer = MyPrinter() drivers: List[Type[CameraDriver]] = [V4L2Driver] @@ -321,6 +325,7 @@ def __init__(self, cfg: Config, settings: Settings) -> None: self.telemetry_passer) self.auto_telemetry.start() + self.keepalive.start() self.printer_polling.start() self.storage_controller.start() self.ip_updater.start() @@ -395,6 +400,7 @@ def stop(self, fast: bool = False) -> None: self.printer.indicate_stop() self.printer_polling.stop() self.storage_controller.stop() + self.keepalive.stop() self.lcd_printer.stop(fast) # This is for pylint to stop complaining, I'd like stop(fast) more if fast: @@ -423,6 +429,7 @@ def stop(self, fast: bool = False) -> None: self.printer.wait_stopped() self.printer_polling.wait_stopped() self.storage_controller.wait_stopped() + self.keepalive.wait_stopped() self.lcd_printer.wait_stopped() self.ip_updater.wait_stopped() self.camera_governor.wait_stopped() @@ -772,7 +779,9 @@ def printer_registered(self, token: str) -> None: self.settings.printer.type = printer_type_string self.settings.service_connect.token = token self.settings.update_sections() - use_connect_errors(self.settings.use_connect()) + use_connect = self.settings.use_connect() + use_connect_errors(use_connect) + self.keepalive.set_use_connect(use_connect) with open(self.cfg.printer.settings, 'w', encoding='utf-8') as ini: self.settings.write(ini) diff --git a/prusa/link/sdk_augmentation/printer.py b/prusa/link/sdk_augmentation/printer.py index 5b3e6a95..7d28fb2a 100644 --- a/prusa/link/sdk_augmentation/printer.py +++ b/prusa/link/sdk_augmentation/printer.py @@ -16,6 +16,7 @@ from .. import __version__ from ..conditions import use_connect_errors from ..const import PRINTER_CONF_TYPES +from ..printer_adapter.keepalive import Keepalive from ..printer_adapter.lcd_printer import LCDPrinter from ..printer_adapter.model import Model from ..printer_adapter.structures.mc_singleton import MCSingleton @@ -35,6 +36,7 @@ class MyPrinter(SDKPrinter, metaclass=MCSingleton): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.lcd_printer = LCDPrinter.get_instance() + self.keepalive = Keepalive.get_instance() self.download_thread = Thread(target=self.download_loop, name="download") self.model = Model.get_instance() @@ -81,7 +83,9 @@ def connection_from_settings(self, settings): token = settings.service_connect.token self.set_connection(server, token) - use_connect_errors(settings.use_connect()) + use_connect = settings.use_connect() + self.keepalive.set_use_connect(use_connect) + use_connect_errors(use_connect) def get_file_info(self, caller: Command) -> Dict[str, Any]: """Return file info for a given file