diff --git a/octoprint_firmwareupdater/__init__.py b/octoprint_firmwareupdater/__init__.py index 70bdc7b..88cf616 100644 --- a/octoprint_firmwareupdater/__init__.py +++ b/octoprint_firmwareupdater/__init__.py @@ -24,6 +24,7 @@ from octoprint_firmwareupdater.methods import bootcmdr from octoprint_firmwareupdater.methods import bossac from octoprint_firmwareupdater.methods import dfuprog +from octoprint_firmwareupdater.methods import dfuutil from octoprint_firmwareupdater.methods import lpc1768 from octoprint_firmwareupdater.methods import stm32flash from octoprint_firmwareupdater.methods import marlinbft @@ -52,6 +53,7 @@ def initialize(self): bootcmdr=bootcmdr._check_bootcmdr, bossac=bossac._check_bossac, dfuprogrammer=dfuprog._check_dfuprog, + dfuutil=dfuutil._check_dfuutil, lpc1768=lpc1768._check_lpc1768, stm32flash=stm32flash._check_stm32flash, marlinbft=marlinbft._check_marlinbft @@ -61,6 +63,7 @@ def initialize(self): bootcmdr=bootcmdr._flash_bootcmdr, bossac=bossac._flash_bossac, dfuprogrammer=dfuprog._flash_dfuprog, + dfuutil=dfuutil._flash_dfuutil, lpc1768=lpc1768._flash_lpc1768, stm32flash=stm32flash._flash_stm32flash, marlinbft=marlinbft._flash_marlinbft @@ -116,7 +119,8 @@ def flash_firmware(self): # Save the printer port self._logger.info("Printer port: {}".format(printer_port)) - self.set_profile_setting("serial_port", printer_port) + if printer_port != "undefined": + self.set_profile_setting("serial_port", printer_port) method = self.get_profile_setting("flash_method") self._logger.info("Flash method: {}".format(method)) @@ -554,6 +558,8 @@ def get_settings_defaults(self): "dfuprog_avrmcu": None, "dfuprog_commandline": "sudo {dfuprogrammer} {mcu} flash {firmware} --debug-level 10", "dfuprog_erasecommandline": "sudo {dfuprogrammer} {mcu} erase --debug-level 10 --force", + "dfuutil_path": None, + "dfuutil_commandline": "sudo {dfuutil} -a 0 -s 0x8000000:leave -D {firmware}", "stm32flash_path": None, "stm32flash_verify": True, "stm32flash_boot0pin": "rts", diff --git a/octoprint_firmwareupdater/methods/dfuutil.py b/octoprint_firmwareupdater/methods/dfuutil.py new file mode 100644 index 0000000..5f31b58 --- /dev/null +++ b/octoprint_firmwareupdater/methods/dfuutil.py @@ -0,0 +1,89 @@ +import re +import os +import sarge + +DFUUTIL_ERASING = "Erase " +DFUUTIL_WRITING = "Download " +DFUUTIL_NOACCESS = "Cannot open DFU device" +DFUUTIL_NODEVICE = "No DFU capable USB device available" + +def _check_dfuutil(self): + dfuutil_path = self.get_profile_setting("dfuutil_path") + pattern = re.compile("^(\/[^\0/]+)+$") + + if not pattern.match(dfuutil_path): + self._logger.error(u"Path to dfu-util is not valid: {path}".format(path=dfuutil_path)) + return False + elif dfuutil_path is None: + self._logger.error(u"Path to dfu-util is not set.") + return False + if not os.path.exists(dfuutil_path): + self._logger.error(u"Path to dfu-util does not exist: {path}".format(path=dfuutil_path)) + return False + elif not os.path.isfile(dfuutil_path): + self._logger.error(u"Path to dfu-util is not a file: {path}".format(path=dfuutil_path)) + return False + elif not os.access(dfuutil_path, os.X_OK): + self._logger.error(u"Path to dfu-util is not executable: {path}".format(path=dfuutil_path)) + return False + else: + return True + +def _flash_dfuutil(self, firmware=None, printer_port=None, **kwargs): + assert(firmware is not None) + + dfuutil_path = self.get_profile_setting("dfuutil_path") + working_dir = os.path.dirname(dfuutil_path) + + dfuutil_command = self.get_profile_setting("dfuutil_commandline") + dfuutil_command = dfuutil_command.replace("{dfuutil}", dfuutil_path) + dfuutil_command = dfuutil_command.replace("{firmware}", firmware) + + import sarge + self._logger.info(u"Running '{}' in {}".format(dfuutil_command, working_dir)) + self._send_status("progress", subtype="writing") + self._console_logger.info(dfuutil_command) + try: + p = sarge.run(dfuutil_command, cwd=working_dir, async_=True, stdout=sarge.Capture(buffer_size=1), stderr=sarge.Capture(buffer_size=1)) + p.wait_events() + + while p.returncode is None: + output = p.stderr.read(timeout=0.2).decode('utf-8') + if not output: + p.commands[0].poll() + continue + + for line in output.split("\n"): + if line.endswith("\r"): + line = line[:-1] + self._console_logger.info(u"> {}".format(line)) + + if DFUUTIL_ERASING in line: + self._logger.info(u"Erasing memory...") + self._send_status("progress", subtype="erasing") + elif DFUUTIL_WRITING in line: + self._logger.info(u"Writing memory...") + self._send_status("progress", subtype="writing") + elif DFUUTIL_NOACCESS in line: + raise FlashException("Cannot access DFU device") + elif DFUUTIL_NODEVICE in line: + raise FlashException("No DFU device found") + + if p.returncode == 0: + return True + else: + raise FlashException("dfu-util returned code {returncode}".format(returncode=p.returncode)) + + except FlashException as ex: + self._logger.error(u"Flashing failed. {error}.".format(error=ex.reason)) + self._send_status("flasherror", message=ex.reason) + return False + except: + self._logger.exception(u"Flashing failed. Unexpected error.") + self._send_status("flasherror") + return False + +class FlashException(Exception): + def __init__(self, reason, *args, **kwargs): + Exception.__init__(self, *args, **kwargs) + self.reason = reason diff --git a/octoprint_firmwareupdater/static/js/firmwareupdater.js b/octoprint_firmwareupdater/static/js/firmwareupdater.js index e790bba..dc0582d 100644 --- a/octoprint_firmwareupdater/static/js/firmwareupdater.js +++ b/octoprint_firmwareupdater/static/js/firmwareupdater.js @@ -138,6 +138,18 @@ $(function() { return self.dfuPathBroken() || self.dfuPathOk(); }); + // Observables for dfu-util config settings + self.configDfuUtilPath = ko.observable(); + self.configDfuUtilCommandLine = ko.observable(); + + // Observables for dfu-util UI messages + self.dfuUtilPathBroken = ko.observable(false); + self.dfuUtilPathOk = ko.observable(false); + self.dfuUtilPathText = ko.observable(); + self.dfuUtilPathHelpVisible = ko.computed(function() { + return self.dfuUtilPathBroken() || self.dfuUtilPathOk(); + }); + // Observables for stm32flash config settings self.configStm32flashPath = ko.observable(); self.configStm32flashVerify = ko.observable(); @@ -163,6 +175,7 @@ $(function() { self.showBossacConfig = ko.observable(false); self.showLpc1768Config = ko.observable(false); self.showDfuConfig = ko.observable(false); + self.showDfuUtilConfig = ko.observable(false); self.showStm32flashConfig = ko.observable(false); self.showMarlinBftConfig = ko.observable(false); @@ -482,6 +495,7 @@ $(function() { self.showBossacConfig(false); self.showLpc1768Config(false); self.showDfuConfig(false); + self.showDfuUtilConfig(false); self.showStm32flashConfig(false); self.showMarlinBftConfig(false); @@ -496,6 +510,8 @@ $(function() { self.showLpc1768Config(true); } else if(value == 'dfuprogrammer'){ self.showDfuConfig(true); + } else if(value == 'dfuutil'){ + self.showDfuUtilConfig(true); } else if(value == 'stm32flash'){ self.showStm32flashConfig(true); } else if(value == 'marlinbft'){ @@ -879,6 +895,10 @@ $(function() { self.configDfuCommandLine(self.getProfileSetting("dfuprog_commandline")); self.configDfuEraseCommandLine(self.getProfileSetting("dfuprog_erasecommandline")); + // Load the dfu-util settings + self.configDfuUtilPath(self.getProfileSetting("dfuutil_path")); + self.configDfuUtilCommandLine(self.getProfileSetting("dfuutil_commandline")); + // Load the lpc1768 settings self.configLpc1768Path(self.getProfileSetting("lpc1768_path")); self.configLpc1768UnmountCommand(self.getProfileSetting("lpc1768_unmount_command")); @@ -1000,6 +1020,10 @@ $(function() { profiles[index]["dfuprog_commandline"] = self.configDfuCommandLine(); profiles[index]["dfuprog_erasecommandline"] = self.configDfuEraseCommandLine(); + // DFU-Util settings + profiles[index]["dfuutil_path"] = self.configDfuUtilPath(); + profiles[index]["dfuutil_commandline"] = self.configDfuUtilCommandLine(); + // LPC176x settings profiles[index]["lpc1768_path"] = self.configLpc1768Path(); profiles[index]["lpc1768_unmount_command"] = self.configLpc1768UnmountCommand(); @@ -1127,6 +1151,10 @@ $(function() { self.dfuPathOk(false); self.dfuPathText(""); + self.dfuUtilPathBroken(false); + self.dfuUtilPathOk(false); + self.dfuUtilPathText(""); + self.lpc1768PathBroken(false); self.lpc1768PathOk(false); self.lpc1768PathText(""); @@ -1156,6 +1184,10 @@ $(function() { self.configDfuEraseCommandLine(self.profileDefaults["dfuprog_erasecommandline"]); }; + self.resetDfuUtilCommandLine = function() { + self.configDfuUtilCommandLine(self.profileDefaults["dfuutil_commandline"]); + }; + self.resetLpc1768UnmountCommand = function() { self.configLpc1768UnmountCommand(self.profileDefaults["lpc1768_unmount_command"]); } @@ -1343,6 +1375,44 @@ $(function() { } }; + self.testDfuUtilPath = function() { + var filePathRegEx = new RegExp("^(\/[^\0/]+)+$"); + + if (!filePathRegEx.test(self.configDfuUtilPath())) { + self.dfuUtilPathText(gettext("The path is not valid")); + self.dfuUtilPathOk(false); + self.dfuUtilPathBroken(true); + } else { + $.ajax({ + url: API_BASEURL + "util/test", + type: "POST", + dataType: "json", + data: JSON.stringify({ + command: "path", + path: self.configDfuUtilPath(), + check_type: "file", + check_access: "x" + }), + contentType: "application/json; charset=UTF-8", + success: function(response) { + if (!response.result) { + if (!response.exists) { + self.dfuUtilPathText(gettext("The path doesn't exist")); + } else if (!response.typeok) { + self.dfuUtilPathText(gettext("The path is not a file")); + } else if (!response.access) { + self.dfuUtilPathText(gettext("The path is not an executable")); + } + } else { + self.dfuUtilPathText(gettext("The path is valid")); + } + self.dfuUtilPathOk(response.result); + self.dfuUtilPathBroken(!response.result); + } + }) + } + }; + self.testStm32flashPath = function() { var filePathRegEx = new RegExp("^(\/[^\0/]+)+$"); diff --git a/octoprint_firmwareupdater/templates/firmwareupdater_settings.jinja2 b/octoprint_firmwareupdater/templates/firmwareupdater_settings.jinja2 index 8360bc6..5bb109f 100644 --- a/octoprint_firmwareupdater/templates/firmwareupdater_settings.jinja2 +++ b/octoprint_firmwareupdater/templates/firmwareupdater_settings.jinja2 @@ -126,6 +126,7 @@ + @@ -266,7 +267,23 @@