Browse Source

Added stubs for cluster & legacy output devices

CL-541
Jaime van Kessel 7 years ago
parent
commit
9202bb11fe

+ 5 - 0
plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py

@@ -0,0 +1,5 @@
+from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
+
+class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
+    def __init__(self, device_id, address, properties, parent = None):
+        super().__init__(device_id = device_id, parent = parent)

+ 4 - 4
plugins/UM3NetworkPrinting/DiscoverUM3Action.py

@@ -37,7 +37,7 @@ class DiscoverUM3Action(MachineAction):
         if not self._network_plugin:
             Logger.log("d", "Starting printer discovery.")
             self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
-            self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged)
+            self._network_plugin.discoveredDevicesChanged.connect(self._onPrinterDiscoveryChanged)
             self.printersChanged.emit()
 
     ##  Re-filters the list of printers.
@@ -87,10 +87,10 @@ class DiscoverUM3Action(MachineAction):
             else:
                 global_printer_type = "unknown"
 
-            printers = list(self._network_plugin.getPrinters().values())
+            printers = list(self._network_plugin.getDiscoveredDevices().values())
             # TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet.
-            printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"]
-            printers.sort(key = lambda k: k.name)
+            #printers = [printer for printer in printers if printer.printerType == global_printer_type or printer.printerType == "unknown"]
+            #printers.sort(key = lambda k: k.name)
             return printers
         else:
             return []

+ 5 - 0
plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py

@@ -0,0 +1,5 @@
+from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
+
+class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
+    def __init__(self, device_id, address, properties, parent = None):
+        super().__init__(device_id = device_id, parent = parent)

+ 185 - 0
plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py

