simplyprint: implement "webcam_stream" demand

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Eric Callahan 2022-10-10 14:28:39 -04:00
parent d5fa40df87
commit 6e121c2221
No known key found for this signature in database
GPG Key ID: 5A1EB336DFB4C71B
1 changed files with 61 additions and 53 deletions

View File

@ -13,6 +13,7 @@ import time
import pathlib
import base64
import tornado.websocket
from tornado.escape import url_escape
from websockets import Subscribable, WebRequest
import logging.handlers
@ -23,7 +24,6 @@ from utils import LocalQueueHandler
from typing import (
TYPE_CHECKING,
Awaitable,
Coroutine,
Optional,
Dict,
List,
@ -242,7 +242,6 @@ class SimplyPrint(Subscribable):
if isinstance(message, str):
self._process_message(message)
elif message is None:
self.webcam_stream.stop()
self.ping_sp_timer.stop()
cur_time = self.eventloop.get_loop_time()
ping_time: float = cur_time - self._last_ping_received
@ -369,11 +368,8 @@ class SimplyPrint(Subscribable):
script = "\n".join(script_list)
coro = self.klippy_apis.run_gcode(script, None)
self.eventloop.create_task(coro)
elif demand == "stream_on":
interval: float = args.get("interval", 1000) / 1000
self.webcam_stream.start(interval)
elif demand == "stream_off":
self.webcam_stream.stop()
elif demand == "webcam_snapshot":
self.eventloop.create_task(self.webcam_stream.post_image(args))
elif demand == "file":
url: Optional[str] = args.get("url")
if not isinstance(url, str):
@ -1271,6 +1267,7 @@ class LayerDetect:
# Ideally we will always fetch from the localhost rather than
# go through the reverse proxy
FALLBACK_URL = "http://127.0.0.1:8080/?action=snapshot"
SP_SNAPSHOT_URL = "https://apirewrite.simplyprint.io/jobs/ReceiveSnapshot"
class WebcamStream:
def __init__(
@ -1282,9 +1279,6 @@ class WebcamStream:
self.webcam_name = config.get("webcam_name", "")
self.url = FALLBACK_URL
self.client: HttpClient = self.server.lookup_component("http_client")
self.running = False
self.interval: float = 1.
self.stream_task: Optional[asyncio.Task] = None
self.cam: Optional[WebCam] = None
self._connected = False
@ -1324,44 +1318,74 @@ class WebcamStream:
return {}
return self.cam.as_dict()
async def extract_image(self) -> bytes:
async def extract_image(self) -> str:
headers = {"Accept": "image/jpeg"}
resp = await self.client.get(self.url, headers, enable_cache=False)
if resp.has_error():
# TODO: We should probably log an error and quit the
# stream here
return b""
return resp.content
resp.raise_for_status()
return await self.eventloop.run_in_thread(
self._encode_image, resp.content
)
def encode_image(self, image: bytes) -> str:
def _encode_image(self, image: bytes) -> str:
return base64.b64encode(image).decode()
def _send_image(self, base_image: str) -> Optional[Coroutine]:
self.simplyprint.send_sp("stream", {"base": base_image})
return None
async def post_image(self, payload: Dict[str, Any]) -> None:
uid: Optional[str] = payload.get("id")
timer: Optional[int] = payload.get("timer")
try:
if uid is not None:
url = payload.get("endpoint", SP_SNAPSHOT_URL)
img = await self.extract_image()
headers = {
"User-Agent": "Mozilla/5.0",
"Content-Type": "application/x-www-form-urlencoded"
}
body = f"id={url_escape(uid)}&image={url_escape(img)}"
resp = await self.client.post(
url, body=body, headers=headers, enable_cache=False
)
resp.raise_for_status()
elif timer is not None:
await asyncio.sleep(timer / 1000)
img = await self.extract_image()
self.simplyprint.send_sp("stream", {"base": img})
except asyncio.CancelledError:
raise
except Exception as e:
if not self.server.is_debug_enabled():
return
logging.exception(f"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"]
headers = {"User-Agent": "Mozilla/5.0"}
data = {
"api_key": token,
"image_array": base_image,
"interval": self.interval
}
resp = await self.client.post(
self.ai_url, body=data, headers=headers, enable_cache=False
)
resp.raise_for_status()
async def _stream(self) -> None:
last_err = Exception()
while self.running:
await asyncio.sleep(self.interval)
try:
ret = await self.extract_image()
if ret:
encoded = await self.eventloop.run_in_thread(
self.encode_image, ret
)
coro = self._send_image(encoded)
if coro is not None:
await coro
img = await self.extract_image()
await self._send_image(img)
except asyncio.CancelledError:
raise
except Exception as e:
if not self.server.is_debug_enabled():
continue
if type(last_err) != type(e) or last_err.args != e.args:
last_err = e
cname = self.__class__.__name__
logging.exception(f"SimplyPrint {cname} Error")
except Exception:
logging.exception(f"SimplyPrint AI Stream Error")
def start(self, interval: float) -> None:
if self.running:
@ -1380,22 +1404,6 @@ class WebcamStream:
self.stream_task.cancel()
self.stream_task = None
class AIStream(WebcamStream):
ai_url = "https://ai.simplyprint.io/api/v2/infer"
async def _send_image(self, base_image: str) -> None:
token = self.simplyprint.sp_info["printer_token"]
headers = {"User-Agent": "Mozilla/5.0"}
data = {
"api_key": token,
"image_array": base_image,
"interval": self.interval
}
resp = await self.client.post(
self.ai_url, body=data, headers=headers, enable_cache=False
)
resp.raise_for_status()
class PrintHandler:
def __init__(self, simplyprint: SimplyPrint) -> None:
self.simplyprint = simplyprint