Merge branch 'feature_firmware_updater' of https://github.com/fieldOfView/Cura into fieldOfView-feature_firmware_updater

This commit is contained in:
Ghostkeeper 2018-10-11 16:42:11 +02:00
commit 7c6970bec3
No known key found for this signature in database
GPG Key ID: 5252B696FB5E7C7A
35 changed files with 671 additions and 530 deletions

View File

@ -0,0 +1,78 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
from enum import IntEnum
from threading import Thread
from typing import Union
MYPY = False
if MYPY:
from cura.PrinterOutputDevice import PrinterOutputDevice
class FirmwareUpdater(QObject):
firmwareProgressChanged = pyqtSignal()
firmwareUpdateStateChanged = pyqtSignal()
def __init__(self, output_device: "PrinterOutputDevice") -> None:
super().__init__()
self._output_device = output_device
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
self._firmware_file = ""
self._firmware_progress = 0
self._firmware_update_state = FirmwareUpdateState.idle
def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
# the file path could be url-encoded.
if firmware_file.startswith("file://"):
self._firmware_file = QUrl(firmware_file).toLocalFile()
else:
self._firmware_file = firmware_file
self._setFirmwareUpdateState(FirmwareUpdateState.updating)
self._update_firmware_thread.start()
def _updateFirmware(self) -> None:
raise NotImplementedError("_updateFirmware needs to be implemented")
## Cleanup after a succesful update
def _cleanupAfterUpdate(self) -> None:
# Clean up for next attempt.
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
self._firmware_file = ""
self._onFirmwareProgress(100)
self._setFirmwareUpdateState(FirmwareUpdateState.completed)
@pyqtProperty(int, notify = firmwareProgressChanged)
def firmwareProgress(self) -> int:
return self._firmware_progress
@pyqtProperty(int, notify=firmwareUpdateStateChanged)
def firmwareUpdateState(self) -> "FirmwareUpdateState":
return self._firmware_update_state
def _setFirmwareUpdateState(self, state: "FirmwareUpdateState") -> None:
if self._firmware_update_state != state:
self._firmware_update_state = state
self.firmwareUpdateStateChanged.emit()
# Callback function for firmware update progress.
def _onFirmwareProgress(self, progress: int, max_progress: int = 100) -> None:
self._firmware_progress = int(progress * 100 / max_progress) # Convert to scale of 0-100
self.firmwareProgressChanged.emit()
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

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Set, Union, Optional
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from PyQt5.QtCore import QTimer
@ -9,27 +9,28 @@ from PyQt5.QtCore import QTimer
if TYPE_CHECKING:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
class GenericOutputController(PrinterOutputController):
def __init__(self, output_device):
def __init__(self, output_device: "PrinterOutputDevice") -> None:
super().__init__(output_device)
self._preheat_bed_timer = QTimer()
self._preheat_bed_timer.setSingleShot(True)
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
self._preheat_printer = None
self._preheat_printer = None # type: Optional[PrinterOutputModel]
self._preheat_hotends_timer = QTimer()
self._preheat_hotends_timer.setSingleShot(True)
self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
self._preheat_hotends = set()
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
self._output_device.printersChanged.connect(self._onPrintersChanged)
self._active_printer = None
self._active_printer = None # type: Optional[PrinterOutputModel]
def _onPrintersChanged(self):
def _onPrintersChanged(self) -> None:
if self._active_printer:
self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
@ -43,32 +44,33 @@ class GenericOutputController(PrinterOutputController):
for extruder in self._active_printer.extruders:
extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
def _onPrinterStateChanged(self):
if self._active_printer.state != "idle":
def _onPrinterStateChanged(self) -> None:
if self._active_printer and self._active_printer.state != "idle":
if self._preheat_bed_timer.isActive():
self._preheat_bed_timer.stop()
if self._preheat_printer:
self._preheat_printer.updateIsPreheating(False)
if self._preheat_hotends_timer.isActive():
self._preheat_hotends_timer.stop()
for extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
self._preheat_hotends = set()
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
self._output_device.sendCommand("G91")
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
self._output_device.sendCommand("G90")
def homeHead(self, printer):
def homeHead(self, printer: "PrinterOutputModel") -> None:
self._output_device.sendCommand("G28 X Y")
def homeBed(self, printer):
def homeBed(self, printer: "PrinterOutputModel") -> None:
self._output_device.sendCommand("G28 Z")
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands.
def setJobState(self, job: "PrintJobOutputModel", state: str):
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
if state == "pause":
self._output_device.pausePrint()
job.updateState("paused")
@ -79,15 +81,15 @@ class GenericOutputController(PrinterOutputController):
self._output_device.cancelPrint()
pass
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
self._output_device.sendCommand("M140 S%s" % temperature)
def _onTargetBedTemperatureChanged(self):
if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
def _onTargetBedTemperatureChanged(self) -> None:
if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0:
self._preheat_bed_timer.stop()
self._preheat_printer.updateIsPreheating(False)
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
try:
temperature = round(temperature) # The API doesn't allow floating point.
duration = round(duration)
@ -100,21 +102,25 @@ class GenericOutputController(PrinterOutputController):
self._preheat_printer = printer
printer.updateIsPreheating(True)
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
self.setTargetBedTemperature(printer, temperature=0)
self._preheat_bed_timer.stop()
printer.updateIsPreheating(False)
def _onPreheatBedTimerFinished(self):
def _onPreheatBedTimerFinished(self) -> None:
if not self._preheat_printer:
return
self.setTargetBedTemperature(self._preheat_printer, 0)
self._preheat_printer.updateIsPreheating(False)
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
def _onTargetHotendTemperatureChanged(self):
def _onTargetHotendTemperatureChanged(self) -> None:
if not self._preheat_hotends_timer.isActive():
return
if not self._active_printer:
return
for extruder in self._active_printer.extruders:
if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
@ -123,7 +129,7 @@ class GenericOutputController(PrinterOutputController):
if not self._preheat_hotends:
self._preheat_hotends_timer.stop()
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
position = extruder.getPosition()
number_of_extruders = len(extruder.getPrinter().extruders)
if position >= number_of_extruders:
@ -141,7 +147,7 @@ class GenericOutputController(PrinterOutputController):
self._preheat_hotends.add(extruder)
extruder.updateIsPreheating(True)
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
if extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
@ -149,21 +155,22 @@ class GenericOutputController(PrinterOutputController):
if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
self._preheat_hotends_timer.stop()
def _onPreheatHotendsTimerFinished(self):
def _onPreheatHotendsTimerFinished(self) -> None:
for extruder in self._preheat_hotends:
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
self._preheat_hotends = set()
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
# Cancel any ongoing preheating timers, without setting back the temperature to 0
# This can be used eg at the start of a print
def stopPreheatTimers(self):
def stopPreheatTimers(self) -> None:
if self._preheat_hotends_timer.isActive():
for extruder in self._preheat_hotends:
extruder.updateIsPreheating(False)
self._preheat_hotends = set()
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
self._preheat_hotends_timer.stop()
if self._preheat_bed_timer.isActive():
if self._preheat_printer:
self._preheat_printer.updateIsPreheating(False)
self._preheat_bed_timer.stop()

