Browse Source

Fixed the firmware update for USB print

CL-541
Jaime van Kessel 7 years ago
parent
commit
32cbd27b70

+ 25 - 6
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

+ 16 - 38
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;

+ 123 - 2
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

+ 0 - 2
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.

+ 7 - 3
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)
         }
     }
 }