From: Joseph Coffland Date: Fri, 5 Aug 2016 20:27:58 +0000 (-0700) Subject: Working GCode streaming X-Git-Url: https://git.buildbotics.com/?a=commitdiff_plain;h=dfa4679f295c38a5793c7bcd1e0f7d79844a7fe8;p=bbctrl-firmware Working GCode streaming --- diff --git a/scripts/setup_rpi.sh b/scripts/setup_rpi.sh index dd38975..d2c5be9 100755 --- a/scripts/setup_rpi.sh +++ b/scripts/setup_rpi.sh @@ -61,9 +61,9 @@ EOF update-rc.d resize2fs_once defaults fi -# Install pacakges +# Install packages apt-get install -y avahi-daemon avrdude minicom python3-pip i2c-tools -pip-3.2 install tornado sockjs-tornado pyserial smbus +pip-3.2 install --upgrade tornado sockjs-tornado pyserial smbus # Clean apt-get autoclean @@ -84,6 +84,7 @@ sed -i 's/^\([23456]:.*\/sbin\/getty\)/#\1/' /etc/inittab # Enable I2C sed -i 's/#dtparam=i2c/dtparam=i2c/' /boot/config.txt +echo 'dtparam=i2c_vc=on' >> /boot/config.txt echo i2c-bcm2708 >> /etc/modules echo i2c-dev >> /etc/modules @@ -92,6 +93,6 @@ cp bbctrl.init.d /etc/init.d/bbctrl chmod +x /etc/init.d/bbctrl update-rc.d bbctrl defaults -# TODO setup input and serial device permissions in udev +# TODO setup input and serial device permissions in udev & forward 80 -> 8080 reboot diff --git a/src/jade/index.jade b/src/jade/index.jade index d661265..eb8a877 100644 --- a/src/jade/index.jade +++ b/src/jade/index.jade @@ -73,7 +73,7 @@ html(lang="en") .content(class="{{currentView}}-view") component(:is="currentView + '-view'", :index="index", - :config="config", :template="template", keep-alive) + :config="config", :template="template", :state="state", keep-alive) #templates include ../../build/templates.jade diff --git a/src/jade/templates/config-view.jade b/src/jade/templates/config-view.jade deleted file mode 100644 index a1747df..0000000 --- a/src/jade/templates/config-view.jade +++ /dev/null @@ -1,14 +0,0 @@ -script#config-view-template(type="text/x-template") - #config-page - h1.title {{page}} {{motor}} - - .buttons - button(@click="back") Back - button(@click="next") Next - - component(:is="page + '-view'", :config="config.motors[motor]", - :template="template") - - .buttons - button(@click="back") Back - button(@click="next") Next diff --git a/src/jade/templates/switches-view.jade b/src/jade/templates/switches-view.jade index e1ca46a..a8dc77b 100644 --- a/src/jade/templates/switches-view.jade +++ b/src/jade/templates/switches-view.jade @@ -4,7 +4,5 @@ script#switches-view-template(type="text/x-template") form.pure-form.pure-form-aligned fieldset - .switch(v-for="sw in switches") - h3 Switch {{$index}} - templated-input(v-for="templ in template.switches", :name="$key", - :model.sync="sw[$key]", :template="templ") + templated-input(v-for="templ in template.switches", :name="$key", + :model.sync="switches[$key]", :template="templ") diff --git a/src/js/app.js b/src/js/app.js index 16d4481..5fae6ae 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -15,7 +15,8 @@ module.exports = new Vue({ index: -1, modified: false, template: {motors: {}, axes: {}}, - config: {motors: [{}]} + config: {motors: [{}]}, + state: {} } }, @@ -37,7 +38,10 @@ module.exports = new Vue({ send: function (msg) { - if (this.status == 'connected') this.sock.send(msg) + if (this.status == 'connected') { + console.debug('>', msg); + this.sock.send(msg) + } }, @@ -53,14 +57,15 @@ module.exports = new Vue({ methods: { update: function () { - $.get('/config-template.json').success(function (data, status, xhr) { - this.template = data; - - api.get('load').done(function (data) { - this.config = data; - this.parse_hash(); + $.get('/config-template.json', {cache: false}) + .success(function (data, status, xhr) { + this.template = data; + + api.get('load').done(function (data) { + this.config = data; + this.parse_hash(); + }.bind(this)) }.bind(this)) - }.bind(this)) }, @@ -68,7 +73,11 @@ module.exports = new Vue({ this.sock = new Sock('//' + window.location.host + '/ws'); this.sock.onmessage = function (e) { - this.$broadcast('message', e.data); + var msg = e.data; + + if (typeof msg == 'object') + for (var key in msg) + this.$set('state.' + key, msg[key]); }.bind(this); this.sock.onopen = function (e) { diff --git a/src/js/config-view.js b/src/js/config-view.js deleted file mode 100644 index 0a8ea27..0000000 --- a/src/js/config-view.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict' - - -module.exports = { - template: '#config-view-template', - - - data: function () { - return { - page: 'motor', - motor: 0, - template: {}, - config: {"motors": [{}]} - } - }, - - - components: { - 'motor-view': require('./motor-view'), - 'switch-view': require('./switch-view') - }, - - - ready: function () { - $.get('/config-template.json').success(function (data, status, xhr) { - this.template = data; - - $.get('/default-config.json').success(function (data, status, xhr) { - this.config = data; - }.bind(this)) - }.bind(this)) - }, - - - methods: { - back: function() { - if (this.motor) this.motor--; - }, - - next: function () { - if (this.motor < this.config.motors.length - 1) this.motor++; - } - } -} diff --git a/src/js/control-view.js b/src/js/control-view.js index 1fd182e..9441baf 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -10,7 +10,7 @@ function is_array(x) { module.exports = { template: '#control-view-template', - props: ['config'], + props: ['config', 'state'], data: function () { @@ -20,7 +20,6 @@ module.exports = { last_file: '', files: [], axes: 'xyzabc', - state: {}, gcode: '', speed_override: 1, feed_override: 1 @@ -35,30 +34,9 @@ module.exports = { events: { - jog: function (axis, move) { - console.debug('jog(' + axis + ', ' + move + ')'); - this.send('g91 g0' + axis + move); - }, - - - home: function (axis) { - console.debug('home(' + axis + ')'); - this.send('$home ' + axis); - }, - - - zero: function (axis) { - console.debug('zero(' + axis + ')'); - this.send('$zero ' + axis); - }, - - - message: function (data) { - if (typeof data == 'object') - for (var key in data) - this.$set('state.' + key, data[key]); - } - }, + jog: function (axis, move) {this.send('g91 g0' + axis + move)}, + home: function (axis) {this.send('$home ' + axis)}, + zero: function (axis) {this.send('$zero ' + axis)} }, ready: function () { @@ -80,7 +58,7 @@ module.exports = { estop: function () { - this.$set('state.es', !this.state.es); + this.send('$es=' + (this.state.es ? 0 : 1)); }, diff --git a/src/js/spindle-view.js b/src/js/spindle-view.js index 2423ff1..475b306 100644 --- a/src/js/spindle-view.js +++ b/src/js/spindle-view.js @@ -35,8 +35,7 @@ module.exports = { var template = this.template.spindle; for (var key in template) if (!this.spindle.hasOwnProperty(key)) - this.$set('spindle["' + key + '"]', - template[key].default); + this.$set('spindle["' + key + '"]', template[key].default); }.bind(this)); } } diff --git a/src/js/switches-view.js b/src/js/switches-view.js index 1d4c554..6c0957a 100644 --- a/src/js/switches-view.js +++ b/src/js/switches-view.js @@ -8,7 +8,7 @@ module.exports = { data: function () { return { - 'switches': [] + switches: {} } }, @@ -31,15 +31,12 @@ module.exports = { Vue.nextTick(function () { if (this.config.hasOwnProperty('switches')) this.switches = this.config.switches; - else this.switches = []; - - for (var i = 0; i < this.switches.length; i++) { - var template = this.template.switches; - for (var key in template) - if (!this.switches[i].hasOwnProperty(key)) - this.$set('switches[' + i + ']["' + key + '"]', - template[key].default); - } + else this.switches = {}; + + var template = this.template.switches; + for (var key in template) + if (!this.switches.hasOwnProperty(key)) + this.$set('switches["' + key + '"]', template[key].default); }.bind(this)); } } diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py index 21b3b3a..c5abb3d 100644 --- a/src/py/bbctrl/AVR.py +++ b/src/py/bbctrl/AVR.py @@ -1,39 +1,126 @@ import re import serial +import json import logging +from collections import deque + +import bbctrl log = logging.getLogger('AVR') +# These constants must be kept in sync with i2c.h from the AVR code +I2C_NULL = 0 +I2C_ESTOP = 1 +I2C_PAUSE = 2 +I2C_OPTIONAL_PAUSE = 3 +I2C_RUN = 4 +I2C_FLUSH = 5 +I2C_STEP = 6 +I2C_REPORT = 7 +I2C_HOME = 8 + class AVR(): def __init__(self, ctrl): self.ctrl = ctrl + self.vars = {} self.state = 'idle' - self.line = -1 - self.step = 0 - self.f = None + self.stream = None + self.queue = deque() + self.in_buf = '' + self.command = None + self.flush_id = 1 try: self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud, - rtscts = 1, timeout = 0) + rtscts = 1, timeout = 0, write_timeout = 0) + self.sp.nonblocking() except Exception as e: log.warning('Failed to open serial port: %s', e) return - self.in_buf = '' - self.out_buf = None - self.ctrl.input_queue.put('$echo=0\n\n') - 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) + try: + self.i2c_bus = smbus.SMBus(ctrl.args.avr_port) + self.i2c_addr = ctrl.args.avr_addr + + except FileNotFoundError as e: + self.i2c_bus = None + log.warning('Failed to open device: %s', e) + + self.report() + + + def _state_transition_error(self, state): + raise Exception('Cannot %s in %s state' % (state, self.state)) + + + def _state_transition(self, state, optional = False, step = False): + if state == self.state: return + + if state == 'idle': + if self.stream is not None: self.stream.reset() + + elif state == 'run': + if self.state in ['idle', 'pause'] and self.stream is not None: + self.set_write(True) + + if step: + self._i2c_command(I2C_STEP) + state = 'pause' + + else: self._i2c_command(I2C_RUN) + + else: self._state_transition_error(state) + + elif state == 'pause' + if self.state == 'run': + if optional: self._i2c_command(I2C_OPTIONAL_PAUSE) + else: self._i2c_command(I2C_PAUSE) + + else: self._state_transition_error(state) + + elif state == 'stop': + if self.state in ['run', 'pause']: self._flush() + else: self._state_transition_error(state) + + elif state == 'estop': self._i2c_command(I2C_ESTOP) + + elif state == 'home': + if self.state == 'idle': self._i2c_command(I2C_HOME) + else: self._state_transition_error(state) - def close(self): - self.sp.close() + else: raise Exception('Unrecognized state "%s"' % state) + + self.state = state + + + def _i2c_command(self, cmd, word = None): + if word is not None: + self.i2c_bus.write_word_data(self.i2c_addr, cmd, word) + self.i2c_bus.write_byte(self.i2c_addr, cmd) + + + def _flush(self): + if self.stream is not None: self.stream.reset() + + self._i2c_command(I2C_FLUSH, word = self.flush_id) + self.queue_command('$end_flush %u' % self.flush_id) + + self.flush_id += 1 + if 1 << 16 <= self.flush_id: self.flush_id = 1 + + + def report(self): self._i2c_command(I2C_REPORT) + + + def load_next_command(self, cmd): + log.info(cmd) + self.command = bytes(cmd.strip() + '\n', 'utf-8') def set_write(self, enable): @@ -48,49 +135,36 @@ class AVR(): def serial_write(self): - # Finish writing current line - if self.out_buf is not None: + # Finish writing current command + if self.command is not None: try: - count = self.sp.write(self.out_buf) - log.debug('Wrote %d', count) + count = self.sp.write(self.command) + 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 + self.command = self.command[count:] + if len(self.command): return # There's more + self.command = None + + # Load next command from queue + if len(self.queue): self.load_next_command(self.queue.pop()) + + # Load next GCode command, if running or paused + elif self.state in ['run', 'pause'] and self.stream is not None: + cmd = self.stream.next() + + if cmd is None: self.set_write(False) + else: self.load_next_command(cmd) + + # Else stop writing + else: self.set_write(False) def serial_read(self): try: - data = self.sp.read(self.sp.inWaiting()) + data = self.sp.read(self.sp.in_waiting) self.in_buf += data.decode('utf-8') except Exception as e: @@ -104,41 +178,53 @@ class AVR(): self.in_buf = self.in_buf[i + 1:] if line: - self.ctrl.output_queue.put(line) - log.debug(line) + try: + msg = json.loads(line) + if 'firmware' in msg: self.report() + if 'es' in msg and msg['es']: self.estop() - def queue_handler(self, fd, events): - if self.ctrl.input_queue.empty(): return + self.vars.update(msg) + self.ctrl.web.broadcast(msg) + log.debug(line) - data = self.ctrl.input_queue.get() - self.sp.write(data.encode()) + except Exception as e: + log.error('%s, data: %s', e, line) - def home(self): - if self.state != 'idle': raise Exception('Already running') - # TODO + def queue_command(self, cmd): + self.queue.append(cmd) + self.set_write(True) - def start(self, path): - if self.f is None: - self.f = open('upload' + path, 'r') - self.line = 0 + def load(self, path): + if self.stream is None: + self.stream = bbctrl.GCodeStream(path) - self.set_write(True) - self.state = 'run' + def mdi(self, cmd): + if self.state != 'idle': + raise Exception('Busy, cannot run MDI command') + + self.queue_command(cmd) + + + def jog(self, axes): + # TODO jogging via I2C + + if self.state != 'idle': raise Exception('Busy, cannot jog') - def stop(self): - if self.state == 'idle': return - self.state == 'stop' + axes = ["{:6.5f}".format(x) for x in axes] + self.queue_command('$jog ' + ' '.join(axes)) - def pause(self, optional): - self.state = 'pause' + def set(self, index, code, value): + self.queue_command('${}{}={}'.format(index, code, value)) - def step(self): - self.step += 1 - if self.state == 'idle': self.start() - else: self.state = 'run' + def home(self): self._state_transition('home') + def start(self): self._state_transition('run') + def estop(self): self._state_transition('estop') + def stop(self): self._state_transition('stop') + def pause(self, opt): self._state_transition('pause', optional = opt) + def step(self): self._state_transition('run', step = True) diff --git a/src/py/bbctrl/Config.py b/src/py/bbctrl/Config.py new file mode 100644 index 0000000..50e2e5b --- /dev/null +++ b/src/py/bbctrl/Config.py @@ -0,0 +1,82 @@ +import json +import logging + +import bbctrl + + +log = logging.getLogger('Config') + + +class Config(object): + def __init__(self, ctrl): + self.ctrl = ctrl + + # Load config template + with open(bbctrl.get_resource('http/config-template.json'), 'r', + encoding = 'utf-8') as f: + self.template = json.load(f) + + + def load_path(self, path): + with open(path, 'r') as f: + return json.load(f) + + + def load(self): + try: + return self.load_path('config.json') + + except Exception as e: + log.warning('%s', e) + return self.load_path( + bbctrl.get_resource('http/default-config.json')) + + + def save(self, config): + with open('config.json', 'w') as f: + json.dump(config, f) + + self.update(config) + + log.info('Saved') + + + 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 + + self.ctrl.avr.set(index, spec['code'], value) + + + def encode_category(self, index, config, category): + for key, spec in category.items(): + if key in config: + self.encode_cmd(index, config[key], spec) + + + def encode(self, index, config, tmpl): + for category in tmpl.values(): + self.encode_category(index, config, category) + + + def update(self, config): + # Motors + tmpl = self.template['motors'] + for index in range(len(config['motors'])): + self.encode(index + 1, config['motors'][index], tmpl) + + # Axes + tmpl = self.template['axes'] + for axis in 'xyzabc': + if not axis in config['axes']: continue + self.encode(axis, config['axes'][axis], tmpl) + + # Switches + tmpl = self.template['switches'] + for index in range(len(config['switches'])): + self.encode_category(index + 1, config['switches'][index], tmpl) + + # Spindle + tmpl = self.template['spindle'] + self.encode_category('', config['spindle'], tmpl) diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py new file mode 100644 index 0000000..97b186a --- /dev/null +++ b/src/py/bbctrl/Ctrl.py @@ -0,0 +1,18 @@ +import logging + +import bbctrl + + +log = logging.getLogger('Ctrl') + + +class Ctrl(object): + def __init__(self, args, ioloop): + self.args = args + self.ioloop = ioloop + + self.config = bbctrl.Config(self) + self.web = bbctrl.Web(self) + self.avr = bbctrl.AVR(self) + self.jog = bbctrl.Jog(self) + self.lcd = bbctrl.LCD(self) diff --git a/src/py/bbctrl/GCodeStream.py b/src/py/bbctrl/GCodeStream.py new file mode 100644 index 0000000..5745f4c --- /dev/null +++ b/src/py/bbctrl/GCodeStream.py @@ -0,0 +1,59 @@ +import re +import logging + + +log = logging.getLogger('GCode') + + +class GCodeStream(): + comment1RE = re.compile(r';.*') + comment2RE = re.compile(r'\(([^\)]*)\)') + + + def __init__(self, path): + self.path = path + self.f = None + + self.open() + + + def close(self): + if self.f is not None: + self.f.close() + self.f = None + + + def open(self): + self.close() + + self.line = 0 + self.f = open('upload' + self.path, 'r') + + + def reset(self): self.open() + + + def comment(self, s): + log.debug('Comment: %s', s) + + + def next(self): + line = self.f.readline() + if line is None: return + + # Remove comments + line = self.comment1RE.sub('', line) + + for comment in self.comment2RE.findall(line): + self.comment(comment) + + line = self.comment2RE.sub(' ', line) + + # Remove space + line = line.strip() + + # Append line number + line += ' N%d' % self.line + self.line += 1 + + return line diff --git a/src/py/bbctrl/Jog.py b/src/py/bbctrl/Jog.py index ef81a0c..6ca5db6 100644 --- a/src/py/bbctrl/Jog.py +++ b/src/py/bbctrl/Jog.py @@ -5,6 +5,8 @@ from inevent.Constants import * # Listen for input events class Jog(inevent.JogHandler): def __init__(self, ctrl): + self.ctrl = ctrl + config = { "deadband": 0.1, "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z], @@ -25,10 +27,7 @@ class Jog(inevent.JogHandler): def processed_events(self): if self.v != self.lastV: self.lastV = self.v - - v = ["{:6.5f}".format(x) for x in self.v] - cmd = '$jog ' + ' '.join(v) + '\n' - input_queue.put(cmd) + self.ctrl.avr.jog(self.v) def changed(self): diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index c60ba46..7c55efd 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -28,6 +28,10 @@ class StartHandler(bbctrl.APIHandler): def put_ok(self, path): self.ctrl.avr.start(path) +class EStopHandler(bbctrl.APIHandler): + def put_ok(self): self.ctrl.avr.estop() + + class StopHandler(bbctrl.APIHandler): def put_ok(self): self.ctrl.avr.stop() @@ -55,37 +59,35 @@ class OverrideSpeedHandler(bbctrl.APIHandler): class Connection(sockjs.tornado.SockJSConnection): def heartbeat(self): self.timer = self.ctrl.ioloop.call_later(3, self.heartbeat) - self.send_json({'heartbeat': self.count}) + self.send({'heartbeat': self.count}) self.count += 1 - def send_json(self, data): - self.send(str.encode(json.dumps(data))) - - def on_open(self, info): self.ctrl = self.session.server.ctrl + self.clients = self.ctrl.web.clients self.timer = self.ctrl.ioloop.call_later(3, self.heartbeat) self.count = 0; - self.ctrl.clients.append(self) - self.send_json(self.ctrl.state) + self.clients.append(self) + self.send(self.ctrl.avr.vars) def on_close(self): self.ctrl.ioloop.remove_timeout(self.timer) - self.ctrl.clients.remove(self) + self.clients.remove(self) def on_message(self, data): - self.ctrl.input_queue.put(data + '\n') + self.ctrl.avr.mdi(data) class Web(tornado.web.Application): def __init__(self, ctrl): self.ctrl = ctrl + self.clients = [] handlers = [ (r'/api/load', LoadHandler), @@ -93,6 +95,7 @@ class Web(tornado.web.Application): (r'/api/file(/.+)?', bbctrl.FileHandler), (r'/api/home', HomeHandler), (r'/api/start(/.+)', StartHandler), + (r'/api/estop', EStopHandler), (r'/api/stop', StopHandler), (r'/api/pause', PauseHandler), (r'/api/pause/optional', OptionalPauseHandler), @@ -118,3 +121,8 @@ class Web(tornado.web.Application): sys.exit(1) log.info('Listening on http://%s:%d/', ctrl.args.addr, ctrl.args.port) + + + def broadcast(self, msg): + if self.clients: + self.clients[0].broadcast(self.clients, msg) diff --git a/src/py/bbctrl/__init__.py b/src/py/bbctrl/__init__.py index 5418cd5..c980cd4 100755 --- a/src/py/bbctrl/__init__.py +++ b/src/py/bbctrl/__init__.py @@ -11,6 +11,7 @@ from pkg_resources import Requirement, resource_filename from bbctrl.APIHandler import APIHandler from bbctrl.FileHandler import FileHandler +from bbctrl.GCodeStream import GCodeStream from bbctrl.Config import Config from bbctrl.LCD import LCD from bbctrl.AVR import AVR @@ -44,6 +45,10 @@ def parse_args(): help = 'LCD I2C port') parser.add_argument('--lcd-addr', default = 0x27, type = int, help = 'LCD I2C address') + parser.add_argument('--avr-port', default = 0, type = int, + help = 'AVR I2C port') + parser.add_argument('--avr-addr', default = 0x2b, type = int, + help = 'AVR I2C address') parser.add_argument('-v', '--verbose', action = 'store_true', help = 'Verbose output') parser.add_argument('-l', '--log', metavar = "FILE", diff --git a/src/py/bbctrl/default-config.json b/src/py/bbctrl/default-config.json deleted file mode 100644 index 5175343..0000000 --- a/src/py/bbctrl/default-config.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "motors": [ - { - "motor-map": "x", - "step-angle": 1.8, - "travel-per-rev": 3.175, - "microsteps": 16, - "polarity": "normal", - "power-mode": "always-on", - "power-level": 80, - "stallguard": 70 - }, { - "motor-map": "y" - }, { - "motor-map": "z" - }, { - "motor-map": "a" - } - ], - - "axes": { - "x": { - "mode": "standard", - "max-velocity": 16000, - "max-feedrate": 16000, - "max-jerk": 40, - "min-soft-limit": 0, - "max-soft-limit": 150, - "max-homing-jerk": 80, - "junction-deviation": 0.05, - "search-velocity": 500, - "latch-velocity": 100, - "latch-backoff": 5, - "zero-backoff": 1 - }, - - "y": { - "mode": "standard" - }, - - "z": { - "mode": "standard" - }, - - "a": { - "mode": "radius", - "max-velocity": 1000000, - "max-feedrate": 1000000, - "min-soft-limit": 0, - "max-soft-limit": 0 - }, - - "b": { - "mode": "disabled" - }, - - "c": { - "mode": "disabled" - } - }, - - "switches": [ - {"type": "normally-open"}, - {}, - {}, - {}, - {}, - {}, - {}, - {} - ], - - "spindle": { - } -} diff --git a/src/resources/config-template.json b/src/resources/config-template.json index ecfb57e..e89be3c 100644 --- a/src/resources/config-template.json +++ b/src/resources/config-template.json @@ -54,7 +54,7 @@ "type": "percent", "unit": "%", "default": 70, - "code": "sg" + "code": "th" } } }, @@ -111,19 +111,15 @@ "code": "tm" }, "min-switch": { - "type": "int", - "unit": "id", - "min": 0, - "max": 8, - "default": 0, + "type": "enum", + "values": ["disabled", "normally-open", "normally-closed"], + "default": "disabled", "code": "sn" }, "max-switch": { - "type": "int", - "unit": "id", - "min": 0, - "max": 8, - "default": 0, + "type": "enum", + "values": ["disabled", "normally-open", "normally-closed"], + "default": "disabled", "code": "sx" } }, @@ -216,11 +212,17 @@ }, "switches": { - "type": { + "estop": { + "type": "enum", + "values": ["disabled", "normally-open", "normally-closed"], + "default": "disabled", + "code": "et" + }, + "probe": { "type": "enum", - "values": ["normally-open", "normally-closed"], - "default": "normally-closed", - "code": "sw" + "values": ["disabled", "normally-open", "normally-closed"], + "default": "disabled", + "code": "pt" } }, diff --git a/src/resources/default-config.json b/src/resources/default-config.json new file mode 100644 index 0000000..70c06b9 --- /dev/null +++ b/src/resources/default-config.json @@ -0,0 +1,64 @@ +{ + "motors": [ + { + "motor-map": "x", + "step-angle": 1.8, + "travel-per-rev": 3.175, + "microsteps": 16, + "polarity": "normal", + "power-mode": "always-on", + "power-level": 80, + "stallguard": 70 + }, { + "motor-map": "y" + }, { + "motor-map": "z" + }, { + "motor-map": "a" + } + ], + + "axes": { + "x": { + "mode": "standard", + "max-velocity": 16000, + "max-feedrate": 16000, + "max-jerk": 40, + "min-soft-limit": 0, + "max-soft-limit": 150, + "max-homing-jerk": 80, + "junction-deviation": 0.05, + "search-velocity": 500, + "latch-velocity": 100, + "latch-backoff": 5, + "zero-backoff": 1 + }, + + "y": { + "mode": "standard" + }, + + "z": { + "mode": "standard" + }, + + "a": { + "mode": "radius", + "max-velocity": 1000000, + "max-feedrate": 1000000, + "min-soft-limit": 0, + "max-soft-limit": 0 + }, + + "b": { + "mode": "disabled" + }, + + "c": { + "mode": "disabled" + } + }, + + "switches": {}, + "spindle": {} +} diff --git a/src/stylus/style.styl b/src/stylus/style.styl index 173a3f8..085484b 100644 --- a/src/stylus/style.styl +++ b/src/stylus/style.styl @@ -97,6 +97,9 @@ body h3, .pure-control-group display inline-block +@keyframes blink + 50% + fill #ff9d00 .control-view table @@ -106,151 +109,146 @@ body td, th border 1px solid #ddd + .axes + .axis-x .name + color #f00 -.axes - .axis-x .name - color #f00 + .axis-y .name + color #0f0 - .axis-y .name - color #0f0 + .axis-z .name + color #00f - .axis-z .name - color #00f + .axis-a .name + color #f80 - .axis-a .name - color #f80 + .axis-b .name + color #0ff - .axis-b .name - color #0ff + .axis-c .name + color #f0f - .axis-c .name - color #f0f + td, th + padding 2px - td, th - padding 2px + th + text-align center - th - text-align center + td + text-align right + font-family Courier - td - text-align right - font-family Courier + .axis + .name + text-transform capitalize - .axis - .name - text-transform capitalize + .name, .position + font-size 36pt + line-height 36pt - .name, .position - font-size 36pt - line-height 36pt + .estop + display inline-block + width 190px + transition 250ms -@keyframes blink - 50% - fill #ff9d00 + &.active .ring + animation blink 2s step-start 0s infinite -.estop - display inline-block - width 190px - transition 250ms + svg + cursor pointer - &.active .ring - animation blink 2s step-start 0s infinite + .button:hover + filter brightness(120%) - svg - cursor pointer + .jog + float right - .button:hover - filter brightness(120%) + .jog svg + text + font-family Sans + font-weight bold + stroke transparent + fill #444 -.jog - float right + .button + cursor pointer + stroke #4c4c4c -.jog svg - text - font-family Sans - font-weight bold - stroke transparent - fill #444 + &:hover + stroke #e55 - .button - cursor pointer - stroke #4c4c4c + path + overflow visible - &:hover - stroke #e55 + .house + stroke #444 + fill #444 - path + .ring + cursor pointer overflow visible - .house - stroke #444 - fill #444 + .button + stroke transparent - .ring - cursor pointer - overflow visible + &:hover + stroke #e55 - .button - stroke transparent + text + font-size 10pt + text-anchor middle - &:hover - stroke #e55 + .info + float right + clear right - text - font-size 10pt - text-anchor middle + th, td + padding 3px -.info - float right - clear right + th + text-align right - th, td - padding 3px + .overrides + clear both - th - text-align right + .override + margin 0.5em + display inline-block -.overrides - clear both + .percent + display inline-block + width 3em - .override - margin 0.5em - display inline-block + input + border-radius 0 + margin -0.4em 0.5em - .percent - display inline-block - width 3em + .mdi + clear both + white-space nowrap + margin 0.5em 0 input - border-radius 0 - margin -0.4em 0.5em + width 90% -.mdi - clear both - white-space nowrap - margin 0.5em 0 - - input - width 90% - -.toolbar - clear both - margin 0.5em 0 + .toolbar + clear both + margin 0.5em 0 - .spacer - display inline-block - width 1px - height 1px - margin 0 1em - -.gcode - clear both - border 2px inset #ccc - border-radius 5px - overflow auto - width 100% - max-width 100% - min-width 100% - height 200px - padding 2px - white-space pre + .spacer + display inline-block + width 1px + height 1px + margin 0 1em + + .gcode + clear both + border 2px inset #ccc + border-radius 5px + overflow auto + width 100% + max-width 100% + min-width 100% + height 200px + padding 2px + white-space pre