Merge pull request #6170 from Ultimaker/CS-234_network_plugin_improvements_part_2

CS-234: Network plugin improvements
This commit is contained in:
ChrisTerBeke 2019-08-07 09:23:20 +02:00 committed by GitHub
commit 70d7bcd412
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 39 deletions

View File

@ -35,6 +35,9 @@ class CloudApiClient:
CLUSTER_API_ROOT = "{}/connect/v1".format(ROOT_PATH)
CURA_API_ROOT = "{}/cura/v1".format(ROOT_PATH)
# In order to avoid garbage collection we keep the callbacks in this list.
_anti_gc_callbacks = [] # type: List[Callable[[], None]]
## Initializes a new cloud API client.
# \param account: The user's account object
# \param on_error: The callback to be called whenever we receive errors from the server.
@ -44,8 +47,6 @@ class CloudApiClient:
self._account = account
self._on_error = on_error
self._upload = None # type: Optional[ToolPathUploader]
# In order to avoid garbage collection we keep the callbacks in this list.
self._anti_gc_callbacks = [] # type: List[Callable[[], None]]
## Gets the account used for the API.
@property

View File

@ -0,0 +1,33 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM import i18nCatalog
from UM.Message import Message
I18N_CATALOG = i18nCatalog("cura")
## Message shown when trying to connect to a legacy printer device.
class LegacyDeviceNoLongerSupportedMessage(Message):
# Singleton used to prevent duplicate messages of this type at the same time.
__is_visible = False
def __init__(self) -> None:
super().__init__(
text = I18N_CATALOG.i18nc("@info:status", "You are attempting to connect to a printer that is not "
"running Ultimaker Connect. Please update the printer to the "
"latest firmware."),
title = I18N_CATALOG.i18nc("@info:title", "Update your printer"),
lifetime = 10
)
def show(self) -> None:
if LegacyDeviceNoLongerSupportedMessage.__is_visible:
return
super().show()
LegacyDeviceNoLongerSupportedMessage.__is_visible = True
def hide(self, send_signal = True) -> None:
super().hide(send_signal)
LegacyDeviceNoLongerSupportedMessage.__is_visible = False

View File

@ -0,0 +1,33 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM import i18nCatalog
from UM.Message import Message
I18N_CATALOG = i18nCatalog("cura")
## Message shown when trying to connect to a printer that is not a host.
class NotClusterHostMessage(Message):
# Singleton used to prevent duplicate messages of this type at the same time.
__is_visible = False
def __init__(self) -> None:
super().__init__(
text = I18N_CATALOG.i18nc("@info:status", "You are attempting to connect to a printer that is not "
"the host of an Ultimaker Connect group. Please connect to "
"the host instead."),
title = I18N_CATALOG.i18nc("@info:title", "Not a cluster host"),
lifetime = 10
)
def show(self) -> None:
if NotClusterHostMessage.__is_visible:
return
super().show()
NotClusterHostMessage.__is_visible = True
def hide(self, send_signal = True) -> None:
super().hide(send_signal)
NotClusterHostMessage.__is_visible = False

View File

@ -25,6 +25,9 @@ class ClusterApiClient:
PRINTER_API_PREFIX = "/api/v1"
CLUSTER_API_PREFIX = "/cluster-api/v1"
# In order to avoid garbage collection we keep the callbacks in this list.
_anti_gc_callbacks = [] # type: List[Callable[[], None]]
## Initializes a new cluster API client.
# \param address: The network address of the cluster to call.
# \param on_error: The callback to be called whenever we receive errors from the server.
@ -33,8 +36,6 @@ class ClusterApiClient:
self._manager = QNetworkAccessManager()
self._address = address
self._on_error = on_error
# In order to avoid garbage collection we keep the callbacks in this list.
self._anti_gc_callbacks = [] # type: List[Callable[[], None]]
## Get printer system information.
# \param on_finished: The callback in case the response is successful.

