Always limit motor max-velocity. #209, Fixes for exception logging, Handle corrupt...
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Mon, 25 Mar 2019 21:39:05 +0000 (14:39 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Mon, 25 Mar 2019 21:39:05 +0000 (14:39 -0700)
12 files changed:
CHANGELOG.md
src/avr/src/switch.c
src/js/motor-view.js
src/js/templated-input.js
src/pug/templates/motor-view.pug
src/pug/templates/templated-input.pug
src/py/bbctrl/AVREmu.py
src/py/bbctrl/Camera.py
src/py/bbctrl/CommandQueue.py
src/py/bbctrl/Config.py
src/py/bbctrl/Ctrl.py
src/py/bbctrl/Preplanner.py

index 254e571f66eb217619fd240fd28f97f542c02c8b..19dfbed312cece2bba92a39dfd7f97946344852b 100644 (file)
@@ -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.
index f6cb5a9888bed3f7d15eeaca965bec6cf323a455..6994b1591223ce9b874e0688ee8f3387e7e11d01 100644 (file)
@@ -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;
index d23e554c9252f955da099b2dc1179afa6d875489..8ec118865b91070782f9ca09a9b143317133e226 100644 (file)
@@ -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;
     }
   }
index 80b18fe9abe9b7eb42d3bca7d9e6104058ed6b81..32d80ebd2dd32a1b45040da9a64ad2309c80157b 100644 (file)
@@ -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')}
   }
 }
index fef27ff303a91caa0a3f3e6e22e7d3cb75fb4c6a..428dd8cc1fa82e2d8c14a9f905c7866367e193a5 100644 (file)
@@ -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)
 
index f1478d2fd6ef7a9a89057a98e29055ba1465a3ac..ff93c58dbd4524fd52f8323d193f6934548b5776 100644 (file)
@@ -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")
index 02ee250edd84574bd6b3e386220b46974c0ef080..f2f562263c58b0addb8a0c5dda241888ff5983d3 100644 (file)
@@ -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')
index 97b83bf6fab4dad0df7e3aa9c5dce818f50f848d..8bfc66065acb36ddd294b450f47fe3f84054c4c2 100755 (executable)
@@ -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('<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()
index 7f89a89a91d73e05fa5767600e474baa58a483d8..c167c57c34ac36dcc05b1d055c095034ae8a8a01 100644 (file)
@@ -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')
 
 
index 58a95d9fe107323ad415d7caf649ee71625a5350..ea0af9cd06e0eaca8e3ad591a19b9f9c65d73e25 100644 (file)
@@ -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)
index e6e536a711295554ddfd47c7c640f5c4163fd706..f969a55083875a6862d04023c9de04172e0bee93 100644 (file)
@@ -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')
index 9a706e667f3c3a9970411e1de695d27ebfb31a60..005198d614e3e886cb8b61f434bc61bfca72f8af 100644 (file)
@@ -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()