View File

@ -91,7 +91,7 @@ class PrintJobOutputModel(QObject):
def assignedPrinter(self):
return self._assigned_printer
def updateAssignedPrinter(self, assigned_printer: "PrinterOutputModel"):
def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]) -> None:
if self._assigned_printer != assigned_printer:
old_printer = self._assigned_printer
self._assigned_printer = assigned_printer

View File

@ -1,57 +1,68 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from UM.Signal import Signal
from typing import Union
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
class PrinterOutputController:
def __init__(self, output_device):
def __init__(self, output_device: "PrinterOutputDevice") -> None:
self.can_pause = True
self.can_abort = True
self.can_pre_heat_bed = True
self.can_pre_heat_hotends = True
self.can_send_raw_gcode = True
self.can_control_manually = True
self.can_update_firmware = False
self._output_device = output_device
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
Logger.log("w", "Set target hotend temperature not implemented in controller")
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
Logger.log("w", "Set target bed temperature not implemented in controller")
def setJobState(self, job: "PrintJobOutputModel", state: str):
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
Logger.log("w", "Set job state not implemented in controller")
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
Logger.log("w", "Cancel preheat bed not implemented in controller")
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
Logger.log("w", "Preheat bed not implemented in controller")
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
Logger.log("w", "Cancel preheat hotend not implemented in controller")
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
Logger.log("w", "Preheat hotend not implemented in controller")
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
Logger.log("w", "Set head position not implemented in controller")
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
Logger.log("w", "Move head not implemented in controller")
def homeBed(self, printer: "PrinterOutputModel"):
def homeBed(self, printer: "PrinterOutputModel") -> None:
Logger.log("w", "Home bed not implemented in controller")
def homeHead(self, printer: "PrinterOutputModel"):
def homeHead(self, printer: "PrinterOutputModel") -> None:
Logger.log("w", "Home head not implemented in controller")
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
Logger.log("w", "Custom command not implemented in controller")
canUpdateFirmwareChanged = Signal()
def setCanUpdateFirmware(self, can_update_firmware: bool) -> None:
if can_update_firmware != self.can_update_firmware:
self.can_update_firmware = can_update_firmware
self.canUpdateFirmwareChanged.emit()

View File

