USBPrinterOutputDeviceManager.py 12 KB

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