USBPrinterOutputDeviceManager.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright (c) 2017 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from UM.Signal import Signal, signalemitter
  4. from UM.Application import Application
  5. from UM.Resources import Resources
  6. from UM.Logger import Logger
  7. from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
  8. from UM.i18n import i18nCatalog
  9. from cura.PrinterOutputDevice import ConnectionState
  10. from cura.CuraApplication import CuraApplication
  11. from . import USBPrinterOutputDevice
  12. from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
  13. import threading
  14. import platform
  15. import time
  16. import serial.tools.list_ports
  17. i18n_catalog = i18nCatalog("cura")
  18. ## Manager class that ensures that an USBPrinterOutput device is created for every connected USB printer.
  19. @signalemitter
  20. class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
  21. addUSBOutputDeviceSignal = Signal()
  22. progressChanged = pyqtSignal()
  23. def __init__(self, parent = None):
  24. super().__init__(parent = parent)
  25. self._serial_port_list = []
  26. self._usb_output_devices = {}
  27. self._usb_output_devices_model = None
  28. self._update_thread = threading.Thread(target = self._updateThread)
  29. self._update_thread.setDaemon(True)
  30. self._check_updates = True
  31. Application.getInstance().applicationShuttingDown.connect(self.stop)
  32. # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
  33. self.addUSBOutputDeviceSignal.connect(self.addOutputDevice)
  34. def start(self):
  35. self._check_updates = True
  36. self._update_thread.start()
  37. def stop(self):
  38. self._check_updates = False
  39. def _onConnectionStateChanged(self, serial_port):
  40. if serial_port not in self._usb_output_devices:
  41. return
  42. changed_device = self._usb_output_devices[serial_port]
  43. if changed_device.connectionState == ConnectionState.connected:
  44. self.getOutputDeviceManager().addOutputDevice(changed_device)
  45. else:
  46. self.getOutputDeviceManager().removeOutputDevice(serial_port)
  47. def _updateThread(self):
  48. while self._check_updates:
  49. container_stack = Application.getInstance().getGlobalContainerStack()
  50. if container_stack is None:
  51. time.sleep(5)
  52. continue
  53. if container_stack.getMetaDataEntry("supports_usb_connection"):
  54. port_list = self.getSerialPortList(only_list_usb=True)
  55. else:
  56. port_list = [] # Just use an empty list; all USB devices will be removed.
  57. self._addRemovePorts(port_list)
  58. time.sleep(5)
  59. ## Return the singleton instance of the USBPrinterManager
  60. @classmethod
  61. def getInstance(cls, engine = None, script_engine = None):
  62. # Note: Explicit use of class name to prevent issues with inheritance.
  63. if USBPrinterOutputDeviceManager._instance is None:
  64. USBPrinterOutputDeviceManager._instance = cls()
  65. return USBPrinterOutputDeviceManager._instance
  66. @pyqtSlot(result = str)
  67. def getDefaultFirmwareName(self):
  68. # Check if there is a valid global container stack
  69. global_container_stack = Application.getInstance().getGlobalContainerStack()
  70. if not global_container_stack:
  71. Logger.log("e", "There is no global container stack. Can not update firmware.")
  72. self._firmware_view.close()
  73. return ""
  74. # The bottom of the containerstack is the machine definition
  75. machine_id = global_container_stack.getBottom().id
  76. machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value")
  77. if platform.system() == "Linux":
  78. baudrate = 115200
  79. else:
  80. baudrate = 250000
  81. # NOTE: The keyword used here is the id of the machine. You can find the id of your machine in the *.json file, eg.
  82. # https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json#L2
  83. # The *.hex files are stored at a seperate repository:
  84. # https://github.com/Ultimaker/cura-binary-data/tree/master/cura/resources/firmware
  85. machine_without_extras = {"bq_witbox" : "MarlinWitbox.hex",
  86. "bq_hephestos_2" : "MarlinHephestos2.hex",
  87. "ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
  88. "ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
  89. "ultimaker_original_dual" : "MarlinUltimaker-{baudrate}-dual.hex",
  90. "ultimaker2" : "MarlinUltimaker2.hex",
  91. "ultimaker2_go" : "MarlinUltimaker2go.hex",
  92. "ultimaker2_plus" : "MarlinUltimaker2plus.hex",
  93. "ultimaker2_extended" : "MarlinUltimaker2extended.hex",
  94. "ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
  95. }
  96. machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
  97. "ultimaker_original_dual" : "MarlinUltimaker-HBK-{baudrate}-dual.hex",
  98. }
  99. ##TODO: Add check for multiple extruders
  100. hex_file = None
  101. if machine_id in machine_without_extras.keys(): # The machine needs to be defined here!
  102. if machine_id in machine_with_heated_bed.keys() and machine_has_heated_bed:
  103. Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_id)
  104. hex_file = machine_with_heated_bed[machine_id] # Return firmware with heated bed enabled
  105. else:
  106. Logger.log("d", "Choosing basic firmware for machine %s.", machine_id)
  107. hex_file = machine_without_extras[machine_id] # Return "basic" firmware
  108. else:
  109. Logger.log("w", "There is no firmware for machine %s.", machine_id)
  110. if hex_file:
  111. try:
  112. return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
  113. except FileNotFoundError:
  114. Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
  115. return ""
  116. else:
  117. Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
  118. return ""
  119. ## Helper to identify serial ports (and scan for them)
  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.addUSBOutputDeviceSignal.emit(serial_port) # Hack to ensure its created in main thread
  125. continue
  126. self._serial_port_list = list(serial_ports)
  127. for port, device in self._usb_output_devices.items():
  128. if port not in self._serial_port_list:
  129. device.close()
  130. ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
  131. def addOutputDevice(self, serial_port):
  132. device = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port)
  133. device.connectionStateChanged.connect(self._onConnectionStateChanged)
  134. self._usb_output_devices[serial_port] = device
  135. device.connect()
  136. ## Create a list of serial ports on the system.
  137. # \param only_list_usb If true, only usb ports are listed
  138. def getSerialPortList(self, only_list_usb = False):
  139. base_list = []
  140. for port in serial.tools.list_ports.comports():
  141. if not isinstance(port, tuple):
  142. port = (port.device, port.description, port.hwid)
  143. if only_list_usb and not port[2].startswith("USB"):
  144. continue
  145. base_list += [port[0]]
  146. return list(base_list)
  147. _instance = None # type: "USBPrinterOutputDeviceManager"