From: Joseph Coffland Date: Mon, 25 Mar 2019 21:39:05 +0000 (-0700) Subject: Always limit motor max-velocity. #209, Fixes for exception logging, Handle corrupt... X-Git-Url: https://git.buildbotics.com/?a=commitdiff_plain;h=434a9a2bb07d6a002120b80f8b8294e9740f1ba6;p=bbctrl-firmware Always limit motor max-velocity. #209, Fixes for exception logging, Handle corrupt GCode simulation data correctly, Switch debounce fixes --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 254e571..19dfbed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Buildbotics CNC Controller Firmware Changelog ## 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. diff --git a/src/avr/src/switch.c b/src/avr/src/switch.c index f6cb5a9..6994b15 100644 --- a/src/avr/src/switch.c +++ b/src/avr/src/switch.c @@ -33,8 +33,8 @@ static struct { - int16_t debounce; - int16_t lockout; + uint16_t debounce; + uint16_t lockout; } sw = { .debounce = SWITCH_DEBOUNCE, .lockout = SWITCH_LOCKOUT, @@ -46,8 +46,8 @@ typedef struct { switch_callback_t cb; bool state; - int8_t debounce; - uint8_t lockout; + uint16_t debounce; + uint16_t lockout; bool initialized; } switch_t; @@ -95,8 +95,7 @@ void switch_rtc_callback() { // 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; diff --git a/src/js/motor-view.js b/src/js/motor-view.js index d23e554..8ec1188 100644 --- a/src/js/motor-view.js +++ b/src/js/motor-view.js @@ -55,7 +55,7 @@ module.exports = { maxMaxVelocity: function () { - return 15 * this.umPerStep / this.motor['microsteps']; + return 1 * (15 * this.umPerStep / this.motor['microsteps']).toFixed(3); }, @@ -85,11 +85,14 @@ module.exports = { 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; } } diff --git a/src/js/templated-input.js b/src/js/templated-input.js index 80b18fe..32d80eb 100644 --- a/src/js/templated-input.js +++ b/src/js/templated-input.js @@ -41,6 +41,17 @@ module.exports = { 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; @@ -58,28 +69,21 @@ module.exports = { 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')} } } diff --git a/src/pug/templates/motor-view.pug b/src/pug/templates/motor-view.pug index fef27ff..428dd8c 100644 --- a/src/pug/templates/motor-view.pug +++ b/src/pug/templates/motor-view.pug @@ -37,7 +37,6 @@ script#motor-view-template(type="text/x-template") :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) diff --git a/src/pug/templates/templated-input.pug b/src/pug/templates/templated-input.pug index f1478d2..ff93c58 100644 --- a/src/pug/templates/templated-input.pug +++ b/src/pug/templates/templated-input.pug @@ -36,13 +36,13 @@ script#templated-input-template(type="text/x-template") 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") diff --git a/src/py/bbctrl/AVREmu.py b/src/py/bbctrl/AVREmu.py index 02ee250..f2f5622 100644 --- a/src/py/bbctrl/AVREmu.py +++ b/src/py/bbctrl/AVREmu.py @@ -108,7 +108,7 @@ class AVREmu(object): 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') diff --git a/src/py/bbctrl/Camera.py b/src/py/bbctrl/Camera.py index 97b83bf..8bfc660 100755 --- a/src/py/bbctrl/Camera.py +++ b/src/py/bbctrl/Camera.py @@ -469,50 +469,3 @@ class VideoHandler(web.RequestHandler): 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('') - - - 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() diff --git a/src/py/bbctrl/CommandQueue.py b/src/py/bbctrl/CommandQueue.py index 7f89a89..c167c57 100644 --- a/src/py/bbctrl/CommandQueue.py +++ b/src/py/bbctrl/CommandQueue.py @@ -71,7 +71,7 @@ class CommandQueue(): try: if cb is not None: cb(*args, **kwargs) - except Exception as e: + except Exception: self.log.exception('During command queue callback') diff --git a/src/py/bbctrl/Config.py b/src/py/bbctrl/Config.py index 58a95d9..ea0af9c 100644 --- a/src/py/bbctrl/Config.py +++ b/src/py/bbctrl/Config.py @@ -52,7 +52,7 @@ class Config(object): 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): @@ -73,7 +73,7 @@ class Config(object): 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) diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py index e6e536a..f969a55 100644 --- a/src/py/bbctrl/Ctrl.py +++ b/src/py/bbctrl/Ctrl.py @@ -64,7 +64,7 @@ class Ctrl(object): 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') diff --git a/src/py/bbctrl/Preplanner.py b/src/py/bbctrl/Preplanner.py index 9a706e6..005198d 100644 --- a/src/py/bbctrl/Preplanner.py +++ b/src/py/bbctrl/Preplanner.py @@ -151,7 +151,7 @@ class Preplanner(object): # Start planner thread plan = yield self.pool.submit( - self._exec_plan, filename, state, config, cancel) + self._load_plan, filename, state, config, cancel) return plan @@ -175,62 +175,80 @@ class Preplanner(object): 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()