Skip to content

Commit

Permalink
Included code for image notifications!
Browse files Browse the repository at this point in the history
  • Loading branch information
Clon1998 committed May 25, 2023
1 parent 0745909 commit ff08ef0
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 14 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ eta_format: %%d.%%m.%%Y, %%H:%%M:%%S
# Note that you will have to escape the % char by using a 2nd one e.g.: %d/%m/%Y -> %%d/%%m/%%Y
# Default: %%d.%%m.%%Y, %%H:%%M:%%S
# Optional
include_snapshot: True
# !! SUPPORTER ONLY - This feature requires beeing a supporter of Mobileraker as of now!
# Include a snapshot of the webcam in any print status/progress update notifications
# Default: True
# Optional



# Add a [printer ...] section for every printer you want to add
[printer <NAME OF YOUR PRINTER: optional>]
Expand All @@ -46,6 +53,16 @@ moonraker_api_key: False
# Moonraker API key if force_logins or trusted clients is active!
# Default value: False
# Optional
snapshot_uri: http://127.0.0.1/webcam/?action=snapshot
# !! SUPPORTER ONLY - This feature requires beeing a supporter of Mobileraker as of now!
# The ABSOLUT url to the webcam, the companion should make a screenshot of.
# Default:
# Optional
snapshot_rotation: 0
# The rotation tapplied to the image. Valid values : 0, 90, 180, 270
# Default: 0
# Optional

