diff --git a/moonraker/components/simplyprint.py b/moonraker/components/simplyprint.py index 6a38bbb..1f53d13 100644 --- a/moonraker/components/simplyprint.py +++ b/moonraker/components/simplyprint.py @@ -77,7 +77,6 @@ class SimplyPrint(Subscribable): self.amb_detect = AmbientDetect(config, self, ambient) self.layer_detect = LayerDetect() self.webcam_stream = WebcamStream(config, self) - self.ai_stream = AIStream(config, self) self.print_handler = PrintHandler(self) self.last_received_temps: Dict[str, float] = {} self.last_err_log_time: float = 0. @@ -434,15 +433,11 @@ class SimplyPrint(Subscribable): self.connect_url = f"{ep}/{printer_id}/{token}" def _update_intervals(self, intervals: Dict[str, Any]) -> None: - prev_ai = self.intervals.get("ai", 0.) for key, val in intervals.items(): self.intervals[key] = val / 1000. - cur_ai = self.intervals.get("ai", prev_ai) - if abs(prev_ai - cur_ai) > 1e-8: - if abs(cur_ai) < 1e-8: - self.ai_stream.stop() - else: - self.ai_stream.start(cur_ai) + cur_ai_interval = self.intervals.get("ai", 0.) + if not cur_ai_interval: + self.webcam_stream.stop_ai() logging.debug(f"Intervals Updated: {self.intervals}") async def _announce_setup(self, short_id: str) -> None: @@ -673,6 +668,8 @@ class SimplyPrint(Subscribable): job_info["started"] = True self.layer_detect.start(metadata) self._send_job_event(job_info) + self.webcam_stream.reset_ai_scores() + self.webcam_stream.start_ai(120.) def _check_job_started( self, @@ -700,10 +697,13 @@ class SimplyPrint(Subscribable): self.send_sp("job_info", {"paused": True}) self._update_state("paused") self.layer_detect.stop() + self.webcam_stream.stop_ai() def _on_print_resumed(self, *args) -> None: self._update_state("printing") self.layer_detect.resume() + self.webcam_stream.reset_ai_scores() + self.webcam_stream.start_ai(self.intervals["ai"]) def _on_print_cancelled(self, *args) -> None: self._check_job_started(*args) @@ -712,6 +712,7 @@ class SimplyPrint(Subscribable): self._update_state_from_klippy() self.cache.job_info = {} self.layer_detect.stop() + self.webcam_stream.stop_ai() def _on_print_error(self, *args) -> None: self._check_job_started(*args) @@ -724,6 +725,7 @@ class SimplyPrint(Subscribable): self._update_state_from_klippy() self.cache.job_info = {} self.layer_detect.stop() + self.webcam_stream.stop_ai() def _on_print_complete(self, *args) -> None: self._check_job_started(*args) @@ -732,11 +734,13 @@ class SimplyPrint(Subscribable): self._update_state_from_klippy() self.cache.job_info = {} self.layer_detect.stop() + self.webcam_stream.stop_ai() def _on_print_standby(self, *args) -> None: self._update_state_from_klippy() self.cache.job_info = {} self.layer_detect.stop() + self.webcam_stream.stop_ai() def _on_pause_requested(self) -> None: self._print_request_event.set() @@ -1088,7 +1092,7 @@ class SimplyPrint(Subscribable): async def close(self): self.print_handler.cancel() - self.webcam_stream.stop() + self.webcam_stream.stop_ai() self.amb_detect.stop() self.printer_info_timer.stop() self.ping_sp_timer.stop() @@ -1268,6 +1272,7 @@ class LayerDetect: # go through the reverse proxy FALLBACK_URL = "http://127.0.0.1:8080/?action=snapshot" SP_SNAPSHOT_URL = "https://apirewrite.simplyprint.io/jobs/ReceiveSnapshot" +SP_AI_URL = "https://ai.simplyprint.io/api/v2/infer" class WebcamStream: def __init__( @@ -1281,6 +1286,10 @@ class WebcamStream: self.client: HttpClient = self.server.lookup_component("http_client") self.cam: Optional[WebCam] = None self._connected = False + self.ai_running = False + self.ai_task: Optional[asyncio.Task] = None + self.ai_scores: List[Any] = [] + self.failed_ai_attempts = 0 @property def connected(self) -> bool: @@ -1354,55 +1363,73 @@ class WebcamStream: except Exception as e: if not self.server.is_debug_enabled(): return - logging.exception(f"SimplyPrint WebCam Stream Error") + logging.exception("SimplyPrint WebCam Stream Error") -class AIStream(WebcamStream): - ai_url = "https://ai.simplyprint.io/api/v2/infer" - def __init__(self, config: ConfigHelper, simplyprint: SimplyPrint) -> None: - super().__init__(config, simplyprint) - self.running = False - self.interval: float = 1. - self.stream_task: Optional[asyncio.Task] = None - - async def _send_image(self, base_image: str) -> None: - token = self.simplyprint.sp_info["printer_token"] + async def _send_ai_image(self, base_image: str) -> None: + interval = self.simplyprint.intervals["ai"] headers = {"User-Agent": "Mozilla/5.0"} data = { - "api_key": token, + "api_key": self.simplyprint.sp_info["printer_token"], "image_array": base_image, - "interval": self.interval + "interval": interval, + "printer_id": self.simplyprint.sp_info["printer_id"], + "settings": { + "buffer_percent": 80, + "confidence": 60, + "buffer_length": 16 + }, + "scores": self.ai_scores } resp = await self.client.post( - self.ai_url, body=data, headers=headers, enable_cache=False + SP_AI_URL, body=data, headers=headers, enable_cache=False ) resp.raise_for_status() + self.failed_ai_attempts = 0 + resp_json = resp.json() + if isinstance(resp_json, dict): + self.ai_scores = resp_json.get("scores", self.ai_scores) + ai_result = resp_json.get("s1", [0, 0, 0]) + self.simplyprint.send_sp("ai_resp", {"ai": ai_result}) - async def _stream(self) -> None: - while self.running: + async def _ai_stream(self, delay: float) -> None: + if delay: + await asyncio.sleep(delay) + while self.ai_running: + interval = self.simplyprint.intervals["ai"] try: img = await self.extract_image() - await self._send_image(img) + await self._send_ai_image(img) except asyncio.CancelledError: raise except Exception: - logging.exception(f"SimplyPrint AI Stream Error") + self.failed_ai_attempts += 1 + if self.failed_ai_attempts == 1: + logging.exception("SimplyPrint AI Stream Error") + elif not self.failed_ai_attempts % 10: + logging.info( + f"SimplyPrint: {self.failed_ai_attempts} consecutive " + "AI failures" + ) + delay = min(120., self.failed_ai_attempts * 5.0) + interval = self.simplyprint.intervals["ai"] + delay + await asyncio.sleep(interval) - def start(self, interval: float) -> None: - if self.running: - if interval == self.interval: - return - self.stop() - self.interval = interval - self.running = True - self.stream_task = self.eventloop.create_task(self._stream()) + def reset_ai_scores(self): + self.ai_scores = [] - def stop(self) -> None: - if not self.running: + def start_ai(self, delay: float = 0) -> None: + if self.ai_running: + self.stop_ai() + self.ai_running = True + self.ai_task = self.eventloop.create_task(self._ai_stream(delay)) + + def stop_ai(self) -> None: + if not self.ai_running: return - self.running = False - if self.stream_task is not None: - self.stream_task.cancel() - self.stream_task = None + self.ai_running = False + if self.ai_task is not None: + self.ai_task.cancel() + self.ai_task = None class PrintHandler: def __init__(self, simplyprint: SimplyPrint) -> None: