diff --git a/octoprint/events.py b/octoprint/events.py index 01a07a9..9e0bfb9 100644 --- a/octoprint/events.py +++ b/octoprint/events.py @@ -1,59 +1,56 @@ -import sys import datetime -import time -import math import re -import logging, logging.config +import logging import subprocess -import octoprint.printer as printer import os # right now we're logging a lot of extra information for testing # we might want to comment out some of the logging eventually class event_record(object): - what = None - who = None - action = None - - - # object that handles receiving events and dispatching them to listeners - # + def __init__(self, what, who, action): + self.what = what + self.who = who + self.action = action + class EventManager(object): + """ + Handles receiving events and dispatching them to listeners + """ + def __init__(self): self.registered_events = [] self.logger = logging.getLogger(__name__) - # Fire an event to anyone listening - # any object can generate an event and any object can listen - # pass in the event_name as a string (arbitrary, but case sensitive) - # and any extra data that may pertain to the event - def FireEvent (self,event_name,extra_data=None): - self.logger.info ( "Firing event: " + event_name + " (" +str (extra_data)+")") - for ev in self.registered_events: - if event_name == ev.what: - self.logger.info ( "Sending action to " + str(ev.who)) - if ev.action != None : - ev.action (event_name,extra_data) -# else: -# self.logger.info ( "events don't match " + str(ev.what)+ " and " + event_name) - - - # register a listener to an event -- pass in - # the event name (as a string), the target object - # and the function to call - def Register (self,event_name, target, action): - new_ev =event_record() - new_ev.what = event_name - new_ev.who = target - new_ev.action= action - self.registered_events=self.registered_events+[new_ev] - self.logger.info ("Registered event '"+new_ev.what+"' to invoke '"+str(new_ev.action)+"' on "+str(new_ev.who) ) + def fire(self, name, payload=None): + """ + Fire an event to anyone listening. + + Any object can generate an event and any object can listen pass in the event_name as a string (arbitrary, but + case sensitive) and any extra payload data that may pertain to the event. + """ + + self.logger.debug("Firing event: %s (%r)" % (name, payload)) + for event in self.registered_events: + (who, what, action) = event + if name == what: + self.logger.debug("Sending action to %r" % who) + if action is not None: + action(name, payload) - - def unRegister (self,event_name, target, action): - self.registered_events[:] = [e for e in self.registered_events if event_name != e.what or e.action != action or e.who!=target] + def subscribe(self, name, target, action): + """ + Subscribe a listener to an event -- pass in the event name (as a string), the target object + and the callback object + """ + + newEvent = (name, target, action) + self.registered_events = self.registered_events.append(newEvent) + self.logger.debug("Registered event \"%s\" to invoke \"%r\" on %r" % (name, action, target)) + + def unsubscribe (self, event_name, target, action): + self.registered_events[:] = [e for e in self.registered_events if event_name != e.what or e.action != action or e.who != target] #sample event receiver # def event_rec(self,event_name,extra_data): @@ -68,12 +65,13 @@ class event_dispatch(object): event_string = None command_data = None -# object that hooks the event manager to system events, gcode, etc. -# creates listeners to any events defined in the config.yaml settings class EventResponse(object): - + """ + Hooks the event manager to system events, gcode, etc. Creates listeners to any events defined in the settings. + """ + def __init__(self, eventManager,printer): - self.registered_responses= [] + self.registered_responses = [] self._eventManager = eventManager self._printer = printer self.logger = logging.getLogger(__name__) @@ -81,18 +79,19 @@ class EventResponse(object): def setupEvents(self,s): availableEvents = s.get(["system", "events"]) - for ev in availableEvents: - event = event_dispatch() - event.type = ev["type"].strip() - event.event_string = ev["event"].strip() - event.command_data = ev["command"] - self._eventManager.Register ( event.event_string ,self,self.eventRec) - self.registered_responses = self.registered_responses+[event] - self.logger.info ("Registered "+event.type +" event '"+event.event_string+"' to execute '"+event.command_data+"'" ) - self.logger.info ( "Registered "+ str(len(self.registered_responses))+" events") + for event in availableEvents: + name = event["event"].strip() + action = event["type"].strip() + data = event["command"] + + self._eventManager.subscribe(event.event_string, self, self.eventRec) + + self.registered_responses = self.registered_responses.append(event) + self.logger.debug("Registered %s event \"%s\" to execute \"%s\"" % (event.type, event.event_string, event.command_data)) + self.logger.debug("Registered %d events" % len(self.registered_responses)) def eventRec (self,event_name, event_data): - self.logger.info ( "Receieved event: " + event_name + " (" + str(event_data) + ")") + self.logger.debug("Received event: %s (%r)" % (event_name, event_data)) self._event_data = event_data for ev in self.registered_responses: if ev.event_string == event_name: @@ -101,8 +100,10 @@ class EventResponse(object): if ev.type == "gcode": self.executeGCode(ev.command_data) - # handle a few regex substs for job data passed to external apps def doStringProcessing (self, command_string): + """ + Handles 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._printer._currentZ), cmd_string_with_params) if self._printer._filename: @@ -129,22 +130,20 @@ class EventResponse(object): def executeGCode(self,command_string): command_string = self.doStringProcessing(command_string) - self.logger.info ("GCode command: " + command_string) + self.logger.debug("GCode command: " + command_string) self._printer.commands(command_string.split(',')) - def executeSystemCommand(self,command_string): + def executeSystemCommand(self, command_string): if command_string is None: return + try: command_string = self.doStringProcessing(command_string) - self.logger.info ("Executing system command: "+ command_string) + self.logger.info("Executing system command: %s" % command_string) #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(command_string,shell = True) + subprocess.Popen(command_string, shell=True) except subprocess.CalledProcessError, e: self.logger.warn("Command failed with return code %i: %s" % (e.returncode, e.message)) except Exception, ex: self.logger.exception("Command failed") - - - \ No newline at end of file diff --git a/octoprint/printer.py b/octoprint/printer.py index 53ae43e..654632f 100644 --- a/octoprint/printer.py +++ b/octoprint/printer.py @@ -196,7 +196,7 @@ class Printer(): self._setCurrentZ(-1) - self._eventManager.FireEvent ('PrintStarted',filename) + self._eventManager.fire ('PrintStarted',filename) self._comm.printGCode(self._gcodeList) @@ -369,11 +369,11 @@ class Printer(): self._timelapse.onPrintjobStarted(self._filename) if state == self._comm.STATE_PRINTING and oldState != self._comm.STATE_PAUSED: - self._eventManager.FireEvent ('PrintStarted',filename) + self._eventManager.fire ('PrintStarted',filename) if state == self._comm.STATE_OPERATIONAL and (oldState <= self._comm.STATE_CONNECTING or oldState >=self._comm.STATE_CLOSED): - self._eventManager.FireEvent ('Connected',self._comm._port+" at " +self._comm._baudrate) + self._eventManager.fire ('Connected',self._comm._port+" at " +self._comm._baudrate) if state == self._comm.STATE_ERROR or state == self._comm.STATE_CLOSED_WITH_ERROR: - self._eventManager.FireEvent ('Error',self._comm.getErrorString()) + self._eventManager.fire ('Error',self._comm.getErrorString()) # forward relevant state changes to gcode manager if self._comm is not None and oldState == self._comm.STATE_PRINTING: @@ -382,10 +382,10 @@ class Printer(): #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._eventManager.FireEvent ('PrintDone',filename) + self._eventManager.fire ('PrintDone',filename) 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._eventManager.FireEvent ('PrintFailed',filename) + self._eventManager.fire ('PrintFailed',filename) self._gcodeManager.resumeAnalysis() # do not analyse gcode while 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 @@ -424,7 +424,7 @@ class Printer(): self.peakZ = newZ if self._timelapse is not None: self._timelapse.onZChange(oldZ, newZ) - self._eventManager.FireEvent ('ZChange',newZ) + self._eventManager.fire ('ZChange',newZ) self._setCurrentZ(newZ) @@ -442,7 +442,7 @@ class Printer(): self._setCurrentZ(None) self._setProgressData(None, None, None) self._gcodeLoader = None - self.eventManager.FireEvent("LoadDone",filename) + self.eventManager.fire("LoadDone",filename) self._stateMonitor.setGcodeData({"filename": None, "progress": None}) self._stateMonitor.setState({"state": self._state, "stateString": self.getStateString(), "flags": self._getStateFlags()}) diff --git a/octoprint/server.py b/octoprint/server.py index ec6f77b..894f26f 100644 --- a/octoprint/server.py +++ b/octoprint/server.py @@ -59,7 +59,7 @@ class PrinterStateConnection(tornadio2.SocketConnection): def on_open(self, info): global eventManager - eventManager.FireEvent("ClientOpen") + eventManager.fire("ClientOpen") self._logger.info("New connection from client") # Use of global here is smelly printer.registerCallback(self) @@ -67,7 +67,7 @@ class PrinterStateConnection(tornadio2.SocketConnection): def on_close(self): global eventManager - eventManager.FireEvent("ClientClosed") + eventManager.fire("ClientClosed") self._logger.info("Closed client connection") # Use of global here is smelly printer.unregisterCallback(self) @@ -152,7 +152,7 @@ def connect(): printer.connect(port=port, baudrate=baudrate) elif "command" in request.values.keys() and request.values["command"] == "disconnect": printer.disconnect() - eventManager.FireEvent("Disconnected") + eventManager.fire("Disconnected") return jsonify(SUCCESS) @@ -188,10 +188,10 @@ def printJobControl(): printer.startPrint() elif request.values["command"] == "pause": printer.togglePausePrint() - eventManager.FireEvent("Paused") + eventManager.fire("Paused") elif request.values["command"] == "cancel": printer.cancelPrint() - eventManager.FireEvent("Cancelled") + eventManager.fire("Cancelled") return jsonify(SUCCESS) @app.route(BASEURL + "control/temperature", methods=["POST"]) @@ -282,7 +282,7 @@ def uploadGcodeFile(): file = request.files["gcode_file"] filename = gcodeManager.addFile(file) global eventManager - eventManager.FireEvent("Upload",filename) + eventManager.fire("Upload",filename) return jsonify(files=gcodeManager.getAllFileData(), filename=filename) @app.route(BASEURL + "gcodefiles/load", methods=["POST"]) @@ -296,7 +296,7 @@ def loadGcodeFile(): if filename is not None: printer.loadGcode(filename, printAfterLoading) global eventManager - eventManager.FireEvent("LoadStart",filename) + eventManager.fire("LoadStart",filename) return jsonify(SUCCESS) @app.route(BASEURL + "gcodefiles/delete", methods=["POST"]) @@ -715,7 +715,7 @@ class Server(): self._server = HTTPServer(self._tornado_app) self._server.listen(self._port, address=self._host) - eventManager.FireEvent("Startup") + eventManager.fire("Startup") IOLoop.instance().start() def _createSocketConnection(self, session, endpoint=None): diff --git a/octoprint/timelapse.py b/octoprint/timelapse.py index 5056219..171cb45 100644 --- a/octoprint/timelapse.py +++ b/octoprint/timelapse.py @@ -86,7 +86,7 @@ class Timelapse(object): filename = os.path.join(self._captureDir, "tmp_%05d.jpg" % (self._imageNumber)) self._imageNumber += 1 self._logger.debug("Capturing image to %s" % filename) - self._eventManager.FireEvent("CaptureStart",filename); + self._eventManager.fire("CaptureStart",filename); captureThread = threading.Thread(target=self._captureWorker, kwargs={"filename": filename}) captureThread.daemon = True captureThread.start() @@ -94,7 +94,7 @@ class Timelapse(object): def _captureWorker(self, filename): urllib.urlretrieve(self._snapshotUrl, filename) self._logger.debug("Image %s captured from %s" % (filename, self._snapshotUrl)) - self._eventManager.FireEvent("CaptureDone",filename); + self._eventManager.fire("CaptureDone",filename); def _createMovie(self): ffmpeg = settings().get(["webcam", "ffmpeg"]) @@ -124,7 +124,7 @@ class Timelapse(object): command.append(output) subprocess.call(command) self._logger.debug("Rendering movie to %s" % output) - self._eventManager.FireEvent("MovieDone",output); + self._eventManager.fire("MovieDone",output); def cleanCaptureDir(self): if not os.path.isdir(self._captureDir): diff --git a/octoprint/util/comm.py b/octoprint/util/comm.py index 29ff801..022283b 100644 --- a/octoprint/util/comm.py +++ b/octoprint/util/comm.py @@ -478,30 +478,30 @@ class MachineCom(object): # some useful event triggered from GCode commands # pause for user input. M0 in Marlin and M1 in G-code standard RS274NGC if re.search ("^\s*M226\D",t_cmd,re.I) or re.search ("^\s*M[01]\D",t_cmd,re.I): - self._eventManager.FireEvent ('Waiting') + self._eventManager.fire ('Waiting') # part cooler started if re.search ("^\s*M245\D",t_cmd,re.I): - self._eventManager.FireEvent ('Cooling') + self._eventManager.fire ('Cooling') # part conveyor started if re.search ("^\s*M240\D",t_cmd,re.I): - self._eventManager.FireEvent ('Conveyor') + self._eventManager.fire ('Conveyor') # part ejector if re.search ("^\s*M40\D",t_cmd,re.I): - self._eventManager.FireEvent ('Eject') + self._eventManager.fire ('Eject') # user alert issued by sending beep command to printer... if re.search ("^\s*M300\D",t_cmd,re.I): - self._eventManager.FireEvent ('Alert') + self._eventManager.fire ('Alert') # Print head has moved to home if re.search ("^\s*G28\D",t_cmd,re.I): - self._eventManager.FireEvent ('Home') + self._eventManager.fire ('Home') if re.search ("^\s*M112\D",t_cmd,re.I): - self._eventManager.FireEvent ('EStop') + self._eventManager.fire ('EStop') if re.search ("^\s*M80\D",t_cmd,re.I): - self._eventManager.FireEvent ('PowerOn') + self._eventManager.fire ('PowerOn') if re.search ("^\s*M81\D",t_cmd,re.I): - self._eventManager.FireEvent ('PowerOff') + self._eventManager.fire ('PowerOff') if re.search ("^\s*M25\D",t_cmd,re.I): # SD Card pause - self._eventManager.FireEvent ('Paused') + self._eventManager.fire ('Paused') # these comparisons assume that the searched-for string is not in a comment or a parameter, for example