Merge branch 'devel' into events

Conflicts:
	octoprint/util/comm.py
master
Gina Häußge 2013-06-22 15:08:53 +02:00
commit 79f2cd0e31
6 changed files with 307 additions and 213 deletions

View File

@ -129,6 +129,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):
@ -435,6 +440,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

@ -107,6 +107,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"
@ -213,6 +213,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,14 +1535,17 @@ 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();
} else if (type == "timelapseFiles") {
timelapseViewModel.requestData();
}
})
});
self._socket.on("feedbackCommandOutput", function(data) {
self.controlViewModel.fromFeedbackCommandData(data);
});
self.reconnect = function() {
self._socket.socket.connect();

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

@ -41,7 +41,13 @@ def serialList():
i+=1
except:
pass
baselist = baselist + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/cu.*") + glob.glob("/dev/rfcomm*")
baselist = baselist \
+ glob.glob("/dev/ttyUSB*") \
+ glob.glob("/dev/ttyACM*") \
+ glob.glob("/dev/ttyAMA*") \
+ glob.glob("/dev/tty.usb*") \
+ glob.glob("/dev/cu.*") \
+ glob.glob("/dev/rfcomm*")
prev = settings().get(["serial", "port"])
if prev in baselist:
baselist.remove(prev)
@ -162,6 +168,12 @@ 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 "M999" in data:
# mirror Marlin behaviour
self.readList.append("Resend: 1")
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")
@ -333,6 +345,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
@ -531,6 +546,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)
@ -580,6 +597,7 @@ class MachineCom(object):
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
heatingUp = False
while True:
try:
line = self._readline()
if line == None:
break
@ -603,9 +621,6 @@ class MachineCom(object):
or 'Missing checksum' in line:
pass
elif not self.isError():
if self.isPrinting():
eventManager().fire("PrintFailed")
self._errorValue = line[6:]
self._changeState(self.STATE_ERROR)
@ -685,6 +700,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
@ -742,8 +768,8 @@ class MachineCom(object):
if self._sdAvailable:
self.refreshSdFiles()
eventManager().fire("Connected", "%s at %s baud" % (self._port, self._baudrate))
elif time.time() > timeout:
self.close()
else:
self._testingBaudrate = False
### Operational
elif self._state == self.STATE_OPERATIONAL or self._state == self.STATE_PAUSED:
@ -793,6 +819,13 @@ class MachineCom(object):
self._sendNext()
elif "resend" in line.lower() or "rs" in line:
self._handleResendRequest(line)
except:
self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:")
errorMsg = "Please see octoprint.log for details"
self._log(errorMsg)
self._errorValue = errorMsg
self._changeState(self.STATE_ERROR)
self._log("Connection closed, closing down monitor")
def _handleResendRequest(self, line):
@ -806,9 +839,15 @@ class MachineCom(object):
if lineToResend is not None:
self._resendDelta = self._currentLine - lineToResend
if self._resendDelta >= len(self._lastLines):
self._errorValue = "Printer requested line %d but history is only available up to line %d" % (lineToResend, self._currentLine - len(self._lastLines))
self._changeState(self.STATE_ERROR)
self._errorValue = "Printer requested line %d but no sufficient history is available, can't resend" % lineToResend
self._logger.warn(self._errorValue)
if self.isPrinting():
# abort the print, there's nothing we can do to rescue it now
self._changeState(self.STATE_ERROR)
else:
# reset resend delta, we can't do anything about it
self._resendDelta = None
else:
self._resendNextCommand()
@ -843,7 +882,7 @@ class MachineCom(object):
def close(self, isError = False):
printing = self.isPrinting() or self.isPaused()
if self._serial != None:
if self._serial is not None:
self._serial.close()
if isError:
self._changeState(self.STATE_CLOSED_WITH_ERROR)
@ -865,13 +904,13 @@ class MachineCom(object):
# Make sure we are only handling one sending job at a time
with self._sendingLock:
self._logger.debug("Resending line %d, delta is %d, history log is %s items strong" % (self._currentLine - self._resendDelta, self._resendDelta, len(self._lastLines)))
cmd = self._lastLines[-self._resendDelta]
cmd = self._lastLines[-(self._resendDelta + 1)]
lineNumber = self._currentLine - self._resendDelta
self._doSendWithChecksum(cmd, lineNumber)
self._resendDelta -= 1
if self._resendDelta <= 0:
if self._resendDelta < 0:
self._resendDelta = None
def _sendCommand(self, cmd, sendChecksum=False):