[CURA-5483] Support more than just the UM3(E) for the firmware-update-check (add S5 only for now).

This commit is contained in:
Remco Burema 2018-10-10 16:24:13 +02:00
parent 1951391a23
commit 10b5584ca6
2 changed files with 100 additions and 25 deletions

View file

@ -12,23 +12,34 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob, MachineId, get_settings_key_for_machine
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
## This Extension checks for new versions of the firmware based on the latest checked version number. ## This Extension checks for new versions of the firmware based on the latest checked version number.
# The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy # The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy
# to change it to work for other applications. # to change it to work for other applications.
class FirmwareUpdateChecker(Extension): class FirmwareUpdateChecker(Extension):
JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources" JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources"
UM_NEW_URL_TEMPLATE = "http://software.ultimaker.com/releases/firmware/{0}/stable/version.txt"
VERSION_URLS_PER_MACHINE = \
{
MachineId.UM3: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3.value)],
MachineId.UM3E: [JEDI_VERSION_URL, UM_NEW_URL_TEMPLATE.format(MachineId.UM3E.value)],
MachineId.S5: [UM_NEW_URL_TEMPLATE.format(MachineId.S5.value)]
}
# The 'new'-style URL is the only way to check for S5 firmware,
# and in the future, the UM3 line will also switch over, but for now the old 'JEDI'-style URL is still needed.
# TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer.
# See also the to-do in FirmWareCheckerJob.
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# Initialize the Preference called `latest_checked_firmware` that stores the last version # Initialize the Preference called `latest_checked_firmware` that stores the last version
# checked for the UM3. In the future if we need to check other printers' firmware # checked for each printer.
Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "") for machine_id in MachineId:
Application.getInstance().getPreferences().addPreference(get_settings_key_for_machine(machine_id), "")
# Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
# 'check for updates' option # 'check for updates' option
@ -68,7 +79,8 @@ class FirmwareUpdateChecker(Extension):
Logger.log("i", "A firmware update check is already running, do nothing.") Logger.log("i", "A firmware update check is already running, do nothing.")
return return
self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL, self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent,
urls = self.VERSION_URLS_PER_MACHINE,
callback = self._onActionTriggered, callback = self._onActionTriggered,
set_download_url_callback = self._onSetDownloadUrl) set_download_url_callback = self._onSetDownloadUrl)
self._check_job.start() self._check_job.start()

View file

