Corrected some merge errors, got gcode scripts to work, broke temperature polling in the process

TODO:
- Fix temperature polling
- Document new hook octoprint.comm.protocol.scripts
This commit is contained in:
Gina Häußge 2015-02-19 22:32:20 +01:00
parent 654e255b68
commit 1e6679c40c
8 changed files with 147 additions and 113 deletions

View file

@ -200,8 +200,8 @@ class Printer():
"""
if self._comm is not None:
self._comm.close()
self._comm = comm.MachineCom(port, baudrate, callbackObject=self)
self._printerProfileManager.select(profile)
self._comm = comm.MachineCom(port, baudrate, callbackObject=self, printerProfileManager=self._printerProfileManager)
def disconnect(self):
"""

View file

@ -171,6 +171,7 @@ def index():
printerprofiles=(gettext("Printer Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
temperatures=(gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)),
terminalfilters=(gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)),
gcodescripts=(gettext("GCODE Scripts"), dict(template="dialogs/settings/gcodescripts.jinja2", _div="settings_gcodeScripts", custom_bindings=False)),
section_features=(gettext("Features"), None),
@ -234,7 +235,7 @@ def index():
templates["sidebar"]["order"] = ["connection", "state", "files"]
templates["tab"]["order"] = ["temperature", "control", "gcodeviewer", "terminal", "timelapse"]
templates["settings"]["order"] = [
"section_printer", "serial", "printerprofiles", "temperatures", "terminalfilters",
"section_printer", "serial", "printerprofiles", "temperatures", "terminalfilters", "gcodescripts",
"section_features", "features", "webcam", "accesscontrol", "api",
"section_octoprint", "folders", "appearance", "logs"
]
@ -262,29 +263,6 @@ def index():
templates[t]["entries"]["section_plugins"] = (gettext("Plugins"), None)
templates[t]["order"] += ["section_plugins"] + sorted_missing
#~~ settings dialog
settings_entries = [
(gettext("Printer"), None),
(gettext("Serial Connection"), dict(template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", custom_bindings=False)),
(gettext("Printer Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
(gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)),
(gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)),
(gettext("GCODE Scripts"), dict(template="dialogs/settings/gcodescripts.jinja2", _div="settings_gcodeScripts", custom_bindings=False)),
(gettext("Features"), None),
(gettext("Features"), dict(template="dialogs/settings/features.jinja2", _div="settings_features", custom_bindings=False)),
(gettext("Webcam"), dict(template="dialogs/settings/webcam.jinja2", _div="settings_webcam", custom_bindings=False)),
(gettext("Access Control"), dict(template="dialogs/settings/accesscontrol.jinja2", _div="settings_users", custom_bindings=False)),
(gettext("API"), dict(template="dialogs/settings/api.jinja2", _div="settings_api", custom_bindings=False)),
(gettext("OctoPrint"), None),
(gettext("Folders"), dict(template="dialogs/settings/folders.jinja2", _div="settings_folders", custom_bindings=False)),
(gettext("Appearance"), dict(template="dialogs/settings/appearance.jinja2", _div="settings_appearance", custom_bindings=False)),
(gettext("Logs"), dict(template="dialogs/settings/logs.jinja2", _div="settings_logs"))
]
if len(plugin_includes_settings):
settings_entries.append((gettext("Plugins"), None))
settings_entries.extend(sorted(plugin_includes_settings, key=lambda x: x[0]))
#~~ prepare full set of template vars for rendering
render_kwargs = dict(

View file

@ -19,7 +19,7 @@ from octoprint.server.api import api
from octoprint.server.util.flask import restricted_access
import octoprint.plugin
import octoprint.util
#~~ settings
@ -91,7 +91,7 @@ def getSettings():
"events": s.get(["system", "events"])
},
"terminalFilters": s.get(["terminalFilters"]),
"scripts": s.get(["scripts"])
"scripts": s.get(["scripts"], merged=True)
}
def process_plugin_result(name, plugin, result):
@ -193,7 +193,9 @@ def setSettings():
if "events" in data["system"].keys(): s.set(["system", "events"], data["system"]["events"])
if "scripts" in data:
if "gcode" in data["scripts"]: s.set(["scripts", "gcode"], data["scripts"]["gcode"])
if "gcode" in data["scripts"]:
gcode_scripts = data["scripts"]["gcode"]
s.set(["scripts", "gcode"], octoprint.util.dict_merge(s.get(["scripts", "gcode"], merged=True), gcode_scripts))
if "plugins" in data:
for name, plugin in octoprint.plugin.plugin_manager().get_implementations(octoprint.plugin.SettingsPlugin).items():

View file

@ -153,11 +153,18 @@ default_settings = {
"plugins": {},
"scripts": {
"gcode": {
"afterPrinterConnected": None,
"beforePrintStarted": None,
"afterPrintDone": None,
"afterPrintCancelled": None,
"afterPrintCancelled": "{disable_steppers}\n{disable_hotends}\n{disable_bed}\n{disable_fan}",
"afterPrintPaused": None,
"beforePrintResumed": None
"beforePrintResumed": None,
"templates": {
"disable_steppers": "M84",
"disable_hotends": "M104 T{tool:d} S0",
"disable_bed": "M140 S0",
"disable_fan": "M106 S0"
}
}
},
"devel": {

File diff suppressed because one or more lines are too long

View file

@ -89,6 +89,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesV
self.scripts_gcode_afterPrintCancelled = ko.observable(undefined);
self.scripts_gcode_afterPrintPaused = ko.observable(undefined);
self.scripts_gcode_beforePrintResumed = ko.observable(undefined);
self.scripts_gcode_afterPrinterConnected = ko.observable(undefined);
self.temperature_profiles = ko.observableArray(undefined);
@ -187,6 +188,7 @@ function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesV
self.scripts_gcode_afterPrintCancelled(response.scripts.gcode.afterPrintCancelled);
self.scripts_gcode_afterPrintPaused(response.scripts.gcode.afterPrintPaused);
self.scripts_gcode_beforePrintResumed(response.scripts.gcode.beforePrintResumed);
self.scripts_gcode_afterPrinterConnected(response.scripts.gcode.afterPrinterConnected);
self.temperature_profiles(response.temperature.profiles);
@ -262,7 +264,8 @@ function SettingsViewModel(loginStateViewModel, usersViewModel, printerProfilesV
"afterPrintDone": self.scripts_gcode_afterPrintDone(),
"afterPrintCancelled": self.scripts_gcode_afterPrintCancelled(),
"afterPrintPaused": self.scripts_gcode_afterPrintPaused(),
"beforePrintResumed": self.scripts_gcode_beforePrintResumed()
"beforePrintResumed": self.scripts_gcode_beforePrintResumed(),
"afterPrinterConnected": self.scripts_gcode_afterPrinterConnected()
}
}
});

