diff --git a/pronserve.py b/pronserve.py index fd4c545..0746bf8 100755 --- a/pronserve.py +++ b/pronserve.py @@ -26,6 +26,7 @@ import socket import mdns import uuid from operator import itemgetter, attrgetter +from collections import deque log = logging.getLogger("root") __UPLOADS__ = "./uploads" @@ -68,6 +69,21 @@ class RootHandler(tornado.web.RequestHandler): def get(self): self.render("index.html") +class PrintHandler(tornado.web.RequestHandler): + def put(self): + pronserve.do_print() + self.finish("ACK") + +class PauseHandler(tornado.web.RequestHandler): + def put(self): + pronserve.do_pause() + self.finish("ACK") + +class StopHandler(tornado.web.RequestHandler): + def put(self): + pronserve.do_stop() + self.finish("ACK") + class JobsHandler(tornado.web.RequestHandler): def post(self): fileinfo = self.request.files['job'][0] @@ -106,8 +122,33 @@ class ConstructSocketHandler(tornado.websocket.WebSocketHandler): self.write_message({'connected': {'jobs': pronserve.jobs.public_list()}}) print "WebSocket opened. %i sockets currently open." % len(pronserve.clients) - def on_sensor_change(self): - self.write_message({'sensors': pronserve.sensors, 'timestamp': time.time()}) + def on_sensor_changed(self): + print "sensor change" + self.write_message({ + 'sensor_changed': {'name': 'bed', 'value': pronserve.sensors['bed']}, + 'timestamp': time.time() + }) + self.write_message({ + 'sensor_changed': {'name': 'extruder', 'value': pronserve.sensors['extruder']}, + 'timestamp': time.time() + }) + def on_job_progress_changed(self, progress): + self.write_message({ + 'job_progress_changed': progress, + 'timestamp': time.time() + }) + + def on_job_started(self, job): + self.write_message({ + 'job_started': job, + 'timestamp': time.time() + }) + + def on_job_finished(self, job): + self.write_message({ + 'job_finished': job, + 'timestamp': time.time() + }) def on_job_added(self, job): self.write_message({'job_added': pronserve.jobs.sanitize(job)}) @@ -124,11 +165,12 @@ class ConstructSocketHandler(tornado.websocket.WebSocketHandler): def on_pronsole_log(self, msg): - self.write_message({'log': {msg: msg, level: "debug"}}) + self.write_message({'log': {'msg': msg, 'level': "debug"}}) def on_message(self, msg): + print "message received: %s"%(msg) # TODO: the read bit of repl! - self.write_message("You said: " + msg) + # self.write_message("You said: " + msg) def on_close(self): pronserve.clients.remove(self) @@ -146,7 +188,10 @@ application = tornado.web.Application([ (r"/inspect", InspectHandler), (r"/socket", ConstructSocketHandler), (r"/jobs", JobsHandler), - (r"/jobs/([0-9]*)", JobHandler) + (r"/jobs/([0-9]*)", JobHandler), + (r"/jobs/print", PrintHandler), + (r"/jobs/pause", PauseHandler), + (r"/stop", StopHandler) ], **settings) @@ -161,15 +206,53 @@ class Pronserve(pronsole.pronsole): self.stdout = sys.stdout self.ioloop = tornado.ioloop.IOLoop.instance() self.clients = set() - self.settings.sensor_poll_rate = 0.3 # seconds + self.settings.sensor_poll_rate = 1 # seconds self.sensors = {'extruder': -1, 'bed': -1} self.load_default_rc() self.jobs = PrintJobQueue() - self.job_id_incr = 0; + self.job_id_incr = 0 + self.printing_jobs = False + self.current_job = None + self.previous_job_progress = 0 services = ({'type': '_construct._tcp', 'port': 8888, 'domain': "local."}) self.mdns = mdns.publisher().save_group({'name': 'pronserve', 'services': services }) self.jobs.listeners.append(self) + def do_print(self): + if self.p.online: + self.printing_jobs = True + + def run_print_queue_loop(self): + # This is a polling work around to the current lack of events in printcore + # A better solution would be one in which a print_finised event could be + # listend for asynchronously without polling. + p = self.p + if self.printing_jobs and p.printing == False and p.paused == False and p.online: + if self.current_job != None: + self.update_job_progress(100) + self.fire("job_finished", self.jobs.sanitize(self.current_job)) + if len(self.jobs.list) > 0: + print "Starting the next print job" + self.current_job = self.jobs.list.popleft() + self.p.startprint(self.current_job['body'].split("\n")) + self.fire("job_started", self.jobs.sanitize(self.current_job)) + else: + print "Finished all print jobs" + self.current_job = None + self.printing_jobs = False + + # Updating the job progress + self.update_job_progress(self.print_progress()) + + #print "print loop" + next_timeout = time.time() + 0.3 + gen.Task(self.ioloop.add_timeout(next_timeout, self.run_print_queue_loop)) + + def update_job_progress(self, progress): + if progress != self.previous_job_progress and self.current_job != None: + self.previous_job_progress = progress + self.fire("job_progress_changed", progress) + def run_sensor_loop(self): self.request_sensor_update() next_timeout = time.time() + self.settings.sensor_poll_rate @@ -181,23 +264,34 @@ class Pronserve(pronsole.pronsole): def recvcb(self, l): """ Parses a line of output from the printer via printcore """ l = l.rstrip() - + print l if "T:" in l: self._receive_sensor_update(l) if l!="ok" and not l.startswith("ok T") and not l.startswith("T:"): self._receive_printer_error(l) + def print_progress(self): + if(self.p.printing): + return 100*float(self.p.queueindex)/len(self.p.mainqueue) + if(self.sdprinting): + return self.percentdone + return 0 + + def _receive_sensor_update(self, l): - words = l.split(" ") - words.pop(0) + words = filter(lambda s: s.find(":") > 0, l.split(" ")) d = dict([ s.split(":") for s in words]) + print "sensor update received!" + for key, value in d.iteritems(): self.__update_sensor(key, value) - self.fire("sensor_change") + self.fire("sensor_changed") def __update_sensor(self, key, value): + if (key in self.settings.sensor_names) == False: + return sensor_name = self.settings.sensor_names[key] self.sensors[sensor_name] = float(value) @@ -219,10 +313,11 @@ class Pronserve(pronsole.pronsole): def write_prompt(self): None + class PrintJobQueue(): def __init__(self): - self.list = [] + self.list = deque([]) self.__last_id = 0 self.listeners = [] @@ -246,20 +341,16 @@ class PrintJobQueue(): def add(self, original_file_name, body): ext = os.path.splitext(original_file_name)[1] - file_name = str(uuid.uuid4()) + ext job = dict( id = self.__last_id, rank = len(self.list), original_file_name=original_file_name, - path= __UPLOADS__ + "/" + file_name, + body= body, ) self.__last_id += 1 - fh = open(job['path'], 'w') - fh.write(body) - self.list.append(job) - print "Added %s as %s"%(original_file_name, file_name) + print "Added %s"%(original_file_name) self.fire("job_added", job) def display_summary(self): @@ -305,8 +396,9 @@ print "Pronserve is starting..." pronserve = Pronserve() pronserve.do_connect("") -time.sleep(0.2) +time.sleep(1) pronserve.run_sensor_loop() +pronserve.run_print_queue_loop() if __name__ == "__main__": application.listen(8888) diff --git a/server/static/css/inspect.css b/server/static/css/inspect.css index 2909fa2..8de149c 100644 --- a/server/static/css/inspect.css +++ b/server/static/css/inspect.css @@ -38,4 +38,17 @@ #temperature-graph { height: 200px; +} + +#print-job-panel +{ + margin: 0px; +} + +.job-pogress +{ + margin: 80px 0; + font-size: 40px; + line-height: 40px; + text-align: center; } \ No newline at end of file diff --git a/server/static/js/inspect.js b/server/static/js/inspect.js index 4867c25..957edaf 100644 --- a/server/static/js/inspect.js +++ b/server/static/js/inspect.js @@ -6,11 +6,15 @@ $(window).focus(function() { windowFocus = true; //if ($console) $console.append("Window refocused, restarting graph.\n"); - $(".focus-lost-overlay").addClass("out").removeClass("in"); + $(".focus-lost-overlay").addClass("out").removeClass("in").delay(1000).hide(); }).blur(function() { windowFocus = false; //if ($console) $console.append("Window's focus, lost stopping graph...\n"); - $(".focus-lost-overlay").addClass("in").removeClass("out"); + $(".focus-lost-overlay") + .stop(true,true) + .show() + .addClass("in") + .removeClass("out"); }.debounce()); var connect = function() { @@ -75,7 +79,6 @@ if(windowFocus == false) return; updateSensorsUi(); updateGraphUi(); - $consoleWrapper.scrollTop($console.innerHeight()); } var onConnect = function(ws) { @@ -85,26 +88,42 @@ // Web Socket is connected, send data using send() }; + var nextGraphPoint = {}; ws.onmessage = function (evt) { msg = JSON.parse(evt.data) - if(msg.sensors != undefined) + if(msg.sensor_changed != undefined) { var sensorNames = ["bed", "extruder"]; - var values = {timestamp: msg.timestamp}; for (var i = 0; i < sensorNames.length; i++) { - var name = sensorNames[i]; - var val = parseFloat(msg.sensors[name]); - values[name] = val; + var name = msg.sensor_changed.name; + var val = parseFloat(msg.sensor_changed.value); + nextGraphPoint[name] = val; $("."+name+" .val").data("val", val.format(1)) } - updateGraphData(values); + if(nextGraphPoint.bed != undefined && nextGraphPoint.extruder != undefined) + { + nextGraphPoint.timestamp = msg.timestamp + updateGraphData(nextGraphPoint); + nextGraphPoint = {}; + } requestAnimationFrame(updateUi); } + else if (msg.job_progress_changed != undefined) + { + val = Math.round(parseFloat(msg.job_progress_changed)*10)/10; + $(".job-pogress .val").html(val); + } else { + console.log($consoleWrapper.scrollTop() - $console.innerHeight()) + var atBottom = $consoleWrapper.scrollTop() - $console.innerHeight() > -220; $console.append(evt.data + "\n"); + if (atBottom) + { + $consoleWrapper.scrollTop($console.innerHeight()); + } } }; ws.onclose = function() diff --git a/server/templates/inspect.html b/server/templates/inspect.html index 38a0100..dd90da1 100644 --- a/server/templates/inspect.html +++ b/server/templates/inspect.html @@ -21,6 +21,12 @@ Connecting... + + +
Extruder: xx.x°C @@ -38,7 +44,7 @@
- + diff --git a/testfiles/quick-test.gcode b/testfiles/quick-test.gcode new file mode 100644 index 0000000..0ee6b3d --- /dev/null +++ b/testfiles/quick-test.gcode @@ -0,0 +1,101 @@ +; THIS IS A TZST. DO NOT ATTZMPT TO PRINT THIS FILZ. +; gZnZratZd by Slic3r 0.9.3-dZv on 2012-09-02 at 04:02:31 + +; layZr_hZight = 0.4 +; pZrimZtZrs = 3 +; solid_layZrs = 3 +; fill_dZnsity = 0.4 +; pZrimZtZr_spZZd = 30 +; infill_spZZd = 60 +; travZl_spZZd = 130 +; scalZ = 1 +; nozzlZ_diamZtZr = 0.5 +; filamZnt_diamZtZr = 3 +; Zxtrusion_multipliZr = 1 +; singlZ wall width = 0.53mm +; first layZr singlZ wall width = 0.80mm + +M104 S200 ; sZt tZmpZraturZ +;G28 ; homZ all axZs +;M109 S200 ; wait for tZmpZraturZ to bZ rZachZd +G90 ; usZ absolutZ coordinatZs +G21 ; sZt units to millimZtZrs +G92 Z0 +M82 ; usZ absolutZ distancZs for Zxtrusion +G1 Z0.400 F71800.000 +G1 X75.725 Y86.681 +G1 F1800.000 Z1.00000 +G1 X87.905 Y75.241 F1040.000 Z1.69560 +G1 X88.365 Y74.871 Z1.72017 +G1 X88.865 Y74.541 Z1.74511 +G1 X89.395 Y74.261 Z1.77006 +G1 X89.945 Y74.031 Z1.79488 +G1 X90.225 Y73.931 Z1.80726 +G1 X90.805 Y73.771 Z1.83230 +G1 X92.375 Y73.501 Z1.89862 +G1 X92.935 Y73.471 Z1.92196 +G1 X109.165 Y73.961 Z2.59789 +G1 X109.475 Y73.991 Z2.61085 +G1 X110.105 Y74.101 Z2.63747 +G1 X110.715 Y74.271 Z2.66383 +G1 X111.795 Y74.681 Z2.71192 +G1 X112.355 Y74.951 Z2.73780 +G1 X112.875 Y75.271 Z2.76322 +G1 X113.135 Y75.451 Z2.77638 +G1 X113.615 Y75.841 Z2.80213 +G1 X124.855 Y87.841 Z3.48656 +G1 X125.485 Y88.631 Z3.52863 +G1 X125.975 Y89.351 Z3.56488 +G1 X126.385 Y90.111 Z3.60083 +G1 X126.605 Y90.651 Z3.62510 +G1 X126.775 Y91.201 Z3.64906 +G1 X126.905 Y91.771 Z3.67340 +G1 X126.975 Y92.341 Z3.69731 +G1 X127.005 Y92.921 Z3.72148 +G1 X126.325 Y109.851 Z4.42681 +G1 X126.255 Y110.391 Z4.44947 +G1 X126.145 Y110.921 Z4.47201 +G1 X125.995 Y111.441 Z4.49453 +G1 X125.805 Y111.951 Z4.51719 +G1 X125.575 Y112.441 Z4.53972 +G1 X125.165 Y113.131 Z4.57313 +G1 X124.835 Y113.571 Z4.59603 +G1 X124.485 Y113.971 Z4.61815 +G1 X124.095 Y114.351 Z4.64082 +G1 X123.885 Y114.531 Z4.65233 +G1 X123.005 Y115.151 Z4.69715 +G1 X122.525 Y115.401 Z4.71967 +G1 X96.195 Y125.661 Z5.89600 +G1 X95.515 Y125.881 Z5.92575 +G1 X94.385 Y126.141 Z5.97402 +G1 X91.335 Y126.551 Z6.10213 +G1 X91.055 Y126.561 Z6.11379 +G1 X90.775 Y126.561 Z6.12545 +G1 X90.215 Y126.521 Z6.14882 +G1 X89.375 Y126.371 Z6.18434 +G1 X88.835 Y126.221 Z6.20767 +G1 X88.305 Y126.021 Z6.23125 +G1 X87.795 Y125.781 Z6.25471 +G1 X87.075 Y125.341 Z6.28984 +G1 X86.415 Y124.811 Z6.32507 +G1 X75.155 Y112.801 Z7.01038 +G1 X74.955 Y112.561 Z7.02339 +G1 X74.595 Y112.041 Z7.04972 +G1 X73.825 Y110.701 Z7.11405 +G1 X73.695 Y110.441 Z7.12615 +G1 X73.475 Y109.911 Z7.15004 +G1 X73.305 Y109.361 Z7.17400 +G1 X73.175 Y108.801 Z7.19794 +G1 X73.095 Y108.231 Z7.22190 +G1 X73.065 Y107.651 Z7.24607 +G1 X73.615 Y91.111 Z7.93497 +G1 X73.725 Y90.281 Z7.96982 +G1 X73.785 Y90.011 Z7.98134 +G1 X74.035 Y89.211 Z8.01623 +G1 X74.255 Y88.701 Z8.03935 +G1 X74.515 Y88.201 Z8.06281 +G1 X74.815 Y87.731 Z8.08602 +G1 X75.335 Y87.081 Z8.12067 +G1 X75.640 Y86.766 Z8.13893 + +