Add CloudOutputDeviceManager, test implementation

This commit is contained in:
ChrisTerBeke 2018-11-19 21:59:57 +01:00
parent 115936c46b
commit 228325eb89
No known key found for this signature in database
GPG key ID: A49F1AB9D7E0C263
5 changed files with 78 additions and 17 deletions

View file

@ -17,6 +17,7 @@ from enum import IntEnum
import os # To get the username import os # To get the username
import gzip import gzip
class AuthState(IntEnum): class AuthState(IntEnum):
NotAuthenticated = 1 NotAuthenticated = 1
AuthenticationRequested = 2 AuthenticationRequested = 2

View file

@ -1,11 +1,15 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from .src import DiscoverUM3Action from .src import DiscoverUM3Action
from .src import UM3OutputDevicePlugin from .src import UM3OutputDevicePlugin
def getMetaData(): def getMetaData():
return {} return {}
def register(app): def register(app):
return { "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()} return {
"output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(app),
"machine_action": DiscoverUM3Action.DiscoverUM3Action()
}

View file

@ -3,13 +3,14 @@
import json import json
from typing import List, Optional, Dict from typing import List, Optional, Dict
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QUrl
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from UM import i18nCatalog from UM import i18nCatalog
from UM.FileHandler.FileHandler import FileHandler from UM.FileHandler.FileHandler import FileHandler
from UM.Logger import Logger from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputController import CloudOutputController
@ -37,19 +38,34 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# Signal triggered when the print jobs in the queue were changed. # Signal triggered when the print jobs in the queue were changed.
printJobsChanged = pyqtSignal() printJobsChanged = pyqtSignal()
def __init__(self, device_id: str, address: str, properties: Dict[bytes, bytes], parent: QObject = None): def __init__(self, device_id: str, parent: QObject = None):
super().__init__(device_id = device_id, address = address, properties = properties, parent = parent) super().__init__(device_id = device_id, address = "", properties = {}, parent = parent)
self._setInterfaceElements() self._setInterfaceElements()
# The API prefix is automatically added when doing any HTTP call on the device. self._device_id = device_id
self._api_prefix = self.API_ROOT_PATH_FORMAT.format(device_id) # TODO: verify we can use device_id here self._account = CuraApplication.getInstance().getCuraAPI().account
self._authentication_state = AuthState.Authenticated # TODO: use cura.API.Account to set this?
# Properties to populate later on with received cloud data. # Properties to populate later on with received cloud data.
self._printers = [] self._printers = []
self._print_jobs = [] self._print_jobs = []
self._number_of_extruders = 2 # All networked printers are dual-extrusion Ultimaker machines. self._number_of_extruders = 2 # All networked printers are dual-extrusion Ultimaker machines.
## We need to override _createEmptyRequest to work for the cloud.
def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
url = QUrl(self.API_ROOT_PATH_FORMAT.format(cluster_id = self._device_id) + path)
request = QNetworkRequest(url)
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
if not self._account.isLoggedIn:
# TODO: show message to user to sign in
self.setAuthenticationState(AuthState.NotAuthenticated)
else:
self.setAuthenticationState(AuthState.Authenticated)
request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode())
return request
## Set all the interface elements and texts for this output device. ## Set all the interface elements and texts for this output device.
def _setInterfaceElements(self): def _setInterfaceElements(self):
self.setPriority(3) self.setPriority(3)
@ -90,7 +106,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
if status_code != 200: if status_code != 200:
Logger.log("w", "Got unexpected response while trying to get cloud cluster data: {}, {}" Logger.log("w", "Got unexpected response while trying to get cloud cluster data: {}, {}"
.format(status_code, reply.getErrorString())) .format(status_code, reply.readAll()))
return return
data = self._parseStatusResponse(reply) data = self._parseStatusResponse(reply)

View file