View file

@ -2,31 +2,37 @@
<div class="control-group">
<label class="control-label">{{ _('Before print job starts') }}</label>
<div class="controls">
<textarea rows="3" class="block" data-bind="value: scripts_gcode_beforePrintStarted"></textarea>
<textarea rows="4" class="block" data-bind="value: scripts_gcode_beforePrintStarted"></textarea>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('After print job completes') }}</label>
<div class="controls">
<textarea rows="3" class="block" data-bind="value: scripts_gcode_afterPrintDone"></textarea>
<textarea rows="4" class="block" data-bind="value: scripts_gcode_afterPrintDone"></textarea>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('After print job is cancelled') }}</label>
<div class="controls">
<textarea rows="3" class="block" data-bind="value: scripts_gcode_afterPrintCancelled"></textarea>
<textarea rows="4" class="block" data-bind="value: scripts_gcode_afterPrintCancelled"></textarea>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('After print job is paused') }}</label>
<div class="controls">
<textarea rows="3" class="block" data-bind="value: scripts_gcode_afterPrintPaused"></textarea>
<textarea rows="4" class="block" data-bind="value: scripts_gcode_afterPrintPaused"></textarea>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Before print job is resumed') }}</label>
<div class="controls">
<textarea rows="3" class="block" data-bind="value: scripts_gcode_beforePrintResumed"></textarea>
<textarea rows="4" class="block" data-bind="value: scripts_gcode_beforePrintResumed"></textarea>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('After connection to printer is established') }}</label>
<div class="controls">
<textarea rows="4" class="block" data-bind="value: scripts_gcode_afterPrinterConnected"></textarea>
</div>
</div>
</form>

View file

