External commands on events

These changes address issues 87 and 22 by adding the ability to trigger
external commands on print start, done, cancel and z-height change.
master
Lars Norpchen 2013-05-09 14:31:54 -07:00
parent b609123d8a
commit c6363ea046
4 changed files with 87 additions and 6 deletions

View File

@ -1,3 +1,16 @@
Added support for system commands at print start, print end, print cancelled and z-height change. Add the commands to run to the config.yaml file:
system_commands:
cancelled: echo cancelled _FILE_ at _PROGRESS_ percent done.
print_done: growlnotify "done with _FILE_"
print_started: echo starting _FILE_
z_change: echo _LINE_ _PROGRESS_ _ZHEIGHT_
These commands take the tokens take _FILE_, _PERCENT_, _LINES_ and _ZHEIGHT_ which will be passed to external commands.
OctoPrint OctoPrint
========= =========

View File

@ -7,6 +7,9 @@ import datetime
import threading import threading
import copy import copy
import os import os
import subprocess
import re
import logging, logging.config
import octoprint.util.comm as comm import octoprint.util.comm as comm
import octoprint.util as util import octoprint.util as util
@ -53,6 +56,7 @@ class Printer():
self._currentZ = None self._currentZ = None
self.peakZ = -1
self._progress = None self._progress = None
self._printTime = None self._printTime = None
self._printTimeLeft = None self._printTimeLeft = None
@ -90,6 +94,9 @@ class Printer():
currentZ=None currentZ=None
) )
self.sys_command= { "z_change": settings().get(["system_commands", "z_change"]), "cancelled" : settings().get(["system_commands", "cancelled"]), "print_done" :settings().get(["system_commands", "print_done"]), "print_started": settings().get(["system_commands", "print_started"])};
#~~ callback handling #~~ callback handling
def registerCallback(self, callback): def registerCallback(self, callback):
@ -190,6 +197,9 @@ class Printer():
return return
self._setCurrentZ(-1) self._setCurrentZ(-1)
self.executeSystemCommand(self.sys_command['print_started'])
self._comm.printGCode(self._gcodeList) self._comm.printGCode(self._gcodeList)
def togglePausePrint(self): def togglePausePrint(self):
@ -216,6 +226,7 @@ class Printer():
# mark print as failure # mark print as failure
self._gcodeManager.printFailed(self._filename) self._gcodeManager.printFailed(self._filename)
self.executeSystemCommand(self.sys_command['cancelled'])
#~~ state monitoring #~~ state monitoring
@ -363,8 +374,13 @@ class Printer():
if self._comm is not None and oldState == self._comm.STATE_PRINTING: if self._comm is not None and oldState == self._comm.STATE_PRINTING:
if state == self._comm.STATE_OPERATIONAL: if state == self._comm.STATE_OPERATIONAL:
self._gcodeManager.printSucceeded(self._filename) self._gcodeManager.printSucceeded(self._filename)
#hrm....we seem to hit this state and THEN the next failed state on a cancel request?
# oh well, add a check to see if we're really done before sending the success event external command
if self._printTimeLeft < 1:
self.executeSystemCommand(self.sys_command['print_done'])
elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR: elif state == self._comm.STATE_CLOSED or state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR:
self._gcodeManager.printFailed(self._filename) self._gcodeManager.printFailed(self._filename)
self.executeSystemCommand(self.sys_command['cancelled'])
self._gcodeManager.resumeAnalysis() # do not analyse gcode while printing self._gcodeManager.resumeAnalysis() # do not analyse gcode while printing
elif self._comm is not None and state == self._comm.STATE_PRINTING: elif self._comm is not None and state == self._comm.STATE_PRINTING:
self._gcodeManager.pauseAnalysis() # printing done, put those cpu cycles to good use self._gcodeManager.pauseAnalysis() # printing done, put those cpu cycles to good use
@ -397,8 +413,13 @@ class Printer():
Callback method for the comm object, called upon change of the z-layer. Callback method for the comm object, called upon change of the z-layer.
""" """
oldZ = self._currentZ oldZ = self._currentZ
# only do this if we hit a new Z peak level. Some slicers do a Z-lift when retracting / moving without printing
# and some do ananti-backlash up-then-down movement when advancing layers
if newZ > self.peakZ:
self.peakZ = newZ
if self._timelapse is not None: if self._timelapse is not None:
self._timelapse.onZChange(oldZ, newZ) self._timelapse.onZChange(oldZ, newZ)
self.executeSystemCommand(self.sys_command['z_change'])
self._setCurrentZ(newZ) self._setCurrentZ(newZ)
@ -469,6 +490,32 @@ class Printer():
def isLoading(self): def isLoading(self):
return self._gcodeLoader is not None return self._gcodeLoader is not None
def executeSystemCommand(self,command_string):
if command_string is None:
return
logger = logging.getLogger(__name__)
try:
# handle a few regex substs for job data passed to external apps
cmd_string_with_params = command_string
cmd_string_with_params = re.sub("_ZHEIGHT_",str(self._currentZ), cmd_string_with_params)
cmd_string_with_params = re.sub("_FILE_",os.path.basename(self._filename), cmd_string_with_params)
# cut down to 2 decimal places, forcing through an int to avoid the 10.320000000001 floating point thing...
if self._gcodeList and self._progress:
prog = int(10000.0 * self._progress / len(self._gcodeList))/100.0
else:
prog = 0.0
cmd_string_with_params = re.sub("_PROGRESS_",str(prog), cmd_string_with_params)
cmd_string_with_params = re.sub("_LINE_",str(self._comm._gcodePos), cmd_string_with_params)
logger.info ("Executing system command: %s " % cmd_string_with_params)
#use Popen here since it won't wait for the shell to return...and we send some of these
# commands during a print job, we don't want to block!
subprocess.Popen(cmd_string_with_params,shell = True)
except subprocess.CalledProcessError, e:
logger.warn("Command failed with return code %i: %s" % (e.returncode, e.message))
except Exception, ex:
logger.exception("Command failed")
class GcodeLoader(threading.Thread): class GcodeLoader(threading.Thread):
""" """
The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate The GcodeLoader takes care of loading a gcode-File from disk and parsing it into a gcode object in a separate
@ -526,6 +573,7 @@ class StateMonitor(object):
self._jobData = None self._jobData = None
self._gcodeData = None self._gcodeData = None
self._currentZ = None self._currentZ = None
self._peakZ = -1
self._progress = None self._progress = None
self._changeEvent = threading.Event() self._changeEvent = threading.Event()