@ -2,7 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
from typing import Optional
from typing import List, Dict, Optional
from UM.Math.Vector import Vector
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
@ -11,6 +11,7 @@ MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.NetworkCamera import NetworkCamera
class PrinterOutputModel(QObject):
@ -26,6 +27,7 @@ class PrinterOutputModel(QObject):
buildplateChanged = pyqtSignal()
cameraChanged = pyqtSignal()
configurationChanged = pyqtSignal()
canUpdateFirmwareChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
super().__init__(parent)
@ -34,6 +36,7 @@ class PrinterOutputModel(QObject):
self._name = ""
self._key = "" # Unique identifier
self._controller = output_controller
self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
self._head_position = Vector(0, 0, 0)
@ -42,7 +45,7 @@ class PrinterOutputModel(QObject):
self._printer_state = "unknown"
self._is_preheating = False
self._printer_type = ""
self._buildplate_name = None
self._buildplate_name = ""
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
self._extruders]
@ -50,32 +53,32 @@ class PrinterOutputModel(QObject):
self._camera = None
@pyqtProperty(str, constant = True)
def firmwareVersion(self):
def firmwareVersion(self) -> str:
return self._firmware_version
def setCamera(self, camera):
def setCamera(self, camera: Optional["NetworkCamera"]) -> None:
if self._camera is not camera:
self._camera = camera
self.cameraChanged.emit()
def updateIsPreheating(self, pre_heating):
def updateIsPreheating(self, pre_heating: bool) -> None:
if self._is_preheating != pre_heating:
self._is_preheating = pre_heating
self.isPreheatingChanged.emit()
@pyqtProperty(bool, notify=isPreheatingChanged)
def isPreheating(self):
def isPreheating(self) -> bool:
return self._is_preheating
@pyqtProperty(QObject, notify=cameraChanged)
def camera(self):
def camera(self) -> Optional["NetworkCamera"]:
return self._camera
@pyqtProperty(str, notify = printerTypeChanged)
def type(self):
def type(self) -> str:
return self._printer_type
def updateType(self, printer_type):
def updateType(self, printer_type: str) -> None:
if self._printer_type != printer_type:
self._printer_type = printer_type
self._printer_configuration.printerType = self._printer_type
@ -83,10 +86,10 @@ class PrinterOutputModel(QObject):
self.configurationChanged.emit()
@pyqtProperty(str, notify = buildplateChanged)
def buildplate(self):
def buildplate(self) -> str:
return self._buildplate_name
def updateBuildplateName(self, buildplate_name):
def updateBuildplateName(self, buildplate_name: str) -> None:
if self._buildplate_name != buildplate_name:
self._buildplate_name = buildplate_name
self._printer_configuration.buildplateConfiguration = self._buildplate_name
@ -94,66 +97,66 @@ class PrinterOutputModel(QObject):
self.configurationChanged.emit()
@pyqtProperty(str, notify=keyChanged)
def key(self):
def key(self) -> str:
return self._key
def updateKey(self, key: str):
def updateKey(self, key: str) -> None:
if self._key != key:
self._key = key
self.keyChanged.emit()
@pyqtSlot()
def homeHead(self):
def homeHead(self) -> None:
self._controller.homeHead(self)
@pyqtSlot()
def homeBed(self):
def homeBed(self) -> None:
self._controller.homeBed(self)
@pyqtSlot(str)
def sendRawCommand(self, command: str):
def sendRawCommand(self, command: str) -> None:
self._controller.sendRawCommand(self, command)
@pyqtProperty("QVariantList", constant = True)
def extruders(self):
def extruders(self) -> List["ExtruderOutputModel"]:
return self._extruders
@pyqtProperty(QVariant, notify = headPositionChanged)
def headPosition(self):
def headPosition(self) -> Dict[str, float]:
return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z}
def updateHeadPosition(self, x, y, z):
def updateHeadPosition(self, x: float, y: float, z: float) -> None:
if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
self._head_position = Vector(x, y, z)
self.headPositionChanged.emit()
@pyqtProperty(float, float, float)
@pyqtProperty(float, float, float, float)
def setHeadPosition(self, x, y, z, speed = 3000):
def setHeadPosition(self, x: float, y: float, z: float, speed: float = 3000) -> None:
self.updateHeadPosition(x, y, z)
self._controller.setHeadPosition(self, x, y, z, speed)
@pyqtProperty(float)
@pyqtProperty(float, float)
def setHeadX(self, x, speed = 3000):
def setHeadX(self, x: float, speed: float = 3000) -> None:
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
@pyqtProperty(float)
@pyqtProperty(float, float)
def setHeadY(self, y, speed = 3000):
def setHeadY(self, y: float, speed: float = 3000) -> None:
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
@pyqtProperty(float)
@pyqtProperty(float, float)
def setHeadZ(self, z, speed = 3000):
def setHeadZ(self, z: float, speed:float = 3000) -> None:
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
@pyqtSlot(float, float, float)
@pyqtSlot(float, float, float, float)
def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None:
self._controller.moveHead(self, x, y, z, speed)
## Pre-heats the heated bed of the printer.
@ -162,47 +165,47 @@ class PrinterOutputModel(QObject):
# Celsius.
# \param duration How long the bed should stay warm, in seconds.
@pyqtSlot(float, float)
def preheatBed(self, temperature, duration):
def preheatBed(self, temperature: float, duration: float) -> None:
self._controller.preheatBed(self, temperature, duration)
@pyqtSlot()
def cancelPreheatBed(self):
def cancelPreheatBed(self) -> None:
self._controller.cancelPreheatBed(self)
def getController(self):
def getController(self) -> "PrinterOutputController":
return self._controller
@pyqtProperty(str, notify=nameChanged)
def name(self):
def name(self) -> str:
return self._name
def setName(self, name):
def setName(self, name: str) -> None:
self._setName(name)
self.updateName(name)
def updateName(self, name):
def updateName(self, name: str) -> None:
if self._name != name:
self._name = name
self.nameChanged.emit()
## Update the bed temperature. This only changes it locally.
def updateBedTemperature(self, temperature):
def updateBedTemperature(self, temperature: int) -> None:
if self._bed_temperature != temperature:
self._bed_temperature = temperature
self.bedTemperatureChanged.emit()
def updateTargetBedTemperature(self, temperature):
def updateTargetBedTemperature(self, temperature: int) -> None:
if self._target_bed_temperature != temperature:
self._target_bed_temperature = temperature
self.targetBedTemperatureChanged.emit()
## Set the target bed temperature. This ensures that it's actually sent to the remote.
@pyqtSlot(int)
def setTargetBedTemperature(self, temperature):
def setTargetBedTemperature(self, temperature: int) -> None:
self._controller.setTargetBedTemperature(self, temperature)
self.updateTargetBedTemperature(temperature)
def updateActivePrintJob(self, print_job):
def updateActivePrintJob(self, print_job: Optional["PrintJobOutputModel"]) -> None:
if self._active_print_job != print_job:
old_print_job = self._active_print_job
@ -214,72 +217,83 @@ class PrinterOutputModel(QObject):
old_print_job.updateAssignedPrinter(None)
self.activePrintJobChanged.emit()
def updateState(self, printer_state):
def updateState(self, printer_state: str) -> None:
if self._printer_state != printer_state:
self._printer_state = printer_state
self.stateChanged.emit()
@pyqtProperty(QObject, notify = activePrintJobChanged)
def activePrintJob(self):
def activePrintJob(self) -> Optional["PrintJobOutputModel"]:
return self._active_print_job
@pyqtProperty(str, notify=stateChanged)
def state(self):
def state(self) -> str:
return self._printer_state
@pyqtProperty(int, notify=bedTemperatureChanged)
def bedTemperature(self):
def bedTemperature(self) -> int:
return self._bed_temperature
@pyqtProperty(int, notify=targetBedTemperatureChanged)
def targetBedTemperature(self):
def targetBedTemperature(self) -> int:
return self._target_bed_temperature
# Does the printer support pre-heating the bed at all
@pyqtProperty(bool, constant=True)
def canPreHeatBed(self):
def canPreHeatBed(self) -> bool:
if self._controller:
return self._controller.can_pre_heat_bed
return False
# Does the printer support pre-heating the bed at all
@pyqtProperty(bool, constant=True)
def canPreHeatHotends(self):
def canPreHeatHotends(self) -> bool:
if self._controller:
return self._controller.can_pre_heat_hotends
return False
# Does the printer support sending raw G-code at all
@pyqtProperty(bool, constant=True)
def canSendRawGcode(self):
def canSendRawGcode(self) -> bool:
if self._controller:
return self._controller.can_send_raw_gcode
return False
# Does the printer support pause at all
@pyqtProperty(bool, constant=True)
def canPause(self):
def canPause(self) -> bool:
if self._controller:
return self._controller.can_pause
return False
# Does the printer support abort at all
@pyqtProperty(bool, constant=True)
def canAbort(self):
def canAbort(self) -> bool:
if self._controller:
return self._controller.can_abort
return False
# Does the printer support manual control at all
@pyqtProperty(bool, constant=True)
def canControlManually(self):
def canControlManually(self) -> bool:
if self._controller:
return self._controller.can_control_manually
return False
# Does the printer support upgrading firmware
@pyqtProperty(bool, notify = canUpdateFirmwareChanged)
def canUpdateFirmware(self) -> bool:
if self._controller:
return self._controller.can_update_firmware
return False
# Stub to connect UM.Signal to pyqtSignal
def _onControllerCanUpdateFirmwareChanged(self) -> None:
self.canUpdateFirmwareChanged.emit()
# Returns the configuration (material, variant and buildplate) of the current printer
@pyqtProperty(QObject, notify = configurationChanged)
def printerConfiguration(self):
def printerConfiguration(self) -> Optional[ConfigurationModel]:
if self._printer_configuration.isValid():
return self._printer_configuration
return None

