camera: add support for moonraker cameras, deprecates camera_url

this also adds support for flipping and rotation (configured in moonraker)
close #976
This commit is contained in:
alfrix 2023-07-30 18:31:51 -03:00 committed by Alfredo Monclus
parent 77a24ec809
commit 8053e97d10
6 changed files with 65 additions and 40 deletions

View File

@ -173,7 +173,7 @@ class KlipperScreenConfig:
strs = ( strs = (
'moonraker_api_key', 'moonraker_host', 'titlebar_name_type', 'moonraker_api_key', 'moonraker_host', 'titlebar_name_type',
'screw_positions', 'power_devices', 'titlebar_items', 'z_babystep_values', 'screw_positions', 'power_devices', 'titlebar_items', 'z_babystep_values',
'extrude_distances', "extrude_speeds", "camera_url", 'extrude_distances', "extrude_speeds",
) )
numbers = ( numbers = (
'moonraker_port', 'move_speed_xy', 'move_speed_z', 'moonraker_port', 'move_speed_xy', 'move_speed_z',
@ -199,6 +199,9 @@ class KlipperScreenConfig:
for key in config[section]: for key in config[section]:
if key not in bools and key not in strs and key not in numbers: if key not in bools and key not in strs and key not in numbers:
msg = f'Option "{key}" not recognized for section "[{section}]"' msg = f'Option "{key}" not recognized for section "[{section}]"'
if key == "camera_url":
msg = "camera_url has been deprecated in favor of moonraker cameras"
msg += "\n\n https://moonraker.readthedocs.io/en/latest/configuration/#webcam"
if remove: if remove:
# This should only be called for the auto-generated section # This should only be called for the auto-generated section
self.config.remove_option(section, key) self.config.remove_option(section, key)

View File

@ -147,7 +147,7 @@ enable: {{ printer.power_devices.count > 0 }}
name: {{ gettext('Camera') }} name: {{ gettext('Camera') }}
icon: camera icon: camera
panel: camera panel: camera
enable: {{ camera_configured }} enable: {{ printer.cameras.count > 0 }}
[menu __main more console] [menu __main more console]
name: {{ gettext('Console') }} name: {{ gettext('Console') }}

View File

@ -24,6 +24,7 @@ class Printer:
self.busy_cb = busy_cb self.busy_cb = busy_cb
self.busy = False self.busy = False
self.tempstore_size = 1200 self.tempstore_size = 1200
self.cameras = []
def reinit(self, printer_info, data): def reinit(self, printer_info, data):
self.config = data['configfile']['config'] self.config = data['configfile']['config']
@ -160,6 +161,10 @@ class Printer:
} }
logging.debug(f"Power devices: {self.power_devices}") logging.debug(f"Power devices: {self.power_devices}")
def configure_cameras(self, data):
self.cameras = data
logging.debug(f"Cameras: {self.cameras}")
def get_config_section_list(self, search=""): def get_config_section_list(self, search=""):
if self.config is not None: if self.config is not None:
return [i for i in list(self.config) if i.startswith(search)] if hasattr(self, "config") else [] return [i for i in list(self.config) if i.startswith(search)] if hasattr(self, "config") else []
@ -228,6 +233,7 @@ class Printer:
"idle_timeout": self.get_stat("idle_timeout").copy(), "idle_timeout": self.get_stat("idle_timeout").copy(),
"pause_resume": {"is_paused": self.state == "paused"}, "pause_resume": {"is_paused": self.state == "paused"},
"power_devices": {"count": len(self.get_power_devices())}, "power_devices": {"count": len(self.get_power_devices())},
"cameras": {"count": len(self.cameras)},
} }
} }

View File