@@ -0,0 +1,185 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
+from UM.Logger import Logger
+from UM.Application import Application
+from UM.Signal import Signal, signalemitter
+
+from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
+from queue import Queue
+from threading import Event, Thread
+
+from time import time
+
+from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice
+
+##      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.
+#       If we discover a printer that has the same key as the active machine instance a connection is made.
+@signalemitter
+class UM3OutputDevicePlugin(OutputDevicePlugin):
+    addDeviceSignal = Signal()
+    removeDeviceSignal = Signal()
+    discoveredDevicesChanged = Signal()
+
+    def __init__(self):
+        super().__init__()
+        self._zero_conf = None
+        self._zero_conf_browser = None
+
+        # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
+        self.addDeviceSignal.connect(self._onAddDevice)
+        self.removeDeviceSignal.connect(self._onRemoveDevice)
+
+        self._discovered_devices = {}
+
+        # The zero-conf service changed requests are handled in a separate thread, so we can re-schedule the requests
+        # which fail to get detailed service info.
+        # Any new or re-scheduled requests will be appended to the request queue, and the handling thread will pick
+        # them up and process them.
+        self._service_changed_request_queue = Queue()
+        self._service_changed_request_event = Event()
+        self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True)
+        self._service_changed_request_thread.start()
+
+    def getDiscoveredDevices(self):
+        return self._discovered_devices
+
+    ##  Start looking for devices on network.
+    def start(self):
+        self.startDiscovery()
+
+    def startDiscovery(self):
+        self.stop()
+        if self._zero_conf_browser:
+            self._zero_conf_browser.cancel()
+            self._zero_conf_browser = None  # Force the old ServiceBrowser to be destroyed.
+
+        self._zero_conf = Zeroconf()
+        self._zero_conf_browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.',
+                                                 [self._appendServiceChangedRequest])
+
+    def stop(self):
+        if self._zero_conf is not None:
+            Logger.log("d", "zeroconf close...")
+            self._zero_conf.close()
+
+    def _onRemoveDevice(self, name):
+        device = self._discovered_devices.pop(name, None)
+        if device:
+            if device.isConnected():
+                device.disconnect()
+                device.connectionStateChanged.disconnect(self._onDeviceConnectionStateChanged)
+
+            self.discoveredDevicesChanged.emit()
+        '''printer = self._printers.pop(name, None)
+        if printer:
+            if printer.isConnected():
+                printer.disconnect()
+                printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
+                Logger.log("d", "removePrinter, disconnecting [%s]..." % name)
+        self.printerListChanged.emit()'''
+
+    def _onAddDevice(self, name, address, properties):
+
+        # Check what kind of device we need to add; Depending on the firmware we either add a "Connect"/"Cluster"
+        # or "Legacy" UM3 device.
+        cluster_size = int(properties.get(b"cluster_size", -1))
+        if cluster_size > 0:
+            device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
+        else:
+            device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties)
+
+        self._discovered_devices[device.getId()] = device
+        self.discoveredDevicesChanged.emit()
+
+        pass
+        '''
+        self._cluster_printers_seen[
+        printer.getKey()] = name  # Cluster printers that may be temporary unreachable or is rebooted keep being stored here
+        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
+        if printer.getKey() not in self._old_printers:  # Was the printer already connected, but a re-scan forced?
+            Logger.log("d", "addPrinter, connecting [%s]..." % printer.getKey())
+            self._printers[printer.getKey()].connect()
+            printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
+        self.printerListChanged.emit()'''
+
+    ##  Appends a service changed request so later the handling thread will pick it up and processes it.
+    def _appendServiceChangedRequest(self, zeroconf, service_type, name, state_change):
+        # append the request and set the event so the event handling thread can pick it up
+        item = (zeroconf, service_type, name, state_change)
+        self._service_changed_request_queue.put(item)
+        self._service_changed_request_event.set()
+
+    def _handleOnServiceChangedRequests(self):
+        while True:
+            # Wait for the event to be set
+            self._service_changed_request_event.wait(timeout = 5.0)
+
+            # Stop if the application is shutting down
+            if Application.getInstance().isShuttingDown():
+                return
+
+            self._service_changed_request_event.clear()
+
+            # Handle all pending requests
+            reschedule_requests = []  # A list of requests that have failed so later they will get re-scheduled
+            while not self._service_changed_request_queue.empty():
+                request = self._service_changed_request_queue.get()
+                zeroconf, service_type, name, state_change = request
+                try:
+                    result = self._onServiceChanged(zeroconf, service_type, name, state_change)
+                    if not result:
+                        reschedule_requests.append(request)
+                except Exception:
+                    Logger.logException("e", "Failed to get service info for [%s] [%s], the request will be rescheduled",
+                                        service_type, name)
+                    reschedule_requests.append(request)
+
+            # Re-schedule the failed requests if any
+            if reschedule_requests:
+                for request in reschedule_requests:
+                    self._service_changed_request_queue.put(request)
+
+    ##  Handler for zeroConf detection.
+    #   Return True or False indicating if the process succeeded.
+    #   Note that this function can take over 3 seconds to complete. Be carefull calling it from the main thread.
+    def _onServiceChanged(self, zero_conf, service_type, name, state_change):
+        if state_change == ServiceStateChange.Added:
+            Logger.log("d", "Bonjour service added: %s" % name)
+
+            # First try getting info from zero-conf cache
+            info = ServiceInfo(service_type, name, properties={})
+            for record in zero_conf.cache.entries_with_name(name.lower()):
+                info.update_record(zero_conf, time(), record)
+
+            for record in zero_conf.cache.entries_with_name(info.server):
+                info.update_record(zero_conf, time(), record)
+                if info.address:
+                    break
+
+            # Request more data if info is not complete
+            if not info.address:
+                Logger.log("d", "Trying to get address of %s", name)
+                info = zero_conf.get_service_info(service_type, name)
+
+            if info:
+                type_of_device = info.properties.get(b"type", None)
+                if type_of_device:
+                    if type_of_device == b"printer":
+                        address = '.'.join(map(lambda n: str(n), info.address))
+                        self.addDeviceSignal.emit(str(name), address, info.properties)
+                    else:
+                        Logger.log("w",
+                                   "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device)
+            else:
+                Logger.log("w", "Could not get information about %s" % name)
+                return False
+
+        elif state_change == ServiceStateChange.Removed:
+            Logger.log("d", "Bonjour service removed: %s" % name)
+            self.removeDeviceSignal.emit(str(name))
+
+        return True

+ 0 - 2
plugins/UM3NetworkPrinting/UM3PrinterOutputDevicePlugin.py

@@ -1,2 +0,0 @@
-# Copyright (c) 2017 Ultimaker B.V.
-# Cura is released under the terms of the LGPLv3 or higher.

+ 3 - 1
plugins/UM3NetworkPrinting/__init__.py

@@ -5,8 +5,10 @@ from . import DiscoverUM3Action
 from UM.i18n import i18nCatalog
 catalog = i18nCatalog("cura")
 
+from . import UM3OutputDevicePlugin
+
 def getMetaData():
     return {}
 
 def register(app):
-    return { "output_device": NetworkPrinterOutputDevicePlugin.NetworkPrinterOutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}
+    return { "output_device": UM3OutputDevicePlugin.UM3OutputDevicePlugin(), "machine_action": DiscoverUM3Action.DiscoverUM3Action()}