View File

@ -4,22 +4,24 @@
from UM.Decorators import deprecated
from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
from UM.Logger import Logger
from UM.FileHandler.FileHandler import FileHandler #For typing.
from UM.Scene.SceneNode import SceneNode #For typing.
from UM.Signal import signalemitter
from UM.Qt.QtApplication import QtApplication
from UM.FlameProfiler import pyqtSlot
from enum import IntEnum # For the connection state tracking.
from typing import Callable, List, Optional
from typing import Callable, List, Optional, Union
MYPY = False
if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
from UM.FileHandler.FileHandler import FileHandler
from UM.Scene.SceneNode import SceneNode
i18n_catalog = i18nCatalog("cura")
@ -83,6 +85,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._connection_state = ConnectionState.closed #type: ConnectionState
self._firmware_updater = None #type: Optional[FirmwareUpdater]
self._firmware_name = None #type: Optional[str]
self._address = "" #type: str
self._connection_text = "" #type: str
@ -128,7 +131,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
return None
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
@pyqtProperty(QObject, notify = printersChanged)
@ -226,3 +229,13 @@ class PrinterOutputDevice(QObject, OutputDevice):
# This name can be used to define device type
def getFirmwareName(self) -> Optional[str]:
return self._firmware_name
def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
return self._firmware_updater
@pyqtSlot(str)
def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
if not self._firmware_updater:
return
self._firmware_updater.updateFirmware(firmware_file)

View File

@ -4,7 +4,7 @@
from collections import defaultdict
import threading
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
from PyQt5.QtCore import pyqtProperty
from PyQt5.QtCore import pyqtProperty, pyqtSlot
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
@ -13,6 +13,8 @@ from UM.Settings.SettingInstance import InstanceState
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import PropertyEvaluationContext
from UM.Logger import Logger
from UM.Resources import Resources
from UM.Platform import Platform
from UM.Util import parseBool
import cura.CuraApplication
@ -200,6 +202,31 @@ class GlobalStack(CuraContainerStack):
def getHasMachineQuality(self) -> bool:
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
## Get default firmware file name if one is specified in the firmware
@pyqtSlot(result = str)
def getDefaultFirmwareName(self) -> str:
machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")
baudrate = 250000
if Platform.isLinux():
# Linux prefers a baudrate of 115200 here because older versions of
# pySerial did not support a baudrate of 250000
baudrate = 115200
# If a firmware file is available, it should be specified in the definition for the printer
hex_file = self.getMetaDataEntry("firmware_file", None)
if machine_has_heated_bed:
hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file)
if not hex_file:
Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id)
return ""
try:
return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
except FileNotFoundError:
Logger.log("w", "Firmware file %s not found.", hex_file)
return ""
## private:
global_stack_mime = MimeType(

View File

@ -0,0 +1,69 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from cura.CuraApplication import CuraApplication
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.MachineAction import MachineAction
from UM.i18n import i18nCatalog
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdateState
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject
from typing import Optional
MYPY = False
if MYPY:
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
from UM.Settings.ContainerInterface import ContainerInterface
catalog = i18nCatalog("cura")
## Upgrade the firmware of a machine by USB with this action.
class FirmwareUpdaterMachineAction(MachineAction):
def __init__(self) -> None:
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Update Firmware"))
self._qml_url = "FirmwareUpdaterMachineAction.qml"
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
self._active_output_device = None # type: Optional[PrinterOutputDevice]
self._active_firmware_updater = None # type: Optional[FirmwareUpdater]
CuraApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
def _onEngineCreated(self) -> None:
CuraApplication.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
def _onContainerAdded(self, container: "ContainerInterface") -> None:
# Add this action as a supported action to all machine definitions if they support USB connection
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
def _onOutputDevicesChanged(self) -> None:
if self._active_output_device and self._active_output_device.activePrinter:
self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged)
output_devices = CuraApplication.getInstance().getMachineManager().printerOutputDevices
self._active_output_device = output_devices[0] if output_devices else None
if self._active_output_device and self._active_output_device.activePrinter:
self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
self.outputDeviceCanUpdateFirmwareChanged.emit()
def _onControllerCanUpdateFirmwareChanged(self) -> None:
self.outputDeviceCanUpdateFirmwareChanged.emit()
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware:
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
return self._active_firmware_updater
elif self._active_firmware_updater and self._active_firmware_updater.firmwareUpdateState not in [FirmwareUpdateState.idle, FirmwareUpdateState.completed]:
# During a firmware update, the PrinterOutputDevice is disconnected but the FirmwareUpdater is still there
return self._active_firmware_updater
self._active_firmware_updater = None
return None

