## v0.4.7
- Fix homing switch to motor channel mapping with non-standard axis order.
- Added ``switch-debounce`` and ``switch-lockout`` config options.
+ - Handle corrupt GCode simulation data correctly.
+ - Fixes for exception logging.
+ - Always limit motor max-velocity. #209
## v0.4.6
- Fixed a rare ``Negative s-curve time`` error.
static struct {
- int16_t debounce;
- int16_t lockout;
+ uint16_t debounce;
+ uint16_t lockout;
} sw = {
.debounce = SWITCH_DEBOUNCE,
.lockout = SWITCH_LOCKOUT,
switch_callback_t cb;
bool state;
- int8_t debounce;
- uint8_t lockout;
+ uint16_t debounce;
+ uint16_t lockout;
bool initialized;
} switch_t;
// Debounce switch
bool state = IN_PIN(s->pin);
if (state == s->state && s->initialized) s->debounce = 0;
- else if ((state && ++s->debounce == sw.debounce) ||
- (!state && --s->debounce == -sw.debounce)) {
+ else if (++s->debounce == sw.debounce) {
s->state = state;
s->debounce = 0;
s->initialized = true;
maxMaxVelocity: function () {
- return 15 * this.umPerStep / this.motor['microsteps'];
+ return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3);
},
events: {
'input-changed': function() {
- // Limit max-velocity
- if (this.invalidMaxVelocity)
- this.motor['max-velocity'] = this.maxMaxVelocity;
+ Vue.nextTick(function () {
+ // Limit max-velocity
+ if (this.invalidMaxVelocity)
+ this.$set('motor["max-velocity"]', this.maxMaxVelocity);
+
+ this.$dispatch('config-changed');
+ }.bind(this))
- this.$dispatch('config-changed');
return false;
}
}
metric: function () {return this.$root.metric()},
+ _view: function () {
+ if (this.template.scale) {
+ if (this.metric) return 1 * this.model.toFixed(3);
+
+ return 1 * (this.model / this.template.scale).toFixed(4);
+ }
+
+ return this.model;
+ },
+
+
units: function () {
return (this.metric || !this.template.iunit) ?
this.template.unit : this.template.iunit;
watch: {
- metric: function () {this.set_view()},
- model: function () {this.set_view()}
- },
-
+ _view: function () {this.view = this._view},
- ready: function () {this.set_view()},
-
- methods: {
- set_view: function () {
+ view: function () {
if (this.template.scale && !this.metric)
- this.view = (this.model / this.template.scale).toFixed(3);
- else this.view = this.model;
- },
+ this.model = this.view * this.template.scale;
+ else this.model = this.view;
+ }
+ },
- change: function () {
- if (this.template.scale && !this.metric)
- this.model = 1 * (this.view * this.template.scale).toFixed(4);
- else this.model = this.view;
+ ready: function () {this.view = this._view},
- this.$dispatch('input-changed');
- }
+
+ methods: {
+ change: function () {this.$dispatch('input-changed')}
}
}
:model.sync="motor[$key]", :template="templ")
label.extra(v-if="$key == 'microsteps'", slot="extra",
- :class="{error: invalidMaxVelocity}",
title="Microsteps per second")
| ({{ustepPerSec / 1000 | fixed 1}}k µstep/sec)
input(v-if="template.type == 'bool'", type="checkbox", v-model="view",
:name="name", @change="change")
- input(v-if="template.type == 'float'", v-model="view", number,
+ input(v-if="template.type == 'float'", v-model.number="view", number,
:min="template.min", :max="template.max", :step="template.step || 'any'",
- type="number", :name="name", @change="change")
+ type="number", :name="name", @change="change")
- input(v-if="template.type == 'int' && !template.values", v-model="view",
- number, :min="template.min", :max="template.max", type="number",
- :name="name", @change="change")
+ input(v-if="template.type == 'int' && !template.values", number,
+ v-model.number="view", :min="template.min", :max="template.max",
+ type="number", :name="name", @change="change")
input(v-if="template.type == 'string'", v-model="view", type="text",
:name="name", @change="change")
self.write_enabled = True
- except Exception as e:
+ except Exception:
self.pid = None
self.avrOut, self.avrIn, self.i2cOut = None, None, None
self.log.exception('Failed to start bbemu')
def on_connection_close(self): self.camera.remove_client(self)
-
-
-
-if __name__ == '__main__':
- class Ctrl(object):
- def __init__(self, args, ioloop):
- self.args = args
- self.ioloop = ioloop
- self.log = bbctrl.log.Log(args, ioloop)
- self.camera = Camera(self)
-
-
- class RootHandler(web.RequestHandler):
- def get(self):
- self.set_header('Content-Type', 'text/html')
- self.write('<html><body><img src="video"/></body></html>')
-
-
- class Web(web.Application):
- def __init__(self, args, ioloop):
- self.ctrl = Ctrl(args, ioloop)
-
- handlers = [
- (r'/', RootHandler),
- (r'/video', VideoHandler)
- ]
-
- web.Application.__init__(self, handlers)
- self.listen(9000, address = '127.0.0.1')
-
-
- def get_ctrl(self, id = None): return self.ctrl
-
-
- import argparse
- parser = argparse.ArgumentParser(description = 'Camera Server Test')
- parser.add_argument('--width', default = 640, type = int)
- parser.add_argument('--height', default = 480, type = int)
- parser.add_argument('--fps', default = 15, type = int)
- parser.add_argument('--fourcc', default = 'MJPG')
- args = parser.parse_args()
-
- from tornado import ioloop
- ioloop = ioloop.IOLoop.current()
-
- server = Web(args, ioloop)
- ioloop.start()
try:
if cb is not None: cb(*args, **kwargs)
- except Exception as e:
+ except Exception:
self.log.exception('During command queue callback')
encoding = 'utf-8') as f:
self.template = json.load(f)
- except Exception as e: self.log.exception(e)
+ except Exception: self.log.exception()
def get(self, name, default = None):
try:
self.upgrade(config)
- except Exception as e: self.log.exception(e)
+ except Exception: self.log.exception()
except Exception as e:
self.log.warning('%s', e)
self.lcd.add_new_page(bbctrl.MainLCDPage(self))
self.lcd.add_new_page(bbctrl.IPLCDPage(self.lcd))
- except Exception as e: self.log.get('Ctrl').exception(e)
+ except Exception: self.log.get('Ctrl').exception()
def __del__(self): print('Ctrl deleted')
# Start planner thread
plan = yield self.pool.submit(
- self._exec_plan, filename, state, config, cancel)
+ self._load_plan, filename, state, config, cancel)
return plan
self.plans[filename][1] = progress
- def _exec_plan(self, filename, state, config, cancel):
- try:
- os.nice(5)
+ def _read_files(self, files):
+ with open(files[0], 'r') as f: meta = json.load(f)
+ with open(files[1], 'rb') as f: positions = f.read()
+ with open(files[2], 'rb') as f: speeds = f.read()
- hid = plan_hash(self.ctrl.get_upload(filename), config)
- base = self.ctrl.get_plan(filename + '.' + hid)
- files = [
- base + '.json', base + '.positions.gz', base + '.speeds.gz']
+ return meta, positions, speeds
+
+
+ def _exec_plan(self, filename, files, state, config, cancel):
+ self._clean_plans(filename) # Clean up old plans
+
+ path = os.path.abspath(self.ctrl.get_upload(filename))
+ with tempfile.TemporaryDirectory() as tmpdir:
+ cmd = (
+ '/usr/bin/env', 'python3',
+ bbctrl.get_resource('plan.py'),
+ path, json.dumps(state), json.dumps(config),
+ '--max-time=%s' % self.max_plan_time,
+ '--max-loop=%s' % self.max_loop_time
+ )
+
+ self.log.info('Running: %s', cmd)
+
+ with subprocess.Popen(cmd, stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE,
+ cwd = tmpdir) as proc:
- found = True
- for path in files:
- if not os.path.exists(path): found = False
+ for line in proc.stdout:
+ self._progress(filename, float(line))
+ if cancel.is_set():
+ proc.terminate()
+ return
- if not found:
- self._clean_plans(filename) # Clean up old plans
+ out, errs = proc.communicate()
- path = os.path.abspath(self.ctrl.get_upload(filename))
- with tempfile.TemporaryDirectory() as tmpdir:
- cmd = (
- '/usr/bin/env', 'python3',
- bbctrl.get_resource('plan.py'),
- path, json.dumps(state), json.dumps(config),
- '--max-time=%s' % self.max_plan_time,
- '--max-loop=%s' % self.max_loop_time
- )
+ self._progress(filename, 1)
+ if cancel.is_set(): return
- self.log.info('Running: %s', cmd)
+ if proc.returncode:
+ raise Exception('Plan failed: ' + errs.decode('utf8'))
- with subprocess.Popen(cmd, stdout = subprocess.PIPE,
- stderr = subprocess.PIPE,
- cwd = tmpdir) as proc:
+ os.rename(tmpdir + '/meta.json', files[0])
+ os.rename(tmpdir + '/positions.gz', files[1])
+ os.rename(tmpdir + '/speeds.gz', files[2])
- for line in proc.stdout:
- self._progress(filename, float(line))
- if cancel.is_set():
- proc.terminate()
- return
- out, errs = proc.communicate()
+ def _files_exist(self, files):
+ for path in files:
+ if not os.path.exists(path): return False
+
+ return True
+
- self._progress(filename, 1)
- if cancel.is_set(): return
+ def _load_plan(self, filename, state, config, cancel):
+ try:
+ os.nice(5)
- if proc.returncode:
- self.log.error('Plan failed: ' +
- errs.decode('utf8'))
- return # Failed
+ hid = plan_hash(self.ctrl.get_upload(filename), config)
+ base = self.ctrl.get_plan(filename + '.' + hid)
+ files = [
+ base + '.json', base + '.positions.gz', base + '.speeds.gz']
+
+ try:
+ if not self._files_exist(files):
+ self._exec_plan(filename, files, state, config, cancel)
- os.rename(tmpdir + '/meta.json', files[0])
- os.rename(tmpdir + '/positions.gz', files[1])
- os.rename(tmpdir + '/speeds.gz', files[2])
+ if not cancel.is_set(): return self._read_files(files)
- with open(files[0], 'r') as f: meta = json.load(f)
- with open(files[1], 'rb') as f: positions = f.read()
- with open(files[2], 'rb') as f: speeds = f.read()
+ except:
+ self.log.exception()
- return meta, positions, speeds
+ for path in files:
+ if os.path.exists(path):
+ os.remove(path)
- except Exception as e: self.log.exception(e)
+ except:
+ self.log.exception()