Added stub for AbstractCloudOutputDevice

It doesn't actually allow you to send a print, but it does ask some info from the API

CURA-8463
This commit is contained in:
Jaime van Kessel 2022-08-30 15:48:10 +02:00
parent 38e4ca1e0f
commit 6fed6b824c
No known key found for this signature in database
GPG key ID: C85F7A3AF1BAA7C4
4 changed files with 146 additions and 16 deletions

View file

@ -0,0 +1,87 @@
from time import time
from typing import List
from PyQt6.QtCore import QObject
from PyQt6.QtNetwork import QNetworkReply
from UM import i18nCatalog
from UM.Logger import Logger
from cura.PrinterOutput.NetworkedPrinterOutputDevice import AuthState
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from .CloudApiClient import CloudApiClient
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
I18N_CATALOG = i18nCatalog("cura")
class AbstractCloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
API_CHECK_INTERVAL = 10.0 # seconds
def __init__(self, api_client: CloudApiClient, printer_type: str, parent: QObject = None) -> None:
self._api = api_client
properties = {
#b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"",
# b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"",
##b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
b"printer_type": printer_type.encode()
#b"cluster_size": str(cluster.printer_count).encode() if cluster.printer_count else b"1"
}
super().__init__(
device_id=f"ABSTRACT_{printer_type}",
address="",
connection_type=ConnectionType.CloudConnection,
properties=properties,
parent=parent
)
print("CREATING ABSTRACT CLOUD OUTPUT DEVIIICEEEEEE")
self._setInterfaceElements()
def connect(self) -> None:
"""Connects this device."""
if self.isConnected():
return
Logger.log("i", "Attempting to connect AbstractCloudOutputDevice %s", self.key)
super().connect()
#CuraApplication.getInstance().getBackend().backendStateChange.connect(self._onBackendStateChange)
self._update()
def disconnect(self) -> None:
"""Disconnects the device"""
if not self.isConnected():
return
super().disconnect()
def _update(self) -> None:
"""Called when the network data should be updated."""
super()._update()
if time() - self._time_of_last_request < self.API_CHECK_INTERVAL:
return # avoid calling the cloud too often
self._time_of_last_request = time()
if self._api.account.isLoggedIn:
self.setAuthenticationState(AuthState.Authenticated)
self._last_request_time = time()
self._api.getClustersByMachineType(self.printerType, self._onCompleted, self._onError)
else:
self.setAuthenticationState(AuthState.NotAuthenticated)
def _setInterfaceElements(self) -> None:
"""Set all the interface elements and texts for this output device."""
self.setPriority(2) # Make sure we end up below the local networking and above 'save to file'.
self.setShortDescription(I18N_CATALOG.i18nc("@action:button", "Print via cloud"))
self.setDescription(I18N_CATALOG.i18nc("@properties:tooltip", "Print via cloud"))
self.setConnectionText(I18N_CATALOG.i18nc("@info:status", "Connected via cloud"))
def _onCompleted(self, clusters: List[CloudClusterResponse]) -> None:
self._responseReceived()
# Todo: actually handle the data that we get back!
def _onError(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None:
pass

View file

@ -61,7 +61,6 @@ class CloudApiClient:
@property @property
def account(self) -> Account: def account(self) -> Account:
"""Gets the account used for the API.""" """Gets the account used for the API."""
return self._account return self._account
def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None: def getClusters(self, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None:
@ -77,6 +76,24 @@ class CloudApiClient:
error_callback = failed, error_callback = failed,
timeout = self.DEFAULT_REQUEST_TIMEOUT) timeout = self.DEFAULT_REQUEST_TIMEOUT)
def getClustersByMachineType(self, machine_type, on_finished: Callable[[List[CloudClusterResponse]], Any], failed: Callable) -> None:
# HACK: There is something weird going on with the API, as it reports printer types in formats like
# "ultimaker_s3", but wants "Ultimaker S3" when using the machine_variant filter query. So we need to do some
# conversion!
machine_type = machine_type.replace("_plus", "+")
machine_type = machine_type.replace("_", " ")
machine_type = machine_type.replace("ultimaker", "ultimaker ")
machine_type = machine_type.replace(" ", " ")
machine_type = machine_type.title()
url = f"{self.CLUSTER_API_ROOT}/clusters?machine_variant={machine_type}"
self._http.get(url,
scope=self._scope,
callback=self._parseCallback(on_finished, CloudClusterResponse, failed),
error_callback=failed,
timeout=self.DEFAULT_REQUEST_TIMEOUT)
def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None: def getClusterStatus(self, cluster_id: str, on_finished: Callable[[CloudClusterStatus], Any]) -> None:
"""Retrieves the status of the given cluster. """Retrieves the status of the given cluster.

View file