View File

@ -0,0 +1,191 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import QtQuick.Dialogs 1.2 // For filedialog
import UM 1.2 as UM
import Cura 1.0 as Cura
Cura.MachineAction
{
anchors.fill: parent;
property bool printerConnected: Cura.MachineManager.printerConnected
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
property bool canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false
Column
{
id: firmwareUpdaterMachineAction
anchors.fill: parent;
UM.I18nCatalog { id: catalog; name:"cura"}
spacing: UM.Theme.getSize("default_margin").height
Label
{
width: parent.width
text: catalog.i18nc("@title", "Update Firmware")
wrapMode: Text.WordWrap
font.pointSize: 18
}
Label
{
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
}
Label
{
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
}
Row
{
anchors.horizontalCenter: parent.horizontalCenter
width: childrenRect.width
spacing: UM.Theme.getSize("default_margin").width
property string firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName()
Button
{
id: autoUpgradeButton
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
enabled: parent.firmwareName != "" && canUpdateFirmware
onClicked:
{
updateProgressDialog.visible = true;
activeOutputDevice.updateFirmware(parent.firmwareName);
}
}
Button
{
id: manualUpgradeButton
text: catalog.i18nc("@action:button", "Upload custom Firmware");
enabled: canUpdateFirmware
onClicked:
{
customFirmwareDialog.open()
}
}
}
Label
{
width: parent.width
wrapMode: Text.WordWrap
visible: !printerConnected && !updateProgressDialog.visible
text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer.");
}
Label
{
width: parent.width
wrapMode: Text.WordWrap
visible: printerConnected && !canUpdateFirmware
text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware.");
}
}
FileDialog
{
id: customFirmwareDialog
title: catalog.i18nc("@title:window", "Select custom firmware")
nameFilters: "Firmware image files (*.hex)"
selectExisting: true
onAccepted:
{
updateProgressDialog.visible = true;
activeOutputDevice.updateFirmware(fileUrl);
}
}
UM.Dialog
{
id: updateProgressDialog
width: minimumWidth
minimumWidth: 500 * screenScaleFactor
height: minimumHeight
minimumHeight: 100 * screenScaleFactor
modality: Qt.ApplicationModal
title: catalog.i18nc("@title:window","Firmware Update")
Column
{
anchors.fill: parent
Label
{
anchors
{
left: parent.left
right: parent.right
}
text: {
if(manager.firmwareUpdater == null)
{
return "";
}
switch (manager.firmwareUpdater.firmwareUpdateState)
{
case 0:
return ""; //Not doing anything (eg; idling)
case 1:
return catalog.i18nc("@label","Updating firmware.");
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.");
}
}
wrapMode: Text.Wrap
}
ProgressBar
{
id: prog
value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress : 0
minimumValue: 0
maximumValue: 100
indeterminate:
{
if(manager.firmwareUpdater == null)
{
return false;
}
return manager.firmwareUpdater.firmwareProgress < 1 && manager.firmwareUpdater.firmwareProgress > 0;
}
anchors
{
left: parent.left;
right: parent.right;
}
}
}
rightButtons: [
Button
{
text: catalog.i18nc("@action:button","Close");
enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true;
onClicked: updateProgressDialog.visible = false;
}
]
}
}

View File

@ -0,0 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import FirmwareUpdaterMachineAction
def getMetaData():
return {}
def register(app):
return { "machine_action": [
FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction()
]}

View File

@ -0,0 +1,8 @@
{
"name": "Firmware Updater",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a machine actions for updating firmware.",
"api": 5,
"i18n-catalog": "cura"
}

View File

