diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
new file mode 100644
index 0000000000..1c099705b1
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from UM.Extension import Extension
+from UM.Preferences import Preferences
+from UM.Logger import Logger
+from UM.i18n import i18nCatalog
+from cura.Settings.GlobalStack import GlobalStack
+
+from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob
+from UM.Settings.ContainerRegistry import ContainerRegistry
+
+i18n_catalog = i18nCatalog("cura")
+
+
+## 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
+# to change it to work for other applications.
+class FirmwareUpdateChecker(Extension):
+ JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version"
+
+ def __init__(self):
+ super().__init__()
+
+ # 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
+ Preferences.getInstance().addPreference("info/latest_checked_firmware", "")
+
+ # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
+ # 'check for updates' option
+ Preferences.getInstance().addPreference("info/automatic_update_check", True)
+ if Preferences.getInstance().getValue("info/automatic_update_check"):
+ ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
+
+ def _onContainerAdded(self, container):
+ # Only take care when a new GlobalStack was added
+ if isinstance(container, GlobalStack):
+ Logger.log("i", "You have a '%s' in printer list. Let's check the firmware!", container.getId())
+ self.checkFirmwareVersion(container, True)
+
+ ## Connect with software.ultimaker.com, load latest.version and check version info.
+ # If the version info is different from the current version, spawn a message to
+ # allow the user to download it.
+ #
+ # \param silent type(boolean) Suppresses messages other than "new version found" messages.
+ # This is used when checking for a new firmware version at startup.
+ def checkFirmwareVersion(self, container = None, silent = False):
+ job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL)
+ job.start()
diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
new file mode 100644
index 0000000000..108cfa4c0d
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from UM.Preferences import Preferences
+from UM.Application import Application
+from UM.Message import Message
+from UM.Logger import Logger
+from UM.Job import Job
+
+import urllib.request
+import codecs
+
+from PyQt5.QtCore import QUrl
+from PyQt5.QtGui import QDesktopServices
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+
+## This job checks if there is an update available on the provided URL.
+class FirmwareUpdateCheckerJob(Job):
+ def __init__(self, container = None, silent = False, url = None):
+ super().__init__()
+ self._container = container
+ self.silent = silent
+ self._url = url
+ self._download_url = None # If an update was found, the download_url will be set to the location of the new version.
+
+ ## Callback for the message that is spawned when there is a new version.
+ def actionTriggered(self, message, action):
+ if action == "download":
+ if self._download_url is not None:
+ QDesktopServices.openUrl(QUrl(self._download_url))
+
+ def run(self):
+ self._download_url = None # Reset download ur.
+ if not self._url:
+ Logger.log("e", "Can not check for a new release. URL not set!")
+ return
+
+ try:
+ request = urllib.request.Request(self._url)
+ current_version_file = urllib.request.urlopen(request)
+ reader = codecs.getreader("utf-8")
+
+ # get machine name from the definition container
+ machine_name = self._container.definition.getName()
+ machine_name_parts = machine_name.lower().split(" ")
+
+ # 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
+ # other Ultimaker 3 that will come in the future
+ if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]:
+ # Nothing to parse, just get the string
+ # 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()
+ Logger.log("i", "Reading firmware version of %s: %s", machine_name, current_version)
+
+ # If it is the first time the version is checked, the checked_version is None
+ checked_version = Preferences.getInstance().getValue("info/latest_checked_firmware")
+
+ # 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
+ if (checked_version != "") and (checked_version != current_version):
+ message = Message(i18n_catalog.i18nc("@info", "New %s firmware available
To ensure that your "
+ "%s is equiped with the latest features it is recommended "
+ "to update the firmware regularly. This can be done on the "
+ "%s (when connected to the network) or via USB."
+ % (machine_name, machine_name, machine_name)))
+ message.addAction("download", i18n_catalog.i18nc("@action:button", "Download"), "[no_icon]", "[no_description]")
+
+ # If we do this in a cool way, the download url should be available in the JSON file
+ self._download_url = "https://ultimaker.com/en/resources/20500-upgrade-firmware"
+ message.actionTriggered.connect(self.actionTriggered)
+ Application.getInstance().showMessage(message)
+
+ # The first time we want to store the current version, the notification will not be shown,
+ # because the new version of Cura will be release before the firmware and we don't want to
+ # notify the user when no new firmware version is available.
+ Preferences.getInstance().setValue("info/latest_checked_firmware", current_version)
+
+ except Exception as e:
+ Logger.log("w", "Failed to check for new version: %s", e)
+ if not self.silent:
+ Message(i18n_catalog.i18nc("@info", "Could not access update information.")).show()
+ return
diff --git a/plugins/FirmwareUpdateChecker/__init__.py b/plugins/FirmwareUpdateChecker/__init__.py
new file mode 100644
index 0000000000..b7343dc565
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (c) 2017 Ultimaker B.V.
+# Cura is released under the terms of the AGPLv3 or higher.
+
+from UM.i18n import i18nCatalog
+
+from . import FirmwareUpdateChecker
+
+i18n_catalog = i18nCatalog("cura")
+
+
+def getMetaData():
+ return {}
+
+
+def register(app):
+ return {"extension": FirmwareUpdateChecker.FirmwareUpdateChecker()}
diff --git a/plugins/FirmwareUpdateChecker/plugin.json b/plugins/FirmwareUpdateChecker/plugin.json
new file mode 100644
index 0000000000..d6a9f9fbd7
--- /dev/null
+++ b/plugins/FirmwareUpdateChecker/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Firmware Update Checker",
+ "author": "Ultimaker B.V.",
+ "version": "1.0.0",
+ "description": "Checks for firmware updates.",
+ "api": 4,
+ "i18n-catalog": "cura"
+}