Skip to content

Commit

Permalink
Added support for ProgressNotifications on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
Clon1998 committed Feb 24, 2024
1 parent 9bd8ede commit e952712
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 14 deletions.
30 changes: 30 additions & 0 deletions mobileraker/data/dtos/mobileraker/companion_request_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,33 @@ def toJSON(self) -> Dict[str, Any]:
return json


class ProgressNotificationContentDto(ContentDto):
def __init__(self,
progress: int, # (0 - 100)
id: int,
channel: str,
title: str,
body: str,
):
super().__init__()
self.id: int = id
self.channel: str = channel
self.title: str = title
self.body: str = body
self.progress: int = progress

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

return json


class LiveActivityContentDto(ContentDto):
def __init__(self,
token: str,
Expand All @@ -64,10 +91,12 @@ def toJSON(self) -> Dict[str, Any]:

class DeviceRequestDto:
def __init__(self,
version: int,
printer_id: str,
token: str,
notifcations: List[ContentDto],
):
self.version: int = version
self.printer_id: str = printer_id
self.token: str = token
self.notifcations: List[ContentDto] = notifcations
Expand All @@ -78,6 +107,7 @@ def toJSON(self) -> Dict[str, Any]:
notifications.append(n.toJSON())

return {
"version": self.version,
"printerIdentifier": self.printer_id,
"token": self.token,
"notifications": notifications,
Expand Down
14 changes: 13 additions & 1 deletion mobileraker/data/dtos/mobileraker/notification_config_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
# "created": "",
# "lastModified": "",
# "liveActivity": ""
# }
# },
# "version": "2.6.11-android"
# }

# These are bundeleted in moonraker DB in an array/map -> each device has its own config! (Use the uuid of the machine class of flutter as id!)
Expand All @@ -40,6 +41,7 @@ def __init__(self):
self.machine_id: str # Flutter: machine.uuid
self.machine_name: str
self.language: str = 'en'
self.version: Optional[str] = None # App version
self.settings: NotificationSettings
self.snap: NotificationSnap
self.apns: Optional[APNs] = None
Expand All @@ -58,6 +60,8 @@ def fromJSON(machine_id: str, json: Dict[str, Any]) -> 'DeviceNotificationEntry'
cfg.snap = NotificationSnap.fromJSON(
json['snap']) if 'snap' in json and json['snap'] else NotificationSnap()
cfg.apns = APNs.fromJSON(json['apns']) if 'apns' in json and json['apns'] else None
cfg.version = json['version'] if 'version' in json else None


return cfg

Expand All @@ -67,6 +71,14 @@ def __str__(self):
', '.join('%s=%s' % item for item in vars(self).items())
)

@property
def is_android(self) -> bool:
return self.version is not None and 'android' in self.version

@property
def is_ios(self) -> bool:
return self.version is not None and 'ios' in self.version

# def __eq__(self, o: object) -> bool:
# if not isinstance(o, CompanionRequestDto):
# return False
Expand Down
52 changes: 40 additions & 12 deletions mobileraker/mobileraker_companion.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import asyncio
import base64
import logging
from typing import List, Optional
from typing import List, Optional, Union
import time


Expand All @@ -11,13 +11,13 @@
from mobileraker.client.moonraker_client import MoonrakerClient
from mobileraker.client.snapshot_client import SnapshotClient
from mobileraker.data.dtos.mobileraker.companion_meta_dto import CompanionMetaDataDto
from mobileraker.data.dtos.mobileraker.companion_request_dto import ContentDto, DeviceRequestDto, FcmRequestDto, LiveActivityContentDto, NotificationContentDto
from mobileraker.data.dtos.mobileraker.companion_request_dto import ContentDto, DeviceRequestDto, FcmRequestDto, LiveActivityContentDto, NotificationContentDto, ProgressNotificationContentDto
from mobileraker.data.dtos.mobileraker.notification_config_dto import DeviceNotificationEntry
from mobileraker.data.dtos.moonraker.printer_snapshot import PrinterSnapshot
from mobileraker.service.data_sync_service import DataSyncService
from mobileraker.util.configs import CompanionLocalConfig, CompanionRemoteConfig

from mobileraker.util.functions import generate_notifcation_id_from_uuid, get_software_version, is_valid_uuid, normalized_progress_interval_reached
from mobileraker.util.functions import compare_version, generate_notifcation_id_from_uuid, get_software_version, is_valid_uuid, normalized_progress_interval_reached
from mobileraker.util.i18n import translate, translate_replace_placeholders
from mobileraker.util.notification_placeholders import replace_placeholders

Expand Down Expand Up @@ -104,7 +104,7 @@ async def _evaluate(self, snapshot: PrinterSnapshot) -> None:
if not cfg.fcm_token:
continue
self._logger.info(
'Evaluate for machineID %s, cfg.snap: %s, cfg.settings: %s', cfg.machine_id, cfg.snap, cfg.settings)
'Evaluate for machineID %s, cfg.version: %s , cfg.snap: %s, cfg.settings: %s', cfg.machine_id, cfg.version, cfg.snap, cfg.settings)
notifications: List[ContentDto] = []