@ -1,10 +1,13 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from enum import Enum, unique
from UM.Application import Application from UM.Application import Application
from UM.Message import Message from UM.Message import Message
from UM.Logger import Logger from UM.Logger import Logger
from UM.Job import Job from UM.Job import Job
from UM.Version import Version
import urllib.request import urllib.request
import codecs import codecs
@ -12,49 +15,104 @@ import codecs
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
# For UM-machines, these need to match the unique firmware-ID (also used in the URLs), i.o.t. only define in one place.
@unique
class MachineId(Enum):
UM3 = 9066
UM3E = 9511
S5 = 9051
def get_settings_key_for_machine(machine_id: MachineId) -> str:
return "info/latest_checked_firmware_for_{0}".format(machine_id.value)
def default_parse_version_response(response: str) -> Version:
raw_str = response.split('\n', 1)[0].rstrip()
return Version(raw_str.split('.')) # Split it into a list; the default parsing of 'single string' is different.
## This job checks if there is an update available on the provided URL. ## This job checks if there is an update available on the provided URL.
class FirmwareUpdateCheckerJob(Job): class FirmwareUpdateCheckerJob(Job):
def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None): MACHINE_PER_NAME = \
{
"ultimaker 3": MachineId.UM3,
"ultimaker 3 extended": MachineId.UM3E,
"ultimaker s5": MachineId.S5
}
PARSE_VERSION_URL_PER_MACHINE = \
{
MachineId.UM3: default_parse_version_response,
MachineId.UM3E: default_parse_version_response,
MachineId.S5: default_parse_version_response
}
REDIRECT_USER_PER_MACHINE = \
{
MachineId.UM3: "https://ultimaker.com/en/resources/20500-upgrade-firmware",
MachineId.UM3E: "https://ultimaker.com/en/resources/20500-upgrade-firmware",
MachineId.S5: "https://ultimaker.com/en/resources/20500-upgrade-firmware"
}
# TODO: Parse all of that from a file, because this will be a big mess of large static values which gets worse with each printer.
def __init__(self, container=None, silent=False, urls=None, callback=None, set_download_url_callback=None):
super().__init__() super().__init__()
self._container = container self._container = container
self.silent = silent self.silent = silent
self._url = url self._urls = urls
self._callback = callback self._callback = callback
self._set_download_url_callback = set_download_url_callback self._set_download_url_callback = set_download_url_callback
application_name = Application.getInstance().getApplicationName()
application_version = Application.getInstance().getVersion()
self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)}
def getUrlResponse(self, url: str) -> str:
request = urllib.request.Request(url, headers=self._headers)
current_version_file = urllib.request.urlopen(request)
reader = codecs.getreader("utf-8")
return reader(current_version_file).read(firstline=True)
def getCurrentVersionForMachine(self, machine_id: MachineId) -> Version:
max_version = Version([0, 0, 0])
machine_urls = self._urls.get(machine_id)
parse_function = self.PARSE_VERSION_URL_PER_MACHINE.get(machine_id)
if machine_urls is not None and parse_function is not None:
for url in machine_urls:
version = parse_function(self.getUrlResponse(url))
if version > max_version:
max_version = version
if max_version < Version([0, 0, 1]):
Logger.log('w', "MachineID {0} not handled!".format(repr(machine_id)))
return max_version
def run(self): def run(self):
if not self._url: if not self._urls or self._urls is None:
Logger.log("e", "Can not check for a new release. URL not set!") Logger.log("e", "Can not check for a new release. URL not set!")
return return
try: try:
application_name = Application.getInstance().getApplicationName()
headers = {"User-Agent": "%s - %s" % (application_name, Application.getInstance().getVersion())}
request = urllib.request.Request(self._url, headers = headers)
current_version_file = urllib.request.urlopen(request)
reader = codecs.getreader("utf-8")
# get machine name from the definition container # get machine name from the definition container
machine_name = self._container.definition.getName() machine_name = self._container.definition.getName()
machine_name_parts = machine_name.lower().split(" ") machine_name_parts = machine_name.lower().split(" ")
# If it is not None, then we compare between the checked_version and the current_version # If it is not None, then we compare between the checked_version and the current_version
# Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any machine_id = self.MACHINE_PER_NAME.get(machine_name.lower())
# other Ultimaker 3 that will come in the future if machine_id is not None:
if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]: Logger.log("i", "You have a {0} in the printer list. Let's check the firmware!".format(machine_name))
Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!")
# Nothing to parse, just get the string current_version = self.getCurrentVersionForMachine(machine_id)
# TODO: In the future may be done by parsing a JSON file with diferent version for each printer model
current_version = reader(current_version_file).readline().rstrip()
# If it is the first time the version is checked, the checked_version is '' # If it is the first time the version is checked, the checked_version is ''
checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware") setting_key_str = get_settings_key_for_machine(machine_id)
checked_version = Application.getInstance().getPreferences().getValue(setting_key_str)
# If the checked_version is '', it's because is the first time we check firmware and in this case # If the checked_version is '', it's because is the first time we check firmware and in this case
# we will not show the notification, but we will store it for the next time # we will not show the notification, but we will store it for the next time
Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version) Application.getInstance().getPreferences().setValue(setting_key_str, current_version)
Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version)
# The first time we want to store the current version, the notification will not be shown, # The first time we want to store the current version, the notification will not be shown,
@ -78,12 +136,17 @@ class FirmwareUpdateCheckerJob(Job):
button_style=Message.ActionButtonStyle.LINK, button_style=Message.ActionButtonStyle.LINK,
button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT) button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT)
# If we do this in a cool way, the download url should be available in the JSON file # If we do this in a cool way, the download url should be available in the JSON file
if self._set_download_url_callback: if self._set_download_url_callback:
self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware") redirect = self.REDIRECT_USER_PER_MACHINE.get(machine_id)
if redirect is not None:
self._set_download_url_callback(redirect)
else:
Logger.log('w', "No callback-url for firmware of {0}".format(repr(machine_id)))
message.actionTriggered.connect(self._callback) message.actionTriggered.connect(self._callback)
message.show() message.show()
else:
Logger.log('i', "No machine with name {0} in list of firmware to check.".format(repr(machine_id)))
except Exception as e: except Exception as e:
Logger.log("w", "Failed to check for new version: %s", e) Logger.log("w", "Failed to check for new version: %s", e)