| Override:
.override
label Feed
- input(title="Feed rate override.", type="range", min="-1", max="1",
+ input(title="Feed rate override.", type="range", min="0", max="2",
step="0.01", v-model="feed_override", @change="override_feed")
span.percent {{feed_override | percent 0}}
.override
label Speed
- input(title="Speed override.", type="range", min="-1", max="1",
+ input(title="Speed override.", type="range", min="0", max="2",
step="0.01", v-model="speed_override", @change="override_speed")
span.percent {{speed_override | percent 0}}
.fa.fa-home
button.pure-button(title="{{running ? 'Pause' : 'Start'}} program.",
- @click="play_pause()", :disabled="!file")
+ @click="start_pause", :disabled="!file")
.fa(:class="running ? 'fa-pause' : 'fa-play'")
button.pure-button(title="Stop program.", @click="stop",
.fa.fa-stop
button.pure-button(title="Pause program at next optional stop (M1).",
- @click="optional_stop", :disabled="!file")
+ @click="optional_pause", :disabled="!file")
.fa.fa-stop-circle-o
button.pure-button(title="Execute one program step.", @click="step",
data: data
}, config);
- return api_cb('POST', url, undefined, config);
+ return api_cb('PUT', url, undefined, config);
},
save: function () {
- api.post('save', this.config).done(function (data) {
+ api.put('save', this.config).done(function (data) {
this.modified = false;
}.bind(this)).fail(function (xhr, status) {
alert('Save failed: ' + status + ': ' + xhr.responseText);
axes: 'xyzabc',
state: {},
gcode: '',
- speed_override: 0,
- feed_override: 0
+ speed_override: 1,
+ feed_override: 1
}
},
},
- run: function (file) {
- api.put('file/' + file).done(this.update);
+ home: function () {api.put('home').done(this.update)},
+
+
+ start_pause: function () {
+ if (this.running) this.pause();
+ else this.start();
+ },
+
+
+ start: function () {
+ if (!this.file) return;
+ api.put('start/' + this.file).done(this.update);
+ },
+
+
+ pause: function () {api.put('pause').done(this.update)},
+ optional_pause: function () {api.put('pause/optional').done(this.update)},
+ stop: function () {api.put('stop').done(this.update)},
+ step: function () {api.put('step').done(this.update)},
+
+
+ override_feed: function () {
+ api.put('override/feed/' + this.feed_override).done(this.update)
+ },
+
+
+ override_speed: function () {
+ api.put('override/speed/' + this.speed_override).done(this.update)
},
var data = {};
data[axis + 'pl'] = x;
this.send(JSON.stringify(data));
- },
-
-
- override_feed: function () {},
- override_speed: function () {},
- step: function () {},
- stop: function () {},
- optional_stop: function () {},
-
-
- home: function () {
- this.send('$calibrate');
}
},
import json
import tornado.web
+import tornado.httpclient
class APIHandler(tornado.web.RequestHandler):
+ def __init__(self, app, request, **kwargs):
+ super(APIHandler, self).__init__(app, request, **kwargs)
+ self.ctrl = app.ctrl
+
+
+ def delete(self, *args, **kwargs):
+ self.delete_ok(*args, **kwargs)
+ self.write_json('ok')
+
+
+ def delete_ok(self): raise tornado.httpclient.HTTPError(405)
+
+
+ def put(self, *args, **kwargs):
+ self.put_ok(*args, **kwargs)
+ self.write_json('ok')
+
+
+ def put_ok(self): raise tornado.httpclient.HTTPError(405)
+
+
def prepare(self):
self.json = {}
+import re
import serial
import logging
class AVR():
- def __init__(self, port, baud, ioloop, app):
- self.app = app
+ def __init__(self, ctrl):
+ self.ctrl = ctrl
+
+ self.state = 'idle'
+ self.line = -1
+ self.step = 0
+ self.f = None
try:
- self.sp = serial.Serial(port, baud, timeout = 1)
+ self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud,
+ rtscts = 1, timeout = 0)
except Exception as e:
log.warning('Failed to open serial port: %s', e)
return
self.in_buf = ''
- self.app.input_queue.put('\n')
+ self.out_buf = None
+ self.ctrl.input_queue.put('$echo=0\n\n')
- ioloop.add_handler(self.sp, self.serial_handler, ioloop.READ)
- ioloop.add_handler(self.app.input_queue._reader.fileno(),
- self.queue_handler, ioloop.READ)
+ ctrl.ioloop.add_handler(self.sp, self.serial_handler, ctrl.ioloop.READ)
+ ctrl.ioloop.add_handler(self.ctrl.input_queue._reader.fileno(),
+ self.queue_handler, ctrl.ioloop.READ)
def close(self):
self.sp.close()
+ def set_write(self, enable):
+ flags = self.ctrl.ioloop.READ
+ if enable: flags |= self.ctrl.ioloop.WRITE
+ self.ctrl.ioloop.update_handler(self.sp, flags)
+
+
def serial_handler(self, fd, events):
+ if self.ctrl.ioloop.READ & events: self.serial_read()
+ if self.ctrl.ioloop.WRITE & events: self.serial_write()
+
+
+ def serial_write(self):
+ # Finish writing current line
+ if self.out_buf is not None:
+ try:
+ count = self.sp.write(self.out_buf)
+ log.debug('Wrote %d', count)
+ except Exception as e:
+ self.set_write(False)
+ raise e
+
+ self.out_buf = self.out_buf[count:]
+ if len(self.out_buf): return
+ self.out_buf = None
+
+ # Close file if stopped
+ if self.state == 'stop' and self.f is not None:
+ self.f.close()
+ self.f = None
+
+ # Read next line if running
+ if self.state == 'run':
+ # Strip white-space & comments and encode to bytearray
+ self.out_buf = self.f.readline().strip()
+ self.out_buf = re.sub(r';.*', '', self.out_buf)
+ if len(self.out_buf):
+ log.info(self.out_buf)
+ self.out_buf = bytes(self.out_buf + '\n', 'utf-8')
+ else: self.out_buf = None
+
+ # Pause if done stepping
+ if self.step:
+ self.step -= 1
+ if not self.step:
+ self.state = 'pause'
+
+ # Stop if not longer running
+ else:
+ self.set_write(False)
+ self.step = 0
+
+
+ def serial_read(self):
try:
data = self.sp.read(self.sp.inWaiting())
self.in_buf += data.decode('utf-8')
except Exception as e:
log.warning('%s: %s', e, data)
+ # Parse incoming serial data into lines
while True:
i = self.in_buf.find('\n')
if i == -1: break
self.in_buf = self.in_buf[i + 1:]
if line:
- self.app.output_queue.put(line)
+ self.ctrl.output_queue.put(line)
log.debug(line)
def queue_handler(self, fd, events):
- if self.app.input_queue.empty(): return
+ if self.ctrl.input_queue.empty(): return
- data = self.app.input_queue.get()
+ data = self.ctrl.input_queue.get()
self.sp.write(data.encode())
+
+
+ def home(self):
+ if self.state != 'idle': raise Exception('Already running')
+ # TODO
+
+
+ def start(self, path):
+ if self.f is None:
+ self.f = open('upload' + path, 'r')
+ self.line = 0
+
+ self.set_write(True)
+ self.state = 'run'
+
+
+ def stop(self):
+ if self.state == 'idle': return
+ self.state == 'stop'
+
+
+ def pause(self, optional):
+ self.state = 'pause'
+
+
+ def step(self):
+ self.step += 1
+ if self.state == 'idle': self.start()
+ else: self.state = 'run'
def prepare(self): pass
- def delete(self, path):
+ def delete_ok(self, path):
path = 'upload' + path
if os.path.exists(path): os.unlink(path)
- self.write_json('ok')
- def put(self, path):
- path = 'upload' + path
- if not os.path.exists(path): return
+ def put_ok(self, path):
+ gcode = self.request.files['gcode'][0]
+
+ if not os.path.exists('upload'): os.mkdir('upload')
- with open(path, 'r') as f:
- for line in f:
- self.application.input_queue.put(line)
+ with open('upload/' + gcode['filename'], 'wb') as f:
+ f.write(gcode['body'])
def get(self, path):
files.append(path)
self.write_json(files)
-
-
- def post(self, path):
- gcode = self.request.files['gcode'][0]
-
- if not os.path.exists('upload'): os.mkdir('upload')
-
- with open('upload/' + gcode['filename'], 'wb') as f:
- f.write(gcode['body'])
-
- self.write_json('ok')
# Listen for input events
class Jog(inevent.JogHandler):
- def __init__(self, args, ioloop):
+ def __init__(self, ctrl):
config = {
"deadband": 0.1,
"axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
self.v = [0.0] * 4
self.lastV = self.v
- self.processor = inevent.InEvent(ioloop, self, types = "js kbd".split())
+ self.processor = inevent.InEvent(ctrl.ioloop, self,
+ types = "js kbd".split())
def processed_events(self):
class LCD:
- def __init__(self, port, addr):
- self.lcd = lcd.LCD(port, addr)
+ def __init__(self, ctrl):
+ self.lcd = lcd.LCD(ctrl.args.lcd_port, ctrl.args.lcd_addr)
self.splash()
atexit.register(self.goodbye)
import os
import sys
import json
-import multiprocessing
import tornado
import sockjs.tornado
import logging
class LoadHandler(bbctrl.APIHandler):
- def send_file(self, path):
- with open(path, 'r') as f:
- self.write_json(json.load(f))
+ def get(self): self.write_json(self.ctrl.config.load())
- def get(self):
- try:
- self.send_file('config.json')
- except Exception as e:
- log.warning('%s', e)
- self.send_file(bbctrl.get_resource('default-config.json'))
+class SaveHandler(bbctrl.APIHandler):
+ def put_ok(self): self.ctrl.config.save(self.json)
+class HomeHandler(bbctrl.APIHandler):
+ def put_ok(self): self.ctrl.avr.home()
+
+
+class StartHandler(bbctrl.APIHandler):
+ def put_ok(self, path): self.ctrl.avr.start(path)
+
+
+class StopHandler(bbctrl.APIHandler):
+ def put_ok(self): self.ctrl.avr.stop()
+
+
+class PauseHandler(bbctrl.APIHandler):
+ def put_ok(self): self.ctrl.avr.pause(False)
-class SaveHandler(bbctrl.APIHandler):
- def post(self):
- with open('config.json', 'w') as f:
- json.dump(self.json, f)
- self.application.update_config(self.json)
- log.info('Saved config')
- self.write_json('ok')
+class OptionalPauseHandler(bbctrl.APIHandler):
+ def put_ok(self): self.ctrl.avr.pause(True)
+class StepHandler(bbctrl.APIHandler):
+ def put_ok(self): self.ctrl.avr.step()
+
+
+class OverrideFeedHandler(bbctrl.APIHandler):
+ def put_ok(self, value): self.ctrl.avr.override_feed(float(value))
+
+
+class OverrideSpeedHandler(bbctrl.APIHandler):
+ def put_ok(self, value): self.ctrl.avr.override_speed(float(value))
+
class Connection(sockjs.tornado.SockJSConnection):
def heartbeat(self):
- self.timer = self.app.ioloop.call_later(3, self.heartbeat)
+ self.timer = self.ctrl.ioloop.call_later(3, self.heartbeat)
self.send_json({'heartbeat': self.count})
self.count += 1
def on_open(self, info):
- self.app = self.session.server.app
- self.timer = self.app.ioloop.call_later(3, self.heartbeat)
+ self.ctrl = self.session.server.ctrl
+
+ self.timer = self.ctrl.ioloop.call_later(3, self.heartbeat)
self.count = 0;
- self.app.clients.append(self)
- self.send_json(self.session.server.app.state)
+
+ self.ctrl.clients.append(self)
+ self.send_json(self.ctrl.state)
def on_close(self):
- self.app.ioloop.remove_timeout(self.timer)
- self.app.clients.remove(self)
+ self.ctrl.ioloop.remove_timeout(self.timer)
+ self.ctrl.clients.remove(self)
def on_message(self, data):
- self.app.input_queue.put(data + '\n')
+ self.ctrl.input_queue.put(data + '\n')
class Web(tornado.web.Application):
- def __init__(self, addr, port, ioloop):
- # Load config template
- with open(bbctrl.get_resource('http/config-template.json'), 'r',
- encoding = 'utf-8') as f:
- self.config_template = json.load(f)
-
- self.ioloop = ioloop
- self.state = {}
- self.clients = []
-
- self.input_queue = multiprocessing.Queue()
- self.output_queue = multiprocessing.Queue()
-
- # Handle output queue events
- ioloop.add_handler(self.output_queue._reader.fileno(),
- self.queue_handler, ioloop.READ)
+ def __init__(self, ctrl):
+ self.ctrl = ctrl
handlers = [
(r'/api/load', LoadHandler),
(r'/api/save', SaveHandler),
- (r'/api/file(/.*)?', bbctrl.FileHandler),
+ (r'/api/file(/.+)?', bbctrl.FileHandler),
+ (r'/api/home', HomeHandler),
+ (r'/api/start(/.+)', StartHandler),
+ (r'/api/stop', StopHandler),
+ (r'/api/pause', PauseHandler),
+ (r'/api/pause/optional', OptionalPauseHandler),
+ (r'/api/step', StepHandler),
+ (r'/api/override/feed/([\d.]+)', OverrideFeedHandler),
+ (r'/api/override/speed/([\d.]+)', OverrideSpeedHandler),
(r'/(.*)', tornado.web.StaticFileHandler,
{'path': bbctrl.get_resource('http/'),
"default_filename": "index.html"}),
]
router = sockjs.tornado.SockJSRouter(Connection, '/ws')
- router.app = self
+ router.ctrl = ctrl
tornado.web.Application.__init__(self, router.urls + handlers)
try:
- self.listen(port, address = addr)
+ self.listen(ctrl.args.port, address = ctrl.args.addr)
except Exception as e:
- log.error('Failed to bind %s:%d: %s', addr, port, e)
+ log.error('Failed to bind %s:%d: %s', ctrl.args.addr,
+ ctrl.args.port, e)
sys.exit(1)
- log.info('Listening on http://%s:%d/', addr, port)
-
-
- def queue_handler(self, fd, events):
- try:
- data = self.output_queue.get()
- msg = json.loads(data)
- self.state.update(msg)
- if self.clients:
- self.clients[0].broadcast(self.clients, msg)
-
- except Exception as e:
- log.error('%s, data: %s', e, data)
-
-
- def encode_cmd(self, index, value, spec):
- if spec['type'] == 'enum': value = spec['values'].index(value)
- elif spec['type'] == 'bool': value = 1 if value else 0
- elif spec['type'] == 'percent': value /= 100.0
-
- cmd = '${}{}={}'.format(index, spec['code'], value)
- self.input_queue.put(cmd + '\n')
- #log.info(cmd)
-
-
- def encode_config_category(self, index, config, category):
- for key, spec in category.items():
- if key in config:
- self.encode_cmd(index, config[key], spec)
-
-
- def encode_config(self, index, config, tmpl):
- for category in tmpl.values():
- self.encode_config_category(index, config, category)
-
-
- def update_config(self, config):
- # Motors
- tmpl = self.config_template['motors']
- for index in range(len(config['motors'])):
- self.encode_config(index + 1, config['motors'][index], tmpl)
-
- # Axes
- tmpl = self.config_template['axes']
- axes = 'xyzabc'
- for axis in axes:
- if not axis in config['axes']: continue
- self.encode_config(axis, config['axes'][axis], tmpl)
-
- # Switches
- tmpl = self.config_template['switches']
- for index in range(len(config['switches'])):
- self.encode_config_category(index + 1,
- config['switches'][index], tmpl)
-
- # Spindle
- tmpl = self.config_template['spindle']
- self.encode_config_category('', config['spindle'], tmpl)
+ log.info('Listening on http://%s:%d/', ctrl.args.addr, ctrl.args.port)
from bbctrl.APIHandler import APIHandler
from bbctrl.FileHandler import FileHandler
+from bbctrl.Config import Config
from bbctrl.LCD import LCD
from bbctrl.AVR import AVR
from bbctrl.Web import Web
from bbctrl.Jog import Jog
+from bbctrl.Ctrl import Ctrl
def get_resource(path):
# Create ioloop
ioloop = tornado.ioloop.IOLoop.current()
- # Start the web server
- app = Web(args.addr, args.port, ioloop)
-
- # Start AVR driver
- avr = AVR(args.serial, args.baud, ioloop, app)
-
- # Start job input controler
- jog = Jog(args, ioloop)
-
- # Start LCD driver
- lcd = LCD(args.lcd_port, args.lcd_addr)
+ # Start controller
+ ctrl = Ctrl(args, ioloop)
try:
ioloop.start()