@ -117,7 +117,7 @@ class MachineCom(object):
STATE_CLOSED_WITH_ERROR = 10
STATE_TRANSFERING_FILE = 11
def __init__(self, port = None, baudrate = None, callbackObject = None):
def __init__(self, port = None, baudrate=None, callbackObject=None, printerProfileManager=None):
self._logger = logging.getLogger(__name__)
self._serialLogger = logging.getLogger("SERIAL")
@ -135,6 +135,7 @@ class MachineCom(object):
self._port = port
self._baudrate = baudrate
self._callback = callbackObject
self._printerProfileManager = printerProfileManager
self._state = self.STATE_NONE
self._serial = None
self._baudrateDetectList = baudrateList()
@ -162,6 +163,7 @@ class MachineCom(object):
self._pluginManager = octoprint.plugin.plugin_manager()
self._gcode_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.gcode")
self._printer_action_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.action")
self._gcodescript_hooks = self._pluginManager.get_hooks("octoprint.comm.protocol.scripts")
# SD status data
self._sdAvailable = False
@ -383,21 +385,53 @@ class MachineCom(object):
elif self.isOperational():
self._sendCommand(cmd)
def sendGcodeScript(self, scriptName):
gcodeScripts = settings().get(["scripts", "gcode"])
def sendGcodeScript(self, scriptName, replacements=None):
gcodeScripts = settings().get(["scripts", "gcode"], merged=True)
if scriptName in gcodeScripts and gcodeScripts[scriptName] is not None:
if scriptName in gcodeScripts and gcodeScripts[scriptName]:
script = gcodeScripts[scriptName]
else:
if scriptName == "afterPrintCancelled":
commands = ["M84"]
commands.extend(map(lambda x: "M104 T%d S0" % x, range(settings().getInt(["printerParameters", "numExtruders"]))))
commands.extend(["M140 S0", "M106 S0"])
script = "\n".join(commands)
else:
return
return
if replacements is None:
replacements = dict()
replacements.update(dict(
disable_steppers=gcodeScripts["templates"]["disable_steppers"],
disable_hotends="\n".join(map(lambda x: gcodeScripts["templates"]["disable_hotends"].format(tool=x), range(self._printerProfileManager.get_current_or_default()["extruder"]["count"]))),
disable_bed=gcodeScripts["templates"]["disable_bed"],
disable_fan=gcodeScripts["templates"]["disable_fan"]
))
script = script.format(**replacements)
scriptLines = map(str.strip, script.split("\n"))
for hook in self._gcodescript_hooks:
try:
retval = self._gcodescript_hooks[hook](self, "gcode", scriptName)
except:
self._logger.exception("Error while processing gcodescript hook %s" % hook)
else:
if retval is None:
continue
if not isinstance(retval, (list, tuple)) or not len(retval) == 2:
continue
def to_list(data):
if isinstance(data, str):
data = map(str.strip, data.split("\n"))
elif isinstance(data, unicode):
data = map(unicode.strip, data.split("\n"))
if isinstance(data, (list, tuple)):
return list(data)
else:
return None
prefix, suffix = map(to_list, retval)
if prefix:
scriptLines = list(prefix) + scriptLines
if suffix:
scriptLines += list(suffix)
for line in scriptLines:
self.sendCommand(line)
@ -418,13 +452,16 @@ class MachineCom(object):
wasPaused = self.isPaused()
self._changeState(self.STATE_PRINTING)
eventManager().fire(Events.PRINT_STARTED, {
self.sendCommand("M110 N0")
payload = {
"file": self._currentFile.getFilename(),
"filename": os.path.basename(self._currentFile.getFilename()),
"origin": self._currentFile.getFileLocation()
})
self.sendGcodeScript("beforePrintStarted")
}
eventManager().fire(Events.PRINT_STARTED, payload)
self.sendGcodeScript("beforePrintStarted", replacements=dict(event=payload))
if self.isSdFileSelected():
if wasPaused:
@ -435,6 +472,9 @@ class MachineCom(object):
line = self._getNext()
if line is not None:
self.sendCommand(line)
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue(sendChecksum=True)
except:
self._logger.exception("Error while trying to start printing")
self._errorValue = getExceptionString()
@ -488,25 +528,37 @@ class MachineCom(object):
self.sendCommand("M25") # pause print
self.sendCommand("M26 S0") # reset position in file to byte 0
self.sendGcodeScript("afterPrintCancelled")
eventManager().fire(Events.PRINT_CANCELLED, {
payload = {
"file": self._currentFile.getFilename(),
"filename": os.path.basename(self._currentFile.getFilename()),
"origin": self._currentFile.getFileLocation()
})
}
self.sendGcodeScript("afterPrintCancelled", replacements=dict(event=payload))
eventManager().fire(Events.PRINT_CANCELLED, payload)
def setPause(self, pause):
if self.isStreaming():
return
if not self._currentFile:
return
payload = {
"file": self._currentFile.getFilename(),
"filename": os.path.basename(self._currentFile.getFilename()),
"origin": self._currentFile.getFileLocation()
}
if not pause and self.isPaused():
if self._pauseWaitStartTime:
self._pauseWaitTimeLost = self._pauseWaitTimeLost + (time.time() - self._pauseWaitStartTime)
self._pauseWaitStartTime = None
self._changeState(self.STATE_PRINTING)
self.sendGcodeScript("beforePrintResumed")
self.sendGcodeScript("beforePrintResumed", replacements=dict(event=payload))
if self.isSdFileSelected():
self.sendCommand("M24")
else:
@ -514,11 +566,10 @@ class MachineCom(object):
if line is not None:
self.sendCommand(line)
eventManager().fire(Events.PRINT_RESUMED, {
"file": self._currentFile.getFilename(),
"filename": os.path.basename(self._currentFile.getFilename()),
"origin": self._currentFile.getFileLocation()
})
# now make sure we actually do something, up until now we only filled up the queue
self._sendFromQueue(sendChecksum=True)
eventManager().fire(Events.PRINT_RESUMED, payload)
elif pause and self.isPrinting():
if not self._pauseWaitStartTime:
self._pauseWaitStartTime = time.time()
@ -526,13 +577,9 @@ class MachineCom(object):
self._changeState(self.STATE_PAUSED)
if self.isSdFileSelected():
self.sendCommand("M25") # pause print
self.sendGcodeScript("afterPrintPaused")
self.sendGcodeScript("afterPrintPaused", replacements=dict(event=payload))
eventManager().fire(Events.PRINT_PAUSED, {
"file": self._currentFile.getFilename(),
"filename": os.path.basename(self._currentFile.getFilename()),
"origin": self._currentFile.getFileLocation()
})
eventManager().fire(Events.PRINT_PAUSED, payload)
def getSdFiles(self):
return self._sdFiles
@ -685,7 +732,6 @@ class MachineCom(object):
sdStatusRequestTimeout = getNewTimeout("sdStatus")
startSeen = not settings().getBoolean(["feature", "waitForStartOnConnect"])
heatingUp = False
swallowOk = False
supportRepetierTargetTemp = settings().getBoolean(["feature", "repetierTargetTemp"])
@ -823,7 +869,6 @@ class MachineCom(object):
})
elif 'Writing to file' in line:
# anwer to M28, at least on Marlin, Repetier and Sprinter: "Writing to file: %s"
self._printSection = "CUSTOM"
self._changeState(self.STATE_PRINTING)
line = "ok"
elif 'Done printing file' in line:
@ -880,9 +925,6 @@ class MachineCom(object):
elif "toggle" in pauseTriggers.keys() and pauseTriggers["toggle"].search(line) is not None:
self.setPause(not self.isPaused())
if "ok" in line and heatingUp:
heatingUp = False
### Baudrate detection
if self._state == self.STATE_DETECT_BAUDRATE:
if line == '' or time.time() > self._timeout:
@ -919,12 +961,7 @@ class MachineCom(object):
else:
self._sendCommand("M999")
self._serial.timeout = settings().getFloat(["serial", "timeout", "connection"])
self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
else:
self.initSdCard()
eventManager().fire(Events.CONNECTED, {"port": self._port, "baudrate": self._baudrate})
self._onConnected()
else:
self._testingBaudrate = False
@ -935,12 +972,7 @@ class MachineCom(object):
elif "start" in line:
startSeen = True
elif "ok" in line and startSeen:
self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
else:
self.initSdCard()
eventManager().fire(Events.CONNECTED, {"port": self._port, "baudrate": self._baudrate})
self._onConnected()
elif time.time() > self._timeout:
self.close()
@ -954,8 +986,8 @@ class MachineCom(object):
elif line == "" or "wait" in line:
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty():
self._sendCommand(self._commandQueue.get())
elif self._sendFromQueue(sendChecksum=False):
pass
else:
self._sendCommand("M105")
tempRequestTimeout = getNewTimeout("temperature")
@ -972,33 +1004,29 @@ class MachineCom(object):
self._log("Communication timeout during printing, forcing a line")
line = 'ok'
if self.isSdPrinting():
if time.time() > tempRequestTimeout and not heatingUp:
if self._heatupWaitStartTime is None:
if time.time() > tempRequestTimeout:
self._sendCommand("M105")
tempRequestTimeout = getNewTimeout("temperature")
if time.time() > sdStatusRequestTimeout and not heatingUp:
self._sendCommand("M27")
sdStatusRequestTimeout = getNewTimeout("sdStatus")
else:
# Even when printing request the temperature every 5 seconds.
if time.time() > tempRequestTimeout and not self.isStreaming():
self._commandQueue.put("M105")
tempRequestTimeout = getNewTimeout("temperature")
if self.isSdPrinting() and time.time() > sdStatusRequestTimeout:
self._sendCommand("M27")
sdStatusRequestTimeout = getNewTimeout("sdStatus")
if "ok" in line and swallowOk:
if "ok" in line:
if swallowOk:
swallowOk = False
elif "ok" in line:
else:
if self._resendDelta is not None:
self._resendNextCommand()
elif not self._commandQueue.empty() and not self.isStreaming():
self._sendCommand(self._commandQueue.get(), True)
elif self._sendFromQueue(sendChecksum=True):
pass
else:
self._sendNext()
elif line.lower().startswith("resend") or line.lower().startswith("rs"):
if settings().get(["feature", "swallowOkAfterResend"]):
swallowOk = True
self._handleResendRequest(line)
elif line.lower().startswith("resend") or line.lower().startswith("rs"):
if settings().get(["feature", "swallowOkAfterResend"]):
swallowOk = True
self._handleResendRequest(line)
except:
self._logger.exception("Something crashed inside the serial connection loop, please report this in OctoPrint's bug tracker:")
@ -1009,6 +1037,23 @@ class MachineCom(object):
eventManager().fire(Events.ERROR, {"error": self.getErrorString()})
self._log("Connection closed, closing down monitor")
def _onConnected(self):
self._changeState(self.STATE_OPERATIONAL)
if self._sdAvailable:
self.refreshSdFiles()
else:
self.initSdCard()
payload = dict(port=self._port, baudrate=self._baudrate)
eventManager().fire(Events.CONNECTED, payload)
self.sendGcodeScript("afterPrinterConnected", replacements=dict(event=payload))
def _sendFromQueue(self, sendChecksum=False):
if not self._commandQueue.empty() and not self.isStreaming():
self._sendCommand(self._commandQueue.get(), sendChecksum)
return True
else:
return False
def _openSerial(self):
if self._port == 'AUTO':
self._changeState(self.STATE_DETECT_SERIAL)
@ -1123,7 +1168,7 @@ class MachineCom(object):
self._changeState(self.STATE_OPERATIONAL)
eventManager().fire(Events.PRINT_DONE, payload)
self.sendGcodeScript("afterPrintDone")
self.sendGcodeScript("afterPrintDone", replacements=dict(event=payload))
return line
def _sendNext(self):
@ -1457,7 +1502,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
self._filehandle = None
self._filesetMenuModehandle = None
self._lineCount = None
self._firstLine = None
self._currentTool = 0
@ -1477,7 +1521,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
"""
PrintingFileInformation.start(self)
self._filehandle = open(self._filename, "r")
self._lineCount = None
def getNext(self):
"""
@ -1486,10 +1529,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
if self._filehandle is None:
raise ValueError("File %s is not open for reading" % self._filename)
if self._lineCount is None:
self._lineCount = 0
return "M110 N0"
try:
processedLine = None
while processedLine is None:
@ -1501,7 +1540,6 @@ class PrintingGcodeFileInformation(PrintingFileInformation):
self._filehandle.close()
self._filehandle = None
processedLine = self._processLine(line)
self._lineCount += 1
self._filepos = self._filehandle.tell()
return processedLine