.sconf_temp/
.sconsign.dblite
/http
+/build
node_modules
*~
\#*
AP := $(NODE_MODS)/autoprefixer/autoprefixer
BROWSERIFY := $(NODE_MODS)/browserify/bin/cmd.js
-HTTP := http
HTML := index
-HTML := $(patsubst %,$(HTTP)/%.html,$(HTML))
-CSS := style
-CSS := $(patsubst %,$(HTTP)/%.css,$(CSS))
+HTML := $(patsubst %,http/%.html,$(HTML))
+CSS := $(wildcard src/stylus/*.styl)
+CSS_ASSETS := build/css/style.css
JS := $(wildcard src/js/*.js)
-JS_ASSETS := $(HTTP)/js/assets.js
-TEMPLS := $(wildcard src/jade/templates/*.jade)
+JS_ASSETS := http/js/assets.js
STATIC := $(shell find src/resources -type f)
STATIC := $(patsubst src/resources/%,http/%,$(STATIC))
+TEMPLS := $(wildcard src/jade/templates/*.jade)
ifndef DEST
DEST=bbctrl/
endif
-WATCH := src/jade src/stylus src/js Makefile
-
-TARGETS := $(HTML) $(CSS) $(JS_ASSETS) $(STATIC)
+WATCH := src/jade src/jade/templates src/stylus src/js src/resources Makefile
-all: node_modules $(TARGETS)
+all: html css js static
-copy: $(TARGETS)
+copy: all
cp -r *.py inevent http/ $(DEST)
-$(HTTP)/admin.html: build/templates.jade
+html: templates $(HTML)
-$(HTTP)/%.html: src/jade/%.jade
- $(JADE) -P $< --out $(shell dirname $@) || \
- (rm -f $@; exit 1)
+css: $(CSS_ASSETS) $(CSS_ASSETS).sha256
+ install -D $< http/css/style-$(shell cat $(CSS_ASSETS).sha256).css
-$(HTTP)/%.css: src/stylus/%.styl
- mkdir -p $(shell dirname $@)
- $(STYLUS) < $< > $@ || (rm -f $@; exit 1)
+js: $(JS_ASSETS) $(JS_ASSETS).sha256
+ install -D $< http/js/assets-$(shell cat $(JS_ASSETS).sha256).js
-$(HTTP)/%: src/resources/%
- install -D $< $@
+static: $(STATIC)
+
+templates: build/templates.jade
build/templates.jade: $(TEMPLS)
mkdir -p build
cat $(TEMPLS) >$@
-$(JS_ASSETS): $(JS)
+build/hashes.jade: $(CSS_ASSETS).sha256 $(JS_ASSETS).sha256
+ echo "- var css_hash = '$(shell cat $(CSS_ASSETS).sha256)'" > $@
+ echo "- var js_hash = '$(shell cat $(JS_ASSETS).sha256)'" >> $@
+
+http/index.html: build/templates.jade build/hashes.jade
+
+$(JS_ASSETS): $(JS) node_modules
@mkdir -p $(shell dirname $@)
$(BROWSERIFY) src/js/main.js -s main -o $@ || \
(rm -f $@; exit 1)
node_modules:
npm install
+%.sha256: %
+ mkdir -p $(shell dirname $@)
+ sha256sum $< | sed 's/^\([a-f0-9]\+\) .*$$/\1/' > $@
+
+http/%: src/resources/%
+ install -D $< $@
+
+http/%.html: src/jade/%.jade $(wildcard src/jade/*.jade) node_modules
+ @mkdir -p $(shell dirname $@)
+ $(JADE) -P $< -o http || (rm -f $@; exit 1)
+
+build/css/%.css: src/stylus/%.styl node_modules
+ mkdir -p $(shell dirname $@)
+ $(STYLUS) < $< > $@ || (rm -f $@; exit 1)
+
watch:
@clear
$(MAKE)
$(MAKE); \
done
-clean:
- rm -rf $(HTTP)
+tidy:
+ rm -f $(shell find "$(DIR)" -name \*~)
+
+clean: tidy
+ rm -rf build html
+
+dist-clean: clean
+ rm -rf node_modules
+
+.PHONY: all install html css static templates clean tidy
## Change this to match your local settings
SERIAL_PORT = '/dev/ttyAMA0'
SERIAL_BAUDRATE = 115200
+HTTP_PORT = 8080
+HTTP_ADDR = '0.0.0.0'
import os
from tornado import web, ioloop
def close(self):
self.sp.close()
-
+
def writeSerial(self, data):
self.sp.write(data.encode())
def run(self):
self.sp.flushInput()
-
+
while True:
# look for incoming tornado request
if not self.input_queue.empty():
data = self.input_queue.get()
-
+
# send it to the serial device
self.writeSerial(data)
data = self.readSerial()
# send it back to tornado
self.output_queue.put(data)
- print(data.decode('utf-8'))
+ try:
+ print(data.decode('utf-8'))
+ except Exception as e:
+ print(e, data)
class Connection(SockJSConnection):
def on_open(self, info):
clients.append(self)
- self.send(state)
+ self.send(str.encode(json.dumps(state)))
def on_close(self):
def __init__(self, config):
super().__init__(config)
- self.lastV = [0.0] * 4
+ self.v = [0.0] * 4
+ self.lastV = self.v
+
+
+ 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)
def changed(self):
if self.speed == 3: scale = 1.0 / 4.0
if self.speed == 4: scale = 1.0
- v = [x * scale for x in self.axes]
+ self.v = [x * scale for x in self.axes]
- if v != self.lastV:
- self.lastV = v
- v = ["{:6.5f}".format(x) for x in v]
- cmd = '$jog ' + ' '.join(v) + '\n'
- input_queue.put(cmd)
+def checkEvents():
+ eventProcessor.process_events(eventHandler)
+ eventHandler.processed_events()
-def checkEvents(): eventProcessor.process_events(eventHandler)
eventProcessor = inevent.InEvent(types = "js kbd".split())
eventHandler = JogHandler(config)
logging.getLogger().setLevel(logging.DEBUG)
# Start the serial worker
- sp = SerialProcess(input_queue, output_queue)
- sp.daemon = True
- sp.start()
+ try:
+ sp = SerialProcess(input_queue, output_queue)
+ sp.daemon = True
+ sp.start()
+ except Exception as e:
+ print(e)
# Adjust the interval according to frames sent by serial port
ioloop.PeriodicCallback(checkQueue, 100).start()
# Start the web server
app = web.Application(router.urls + handlers)
- app.listen(8080)
+ app.listen(HTTP_PORT, address = HTTP_ADDR)
+ print('Listening on http://{}:{}/'.format(HTTP_ADDR, HTTP_PORT))
ioloop.IOLoop.instance().start()
if self.buttons[k] != 0: k_list.append(k)
return k_list
-
def __init__(self, config):
self.config = config
self.axes = [0.0, 0.0, 0.0, 0.0]
- self.speed = 1
+ self.speed = 3
self.activate = 0
self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]]
if abs(self.axes[axis]) < self.config['deadband']:
self.axes[axis] = 0
- if not (1 << axis) & self.activate: self.axes[axis] = 0
+ if not (1 << axis) & self.activate and self.activate:
+ self.axes[axis] = 0
if old_axes != self.axes: changed = True
+include ../../build/hashes.jade
+
+
doctype html
html(lang="en")
head
- title Buildbotics Controller - Web interface
meta(charset="utf-8")
+ meta(name="viewport", content="width=device-width, initial-scale=1.0")
- script(src="//code.jquery.com/jquery-1.11.3.min.js")
- script(src="//cdn.jsdelivr.net/vue/1.0.13/vue.min.js")
- script(src="js/sockjs.min.js")
- script(src="js/gauge.min.js")
- script(src="js/fd-slider.min.js")
- script(src="js/assets.js")
+ title Buildbotics Controller - Web interface
+
+ link(rel="stylesheet",
+ href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css")
+ //if lte IE 8
+ link(rel="stylesheet", href="css/side-menu-old-ie.css")
+ // [if gt IE 8] <!
+ link(rel="stylesheet", href="css/side-menu.css")
+ // <![endif]
- var faURL = "//maxcdn.bootstrapcdn.com/font-awesome"
link(rel="stylesheet" href=faURL + "/4.5.0/css/font-awesome.min.css")
+ link(href='//fonts.googleapis.com/css?family=Audiowide' rel='stylesheet'
+ type='text/css')
+ link(rel='stylesheet' href='//yui.yahooapis.com/pure/0.6.0/pure-min.css')
+
+ link(rel='stylesheet' href='/css/style-' + css_hash + '.css')
- link(rel="stylesheet" href="/style.css")
body
- table
- tr
- td
- td: button(@click="jog('y', 1)") Y+
- td
- td: button(@click="jog('z', 1)") Z+
- td: button(@click="jog('a', 1)") A+
- tr
- td: button(@click="jog('x', -1)") -X
- td
- td: button(@click="jog('x', 1)") X+
- tr
- td
- td: button(@click="jog('y', -1)") -Y
- td
- td: button(@click="jog('z', -1)") Z-
- td: button(@click="jog('a', -1)") A-
-
- h2 Velocity {{state.vel}}
- h2 Step {{step}}
-
- table.axes
- tr
- th Axis
- th Position
- th Step
- th Flags
- th Current
- th StallGuard
-
- each axis in ['x', 'y', 'z']
- tr.axis
- th.name #{axis}
- td {{state.pos#{axis}}}
- td {{state.dstep#{axis}}}
- td {{state.dflags#{axis}}}
- td
- | {{state.dcur#{axis} | percent}}
- gauge(:value="state.dcur#{axis} * 32", :min="1", :max="32")
- input(type="range" min=1 max=32 v-model="current#{axis}")
- td
- gauge(:value="state.sguard#{axis}", :min="0", :max="511")
+ #layout
+ a#menuLink.menu-link(href="#menu"): span
+
+ #menu
+ button.save(:disabled="!modified", class="pure-button button-success"
+ @click="save") Save
+
+ .pure-menu
+ ul.pure-menu-list
+ li.pure-menu-heading
+ a.pure-menu-link(href="#status") Status
+
+ li.pure-menu-heading
+ a.pure-menu-link(href="#motor:0") Motors
+
+ li.pure-menu-item(v-for="motor in config.motors")
+ a.pure-menu-link(:href="'#motor:' + $index") Motor {{$index}}
+ li.pure-menu-heading
+ a.pure-menu-link(href="#axis:x") Axes
+
+ li.pure-menu-item(v-for="axis in config.axes")
+ a.pure-menu-link(:href="'#axis:' + $key")
+ | {{$key | capitalize}} Axis
+
+ li.pure-menu-heading
+ a.pure-menu-link(href="#spindle") Spindle
+
+ li.pure-menu-heading
+ a.pure-menu-link(href="#switches") Switches
+
+ li.pure-menu-heading
+ a.pure-menu-link(href="#gcode") Gcode
+
+ li.pure-menu-heading
+ a.pure-menu-link(href="#admin") Admin
+
+ #main
+ .header
+ img(src="/images/buildbotics_logo.png")
+ .logo
+ span.left Build
+ span.right botics
+ h2 Machine Controller
+
+ .content
+ component(:is="currentView + '-view'", :index="index",
+ :config="config", :template="template")
#templates
+ include ../../build/templates.jade
+
+ script(src="//code.jquery.com/jquery-1.11.3.min.js")
+ script(src="//cdn.jsdelivr.net/vue/1.0.17/vue.js")
+ script(src="js/sockjs.min.js")
+ script(src="js/gauge.min.js")
+ script(src="js/fd-slider.min.js")
+
+ script(src='/js/assets-' + js_hash + '.js')
+ script(src="js/ui.js")
--- /dev/null
+script#admin-view-template(type="text/x-template")
+ button.pure-button.pure-button-primary(@click="backup") Backup configuration
+ button.pure-button.pure-button-primary(@click="restore") Restore configuration
+ button.pure-button.pure-button-primary(@click="upgrade") Upgrade firmware
--- /dev/null
+script#axis-view-template(type="text/x-template")
+ #axis
+ h1 {{index | capitalize}} Axis Configuration
+
+ form.pure-form.pure-form-aligned
+ fieldset(v-for="category in template.axes", :class="$key")
+ h2 {{$key}}
+
+ templated-input(v-for="templ in category", :name="$key",
+ :model.sync="axis[$key]", :template="templ")
--- /dev/null
+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
--- /dev/null
+script#gcode-view-template(type="text/x-template")
+ #gcode
+ h1 GCode Configuration
+
+ form.pure-form.pure-form-aligned
+ fieldset
+ templated-input(v-for="templ in template.gcode", :name="$key",
+ :model.sync="gcode[$key]", :template="templ")
--- /dev/null
+script#motor-view-template(type="text/x-template")
+ #motor
+ h1 Motor {{index}} Configuration
+
+ form.pure-form.pure-form-aligned
+ fieldset(v-for="category in template.motors", :class="$key")
+ h2 {{$key}}
+
+ templated-input(v-for="templ in category", :name="$key",
+ :model.sync="motor[$key]", :template="templ")
--- /dev/null
+script#spindle-view-template(type="text/x-template")
+ #spindle
+ h1 Spindle Configuration
+
+ form.pure-form.pure-form-aligned
+ fieldset
+ templated-input(v-for="templ in template.spindle", :name="$key",
+ :model.sync="spindle[$key]", :template="templ")
--- /dev/null
+script#status-view-template(type="text/x-template")
+ table
+ tr
+ td
+ td: button(@click="jog('y', 1)") Y+
+ td
+ td: button(@click="jog('z', 1)") Z+
+ td: button(@click="jog('a', 1)") A+
+ tr
+ td: button(@click="jog('x', -1)") -X
+ td
+ td: button(@click="jog('x', 1)") X+
+ tr
+ td
+ td: button(@click="jog('y', -1)") -Y
+ td
+ td: button(@click="jog('z', -1)") Z-
+ td: button(@click="jog('a', -1)") A-
+
+ h2 Velocity {{state.vel}}
+ h2 Step {{step}}
+
+ table.axes
+ tr
+ th Axis
+ th Position
+ th Step
+ th Flags
+ th Current
+ th StallGuard
+
+ each axis in ['x', 'y', 'z']
+ tr.axis
+ th.name #{axis}
+ td {{state.pos#{axis}}}
+ td {{state.dstep#{axis}}}
+ td {{state.dflags#{axis}}}
+ td
+ | {{state.dcur#{axis} | percent}}
+ gauge(:value="state.dcur#{axis} * 32", :min="1", :max="32")
+ input(type="range" min=1 max=32 v-model="current#{axis}")
+ td
+ gauge(:value="state.sguard#{axis}", :min="0", :max="511")
--- /dev/null
+script#switches-view-template(type="text/x-template")
+ #switches
+ h1 Switch Configuration
+
+ form.pure-form.pure-form-aligned
+ fieldset
+ .switch(v-for="switch in switches")
+ h3 Switch {{$index}}
+ templated-input(v-for="templ in template.switches", :name="$key",
+ :model.sync="switch[$key]", :template="templ")
--- /dev/null
+script#templated-input-template(type="text/x-template")
+ .pure-control-group(:class="name")
+ label(:for="name") {{name}}
+
+ select(v-if="template.type == 'enum'", v-model="model",
+ :id="name", @change="change")
+ option(v-for="opt in template.values", :value="opt") {{opt}}
+
+ input(v-if="template.type == 'bool'", type="checkbox",
+ v-model="model", :id="name", @change="change")
+
+ input(v-if="template.type == 'float'", v-model="model", number,
+ :min="template.min", :max="template.max", step="any", type="number",
+ :id="name", @change="change")
+
+ input(v-if="template.type == 'int'", v-model="model", number,
+ :min="template.min", :max="template.max", type="number",
+ :id="name", @change="change")
+
+ input(v-if="template.type == 'string'", v-model="model",
+ type="text", :id="name", @change="change")
+
+ textarea(v-if="template.type == 'text'", v-model="model",
+ :id="name", @change="change")
+
+ label.units {{template.unit}}
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#admin-view-template',
+ props: ['config'],
+
+
+ ready: function () {
+ },
+
+
+ methods: {
+ backup: function () {
+ },
+
+
+ restore: function () {
+ }
+ }
+}
'use strict'
-function is_array(x) {
- return Object.prototype.toString.call(x) === '[object Array]';
-}
-
-
module.exports = new Vue({
el: 'body',
data: function () {
return {
- axes: 'xyza',
- state: {
- dcurx: 1, dcury: 1, dcurz: 1, dcura: 1
- //sguardx: 1, sguardy: 1, sguardz: 1, sguarda: 1
- },
- step: 10
+ currentView: 'loading',
+ index: -1,
+ modified: false,
+ template: {"motors": {}, "axes": {}},
+ config: {"motors": [{}]}
}
},
components: {
- gauge: require('./gauge')
+ 'loading-view': {template: '<h1>Loading...</h1>'},
+ 'status-view': require('./status-view'),
+ 'axis-view': require('./axis-view'),
+ 'motor-view': require('./motor-view'),
+ 'spindle-view': require('./spindle-view'),
+ 'switches-view': require('./switches-view'),
+ 'gcode-view': require('./gcode-view'),
+ 'admin-view': require('./admin-view')
},
- watch: {
- currentx: function (value) {this.current('x', value);},
- currenty: function (value) {this.current('y', value);},
- currentz: function (value) {this.current('z', value);},
- currenta: function (value) {this.current('a', value);}
+ events: {
+ 'config-changed': function () {this.modified = true;}
},
ready: function () {
- this.sock = new SockJS('//' + window.location.host + '/ws');
-
- this.sock.onmessage = function (e) {
- var data = e.data;
- console.debug(data);
+ $.get('/config-template.json').success(function (data, status, xhr) {
+ this.template = data;
- for (var key in data) {
- this.$set('state.' + key, data[key]);
+ $.get('/default-config.json').success(function (data, status, xhr) {
+ this.config = data;
- for (var axis of ['x', 'y', 'z', 'a'])
- if (key == 'dcur' + axis && typeof this.$get('current' + axis) == 'undefined')
- this.$set('current' + axis, (32 * data[key]).toFixed());
- }
- }.bind(this);
+ this.parse_hash();
+ $(window).on('hashchange', this.parse_hash);
+ }.bind(this))
+ }.bind(this))
},
methods: {
- send: function (data) {
- this.sock.send(JSON.stringify(data));
- },
+ parse_hash: function () {
+ var hash = location.hash.substr(1);
+ var parts = hash.split(':');
+ if (parts.length == 2) this.index = parts[1];
- jog: function (axis, dir) {
- var pos = this.state['pos' + axis] + dir * this.step;
- this.sock.send('g0' + axis + pos);
+ this.currentView = parts[0];
},
- current: function (axis, value) {
- var x = value / 32.0;
- if (this.state['dcur' + axis] == x) return;
-
- var data = {};
- data['dcur' + axis] = x;
- this.send(data);
- }
- },
-
-
- filters: {
- percent: function (value, precision) {
- if (typeof precision == 'undefined') precision = 2;
- return (value * 100.0).toFixed(precision) + '%';
+ save: function () {
+ this.modified = false;
}
}
})
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#axis-view-template',
+ props: ['index', 'config', 'template'],
+
+
+ data: function () {
+ return {axis: {}}
+ },
+
+
+ watch: {
+ index: function() {this.update();}
+ },
+
+
+ events: {
+ 'input-changed': function() {
+ this.$dispatch('config-changed');
+ return false;
+ }
+ },
+
+
+ ready: function () {
+ this.update();
+ },
+
+
+ methods: {
+ update: function () {
+ Vue.nextTick(function () {
+ if (this.config.hasOwnProperty('axes'))
+ this.axis = this.config.axes[this.index];
+ else this.axes = {};
+
+ var template = this.template.axes;
+ for (var category in template)
+ for (var key in template[category])
+ if (!this.axis.hasOwnProperty(key))
+ this.$set('axis["' + key + '"]',
+ template[category][key].default);
+ }.bind(this));
+ }
+ }
+}
--- /dev/null
+'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++;
+ }
+ }
+}
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#gcode-view-template',
+ props: ['config', 'template'],
+
+
+ data: function () {
+ return {
+ gcode: {}
+ }
+ },
+
+
+ events: {
+ 'input-changed': function() {
+ this.$dispatch('config-changed');
+ return false;
+ }
+ },
+
+
+ ready: function () {
+ this.update();
+ },
+
+
+ methods: {
+ update: function () {
+ Vue.nextTick(function () {
+ if (this.config.hasOwnProperty('gcode'))
+ this.gcode = this.config.gcode;
+
+ var template = this.template.gcode;
+ for (var key in template)
+ if (!this.gcode.hasOwnProperty(key))
+ this.$set('gcode["' + key + '"]',
+ template[key].default);
+ }.bind(this));
+ }
+ }
+}
Vue.config.debug = true;
Vue.util.warn = function (msg) {console.debug('[Vue warn]: ' + msg)}
+ // Register global components
+ Vue.component('templated-input', require('./templated-input'));
+
// Vue app
require('./app');
});
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#motor-view-template',
+ props: ['index', 'config', 'template'],
+
+
+ data: function () {
+ return {
+ motor: {}
+ }
+ },
+
+
+ watch: {
+ index: function() {this.update();}
+ },
+
+
+ events: {
+ 'input-changed': function() {
+ this.$dispatch('config-changed');
+ return false;
+ }
+ },
+
+
+ ready: function () {
+ this.update();
+ },
+
+
+ methods: {
+ update: function () {
+ Vue.nextTick(function () {
+ if (this.config.hasOwnProperty('motors'))
+ this.motor = this.config.motors[this.index];
+ else this.motor = {};
+
+ var template = this.template.motors;
+ for (var category in template)
+ for (var key in template[category])
+ if (!this.motor.hasOwnProperty(key))
+ this.$set('motor["' + key + '"]',
+ template[category][key].default);
+ }.bind(this));
+ }
+ }
+}
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#spindle-view-template',
+ props: ['config', 'template'],
+
+
+ data: function () {
+ return {
+ spindle: {}
+ }
+ },
+
+
+ events: {
+ 'input-changed': function() {
+ this.$dispatch('config-changed');
+ return false;
+ }
+ },
+
+
+ ready: function () {
+ this.update();
+ },
+
+
+ methods: {
+ update: function () {
+ Vue.nextTick(function () {
+ if (this.config.hasOwnProperty('spindle'))
+ this.spindle = this.config.spindle;
+
+ var template = this.template.spindle;
+ for (var key in template)
+ if (!this.spindle.hasOwnProperty(key))
+ this.$set('spindle["' + key + '"]',
+ template[key].default);
+ }.bind(this));
+ }
+ }
+}
--- /dev/null
+'use strict'
+
+
+function is_array(x) {
+ return Object.prototype.toString.call(x) === '[object Array]';
+}
+
+
+module.exports = {
+ template: '#status-view-template',
+
+
+ data: function () {
+ return {
+ axes: 'xyza',
+ state: {
+ dcurx: 1, dcury: 1, dcurz: 1, dcura: 1
+ //sguardx: 1, sguardy: 1, sguardz: 1, sguarda: 1
+ },
+ step: 10
+ }
+ },
+
+
+ components: {
+ gauge: require('./gauge')
+ },
+
+
+ watch: {
+ currentx: function (value) {this.current('x', value);},
+ currenty: function (value) {this.current('y', value);},
+ currentz: function (value) {this.current('z', value);},
+ currenta: function (value) {this.current('a', value);}
+ },
+
+
+ ready: function () {
+ this.sock = new SockJS('//' + window.location.host + '/ws');
+
+ this.sock.onmessage = function (e) {
+ var data = e.data;
+ console.debug(data);
+
+ for (var key in data) {
+ this.$set('state.' + key, data[key]);
+
+ for (var axis of ['x', 'y', 'z', 'a'])
+ if (key == 'dcur' + axis &&
+ typeof this.$get('current' + axis) == 'undefined')
+ this.$set('current' + axis, (32 * data[key]).toFixed());
+ }
+ }.bind(this);
+ },
+
+
+ methods: {
+ send: function (data) {
+ this.sock.send(JSON.stringify(data));
+ },
+
+
+ jog: function (axis, dir) {
+ var pos = this.state['pos' + axis] + dir * this.step;
+ this.sock.send('g0' + axis + pos);
+ },
+
+
+ current: function (axis, value) {
+ var x = value / 32.0;
+ if (this.state['dcur' + axis] == x) return;
+
+ var data = {};
+ data['dcur' + axis] = x;
+ this.send(data);
+ }
+ },
+
+
+ filters: {
+ percent: function (value, precision) {
+ if (typeof precision == 'undefined') precision = 2;
+ return (value * 100.0).toFixed(precision) + '%';
+ }
+ }
+}
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ template: '#switches-view-template',
+ props: ['config', 'template'],
+
+
+ data: function () {
+ return {
+ 'switches': []
+ }
+ },
+
+
+ events: {
+ 'input-changed': function() {
+ this.$dispatch('config-changed');
+ return false;
+ }
+ },
+
+
+ ready: function () {
+ this.update();
+ },
+
+
+ methods: {
+ update: function () {
+ 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);
+ }
+ }.bind(this));
+ }
+ }
+}
--- /dev/null
+'use strict'
+
+
+module.exports = {
+ replace: true,
+ template: '#templated-input-template',
+ props: ['name', 'model', 'template'],
+
+
+ methods: {
+ change: function () {
+ this.$dispatch('input-changed');
+ }
+ }
+}
--- /dev/null
+{
+ "axes": {
+ "motion": {
+ "mode": {
+ "type": "enum",
+ "values": ["standard", "radius", "disabled"],
+ "default": "disabled"
+ },
+ "max-velocity": {
+ "type": "float",
+ "min": 0,
+ "unit": "mm/min",
+ "default": 16000
+ },
+ "max-feedrate": {
+ "type": "float",
+ "min": 0, "unit":
+ "mm/min",
+ "default": 16000
+ },
+ "max-jerk": {
+ "type": "float",
+ "min": 0,
+ "unit": "km/min^3",
+ "default": 20
+ },
+ "junction-deviation": {
+ "type": "float",
+ "min": 0,
+ "unit": "mm",
+ "default": 0.05
+ }
+ },
+
+ "limits": {
+ "min-soft-limit": {
+ "type": "float",
+ "unit": "mm",
+ "default": 0
+ },
+ "max-soft-limit": {
+ "type": "float",
+ "unit": "mm",
+ "default": 150
+ },
+ "min-switch": {
+ "type": "int",
+ "unit": "id",
+ "min": 0,
+ "max": 8,
+ "default": 0
+ },
+ "max-switch": {
+ "type": "int",
+ "unit": "id",
+ "min": 0,
+ "max": 8,
+ "default": 0
+ }
+ },
+
+ "homing": {
+ "max-homing-jerk": {
+ "type": "float",
+ "min": 0,
+ "unit": "km/min^3",
+ "default": 40
+ },
+ "search-velocity": {
+ "type": "float",
+ "min": 0,
+ "unit": "mm/min",
+ "default": 500
+ },
+ "latch-velocity": {
+ "type": "float",
+ "min": 0,
+ "unit": "mm/min",
+ "default": 100
+ },
+ "latch-backoff": {
+ "type": "float",
+ "min": 0,
+ "unit": "mm",
+ "default": 5
+ },
+ "zero-backoff": {
+ "type": "float",
+ "min": 0,
+ "unit": "mm",
+ "default": 1
+ }
+ }
+ },
+
+ "motors": {
+ "general": {
+ "axis-mapping": {
+ "type": "enum",
+ "values": ["x", "y", "z", "a", "b", "c"],
+ "default": "x"
+ },
+ "step-angle": {
+ "type": "float",
+ "unit": "degrees",
+ "default": 1.8
+ },
+ "travel-per-rev": {
+ "type": "float",
+ "unit": "mm",
+ "default": 3.175
+ },
+ "microsteps": {
+ "type": "int",
+ "values": [1, 2, 4, 8, 16, 32, 64, 128, 256],
+ "unit": "per full step",
+ "default": 16
+ },
+ "polarity": {
+ "type": "enum",
+ "values": ["normal", "reversed"],
+ "default": "normal"
+ }
+ },
+
+ "power": {
+ "power-mode": {
+ "type": "enum",
+ "values": ["always-on", "in-cycle", "when-moving"],
+ "default": "in-cycle"
+ },
+ "power-level": {
+ "type": "float",
+ "min": 0,
+ "max": 100,
+ "unit": "%",
+ "default": 80
+ },
+ "stall-guard": {
+ "type": "float",
+ "min": 0,
+ "max": 100,
+ "unit": "%",
+ "default": 70
+ },
+ "cool-step": {
+ "type": "bool",
+ "default": true
+ }
+ }
+ },
+
+ "spindle": {
+ "max-speed": {
+ "type": "float",
+ "unit": "RPM",
+ "min": 0,
+ "default": 10000
+ },
+ "type": {
+ "type": "enum",
+ "values": ["RS485", "PWM"],
+ "default": "RS485"
+ },
+ "min-pulse-width": {
+ "type": "float",
+ "unit": "ms",
+ "default": 20
+ },
+ "max-pulse-width": {
+ "type": "float",
+ "unit": "ms",
+ "default": 100
+ },
+ "polarity": {
+ "type": "enum",
+ "values": ["normal", "reversed"],
+ "default": "normal"
+ },
+ "ramp-up-velocity": {
+ "type": "float",
+ "unit": "rev/min^2",
+ "min": 0,
+ "default": 48000
+ },
+ "ramp-down-velocity": {
+ "type": "float",
+ "unit": "rev/min^2",
+ "min": 0,
+ "default": 48000
+ }
+ },
+
+ "switches": {
+ "type": {
+ "type": "enum",
+ "values": ["normally-open", "normally-closed"],
+ "default": "normally-closed"
+ }
+ },
+
+ "gcode": {
+ "preamble": {"type": "text"},
+ "tool-change": {"type": "text"},
+ "epilogue": {"type": "text"}
+ }
+}
--- /dev/null
+body {
+ color: #777;
+}
+
+.pure-img-responsive {
+ max-width: 100%;
+ height: auto;
+}
+
+/*
+Add transition to containers so they can push in and out.
+*/
+
+#layout,
+#menu,
+.menu-link {
+ -webkit-transition: all 0.2s ease-out;
+ -moz-transition: all 0.2s ease-out;
+ -ms-transition: all 0.2s ease-out;
+ -o-transition: all 0.2s ease-out;
+ transition: all 0.2s ease-out;
+}
+
+/*
+This is the parent `<div>` that contains the menu and the content area.
+*/
+
+#layout {
+ position: relative;
+ padding-left: 0;
+}
+
+#layout.active #menu {
+ left: 150px;
+ width: 150px;
+}
+
+#layout.active .menu-link {
+ left: 150px;
+}
+
+/*
+The content `<div>` is where all your content goes.
+*/
+
+.content {
+ margin: 0 auto;
+ padding: 0 2em;
+ max-width: 800px;
+ margin-bottom: 50px;
+ line-height: 1.6em;
+}
+
+.header {
+ margin: 0;
+ color: #333;
+ text-align: center;
+ padding: 2.5em 2em 0;
+ border-bottom: 1px solid #eee;
+}
+
+.header h1 {
+ margin: 0.2em 0;
+ font-size: 3em;
+ font-weight: 300;
+}
+
+.header h2 {
+ font-weight: 300;
+ color: #ccc;
+ padding: 0;
+ margin-top: 0;
+}
+
+.content-subhead {
+ margin: 50px 0 20px 0;
+ font-weight: 300;
+ color: #888;
+}
+
+/*
+The `#menu` `<div>` is the parent `<div>` that contains the `.pure-menu` that
+appears on the left side of the page.
+*/
+
+#menu {
+ margin-left: -150px;
+ /* "#menu" width */
+ width: 150px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ z-index: 1000;
+ /* so the menu or its navicon stays above all content */
+ background: #191818;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+/*
+ All anchors inside the menu should be styled like this.
+ */
+
+#menu a {
+ color: #999;
+ border: none;
+ padding: 0.6em 0 0.6em 0.6em;
+}
+
+/*
+ Remove all background/borders, since we are applying them to #menu.
+ */
+
+#menu .pure-menu,
+#menu .pure-menu ul {
+ border: none;
+ background: transparent;
+}
+
+/*
+ Add that light border to separate items into groups.
+ */
+
+#menu .pure-menu ul,
+#menu .pure-menu .menu-item-divided {
+ border-top: 1px solid #333;
+}
+
+/*
+ Change color of the anchor links on hover/focus.
+ */
+
+#menu .pure-menu li a:hover,
+#menu .pure-menu li a:focus {
+ background: #333;
+}
+
+/*
+ This styles the selected menu item `<li>`.
+ */
+
+#menu .pure-menu-selected,
+#menu .pure-menu-heading {
+ background: #1f8dd6;
+}
+
+/*
+ This styles a link within a selected menu item `<li>`.
+ */
+
+#menu .pure-menu-selected a {
+ color: #fff;
+}
+
+/*
+ This styles the menu heading.
+ */
+
+#menu .pure-menu-heading {
+ font-size: 110%;
+ color: #fff;
+ margin: 0;
+}
+
+/* -- Dynamic Button For Responsive Menu -------------------------------------*/
+
+/*
+The button to open/close the Menu is custom-made and not part of Pure. Here's
+how it works:
+*/
+
+/*
+`.menu-link` represents the responsive menu toggle that shows/hides on
+small screens.
+*/
+
+.menu-link {
+ position: fixed;
+ display: block;
+ /* show this only on small screens */
+ top: 0;
+ left: 0;
+ /* "#menu width" */
+ background: #000;
+ background: rgba(0,0,0,0.7);
+ font-size: 10px;
+ /* change this value to increase/decrease button size */
+ z-index: 10;
+ width: 2em;
+ height: auto;
+ padding: 2.1em 1.6em;
+}
+
+.menu-link:hover,
+.menu-link:focus {
+ background: #000;
+}
+
+.menu-link span {
+ position: relative;
+ display: block;
+}
+
+.menu-link span,
+.menu-link span:before,
+.menu-link span:after {
+ background-color: #fff;
+ width: 100%;
+ height: 0.2em;
+}
+
+.menu-link span:before,
+.menu-link span:after {
+ position: absolute;
+ margin-top: -0.6em;
+ content: " ";
+}
+
+.menu-link span:after {
+ margin-top: 0.6em;
+}
+
+/* -- Responsive Styles (Media Queries) ------------------------------------- */
+
+/*
+Hides the menu at `48em`, but modify this based on your app's needs.
+*/
+
+.header,
+.content {
+ padding-left: 2em;
+ padding-right: 2em;
+}
+
+#layout {
+ padding-left: 150px;
+ /* left col width "#menu" */
+ left: 0;
+}
+
+#menu {
+ left: 150px;
+}
+
+.menu-link {
+ position: fixed;
+ left: 150px;
+ display: none;
+}
+
+#layout.active .menu-link {
+ left: 150px;
+}
\ No newline at end of file
--- /dev/null
+body {
+ color: #777;
+}
+
+.pure-img-responsive {
+ max-width: 100%;
+ height: auto;
+}
+
+/*
+Add transition to containers so they can push in and out.
+*/
+#layout,
+#menu,
+.menu-link {
+ -webkit-transition: all 0.2s ease-out;
+ -moz-transition: all 0.2s ease-out;
+ -ms-transition: all 0.2s ease-out;
+ -o-transition: all 0.2s ease-out;
+ transition: all 0.2s ease-out;
+}
+
+/*
+This is the parent `<div>` that contains the menu and the content area.
+*/
+#layout {
+ position: relative;
+ padding-left: 0;
+}
+ #layout.active #menu {
+ left: 150px;
+ width: 150px;
+ }
+
+ #layout.active .menu-link {
+ left: 150px;
+ }
+/*
+The content `<div>` is where all your content goes.
+*/
+.content {
+ margin: 0 auto;
+ padding: 0 2em;
+ max-width: 800px;
+ margin-bottom: 50px;
+ line-height: 1.6em;
+}
+
+.header {
+ margin: 0;
+ color: #333;
+ text-align: center;
+ padding: 2.5em 2em 0;
+ border-bottom: 1px solid #eee;
+ }
+ .header h1 {
+ margin: 0.2em 0;
+ font-size: 3em;
+ font-weight: 300;
+ }
+ .header h2 {
+ font-weight: 300;
+ color: #ccc;
+ padding: 0;
+ margin-top: 0;
+ }
+
+.content-subhead {
+ margin: 50px 0 20px 0;
+ font-weight: 300;
+ color: #888;
+}
+
+
+
+/*
+The `#menu` `<div>` is the parent `<div>` that contains the `.pure-menu` that
+appears on the left side of the page.
+*/
+
+#menu {
+ margin-left: -150px; /* "#menu" width */
+ width: 150px;
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ z-index: 1000; /* so the menu or its navicon stays above all content */
+ background: #191818;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+}
+ /*
+ All anchors inside the menu should be styled like this.
+ */
+ #menu a {
+ color: #999;
+ border: none;
+ padding: 0.6em 0 0.6em 0.6em;
+ }
+
+ /*
+ Remove all background/borders, since we are applying them to #menu.
+ */
+ #menu .pure-menu,
+ #menu .pure-menu ul {
+ border: none;
+ background: transparent;
+ }
+
+ /*
+ Add that light border to separate items into groups.
+ */
+ #menu .pure-menu ul,
+ #menu .pure-menu .menu-item-divided {
+ border-top: 1px solid #333;
+ }
+ /*
+ Change color of the anchor links on hover/focus.
+ */
+ #menu .pure-menu li a:hover,
+ #menu .pure-menu li a:focus {
+ background: #333;
+ }
+
+ /*
+ This styles the selected menu item `<li>`.
+ */
+ #menu .pure-menu-selected,
+ #menu .pure-menu-heading {
+ background: #1f8dd6;
+ }
+ /*
+ This styles a link within a selected menu item `<li>`.
+ */
+ #menu .pure-menu-selected a {
+ color: #fff;
+ }
+
+ /*
+ This styles the menu heading.
+ */
+ #menu .pure-menu-heading {
+ font-size: 110%;
+ color: #fff;
+ margin: 0;
+ }
+
+/* -- Dynamic Button For Responsive Menu -------------------------------------*/
+
+/*
+The button to open/close the Menu is custom-made and not part of Pure. Here's
+how it works:
+*/
+
+/*
+`.menu-link` represents the responsive menu toggle that shows/hides on
+small screens.
+*/
+.menu-link {
+ position: fixed;
+ display: block; /* show this only on small screens */
+ top: 0;
+ left: 0; /* "#menu width" */
+ background: #000;
+ background: rgba(0,0,0,0.7);
+ font-size: 10px; /* change this value to increase/decrease button size */
+ z-index: 10;
+ width: 2em;
+ height: auto;
+ padding: 2.1em 1.6em;
+}
+
+ .menu-link:hover,
+ .menu-link:focus {
+ background: #000;
+ }
+
+ .menu-link span {
+ position: relative;
+ display: block;
+ }
+
+ .menu-link span,
+ .menu-link span:before,
+ .menu-link span:after {
+ background-color: #fff;
+ width: 100%;
+ height: 0.2em;
+ }
+
+ .menu-link span:before,
+ .menu-link span:after {
+ position: absolute;
+ margin-top: -0.6em;
+ content: " ";
+ }
+
+ .menu-link span:after {
+ margin-top: 0.6em;
+ }
+
+
+/* -- Responsive Styles (Media Queries) ------------------------------------- */
+
+/*
+Hides the menu at `48em`, but modify this based on your app's needs.
+*/
+@media (min-width: 48em) {
+
+ .header,
+ .content {
+ padding-left: 2em;
+ padding-right: 2em;
+ }
+
+ #layout {
+ padding-left: 150px; /* left col width "#menu" */
+ left: 0;
+ }
+ #menu {
+ left: 150px;
+ }
+
+ .menu-link {
+ position: fixed;
+ left: 150px;
+ display: none;
+ }
+
+ #layout.active .menu-link {
+ left: 150px;
+ }
+}
+
+@media (max-width: 48em) {
+ /* Only apply this when the window is small. Otherwise, the following
+ case results in extra padding on the left:
+ * Make the window small.
+ * Tap the menu to trigger the active state.
+ * Make the window large again.
+ */
+ #layout.active {
+ position: relative;
+ left: 150px;
+ }
+}
+
--- /dev/null
+(function (window, document) {
+
+ var layout = document.getElementById('layout'),
+ menu = document.getElementById('menu'),
+ menuLink = document.getElementById('menuLink');
+
+ function toggleClass(element, className) {
+ var classes = element.className.split(/\s+/),
+ length = classes.length,
+ i = 0;
+
+ for(; i < length; i++) {
+ if (classes[i] === className) {
+ classes.splice(i, 1);
+ break;
+ }
+ }
+ // The className is not found
+ if (length === classes.length) {
+ classes.push(className);
+ }
+
+ element.className = classes.join(' ');
+ }
+
+ menuLink.onclick = function (e) {
+ var active = 'active';
+
+ e.preventDefault();
+ toggleClass(layout, active);
+ toggleClass(menu, active);
+ toggleClass(menuLink, active);
+ };
+
+}(this, this.document));
+.button-success:not([disabled])
+ background rgb(28, 184, 65)
+
+.header img
+ vertical-align top
+
+.logo
+ font-size 30pt
+ font-family Audiowide
+ display inline
+ margin-right 0.5em
+
+ .left
+ color #444
+ .right
+ color #e5aa3d
+
+
+#menu
+ .save
+ display block
+ margin 0.25em auto
+
+ .pure-menu-heading
+ background inherit
+ padding 0
+
+ .pure-menu-link
+ padding 0.6em
+ color #fff
+
+ .pure-menu-item .pure-menu-link
+ padding-left 1.5em
+
+
+#main
+ .content
+ h2
+ text-transform capitalize
+
+ .pure-control-group
+ label.units
+ text-align left
+
+ .switch
+ h3, .pure-control-group
+ display inline-block
+
+
table.axes
border-collapse collapse