mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Fixed the firmware update for USB print
CL-541
This commit is contained in:
parent
a35f665201
commit
32cbd27b70
5 changed files with 171 additions and 51 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue