Merge pull request #356 from D1plo1d/pronserve

Merging awesomeness
master
kliment 2013-04-19 23:17:04 -07:00
commit 8c24401ddb
20 changed files with 1113 additions and 969 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.pronsolerc
*.swp
*.bak
uploads

View File

@ -3,7 +3,6 @@ Printrun consists of printcore, pronsole and pronterface, and a small collection
* printcore.py is a library that makes writing reprap hosts easy
* pronsole.py is an interactive command-line host software with tabcompletion goodness
* pronterface.py is a graphical host software with the same functionality as pronsole
* webinterface.py is a browser-usable remote control function for Pronterface
# GETTING PRINTRUN
@ -51,23 +50,19 @@ The command box recognizes all pronsole commands, but has no tabcompletion.
If you want to load stl files, you need to install a slicing program such as Slic3r and add its path to the settings.
See the Slic3r readme for more details on integration.
# Using the browser interface
To run the web interface, install Cherrypy and run Pronterface as described above.
The www server will start on the port/address you have chosen.
# USING PRONSERVE
## Webinterface Dependencies
Pronserve runs a server for remote controlling your 3D printer over your network. To use pronserve you need:
Cherrypy is required for the web interface. Download and install it by opening a
command prompt in its directory and running python setup.py install.
* python (ideally 2.6.x or 2.7.x),
* pyserial (or python-serial on ubuntu/debian) and
* tornado
* D1plo1d's py-mdns fork (https://github.com/D1plo1d/py-mdns)
* pybonjour
* bonjour for windows (Windows ONLY)
## Webinterface Configuration
* The Web interface port / ip is configurable in http.config
* The Default User / Password can be set in auth.config
## Webinterface Styling
* css/style.css can be modified to change the style of the Web Interface.
When you're done setting up Printrun, you can start `pronserve.py` in the directory you unpacked it. Once the server starts you can verify it's working by going to http://localhost:8888 in your web browser.
# USING PRONSOLE

View File

@ -1,35 +0,0 @@
<?php
$pronterfaceIP = "192.168.0.102:8080"; //Format: ip:port
$curl = curl_init();
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 1);
curl_setopt($curl,CURLOPT_TIMEOUT, 1);
curl_setopt($curl, CURLOPT_URL, "http://" . $pronterfaceIP . "/status/");
$data = curl_exec($curl);
if (curl_errno($curl) || empty($data))
{
die("Printer offline");
}
curl_close($curl);
try
{
$xml = new SimpleXMLElement($data);
echo "State: " . $xml->state . "<br />";
echo "Hotend: " . round($xml->hotend, 0) . "&deg;c<br />";
echo "Bed: " . round($xml->bed, 0) . "&deg;c<br />";
if ($xml->progress != "NA")
{
echo "Progress: " . $xml->progress . "%";
}
}
catch(Exception $e)
{
echo "ERROR:\n" . $e->getMessage(). " (severity " . $e->getCode() . ")";
}
?>

View File

@ -1,397 +0,0 @@
#!/usr/bin/python
# This file is part of the Printrun suite.
#
# Printrun is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Printrun is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Printrun. If not, see <http://www.gnu.org/licenses/>.
import pronterface
import cherrypy, re, ConfigParser, threading, sys
import os.path
from printrun.printrun_utils import configfile, imagefile, sharedfile
users = {}
def PrintHeader():
return '<html>\n<head>\n<title>Pronterface-Web</title>\n<link rel = "stylesheet" type = "text/css" href = "/css/style.css" type = "text/css"></link>\n<script src="/js/asyncCommand.js"></script>\n</head>\n<body>\n'
def PrintMenu():
return '<div id = "mainmenu"><ul><li><a href = "/">home</a></li><li><a href = "/settings">settings</a></li><li><a href = "/console">console</a></li><li><a href = "/status">status (XML)</a></li></ul></div>'
def PrintFooter():
return "</body></html>"
def ReloadPage(action):
return "<html><head><meta http-equiv='refresh' content='0;url=/'></head><body>"+action+"</body></html>"
def TReloadPage(action):
return action
def clear_text(mypass):
return mypass
gPronterPtr = 0
gWeblog = ""
gLogRefresh =5
class SettingsPage(object):
def __init__(self):
self.name = "<div id='title'>Pronterface Settings</div>"
def index(self):
pageText = PrintHeader()+self.name+PrintMenu()
pageText = pageText+"<div id='settings'><table>\n<tr><th>setting</th><th>value</th>"
pageText = pageText+"<tr>\n <td><b>Build Dimenstions</b></td><td>"+str(gPronterPtr.settings.build_dimensions)+"</td>\n</tr>"
pageText = pageText+" <tr>\n <td><b>Last Bed Temp</b></td><td>"+str(gPronterPtr.settings.last_bed_temperature)+"</td>\n</tr>"
pageText = pageText+" <tr>\n <td><b>Last File Path</b></td><td>"+gPronterPtr.settings.last_file_path+"</td>\n</tr>"
pageText = pageText+" <tr>\n <td><b>Last Temperature</b></td><td>"+str(gPronterPtr.settings.last_temperature)+"</td>\n</tr>"
pageText = pageText+" <tr>\n <td><b>Preview Extrusion Width</b></td><td>"+str(gPronterPtr.settings.preview_extrusion_width)+"</td>\n</tr>"
pageText = pageText+" <tr>\n <td><b>Filename</b></td><td>"+str(gPronterPtr.filename)+"</td></tr></div>"
pageText = pageText+PrintFooter()
return pageText
index.exposed = True
class LogPage(object):
def __init__(self):
self.name = "<div id='title'>Pronterface Console</div>"
def index(self):
pageText = "<html><head><meta http-equiv='refresh' content='"+str(gLogRefresh)+"'></head><body>"
pageText+="<div id='status'>"
pageText+=gPronterPtr.status.GetStatusText()
pageText+="</div>"
pageText = pageText+"<div id='console'>"+gWeblog+"</div>"
pageText = pageText+"</body></html>"
return pageText
index.exposed = True
class ConsolePage(object):
def __init__(self):
self.name = "<div id='title'>Pronterface Settings</div>"
def index(self):
pageText = PrintHeader()+self.name+PrintMenu()
pageText+="<div id='logframe'><iframe src='/logpage' width='100%' height='100%'>iFraming Not Supported?? No log for you.</iframe></div>"
pageText+=PrintFooter()
return pageText
index.exposed = True
class ConnectButton(object):
def index(self):
#handle connect push, then reload page
gPronterPtr.connect(0)
return ReloadPage("Connect...")
index.exposed = True
index._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class DisconnectButton(object):
def index(self):
#handle connect push, then reload page
gPronterPtr.disconnect(0)
return ReloadPage("Disconnect...")
index.exposed = True
index._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class ResetButton(object):
def index(self):
#handle connect push, then reload page
gPronterPtr.reset(0)
return ReloadPage("Reset...")
index.exposed = True
index._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class PrintButton(object):
def index(self):
#handle connect push, then reload page
gPronterPtr.printfile(0)
return ReloadPage("Print...")
index.exposed = True
index._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class PauseButton(object):
def index(self):
#handle connect push, then reload page
gPronterPtr.pause(0)
return ReloadPage("Pause...")
index.exposed = True
index._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class MoveButton(object):
def axis(self, *args):
if not args:
raise cherrypy.HTTPError(400, "No Move Command Provided!")
margs = list(args)
axis = margs.pop(0)
if(margs and axis == "x"):
distance = margs.pop(0)
gPronterPtr.onecmd('move X %s' % distance)
return ReloadPage("Moving X Axis " + str(distance))
if(margs and axis == "y"):
distance = margs.pop(0)
gPronterPtr.onecmd('move Y %s' % distance)
return ReloadPage("Moving Y Axis " + str(distance))
if(margs and axis == "z"):
distance = margs.pop(0)
gPronterPtr.onecmd('move Z %s' % distance)
return ReloadPage("Moving Z Axis " + str(distance))
raise cherrypy.HTTPError(400, "Unmached Move Command!")
axis.exposed = True
axis._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class CustomButton(object):
def button(self, *args):
if not args:
raise cherrypy.HTTPError(400, "No Custom Command Provided!")
margs = list(args)
command = margs.pop(0)
if(command):
gPronterPtr.onecmd(command)
return ReloadPage(str(command))
button.exposed = True
button._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class HomeButton(object):
def axis(self, *args):
if not args:
raise cherrypy.HTTPError(400, "No Axis Provided!")
margs = list(args)
taxis = margs.pop(0)
if(taxis == "x"):
gPronterPtr.onecmd('home X')
return ReloadPage("Home X")
if(taxis == "y"):
gPronterPtr.onecmd('home Y')
return ReloadPage("Home Y")
if(taxis == "z"):
gPronterPtr.onecmd('home Z')
return ReloadPage("Home Z")
if(taxis == "all"):
gPronterPtr.onecmd('home')
return ReloadPage("Home All")
axis.exposed = True
axis._cp_config = {'tools.basic_auth.on': True,
'tools.basic_auth.realm': 'My Print Server',
'tools.basic_auth.users': users,
'tools.basic_auth.encrypt': clear_text}
class XMLstatus(object):
def index(self):
#handle connect push, then reload page
txt='<?xml version = "1.0"?>\n<pronterface>\n'
state = "Offline"
if gPronterPtr.statuscheck or gPronterPtr.p.online:
state = "Idle"
if gPronterPtr.sdprinting:
state = "SDPrinting"
if gPronterPtr.p.printing:
state = "Printing"
if gPronterPtr.paused:
state = "Paused"
txt = txt+'<state>'+state+'</state>\n'
txt = txt+'<file>'+str(gPronterPtr.filename)+'</file>\n'
txt = txt+'<status>'+str(gPronterPtr.status.GetStatusText())+'</status>\n'
try:
temp = str(float(filter(lambda x:x.startswith("T:"), gPronterPtr.tempreport.split())[0].split(":")[1]))
txt = txt+'<hotend>'+temp+'</hotend>\n'
except:
txt = txt+'<hotend>NA</hotend>\n'
pass
try:
temp = str(float(filter(lambda x:x.startswith("B:"), gPronterPtr.tempreport.split())[0].split(":")[1]))
txt = txt+'<bed>'+temp+'</bed>\n'
except:
txt = txt+'<bed>NA</bed>\n'
pass
if gPronterPtr.sdprinting:
fractioncomplete = float(gPronterPtr.percentdone/100.0)
txt+= _("<progress>%04.2f") % (gPronterPtr.percentdone,)
txt+="</progress>\n"
elif gPronterPtr.p.printing:
fractioncomplete = float(gPronterPtr.p.queueindex)/len(gPronterPtr.p.mainqueue)
txt+= _("<progress>%04.2f") % (100*float(gPronterPtr.p.queueindex)/len(gPronterPtr.p.mainqueue),)
txt+="</progress>\n"
else:
txt+="<progress>NA</progress>\n"
txt+='</pronterface>'
return txt
index.exposed = True
class WebInterface(object):
def __init__(self, pface):
if (sys.version_info[1] > 6):
# 'allow_no_value' wasn't added until 2.7
config = ConfigParser.SafeConfigParser(allow_no_value = True)
else:
config = ConfigParser.SafeConfigParser()
config.read(configfile(pface.web_auth_config or 'auth.config'))
users[config.get("user", "user")] = config.get("user", "pass")
self.pface = pface
global gPronterPtr
global gWeblog
self.name = "<div id='title'>Pronterface Web-Interface</div>"
gWeblog = ""
gPronterPtr = self.pface
settings = SettingsPage()
logpage = LogPage()
console = ConsolePage()
#actions
connect = ConnectButton()
disconnect = DisconnectButton()
reset = ResetButton()
printbutton = PrintButton()
pausebutton = PrintButton()
status = XMLstatus()
home = HomeButton()
move = MoveButton()
custom =CustomButton()
def index(self):
pageText = PrintHeader()+self.name+PrintMenu()
pageText+="<div id='content'>\n"
pageText+="<div id='controls'>\n"
pageText+="<ul><li><a class='command' href='/connect'>Connect</a></li>\n"
pageText+="<li><a class='command' href='/disconnect'>Disconnect</a></li>\n"
pageText+="<li><a class='command' href='/reset'>Reset</a></li>\n"
pageText+="<li><a class='command' href='/printbutton'>Print</a></li>\n"
pageText+="<li><a class='command' href='/pausebutton'>Pause</a></li>\n"
for i in gPronterPtr.cpbuttons:
pageText+="<li><a class='command' href='/custom/button/"+i.command+"'>"+i.label+"</a></li>\n"
#for i in gPronterPtr.custombuttons:
# print(str(i));
pageText+="</ul>\n"
pageText+="</div>\n"
pageText+="<div id='gui'>\n"
pageText+="<div id='control_xy'>"
pageText+="<img src='/images/control_xy.png' usemap='#xymap'/>"
pageText+='<map name = "xymap">'
pageText+='<area shape = "rect" class="command" coords = "8, 5, 51, 48" href = "/home/axis/x" alt = "X Home" title = "X Home" />'
pageText+='<area shape = "rect" class="command" coords = "195, 6, 236, 46" href = "/home/axis/y" alt = "Y Home" title = "Y Home" />'
pageText+='<area shape = "rect" class="command" coords = "7, 192, 48, 232" href = "/home/axis/all" alt = "All Home" title = "All Home" />'
pageText+='<area shape = "rect" class="command" coords = "194, 192, 235, 232" href = "/home/axis/z" alt = "Z Home" title = "Z Home" />'
pageText+='<area shape = "rect" class="command" coords = "62, 7, 185, 34" href = "/move/axis/y/100" alt = "Y 100" title = "Y 100" />'
pageText+='<area shape = "rect" class="command" coords = "68, 34, 175, 61" href = "/move/axis/y/10" alt = "Y 10" title = "Y 10" />'
pageText+='<area shape = "rect" class="command" coords = "80, 60, 163, 84" href = "/move/axis/y/1" alt = "Y 1" title = "Y 1" />'
pageText+='<area shape = "rect" class="command" coords = "106, 83, 138, 107" href = "/move/axis/y/.1" alt = "Y .1" title = "Y .1" />'
pageText+='<area shape = "rect" class="command" coords = "110, 135, 142, 159" href = "/move/axis/y/-.1" alt = "Y -.1" title = "Y -.1" />'
pageText+='<area shape = "rect" class="command" coords = "81, 157, 169, 181" href = "/move/axis/y/-1" alt = "Y -1" title = "Y -1" />'
pageText+='<area shape = "rect" class="command" coords = "69, 180, 178, 206" href = "/move/axis/y/-10" alt = "Y -10" title = "Y -10" />'
pageText+='<area shape = "rect" class="command" coords = "60, 205, 186, 231" href = "/move/axis/y/-100" alt = "Y -100" title = "Y -100" />'
pageText+='<area shape = "rect" class="command" coords = "11, 53, 37, 179" href = "/move/axis/x/-100" alt = "X -100" title = "X -100" />'
pageText+='<area shape = "rect" class="command" coords = "210, 59, 236, 185" href = "/move/axis/x/100" alt = "X 100" title = "X 100" />'
pageText+='<area shape = "rect" class="command" coords = "38, 60, 64, 172" href = "/move/axis/x/-10" alt = "X -10" title = "X -10" />'
pageText+='<area shape = "rect" class="command" coords = "185, 66, 211, 178" href = "/move/axis/x/10" alt = "X 10" title = "X 10" />'
pageText+='<area shape = "rect" class="command" coords = "62, 84, 83, 157" href = "/move/axis/x/-1" alt = "X -1" title = "X -1" />'
pageText+='<area shape = "rect" class="command" coords = "163, 87, 187, 160" href = "/move/axis/x/1" alt = "X 1" title = "X 1" />'
pageText+='<area shape = "rect" class="command" coords = "82, 104, 110, 139" href = "/move/axis/x/-.1" alt = "X -.1" title = "X -.1" />'
pageText+='<area shape = "rect" class="command" coords = "137, 105, 165, 140" href = "/move/axis/x/.1" alt = "X .1" title = "X .1" />'
pageText+="</map>"
pageText+="</div>\n" #endxy
pageText+="<div id='control_z'>"
pageText+="<img src='/images/control_z.png' usemap='#zmap'/>"
pageText+='<map name = "zmap">'
pageText+='<area shape = "rect" class="command" coords = "4, 35, 54, 64" href = "/move/axis/z/10" alt = "Z 10" title = "Z 10" />'
pageText+='<area shape = "rect" class="command" coords = "4, 60, 54, 89" href = "/move/axis/z/1" alt = "Z 1" title = "Z 1" />'
pageText+='<area shape = "rect" class="command" coords = "4, 87, 54, 116" href = "/move/axis/z/.1" alt = "Z .1" title = "Z .1" />'
pageText+='<area shape = "rect" class="command" coords = "4, 121, 54, 150" href = "/move/axis/z/-.1" alt = "Z -.1" title = "Z -.1" />'
pageText+='<area shape = "rect" class="command" coords = "4, 147, 54, 176" href = "/move/axis/z/-1" alt = "Z -1" title = "Z -1" />'
pageText+='<area shape = "rect" class="command" coords = "4, 173, 54, 202" href = "/move/axis/z/-10" alt = "Z -10" title = "Z -10" />'
pageText+="</map>"
#TODO Map Z Moves
pageText+="</div>\n" #endz
pageText+="</div>\n" #endgui
pageText+="</div>\n" #endcontent
pageText+="</br>\n"
# Temp Control TBD
# pageText+="<div id='temp'>"
# pageText+="<div id='tempmenu'>"
# pageText+="<ul><li><b>Heater Temp:</b></li><li><a href='/off'>OFF</a></li><li><a href='/185'>185 (PLA)</a></li><li><a href='/240'>240 (ABS)</a></li></ul>"
# pageText+="</div>"
# pageText+="<div id='tempmenu'>"
# pageText+="<ul><li><b>Bed Temp:</b></li><li><a href='/off'>OFF</a></li><li><a href='/185'>185 (PLA)</a></li><li><a href='/240'>240 (ABS)</a></li></ul>"
# pageText+="</div>"
# pageText+="</div>"
pageText = pageText+"<div id='file'>File Loaded: <i>"+str(gPronterPtr.filename)+"</i></div>"
pageText+="<div id='logframe'><iframe src='/logpage' width='100%' height='100%'>iFraming Not Supported?? No log for you.</iframe></div>"
pageText+=PrintFooter()
return pageText
def AddLog(self, log):
global gWeblog
gWeblog = gWeblog+"</br>"+log
def AppendLog(self, log):
global gWeblog
gWeblog = re.sub("\n", "</br>", gWeblog)+log
index.exposed = True
class WebInterfaceStub(object):
def index(self):
return "<b>Web Interface Must be launched by running Pronterface!</b>"
index.exposed = True
def KillWebInterfaceThread():
cherrypy.engine.exit()
def StartWebInterfaceThread(webInterface):
current_dir = os.path.dirname(os.path.abspath(__file__))
cherrypy.config.update({'engine.autoreload_on':False})
cherrypy.config.update(configfile(webInterface.pface.web_config or "http.config"))
conf = {'/css/style.css': {'tools.staticfile.on': True,
'tools.staticfile.filename': sharedfile('web/css/style.css'),
},
'/js/asyncCommand.js': {'tools.staticfile.on': True,
'tools.staticfile.filename': sharedfile('web/js/asyncCommand.js'),
},
'/images/control_xy.png': {'tools.staticfile.on': True,
'tools.staticfile.filename': imagefile('control_xy.png'),
},
'/images/control_z.png': {'tools.staticfile.on': True,
'tools.staticfile.filename': imagefile('control_z.png'),
}}
cherrypy.config.update(configfile(webInterface.pface.web_config or "http.config"))
cherrypy.quickstart(webInterface, '/', config = conf)
if __name__ == '__main__':
cherrypy.config.update(configfile("http.config"))
cherrypy.quickstart(WebInterfaceStub())

403
pronserve.py Executable file
View File