@ -19,6 +19,7 @@ from cura.Settings.CuraContainerRegistry import CuraContainerRegistry # To upda
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT from cura.UltimakerCloud.UltimakerCloudConstants import META_CAPABILITIES, META_UM_LINKED_TO_ACCOUNT
from .AbstractCloudOutputDevice import AbstractCloudOutputDevice
from .CloudApiClient import CloudApiClient from .CloudApiClient import CloudApiClient
from .CloudOutputDevice import CloudOutputDevice from .CloudOutputDevice import CloudOutputDevice
from ..Messages.RemovedPrintersMessage import RemovedPrintersMessage from ..Messages.RemovedPrintersMessage import RemovedPrintersMessage
@ -49,6 +50,8 @@ class CloudOutputDeviceManager:
# Persistent dict containing the remote clusters for the authenticated user. # Persistent dict containing the remote clusters for the authenticated user.
self._remote_clusters: Dict[str, CloudOutputDevice] = {} self._remote_clusters: Dict[str, CloudOutputDevice] = {}
self._abstract_clusters: Dict[str, AbstractCloudOutputDevice] = {}
# Dictionary containing all the cloud printers loaded in Cura # Dictionary containing all the cloud printers loaded in Cura
self._um_cloud_printers: Dict[str, GlobalStack] = {} self._um_cloud_printers: Dict[str, GlobalStack] = {}
@ -189,6 +192,10 @@ class CloudOutputDeviceManager:
for cluster_data in discovered_clusters: for cluster_data in discovered_clusters:
output_device = CloudOutputDevice(self._api, cluster_data) output_device = CloudOutputDevice(self._api, cluster_data)
if cluster_data.printer_type not in self._abstract_clusters:
self._abstract_clusters[cluster_data.printer_type] = AbstractCloudOutputDevice(self._api, cluster_data.printer_type)
# If the machine already existed before, it will be present in the host_guid_map # If the machine already existed before, it will be present in the host_guid_map
if cluster_data.host_guid in host_guid_map: if cluster_data.host_guid in host_guid_map:
machine = machine_manager.getMachine(output_device.printerType, {self.META_HOST_GUID: cluster_data.host_guid}) machine = machine_manager.getMachine(output_device.printerType, {self.META_HOST_GUID: cluster_data.host_guid})
@ -373,22 +380,33 @@ class CloudOutputDeviceManager:
if not active_machine: if not active_machine:
return return
# Check if we should directly connect with a "normal" CloudOutputDevice or that we should connect to an
# 'abstract' one
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager() output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID) if active_machine.getMetaDataEntry("is_abstract_machine") != "True":
local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY) stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID)
local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY)
# Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously. # Copy of the device list, to prevent modifying the list while iterating, if a device gets added asynchronously.
remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values()) remote_cluster_copy: List[CloudOutputDevice] = list(self._remote_clusters.values())
for device in remote_cluster_copy: for device in remote_cluster_copy:
if device.key == stored_cluster_id: if device.key == stored_cluster_id:
# Connect to it if the stored ID matches. # Connect to it if the stored ID matches.
self._connectToOutputDevice(device, active_machine) self._connectToOutputDevice(device, active_machine)
elif local_network_key and device.matchesNetworkKey(local_network_key): elif local_network_key and device.matchesNetworkKey(local_network_key):
# Connect to it if we can match the local network key that was already present. # Connect to it if we can match the local network key that was already present.
self._connectToOutputDevice(device, active_machine) self._connectToOutputDevice(device, active_machine)
elif device.key in output_device_manager.getOutputDeviceIds(): elif device.key in output_device_manager.getOutputDeviceIds():
# Remove device if it is not meant for the active machine. # Remove device if it is not meant for the active machine.
output_device_manager.removeOutputDevice(device.key) output_device_manager.removeOutputDevice(device.key)
else: # Abstract it is!
remote_abstract_cluster_copy: List[CloudOutputDevice] = list(self._abstract_clusters.values())
for device in remote_abstract_cluster_copy:
if device.printerType == active_machine.definition.getId():
print("Found the device to activate", device)
self._connectToAbstractOutputDevice(device, active_machine)
else:
output_device_manager.removeOutputDevice(device.key)
def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack): def _setOutputDeviceMetadata(self, device: CloudOutputDevice, machine: GlobalStack):
machine.setName(device.name) machine.setName(device.name)
@ -405,6 +423,15 @@ class CloudOutputDeviceManager:
machine.setMetaDataEntry("removal_warning", removal_warning_string) machine.setMetaDataEntry("removal_warning", removal_warning_string)
machine.addConfiguredConnectionType(device.connectionType.value) machine.addConfiguredConnectionType(device.connectionType.value)
def _connectToAbstractOutputDevice(self, device: AbstractCloudOutputDevice, machine: GlobalStack) -> None:
if not device.isConnected():
device.connect()
machine.addConfiguredConnectionType(device.connectionType.value)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if device.key not in output_device_manager.getOutputDeviceIds():
output_device_manager.addOutputDevice(device)
def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None: def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None:
"""Connects to an output device and makes sure it is registered in the output device manager.""" """Connects to an output device and makes sure it is registered in the output device manager."""

View file

@ -8,7 +8,6 @@ from ..BaseModel import BaseModel
class CloudClusterResponse(BaseModel): class CloudClusterResponse(BaseModel):
"""Class representing a cloud connected cluster.""" """Class representing a cloud connected cluster."""
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None, host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1,