```

By default the companion searches for a `Mobileraker.conf` file in:
Expand Down
13 changes: 10 additions & 3 deletions dtos/mobileraker/companion_request_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,30 @@
class NotificationContentDto:
def __init__(self,
id: int,
channel:str,
channel: str,
title: str,
body: str,
image: Optional[str] = None,
):
self.id: int = id
self.channel: str = channel
self.title: str = title
self.body: str = body
self.image: Optional[str] = image

def toJSON(self) -> Dict[str, Any]:
return {
json = {
"id": self.id,
"channel": self.channel,
"title": self.title,
"body": self.body,
}

if self.image is not None:
json['image'] = self.image

return json


class DeviceRequestDto:
def __init__(self,
Expand Down Expand Up @@ -59,7 +66,7 @@ def __init__(self,
):
self.device_requests: List[DeviceRequestDto] = device_requests

def toJSON(self) -> Dict[str, Any]:
def toJSON(self) -> Dict[str, Any]:
dtos = []
for n in self.device_requests:
dtos.append(n.toJSON())
Expand Down
24 changes: 21 additions & 3 deletions mobileraker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import argparse
import asyncio
import base64
import datetime
import hashlib
import logging
Expand All @@ -10,6 +11,7 @@

import coloredlogs
from dtos.mobileraker.companion_meta_dto import CompanionMetaDataDto
from snapshot_client import SnapshotClient

from util.configs import CompanionLocalConfig, CompanionRemoteConfig, printer_data_logs_dir
from dtos.mobileraker.companion_request_dto import (DeviceRequestDto,
Expand All @@ -33,13 +35,15 @@ def __init__(
self,
jrpc: MoonrakerClient,
fcm_client: MobilerakerFcmClient,
snapshot_client: SnapshotClient,
printer_name: str,
loop: AbstractEventLoop,
companion_config: CompanionLocalConfig
) -> None:
super().__init__()
self._jrpc: MoonrakerClient = jrpc
self._fcm_client: MobilerakerFcmClient = fcm_client
self._snapshot_client: SnapshotClient = snapshot_client
self.printer_name: str = printer_name
self.loop: AbstractEventLoop = loop
self.companion_config: CompanionLocalConfig = companion_config
Expand Down Expand Up @@ -188,6 +192,7 @@ def _take_snapshot(self) -> PrinterSnapshot:
snapshot.progress = round(self.virtual_sdcard.progress*100)
snapshot.printing_duration = self.print_stats.print_duration
return snapshot


async def update_snap_in_fcm_cfg(self, machine_id: str, notification_snap: NotificationSnap) -> None:
self.logger.info(
Expand Down Expand Up @@ -342,7 +347,7 @@ def _state_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSna

body = translate_replace_placeholders(
body, cfg, cur_snap, self.companion_config)
return NotificationContentDto(111, f'{cfg.machine_id}-statusUpdates', title, body)
return NotificationContentDto(111, f'{cfg.machine_id}-statusUpdates', title, body, self._take_image())

def _progress_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSnapshot) -> Optional[NotificationContentDto]:

Expand All @@ -366,14 +371,22 @@ def _progress_notification(self, cfg: DeviceNotificationEntry, cur_snap: Printer
'print_progress_title', cfg, cur_snap, self.companion_config)
body = translate_replace_placeholders(
'print_progress_body', cfg, cur_snap, self.companion_config)
return NotificationContentDto(222, f'{cfg.machine_id}-progressUpdates', title, body)
return NotificationContentDto(222, f'{cfg.machine_id}-progressUpdates', title, body, self._take_image())

async def _push_and_clear_faulty(self, dtos: List[DeviceRequestDto]):
if dtos:
request = FcmRequestDto(dtos)
response = self._fcm_client.push(request)
# todo: remove faulty token lol

def _take_image(self) -> Optional[str]:
img_encoded = None
if self.companion_config.include_snapshot:
bytes = self._snapshot_client.takeSnapshot()
if bytes is not None:
img_encoded = base64.b64encode(bytes).decode("ascii")
return img_encoded

def evaluate_m117(self) -> None:
if not self.init_done:
return
Expand Down Expand Up @@ -440,7 +453,7 @@ def _m117_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSnap
f'Got M117/Custom: {msg}: {title} - {body}')

return NotificationContentDto(333, f'{cfg.machine_id}-m117',
title, body)
title, body, self._take_image())


def main() -> None:
Expand Down Expand Up @@ -485,10 +498,15 @@ def main() -> None:
# 'http://127.0.0.1:8080',
'https://mobileraker.eliteschw31n.de',
event_loop)
snc = SnapshotClient(
p_config['snapshot_uri'],
p_config['snapshot_rotation'],
)

client = MobilerakerCompanion(
jrpc=jrpc,
fcm_client=fcmc,
snapshot_client=snc,
printer_name=printer_name,
loop=event_loop,
companion_config=local_config)
Expand Down
4 changes: 2 additions & 2 deletions mobileraker_fcm.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def __init__(
def push(self, request: FcmRequestDto,) -> Optional[requests.Response]:
jsons = request.toJSON()

self.logger.info(
self.logger.debug(
f"Sending to firebase fcm ({self.fcm_uri}): {jsons}")
try:
res = requests.post(
f'{self.fcm_uri}/companion/v2/update', json=jsons, timeout=5)
f'{self.fcm_uri}/companion/v2/update', json=jsons, timeout=60)
res.raise_for_status()
return res
except requests.exceptions.ConnectionError as err:
Expand Down
3 changes: 2 additions & 1 deletion scripts/mobileraker-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ coloredlogs==15.0.1
requests==2.26.0
websockets==10.1
pytz==2022.7.1
tzlocal==4.3
tzlocal==4.3
Pillow==9.5.0
38 changes: 38 additions & 0 deletions snapshot_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from io import BytesIO
import logging
from PIL import Image
from typing import List, Optional

import requests


class SnapshotClient:
def __init__(
self,
uri: str,
rotation: int = 0
) -> None:
super().__init__()
self.uri: str = uri
self.rotation: int = rotation
self.logger = logging.getLogger('mobileraker.cam')

def takeSnapshot(self,) -> Optional[bytes]:

self.logger.info(
f"Trying to take a snapshot from URI: {self.uri}")
try:
res = requests.get(self.uri, timeout=5)
res.raise_for_status()
self.logger.info(
f"Took webcam snapshot! Converting using {self.rotation}")
image = Image.open(BytesIO(res.content)).convert("RGB")
image = image.rotate(self.rotation)
buffered = BytesIO()
image.save(buffered, format="JPEG")

return buffered.getvalue()
except requests.exceptions.ConnectionError as err:
self.logger.error("Could not connect to webcam!")
except:
self.logger.error("Unable to process take snapshot request")
22 changes: 17 additions & 5 deletions util/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
printer_data_logs_dir = os.path.join(
home_dir, "printer_data", "logs")


class CompanionRemoteConfig:

def __init__(self) -> None:
Expand Down Expand Up @@ -47,35 +48,46 @@ def __init__(self, passed_config_file: str) -> None:
for printer in printer_sections:
api_key = self.config.get(
printer, "moonraker_api_key", fallback=None)

rotation = self.config.getint(
printer, "snapshot_rotation", fallback=0)
if rotation not in [0, 90, 180, 270]:
rotation = 0

self.printers[printer[8:]] = {
"moonraker_uri": self.config.get(printer, "moonraker_uri", fallback="ws://127.0.0.1:7125/websocket"),
"moonraker_api_key": None if api_key == 'False' or not api_key else api_key,
"snapshot_uri": self.config.get(printer, "snapshot_uri", fallback="http://127.0.0.1/webcam/?action=snapshot"),
"snapshot_rotation": rotation
}

if len(self.printers) <= 0:
self.printers['_Default'] = {
"moonraker_uri": "ws://127.0.0.1:7125/websocket",
"moonraker_api_key": None,
"snapshot_uri": "http://127.0.0.1/webcam/?action=snapshot",
"snapshot_rotation": 0
}
logging.info("Read %i printer config sections" % len(self.printers))


self.language: str = self.config.get(
'general', 'language', fallback='en')
self.timezone_str: str = self.config.get(
'general', 'timezone', fallback=tzlocal.get_localzone_name()) # fallback to system timezone (Hopefully)
self.timezone: datetime.tzinfo = pytz.timezone(self.timezone_str)
self.eta_format: str = self.config.get(
'general', 'eta_format', fallback='%d.%m.%Y, %H:%M:%S')
self.include_snapshot: bool = self.config.getboolean(
'general', 'include_snapshot', fallback=True)

logging.info(
f'Main section read, language:"{self.language}", timezone:"{self.timezone_str}", eta_format:"{self.eta_format}"')
f'Main section read, language:"{self.language}", timezone:"{self.timezone_str}", eta_format:"{self.eta_format}", include_snapshot:"{self.include_snapshot}"')

def get_config_file_location(self, passed_config: str) -> Optional[str]:
logging.info("Passed config file is: %s" % passed_config)

foundFile = passed_config if os.path.exists(passed_config) else self.__check_companion_dir() or self.__check_klipper_config_dir(
) or self.__check_printer_data_config_dir() or self.__check_user_dir()

) or self.__check_printer_data_config_dir() or self.__check_user_dir()

if foundFile and os.path.exists(foundFile):
logging.info("Found configuration file at: %s" %
Expand Down Expand Up @@ -113,4 +125,4 @@ def __check_printer_data_config_dir(self) -> Optional[str]:
# Check user-dir -> ~/Mobileraker.conf
def __check_user_dir(self) -> Optional[str]:
logging.info("Checking user dir")
return self.__check_file_exists(home_dir, self.default_file_name)
return self.__check_file_exists(home_dir, self.default_file_name)

0 comments on commit ff08ef0

Please sign in to comment.