simplyprint: implement "webcam_stream" demand
Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
d5fa40df87
commit
6e121c2221
|
@ -13,6 +13,7 @@ import time
|
||||||
import pathlib
|
import pathlib
|
||||||
import base64
|
import base64
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
|
from tornado.escape import url_escape
|
||||||
from websockets import Subscribable, WebRequest
|
from websockets import Subscribable, WebRequest
|
||||||
|
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
|
@ -23,7 +24,6 @@ from utils import LocalQueueHandler
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
Coroutine,
|
|
||||||
Optional,
|
Optional,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
List,
|
||||||
|
@ -242,7 +242,6 @@ class SimplyPrint(Subscribable):
|
||||||
if isinstance(message, str):
|
if isinstance(message, str):
|
||||||
self._process_message(message)
|
self._process_message(message)
|
||||||
elif message is None:
|
elif message is None:
|
||||||
self.webcam_stream.stop()
|
|
||||||
self.ping_sp_timer.stop()
|
self.ping_sp_timer.stop()
|
||||||
cur_time = self.eventloop.get_loop_time()
|
cur_time = self.eventloop.get_loop_time()
|
||||||
ping_time: float = cur_time - self._last_ping_received
|
ping_time: float = cur_time - self._last_ping_received
|
||||||
|
@ -369,11 +368,8 @@ class SimplyPrint(Subscribable):
|
||||||
script = "\n".join(script_list)
|
script = "\n".join(script_list)
|
||||||
coro = self.klippy_apis.run_gcode(script, None)
|
coro = self.klippy_apis.run_gcode(script, None)
|
||||||
self.eventloop.create_task(coro)
|
self.eventloop.create_task(coro)
|
||||||
elif demand == "stream_on":
|
elif demand == "webcam_snapshot":
|
||||||
interval: float = args.get("interval", 1000) / 1000
|
self.eventloop.create_task(self.webcam_stream.post_image(args))
|
||||||
self.webcam_stream.start(interval)
|
|
||||||
elif demand == "stream_off":
|
|
||||||
self.webcam_stream.stop()
|
|
||||||
elif demand == "file":
|
elif demand == "file":
|
||||||
url: Optional[str] = args.get("url")
|
url: Optional[str] = args.get("url")
|
||||||
if not isinstance(url, str):
|
if not isinstance(url, str):
|
||||||
|
@ -1271,6 +1267,7 @@ class LayerDetect:
|
||||||
# Ideally we will always fetch from the localhost rather than
|
# Ideally we will always fetch from the localhost rather than
|
||||||
# go through the reverse proxy
|
# go through the reverse proxy
|
||||||
FALLBACK_URL = "http://127.0.0.1:8080/?action=snapshot"
|
FALLBACK_URL = "http://127.0.0.1:8080/?action=snapshot"
|
||||||
|
SP_SNAPSHOT_URL = "https://apirewrite.simplyprint.io/jobs/ReceiveSnapshot"
|
||||||
|
|
||||||
class WebcamStream:
|
class WebcamStream:
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -1282,9 +1279,6 @@ class WebcamStream:
|
||||||
self.webcam_name = config.get("webcam_name", "")
|
self.webcam_name = config.get("webcam_name", "")
|
||||||
self.url = FALLBACK_URL
|
self.url = FALLBACK_URL
|
||||||
self.client: HttpClient = self.server.lookup_component("http_client")
|
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.cam: Optional[WebCam] = None
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
|
@ -1324,44 +1318,74 @@ class WebcamStream:
|
||||||
return {}
|
return {}
|
||||||
return self.cam.as_dict()
|
return self.cam.as_dict()
|
||||||
|
|
||||||
async def extract_image(self) -> bytes:
|
async def extract_image(self) -> str:
|
||||||
headers = {"Accept": "image/jpeg"}
|
headers = {"Accept": "image/jpeg"}
|
||||||
resp = await self.client.get(self.url, headers, enable_cache=False)
|
resp = await self.client.get(self.url, headers, enable_cache=False)
|
||||||
if resp.has_error():
|
resp.raise_for_status()
|
||||||
# TODO: We should probably log an error and quit the
|
return await self.eventloop.run_in_thread(
|
||||||
# stream here
|
self._encode_image, resp.content
|
||||||
return b""
|
)
|
||||||
return resp.content
|
|
||||||
|
|
||||||
def encode_image(self, image: bytes) -> str:
|
def _encode_image(self, image: bytes) -> str:
|
||||||
return base64.b64encode(image).decode()
|
return base64.b64encode(image).decode()
|
||||||
|
|
||||||
def _send_image(self, base_image: str) -> Optional[Coroutine]:
|
async def post_image(self, payload: Dict[str, Any]) -> None:
|
||||||
self.simplyprint.send_sp("stream", {"base": base_image})
|
uid: Optional[str] = payload.get("id")
|
||||||
return None
|
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:
|
async def _stream(self) -> None:
|
||||||
last_err = Exception()
|
|
||||||
while self.running:
|
while self.running:
|
||||||
await asyncio.sleep(self.interval)
|
|
||||||
try:
|
try:
|
||||||
ret = await self.extract_image()
|
img = await self.extract_image()
|
||||||
if ret:
|
await self._send_image(img)
|
||||||
encoded = await self.eventloop.run_in_thread(
|
|
||||||
self.encode_image, ret
|
|
||||||
)
|
|
||||||
coro = self._send_image(encoded)
|
|
||||||
if coro is not None:
|
|
||||||
await coro
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception:
|
||||||
if not self.server.is_debug_enabled():
|
logging.exception(f"SimplyPrint AI Stream Error")
|
||||||
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")
|
|
||||||
|
|
||||||
def start(self, interval: float) -> None:
|
def start(self, interval: float) -> None:
|
||||||
if self.running:
|
if self.running:
|
||||||
|
@ -1380,22 +1404,6 @@ class WebcamStream:
|
||||||
self.stream_task.cancel()
|
self.stream_task.cancel()
|
||||||
self.stream_task = None
|
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:
|
class PrintHandler:
|
||||||
def __init__(self, simplyprint: SimplyPrint) -> None:
|
def __init__(self, simplyprint: SimplyPrint) -> None:
|
||||||
self.simplyprint = simplyprint
|
self.simplyprint = simplyprint
|
||||||
|
|
Loading…
Reference in New Issue