state_noti = self._state_notification(cfg, snapshot)
Expand All @@ -116,8 +116,8 @@ async def _evaluate(self, snapshot: PrinterSnapshot) -> None:
progress_noti = self._progress_notification(cfg, snapshot)
if progress_noti is not None:
notifications.append(progress_noti)
self._logger.info('ProgressNoti: %s - %s',
progress_noti.title, progress_noti.body)
self._logger.info('%s: %s - %s',
type(progress_noti).__name__, progress_noti.title, progress_noti.body)

m117_noti = self._custom_notification(cfg, snapshot, True)
if m117_noti is not None:
Expand All @@ -144,10 +144,12 @@ async def _evaluate(self, snapshot: PrinterSnapshot) -> None:

self._logger.info('%i Notifications for machineID: %s: state: %s, proggress: %s, M117 %s, GcodeResponse: %s, LiveActivity: %s', len(
notifications), cfg.machine_id, state_noti is not None, progress_noti is not None, m117_noti is not None, gcode_response_noti is not None, live_activity_update is not None)

if notifications:
# Set a webcam img to all DTOs if available
dto = DeviceRequestDto(
# Version 2 is used to indicate that we want to use flattened structure of awesome notifications. This is only available in 2.6.10 and later
version= 2 if cfg.version is not None and compare_version(cfg.version, "2.6.10") >= 0 else 1,
printer_id=cfg.machine_id,
token=cfg.fcm_token,
notifcations=notifications
Expand Down Expand Up @@ -305,37 +307,59 @@ def _state_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSna
body, cfg, cur_snap, self.companion_config)
return NotificationContentDto(generate_notifcation_id_from_uuid(cfg.machine_id, 0), f'{cfg.machine_id}-statusUpdates', title, body)

def _progress_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSnapshot) -> Optional[NotificationContentDto]:
def _progress_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSnapshot) -> Optional[Union[NotificationContentDto, ProgressNotificationContentDto]]:
"""
Generates a progress notification based on the given configuration and current printer snapshot.
Args:
cfg (DeviceNotificationEntry): The device notification configuration.
cur_snap (PrinterSnapshot): The current printer snapshot.
Returns:
Optional[ContentDto]: The generated progress notification content, or None if no notification should be issued.
"""
# If progress notifications are disabled, skip it!
if cfg.settings.progress_config == -1:
return None

# starting from 2.6.10 we can use the progress notification for android. If not, use the text based one
use_progress_notification = cfg.is_android and cfg.version is not None and compare_version(cfg.version, "2.6.10") >= 0


# only issue new progress notifications if the printer is printing, or paused
# also skip if progress is at 100 since this notification is handled via the print state transition from printing to completed
if cur_snap.print_state not in ["printing", "paused"] or cur_snap.progress is None or cur_snap.progress == 100:
return None

