diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py index 8dcc705397..574e241453 100644 --- a/plugins/USBPrinting/AutoDetectBaudJob.py +++ b/plugins/USBPrinting/AutoDetectBaudJob.py @@ -4,9 +4,12 @@ from UM.Job import Job from UM.Logger import Logger -from time import time +from .avr_isp.stk500v2 import Stk500v2 + +from time import time, sleep from serial import Serial, SerialException + class AutoDetectBaudJob(Job): def __init__(self, serial_port): super().__init__() @@ -17,14 +20,30 @@ class AutoDetectBaudJob(Job): Logger.log("d", "Auto detect baud rate started.") timeout = 3 + programmer = Stk500v2() + serial = None + try: + programmer.connect(self._serial_port) + serial = programmer.leaveISP() + except: + programmer.close() + for baud_rate in self._all_baud_rates: Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate)) - try: - serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout) - except SerialException as e: - Logger.logException("w", "Unable to create serial") - continue + if serial is None: + try: + serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout) + except SerialException as e: + Logger.logException("w", "Unable to create serial") + continue + else: + # We already have a serial connection, just change the baud rate. + try: + serial.baudrate = baud_rate + except: + continue + sleep(1.5) # Ensure that we are not talking to the bootloader. 1.5 seconds seems to be the magic number successful_responses = 0 serial.write(b"\n") # Ensure we clear out previous responses diff --git a/plugins/USBPrinting/FirmwareUpdateWindow.qml b/plugins/USBPrinting/FirmwareUpdateWindow.qml index 44218b61b1..bd0c85f49d 100644 --- a/plugins/USBPrinting/FirmwareUpdateWindow.qml +++ b/plugins/USBPrinting/FirmwareUpdateWindow.qml @@ -34,44 +34,22 @@ UM.Dialog } text: { - if (manager.errorCode == 0) + switch (manager.firmwareUpdateState) { - if (manager.firmwareUpdateCompleteStatus) - { - //: Firmware update status label - return catalog.i18nc("@label","Firmware update completed.") - } - else if (manager.progress == 0) - { - //: Firmware update status label - return catalog.i18nc("@label","Starting firmware update, this may take a while.") - } - else - { - //: Firmware update status label + case 0: + return "" //Not doing anything (eg; idling) + case 1: return catalog.i18nc("@label","Updating firmware.") - } - } - else - { - switch (manager.errorCode) - { - case 1: - //: Firmware update status label - return catalog.i18nc("@label","Firmware update failed due to an unknown error.") - case 2: - //: Firmware update status label - return catalog.i18nc("@label","Firmware update failed due to an communication error.") - case 3: - //: Firmware update status label - return catalog.i18nc("@label","Firmware update failed due to an input/output error.") - case 4: - //: Firmware update status label - return catalog.i18nc("@label","Firmware update failed due to missing firmware.") - default: - //: Firmware update status label - return catalog.i18nc("@label", "Unknown error code: %1").arg(manager.errorCode) - } + case 2: + return catalog.i18nc("@label","Firmware update completed.") + case 3: + return catalog.i18nc("@label","Firmware update failed due to an unknown error.") + case 4: + return catalog.i18nc("@label","Firmware update failed due to an communication error.") + case 5: + return catalog.i18nc("@label","Firmware update failed due to an input/output error.") + case 6: + return catalog.i18nc("@label","Firmware update failed due to missing firmware.") } } @@ -81,10 +59,10 @@ UM.Dialog ProgressBar { id: prog - value: manager.firmwareUpdateCompleteStatus ? 100 : manager.progress + value: manager.firmwareProgress minimumValue: 0 maximumValue: 100 - indeterminate: (manager.progress < 1) && (!manager.firmwareUpdateCompleteStatus) + indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0 anchors { left: parent.left; diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 867233561e..100643b490 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -5,6 +5,7 @@ from UM.Logger import Logger from UM.i18n import i18nCatalog from UM.Application import Application from UM.Qt.Duration import DurationFormat +from UM.PluginRegistry import PluginRegistry from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel @@ -12,19 +13,27 @@ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from .AutoDetectBaudJob import AutoDetectBaudJob from .USBPrinterOutputController import USBPrinterOuptutController +from .avr_isp import stk500v2, intelHex + +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty from serial import Serial, SerialException from threading import Thread -from time import time +from time import time, sleep from queue import Queue +from enum import IntEnum import re import functools # Used for reduce +import os catalog = i18nCatalog("cura") class USBPrinterOutputDevice(PrinterOutputDevice): + firmwareProgressChanged = pyqtSignal() + firmwareUpdateStateChanged = pyqtSignal() + def __init__(self, serial_port, baud_rate = None): super().__init__(serial_port) self.setName(catalog.i18nc("@item:inmenu", "USB printing")) @@ -50,6 +59,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice): # Instead of using a timer, we really need the update to be as a thread, as reading from serial can block. self._update_thread = Thread(target=self._update, daemon = True) + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True) + self._last_temperature_request = None self._is_printing = False # A print is being sent. @@ -62,6 +73,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._paused = False + self._firmware_view = None + self._firmware_location = None + self._firmware_progress = 0 + self._firmware_update_state = FirmwareUpdateState.idle + # Queue for commands that need to be send. Used when command is sent when a print is active. self._command_queue = Queue() @@ -81,6 +97,88 @@ class USBPrinterOutputDevice(PrinterOutputDevice): gcode_list = getattr(Application.getInstance().getController().getScene(), "gcode_list") self._printGCode(gcode_list) + + ## Show firmware interface. + # This will create the view if its not already created. + def showFirmwareInterface(self): + if self._firmware_view is None: + path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") + self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self}) + + self._firmware_view.show() + + @pyqtSlot(str) + def updateFirmware(self, file): + self._firmware_location = file + self.showFirmwareInterface() + self.setFirmwareUpdateState(FirmwareUpdateState.updating) + self._update_firmware_thread.start() + + def _updateFirmware(self): + # Ensure that other connections are closed. + if self._connection_state != ConnectionState.closed: + self.close() + + hex_file = intelHex.readHex(self._firmware_location) + if len(hex_file) == 0: + Logger.log("e", "Unable to read provided hex file. Could not update firmware") + self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) + return + + programmer = stk500v2.Stk500v2() + programmer.progress_callback = self._onFirmwareProgress + + try: + programmer.connect(self._serial_port) + except: + programmer.close() + Logger.logException("e", "Failed to update firmware") + self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) + return + + # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases. + sleep(1) + if not programmer.isConnected(): + Logger.log("e", "Unable to connect with serial. Could not update firmware") + self.setFirmwareUpdateState(FirmwareUpdateState.communication_error) + try: + programmer.programChip(hex_file) + except SerialException: + self.setFirmwareUpdateState(FirmwareUpdateState.io_error) + return + except: + self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error) + return + + programmer.close() + + # Clean up for next attempt. + self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True) + self._firmware_location = "" + self._onFirmwareProgress(100) + self.setFirmwareUpdateState(FirmwareUpdateState.completed) + + # Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later. + Application.getInstance().callLater(self.connect) + + @pyqtProperty(float, notify = firmwareProgressChanged) + def firmwareProgress(self): + return self._firmware_progress + + @pyqtProperty(int, notify=firmwareUpdateStateChanged) + def firmwareUpdateState(self): + return self._firmware_update_state + + def setFirmwareUpdateState(self, state): + if self._firmware_update_state != state: + self._firmware_update_state = state + self.firmwareUpdateStateChanged.emit() + + # Callback function for firmware update progress. + def _onFirmwareProgress(self, progress, max_progress = 100): + self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100 + self.firmwareProgressChanged.emit() + ## Start a print based on a g-code. # \param gcode_list List with gcode (strings). def _printGCode(self, gcode_list): @@ -136,6 +234,15 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self.setConnectionState(ConnectionState.connected) self._update_thread.start() + def close(self): + super().close() + if self._serial is not None: + self._serial.close() + + # Re-create the thread so it can be started again later. + self._update_thread = Thread(target=self._update, daemon=True) + self._serial = None + def sendCommand(self, command): if self._is_printing: self._command_queue.put(command) @@ -155,7 +262,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): def _update(self): while self._connection_state == ConnectionState.connected and self._serial is not None: - line = self._serial.readline() + try: + line = self._serial.readline() + except: + continue + if self._last_temperature_request is None or time() > self._last_temperature_request + self._timeout: # Timeout, or no request has been sent at all. self.sendCommand("M105") @@ -255,3 +366,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice): print_job.updateTimeTotal(estimated_time) self._gcode_position += 1 + + +class FirmwareUpdateState(IntEnum): + idle = 0 + updating = 1 + completed = 2 + unknown_error = 3 + communication_error = 4 + io_error = 5 + firmware_not_found_error = 6 \ No newline at end of file diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index c97d8c0160..47e2776286 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -6,7 +6,6 @@ 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 @@ -41,7 +40,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): self._update_thread.setDaemon(True) self._check_updates = True - self._firmware_view = None Application.getInstance().applicationShuttingDown.connect(self.stop) self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 72a77e992d..f36788daa5 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -14,6 +14,9 @@ import Cura 1.0 as Cura Cura.MachineAction { anchors.fill: parent; + property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 + property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null + Item { id: upgradeFirmwareMachineAction @@ -60,16 +63,17 @@ Cura.MachineAction { id: autoUpgradeButton text: catalog.i18nc("@action:button", "Automatically upgrade Firmware"); - enabled: parent.firmwareName != "" + enabled: parent.firmwareName != "" && activeOutputDevice onClicked: { - Cura.USBPrinterManager.updateAllFirmware(parent.firmwareName) + activeOutputDevice.updateFirmware(parent.firmwareName) } } Button { id: manualUpgradeButton text: catalog.i18nc("@action:button", "Upload custom Firmware"); + enabled: activeOutputDevice != null onClicked: { customFirmwareDialog.open() @@ -83,7 +87,7 @@ Cura.MachineAction title: catalog.i18nc("@title:window", "Select custom firmware") nameFilters: "Firmware image files (*.hex)" selectExisting: true - onAccepted: Cura.USBPrinterManager.updateAllFirmware(fileUrl) + onAccepted: activeOutputDevice.updateFirmware(fileUrl) } } } \ No newline at end of file