Refactoring & documentation

CURA-1339
This commit is contained in:
Jaime van Kessel 2016-04-14 13:55:33 +02:00
parent 9ee6323177
commit 4b5c118ed2
3 changed files with 28 additions and 270 deletions

View file

@ -1,244 +0,0 @@
# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Signal import Signal, SignalEmitter
from . import USBPrinterOutputDevice
from UM.Application import Application
from UM.Resources import Resources
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from cura.PrinterOutputDevice import ConnectionState
from UM.Qt.ListModel import ListModel
from UM.Message import Message
from cura.CuraApplication import CuraApplication
import threading
import platform
import glob
import time
import os.path
from UM.Extension import Extension
from PyQt5.QtQml import QQmlComponent, QQmlContext
from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal, Qt
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
def __init__(self, parent = None):
QObject.__init__(self, parent)
SignalEmitter.__init__(self)
OutputDevicePlugin.__init__(self)
Extension.__init__(self)
self._serial_port_list = []
self._printer_connections = {}
self._printer_connections_model = None
self._update_thread = threading.Thread(target = self._updateThread)
self._update_thread.setDaemon(True)
self._check_updates = True
self._firmware_view = None
## Add menu item to top menu of the application.
self.setMenuName(i18n_catalog.i18nc("@title:menu","Firmware"))
self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Update Firmware"), self.updateAllFirmware)
Application.getInstance().applicationShuttingDown.connect(self.stop)
self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
addConnectionSignal = Signal()
printerConnectionStateChanged = pyqtSignal()
progressChanged = pyqtSignal()
@pyqtProperty(float, notify = progressChanged)
def progress(self):
progress = 0
for printer_name, connection in self._printer_connections.items(): # TODO: @UnusedVariable "printer_name"
progress += connection.progress
return progress / len(self._printer_connections)
def start(self):
self._check_updates = True
self._update_thread.start()
def stop(self):
self._check_updates = False
try:
self._update_thread.join()
except RuntimeError:
pass
def _updateThread(self):
while self._check_updates:
result = self.getSerialPortList(only_list_usb = True)
self._addRemovePorts(result)
time.sleep(5)
## Show firmware interface.
# This will create the view if its not already created.
def spawnFirmwareInterface(self, serial_port):
if self._firmware_view is None:
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml"))
component = QQmlComponent(Application.getInstance()._engine, path)
self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
self._firmware_context.setContextProperty("manager", self)
self._firmware_view = component.create(self._firmware_context)
self._firmware_view.show()
@pyqtSlot()
def updateAllFirmware(self):
if not self._printer_connections:
Message(i18n_catalog.i18nc("@info","Cannot update firmware, there were no connected printers found.")).show()
return
self.spawnFirmwareInterface("")
for printer_connection in self._printer_connections:
try:
self._printer_connections[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
except FileNotFoundError:
self._printer_connections[printer_connection].setProgress(100, 100)
Logger.log("w", "No firmware found for printer %s", printer_connection)
continue
@pyqtSlot(str, result = bool)
def updateFirmwareBySerial(self, serial_port):
if serial_port in self._printer_connections:
self.spawnFirmwareInterface(self._printer_connections[serial_port].getSerialPort())
try:
self._printer_connections[serial_port].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
except FileNotFoundError:
self._firmware_view.close()
Logger.log("e", "Could not find firmware required for this machine")
return False
return True
return False
## Return the singleton instance of the USBPrinterManager
@classmethod
def getInstance(cls, engine = None, script_engine = None):
# Note: Explicit use of class name to prevent issues with inheritance.
if USBPrinterManager._instance is None:
USBPrinterManager._instance = cls()
return USBPrinterManager._instance
def _getDefaultFirmwareName(self):
machine_instance = Application.getInstance().getMachineManager().getActiveMachineInstance()
machine_type = machine_instance.getMachineDefinition().getId()
if platform.system() == "Linux":
baudrate = 115200
else:
baudrate = 250000
# NOTE: The keyword used here is the id of the machine. You can find the id of your machine in the *.json file, eg.
# https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json#L2
# The *.hex files are stored at a seperate repository:
# https://github.com/Ultimaker/cura-binary-data/tree/master/cura/resources/firmware
machine_without_extras = {"bq_witbox" : "MarlinWitbox.hex",
"ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
"ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
"ultimaker2" : "MarlinUltimaker2.hex",
"ultimaker2_go" : "MarlinUltimaker2go.hex",
"ultimaker2plus" : "MarlinUltimaker2plus.hex",
"ultimaker2_extended" : "MarlinUltimaker2extended.hex",
"ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
}
machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
}
##TODO: Add check for multiple extruders
hex_file = None
if machine_type in machine_without_extras.keys(): # The machine needs to be defined here!
if machine_type in machine_with_heated_bed.keys() and machine_instance.getMachineSettingValue("machine_heated_bed"):
Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_type)
hex_file = machine_with_heated_bed[machine_type] # Return firmware with heated bed enabled
else:
Logger.log("d", "Choosing basic firmware for machine %s.", machine_type)
hex_file = machine_without_extras[machine_type] # Return "basic" firmware
else:
Logger.log("e", "There is no firmware for machine %s.", machine_type)
if hex_file:
return hex_file.format(baudrate=baudrate)
else:
Logger.log("e", "Could not find any firmware for machine %s.", machine_type)
raise FileNotFoundError()
def _addRemovePorts(self, serial_ports):
# First, find and add all new or changed keys
for serial_port in list(serial_ports):
if serial_port not in self._serial_port_list:
self.addConnectionSignal.emit(serial_port) #Hack to ensure its created in main thread
continue
self._serial_port_list = list(serial_ports)
connections_to_remove = []
for port, connection in self._printer_connections.items():
if port not in self._serial_port_list:
connection.close()
connections_to_remove.append(port)
for port in connections_to_remove:
del self._printer_connections[port]
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
def addConnection(self, serial_port):
connection = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port)
connection.connect()
connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
connection.progressChanged.connect(self.progressChanged)
self._printer_connections[serial_port] = connection
def _onPrinterConnectionStateChanged(self, serial_port):
try:
if self._printer_connections[serial_port].connectionState == ConnectionState.CONNECTED:
self.getOutputDeviceManager().addOutputDevice(self._printer_connections[serial_port])
else:
self.getOutputDeviceManager().removeOutputDevice(serial_port)
self.printerConnectionStateChanged.emit()
except KeyError:
pass # no output device by this device_id found in connection list.
@pyqtProperty(QObject , notify = printerConnectionStateChanged)
def connectedPrinterList(self):
self._printer_connections_model = ListModel()
self._printer_connections_model.addRoleName(Qt.UserRole + 1,"name")
self._printer_connections_model.addRoleName(Qt.UserRole + 2, "printer")
for connection in self._printer_connections:
if self._printer_connections[connection].connectionState == ConnectionState.CONNECTED:
self._printer_connections_model.appendItem({"name":connection, "printer": self._printer_connections[connection]})
return self._printer_connections_model
## Create a list of serial ports on the system.
# \param only_list_usb If true, only usb ports are listed
def getSerialPortList(self, only_list_usb = False):
base_list = []
if platform.system() == "Windows":
import winreg
try:
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
i = 0
while True:
values = winreg.EnumValue(key, i)
if not only_list_usb or "USBSER" in values[0]:
base_list += [values[1]]
i += 1
except Exception as e:
pass
else:
if only_list_usb:
base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.usb*")
base_list = filter(lambda s: "Bluetooth" not in s, base_list) # Filter because mac sometimes puts them in the list
else:
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/*")
return list(base_list)
_instance = None

View file

@ -72,6 +72,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# Current Z stage location # Current Z stage location
self._current_z = 0 self._current_z = 0
# Check if endstops are ever pressed (used for first run)
self._x_min_endstop_pressed = False self._x_min_endstop_pressed = False
self._y_min_endstop_pressed = False self._y_min_endstop_pressed = False
self._z_min_endstop_pressed = False self._z_min_endstop_pressed = False
@ -162,7 +163,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if not self._updating_firmware and not self._connect_thread.isAlive(): if not self._updating_firmware and not self._connect_thread.isAlive():
self._connect_thread.start() self._connect_thread.start()
## Private fuction (threaded) that actually uploads the firmware. ## Private function (threaded) that actually uploads the firmware.
def _updateFirmware(self): def _updateFirmware(self):
self.setProgress(0, 100) self.setProgress(0, 100)
@ -182,7 +183,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
except Exception: except Exception:
pass pass
time.sleep(1) # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
time.sleep(1)
if not programmer.isConnected(): if not programmer.isConnected():
Logger.log("e", "Unable to connect with serial. Could not update firmware") Logger.log("e", "Unable to connect with serial. Could not update firmware")
@ -238,7 +240,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
except Exception as e: except Exception as e:
Logger.log("i", "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port) Logger.log("i", "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port)
# If the programmer connected, we know its an atmega based version. Not all that useful, but it does give some debugging information. # If the programmer connected, we know its an atmega based version.
# Not all that useful, but it does give some debugging information.
for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect) for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect)
Logger.log("d","Attempting to connect to printer with serial %s on baud rate %s", self._serial_port, baud_rate) Logger.log("d","Attempting to connect to printer with serial %s on baud rate %s", self._serial_port, baud_rate)
if self._serial is None: if self._serial is None:
@ -312,7 +315,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._listen_thread.daemon = True self._listen_thread.daemon = True
self._serial = None self._serial = None
## Directly send the command, withouth checking connection state (eg; printing). ## Directly send the command, withouth checking connection state (eg; printing).
# \param cmd string with g-code # \param cmd string with g-code
def _sendCommand(self, cmd): def _sendCommand(self, cmd):
@ -435,7 +437,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._is_printing: if self._is_printing:
if line == b"" and time.time() > ok_timeout: if line == b"" and time.time() > ok_timeout:
line = b"ok" # Force a timeout (basicly, send next command) line = b"ok" # Force a timeout (basically, send next command)
if b"ok" in line: if b"ok" in line:
ok_timeout = time.time() + 5 ok_timeout = time.time() + 5
@ -507,7 +509,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
## Check if the process did not encounter an error yet. ## Check if the process did not encounter an error yet.
def hasError(self): def hasError(self):
return self._error_state != None return self._error_state is not None
## private read line used by printer connection to listen for data on serial port. ## private read line used by printer connection to listen for data on serial port.
def _readline(self): def _readline(self):

View file

@ -1,7 +1,7 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher. # Cura is released under the terms of the AGPLv3 or higher.
from . import USBPrinterManager from . import USBPrinterOutputDeviceManager
from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
@ -19,5 +19,5 @@ def getMetaData():
} }
def register(app): def register(app):
qmlRegisterSingletonType(USBPrinterManager.USBPrinterManager, "UM", 1, 0, "USBPrinterManager", USBPrinterManager.USBPrinterManager.getInstance) qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "UM", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
return {"extension":USBPrinterManager.USBPrinterManager.getInstance(),"output_device": USBPrinterManager.USBPrinterManager.getInstance() } return {"extension":USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance(), "output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()}