View File

@ -375,7 +375,15 @@ def getSettings():
}, },
"system": { "system": {
"actions": s.get(["system", "actions"]) "actions": s.get(["system", "actions"])
},
"system_commands": {
"print_done": s.get(["system_commands", "print_done"]),
"cancelled": s.get(["system_commands", "cancelled"]),
"print_started": s.get(["system_commands", "print_started"]),
"z_change": s.get(["system_commands", "z_change"])
} }
}) })
@app.route(BASEURL + "settings", methods=["POST"]) @app.route(BASEURL + "settings", methods=["POST"])
@ -417,6 +425,12 @@ def setSettings():
if "system" in data.keys(): if "system" in data.keys():
if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"]) if "actions" in data["system"].keys(): s.set(["system", "actions"], data["system"]["actions"])
if "system_commands" in data.keys():
if "z_change" in data["system_commands"].keys(): s.set(["system_commands", "z_change"], data["system_commands"]["z_change"])
if "print_started" in data["system_commands"].keys(): s.set(["system_commands", "print_started"], data["system_commands"]["print_started"])
if "cancelled" in data["system_commands"].keys(): s.set(["system_commands", "cancelled"], data["system_commands"]["cancelled"])
if "print_done" in data["system_commands"].keys(): s.set(["system_commands", "print_done"], data["system_commands"]["print_done"])
s.save() s.save()
return getSettings() return getSettings()

View File

@ -69,6 +69,12 @@ default_settings = {
"controls": [], "controls": [],
"system": { "system": {
"actions": [] "actions": []
},
"system_commands": {
"z_change":None,
"print_started":None,
"cancelled":None,
"print_done":None
} }
} }