self._logger.info(
'ProgressNoti preChecks: cfg.progress.config: %i - %i = %i < %i RESULT: %s',
'ProgressNoti preChecks: cfg.progress.config: %i - %i = %i < %i RESULT: %s, use ProgressNoti: %s',
cur_snap.progress,
cfg.snap.progress,
cur_snap.progress - cfg.snap.progress,
max(self.remote_config.increments, cfg.settings.progress_config),
normalized_progress_interval_reached(cfg.snap.progress, cur_snap.progress, max(
self.remote_config.increments, cfg.settings.progress_config))
self.remote_config.increments, cfg.settings.progress_config)),
use_progress_notification
)

# ensure the progress threshhold of the user's cfg is reached. If the cfg.snap is not yet printing also issue a notification
if (cfg.snap.state in ["printing", "paused"]
and not use_progress_notification
and not normalized_progress_interval_reached(cfg.snap.progress, cur_snap.progress, max(self.remote_config.increments, cfg.settings.progress_config))
):
return None

nid = generate_notifcation_id_from_uuid(cfg.machine_id, 1)
channel = f'{cfg.machine_id}-progressUpdates'
title = translate_replace_placeholders(
'print_progress_title', cfg, cur_snap, self.companion_config)
body = translate_replace_placeholders(
'print_progress_body', cfg, cur_snap, self.companion_config)
return NotificationContentDto(generate_notifcation_id_from_uuid(cfg.machine_id, 1), f'{cfg.machine_id}-progressUpdates', title, body)

if use_progress_notification:
return ProgressNotificationContentDto(cur_snap.progress, nid, channel, title, body)
else:
return NotificationContentDto(nid, channel, title, body)

def _live_activity_update(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSnapshot) -> Optional[LiveActivityContentDto]:
# If uuid is none or empty returm
Expand Down Expand Up @@ -448,12 +472,16 @@ async def _update_app_snapshot(self, cfg: DeviceNotificationEntry, printer_snap:
try:
last = cfg.snap

# starting from 2.6.10 we can use the progress notification for android. If not, use the text based one
use_progress_notification = cfg.is_android and cfg.version is not None and compare_version(cfg.version, "2.6.10") >= 0


progress_update = None
if printer_snap.print_state not in ['printing', 'paused']:
progress_update = 0
elif (last.progress != printer_snap.progress
and printer_snap.progress is not None
and (normalized_progress_interval_reached(last.progress, printer_snap.progress, max(self.remote_config.increments, cfg.settings.progress_config))
and (normalized_progress_interval_reached(last.progress, printer_snap.progress, self.remote_config.increments if use_progress_notification else max(self.remote_config.increments, cfg.settings.progress_config))
or printer_snap.progress < last.progress)):
progress_update = printer_snap.progress

Expand Down
29 changes: 28 additions & 1 deletion mobileraker/util/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def normalized_progress_interval_reached(last: int, current: int, interval: int)
Returns:
bool: True if the current progress exceeds the normalized threshold, False otherwise.
"""
# logging.info(f"!!!last: {last}, current: {current}, interval: {interval}")
noramlized = last - (last % interval)
return abs(current - noramlized) >= interval

Expand All @@ -64,4 +65,30 @@ def generate_notifcation_id_from_uuid(uuid_string: str, offset: int) -> int:
int: The generated notification ID.
"""
# Convert the UUID into an integer and take the modulus in a single line
return (uuid.UUID(uuid_string).int + offset) % 2147483647 + 1
return (uuid.UUID(uuid_string).int + offset) % 2147483647 + 1



def compare_version(a: str, b: str) -> int:
"""
Compare two version strings.
Args:
a (str): The first version string.
b (str): The second version string.
Returns:
int: 0 if the versions are equal, 1 if a is greater than b, -1 if a is less than b.
"""
a = a.split("-")[0]
b = b.split("-")[0]
aVersions = list(map(int, a.split(".")))
bVersions = list(map(int, b.split(".")))

for i in range(3):
if aVersions[i] > bVersions[i]:
return 1
if aVersions[i] < bVersions[i]:
return -1
return 0

0 comments on commit e952712

Please sign in to comment.