@ -0,0 +1,403 @@
#!/usr/bin/env python
import tornado.ioloop
import tornado.web
import tornado.websocket
from tornado import gen
import tornado.httpserver
import time
import base64
import logging
import logging.config
import cmd, sys
import glob, os, time, datetime
import sys, subprocess
import math, codecs
from math import sqrt
from gcoder import GCode
import printcore
from pprint import pprint
import pronsole
from server import basic_auth
import random
import textwrap
import SocketServer
import socket
import mdns
import uuid
from operator import itemgetter, attrgetter
from collections import deque
log = logging.getLogger("root")
__UPLOADS__ = "./uploads"
# Authentication
# -------------------------------------------------
def authenticator(realm,handle,password):
"""
This method is a sample authenticator.
It treats authentication as successful
if the handle and passwords are the same.
It returns a tuple of handle and user name
"""
if handle == "admin" and password == "admin" :
return (handle,'Authorized User')
return None
def user_extractor(user_data):
"""
This method extracts the user handle from
the data structure returned by the authenticator
"""
return user_data[0]
def socket_auth(self):
user = self.get_argument("user", None)
password = self.get_argument("password", None)
return authenticator(None, user, password)
interceptor = basic_auth.interceptor
auth = basic_auth.authenticate('auth_realm', authenticator, user_extractor)
#@interceptor(auth)
# Routing
# -------------------------------------------------
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]
pronserve.jobs.add(fileinfo['filename'], fileinfo['body'])
self.finish("ACK")
class JobHandler(tornado.web.RequestHandler):
def delete(self, job_id):
pronserve.jobs.remove(int(job_id))
self.finish("ACK")
def put(self, job_id):
args = {'position': int(self.get_argument("job[position]"))}
pronserve.jobs.update(int(job_id), args)
self.finish("ACK")
class InspectHandler(tornado.web.RequestHandler):
def prepare(self):
auth(self, None)
def get(self):
self.render("inspect.html")
#class EchoWebSocketHandler(tornado.web.RequestHandler):
class ConstructSocketHandler(tornado.websocket.WebSocketHandler):
def on_sensor_changed(self):
for name in ['bed', 'extruder']:
self.send(
sensor_changed= {'name': name, 'value': pronserve.sensors[name]},
)
def on_uncaught_event(self, event_name, data):
listener = "on_%s"%event_name
if event_name[:4] == 'job_' and event_name != "job_progress_changed":
data = pronserve.jobs.sanitize(data)
self.send({event_name: data})
def _execute(self, transforms, *args, **kwargs):
if socket_auth(self):
super(ConstructSocketHandler, self)._execute(transforms, *args, **kwargs)
else:
self.stream.close();
def open(self):
pronserve.listeners.add(self)
self.write_message({'connected': {'jobs': pronserve.jobs.public_list()}})
print "WebSocket opened. %i sockets currently open." % len(pronserve.listeners)
def send(self, dict_args = {}, **kwargs):
args = dict(dict_args.items() + kwargs.items())
args['timestamp']= time.time()
self.write_message(args)
def on_message(self, msg):
print "message received: %s"%(msg)
# TODO: the read bit of repl!
# self.write_message("You said: " + msg)
def on_close(self):
pronserve.listeners.remove(self)
print "WebSocket closed. %i sockets currently open." % len(pronserve.listeners)
dir = os.path.dirname(__file__)
settings = dict(
template_path=os.path.join(dir, "server", "templates"),
static_path=os.path.join(dir, "server", "static"),
debug=True
)
application = tornado.web.Application([
(r"/", RootHandler),
(r"/inspect", InspectHandler),
(r"/socket", ConstructSocketHandler),
(r"/jobs", JobsHandler),
(r"/jobs/([0-9]*)", JobHandler),
(r"/jobs/print", PrintHandler),
(r"/jobs/pause", PauseHandler),
(r"/stop", StopHandler)
], **settings)
# Event Emitter
# -------------------------------------------------
class EventEmitter(object):
def __init__(self):
self.listeners = set()
def fire(self, event_name, content=None):
callback_name = "on_%s" % event_name
for listener in self.listeners:
if hasattr(listener, callback_name):
callback = getattr(listener, callback_name)
if content == None: callback()
else: callback(content)
elif hasattr(listener, "on_uncaught_event"):
listener.on_uncaught_event(event_name, content)
else:
continue
# Pronserve: Server-specific functionality
# -------------------------------------------------
class Pronserve(pronsole.pronsole, EventEmitter):
def __init__(self):
pronsole.pronsole.__init__(self)
EventEmitter.__init__(self)
self.settings.sensor_names = {'T': 'extruder', 'B': 'bed'}
self.stdout = sys.stdout
self.ioloop = tornado.ioloop.IOLoop.instance()
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.printing_jobs = False
self.current_job = None
self.previous_job_progress = 0
self.silent = True
services = ({'type': '_construct._tcp', 'port': 8888, 'domain': "local."})
self.mdns = mdns.publisher().save_group({'name': 'pronserve', 'services': services })
self.jobs.listeners.add(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
gen.Task(self.ioloop.add_timeout(next_timeout, self.run_sensor_loop))
def request_sensor_update(self):
if self.p.online: self.p.send_now("M105")
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 = 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_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)
def on_uncaught_event(self, event_name, content=None):
self.fire(event_name, content)
def log(self, *msg):
msg = ''.join(str(i) for i in msg)
msg.replace("\r", "")
print msg
self.fire("log", {'msg': msg, 'level': "debug"})
def write_prompt(self):
None
class PrintJobQueue(EventEmitter):
def __init__(self):
super(PrintJobQueue, self).__init__()
self.list = deque([])
self.__last_id = 0
def public_list(self):
# A sanitized version of list for public consumption via construct
l2 = []
for job in self.list:
l2.append(self.sanitize(job))
return l2
def sanitize(self, job):
return dict(
id = job["id"],
original_file_name = job["original_file_name"],
rank = job["rank"]
)
def order(self):
sorted(self.list, key=lambda job: job['rank'])
def add(self, original_file_name, body):
ext = os.path.splitext(original_file_name)[1]
job = dict(
id = self.__last_id,
rank = len(self.list),
original_file_name=original_file_name,
body= body,
)
self.__last_id += 1
self.list.append(job)
print "Added %s"%(original_file_name)
self.fire("job_added", job)
def display_summary(self):
print "Print Jobs:"
for job in self.list:
print " %i: %s"%(job['id'], job['original_file_name'])
print ""
return True
def remove(self, job_id):
job = self.find_by_id(job_id)
if job == None:
return False
self.list.remove(job)
print "Print Job Removed"
self.fire("job_removed", job)
def update(self, job_id, job_attrs):
job = self.find_by_id(job_id)
if job == None:
return False
job['rank'] = job_attrs['position']
self.order()
print "Print Job Updated"
self.fire("job_updated", job)
def find_by_id(self, job_id):
for job in self.list:
if job['id'] == job_id: return job
return None
def fire(self, event_name, content):
self.display_summary()
super(PrintJobQueue, self).fire(event_name, content)
# Server Start Up
# -------------------------------------------------
print "Pronserve is starting..."
pronserve = Pronserve()
pronserve.do_connect("")
time.sleep(1)
pronserve.run_sensor_loop()
pronserve.run_print_queue_loop()
if __name__ == "__main__":
application.listen(8888)
print "\n"+"-"*80
welcome = textwrap.dedent(u"""
+---+ \x1B[0;32mPronserve: Your printer just got a whole lot better.\x1B[0m
| \u2713 | Ready to print.
+---+ More details at http://localhost:8888/""")
sys.stdout.write(welcome)
print "\n\n" + "-"*80 + "\n"
try:
pronserve.ioloop.start()
except:
pronserve.p.disconnect()

View File

