diff --git a/mobileraker/data/dtos/moonraker/printer_snapshot.py b/mobileraker/data/dtos/moonraker/printer_snapshot.py index 8d24503..7cfd7ec 100644 --- a/mobileraker/data/dtos/moonraker/printer_snapshot.py +++ b/mobileraker/data/dtos/moonraker/printer_snapshot.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Dict, Optional +from typing import Dict, List, Optional import math from dateutil import tz @@ -47,6 +47,59 @@ def __eq__(self, other): and self.filament_sensors == other.filament_sensors ) + def remaining_time_avg(self, sources: List[str]) -> Optional[int]: + """ + Calculate the average of remaining times from different criteria based on the provided sources. + + Args: + sources (List[str]): List of sources to calculate the average from. Possible values are 'file', 'filament', 'slicer'. + + Returns: + Optional[int]: Average remaining time in seconds, or None if calculation is not possible. + """ + remaining = 0 + cnt = 0 + + r_file = self.remaining_time_by_file or 0 + if 'file' in sources and r_file > 0: + remaining += r_file + cnt += 1 + + r_filament = self.remaining_time_by_filament or 0 + if 'filament' in sources and r_filament > 0: + remaining += r_filament + cnt += 1 + + r_slicer = self.remaining_time_by_slicer or 0 + if 'slicer' in sources and r_slicer > 0: + remaining += r_slicer + cnt += 1 + + if cnt == 0: + return None + + return remaining // cnt + + def remaining_time_formatted(self, sources: List[str]) -> Optional[str]: + sec = self.remaining_time_avg(sources) + if sec: + return str(timedelta(seconds=sec))[:-3] # remove the seconds part + + + def calc_eta(self, sources: List[str]) -> Optional[datetime]: + remaining = self.remaining_time_avg(sources) + if remaining: + now = datetime.now() + return now + timedelta(seconds=remaining) + + def calc_eta_seconds_utc(self, sources: List[str]) -> Optional[int]: + eta = self.calc_eta(sources) + return int(eta.astimezone(tz.UTC).timestamp()) if eta else None + + @property + def eta_available(self) -> bool: + return self.remaining_time_avg(['file', 'filament', 'slicer']) is not None + @property def remaining_time_by_file(self) -> Optional[int]: """ @@ -96,37 +149,6 @@ def remaining_time_by_slicer(self) -> Optional[int]: return None return int((slicer_estimate - print_duration)) - @property - def remaining_time_avg(self) -> Optional[int]: - """ - Calculate the average of remaining times from different criteria. - - Returns: - Optional[int]: Average remaining time in seconds, or None if calculation is not possible. - """ - remaining = 0 - cnt = 0 - - r_file = self.remaining_time_by_file or 0 - if r_file > 0: - remaining += r_file - cnt += 1 - - r_filament = self.remaining_time_by_filament or 0 - if r_filament > 0: - remaining += r_filament - cnt += 1 - - r_slicer = self.remaining_time_by_slicer or 0 - if r_slicer > 0: - remaining += r_slicer - cnt += 1 - - if cnt == 0: - return None - - return remaining // cnt - @property def print_progress_by_fileposition_relative(self) -> Optional[float]: """ @@ -157,23 +179,7 @@ def print_progress_by_fileposition_relative(self) -> Optional[float]: return self.virtual_sdcard.progress if self.virtual_sdcard else None - @property - def remaining_time_formatted(self) -> Optional[str]: - sec = self.remaining_time_avg - if sec: - return str(timedelta(seconds=sec))[:-3] # remove the seconds part - @property - def eta(self) -> Optional[datetime]: - remaining = self.remaining_time_avg - if remaining: - now = datetime.now() - return now + timedelta(seconds=remaining) - - @property - def eta_seconds_utc(self) -> Optional[int]: - return int(self.eta.astimezone( - tz.UTC).timestamp()) if self.eta else None @property diff --git a/mobileraker/mobileraker_companion.py b/mobileraker/mobileraker_companion.py index b97fa9f..dca6501 100644 --- a/mobileraker/mobileraker_companion.py +++ b/mobileraker/mobileraker_companion.py @@ -212,7 +212,7 @@ def _fulfills_evaluation_threshold(self, snapshot: PrinterSnapshot) -> bool: return True - if self._last_snapshot.eta is None and snapshot.eta is not None: + if not self._last_snapshot.eta_available and snapshot.eta_available: self._logger.info('ETA is available. Evaluating!') return True @@ -459,13 +459,17 @@ def _live_activity_update(self, cfg: DeviceNotificationEntry, cur_snap: PrinterS # Calculate the eta delta based on the estimated time of the current file or 15 minutes (whichever is higher) eta_delta = max(15, cur_snap.eta_window) if cur_snap.eta_window is not None else 15 - etaUpdate = self._last_snapshot is not None and \ - self._last_snapshot.eta is not None and cur_snap.eta is not None and \ - abs((self._last_snapshot.eta - cur_snap.eta).seconds) > eta_delta + last_remaining_time = self._last_snapshot.remaining_time_avg(cfg.settings.eta_sources) if self._last_snapshot is not None else None + cur_remaining_time = cur_snap.remaining_time_avg(cfg.settings.eta_sources) + + + eta_update = last_remaining_time is not None and cur_remaining_time is not None and \ + abs(last_remaining_time - cur_remaining_time) > eta_delta # The live activity can be updted more frequent. Max however in 5 percent steps or if there was a state change - if not normalized_progress_interval_reached(cfg.snap.progress_live_activity, cur_snap.progress, self.remote_config.increments) and cfg.snap.state == cur_snap.print_state and not etaUpdate: + if not normalized_progress_interval_reached(cfg.snap.progress_live_activity, cur_snap.progress, self.remote_config.increments) and cfg.snap.state == cur_snap.print_state and not eta_update: return None + self._logger.info( 'LiveActivityUpdate passed' @@ -474,7 +478,7 @@ def _live_activity_update(self, cfg: DeviceNotificationEntry, cur_snap: PrinterS # Set the last apns message time to now self._last_apns_message = time.monotonic_ns() - return LiveActivityContentDto(cfg.apns.liveActivity, cur_snap.progress, cur_snap.eta_seconds_utc, "update" if cur_snap.print_state in [ + return LiveActivityContentDto(cfg.apns.liveActivity, cur_snap.progress, cur_snap.calc_eta_seconds_utc(cfg.settings.eta_sources), "update" if cur_snap.print_state in [ "printing", "paused"] else "end") def _custom_notification(self, cfg: DeviceNotificationEntry, cur_snap: PrinterSnapshot, is_m117: bool) -> Optional[NotificationContentDto]: diff --git a/mobileraker/util/notification_placeholders.py b/mobileraker/util/notification_placeholders.py index 64af747..74dc070 100644 --- a/mobileraker/util/notification_placeholders.py +++ b/mobileraker/util/notification_placeholders.py @@ -19,11 +19,14 @@ def replace_placeholders(input: str, cfg: DeviceNotificationEntry, snap: Printer Returns: str: The input string with placeholders replaced by their corresponding values. """ - eta = snap.eta + eta_source = cfg.settings.eta_sources + + eta = snap.calc_eta(eta_source) if eta is not None: eta = eta.astimezone(companion_config.timezone) progress = snap.print_progress_by_fileposition_relative if snap.print_state == 'printing' else None + remaining_time_avg = snap.remaining_time_avg(eta_source) data = { 'printer_name': cfg.machine_name, @@ -31,7 +34,7 @@ def replace_placeholders(input: str, cfg: DeviceNotificationEntry, snap: Printer 'file': snap.filename if snap.filename is not None else 'UNKNOWN', 'eta': eta_formatted(eta, companion_config.eta_format), 'a_eta': adaptive_eta_formatted(eta, companion_config.eta_format), - 'remaining_avg': snap.remaining_time_avg if snap.remaining_time_avg else '--:--', + 'remaining_avg': remaining_time_avg if remaining_time_avg else '--:--', 'remaining_file': snap.remaining_time_by_file if snap.remaining_time_by_file else '--:--', 'remaining_filament': snap.remaining_time_by_filament if snap.remaining_time_by_filament else '--:--', 'remaining_slicer': snap.remaining_time_by_slicer if snap.remaining_time_by_slicer else '--:--',