@ -0,0 +1,31 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputDevice import CloudOutputDevice
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
## The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
# Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
class CloudOutputDeviceManager:
def __init__(self, application: "CuraApplication"):
self._output_device_manager = application.getOutputDeviceManager()
self._account = application.getCuraAPI().account
self._getRemoteClusters()
# For testing:
application.globalContainerStackChanged.connect(self._addCloudOutputDevice)
def _getRemoteClusters(self):
# TODO: get list of remote clusters and create an output device for each.
pass
def _addCloudOutputDevice(self):
device = CloudOutputDevice("xxxx-xxxx-xxxx-xxxx")
self._output_device_manager.addOutputDevice(device)
device.connect()

View file

@ -1,11 +1,12 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.Logger import Logger from UM.Logger import Logger
from UM.Application import Application
from UM.Signal import Signal, signalemitter from UM.Signal import Signal, signalemitter
from UM.Version import Version from UM.Version import Version
from plugins.UM3NetworkPrinting.src.Cloud.CloudOutputDeviceManager import CloudOutputDeviceManager
from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
@ -19,6 +20,9 @@ from time import time
import json import json
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
## This plugin handles the connection detection & creation of output device objects for the UM3 printer. ## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
# Zero-Conf is used to detect printers, which are saved in a dict. # Zero-Conf is used to detect printers, which are saved in a dict.
@ -29,8 +33,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
removeDeviceSignal = Signal() removeDeviceSignal = Signal()
discoveredDevicesChanged = Signal() discoveredDevicesChanged = Signal()
def __init__(self): def __init__(self, application: "CuraApplication"):
super().__init__() super().__init__()
self._application = application
self._zero_conf = None self._zero_conf = None
self._zero_conf_browser = None self._zero_conf_browser = None
@ -38,7 +44,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self.addDeviceSignal.connect(self._onAddDevice) self.addDeviceSignal.connect(self._onAddDevice)
self.removeDeviceSignal.connect(self._onRemoveDevice) self.removeDeviceSignal.connect(self._onRemoveDevice)
Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections) application.globalContainerStackChanged.connect(self.reCheckConnections)
self._discovered_devices = {} self._discovered_devices = {}
@ -53,7 +59,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/" self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
# Get list of manual instances from preferences # Get list of manual instances from preferences
self._preferences = Application.getInstance().getPreferences() self._preferences = self._application.getPreferences()
self._preferences.addPreference("um3networkprinting/manual_instances", self._preferences.addPreference("um3networkprinting/manual_instances",
"") # A comma-separated list of ip adresses or hostnames "") # A comma-separated list of ip adresses or hostnames
@ -70,6 +76,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_event = Event() self._service_changed_request_event = Event()
self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True) self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True)
self._service_changed_request_thread.start() self._service_changed_request_thread.start()
# Create a cloud output device manager that abstract all cloud connection logic away.
self._cloud_output_device_manager = CloudOutputDeviceManager(self._application)
def getDiscoveredDevices(self): def getDiscoveredDevices(self):
return self._discovered_devices return self._discovered_devices
@ -104,7 +113,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self.resetLastManualDevice() self.resetLastManualDevice()
def reCheckConnections(self): def reCheckConnections(self):
active_machine = Application.getInstance().getGlobalContainerStack() active_machine = self._application.getGlobalContainerStack()
if not active_machine: if not active_machine:
return return
@ -129,7 +138,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
return return
if self._discovered_devices[key].isConnected(): if self._discovered_devices[key].isConnected():
# Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine # Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine
um_network_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") um_network_key = self._application.getGlobalContainerStack().getMetaDataEntry("um_network_key")
if key == um_network_key: if key == um_network_key:
self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key]) self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
else: else:
@ -281,7 +290,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._discovered_devices[device.getId()] = device self._discovered_devices[device.getId()] = device
self.discoveredDevicesChanged.emit() self.discoveredDevicesChanged.emit()
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = self._application.getGlobalContainerStack()
if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"):
device.connect() device.connect()
device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged) device.connectionStateChanged.connect(self._onDeviceConnectionStateChanged)
@ -299,7 +308,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_event.wait(timeout = 5.0) self._service_changed_request_event.wait(timeout = 5.0)
# Stop if the application is shutting down # Stop if the application is shutting down
if Application.getInstance().isShuttingDown(): if self._application.isShuttingDown():
return return
self._service_changed_request_event.clear() self._service_changed_request_event.clear()