@ -12,35 +12,53 @@ class Panel(ScreenPanel):
def __init__(self, screen, title): def __init__(self, screen, title):
super().__init__(screen, title) super().__init__(screen, title)
self.mpv = None self.mpv = None
self.da = Gtk.DrawingArea()
self.da.set_hexpand(True)
self.da.set_vexpand(True)
fs = self._gtk.Button("move", _("Fullscreen"), None, self.bts, Gtk.PositionType.LEFT, 1)
fs.connect("clicked", self.play)
fs.set_hexpand(True)
fs.set_vexpand(False)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
box.add(self.da) for i, cam in enumerate(self._printer.cameras):
box.add(fs) if not cam["enabled"]:
self.content.add(box) continue
logging.info(cam)
cam[cam["name"]] = self._gtk.Button(
image_name="camera", label=cam["name"], style=f"color{i % 4 + 1}",
scale=self.bts, position=Gtk.PositionType.LEFT, lines=1
)
cam[cam["name"]].set_hexpand(True)
cam[cam["name"]].set_vexpand(True)
cam[cam["name"]].connect("clicked", self.play, cam)
box.add(cam[cam["name"]])
self.scroll = self._gtk.ScrolledWindow()
self.scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
self.scroll.add(box)
self.content.add(self.scroll)
self.content.show_all() self.content.show_all()
self.url = self.ks_printer_cfg.get("camera_url", "http://127.0.0.1/webcam/?action=stream").replace('"', '')
logging.debug(f"Camera URL: {self.url}")
def activate(self): def activate(self):
self.play() # if only 1 cam start playing fullscreen
if len(self._printer.cameras) == 1:
cam = next(iter(self._printer.cameras))
if cam['enabled']:
self.play(None, cam)
def deactivate(self): def deactivate(self):
if self.mpv: if self.mpv:
self.mpv.terminate() self.mpv.terminate()
self.mpv = None self.mpv = None
def play(self, fs=None): def play(self, widget, cam):
url = cam['stream_url']
vf = ""
if cam["flip_horizontal"]:
vf += "hflip,"
if cam["flip_vertical"]:
vf += "vflip,"
vf += f"rotate:{cam['rotation']*3.14159/180}"
logging.info(f"video filters: {vf}")
if self.mpv: if self.mpv:
self.mpv.terminate() self.mpv.terminate()
self.mpv = None self.mpv = mpv.MPV(fullscreen=True, log_handler=self.log, vo='gpu,wlshm,xv,x11')
# Create mpv after show or the 'window' property will be None
self.mpv = mpv.MPV(log_handler=self.log, vo='gpu,wlshm,xv,x11') self.mpv.vf = vf
with suppress(Exception): with suppress(Exception):
self.mpv.profile = 'sw-fast' self.mpv.profile = 'sw-fast'
@ -51,22 +69,13 @@ class Panel(ScreenPanel):
self.mpv.untimed = True self.mpv.untimed = True
self.mpv.audio = 'no' self.mpv.audio = 'no'
# On wayland mpv cannot be embedded at least for now
# https://github.com/mpv-player/mpv/issues/9654
# if fs:
self.mpv.fullscreen = True
@self.mpv.on_key_press('MBTN_LEFT' or 'MBTN_LEFT_DBL') @self.mpv.on_key_press('MBTN_LEFT' or 'MBTN_LEFT_DBL')
def clicked(): def clicked():
self.mpv.quit(0) self.mpv.quit(0)
# else:
# self.mpv.wid = f'{self.da.get_property("window").get_xid()}' logging.debug(f"Camera URL: {url}")
# self.mpv.play(url)
# @self.mpv.on_key_press('MBTN_LEFT' or 'MBTN_LEFT_DBL')
# def clicked():
# self._screen.show_popup_message(self.url, level=1)
self.mpv.play(self.url)
# if fs:
try: try:
self.mpv.wait_for_playback() self.mpv.wait_for_playback()
except mpv.ShutdownError: except mpv.ShutdownError:
@ -75,8 +84,10 @@ class Panel(ScreenPanel):
logging.exception(e) logging.exception(e)
self.mpv.terminate() self.mpv.terminate()
self.mpv = None self.mpv = None
self._screen._menu_go_back() if len(self._printer.cameras) == 1:
self._screen._menu_go_back()
@staticmethod def log(self, loglevel, component, message):
def log(loglevel, component, message):
logging.debug(f'[{loglevel}] {component}: {message}') logging.debug(f'[{loglevel}] {component}: {message}')
if loglevel == 'error':
self._screen.show_popup_message(f'{message}')

View File

@ -103,8 +103,6 @@ class Panel(ScreenPanel):
if enable == "{{ moonraker_connected }}": if enable == "{{ moonraker_connected }}":
logging.info(f"moonraker connected {self._screen._ws.connected}") logging.info(f"moonraker connected {self._screen._ws.connected}")
return self._screen._ws.connected return self._screen._ws.connected
elif enable == "{{ camera_configured }}":
return self.ks_printer_cfg and self.ks_printer_cfg.get("camera_url", None) is not None
self.j2_data = self._printer.get_printer_status_data() self.j2_data = self._printer.get_printer_status_data()
try: try:
j2_temp = Template(enable, autoescape=True) j2_temp = Template(enable, autoescape=True)

View File

@ -832,9 +832,16 @@ class KlipperScreen(Gtk.Window):
# Moonraker is ready, set a loop to init the printer # Moonraker is ready, set a loop to init the printer
self.reinit_count += 1 self.reinit_count += 1
powerdevs = self.apiclient.send_request("machine/device_power/devices") server_info = self.apiclient.get_server_info()["result"]
if powerdevs is not False: logging.info(f"Moonraker info {server_info}")
self.printer.configure_power_devices(powerdevs['result']) if "power" in server_info["components"]:
powerdevs = self.apiclient.send_request("machine/device_power/devices")
if powerdevs is not False:
self.printer.configure_power_devices(powerdevs['result'])
if "webcam" in server_info["components"]:
cameras = self.apiclient.send_request("server/webcams/list")
if cameras is not False:
self.printer.configure_cameras(cameras['result']['webcams'])
if state['result']['klippy_connected'] is False: if state['result']['klippy_connected'] is False:
logging.info("Klipper not connected") logging.info("Klipper not connected")