@ -0,0 +1,68 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState
from .avr_isp import stk500v2, intelHex
from serial import SerialException
from time import sleep
MYPY = False
if MYPY:
from cura.PrinterOutputDevice import PrinterOutputDevice
class AvrFirmwareUpdater(FirmwareUpdater):
def __init__(self, output_device: "PrinterOutputDevice") -> None:
super().__init__(output_device)
def _updateFirmware(self) -> None:
try:
hex_file = intelHex.readHex(self._firmware_location)
assert len(hex_file) > 0
except (FileNotFoundError, AssertionError):
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
# Ensure that other connections are closed.
if self._output_device.isConnected():
self._output_device.close()
try:
programmer.connect(self._output_device._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 as e:
Logger.log("e", "A serial port exception occured during firmware update: %s" % e)
self._setFirmwareUpdateState(FirmwareUpdateState.io_error)
return
except Exception as e:
Logger.log("e", "An unknown exception occured during firmware update: %s" % e)
self._setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
return
programmer.close()
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
CuraApplication.getInstance().callLater(self._output_device.connect)
self._cleanupAfterUpdate()

View File

@ -1,89 +0,0 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
import UM 1.1 as UM
UM.Dialog
{
id: base;
width: minimumWidth;
minimumWidth: 500 * screenScaleFactor;
height: minimumHeight;
minimumHeight: 100 * screenScaleFactor;
visible: true;
modality: Qt.ApplicationModal;
title: catalog.i18nc("@title:window","Firmware Update");
Column
{
anchors.fill: parent;
Label
{
anchors
{
left: parent.left;
right: parent.right;
}
text: {
switch (manager.firmwareUpdateState)
{
case 0:
return "" //Not doing anything (eg; idling)
case 1:
return catalog.i18nc("@label","Updating firmware.")
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.")
}
}
wrapMode: Text.Wrap;
}
ProgressBar
{
id: prog
value: manager.firmwareProgress
minimumValue: 0
maximumValue: 100
indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0
anchors
{
left: parent.left;
right: parent.right;
}
}
SystemPalette
{
id: palette;
}
UM.I18nCatalog { id: catalog; name: "cura"; }
}
rightButtons: [
Button
{
text: catalog.i18nc("@action:button","Close");
enabled: manager.firmwareUpdateCompleteStatus;
onClicked: base.visible = false;
}
]
}

View File

@ -4,7 +4,6 @@
from UM.Logger import Logger
from UM.i18n import i18nCatalog
from UM.Qt.Duration import DurationFormat
from UM.PluginRegistry import PluginRegistry
from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
@ -13,28 +12,21 @@ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob
from .avr_isp import stk500v2, intelHex
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl
from .AvrFirmwareUpdater import AvrFirmwareUpdater
from serial import Serial, SerialException, SerialTimeoutException
from threading import Thread, Event
from time import time, sleep
from time import time
from queue import Queue
from enum import IntEnum
from typing import Union, Optional, List, cast
import re
import functools # Used for reduce
import os
catalog = i18nCatalog("cura")
class USBPrinterOutputDevice(PrinterOutputDevice):
firmwareProgressChanged = pyqtSignal()
firmwareUpdateStateChanged = pyqtSignal()
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
super().__init__(serial_port)
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
@ -61,8 +53,6 @@ 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 # type: Optional[int]
self._is_printing = False # A print is being sent.
@ -75,11 +65,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._paused = False
self._firmware_view = None
self._firmware_location = None
self._firmware_progress = 0
self._firmware_update_state = FirmwareUpdateState.idle
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
# Queue for commands that need to be sent.
@ -88,6 +73,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received = Event()
self._command_received.set()
self._firmware_updater = AvrFirmwareUpdater(self)
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
# This is a callback function that checks if there is any printing in progress via USB when the application tries
@ -109,7 +96,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
## Reset USB device settings
#
def resetDeviceSettings(self):
def resetDeviceSettings(self) -> None:
self._firmware_name = None
## Request the current scene to be sent to a USB-connected printer.
@ -135,93 +122,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
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 = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
self._firmware_view.show()
@pyqtSlot(str)
def updateFirmware(self, file):
# the file path could be url-encoded.
if file.startswith("file://"):
self._firmware_location = QUrl(file).toLocalFile()
else:
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()
try:
hex_file = intelHex.readHex(self._firmware_location)
assert len(hex_file) > 0
except (FileNotFoundError, AssertionError):
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.
CuraApplication.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: List[str]):
@ -275,7 +175,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
# Ensure that a printer is created.
self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
controller = GenericOutputController(self)
controller.setCanUpdateFirmware(True)
self._printers = [PrinterOutputModel(output_controller=controller, number_of_extruders=num_extruders)]
self._printers[0].updateName(container_stack.getName())
self.setConnectionState(ConnectionState.connected)
self._update_thread.start()
@ -295,6 +197,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_queue.put(command)
else:
self._sendCommand(command)
def _sendCommand(self, command: Union[str, bytes]):
if self._serial is None or self._connection_state != ConnectionState.connected:
return
@ -445,7 +348,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
elapsed_time = int(time() - self._print_start_time)
print_job = self._printers[0].activePrintJob
if print_job is None:
print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= CuraApplication.getInstance().getPrintInformation().jobName)
controller = GenericOutputController(self)
controller.setCanUpdateFirmware(True)
print_job = PrintJobOutputModel(output_controller=controller, name=CuraApplication.getInstance().getPrintInformation().jobName)
print_job.updateState("printing")
self._printers[0].updateActivePrintJob(print_job)
@ -456,13 +361,3 @@ 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

View File

@ -2,14 +2,12 @@
# Cura is released under the terms of the LGPLv3 or higher.
import threading
import platform
import time
import serial.tools.list_ports
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
from UM.Resources import Resources
from UM.Signal import Signal, signalemitter
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.i18n import i18nCatalog
@ -87,65 +85,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._addRemovePorts(port_list)
time.sleep(5)
@pyqtSlot(result = str)
def getDefaultFirmwareName(self):
# Check if there is a valid global container stack
global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack:
Logger.log("e", "There is no global container stack. Can not update firmware.")
self._firmware_view.close()
return ""
# The bottom of the containerstack is the machine definition
machine_id = global_container_stack.getBottom().id
machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value")
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",
"bq_hephestos_2" : "MarlinHephestos2.hex",
"ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
"ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
"ultimaker_original_dual" : "MarlinUltimaker-{baudrate}-dual.hex",
"ultimaker2" : "MarlinUltimaker2.hex",
"ultimaker2_go" : "MarlinUltimaker2go.hex",
"ultimaker2_plus" : "MarlinUltimaker2plus.hex",
"ultimaker2_extended" : "MarlinUltimaker2extended.hex",
"ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
}
machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
"ultimaker_original_dual" : "MarlinUltimaker-HBK-{baudrate}-dual.hex",
}
##TODO: Add check for multiple extruders
hex_file = None
if machine_id in machine_without_extras.keys(): # The machine needs to be defined here!
if machine_id in machine_with_heated_bed.keys() and machine_has_heated_bed:
Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_id)
hex_file = machine_with_heated_bed[machine_id] # Return firmware with heated bed enabled
else:
Logger.log("d", "Choosing basic firmware for machine %s.", machine_id)
hex_file = machine_without_extras[machine_id] # Return "basic" firmware
else:
Logger.log("w", "There is no firmware for machine %s.", machine_id)
if hex_file:
try:
return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
except FileNotFoundError:
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
return ""
else:
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
return ""
## Helper to identify serial ports (and scan for them)
def _addRemovePorts(self, serial_ports):
# First, find and add all new or changed keys