@ -77,7 +77,7 @@ def estimate_duration(g):
#TODO:
# get device caps from firmware: max speed, acceleration/axis (including extruder)
# calculate the maximum move duration accounting for above ;)
# print ".... estimating ...."
# self.log(".... estimating ....")
for i in g:
i = i.split(";")[0]
if "G4" in i or "G1" in i:
@ -119,7 +119,7 @@ def estimate_duration(g):
if z > lastz:
layercount +=1
#print "layer z: ", lastz, " will take: ", time.strftime('%H:%M:%S', time.gmtime(totalduration-layerbeginduration))
#self.log("layer z: ", lastz, " will take: ", time.strftime('%H:%M:%S', time.gmtime(totalduration-layerbeginduration)))
layerbeginduration = totalduration
lastx = x
@ -128,7 +128,7 @@ def estimate_duration(g):
laste = e
lastf = f
#print "Total Duration: " #, time.strftime('%H:%M:%S', time.gmtime(totalduration))
#self.log("Total Duration: " #, time.strftime('%H:%M:%S', time.gmtime(totalduration)))
return "{0:d} layers, ".format(int(layercount)) + str(datetime.timedelta(seconds = int(totalduration)))
def confirm():
@ -252,6 +252,7 @@ class pronsole(cmd.Cmd):
self.settings._bedtemp_abs_cb = self.set_temp_preset
self.settings._bedtemp_pla_cb = self.set_temp_preset
self.monitoring = 0
self.silent = False
self.helpdict = {}
self.helpdict["baudrate"] = _("Communications Speed (default: 115200)")
self.helpdict["bedtemp_abs"] = _("Heated Build Platform temp for ABS (default: 110 deg C)")
@ -274,6 +275,9 @@ class pronsole(cmd.Cmd):
"macro" : "%(bold)s..>%(normal)s ",
"online" : "%(bold)sT:%(extruder_temp_fancy)s %(progress_fancy)s >%(normal)s "}
def log(self, *msg):
print ''.join(str(i) for i in msg)
def promptf(self):
"""A function to generate prompts so that we can do dynamic prompts. """
if self.in_macro:
@ -323,11 +327,11 @@ class pronsole(cmd.Cmd):
if not key.startswith("bed"):
self.temps["pla"] = str(self.settings.temperature_pla)
self.temps["abs"] = str(self.settings.temperature_abs)
print "Hotend temperature presets updated, pla:%s, abs:%s" % (self.temps["pla"], self.temps["abs"])
self.log("Hotend temperature presets updated, pla:%s, abs:%s" % (self.temps["pla"], self.temps["abs"]))
else:
self.bedtemps["pla"] = str(self.settings.bedtemp_pla)
self.bedtemps["abs"] = str(self.settings.bedtemp_abs)
print "Bed temperature presets updated, pla:%s, abs:%s" % (self.bedtemps["pla"], self.bedtemps["abs"])
self.log("Bed temperature presets updated, pla:%s, abs:%s" % (self.bedtemps["pla"], self.bedtemps["abs"]))
def scanserial(self):
"""scan for available ports. return a list of device names."""
@ -345,7 +349,10 @@ class pronsole(cmd.Cmd):
return baselist+glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') +glob.glob("/dev/tty.*")+glob.glob("/dev/cu.*")+glob.glob("/dev/rfcomm*")
def online(self):
print "\rPrinter is now online"
self.log("\rPrinter is now online")
self.write_prompt()
def write_prompt(self):
sys.stdout.write(self.promptf())
sys.stdout.flush()
@ -356,7 +363,7 @@ class pronsole(cmd.Cmd):
self.help_gcodes()
def help_gcodes(self):
print "Gcodes are passed through to the printer as they are"
self.log("Gcodes are passed through to the printer as they are")
def complete_macro(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@ -386,7 +393,7 @@ class pronsole(cmd.Cmd):
setattr(self.__class__, "do_"+self.cur_macro_name, lambda self, largs, macro = macro:macro(self,*largs.split()))
setattr(self.__class__, "help_"+self.cur_macro_name, lambda self, macro_name = self.cur_macro_name: self.subhelp_macro(macro_name))
if not self.processing_rc:
print "Macro '"+self.cur_macro_name+"' defined"
self.log("Macro '"+self.cur_macro_name+"' defined")
# save it
if not self.processing_args:
macro_key = "macro "+self.cur_macro_name
@ -398,7 +405,7 @@ class pronsole(cmd.Cmd):
macro_def += self.cur_macro_def
self.save_in_rc(macro_key, macro_def)
else:
print "Empty macro - cancelled"
self.log("Empty macro - cancelled")
del self.cur_macro_name, self.cur_macro_def
def compile_macro_line(self, line):
@ -413,7 +420,7 @@ class pronsole(cmd.Cmd):
def compile_macro(self, macro_name, macro_def):
if macro_def.strip() == "":
print "Empty macro - cancelled"
self.log("Empty macro - cancelled")
return
pycode = "def macro(self,*arg):\n"
if "\n" not in macro_def.strip():
@ -427,7 +434,7 @@ class pronsole(cmd.Cmd):
def start_macro(self, macro_name, prev_definition = "", suppress_instructions = False):
if not self.processing_rc and not suppress_instructions:
print "Enter macro using indented lines, end with empty line"
self.log("Enter macro using indented lines, end with empty line")
self.cur_macro_name = macro_name
self.cur_macro_def = ""
self.onecmd = self.hook_macro # override onecmd temporarily
@ -438,11 +445,11 @@ class pronsole(cmd.Cmd):
if macro_name in self.macros.keys():
delattr(self.__class__, "do_"+macro_name)
del self.macros[macro_name]
print "Macro '"+macro_name+"' removed"
self.log("Macro '"+macro_name+"' removed")
if not self.processing_rc and not self.processing_args:
self.save_in_rc("macro "+macro_name, "")
else:
print "Macro '"+macro_name+"' is not defined"
self.log("Macro '"+macro_name+"' is not defined")
def do_macro(self, args):
if args.strip()=="":
self.print_topics("User-defined macros", self.macros.keys(), 15, 80)
@ -450,7 +457,7 @@ class pronsole(cmd.Cmd):
arglist = args.split(None, 1)
macro_name = arglist[0]
if macro_name not in self.macros and hasattr(self.__class__, "do_"+macro_name):
print "Name '"+macro_name+"' is being used by built-in command"
self.log("Name '"+macro_name+"' is being used by built-in command")
return
if len(arglist) == 2:
macro_def = arglist[1]
@ -470,24 +477,24 @@ class pronsole(cmd.Cmd):
self.start_macro(macro_name)
def help_macro(self):
print "Define single-line macro: macro <name> <definition>"
print "Define multi-line macro: macro <name>"
print "Enter macro definition in indented lines. Use {0} .. {N} to substitute macro arguments"
print "Enter python code, prefixed with ! Use arg[0] .. arg[N] to substitute macro arguments"
print "Delete macro: macro <name> /d"
print "Show macro definition: macro <name> /s"
print "'macro' without arguments displays list of defined macros"
self.log("Define single-line macro: macro <name> <definition>")
self.log("Define multi-line macro: macro <name>")
self.log("Enter macro definition in indented lines. Use {0} .. {N} to substitute macro arguments")
self.log("Enter python code, prefixed with ! Use arg[0] .. arg[N] to substitute macro arguments")
self.log("Delete macro: macro <name> /d")
self.log("Show macro definition: macro <name> /s")
self.log("'macro' without arguments displays list of defined macros")
def subhelp_macro(self, macro_name):
if macro_name in self.macros.keys():
macro_def = self.macros[macro_name]
if "\n" in macro_def:
print "Macro '"+macro_name+"' defined as:"
print self.macros[macro_name]+"----------------"
self.log("Macro '"+macro_name+"' defined as:")
self.log(self.macros[macro_name]+"----------------")
else:
print "Macro '"+macro_name+"' defined as: '"+macro_def+"'"
self.log("Macro '"+macro_name+"' defined as: '"+macro_def+"'")
else:
print "Macro '"+macro_name+"' is not defined"
self.log("Macro '"+macro_name+"' is not defined")
def set(self, var, str):
try:
@ -496,29 +503,29 @@ class pronsole(cmd.Cmd):
if not self.processing_rc and not self.processing_args:
self.save_in_rc("set "+var, "set %s %s" % (var, value))
except AttributeError:
print "Unknown variable '%s'" % var
self.log("Unknown variable '%s'" % var)
except ValueError, ve:
print "Bad value for variable '%s', expecting %s (%s)" % (var, repr(t)[1:-1], ve.args[0])
self.log("Bad value for variable '%s', expecting %s (%s)" % (var, repr(t)[1:-1], ve.args[0]))
def do_set(self, argl):
args = argl.split(None, 1)
if len(args) < 1:
for k in [kk for kk in dir(self.settings) if not kk.startswith("_")]:
print "%s = %s" % (k, str(getattr(self.settings, k)))
self.log("%s = %s" % (k, str(getattr(self.settings, k))))
return
value = getattr(self.settings, args[0])
if len(args) < 2:
try:
print "%s = %s" % (args[0], getattr(self.settings, args[0]))
self.log("%s = %s" % (args[0], getattr(self.settings, args[0])))
except AttributeError:
print "Unknown variable '%s'" % args[0]
self.log("Unknown variable '%s'" % args[0])
return
self.set(args[0], args[1])
def help_set(self):
print "Set variable: set <variable> <value>"
print "Show variable: set <variable>"
print "'set' without arguments displays all variables"
self.log("Set variable: set <variable> <value>")
self.log("Show variable: set <variable>")
self.log("'set' without arguments displays all variables")
def complete_set(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@ -600,16 +607,16 @@ class pronsole(cmd.Cmd):
rci.close()
rco.close()
#if definition != "":
# print "Saved '"+key+"' to '"+self.rc_filename+"'"
# self.log("Saved '"+key+"' to '"+self.rc_filename+"'")
#else:
# print "Removed '"+key+"' from '"+self.rc_filename+"'"
# self.log("Removed '"+key+"' from '"+self.rc_filename+"'")
except Exception, e:
print "Saving failed for", key+":", str(e)
self.log("Saving failed for", key+":", str(e))
finally:
del rci, rco
def preloop(self):
print "Welcome to the printer console! Type \"help\" for a list of available commands."
self.log("Welcome to the printer console! Type \"help\" for a list of available commands.")
self.prompt = self.promptf()
cmd.Cmd.preloop(self)
@ -626,12 +633,12 @@ class pronsole(cmd.Cmd):
try:
baud = int(a[1])
except:
print "Bad baud value '"+a[1]+"' ignored"
self.log("Bad baud value '"+a[1]+"' ignored")
if len(p) == 0 and not port:
print "No serial ports detected - please specify a port"
self.log("No serial ports detected - please specify a port")
return
if len(a) == 0:
print "No port specified - connecting to %s at %dbps" % (port, baud)
self.log("No port specified - connecting to %s at %dbps" % (port, baud))
if port != self.settings.port:
self.settings.port = port
self.save_in_rc("set port", "set port %s" % port)
@ -641,14 +648,14 @@ class pronsole(cmd.Cmd):
self.p.connect(port, baud)
def help_connect(self):
print "Connect to printer"
print "connect <port> <baudrate>"
print "If port and baudrate are not specified, connects to first detected port at 115200bps"
self.log("Connect to printer")
self.log("connect <port> <baudrate>")
self.log("If port and baudrate are not specified, connects to first detected port at 115200bps")
ports = self.scanserial()
if(len(ports)):
print "Available ports: ", " ".join(ports)
self.log("Available ports: ", " ".join(ports))
else:
print "No serial ports were automatically found."
self.log("No serial ports were automatically found.")
def complete_connect(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@ -662,22 +669,22 @@ class pronsole(cmd.Cmd):
self.p.disconnect()
def help_disconnect(self):
print "Disconnects from the printer"
self.log("Disconnects from the printer")
def do_load(self,l):
self._do_load(l)
def _do_load(self,l):
if len(l)==0:
print "No file name given."
self.log("No file name given.")
return
print "Loading file:"+l
self.log("Loading file:"+l)
if not(os.path.exists(l)):
print "File not found!"
self.log("File not found!")
return
self.f = [i.replace("\n", "").replace("\r", "") for i in open(l)]
self.filename = l
print "Loaded ", l, ", ", len(self.f)," lines."
self.log("Loaded ", l, ", ", len(self.f)," lines.")
def complete_load(self, text, line, begidx, endidx):
s = line.split()
@ -690,32 +697,32 @@ class pronsole(cmd.Cmd):
return glob.glob("*/")+glob.glob("*.g*")
def help_load(self):
print "Loads a gcode file (with tab-completion)"
self.log("Loads a gcode file (with tab-completion)")
def do_upload(self, l):
if len(l) == 0:
print "No file name given."
self.log("No file name given.")
return
print "Loading file:"+l.split()[0]
self.log("Loading file:"+l.split()[0])
if not(os.path.exists(l.split()[0])):
print "File not found!"
self.log("File not found!")
return
if not self.p.online:
print "Not connected to printer."
self.log("Not connected to printer.")
return
self.f = [i.replace("\n", "") for i in open(l.split()[0])]
self.filename = l.split()[0]
print "Loaded ", l, ", ", len(self.f)," lines."
self.log("Loaded ", l, ", ", len(self.f)," lines.")
tname = ""
if len(l.split())>1:
tname = l.split()[1]
else:
print "please enter target name in 8.3 format."
self.log("please enter target name in 8.3 format.")
return
print "Uploading as ", tname
print("Uploading "+self.filename)
self.log("Uploading as ", tname)
self.log(("Uploading "+self.filename))
self.p.send_now("M28 "+tname)
print("Press Ctrl-C to interrupt upload.")
self.log(("Press Ctrl-C to interrupt upload."))
self.p.startprint(self.f)
try:
sys.stdout.write("Progress: 00.0%")
@ -733,16 +740,16 @@ class pronsole(cmd.Cmd):
self.recvlisteners+=[self.listfiles]
self.p.send_now("M20")
time.sleep(0.5)
print "\b\b\b\b\b100%. Upload completed. ", tname, " should now be on the card."
self.log("\b\b\b\b\b100%. Upload completed. ", tname, " should now be on the card.")
return
except:
print "...interrupted!"
self.log("...interrupted!")
self.p.pause()
self.p.send_now("M29 "+tname)
time.sleep(0.2)
self.p.clear = 1
self.p.startprint([])
print "A partial file named ", tname, " may have been written to the sd card."
self.log("A partial file named ", tname, " may have been written to the sd card.")
def complete_upload(self, text, line, begidx, endidx):
@ -756,23 +763,23 @@ class pronsole(cmd.Cmd):
return glob.glob("*/")+glob.glob("*.g*")
def help_upload(self):
print "Uploads a gcode file to the sd card"
self.log("Uploads a gcode file to the sd card")
def help_print(self):
if self.f is None:
print "Send a loaded gcode file to the printer. Load a file with the load command first."
self.log("Send a loaded gcode file to the printer. Load a file with the load command first.")
else:
print "Send a loaded gcode file to the printer. You have "+self.filename+" loaded right now."
self.log("Send a loaded gcode file to the printer. You have "+self.filename+" loaded right now.")
def do_print(self, l):
if self.f is None:
print "No file loaded. Please use load first."
self.log("No file loaded. Please use load first.")
return
if not self.p.online:
print "Not connected to printer."
self.log("Not connected to printer.")
return
print("Printing "+self.filename)
print("You can monitor the print with the monitor command.")
self.log(("printing "+self.filename))
self.log(("You can monitor the print with the monitor command."))
self.p.startprint(self.f)
#self.p.pause()
#self.paused = True
@ -783,7 +790,7 @@ class pronsole(cmd.Cmd):
self.p.send_now("M25")
else:
if(not self.p.printing):
print "Not printing, cannot pause."
self.log("Not self.log(ing, cannot pause.")
return
self.p.pause()
#self.p.connect()# This seems to work, but is not a good solution.
@ -792,11 +799,11 @@ class pronsole(cmd.Cmd):
#self.do_resume(None)
def help_pause(self):
print "Pauses a running print"
self.log("Pauses a running print")
def do_resume(self, l):
if not self.paused:
print "Not paused, unable to resume. Start a print first."
self.log("Not paused, unable to resume. Start a print first.")
return
self.paused = False
if self.sdprinting:
@ -806,7 +813,7 @@ class pronsole(cmd.Cmd):
self.p.resume()
def help_resume(self):
print "Resumes a paused print."
self.log("Resumes a paused print.")
def emptyline(self):
pass
@ -825,33 +832,33 @@ class pronsole(cmd.Cmd):
def do_ls(self, l):
if not self.p.online:
print "Printer is not online. Try connect to it first."
self.log("printer is not online. Try connect to it first.")
return
self.listing = 2
self.sdfiles = []
self.recvlisteners+=[self.listfiles]
self.p.send_now("M20")
time.sleep(0.5)
print " ".join(self.sdfiles)
self.log(" ".join(self.sdfiles))
def help_ls(self):
print "lists files on the SD card"
self.log("lists files on the SD card")
def waitforsdresponse(self, l):
if "file.open failed" in l:
print "Opening file failed."
self.log("Opening file failed.")
self.recvlisteners.remove(self.waitforsdresponse)
return
if "File opened" in l:
print l
self.log(l)
if "File selected" in l:
print "Starting print"
self.log("Starting print")
self.p.send_now("M24")
self.sdprinting = 1
#self.recvlisteners.remove(self.waitforsdresponse)
return
if "Done printing file" in l:
print l
self.log(l)
self.sdprinting = 0
self.recvlisteners.remove(self.waitforsdresponse)
return
@ -868,11 +875,11 @@ class pronsole(cmd.Cmd):
self.p.reset()
def help_reset(self):
print "Resets the printer."
self.log("Resets the printer.")
def do_sdprint(self, l):
if not self.p.online:
print "Printer is not online. Try connect to it first."
self.log("printer is not online. Try connect to it first.")
return
self.listing = 2
self.sdfiles = []
@ -880,17 +887,17 @@ class pronsole(cmd.Cmd):
self.p.send_now("M20")
time.sleep(0.5)
if not (l.lower() in self.sdfiles):
print "File is not present on card. Upload it first"
self.log("File is not present on card. Upload it first")
return
self.recvlisteners+=[self.waitforsdresponse]
self.p.send_now("M23 "+l.lower())
print "Printing file: "+l.lower()+" from SD card."
print "Requesting SD print..."
self.log("printing file: "+l.lower()+" from SD card.")
self.log("Requesting SD print...")
time.sleep(1)
def help_sdprint(self):
print "Print a file from the SD card. Tabcompletes with available file names."
print "sdprint filename.g"
self.log("print a file from the SD card. Tabcompletes with available file names.")
self.log("sdprint filename.g")
def complete_sdprint(self, text, line, begidx, endidx):
if self.sdfiles==[] and self.p.online:
@ -909,32 +916,32 @@ class pronsole(cmd.Cmd):
if(tstring!="ok" and not tstring.startswith("ok T") and not tstring.startswith("T:") and not self.listing and not self.monitoring):
if tstring[:5] == "echo:":
tstring = tstring[5:].lstrip()
print "\r" + tstring.ljust(15)
if self.silent == False: print "\r" + tstring.ljust(15)
sys.stdout.write(self.promptf())
sys.stdout.flush()
for i in self.recvlisteners:
i(l)
def help_shell(self):
print "Executes a python command. Example:"
print "! os.listdir('.')"
self.log("Executes a python command. Example:")
self.log("! os.listdir('.')")
def default(self, l):
if(l[0] in self.commandprefixes.upper()):
if(self.p and self.p.online):
if(not self.p.loud):
print "SENDING:"+l
self.log("SENDING:"+l)
self.p.send_now(l)
else:
print "Printer is not online."
self.log("printer is not online.")
return
elif(l[0] in self.commandprefixes.lower()):
if(self.p and self.p.online):
if(not self.p.loud):
print "SENDING:"+l.upper()
self.log("SENDING:"+l.upper())
self.p.send_now(l.upper())
else:
print "Printer is not online."
self.log("printer is not online.")
return
else:
cmd.Cmd.default(self, l)
@ -942,6 +949,10 @@ class pronsole(cmd.Cmd):
def help_help(self):
self.do_help("")
def tempcb(self, l):
if "T:" in l:
self.log(l.replace("\r", "").replace("T", "Hotend").replace("B", "Bed").replace("\n", "").replace("ok ", ""))
def do_gettemp(self, l):
if "dynamic" in l:
self.dynamic_temp = True
@ -955,7 +966,7 @@ class pronsole(cmd.Cmd):
print "Bed: %s/%s" % (self.status.bed_temp, self.status.bed_temp_target)
def help_gettemp(self):
print "Read the extruder and bed temperature."
self.log("Read the extruder and bed temperature.")
def do_settemp(self, l):
try:
@ -970,18 +981,18 @@ class pronsole(cmd.Cmd):
return
if self.p.online:
self.p.send_now("M104 S"+l)
print "Setting hotend temperature to ", f, " degrees Celsius."
self.log("Setting hotend temperature to ", f, " degrees Celsius.")
else:
print "Printer is not online."
self.log("printer is not online.")
else:
print "You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0."
self.log("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")
except:
print "You must enter a temperature."
self.log("You must enter a temperature.")
def help_settemp(self):
print "Sets the hotend temperature to the value entered."
print "Enter either a temperature in celsius or one of the following keywords"
print ", ".join([i+"("+self.temps[i]+")" for i in self.temps.keys()])
self.log("Sets the hotend temperature to the value entered.")
self.log("Enter either a temperature in celsius or one of the following keywords")
self.log(", ".join([i+"("+self.temps[i]+")" for i in self.temps.keys()]))
def complete_settemp(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@ -996,18 +1007,18 @@ class pronsole(cmd.Cmd):
if f>=0:
if self.p.online:
self.p.send_now("M140 S"+l)
print "Setting bed temperature to ", f, " degrees Celsius."
self.log("Setting bed temperature to ", f, " degrees Celsius.")
else:
print "Printer is not online."
self.log("printer is not online.")
else:
print "You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0."
self.log("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")
except:
print "You must enter a temperature."
self.log("You must enter a temperature.")
def help_bedtemp(self):
print "Sets the bed temperature to the value entered."
print "Enter either a temperature in celsius or one of the following keywords"
print ", ".join([i+"("+self.bedtemps[i]+")" for i in self.bedtemps.keys()])
self.log("Sets the bed temperature to the value entered.")
self.log("Enter either a temperature in celsius or one of the following keywords")
self.log(", ".join([i+"("+self.bedtemps[i]+")" for i in self.bedtemps.keys()]))
def complete_bedtemp(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@ -1015,13 +1026,13 @@ class pronsole(cmd.Cmd):
def do_move(self, l):
if(len(l.split())<2):
print "No move specified."
self.log("No move specified.")
return
if self.p.printing:
print "Printer is currently printing. Please pause the print before you issue manual commands."
self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
if not self.p.online:
print "Printer is not online. Unable to move."
self.log("printer is not online. Unable to move.")
return
l = l.split()
if(l[0].lower()=="x"):
@ -1037,13 +1048,13 @@ class pronsole(cmd.Cmd):
feed = self.settings.e_feedrate
axis = "E"
else:
print "Unknown axis."
self.log("Unknown axis.")
return
dist = 0
try:
dist = float(l[1])
except:
print "Invalid distance"
self.log("Invalid distance")
return
try:
feed = int(l[2])
@ -1054,11 +1065,11 @@ class pronsole(cmd.Cmd):
self.p.send_now("G90")
def help_move(self):
print "Move an axis. Specify the name of the axis and the amount. "
print "move X 10 will move the X axis forward by 10mm at ", self.settings.xy_feedrate, "mm/min (default XY speed)"
print "move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min"
print "move Z -1 will move the Z axis down by 1mm at ", self.settings.z_feedrate, "mm/min (default Z speed)"
print "Common amounts are in the tabcomplete list."
self.log("Move an axis. Specify the name of the axis and the amount. ")
self.log("move X 10 will move the X axis forward by 10mm at ", self.settings.xy_feedrate, "mm/min (default XY speed)")
self.log("move Y 10 5000 will move the Y axis forward by 10mm at 5000mm/min")
self.log("move Z -1 will move the Z axis down by 1mm at ", self.settings.z_feedrate, "mm/min (default Z speed)")
self.log("Common amounts are in the tabcomplete list.")
def complete_move(self, text, line, begidx, endidx):
if (len(line.split()) == 2 and line[-1] != " ") or (len(line.split()) == 1 and line[-1]==" "):
@ -1078,29 +1089,29 @@ class pronsole(cmd.Cmd):
length = 5#default extrusion length
feed = self.settings.e_feedrate#default speed
if not self.p.online:
print "Printer is not online. Unable to move."
self.log("printer is not online. Unable to move.")
return
if self.p.printing:
print "Printer is currently printing. Please pause the print before you issue manual commands."
self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
ls = l.split()
if len(ls):
try:
length = float(ls[0])
except:
print "Invalid length given."
self.log("Invalid length given.")
if len(ls)>1:
try:
feed = int(ls[1])
except:
print "Invalid speed given."
self.log("Invalid speed given.")
if override is not None:
length = override
feed = overridefeed
if length > 0:
print "Extruding %fmm of filament."%(length,)
self.log("Extruding %fmm of filament."%(length,))
elif length <0:
print "Reversing %fmm of filament."%(-1*length,)
self.log("Reversing %fmm of filament."%(-1*length,))
else:
"Length is 0, not doing anything."
self.p.send_now("G91")
@ -1108,40 +1119,40 @@ class pronsole(cmd.Cmd):
self.p.send_now("G90")
def help_extrude(self):
print "Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter"
print "extrude - extrudes 5mm of filament at 300mm/min (5mm/s)"
print "extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)"
print "extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)"
print "extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)"
self.log("Extrudes a length of filament, 5mm by default, or the number of mm given as a parameter")
self.log("extrude - extrudes 5mm of filament at 300mm/min (5mm/s)")
self.log("extrude 20 - extrudes 20mm of filament at 300mm/min (5mm/s)")
self.log("extrude -5 - REVERSES 5mm of filament at 300mm/min (5mm/s)")
self.log("extrude 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")
def do_reverse(self, l):
length = 5#default extrusion length
feed = self.settings.e_feedrate#default speed
if not self.p.online:
print "Printer is not online. Unable to move."
self.log("printer is not online. Unable to move.")
return
if self.p.printing:
print "Printer is currently printing. Please pause the print before you issue manual commands."
self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
ls = l.split()
if len(ls):
try:
length = float(ls[0])
except:
print "Invalid length given."
self.log("Invalid length given.")
if len(ls)>1:
try:
feed = int(ls[1])
except:
print "Invalid speed given."
self.log("Invalid speed given.")
self.do_extrude("", length*-1.0, feed)
def help_reverse(self):
print "Reverses the extruder, 5mm by default, or the number of mm given as a parameter"
print "reverse - reverses 5mm of filament at 300mm/min (5mm/s)"
print "reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)"
print "reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)"
print "reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)"
self.log("Reverses the extruder, 5mm by default, or the number of mm given as a parameter")
self.log("reverse - reverses 5mm of filament at 300mm/min (5mm/s)")
self.log("reverse 20 - reverses 20mm of filament at 300mm/min (5mm/s)")
self.log("reverse 10 210 - extrudes 10mm of filament at 210mm/min (3.5mm/s)")
self.log("reverse -5 - EXTRUDES 5mm of filament at 300mm/min (5mm/s)")
def do_exit(self, l):
if self.status.extruder_temp_target != 0:
@ -1151,35 +1162,35 @@ class pronsole(cmd.Cmd):
if self.status.bed_temp_taret != 0:
print "Setting bed temp to 0"
self.p.send_now("M140 S0.0")
print "Disconnecting from printer..."
self.log("Disconnecting from printer...")
print self.p.printing
if self.p.printing:
print "Are you sure you want to exit while printing?"
print "(this will terminate the print)."
if not confirm():
return False
print "Exiting program. Goodbye!"
self.log("Exiting program. Goodbye!")
self.p.disconnect()
return True
def help_exit(self):
print "Disconnects from the printer and exits the program."
self.log("Disconnects from the printer and exits the program.")
def do_monitor(self, l):
interval = 5
if not self.p.online:
print "Printer is not online. Please connect first."
self.log("printer is not online. Please connect first.")
return
if not (self.p.printing or self.sdprinting):
print "Printer not printing. Please print something before monitoring."
self.log("Printer not printing. Please print something before monitoring.")
return
print "Monitoring printer, use ^C to interrupt."
self.log("Monitoring printer, use ^C to interrupt.")
if len(l):
try:
interval = float(l)
except:
print "Invalid period given."
print "Updating values every %f seconds."%(interval,)
self.log("Invalid period given.")
self.log("Updating values every %f seconds."%(interval,))
self.monitoring = 1
prev_msg_len = 0
try:
@ -1197,17 +1208,18 @@ class pronsole(cmd.Cmd):
progress = self.percentdone
progress = int(progress*10)/10.0 #limit precision
prev_msg = preface + str(progress) + "%"
sys.stdout.write("\r" + prev_msg.ljust(prev_msg_len))
sys.stdout.flush()
if self.silent == False:
sys.stdout.write("\r" + prev_msg.ljust(prev_msg_len))
sys.stdout.flush()
prev_msg_len = len(prev_msg)
except KeyboardInterrupt:
print "Done monitoring."
if self.silent == False: print "Done monitoring."
self.monitoring = 0
def help_monitor(self):
print "Monitor a machine's temperatures and an SD print's status."
print "monitor - Reports temperature and SD print status (if SD printing) every 5 seconds"
print "monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds"
self.log("Monitor a machine's temperatures and an SD print's status.")
self.log("monitor - Reports temperature and SD print status (if SD printing) every 5 seconds")
self.log("monitor 2 - Reports temperature and SD print status (if SD printing) every 2 seconds")
def expandcommand(self, c):
return c.replace("$python", sys.executable)
@ -1215,31 +1227,31 @@ class pronsole(cmd.Cmd):
def do_skein(self, l):
l = l.split()
if len(l) == 0:
print "No file name given."
self.log("No file name given.")
return
settings = 0
if(l[0]=="set"):
settings = 1
else:
print "Skeining file:"+l[0]
self.log("Skeining file:"+l[0])
if not(os.path.exists(l[0])):
print "File not found!"
self.log("File not found!")
return
try:
import shlex
if(settings):
param = self.expandcommand(self.settings.sliceoptscommand).replace("\\", "\\\\").encode()
print "Entering slicer settings: ", param
self.log("Entering slicer settings: ", param)
subprocess.call(shlex.split(param))
else:
param = self.expandcommand(self.settings.slicecommand).encode()
print "Slicing: ", param
self.log("Slicing: ", param)
params = [i.replace("$s", l[0]).replace("$o", l[0].replace(".stl", "_export.gcode").replace(".STL", "_export.gcode")).encode() for i in shlex.split(param.replace("\\", "\\\\").encode())]
subprocess.call(params)
print "Loading sliced file."
self.log("Loading sliced file.")
self.do_load(l[0].replace(".stl", "_export.gcode"))
except Exception, e:
print "Skeinforge execution failed: ", e
self.log("Skeinforge execution failed: ", e)
def complete_skein(self, text, line, begidx, endidx):
s = line.split()
@ -1252,18 +1264,18 @@ class pronsole(cmd.Cmd):
return glob.glob("*/")+glob.glob("*.stl")
def help_skein(self):
print "Creates a gcode file from an stl model using the slicer (with tab-completion)"
print "skein filename.stl - create gcode file"
print "skein filename.stl view - create gcode file and view using skeiniso"
print "skein set - adjust slicer settings"
self.log("Creates a gcode file from an stl model using the slicer (with tab-completion)")
self.log("skein filename.stl - create gcode file")
self.log("skein filename.stl view - create gcode file and view using skeiniso")
self.log("skein set - adjust slicer settings")
def do_home(self, l):
if not self.p.online:
print "Printer is not online. Unable to move."
self.log("printer is not online. Unable to move.")
return
if self.p.printing:
print "Printer is currently printing. Please pause the print before you issue manual commands."
self.log("printer is currently printing. Please pause the print before you issue manual commands.")
return
if "x" in l.lower():
self.p.send_now("G28 X0")
@ -1278,32 +1290,26 @@ class pronsole(cmd.Cmd):
self.p.send_now("G92 E0")
def help_home(self):
print "Homes the printer"
print "home - homes all axes and zeroes the extruder(Using G28 and G92)"
print "home xy - homes x and y axes (Using G28)"
print "home z - homes z axis only (Using G28)"
print "home e - set extruder position to zero (Using G92)"
print "home xyze - homes all axes and zeroes the extruder (Using G28 and G92)"
self.log("Homes the printer")
self.log("home - homes all axes and zeroes the extruder(Using G28 and G92)")
self.log("home xy - homes x and y axes (Using G28)")
self.log("home z - homes z axis only (Using G28)")
self.log("home e - set extruder position to zero (Using G92)")
self.log("home xyze - homes all axes and zeroes the extruder (Using G28 and G92)")
def parse_cmdline(self, args):
import getopt
opts, args = getopt.getopt(args, "c:e:hw", ["conf = ", "config = ", "help", "web", "web-config = ", "web-auth-config = "])
opts, args = getopt.getopt(args, "c:e:hw", ["conf = ", "config = ", "help"])
for o, a in opts:
#print repr((o, a))
#self.log(repr((o, a)))
if o in ("-c", "--conf", "--config"):
self.load_rc(a)
elif o in ("-w", "--web"):
self.webrequested = True
elif o == "--web-config":
self.web_config = a
elif o == "--web-auth-config":
self.web_auth_config = a
elif o in ("-h", "--help"):
print "Usage: "+sys.argv[0]+' [-c filename [-c filename2 ... ] ] [-e "command" ...]'
print " -c | --conf | --config - override startup .pronsolerc file"
print " may chain config files, settings auto-save will go into last file in the chain"
print ' -e <command> - executes command after configuration/.pronsolerc is loaded'
print " macros/settings from these commands are not autosaved"
self.log("Usage: "+sys.argv[0]+' [-c filename [-c filename2 ... ] ] [-e "command" ...]')
self.log(" -c | --conf | --config - override startup .pronsolerc file")
self.log(" may chain config files, settings auto-save will go into last file in the chain")
self.log(' -e <command> - executes command after configuration/.pronsolerc is loaded')
self.log(" macros/settings from these commands are not autosaved")
sys.exit()
if not self.rc_loaded:
self.load_default_rc()

View File

@ -196,21 +196,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.predisconnect_layer = None
self.hsetpoint = 0.0
self.bsetpoint = 0.0
self.webInterface = None
if self.webrequested:
try :
import cherrypy
from printrun import webinterface
try:
self.webInterface = webinterface.WebInterface(self)
self.webThread = threading.Thread(target = webinterface.StartWebInterfaceThread, args = (self.webInterface, ))
self.webThread.start()
except:
print _("Failed to start web interface")
traceback.print_exc(file = sys.stdout)
self.webInterface = None
except:
print _("CherryPy is not installed. Web Interface Disabled.")
if self.filename is not None:
self.do_load(self.filename)
@ -364,8 +349,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
print _("You cannot set negative temperatures. To turn the hotend off entirely, set its temperature to 0.")
except Exception, x:
print _("You must enter a temperature. (%s)") % (repr(x),)
if self.webInterface:
self.webInterface.AddLog("You must enter a temperature. (%s)" % (repr(x),))
def do_bedtemp(self, l = ""):
try:
@ -382,16 +365,10 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.setbedgui(f)
else:
print _("Printer is not online.")
if self.webInterface:
self.webInterface.AddLog("Printer is not online.")
else:
print _("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")
if self.webInterface:
self.webInterface.AddLog("You cannot set negative temperatures. To turn the bed off entirely, set its temperature to 0.")
except Exception, x:
print _("You must enter a temperature. (%s)") % (repr(x),)
if self.webInterface:
self.webInterface.AddLog("You must enter a temperature.")
def end_macro(self):
pronsole.pronsole.end_macro(self)
@ -411,8 +388,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.delete_macro(macro_name)
return
print _("Cancelled.")
if self.webInterface:
self.webInterface.AddLog("Cancelled.")
return
self.cur_macro_name = macro_name
self.cur_macro_def = definition
@ -452,8 +427,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
projectlayer.setframe(self,self.p).Show()
else:
print _("Printer is not online.")
if self.webInterface:
self.webInterface.AddLog("Printer is not online.")
def popmenu(self):
self.menustrip = wx.MenuBar()
@ -530,8 +503,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
old_def = self.macros[macro]
elif len([c for c in macro.encode("ascii", "replace") if not c.isalnum() and c != "_"]):
print _("Macro name may contain only ASCII alphanumeric symbols and underscores")
if self.webInterface:
self.webInterface.AddLog("Macro name may contain only alphanumeric symbols and underscores")
return
elif hasattr(self.__class__, "do_"+macro):
print _("Name '%s' is being used by built-in command") % macro
@ -713,8 +684,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
def help_button(self):
print _('Defines custom button. Usage: button <num> "title" [/c "colour"] command')
if self.webInterface:
self.webInterface.AddLog('Defines custom button. Usage: button <num> "title" [/c "colour"] command')
def do_button(self, argstr):
def nextarg(rest):
@ -737,8 +706,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
command = argstr.strip()
if num<0 or num>=64:
print _("Custom button number should be between 0 and 63")
if self.webInterface:
self.webInterface.AddLog("Custom button number should be between 0 and 63")
return
while num >= len(self.custombuttons):
self.custombuttons.append(None)
@ -1004,8 +971,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.cur_button = None
except:
print _("event object missing")
if self.webInterface:
self.webInterface.AddLog("event object missing")
self.cur_button = None
raise
@ -1025,9 +990,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
except:
pass
self.Destroy()
if self.webInterface:
from printrun import webinterface
webinterface.KillWebInterfaceThread()
def do_monitor(self, l = ""):
if l.strip()=="":
@ -1040,17 +1002,11 @@ class PronterWindow(MainWindow, pronsole.pronsole):
wx.CallAfter(self.monitorbox.SetValue, self.monitor_interval>0)
except:
print _("Invalid period given.")
if self.webInterface:
self.webInterface.AddLog("Invalid period given.")
self.setmonitor(None)
if self.monitor:
print _("Monitoring printer.")
if self.webInterface:
self.webInterface.AddLog("Monitoring printer.")
else:
print _("Done monitoring.")
if self.webInterface:
self.webInterface.AddLog("Done monitoring.")
def setmonitor(self, e):
self.monitor = self.monitorbox.GetValue()
@ -1065,8 +1021,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
except:
print "attempted to write invalid text to console"
pass
if self.webInterface:
self.webInterface.AppendLog(text)
def setloud(self,e):
self.p.loud=e.IsChecked()
@ -1229,8 +1183,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
import shlex
param = self.expandcommand(self.settings.slicecommand).encode()
print "Slicing: ", param
if self.webInterface:
self.webInterface.AddLog("Slicing: "+param)
pararray = [i.replace("$s", self.filename).replace("$o", self.filename.replace(".stl", "_export.gcode").replace(".STL", "_export.gcode")).encode() for i in shlex.split(param.replace("\\", "\\\\").encode())]
#print pararray
self.skeinp = subprocess.Popen(pararray, stderr = subprocess.STDOUT, stdout = subprocess.PIPE)
@ -1242,8 +1194,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
self.stopsf = 1
except:
print _("Failed to execute slicing software: ")
if self.webInterface:
self.webInterface.AddLog("Failed to execute slicing software: ")
self.stopsf = 1
traceback.print_exc(file = sys.stdout)
@ -1336,8 +1286,6 @@ class PronterWindow(MainWindow, pronsole.pronsole):
Xtot, Ytot, Ztot, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax = pronsole.measurements(self.f)
print pronsole.totalelength(self.f), _("mm of filament used in this print\n")
print _("the print goes from %f mm to %f mm in X\nand is %f mm wide\n") % (Xmin, Xmax, Xtot)
if self.webInterface:
self.webInterface.AddLog(_("the print goes from %f mm to %f mm in X\nand is %f mm wide\n") % (Xmin, Xmax, Xtot))
print _("the print goes from %f mm to %f mm in Y\nand is %f mm wide\n") % (Ymin, Ymax, Ytot)
print _("the print goes from %f mm to %f mm in Z\nand is %f mm high\n") % (Zmin, Zmax, Ztot)
try:

0
server/__init__.py Normal file
View File

93
server/basic_auth.py Normal file
View File

@ -0,0 +1,93 @@
import tornado.ioloop
import tornado.web
import tornado.websocket
import base64
import logging
import logging.config
import tornado.httpserver
import tornado.ioloop
import tornado.web
log = logging.getLogger("root")
def authenticate(realm, authenticator,user_extractor) :
"""
This is a basic authentication interceptor which
protects the desired URIs and requires
authentication as per configuration
"""
def wrapper(self, transforms, *args, **kwargs):
def _request_basic_auth(self):
if self._headers_written:
raise Exception('headers have already been written')
# If this is a websocket accept parameter-based (user/password) auth:
if hasattr(self, 'stream'):
"""
self.stream.write(tornado.escape.utf8(
"HTTP/1.1 401 Unauthorized\r\n"+
"Date: Wed, 10 Apr 2013 02:09:52 GMT\r\n"+
"Content-Length: 0\r\n"+
"Content-Type: text/html; charset=UTF-8\r\n"+
"Www-Authenticate: Basic realm=\"auth_realm\"\r\n"+
"Server: TornadoServer/3.0.1\r\n\r\n"
))
self.stream.close()
"""
# If this is a restful request use the standard tornado methods:
else:
self.set_status(401)
self.set_header('WWW-Authenticate','Basic realm="%s"' % realm)
self._transforms = []
self.finish()
return False
request = self.request
format = ''
clazz = self.__class__
log.debug('intercepting for class : %s', clazz)
try:
auth_hdr = request.headers.get('Authorization')
if auth_hdr == None:
return _request_basic_auth(self)
if not auth_hdr.startswith('Basic '):
return _request_basic_auth(self)
auth_decoded = base64.decodestring(auth_hdr[6:])
username, password = auth_decoded.split(':', 2)
user_info = authenticator(realm, unicode(username), password)
if user_info :
self._user_info = user_info
self._current_user = user_extractor(user_info)
log.debug('authenticated user is : %s',
str(self._user_info))
else:
return _request_basic_auth(self)
except Exception, e:
return _request_basic_auth(self)
return True
return wrapper
def interceptor(func):
"""
This is a class decorator which is helpful in configuring
one or more interceptors which are able to intercept, inspect,
process and approve or reject further processing of the request
"""
def classwrapper(cls):
def wrapper(old):
def inner(self, transforms, *args, **kwargs):
log.debug('Invoking wrapper %s',func)
ret = func(self,transforms,*args,**kwargs)
if ret :
return old(self,transforms,*args,**kwargs)
else :
return ret
return inner
cls._execute = wrapper(cls._execute)
return cls
return classwrapper
print "moo"

BIN
server/static/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,34 @@
html, body
{
margin: 0px;
padding: 0px;
height: 100%;
}
body
{
background: url("/static/img/background.jpg");
background-color: black;
background-repeat: no-repeat;
background-position: 50% 50%;
background-size: auto 100%;
}
.lead-box
{
position: absolute;
text-align: right;
top: 80%;
margin-top: -40px;
padding: 0px;
color: white;
padding-left: 100px;
padding-right: 20px;
background: rgba(0, 0, 0, 0.6);
}
.lead-box a, .lead-box a:hover
{
color: #3198EC;
font-weight: bold;
}

View File

@ -0,0 +1,54 @@
.sensors
{
margin-top: 15px;
}
.sensors>*
{
margin-left: 40px;
font-weight: bold;
font-size: 120%;
}
.sensors .val
{
display: inline-block;
width: 50px;
text-align: right;
}
.sensors .val, .sensors .deg
{
font-weight: normal;
}
.console
{
height: 200px;
overflow-y: scroll;
}
.console pre
{
border: 0px;
margin: 0px;
padding: 0px;
}
#temperature-graph
{
height: 200px;
}
#print-job-panel
{
margin: 0px;
}
.job-pogress
{
margin: 80px 0;
font-size: 40px;
line-height: 40px;
text-align: center;
}

BIN
server/static/img/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

145
server/static/js/inspect.js Normal file
View File

@ -0,0 +1,145 @@
(function() {
var $console;
var windowFocus = true;
$(window).focus(function() {
windowFocus = true;
//if ($console) $console.append("Window refocused, restarting graph.\n");
$(".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")
.stop(true,true)
.show()
.addClass("in")
.removeClass("out");
}.debounce());
var connect = function() {
// Let us open a web socket
var url = "ws://localhost:8888/socket?user=admin&password=admin";
console.log(url);
var ws = new WebSocket(url);
$(function () {
$consoleWrapper = $(".console");
$console = $(".console pre");
$console.html("Connecting...\n")
onConnect(ws)
});
};
var updateSensorsUi = function() {
$(".sensors .val").each(function() {
$(this).html($(this).data("val")||"xx.x");
})
}.throttle(800);
var graph = null;
var graphData = [];
var graphResolution = 40;
var updateGraphData = function(current) {
current.time = Date.now();
if(graphData.length == graphResolution) graphData.shift();
graphData.push(current);
}
var updateGraphUi = function(current) {
if(graph == null)
{
graph = new Morris.Line({
// ID of the element in which to draw the chart.
element: "temperature-graph",
// Chart data records -- each entry in this array corresponds to a point on
// the chart.
data: graphData,
// The name of the data record attribute that contains x-values.
xkey: 'timestamp',
// A list of names of data record attributes that contain y-values.
ykeys: ['extruder', 'bed'],
// Labels for the ykeys -- will be displayed when you hover over the
// chart.
labels: ['extruder &deg;C', 'bed &deg;C'],
hideHover: 'always',
ymax: 'auto 250',
//pointSize: 0,
//parseTime: false,
xLabels: "decade"
});
}
else
{
graph.setData(graphData);
}
}
var updateUi = function(msg) {
if(windowFocus == false) return;
updateSensorsUi();
updateGraphUi();
}
var onConnect = function(ws) {
ws.onopen = function()
{
$console.append("Connected.\n");
// Web Socket is connected, send data using send()
};
var nextGraphPoint = {};
ws.onmessage = function (evt)
{
msg = JSON.parse(evt.data)
if(msg.sensor_changed != undefined)
{
var sensorNames = ["bed", "extruder"];
for (var i = 0; i < sensorNames.length; i++)
{
var name = msg.sensor_changed.name;
var val = parseFloat(msg.sensor_changed.value);
nextGraphPoint[name] = val;
$("."+name+" .val").data("val", val.format(1))
}
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()
{
// websocket is closed.
$console.append("\nConnection closed.");
};
};
if ("WebSocket" in window)
{
connect();
}
else
{
// The browser doesn't support WebSocket
alert("Error: WebSocket NOT supported by your Browser!");
}
})();

View File

@ -0,0 +1,24 @@
<!doctype html>
<html>
<head>
<title>Pronserve</title>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<link href="/static/css/index.css" rel="stylesheet">
</head>
<body>
<div class="lead-box">
<h1>
Your printer just got a whole lot better.
</h1>
<p class="lead">
Pronserve is ready to print. Why not try it out with
<a href="/inspect">Inspector</a> or
<a href="https://github.com/D1plo1d/ctrlpanel">Ctrl Panel</a>?
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!doctype html>
<html>
<head>
<title>Pronserve Inspector</title>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
<link rel="stylesheet" href="http://cdn.oesmith.co.uk/morris-0.4.1.min.css">
<link href="/static/css/inspect.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<h1>
Pronserve Inspector
</h1>
<h2>Console</h2>
<div class="well console">
<pre>
Connecting...
</pre>
</div>
</div>
<div class="span3" id="print-job-panel">
<h2>Job Progress</h2>
<div class="job-pogress"><span class="val">XX.X</span>%</div>
</div>
<div class="span9">
<div class="sensors pull-right">
<div class="extruder pull-right">
Extruder: <span class="val">xx.x</span><span class="deg">&deg;C</span>
</div>
<div class="bed pull-right">
Bed: <span class="val"/>xx.x</span><span class="deg">&deg;C</span>
</div>
</div>
<h2>Temperature</h2>
<div class="clearfix"></div>
<div id="temperature-graph">
</div>
</div>
</div>
</div>
<div class="focus-lost-overlay modal-backdrop fade out hide"></div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sugar/1.3.9/sugar.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="http://cdn.oesmith.co.uk/morris-0.4.1.min.js"></script>
<script src="/static/js/inspect.js"></script>
</body>
</html>

101
testfiles/quick-test.gcode Normal file
View File

@ -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

View File

@ -1,206 +0,0 @@
#title
{
text-align:center;
color:red;
}
#mainmenu
{
margin: 0;
padding: 0 0 20px 10px;
border-bottom: 1px solid #000;
}
#mainmenu ul, #mainmenu li
{
margin: 0;
padding: 0;
display: inline;
list-style-type: none;
}
#mainmenu a:link, #mainmenu a:visited
{
float: left;
line-height: 14px;
font-weight: bold;
margin: 0 10px 4px 10px;
text-decoration: none;
color: #999;
}
#mainmenu a:link#current, #mainmenu a:visited#current, #mainmenu a:hover
{
border-bottom: 4px solid #000;
padding-bottom: 2px;
background: transparent;
color: #000;
}
#mainmenu a:hover { color: #000; }
#content{
padding-top: 25px;
}
#controls{
float:left;
padding:0 0 1em 0;
overflow:hidden;
width:71%; /* right column content width */
left:102%; /* 100% plus left column left padding */
}
#control_xy{
display:inline;
}
#control_z{
display:inline;
position:absolute;
}
#gui{
float:left;
padding:0 0 1em 0;
overflow:hidden;
width:21%; /* left column content width (column width minus left and right padding) */
left:6%; /* (right column left and right padding) plus (left column left padding) */
}
#controls
{
width:21%; /* Width of left column content (column width minus padding on either side) */
left:31%; /* width of (right column) plus (center column left and right padding) plus (left column left padding) */
}
#controls ul
{
list-style: none;
margin: 0px;
padding: 0px;
border: none;
}
#controls ul li
{
margin: 0px;
padding: 0px;
}
#controls ul li a
{
font-size: 80%;
display: block;
border-bottom: 1px dashed #C39C4E;
padding: 5px 0px 2px 4px;
text-decoration: none;
color: #666666;
width:160px;
}
#controls ul li a:hover, #controls ul li a:focus
{
color: #000000;
background-color: #eeeeee;
}
#settings
{
margin: 0px;
padding-top: 50px;
border: none;
}
#settings table
{
font-family: verdana,arial,sans-serif;
font-size:11px;
color:#333333;
border-width: 1px;
border-color: #999999;
border-collapse: collapse;
}
#settings table th {
background-color:#c3dde0;
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
#settings table tr {
background-color:#d4e3e5;
}
#settings table td {
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #a9c6c9;
}
#status{
}
#console{
}
#file{
position:relative;
float:left;
width:100%;
height:20px; /* Height of the footer */
background:#eee;
}
#logframe{
}
#temp{
}
#tempmenu
{
padding: 0 0 10px 10px;
position: relative;
float: left;
width: 100%;
}
#tempmenu ul, #tempmenu li
{
margin: 0;
display: inline;
list-style-type: none;
}
#tempmenu b
{
padding-top: 4px;
float: left;
line-height: 14px;
font-weight: bold;
color: #888;
margin: 0 10px 4px 10px;
text-decoration: none;
color: #999;
}
#tempmenu a:link, #tempmenu a:visited
{
float: left;
border-bottom: 1px solid #000;
line-height: 14px;
font-weight: bold;
margin: 0 10px 4px 10px;
text-decoration: none;
color: #999;
}
#tempmenu a:link#tempmenu, #tempmenu a:visited#current, #tempmenu a:hover
{
border-bottom: 2px solid #000;
padding-bottom: 2px;
background: transparent;
color: #000;
}
#tempmenu a:hover { color: #000; }

