Fixed the firmware update for USB print

CL-541
This commit is contained in:
Jaime van Kessel 2017-12-19 15:59:21 +01:00
parent a35f665201
commit 32cbd27b70
5 changed files with 171 additions and 51 deletions

View file

@ -4,9 +4,12 @@
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger 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 from serial import Serial, SerialException
class AutoDetectBaudJob(Job): class AutoDetectBaudJob(Job):
def __init__(self, serial_port): def __init__(self, serial_port):
super().__init__() super().__init__()
@ -17,14 +20,30 @@ class AutoDetectBaudJob(Job):
Logger.log("d", "Auto detect baud rate started.") Logger.log("d", "Auto detect baud rate started.")
timeout = 3 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: 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)) Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
if serial is None:
try: try:
serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout) serial = Serial(str(self._serial_port), baud_rate, timeout = timeout, writeTimeout = timeout)
except SerialException as e: except SerialException as e:
Logger.logException("w", "Unable to create serial") Logger.logException("w", "Unable to create serial")
continue 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 successful_responses = 0
serial.write(b"\n") # Ensure we clear out previous responses serial.write(b"\n") # Ensure we clear out previous responses

View file

@ -34,44 +34,22 @@ UM.Dialog
} }
text: { 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
return catalog.i18nc("@label","Updating firmware.")
}
}
else
{
switch (manager.errorCode)
{ {
case 0:
return "" //Not doing anything (eg; idling)
case 1: case 1:
//: Firmware update status label return catalog.i18nc("@label","Updating firmware.")
return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
case 2: case 2:
//: Firmware update status label return catalog.i18nc("@label","Firmware update completed.")
return catalog.i18nc("@label","Firmware update failed due to an communication error.")
case 3: case 3:
//: Firmware update status label return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
case 4: case 4:
//: Firmware update status label 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.") 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)
}
} }
} }
@ -81,10 +59,10 @@ UM.Dialog
ProgressBar ProgressBar
{ {
id: prog id: prog
value: manager.firmwareUpdateCompleteStatus ? 100 : manager.progress value: manager.firmwareProgress
minimumValue: 0 minimumValue: 0
maximumValue: 100 maximumValue: 100
indeterminate: (manager.progress < 1) && (!manager.firmwareUpdateCompleteStatus) indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0
anchors anchors
{ {
left: parent.left; left: parent.left;

View file

@ -5,6 +5,7 @@ from UM.Logger import Logger
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Application import Application from UM.Application import Application
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
from UM.PluginRegistry import PluginRegistry
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@ -12,19 +13,27 @@ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from .AutoDetectBaudJob import AutoDetectBaudJob from .AutoDetectBaudJob import AutoDetectBaudJob
from .USBPrinterOutputController import USBPrinterOuptutController from .USBPrinterOutputController import USBPrinterOuptutController
from .avr_isp import stk500v2, intelHex
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
from serial import Serial, SerialException from serial import Serial, SerialException
from threading import Thread from threading import Thread
from time import time from time import time, sleep
from queue import Queue from queue import Queue
from enum import IntEnum
import re import re
import functools # Used for reduce import functools # Used for reduce
import os
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice): class USBPrinterOutputDevice(PrinterOutputDevice):
firmwareProgressChanged = pyqtSignal()
firmwareUpdateStateChanged = pyqtSignal()
def __init__(self, serial_port, baud_rate = None): def __init__(self, serial_port, baud_rate = None):
super().__init__(serial_port) super().__init__(serial_port)
self.setName(catalog.i18nc("@item:inmenu", "USB printing")) 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. # 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_thread = Thread(target=self._update, daemon = True)
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True)
self._last_temperature_request = None self._last_temperature_request = None
self._is_printing = False # A print is being sent. self._is_printing = False # A print is being sent.
@ -62,6 +73,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._paused = False 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. # Queue for commands that need to be send. Used when command is sent when a print is active.
self._command_queue = Queue() self._command_queue = Queue()
@ -81,6 +97,88 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
gcode_list = getattr(Application.getInstance().getController().getScene(), "gcode_list") gcode_list = getattr(Application.getInstance().getController().getScene(), "gcode_list")
self._printGCode(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. ## Start a print based on a g-code.
# \param gcode_list List with gcode (strings). # \param gcode_list List with gcode (strings).
def _printGCode(self, gcode_list): def _printGCode(self, gcode_list):
@ -136,6 +234,15 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self.setConnectionState(ConnectionState.connected) self.setConnectionState(ConnectionState.connected)
self._update_thread.start() 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): def sendCommand(self, command):
if self._is_printing: if self._is_printing:
self._command_queue.put(command) self._command_queue.put(command)
@ -155,7 +262,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
def _update(self): def _update(self):
while self._connection_state == ConnectionState.connected and self._serial is not None: while self._connection_state == ConnectionState.connected and self._serial is not None:
try:
line = self._serial.readline() line = self._serial.readline()
except:
continue
if self._last_temperature_request is None or time() > self._last_temperature_request + self._timeout: if self._last_temperature_request is None or time() > self._last_temperature_request + self._timeout:
# Timeout, or no request has been sent at all. # Timeout, or no request has been sent at all.
self.sendCommand("M105") self.sendCommand("M105")
@ -255,3 +366,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
print_job.updateTimeTotal(estimated_time) print_job.updateTimeTotal(estimated_time)
self._gcode_position += 1 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

View file

@ -6,7 +6,6 @@ from . import USBPrinterOutputDevice
from UM.Application import Application from UM.Application import Application
from UM.Resources import Resources from UM.Resources import Resources
from UM.Logger import Logger from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from cura.PrinterOutputDevice import ConnectionState from cura.PrinterOutputDevice import ConnectionState
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
@ -41,7 +40,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
self._update_thread.setDaemon(True) self._update_thread.setDaemon(True)
self._check_updates = True self._check_updates = True
self._firmware_view = None
Application.getInstance().applicationShuttingDown.connect(self.stop) 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. self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal.

View file

@ -14,6 +14,9 @@ import Cura 1.0 as Cura
Cura.MachineAction Cura.MachineAction
{ {
anchors.fill: parent; anchors.fill: parent;
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
Item Item
{ {
id: upgradeFirmwareMachineAction id: upgradeFirmwareMachineAction
@ -60,16 +63,17 @@ Cura.MachineAction
{ {
id: autoUpgradeButton id: autoUpgradeButton
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware"); text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
enabled: parent.firmwareName != "" enabled: parent.firmwareName != "" && activeOutputDevice
onClicked: onClicked:
{ {
Cura.USBPrinterManager.updateAllFirmware(parent.firmwareName) activeOutputDevice.updateFirmware(parent.firmwareName)
} }
} }
Button Button
{ {
id: manualUpgradeButton id: manualUpgradeButton
text: catalog.i18nc("@action:button", "Upload custom Firmware"); text: catalog.i18nc("@action:button", "Upload custom Firmware");
enabled: activeOutputDevice != null
onClicked: onClicked:
{ {
customFirmwareDialog.open() customFirmwareDialog.open()
@ -83,7 +87,7 @@ Cura.MachineAction
title: catalog.i18nc("@title:window", "Select custom firmware") title: catalog.i18nc("@title:window", "Select custom firmware")
nameFilters: "Firmware image files (*.hex)" nameFilters: "Firmware image files (*.hex)"
selectExisting: true selectExisting: true
onAccepted: Cura.USBPrinterManager.updateAllFirmware(fileUrl) onAccepted: activeOutputDevice.updateFirmware(fileUrl)
} }
} }
} }