View File

@ -2,9 +2,6 @@
# Cura is released under the terms of the LGPLv3 or higher.
from . import USBPrinterOutputDeviceManager
from PyQt5.QtQml import qmlRegisterSingletonType
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura")
def getMetaData():
@ -14,5 +11,4 @@ def getMetaData():
def register(app):
# We are violating the QT API here (as we use a factory, which is technically not allowed).
# but we don't really have another means for doing this (and it seems to you know -work-)
qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)}

View File

@ -17,7 +17,7 @@ Cura.MachineAction
property int rightRow: (checkupMachineAction.width * 0.60) | 0
property bool heatupHotendStarted: false
property bool heatupBedStarted: false
property bool usbConnected: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0
property bool printerConnected: Cura.MachineManager.printerConnected
UM.I18nCatalog { id: catalog; name:"cura"}
Label
@ -86,7 +86,7 @@ Cura.MachineAction
anchors.left: connectionLabel.right
anchors.top: parent.top
wrapMode: Text.WordWrap
text: checkupMachineAction.usbConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
text: checkupMachineAction.printerConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
}
//////////////////////////////////////////////////////////
Label
@ -97,7 +97,7 @@ Cura.MachineAction
anchors.top: connectionLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Min endstop X: ")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
Label
{
@ -107,7 +107,7 @@ Cura.MachineAction
anchors.top: connectionLabel.bottom
wrapMode: Text.WordWrap
text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
//////////////////////////////////////////////////////////////
Label
@ -118,7 +118,7 @@ Cura.MachineAction
anchors.top: endstopXLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Min endstop Y: ")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
Label
{
@ -128,7 +128,7 @@ Cura.MachineAction
anchors.top: endstopXLabel.bottom
wrapMode: Text.WordWrap
text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
/////////////////////////////////////////////////////////////////////
Label
@ -139,7 +139,7 @@ Cura.MachineAction
anchors.top: endstopYLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Min endstop Z: ")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
Label
{
@ -149,7 +149,7 @@ Cura.MachineAction
anchors.top: endstopYLabel.bottom
wrapMode: Text.WordWrap
text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
////////////////////////////////////////////////////////////
Label
@ -161,7 +161,7 @@ Cura.MachineAction
anchors.top: endstopZLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Nozzle temperature check: ")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
Label
{
@ -171,7 +171,7 @@ Cura.MachineAction
anchors.left: nozzleTempLabel.right
wrapMode: Text.WordWrap
text: catalog.i18nc("@info:status","Not checked")
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
Item
{
@ -181,7 +181,7 @@ Cura.MachineAction
anchors.top: nozzleTempLabel.top
anchors.left: bedTempStatus.right
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
Button
{
text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
@ -209,7 +209,7 @@ Cura.MachineAction
wrapMode: Text.WordWrap
text: manager.hotendTemperature + "°C"
font.bold: true
visible: checkupMachineAction.usbConnected
visible: checkupMachineAction.printerConnected
}
/////////////////////////////////////////////////////////////////////////////
Label
@ -221,7 +221,7 @@ Cura.MachineAction
anchors.top: nozzleTempLabel.bottom
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Build plate temperature check:")
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
}
Label
@ -232,7 +232,7 @@ Cura.MachineAction
anchors.left: bedTempLabel.right
wrapMode: Text.WordWrap
text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked")
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
}
Item
{
@ -242,7 +242,7 @@ Cura.MachineAction
anchors.top: bedTempLabel.top
anchors.left: bedTempStatus.right
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
Button
{
text: checkupMachineAction.heatupBedStarted ?catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
@ -270,7 +270,7 @@ Cura.MachineAction
wrapMode: Text.WordWrap
text: manager.bedTemperature + "°C"
font.bold: true
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
}
Label
{

View File

@ -1,19 +0,0 @@
from UM.Application import Application
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.MachineAction import MachineAction
from UM.i18n import i18nCatalog
from UM.Settings.ContainerRegistry import ContainerRegistry
catalog = i18nCatalog("cura")
## Upgrade the firmware of a machine by USB with this action.
class UpgradeFirmwareMachineAction(MachineAction):
def __init__(self):
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
self._qml_url = "UpgradeFirmwareMachineAction.qml"
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
def _onContainerAdded(self, container):
# Add this action as a supported action to all machine definitions if they support USB connection
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())

View File

@ -1,93 +0,0 @@
// Copyright (c) 2016 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import QtQuick.Dialogs 1.2 // For filedialog
import UM 1.2 as UM
import Cura 1.0 as Cura
Cura.MachineAction
{
anchors.fill: parent;
property bool printerConnected: Cura.MachineManager.printerConnected
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
Item
{
id: upgradeFirmwareMachineAction
anchors.fill: parent;
UM.I18nCatalog { id: catalog; name:"cura"}
Label
{
id: pageTitle
width: parent.width
text: catalog.i18nc("@title", "Upgrade Firmware")
wrapMode: Text.WordWrap
font.pointSize: 18
}
Label
{
id: pageDescription
anchors.top: pageTitle.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
}
Label
{
id: upgradeText1
anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
}
Row
{
anchors.top: upgradeText1.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
width: childrenRect.width
spacing: UM.Theme.getSize("default_margin").width
property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName()
Button
{
id: autoUpgradeButton
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
enabled: parent.firmwareName != "" && activeOutputDevice
onClicked:
{
activeOutputDevice.updateFirmware(parent.firmwareName)
}
}
Button
{
id: manualUpgradeButton
text: catalog.i18nc("@action:button", "Upload custom Firmware");
enabled: activeOutputDevice != null
onClicked:
{
customFirmwareDialog.open()
}
}
}
FileDialog
{
id: customFirmwareDialog
title: catalog.i18nc("@title:window", "Select custom firmware")
nameFilters: "Firmware image files (*.hex)"
selectExisting: true
onAccepted: activeOutputDevice.updateFirmware(fileUrl)
}
}
}

View File

@ -1,22 +1,16 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import BedLevelMachineAction
from . import UpgradeFirmwareMachineAction
from . import UMOUpgradeSelection
from . import UM2UpgradeSelection
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
def getMetaData():
return {
}
return {}
def register(app):
return { "machine_action": [
BedLevelMachineAction.BedLevelMachineAction(),
UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
UMOUpgradeSelection.UMOUpgradeSelection(),
UM2UpgradeSelection.UM2UpgradeSelection()
]}

View File

@ -118,6 +118,23 @@
}
}
},
"FirmwareUpdater": {
"package_info": {
"package_id": "FirmwareUpdater",
"package_type": "plugin",
"display_name": "Firmware Updater",
"description": "Provides a machine actions for updating firmware.",
"package_version": "1.0.0",
"sdk_version": 5,
"website": "https://ultimaker.com",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"GCodeGzReader": {
"package_info": {
"package_id": "GCodeGzReader",

View File

@ -12,7 +12,8 @@
"machine_extruder_trains":
{
"0": "bq_hephestos_extruder_0"
}
},
"firmware_file": "MarlinHephestos2.hex"
},
"overrides": {

View File

@ -12,7 +12,8 @@
"machine_extruder_trains":
{
"0": "bq_witbox_extruder_0"
}
},
"firmware_file": "MarlinWitbox.hex"
},
"overrides": {

View File

@ -8,7 +8,6 @@
"manufacturer": "NA",
"file_formats": "text/x-gcode",
"has_materials": false,
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
"machine_extruder_trains":
{
"0": "makeit_l_dual_1st",

View File

@ -8,7 +8,6 @@
"manufacturer": "NA",
"file_formats": "text/x-gcode",
"has_materials": false,
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
"machine_extruder_trains":
{
"0": "makeit_dual_1st",

View File

@ -10,7 +10,6 @@
"platform": "tam_series1.stl",
"platform_offset": [-580.0, -6.23, 253.5],
"has_materials": false,
"supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "tam_extruder_0"

View File

@ -17,11 +17,12 @@
"preferred_variant_name": "0.4 mm",
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
"first_start_actions": ["UM2UpgradeSelection"],
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"],
"supported_actions":["UM2UpgradeSelection"],
"machine_extruder_trains":
{
"0": "ultimaker2_extruder_0"
}
},
"firmware_file": "MarlinUltimaker2.hex"
},
"overrides": {
"machine_name": { "default_value": "Ultimaker 2" },

View File

@ -14,7 +14,8 @@
"machine_extruder_trains":
{
"0": "ultimaker2_extended_extruder_0"
}
},
"firmware_file": "MarlinUltimaker2extended.hex"
},
"overrides": {

View File

@ -10,11 +10,11 @@
"file_formats": "text/x-gcode",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2ExtendedPlusbackplate.png",
"supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "ultimaker2_extended_plus_extruder_0"
}
},
"firmware_file": "MarlinUltimaker2extended-plus.hex"
},
"overrides": {

View File

@ -13,11 +13,11 @@
"platform_texture": "Ultimaker2Gobackplate.png",
"platform_offset": [0, 0, 0],
"first_start_actions": [],
"supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "ultimaker2_go_extruder_0"
}
},
"firmware_file": "MarlinUltimaker2go.hex"
},
"overrides": {

View File

@ -15,11 +15,11 @@
"has_machine_materials": true,
"has_machine_quality": true,
"first_start_actions": [],
"supported_actions": ["UpgradeFirmware"],
"machine_extruder_trains":
{
"0": "ultimaker2_plus_extruder_0"
}
},
"firmware_file": "MarlinUltimaker2plus.hex"
},
"overrides": {

View File

@ -14,11 +14,13 @@
"has_machine_quality": true,
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"],
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
"machine_extruder_trains":
{
"0": "ultimaker_original_extruder_0"
}
},
"firmware_file": "MarlinUltimaker-{baudrate}.hex",
"firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}.hex"
},
"overrides": {

View File

@ -19,8 +19,10 @@
"0": "ultimaker_original_dual_1st",
"1": "ultimaker_original_dual_2nd"
},
"firmware_file": "MarlinUltimaker-{baudrate}-dual.hex",
"firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}-dual.hex",
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"]
},
"overrides": {

View File

@ -12,11 +12,12 @@
"platform_texture": "UltimakerPlusbackplate.png",
"quality_definition": "ultimaker_original",
"first_start_actions": ["UMOCheckup", "BedLevel"],
"supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"],
"supported_actions": ["UMOCheckup", "BedLevel"],
"machine_extruder_trains":
{
"0": "ultimaker_original_plus_extruder_0"
}
},
"firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex"
},
"overrides": {

View File

@ -18,9 +18,6 @@
0,
-28,
0
],
"supported_actions": [
"UpgradeFirmware"
]
},
"overrides": {