USBPrinterManager.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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 UM.Message import Message
  14. from cura.CuraApplication import CuraApplication
  15. import threading
  16. import platform
  17. import glob
  18. import time
  19. import os
  20. import os.path
  21. import sys
  22. from UM.Extension import Extension
  23. from PyQt5.QtQuick import QQuickView
  24. from PyQt5.QtQml import QQmlComponent, QQmlContext
  25. from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal, Qt
  26. from UM.i18n import i18nCatalog
  27. i18n_catalog = i18nCatalog("cura")
  28. class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
  29. def __init__(self, parent = None):
  30. QObject.__init__(self, parent)
  31. SignalEmitter.__init__(self)
  32. OutputDevicePlugin.__init__(self)
  33. Extension.__init__(self)
  34. self._serial_port_list = []
  35. self._printer_connections = {}
  36. self._printer_connections_model = None
  37. self._update_thread = threading.Thread(target = self._updateThread)
  38. self._update_thread.setDaemon(True)
  39. self._check_updates = True
  40. self._firmware_view = None
  41. ## Add menu item to top menu of the application.
  42. self.setMenuName(i18n_catalog.i18nc("@title:menu","Firmware"))
  43. self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Update Firmware"), self.updateAllFirmware)
  44. Application.getInstance().applicationShuttingDown.connect(self.stop)
  45. self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
  46. addConnectionSignal = Signal()
  47. printerConnectionStateChanged = pyqtSignal()
  48. progressChanged = pyqtSignal()
  49. @pyqtProperty(float, notify = progressChanged)
  50. def progress(self):
  51. progress = 0
  52. for name, connection in self._printer_connections.items():
  53. progress += connection.progress
  54. return progress / len(self._printer_connections)
  55. def start(self):
  56. self._check_updates = True
  57. self._update_thread.start()
  58. def stop(self):
  59. self._check_updates = False
  60. try:
  61. self._update_thread.join()
  62. except RuntimeError:
  63. pass
  64. def _updateThread(self):
  65. while self._check_updates:
  66. result = self.getSerialPortList(only_list_usb = True)
  67. self._addRemovePorts(result)
  68. time.sleep(5)
  69. ## Show firmware interface.
  70. # This will create the view if its not already created.
  71. def spawnFirmwareInterface(self, serial_port):
  72. if self._firmware_view is None:
  73. path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml"))
  74. component = QQmlComponent(Application.getInstance()._engine, path)
  75. self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
  76. self._firmware_context.setContextProperty("manager", self)
  77. self._firmware_view = component.create(self._firmware_context)
  78. self._firmware_view.show()
  79. @pyqtSlot()
  80. def updateAllFirmware(self):
  81. if not self._printer_connections:
  82. Message(i18n_catalog.i18nc("@info","Cannot update firmware, there were no connected printers found.")).show()
  83. return
  84. self.spawnFirmwareInterface("")
  85. for printer_connection in self._printer_connections:
  86. try:
  87. self._printer_connections[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
  88. except FileNotFoundError:
  89. self._printer_connections[printer_connection].setProgress(100, 100)
  90. Logger.log("w", "No firmware found for printer %s", printer_connection)
  91. continue
  92. @pyqtSlot(str, result = bool)
  93. def updateFirmwareBySerial(self, serial_port):
  94. if serial_port in self._printer_connections:
  95. self.spawnFirmwareInterface(self._printer_connections[serial_port].getSerialPort())
  96. try:
  97. self._printer_connections[serial_port].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
  98. except FileNotFoundError:
  99. self._firmware_view.close()
  100. Logger.log("e", "Could not find firmware required for this machine")
  101. return False
  102. return True
  103. return False
  104. ## Return the singleton instance of the USBPrinterManager
  105. @classmethod
  106. def getInstance(cls, engine = None, script_engine = None):
  107. # Note: Explicit use of class name to prevent issues with inheritance.
  108. if USBPrinterManager._instance is None:
  109. USBPrinterManager._instance = cls()
  110. return USBPrinterManager._instance
  111. def _getDefaultFirmwareName(self):
  112. machine_instance = Application.getInstance().getMachineManager().getActiveMachineInstance()
  113. machine_type = machine_instance.getMachineDefinition().getId()
  114. baudrate = 250000
  115. if sys.platform.startswith("linux"):
  116. baudrate = 115200
  117. if machine_type == "ultimaker_original":
  118. firmware_name = "MarlinUltimaker"
  119. if machine_instance.getMachineSettingValue("machine_heated_bed"): #Has heated bed upgrade kit?
  120. firmware_name += "-HBK"
  121. firmware_name += "-%d" % (baudrate)
  122. return firmware_name + ".hex"
  123. elif machine_type == "ultimaker_original_plus":
  124. firmware_name = "MarlinUltimaker-UMOP-%d" % (baudrate)
  125. return firmware_name + ".hex"
  126. elif machine_type == "bq_witbox":
  127. return "MarlinWitbox.hex"
  128. elif machine_type == "ultimaker2_go":
  129. return "MarlinUltimaker2go.hex"
  130. elif machine_type == "ultimaker2_extended":
  131. return "MarlinUltimaker2extended.hex"
  132. elif machine_type == "ultimaker2":
  133. return "MarlinUltimaker2.hex"
  134. elif machine_type == "ultimaker2plus":
  135. return "MarlinUltimaker2plus.hex"
  136. elif machine_type == "ultimaker2_extended_plus":
  137. return "MarlinUltimaker2extended-plus.hex"
  138. else:
  139. Logger.log("e", "I don't know of any firmware for machine %s.", machine_type)
  140. raise FileNotFoundError()
  141. ##TODO: Add check for multiple extruders
  142. def _addRemovePorts(self, serial_ports):
  143. # First, find and add all new or changed keys
  144. for serial_port in list(serial_ports):
  145. if serial_port not in self._serial_port_list:
  146. self.addConnectionSignal.emit(serial_port) #Hack to ensure its created in main thread
  147. continue
  148. self._serial_port_list = list(serial_ports)
  149. connections_to_remove = []
  150. for port, connection in self._printer_connections.items():
  151. if port not in self._serial_port_list:
  152. connection.close()
  153. connections_to_remove.append(port)
  154. for port in connections_to_remove:
  155. del self._printer_connections[port]
  156. ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
  157. def addConnection(self, serial_port):
  158. connection = PrinterConnection.PrinterConnection(serial_port)
  159. connection.connect()
  160. connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
  161. connection.progressChanged.connect(self.progressChanged)
  162. self._printer_connections[serial_port] = connection
  163. def _onPrinterConnectionStateChanged(self, serial_port):
  164. if self._printer_connections[serial_port].isConnected():
  165. self.getOutputDeviceManager().addOutputDevice(self._printer_connections[serial_port])
  166. else:
  167. self.getOutputDeviceManager().removeOutputDevice(serial_port)
  168. self.printerConnectionStateChanged.emit()
  169. @pyqtProperty(QObject , notify = printerConnectionStateChanged)
  170. def connectedPrinterList(self):
  171. self._printer_connections_model = ListModel()
  172. self._printer_connections_model.addRoleName(Qt.UserRole + 1,"name")
  173. self._printer_connections_model.addRoleName(Qt.UserRole + 2, "printer")
  174. for connection in self._printer_connections:
  175. if self._printer_connections[connection].isConnected():
  176. self._printer_connections_model.appendItem({"name":connection, "printer": self._printer_connections[connection]})
  177. return self._printer_connections_model
  178. ## Create a list of serial ports on the system.
  179. # \param only_list_usb If true, only usb ports are listed
  180. def getSerialPortList(self, only_list_usb = False):
  181. base_list = []
  182. if platform.system() == "Windows":
  183. import winreg
  184. try:
  185. key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
  186. i = 0
  187. while True:
  188. values = winreg.EnumValue(key, i)
  189. if not only_list_usb or "USBSER" in values[0]:
  190. base_list += [values[1]]
  191. i += 1
  192. except Exception as e:
  193. pass
  194. else:
  195. if only_list_usb:
  196. base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.usb*")
  197. base_list = filter(lambda s: "Bluetooth" not in s, base_list) # Filter because mac sometimes puts them in the list
  198. else:
  199. 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/*")
  200. return list(base_list)
  201. _instance = None