USBPrinterManager.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.Signal import Signal, SignalEmitter
  4. from . import PrinterConnection
  5. from UM.Application import Application
  6. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  7. from UM.Scene.SceneNode import SceneNode
  8. from UM.Resources import Resources
  9. from UM.Logger import Logger
  10. from UM.PluginRegistry import PluginRegistry
  11. from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
  12. from UM.Qt.ListModel import ListModel
  13. from cura.CuraApplication import CuraApplication
  14. import threading
  15. import platform
  16. import glob
  17. import time
  18. import os
  19. import os.path
  20. import sys
  21. from UM.Extension import Extension
  22. from PyQt5.QtQuick import QQuickView
  23. from PyQt5.QtQml import QQmlComponent, QQmlContext
  24. from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal, Qt
  25. from UM.i18n import i18nCatalog
  26. i18n_catalog = i18nCatalog("cura")
  27. class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
  28. def __init__(self, parent = None):
  29. QObject.__init__(self, parent)
  30. SignalEmitter.__init__(self)
  31. OutputDevicePlugin.__init__(self)
  32. Extension.__init__(self)
  33. self._serial_port_list = []
  34. self._printer_connections = {}
  35. self._printer_connections_model = None
  36. self._update_thread = threading.Thread(target = self._updateThread)
  37. self._update_thread.setDaemon(True)
  38. self._check_updates = True
  39. self._firmware_view = None
  40. ## Add menu item to top menu of the application.
  41. self.setMenuName(i18n_catalog.i18nc("@title:menu","Firmware"))
  42. self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Update Firmware"), self.updateAllFirmware)
  43. Application.getInstance().applicationShuttingDown.connect(self.stop)
  44. self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
  45. addConnectionSignal = Signal()
  46. printerConnectionStateChanged = pyqtSignal()
  47. def start(self):
  48. self._check_updates = True
  49. self._update_thread.start()
  50. def stop(self):
  51. self._check_updates = False
  52. try:
  53. self._update_thread.join()
  54. except RuntimeError:
  55. pass
  56. def _updateThread(self):
  57. while self._check_updates:
  58. result = self.getSerialPortList(only_list_usb = True)
  59. self._addRemovePorts(result)
  60. time.sleep(5)
  61. ## Show firmware interface.
  62. # This will create the view if its not already created.
  63. def spawnFirmwareInterface(self, serial_port):
  64. if self._firmware_view is None:
  65. path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml"))
  66. component = QQmlComponent(Application.getInstance()._engine, path)
  67. self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
  68. self._firmware_context.setContextProperty("manager", self)
  69. self._firmware_view = component.create(self._firmware_context)
  70. self._firmware_view.show()
  71. def updateAllFirmware(self):
  72. self.spawnFirmwareInterface("")
  73. for printer_connection in self._printer_connections:
  74. try:
  75. self._printer_connections[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
  76. except FileNotFoundError:
  77. continue
  78. @pyqtSlot(str, result = bool)
  79. def updateFirmwareBySerial(self, serial_port):
  80. if serial_port in self._printer_connections:
  81. self.spawnFirmwareInterface(self._printer_connections[serial_port].getSerialPort())
  82. try:
  83. self._printer_connections[serial_port].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
  84. except FileNotFoundError:
  85. self._firmware_view.close()
  86. Logger.log("e", "Could not find firmware required for this machine")
  87. return False
  88. return True
  89. return False
  90. ## Return the singleton instance of the USBPrinterManager
  91. @classmethod
  92. def getInstance(cls, engine = None, script_engine = None):
  93. # Note: Explicit use of class name to prevent issues with inheritance.
  94. if USBPrinterManager._instance is None:
  95. USBPrinterManager._instance = cls()
  96. return USBPrinterManager._instance
  97. def _getDefaultFirmwareName(self):
  98. machine_type = Application.getInstance().getMachineManager().getActiveMachineInstance().getMachineDefinition().getId()
  99. firmware_name = ""
  100. baudrate = 250000
  101. if sys.platform.startswith("linux"):
  102. baudrate = 115200
  103. if machine_type == "ultimaker_original":
  104. firmware_name = "MarlinUltimaker"
  105. firmware_name += "-%d" % (baudrate)
  106. elif machine_type == "ultimaker_original_plus":
  107. firmware_name = "MarlinUltimaker-UMOP-%d" % (baudrate)
  108. elif machine_type == "Witbox":
  109. return "MarlinWitbox.hex"
  110. elif machine_type == "ultimaker2go":
  111. return "MarlinUltimaker2go.hex"
  112. elif machine_type == "ultimaker2extended":
  113. return "MarlinUltimaker2extended.hex"
  114. elif machine_type == "ultimaker2":
  115. return "MarlinUltimaker2.hex"
  116. ##TODO: Add check for multiple extruders
  117. if firmware_name != "":
  118. firmware_name += ".hex"
  119. return firmware_name
  120. def _addRemovePorts(self, serial_ports):
  121. # First, find and add all new or changed keys
  122. for serial_port in list(serial_ports):
  123. if serial_port not in self._serial_port_list:
  124. self.addConnectionSignal.emit(serial_port) #Hack to ensure its created in main thread
  125. continue
  126. self._serial_port_list = list(serial_ports)
  127. ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
  128. def addConnection(self, serial_port):
  129. connection = PrinterConnection.PrinterConnection(serial_port)
  130. connection.connect()
  131. connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
  132. self._printer_connections[serial_port] = connection
  133. def _onPrinterConnectionStateChanged(self, serial_port):
  134. if self._printer_connections[serial_port].isConnected():
  135. self.getOutputDeviceManager().addOutputDevice(self._printer_connections[serial_port])
  136. else:
  137. self.getOutputDeviceManager().removeOutputDevice(serial_port)
  138. self.printerConnectionStateChanged.emit()
  139. @pyqtProperty(QObject , notify = printerConnectionStateChanged)
  140. def connectedPrinterList(self):
  141. self._printer_connections_model = ListModel()
  142. self._printer_connections_model.addRoleName(Qt.UserRole + 1,"name")
  143. self._printer_connections_model.addRoleName(Qt.UserRole + 2, "printer")
  144. for connection in self._printer_connections:
  145. if self._printer_connections[connection].isConnected():
  146. self._printer_connections_model.appendItem({"name":connection, "printer": self._printer_connections[connection]})
  147. return self._printer_connections_model
  148. ## Create a list of serial ports on the system.
  149. # \param only_list_usb If true, only usb ports are listed
  150. def getSerialPortList(self, only_list_usb = False):
  151. base_list = []
  152. if platform.system() == "Windows":
  153. import winreg
  154. try:
  155. key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
  156. i = 0
  157. while True:
  158. values = winreg.EnumValue(key, i)
  159. if not only_list_usb or "USBSER" in values[0]:
  160. base_list += [values[1]]
  161. i += 1
  162. except Exception as e:
  163. pass
  164. else:
  165. if only_list_usb:
  166. base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.usb*")
  167. base_list = filter(lambda s: "Bluetooth" not in s, base_list) # Filter because mac sometimes puts them in the list
  168. else:
  169. base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/rfcomm*") + glob.glob("/dev/serial/by-id/*")
  170. return list(base_list)
  171. _instance = None