View File

@ -1,19 +1,22 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Dict, Optional, Callable
from typing import Dict, Optional, Callable, List
from UM import i18nCatalog
from UM.Logger import Logger
from UM.Signal import Signal
from UM.Version import Version
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
from cura.Settings.GlobalStack import GlobalStack
from .ZeroConfClient import ZeroConfClient
from .ClusterApiClient import ClusterApiClient
from .LocalClusterOutputDevice import LocalClusterOutputDevice
from ..UltimakerNetworkedPrinterOutputDevice import UltimakerNetworkedPrinterOutputDevice
from ..CloudFlowMessage import CloudFlowMessage
from ..Messages.LegacyDeviceNoLongerSupportedMessage import LegacyDeviceNoLongerSupportedMessage
from ..Messages.NotClusterHostMessage import NotClusterHostMessage
from ..Models.Http.PrinterSystemStatus import PrinterSystemStatus
@ -45,15 +48,10 @@ class LocalClusterOutputDeviceManager:
self._zero_conf_client.addedNetworkCluster.connect(self._onDeviceDiscovered)
self._zero_conf_client.removedNetworkCluster.connect(self._onDiscoveredDeviceRemoved)
# Persistent dict containing manually connected clusters.
self._manual_instances = {} # type: Dict[str, Optional[Callable]]
## Start the network discovery.
def start(self) -> None:
self._zero_conf_client.start()
# Load all manual devices.
self._manual_instances = self._getStoredManualInstances()
for address in self._manual_instances:
for address in self._getStoredManualAddresses():
self.addManualDevice(address)
## Stop network discovery and clean up discovered devices.
@ -65,11 +63,8 @@ class LocalClusterOutputDeviceManager:
## Add a networked printer manually by address.
def addManualDevice(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None:
self._manual_instances[address] = callback
new_manual_devices = ",".join(self._manual_instances.keys())
CuraApplication.getInstance().getPreferences().setValue(self.MANUAL_DEVICES_PREFERENCE_KEY, new_manual_devices)
api_client = ClusterApiClient(address, lambda error: print(error))
api_client.getSystem(lambda status: self._onCheckManualDeviceResponse(address, status))
api_client.getSystem(lambda status: self._onCheckManualDeviceResponse(address, status, callback))
## Remove a manually added networked printer.
def removeManualDevice(self, device_id: str, address: Optional[str] = None) -> None:
@ -80,19 +75,15 @@ class LocalClusterOutputDeviceManager:
address = address or self._discovered_devices[device_id].ipAddress
self._onDiscoveredDeviceRemoved(device_id)
if address in self._manual_instances:
manual_instance_callback = self._manual_instances.pop(address)
new_devices = ",".join(self._manual_instances.keys())
CuraApplication.getInstance().getPreferences().setValue(self.MANUAL_DEVICES_PREFERENCE_KEY, new_devices)
if manual_instance_callback:
CuraApplication.getInstance().callLater(manual_instance_callback, False, address)
if address in self._getStoredManualAddresses():
self._removeStoredManualAddress(address)
## Force reset all network device connections.
def refreshConnections(self):
def refreshConnections(self) -> None:
self._connectToActiveMachine()
## Callback for when the active machine was changed by the user or a new remote cluster was found.
def _connectToActiveMachine(self):
def _connectToActiveMachine(self) -> None:
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
if not active_machine:
return
@ -108,10 +99,8 @@ class LocalClusterOutputDeviceManager:
CuraApplication.getInstance().getOutputDeviceManager().removeOutputDevice(device.key)
## Callback for when a manual device check request was responded to.
def _onCheckManualDeviceResponse(self, address: str, status: PrinterSystemStatus) -> None:
callback = self._manual_instances.get(address, None)
if callback is None:
return
def _onCheckManualDeviceResponse(self, address: str, status: PrinterSystemStatus,
callback: Optional[Callable[[bool, str], None]] = None) -> None:
self._onDeviceDiscovered("manual:{}".format(address), address, {
b"name": status.name.encode("utf-8"),
b"address": address.encode("utf-8"),
@ -120,6 +109,8 @@ class LocalClusterOutputDeviceManager:
b"firmware_version": status.firmware.encode("utf-8"),
b"cluster_size": b"1"
})
self._storeManualAddress(address)
if callback is not None:
CuraApplication.getInstance().callLater(callback, True, address)
## Returns a dict of printer BOM numbers to machine types.
@ -138,7 +129,6 @@ class LocalClusterOutputDeviceManager:
## Add a new device.
def _onDeviceDiscovered(self, key: str, address: str, properties: Dict[bytes, bytes]) -> None:
cluster_size = int(properties.get(b"cluster_size", -1))
machine_identifier = properties.get(b"machine", b"").decode("utf-8")
printer_type_identifiers = self._getPrinterTypeIdentifiers()
@ -149,10 +139,6 @@ class LocalClusterOutputDeviceManager:
properties[b"printer_type"] = bytes(p_type, encoding="utf8")
break
# We no longer support legacy devices, so check that here.
if cluster_size == -1:
return
device = LocalClusterOutputDevice(key, address, properties)
CuraApplication.getInstance().getDiscoveredPrintersModel().addDiscoveredPrinter(
ip_address=address,
@ -191,16 +177,45 @@ class LocalClusterOutputDeviceManager:
self._connectToOutputDevice(device, active_machine)
CloudFlowMessage(device.ipAddress).show() # Nudge the user to start using Ultimaker Cloud.
## Add an address to the stored preferences.
def _storeManualAddress(self, address: str) -> None:
stored_addresses = self._getStoredManualAddresses()
if address in stored_addresses:
return # Prevent duplicates.
stored_addresses.append(address)
new_value = ",".join(stored_addresses)
CuraApplication.getInstance().getPreferences().setValue(self.MANUAL_DEVICES_PREFERENCE_KEY, new_value)
## Remove an address from the stored preferences.
def _removeStoredManualAddress(self, address: str) -> None:
stored_addresses = self._getStoredManualAddresses()
try:
stored_addresses.remove(address) # Can throw a ValueError
new_value = ",".join(stored_addresses)
CuraApplication.getInstance().getPreferences().setValue(self.MANUAL_DEVICES_PREFERENCE_KEY, new_value)
except ValueError:
Logger.log("w", "Could not remove address from stored_addresses, it was not there")
## Load the user-configured manual devices from Cura preferences.
def _getStoredManualInstances(self) -> Dict[str, Optional[Callable]]:
def _getStoredManualAddresses(self) -> List[str]:
preferences = CuraApplication.getInstance().getPreferences()
preferences.addPreference(self.MANUAL_DEVICES_PREFERENCE_KEY, "")
manual_instances = preferences.getValue(self.MANUAL_DEVICES_PREFERENCE_KEY).split(",")
return {address: None for address in manual_instances}
return manual_instances
## Add a device to the current active machine.
@staticmethod
def _connectToOutputDevice(device: PrinterOutputDevice, active_machine: GlobalStack) -> None:
def _connectToOutputDevice(self, device: UltimakerNetworkedPrinterOutputDevice, machine: GlobalStack) -> None:
# Make sure users know that we no longer support legacy devices.
if Version(device.firmwareVersion) < self.MIN_SUPPORTED_CLUSTER_VERSION:
LegacyDeviceNoLongerSupportedMessage().show()
return
# Tell the user that they cannot connect to a non-host printer.
if device.clusterSize < 1:
NotClusterHostMessage().show()
return
device.connect()
active_machine.addConfiguredConnectionType(device.connectionType.value)
machine.addConfiguredConnectionType(device.connectionType.value)
CuraApplication.getInstance().getOutputDeviceManager().addOutputDevice(device)