View File

@ -1,79 +0,0 @@
function pronterfaceWebInterface_setup(){
pronterfaceWebInterface_attachAsync();
}
function pronterfaceWebInterface_attachAsync(){
var list = [];
if(document.getElementsByClassName){
list = document.getElementsByClassName('command');
}else if(document.getElementsByTagName){
list = document.getElementsByTagName('a');
list.concat( document.getElementsByTagName('area') );
//TODO filter list via checking the className attributes
}else{
console && console.error && console.error('unable to gather list of elements');
return false;
}
for(var i=0; i < list.length; i++){
list[i].addEventListener && list[i].addEventListener( 'click', function(e){return pronterfaceWebInterface_asyncCommand(null, e);}, true );
list[i].attachEvent && list[i].attachEvent( 'onclick', function(e){return pronterfaceWebInterface_asyncCommand(null, e);} );
}
return true;
}
function pronterfaceWebInterface_asyncCommand( urlOrElement, event ){
if( ! urlOrElement && event.target)
urlOrElement = event.target;
var url = null;
if( typeof urlOrElement == 'string' ){
url = urlOrElement;
}else{
url = urlOrElement&&urlOrElement.href;
}
if( typeof url != 'string' ){
console && console.error && console.error('url not a string', urlOrElement, url);
return true;
}
var httpRequest;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE 8 and older
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
if( ! httpRequest ){
alert('no AJAX available?');
// follow link
return true;
}
//onreadystatechange
//onerror
httpRequest.open( 'GET', url, true);
httpRequest.send(null);
// don't follow link
if( event ){
event.stopImmediatePropagation && event.stopImmediatePropagation();
event.defaultPrevented = true;
event.preventDefault && event.preventDefault();
}
return false;
}
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", pronterfaceWebInterface_setup, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", pronterfaceWebInterface_setup);
} else {
document.onload = pronterfaceWebInterface_setup;
}