Merge branch 'feedbackCommands' into devel

master
Gina Häußge 2013-06-21 23:19:25 +02:00
commit 40b4bfad85
6 changed files with 101 additions and 27 deletions

View File

@ -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):

View File

@ -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)

View File

@ -2,11 +2,11 @@
__author__ = "Gina Häußge <osd@foosel.net>"
__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):

View File

@ -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.fromFeedbackCommandData = 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;
}
@ -621,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") {
@ -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";
}
@ -1487,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 " +
@ -1496,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 <strong>could not reconnect automatically</strong>, " +
"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);
@ -1512,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);
@ -1522,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();
@ -1737,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"])));
}
}

View File

@ -390,6 +390,11 @@
<button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button>
</form>
</script>
<script type="text/html" id="customControls_feedbackCommandTemplate">
<form class="form-inline">
<button class="btn" data-bind="text: name, enable: $root.isOperational() && $root.loginState.isUser(), click: function() { $root.sendCustomCommand($data) }"></button> <span data-bind="text: output"></span>
</form>
</script>
<script type="text/html" id="customControls_parametricCommandTemplate">
<form class="form-inline">
<!-- ko foreach: input -->

View File

@ -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, str.format(template, *(match.groups("n/a"))))
except:
# ignored on purpose
pass
if "ok" in line and heatingUp:
heatingUp = False