From f041c6b4f38c53305549716220c2bfae647199b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 21 Jun 2013 20:50:57 +0200 Subject: [PATCH 1/2] First work on custom controls with printer feedback evaluation and presentation in the UI --- octoprint/printer.py | 8 ++++++++ octoprint/server.py | 3 +++ octoprint/settings.py | 25 ++++++++++++++++++++++++- octoprint/static/js/ui.js | 23 ++++++++++++++++++----- octoprint/util/comm.py | 19 +++++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/octoprint/printer.py b/octoprint/printer.py index 1927fbf..9eef63c 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -128,6 +128,11 @@ class Printer(): try: callback.sendUpdateTrigger(type) except: pass + def _sendFeedbackCommandOutput(self, name, output): + for callback in self._callbacks: + try: callback.sendFeedbackCommandOutput(name, output) + except: pass + #~~ printer commands def connect(self, port=None, baudrate=None): @@ -442,6 +447,9 @@ class Printer(): self._setProgressData(None, None, None, None) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) + def mcReceivedRegisteredMessage(self, command, output): + self._sendFeedbackCommandOutput(command, output) + #~~ sd file handling def getSdFiles(self): diff --git a/octoprint/server.py b/octoprint/server.py index badad6d..73c355b 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -95,6 +95,9 @@ class PrinterStateConnection(tornadio2.SocketConnection): def sendUpdateTrigger(self, type): self.emit("updateTrigger", type) + def sendFeedbackCommandOutput(self, name, output): + self.emit("feedbackCommandOutput", {"name": name, "output": output}) + def addLog(self, data): with self._logBacklogMutex: self._logBacklog.append(data) diff --git a/octoprint/settings.py b/octoprint/settings.py index 71251b4..82032f2 100644 --- a/octoprint/settings.py +++ b/octoprint/settings.py @@ -2,11 +2,11 @@ __author__ = "Gina Häußge " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' -import ConfigParser import sys import os import yaml import logging +import re APPNAME="OctoPrint" @@ -204,6 +204,29 @@ class Settings(object): return folder + def getFeedbackControls(self): + feedbackControls = [] + for control in self.get(["controls"]): + feedbackControls.extend(self._getFeedbackControls(control)) + return feedbackControls + + def _getFeedbackControls(self, control=None): + if control["type"] == "feedback_command": + pattern = control["regex"] + try: + matcher = re.compile(pattern) + return [(control["name"], matcher, control["template"])] + except: + # invalid regex or something like this, we'll just skip this entry + pass + elif control["type"] == "section": + result = [] + for c in control["children"]: + result.extend(self._getFeedbackControls(c)) + return result + else: + return [] + #~~ setter def set(self, path, value, force=False): diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index 4d85b34..e801be5 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -525,6 +525,8 @@ function ControlViewModel(loginStateViewModel) { self.extrusionAmount = ko.observable(undefined); self.controls = ko.observableArray([]); + self.feedbackControlLookup = {}; + self.fromCurrentData = function(data) { self._processStateData(data.state); } @@ -543,6 +545,12 @@ function ControlViewModel(loginStateViewModel) { self.isLoading(data.flags.loading); } + self._processFeedbackCommandData = function(data) { + if (data.name in self.feedbackControlLookup) { + self.feedbackControlLookup[data.name](data.output); + } + } + self.requestData = function() { $.ajax({ url: AJAX_BASEURL + "control/custom", @@ -555,23 +563,26 @@ function ControlViewModel(loginStateViewModel) { } self._fromResponse = function(response) { - self.controls(self._enhanceControls(response.controls)); + self.controls(self._processControls(response.controls)); } - self._enhanceControls = function(controls) { + self._processControls = function(controls) { for (var i = 0; i < controls.length; i++) { - controls[i] = self._enhanceControl(controls[i]); + controls[i] = self._processControl(controls[i]); } return controls; } - self._enhanceControl = function(control) { + self._processControl = function(control) { if (control.type == "parametric_command" || control.type == "parametric_commands") { for (var i = 0; i < control.input.length; i++) { control.input[i].value = control.input[i].default; } + } else if (control.type == "feedback_command") { + control.output = ko.observable(""); + self.feedbackControlLookup[control.name] = control.output; } else if (control.type == "section") { - control.children = self._enhanceControls(control.children); + control.children = self._processControls(control.children); } return control; } @@ -659,6 +670,8 @@ function ControlViewModel(loginStateViewModel) { case "parametric_command": case "parametric_commands": return "customControls_parametricCommandTemplate"; + case "feedback_command": + return "customControls_feedbackCommandTemplate"; default: return "customControls_emptyTemplate"; } diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index d1561d4..40c6abe 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -153,6 +153,9 @@ class VirtualPrinter(): # reset current line self.currentLine = int(re.search('N([0-9]+)', data).group(1)) self.readList.append("ok\n") + elif "M114" in data: + # send dummy position report + self.readList.append("ok C: X:10.00 Y:3.20 Z:5.20 E:1.24") elif self.currentLine == 100: # simulate a resend at line 100 of the last 5 lines self.readList.append("Error: Line Number is not Last Line Number\n") @@ -324,6 +327,9 @@ class MachineComPrintCallback(object): def mcFileTransferStarted(self, filename, filesize): pass + def mcReceivedRegisteredMessage(self, command, message): + pass + class MachineCom(object): STATE_NONE = 0 STATE_OPEN_SERIAL = 1 @@ -522,6 +528,8 @@ class MachineCom(object): return ret def _monitor(self): + feedbackControls = settings().getFeedbackControls() + #Open the serial port. if self._port == 'AUTO': self._changeState(self.STATE_DETECT_SERIAL) @@ -668,6 +676,17 @@ class MachineCom(object): elif line.strip() != '' and line.strip() != 'ok' and not line.startswith("wait") and not line.startswith('Resend:') and line != 'echo:Unknown command:""\n' and self.isOperational(): self._callback.mcMessage(line) + ##~~ Parsing for feedback commands + if feedbackControls: + for name, matcher, template in feedbackControls: + try: + match = matcher.search(line) + if match is not None: + self._callback.mcReceivedRegisteredMessage(name, template % match.groups("n/a")) + except: + # ignored on purpose + pass + if "ok" in line and heatingUp: heatingUp = False From 6600eea1a20ca5c63256dcb1c43e4aa6b001b0ae Mon Sep 17 00:00:00 2001 From: Bryan Mayland Date: Thu, 20 Jun 2013 17:41:10 +0200 Subject: [PATCH 2/2] Feedback commands now work --- octoprint/static/js/ui.js | 47 +++++++++++++++++--------------- octoprint/templates/index.jinja2 | 5 ++++ octoprint/util/comm.py | 2 +- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js index e801be5..6ccf5e3 100644 --- a/octoprint/static/js/ui.js +++ b/octoprint/static/js/ui.js @@ -545,7 +545,7 @@ function ControlViewModel(loginStateViewModel) { self.isLoading(data.flags.loading); } - self._processFeedbackCommandData = function(data) { + self.fromFeedbackCommandData = function(data) { if (data.name in self.feedbackControlLookup) { self.feedbackControlLookup[data.name](data.output); } @@ -632,7 +632,7 @@ function ControlViewModel(loginStateViewModel) { return; var data = undefined; - if (command.type == "command" || command.type == "parametric_command") { + if (command.type == "command" || command.type == "parametric_command" || command.type == "feedback_command") { // single command data = {"command" : command.command}; } else if (command.type == "commands" || command.type == "parametric_commands") { @@ -1500,7 +1500,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self.loginStateViewModel.requestData(); self.gcodeFilesViewModel.requestData(); } - }) + }); self._socket.on("disconnect", function() { $("#offline_overlay_message").html( "The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect " + @@ -1509,13 +1509,13 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM ); if (!$("#offline_overlay").is(":visible")) $("#offline_overlay").show(); - }) + }); self._socket.on("reconnect_failed", function() { $("#offline_overlay_message").html( "The server appears to be offline, at least I'm not getting any response from it. I could not reconnect automatically, " + "but you may try a manual reconnect using the button below." ); - }) + }); self._socket.on("history", function(data) { self.connectionViewModel.fromHistoryData(data); self.printerStateViewModel.fromHistoryData(data); @@ -1525,7 +1525,7 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self.timelapseViewModel.fromHistoryData(data); self.gcodeViewModel.fromHistoryData(data); self.gcodeFilesViewModel.fromCurrentData(data); - }) + }); self._socket.on("current", function(data) { self.connectionViewModel.fromCurrentData(data); self.printerStateViewModel.fromCurrentData(data); @@ -1535,12 +1535,15 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM self.timelapseViewModel.fromCurrentData(data); self.gcodeViewModel.fromCurrentData(data); self.gcodeFilesViewModel.fromCurrentData(data); - }) + }); self._socket.on("updateTrigger", function(type) { if (type == "gcodeFiles") { gcodeFilesViewModel.requestData(); } - }) + }); + self._socket.on("feedbackCommandOutput", function(data) { + self.controlViewModel.fromFeedbackCommandData(data); + }); self.reconnect = function() { self._socket.socket.connect(); @@ -1750,33 +1753,33 @@ function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSor self._saveCurrentSortingToLocalStorage = function() { if ( self._initializeLocalStorage() ) { - var currentSorting = self.currentSorting(); - if (currentSorting !== undefined) - localStorage[self.listType + "." + "currentSorting"] = currentSorting; - else - localStorage[self.listType + "." + "currentSorting"] = undefined; + var currentSorting = self.currentSorting(); + if (currentSorting !== undefined) + localStorage[self.listType + "." + "currentSorting"] = currentSorting; + else + localStorage[self.listType + "." + "currentSorting"] = undefined; } } self._loadCurrentSortingFromLocalStorage = function() { if ( self._initializeLocalStorage() ) { - if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"])) - self.currentSorting(localStorage[self.listType + "." + "currentSorting"]); - else - self.currentSorting(defaultSorting); - } + if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"])) + self.currentSorting(localStorage[self.listType + "." + "currentSorting"]); + else + self.currentSorting(defaultSorting); + } } self._saveCurrentFiltersToLocalStorage = function() { if ( self._initializeLocalStorage() ) { - var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters()); - localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters); - } + var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters()); + localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters); + } } self._loadCurrentFiltersFromLocalStorage = function() { if ( self._initializeLocalStorage() ) { - self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"]))); + self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"]))); } } diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2 index 3116729..5fa53f3 100644 --- a/octoprint/templates/index.jinja2 +++ b/octoprint/templates/index.jinja2 @@ -390,6 +390,11 @@ +