Synchronized slave motors, Gamepad configs, Attention to hold/estop reason, Delete...
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Thu, 14 Sep 2017 08:52:40 +0000 (01:52 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Thu, 14 Sep 2017 08:52:40 +0000 (01:52 -0700)
31 files changed:
package.json
scripts/setup_rpi.sh
src/avr/src/axis.c
src/avr/src/axis.h
src/avr/src/motor.c
src/avr/src/motor.h
src/avr/src/vars.def
src/avr/test/planner-test.c
src/jade/index.jade
src/jade/templates/admin-view.jade
src/jade/templates/control-view.jade
src/jade/templates/spindle-view.jade [deleted file]
src/jade/templates/tool-view.jade [new file with mode: 0644]
src/js/app.js
src/js/control-view.js
src/js/motor-view.js
src/js/spindle-view.js [deleted file]
src/js/tool-view.js [new file with mode: 0644]
src/pwr/main.c
src/py/bbctrl/AVR.py
src/py/bbctrl/Config.py
src/py/bbctrl/FileHandler.py
src/py/bbctrl/Jog.py
src/py/inevent/AbsAxisScaling.py
src/py/inevent/EventHandler.py
src/py/inevent/EventStream.py
src/py/inevent/InEvent.py
src/py/inevent/JogHandler.py
src/resources/config-defaults.json [deleted file]
src/resources/config-template.json
src/stylus/style.styl

index 33531cd72069ceb6d05b4e0f9c91911b031eb12f..4e92c8a6ee89b91f4bbbba2a0da256226bacf6d7 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "bbctrl",
-  "version": "0.1.22",
+  "version": "0.2.1",
   "homepage": "https://github.com/buildbotics/rpi-firmware",
   "license": "GPL 3+",
 
index 8f02d904b6fe395972d2ca47a658d10279946e8a..7c3b266a3e1186e579f9ed9e76048e04d9ef66ab 100755 (executable)
@@ -102,6 +102,10 @@ cp upgrade-bbctrl /usr/local/bin
 echo -e "\ndtoverlay=pi3-disable-bt" >> /boot/config.txt
 # sudo systemctl disable hciuart
 
+# Install hawkeye
+dpkg -i hawkeye_0.5_armhf.deb
+echo 'ACTION=="add", KERNEL=="video0", RUN+="/usr/sbin/service hawkeye restart"' > /etc/udev/rules.d/50-hawkeye.rules
+
 # TODO setup input and serial device permissions in udev & forward 80 -> 8080
 
 reboot
index 0697aafb056b193757f9ba5ef6d142661d708d21..a0004395dc7296371a2934acd516d73cd9997ba9 100644 (file)
@@ -76,7 +76,17 @@ int axis_get_id(char axis) {
 
 
 int axis_get_motor(int axis) {return motor_map[axis];}
-void axis_set_motor(int axis, int motor) {motor_map[axis] = motor;}
+
+
+// Map axes to first matching motor
+void axis_map_motors() {
+  for (int axis = 0; axis < AXES; axis++)
+    for (int motor = 0; motor < MOTORS; motor++)
+      if (motor_get_axis(motor) == axis) {
+        motor_map[axis] = motor;
+        break;
+      }
+}
 
 
 float axis_get_vector_length(const float a[], const float b[]) {
@@ -110,11 +120,11 @@ float axis_get_vector_length(const float a[], const float b[]) {
   AXIS_VAR_SET(NAME, TYPE)
 
 
+AXIS_SET(homed, bool)
+
 AXIS_GET(velocity_max, float, 0)
 AXIS_GET(homed, bool, false)
-AXIS_SET(homed, bool)
 AXIS_GET(homing_mode, homing_mode_t, HOMING_MANUAL)
-AXIS_SET(homing_mode, homing_mode_t)
 AXIS_GET(radius, float, 0)
 AXIS_GET(travel_min, float, 0)
 AXIS_GET(travel_max, float, 0)
@@ -137,6 +147,7 @@ AXIS_VAR_SET(velocity_max, float)
 AXIS_VAR_SET(radius, float)
 AXIS_VAR_SET(travel_min, float)
 AXIS_VAR_SET(travel_max, float)
+AXIS_VAR_SET(homing_mode, homing_mode_t)
 AXIS_VAR_SET(search_velocity, float)
 AXIS_VAR_SET(latch_velocity, float)
 AXIS_VAR_SET(zero_backoff, float)
index af7f658e868fe972f8299421255fb7e0203edc0c..0d8251ae70148a7dc1d1055d37de494058d84b6b 100644 (file)
@@ -53,7 +53,7 @@ bool axis_is_enabled(int axis);
 char axis_get_char(int axis);
 int axis_get_id(char axis);
 int axis_get_motor(int axis);
-void axis_set_motor(int axis, int motor);
+void axis_map_motors();
 float axis_get_vector_length(const float a[], const float b[]);
 
 float axis_get_velocity_max(int axis);
@@ -61,7 +61,6 @@ float axis_get_jerk_max(int axis);
 bool axis_get_homed(int axis);
 void axis_set_homed(int axis, bool homed);
 homing_mode_t axis_get_homing_mode(int axis);
-void axis_set_homing_mode(int axis, homing_mode_t mode);
 float axis_get_radius(int axis);
 float axis_get_travel_min(int axis);
 float axis_get_travel_max(int axis);
index 4e421ff06da32596fd4eb818af1933c8096059cd..b8a43e7e7eb6312874cee4346d8b3d99e5251aec 100644 (file)
@@ -52,6 +52,7 @@
 typedef struct {
   // Config
   uint8_t axis;                  // map motor to axis
+  bool slave;
   uint16_t microsteps;           // microsteps per full step
   bool reverse;
   motor_power_mode_t power_mode;
@@ -136,7 +137,6 @@ void motor_init() {
     motor_t *m = &motors[motor];
 
     _update_config(motor);
-    axis_set_motor(m->axis, motor);
 
     // IO pins
     DIRSET_PIN(m->step_pin); // Output
@@ -162,9 +162,9 @@ void motor_init() {
     m->dma->REPCNT = 0;
     m->dma->CTRLB = 0;
     m->dma->CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_1BYTE_gc;
-
-    drv8711_set_microsteps(motor, m->microsteps);
   }
+
+  axis_map_motors();
 }
 
 
@@ -176,29 +176,7 @@ bool motor_is_enabled(int motor) {
 int motor_get_axis(int motor) {return motors[motor].axis;}
 
 
-void motor_set_axis(int motor, uint8_t axis) {
-  if (MOTORS <= motor || AXES <= axis || axis == motors[motor].axis) return;
-  axis_set_motor(motors[motor].axis, -1);
-  motors[motor].axis = axis;
-  axis_set_motor(axis, motor);
-}
-
-
 float motor_get_steps_per_unit(int motor) {return motors[motor].steps_per_unit;}
-uint16_t motor_get_microsteps(int motor) {return motors[motor].microsteps;}
-
-
-void motor_set_microsteps(int motor, uint16_t microsteps) {
-  switch (microsteps) {
-  case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
-    break;
-  default: return;
-  }
-
-  motors[motor].microsteps = microsteps;
-  _update_config(motor);
-  drv8711_set_microsteps(motor, microsteps);
-}
 
 
 void motor_set_position(int motor, int32_t position) {
@@ -384,8 +362,13 @@ float get_step_angle(int motor) {return motors[motor].step_angle;}
 
 
 void set_step_angle(int motor, float value) {
-  motors[motor].step_angle = value;
-  _update_config(motor);
+  if (motors[motor].slave) return;
+
+  for (int m = motor; m < MOTORS; m++)
+    if (motors[m].axis == motors[motor].axis) {
+      motors[m].step_angle = value;
+      _update_config(m);
+    }
 }
 
 
@@ -393,8 +376,13 @@ float get_travel(int motor) {return motors[motor].travel_rev;}
 
 
 void set_travel(int motor, float value) {
-  motors[motor].travel_rev = value;
-  _update_config(motor);
+  if (motors[motor].slave) return;
+
+  for (int m = motor; m < MOTORS; m++)
+    if (motors[m].axis == motors[motor].axis) {
+      motors[m].travel_rev = value;
+      _update_config(m);
+    }
 }
 
 
@@ -403,7 +391,21 @@ uint16_t get_microstep(int motor) {return motors[motor].microsteps;}
 
 void set_microstep(int motor, uint16_t value) {
   if (motor < 0 || MOTORS <= motor) return;
-  motor_set_microsteps(motor, value);
+
+  switch (value) {
+  case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: case 256:
+    break;
+  default: return;
+  }
+
+  if (motors[motor].slave) return;
+
+  for (int m = motor; m < MOTORS; m++)
+    if (motors[m].axis == motors[motor].axis) {
+      motors[m].microsteps = value;
+      _update_config(m);
+      drv8711_set_microsteps(m, value);
+    }
 }
 
 
@@ -414,17 +416,41 @@ bool get_reverse(int motor) {
 
 
 void set_reverse(int motor, bool value) {motors[motor].reverse = value;}
-char get_motor_axis(int motor) {return motors[motor].axis;}
-void set_motor_axis(int motor, uint8_t axis) {motor_set_axis(motor, axis);}
 
 
 uint8_t get_power_mode(int motor) {return motors[motor].power_mode;}
 
 
 void set_power_mode(int motor, uint8_t value) {
-  if (value <= MOTOR_POWERED_ONLY_WHEN_MOVING)
-    motors[motor].power_mode = value;
-  else motors[motor].power_mode = MOTOR_DISABLED;
+  if (motors[motor].slave) return;
+
+  for (int m = motor; m < MOTORS; m++)
+    if (motors[m].axis == motors[motor].axis)
+      motors[m].power_mode =
+        value <= MOTOR_POWERED_ONLY_WHEN_MOVING ? value : MOTOR_DISABLED;
+}
+
+
+char get_motor_axis(int motor) {return motors[motor].axis;}
+
+void set_motor_axis(int motor, uint8_t axis) {
+  if (MOTORS <= motor || AXES <= axis || axis == motors[motor].axis) return;
+  motors[motor].axis = axis;
+  axis_map_motors();
+  mp_runtime_set_steps_from_position(); // Reset encoder counts
+
+  // Check if this is now a slave motor
+  motors[motor].slave = false;
+  for (int m = 0; m < motor; m++)
+    if (motors[m].axis == motors[motor].axis) {
+      // Sync with master
+      set_step_angle(motor, motors[m].step_angle);
+      set_travel(motor, motors[m].travel_rev);
+      set_microstep(motor, motors[m].microsteps);
+      set_power_mode(motor, motors[m].power_mode);
+      motors[motor].slave = true;
+      break;
+    }
 }
 
 
index ce568b47590f4b69911a4f968b5ce7632b9bd062..2115bf79b68a2ec23dd4413a1b105addc5b3df8c 100644 (file)
@@ -47,8 +47,6 @@ void motor_init();
 bool motor_is_enabled(int motor);
 int motor_get_axis(int motor);
 float motor_get_steps_per_unit(int motor);
-uint16_t motor_get_microsteps(int motor);
-void motor_set_microsteps(int motor, uint16_t microsteps);
 void motor_set_position(int motor, int32_t position);
 int32_t motor_get_position(int motor);
 
index 8346d7cca289614c135d8837e0ece7fb12fbd060..683abc118fba6cbf308c3880415a4be27bcc3052 100644 (file)
@@ -60,10 +60,10 @@ VAR(min_sw_mode,    ls, uint8_t,  MOTORS, 1, 1, "Minimum switch mode")
 VAR(max_sw_mode,    xs, uint8_t,  MOTORS, 1, 1, "Maximum switch mode")
 VAR(estop_mode,     et, uint8_t,  0,      1, 1, "Estop switch mode")
 VAR(probe_mode,     pt, uint8_t,  0,      1, 1, "Probe switch mode")
-VAR(min_switch,     lw, uint8_t,     MOTORS, 0, 1, "Minimum switch state")
-VAR(max_switch,     xw, uint8_t,     MOTORS, 0, 1, "Maximum switch state")
-VAR(estop_switch,   ew, uint8_t,     0,      0, 1, "Estop switch state")
-VAR(probe_switch,   pw, uint8_t,     0,      0, 1, "Probe switch state")
+VAR(min_switch,     lw, uint8_t,  MOTORS, 0, 1, "Minimum switch state")
+VAR(max_switch,     xw, uint8_t,  MOTORS, 0, 1, "Maximum switch state")
+VAR(estop_switch,   ew, uint8_t,  0,      0, 1, "Estop switch state")
+VAR(probe_switch,   pw, uint8_t,  0,      0, 1, "Probe switch state")
 
 // Homing
 VAR(homing_mode,    ho, uint8_t,  MOTORS, 1, 1, "Homing type")
index 78206eecb6bd2f513601b61eddaedf54d325a334..3161da5fe79b53c6b2595d4be55ccaaeed120e31 100644 (file)
@@ -40,7 +40,7 @@
 int main(int argc, char *argv[]) {
   mp_init();                      // motion planning
   machine_init();                 // gcode machine
-  for (int i = 0; i < 4; i++) axis_set_motor(i, i);
+  axis_map_motors();
 
   stat_t status = STAT_OK;
 
index 3de168c8bf1b7f64557a4c9294b604b00fca366d..de300698335fc416b6d54e69bbc2ddeae21c9b66 100644 (file)
@@ -29,7 +29,7 @@ html(lang="en")
       a#menuLink.menu-link(href="#menu"): span
 
       #menu
-        button.save(:disabled="!modified", class="pure-button button-success"
+        button.save.pure-button.button-success(:disabled="!modified",
           @click="save") Save
 
         .pure-menu
@@ -44,12 +44,12 @@ html(lang="en")
               a.pure-menu-link(:href="'#motor:' + $index") Motor {{$index}}
 
             li.pure-menu-heading
-              a.pure-menu-link(href="#spindle") Spindle
+              a.pure-menu-link(href="#tool") Tool
 
             li.pure-menu-heading
               a.pure-menu-link(href="#io") I/O
 
-            li.pure-menu-heading
+            li.pure-menu-heading(style="display:none")
               a.pure-menu-link(href="#gcode") Gcode
 
             li.pure-menu-heading
index fee2ce4c8b1a3f2650b4029de3d5fa6cadc83c7d..13150aa63d605bf7eb5d524b6f60021eae34e70a 100644 (file)
@@ -42,7 +42,7 @@ script#admin-view-template(type="text/x-template")
       h3(slot="header") Reset to default configuration?
       p(slot="body") All configuration changes will be lost.
       div(slot="footer")
-        button.pure-button.button-error(@click="confirmReset = false") Cancel
+        button.pure-button(@click="confirmReset = false") Cancel
         button.pure-button.button-success(@click="reset") OK
 
     message(:show.sync="configReset")
index d38b7d60ac34e9c91d123347feeb0a54b38096fb..d69fef8f659fec184f4e3968186db0bd6c1f80a7 100644 (file)
@@ -23,8 +23,7 @@ script#control-view-template(type="text/x-template")
 
             button.pure-button(:disabled="state.x != 'READY'",
               title="Zero {{'#{axis}' | upper}} axis offset.",
-              @click="set_position('#{axis}', state['#{axis}p'])")
-              | &empty;
+              @click="zero_axis('#{axis}')") &empty;
 
             button.pure-button(:disabled="state.x != 'READY'",
               title="Home {{'#{axis}' | upper}} axis.",
@@ -74,11 +73,11 @@ script#control-view-template(type="text/x-template")
      table.info
       tr
         th State
-        td {{get_state()}}
+        td(:class="{attention: highlight_reason()}") {{get_state()}}
         td
       tr
         th Reason
-        td.reason {{get_reason()}}
+        td.reason(:class="{attention: highlight_reason()}") {{get_reason()}}
         td
       tr
         th Feed
@@ -172,18 +171,30 @@ script#control-view-template(type="text/x-template")
 
       section#content1.tab-content
         .toolbar
-          button.pure-button(title="Upload a new program file.", @click="open",
+          button.pure-button(title="Upload a new GCode program.", @click="open",
             :disabled="state.x == 'RUNNING' || state.x == 'STOPPING'")
             .fa.fa-folder-open
 
           input.gcode-file-input(type="file", @change="upload",
             style="display:none", accept=".nc,.gcode,.gc,.ngc")
 
-          button.pure-button(title="Delete current program file.",
-            @click="delete", :disabled="!file")
+          button.pure-button(title="Delete current GCode program.",
+            @click="deleteGCode = true", :disabled="!file")
             .fa.fa-trash
 
-          select(title="Select previously uploaded program files.",
+          message(:show.sync="deleteGCode")
+            h3(slot="header") Delete GCode?
+            p(slot="body")
+            div(slot="footer")
+              button.pure-button(@click="deleteGCode = false") Cancel
+              button.pure-button.button-error(@click="deleteAll")
+                .fa.fa-trash
+                | &nbsp;all
+              button.pure-button.button-success(@click="deleteCurrent")
+                .fa.fa-trash
+                | &nbsp;selected
+
+          select(title="Select previously uploaded GCode programs.",
             v-model="file", @change="load",
             :disabled="state.x == 'RUNNING' || state.x == 'STOPPING'")
             option(v-for="file in files", :value="file") {{file}}
diff --git a/src/jade/templates/spindle-view.jade b/src/jade/templates/spindle-view.jade
deleted file mode 100644 (file)
index c744d0a..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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")
diff --git a/src/jade/templates/tool-view.jade b/src/jade/templates/tool-view.jade
new file mode 100644 (file)
index 0000000..d92eb3a
--- /dev/null
@@ -0,0 +1,8 @@
+script#tool-view-template(type="text/x-template")
+  #tool
+    h1 Spindle Configuration
+
+    form.pure-form.pure-form-aligned
+      fieldset
+        templated-input(v-for="templ in template.tool", :name="$key",
+          :model.sync="tool[$key]", :template="templ")
index cdf61072c4785d20fc36e217a950d61c267fc50b..0690840b73c67167db08faf110db7cc1c3b200e8 100644 (file)
@@ -26,7 +26,7 @@ module.exports = new Vue({
     'loading-view': {template: '<h1>Loading...</h1>'},
     'control-view': require('./control-view'),
     'motor-view': require('./motor-view'),
-    'spindle-view': require('./spindle-view'),
+    'tool-view': require('./tool-view'),
     'io-view': require('./io-view'),
     'gcode-view': require('./gcode-view'),
     'admin-view': require('./admin-view')
index 7427a61eabcdd53c7d10c0ec438c79807f783d29..3b92f091c7e160570c37fd9728fa7bd9b6973ac6 100644 (file)
@@ -22,7 +22,7 @@ function escapeHTML(s) {
 
 module.exports = {
   template: '#control-view-template',
-  props: ['config', 'state'],
+  props: ['config', 'template', 'state'],
 
 
   data: function () {
@@ -41,7 +41,8 @@ module.exports = {
       position_msg:
       {x: false, y: false, z: false, a: false, b: false, c: false},
       axis_position: 0,
-      video_url: ''
+      video_url: '',
+      deleteGCode: false
     }
   },
 
@@ -94,9 +95,8 @@ module.exports = {
     },
 
 
-    send: function (msg) {
-      this.$dispatch('send', msg);
-    },
+    highlight_reason: function () {return this.get_reason() != ''},
+    send: function (msg) {this.$dispatch('send', msg)},
 
 
     get_axis_motor_id: function (axis) {
@@ -117,9 +117,21 @@ module.exports = {
     },
 
 
-    enabled: function (axis) {
+    get_axis_motor_param: function (axis, name) {
       var motor = this.get_axis_motor(axis);
-      return typeof motor != 'undefined' && motor['power-mode'] != 'disabled';
+      if (typeof motor == 'undefined') return;
+      if (typeof motor[name] != 'undefined') return motor[name];
+
+      for (var section in this.template['motors']) {
+        var sec = this.template['motors'][section];
+        if (typeof sec[name] != 'undefined') return sec[name]['default'];
+      }
+    },
+
+
+    enabled: function (axis) {
+      var pm = this.get_axis_motor_param(axis, 'power-mode')
+      return typeof pm != 'undefined' && pm != 'disabled';
     },
 
 
@@ -148,6 +160,7 @@ module.exports = {
 
 
     update: function () {
+      // Update file list
       api.get('file')
         .done(function (files) {
           var index = files.indexOf(this.file);
@@ -215,9 +228,15 @@ module.exports = {
     },
 
 
-    delete: function () {
-      if (!this.file) return;
-      api.delete('file/' + this.file).done(this.update);
+    deleteCurrent: function () {
+      if (this.file) api.delete('file/' + this.file).done(this.update);
+      this.deleteGCode = false;
+    },
+
+
+    deleteAll: function () {
+      api.delete('file').done(this.update);
+      this.deleteGCode = false;
     },
 
 
@@ -225,11 +244,9 @@ module.exports = {
       if (typeof axis == 'undefined') api.put('home');
 
       else {
-        var motor = this.get_axis_motor(axis);
-
-        if (motor['homing-mode'] == 'manual')
-          this.manual_home[axis] = true;
-        else api.put('home/' + axis);
+        if (this.get_axis_motor_param(axis, 'homing-mode') != 'manual')
+          api.put('home/' + axis);
+        else this.manual_home[axis] = true;
       }
     },
 
@@ -253,10 +270,13 @@ module.exports = {
 
     set_position: function (axis, position) {
       this.position_msg[axis] = false;
-      api.put('position/' + axis, {position: 0});
+      api.put('position/' + axis, {'position': parseFloat(position)});
     },
 
 
+    zero_axis: function (axis) {this.set_position(axis, 0)},
+
+
     start_pause: function () {
       if (this.state.x == 'RUNNING') this.pause();
 
@@ -297,7 +317,8 @@ module.exports = {
 
 
     load_video: function () {
-      this.video_url = '//' + document.location.hostname + ':8000/stream/0';
+      this.video_url = '//' + document.location.hostname + ':8000/stream/0?=' +
+        Math.random();
     }
   }
 }
index 534428cff294e985d88a62f2d7410191cccd0942..adc7d1125400d5e32333e019449b5c99ffb6dc59 100644 (file)
@@ -21,6 +21,7 @@ module.exports = {
 
   events: {
     'input-changed': function() {
+      this.slave_update();
       this.$dispatch('config-changed');
       return false;
     }
@@ -32,6 +33,28 @@ module.exports = {
 
 
   methods: {
+    slave_update: function () {
+      var slave = false;
+      for (var i = 0; i < this.index; i++)
+        if (this.motor['axis'] == this.config.motors[i]['axis'])
+          slave = true;
+
+      var el = $(this.$el);
+
+      if (slave) {
+        el.find('.axis .units').text('(slave motor)');
+        el.find('.limits, .homing, .motion').find('input, select')
+          .attr('disabled', 1);
+        el.find('.power-mode select').attr('disabled', 1);
+        el.find('.motion .reverse input').removeAttr('disabled');
+
+      } else {
+        el.find('.axis .units').text('');
+        el.find('input,select').removeAttr('disabled');
+      }
+    },
+
+
     update: function () {
       if (!this.active) return;
 
@@ -46,6 +69,8 @@ module.exports = {
             if (!this.motor.hasOwnProperty(key))
               this.$set('motor["' + key + '"]',
                         template[category][key].default);
+
+        this.slave_update();
       }.bind(this));
     }
   }
diff --git a/src/js/spindle-view.js b/src/js/spindle-view.js
deleted file mode 100644 (file)
index 475b306..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-'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));
-    }
-  }
-}
diff --git a/src/js/tool-view.js b/src/js/tool-view.js
new file mode 100644 (file)
index 0000000..00fe6fe
--- /dev/null
@@ -0,0 +1,42 @@
+'use strict'
+
+
+module.exports = {
+  template: '#tool-view-template',
+  props: ['config', 'template'],
+
+
+  data: function () {
+    return {
+      tool: {}
+    }
+  },
+
+
+  events: {
+    'input-changed': function() {
+      this.$dispatch('config-changed');
+      return false;
+    }
+  },
+
+
+  ready: function () {
+    this.update();
+  },
+
+
+  methods: {
+    update: function () {
+      Vue.nextTick(function () {
+        if (this.config.hasOwnProperty('tool'))
+          this.tool = this.config.tool;
+
+        var template = this.template.tool;
+        for (var key in template)
+          if (!this.tool.hasOwnProperty(key))
+            this.$set('tool["' + key + '"]', template[key].default);
+      }.bind(this));
+    }
+  }
+}
index 1aa7cc217b9e0363443a9be9f5c2bdf6c3b02626..58ba6d52b773b5e37072444626c4af9c3cee6d95 100644 (file)
@@ -138,11 +138,7 @@ inline static uint16_t convert_voltage(uint16_t sample) {
 
 
 inline static uint16_t convert_current(uint16_t sample) {
-#define CR1 1000
-#define CR2 137
-
-  // TODO This isn't correct
-  return sample * (VREF / 1024.0 * (CR1 + CR2) / CR2 * 100);
+  return sample * (VREF / 1024.0 * 1970);
 }
 
 
@@ -272,6 +268,13 @@ int main() {
       OCR0A = 0; // 0% duty cycle
       _delay_ms(3000);
     }
+
+    if (10 < get_reg(MOTOR_REG)) {
+      OCR0A = 0; // 0% duty cycle
+      TCCR0A = 0;
+      GATE_DDR = 0;
+      _delay_ms(1000);
+    }
   }
 
   return 0;
index 8d94361200053965ce79eedc6dec75afc984ac9c..93810395af2ca6bcdbf185e581d5bad8b4cb42fe 100644 (file)
@@ -91,7 +91,6 @@ class AVR():
             self.stop();
             self.ctrl.config.config_avr()
             self._restore_machine_state()
-            self.report()
 
         except Exception as e:
             log.warning('Connect failed: %s', e)
@@ -129,6 +128,8 @@ class AVR():
 
                 self.queue_command('${}={}'.format(var, value))
 
+        self.queue_command('$$') # Refresh all vars, must come after above
+
 
     def report(self): self._i2c_command(I2C_REPORT)
 
@@ -246,6 +247,12 @@ class AVR():
                 return motor
 
 
+    def _is_axis_homed(self, axis):
+        motor = self._find_motor(axis)
+        if axis is None: return False
+        return self.vars['%dh' % motor]
+
+
     def _update_lcd(self, msg):
         if 'x' in msg or 'c' in msg:
             v = self.vars
@@ -346,4 +353,6 @@ class AVR():
 
     def set_position(self, axis, position):
         if self.stream is not None: raise Exception('Busy, cannot set position')
-        self.queue_command('G92 %c%f' % (axis, position))
+        if self._is_axis_homed('%c' % axis):
+            self.queue_command('G92 %c%f' % (axis, position))
+        else: self.queue_command('$%cp=%f' % (axis, position))
index d0e5c91a72758144c45eeeeaed1140da79ff9ce0..c377cae68d17e09c94f0256f6e3360d4c0787d7a 100644 (file)
@@ -13,11 +13,11 @@ default_config = {
         {"axis": "X"},
         {"axis": "Y"},
         {"axis": "Z"},
-        {"axis": "A"},
+        {"axis": "A", "power-mode" : "disabled"},
         ],
     "switches": {},
     "outputs": {},
-    "spindle": {},
+    "tool": {},
     "gcode": {},
     }
 
index c51eb23abe069348ffdacd0332bd45e78ac9e7a7..576f9590357cb7ab4bfe249899f5816492de93ab 100644 (file)
@@ -7,8 +7,14 @@ class FileHandler(bbctrl.APIHandler):
 
 
     def delete_ok(self, path):
-        path = 'upload' + path
-        if os.path.exists(path): os.unlink(path)
+        if not path:
+            if os.path.exists('upload'):
+                for path in os.listdir('upload'):
+                    if os.path.isfile('upload/' + path):
+                        os.unlink('upload/' + path)
+        else:
+            path = 'upload' + path
+            if os.path.exists(path): os.unlink(path)
 
 
     def put_ok(self, path):
index ac88ee4d283b51dbddbb65acdf56fd0efdf9020d..48e5de06dd9e8278ba70cf3fa7c69500c8c761f6 100644 (file)
@@ -8,11 +8,23 @@ class Jog(inevent.JogHandler):
         self.ctrl = ctrl
 
         config = {
-            "deadband": 0.1,
-            "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
-            "arrows": [ABS_HAT0X, ABS_HAT0Y],
-            "speed": [0x120, 0x121, 0x122, 0x123],
-            "activate": [0x124, 0x126, 0x125, 0x127],
+            "Logitech Logitech RumblePad 2 USB": {
+                "deadband": 0.1,
+                "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
+                "dir":  [1, -1, -1, 1],
+                "arrows": [ABS_HAT0X, ABS_HAT0Y],
+                "speed": [0x120, 0x121, 0x122, 0x123],
+                "lock": [0x124, 0x125],
+                },
+
+            "default": {
+                "deadband": 0.1,
+                "axes": [ABS_X, ABS_Y, ABS_RY, ABS_RX],
+                "dir":  [1, -1, -1, 1],
+                "arrows": [ABS_HAT0X, ABS_HAT0Y],
+                "speed": [0x133, 0x130, 0x131, 0x134],
+                "lock": [0x136, 0x137],
+                }
             }
 
         super().__init__(config)
@@ -48,5 +60,3 @@ class Jog(inevent.JogHandler):
         if self.speed == 4: scale = 1.0
 
         self.v = [x * scale for x in self.axes]
-        self.v[ABS_Y] = -self.v[ABS_Y]
-        self.v[ABS_Z] = -self.v[ABS_Z]
index bd4baa14de51dd074a745c1cb8c6f711baab6197..c5ddcf5f68e18180a7d1cc6a71bed13d2d5d7c6f 100644 (file)
@@ -77,4 +77,3 @@ class AbsAxisScaling(object):
     """
     return (float(value) - float(self.minimum)) / \
         float(self.maximum - self.minimum) * 2.0 - 1.0
-
index 97642503a559395b791df8c2743bfe1f5bd8fd2b..699c80b0662ec96ae215e9a6b2f466874523e369 100644 (file)
@@ -42,7 +42,7 @@ class EventHandler(object):
     self.buttons = dict()
 
 
-  def event(self, event, handler):
+  def event(self, event, handler, name):
     """
     Handles the given event.
 
@@ -80,7 +80,7 @@ class EventHandler(object):
     elif event.type == EV_ABS:
       state.abs[event.code] = event.stream.scale(event.code, event.value)
 
-    if handler: handler(event, state)
+    if handler: handler.event(event, state, name)
 
 
   def key_state(self, code):
index a69bb381cec5b7b670ab24f45889be693293295c..ba2709cf5a4276d9e3fcbf94a869c1ffe8334e87 100644 (file)
@@ -89,7 +89,7 @@ class EventStream(object):
     ABS_DISTANCE, ABS_TILT_X, ABS_TILT_Y, ABS_TOOL_WIDTH]
 
 
-  def __init__(self, devIndex, devType):
+  def __init__(self, devIndex, devType, devName):
     """
     Opens the given /dev/input/event file and grabs it.
 
@@ -97,6 +97,7 @@ class EventStream(object):
     """
     self.devIndex = devIndex
     self.devType = devType
+    self.devName = devName
     self.filename = "/dev/input/event" + str(devIndex)
     self.filehandle = os.open(self.filename, os.O_RDWR)
     self.state = EventState()
index 4a89e994311d3c6cae7dedbe0bd4a59eb746e2ff..400cea7f70fc3d16f6468ed696b29e73b01c25d3 100644 (file)
@@ -55,33 +55,6 @@ def key_to_code(key):
 def code_to_key(code): return CODE_KEY.get(code, '')
 
 
-def find_devices(types):
-  """Finds the event indices of all devices of the specified types.
-
-  A type is a string on the handlers line of /proc/bus/input/devices.
-  Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
-
-  Returns a list of integer indexes N, where /dev/input/eventN is the event
-  stream for each device.
-
-  If butNot is given it holds a list of tuples which the returned values should
-  not match.
-
-  All devices of each type are returned; if you have two mice, they will both
-  be used.
-  """
-  with open("/proc/bus/input/devices", "r") as filehandle:
-    for line in filehandle:
-      if line[0] == "H":
-        for type in types:
-          if type in line:
-            match = re.search("event([0-9]+)", line)
-            index = match and match.group(1)
-            if index: yield int(index), type
-            break
-
-
-
 class InEvent(object):
   """Encapsulates the entire InEvent subsystem.
 
@@ -124,17 +97,54 @@ class InEvent(object):
     self.handler = EventHandler()
     self.types = types
 
-    devs = list(find_devices(types))
-    for index, type in devs:
-      self.add_stream(index, type)
-
     self.udevCtx = pyudev.Context()
     self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx)
     self.udevMon.filter_by(subsystem = 'input')
+
+    devs = list(self.find_devices(types))
+    for index, type, name in devs:
+      self.add_stream(index, type, name)
+
     self.udevMon.start()
     ioloop.add_handler(self.udevMon.fileno(), self.udev_handler, ioloop.READ)
 
 
+  def get_dev(self, index):
+    return pyudev.Device.from_name(self.udevCtx, 'input', 'event%s' % index)
+
+  def get_dev_name(self, index):
+    dev = self.get_dev(index)
+    for name, value in dev.parent.attributes.items():
+      if name == 'name': return value.decode('utf-8')
+
+
+  def find_devices(self, types):
+    """Finds the event indices of all devices of the specified types.
+
+    A type is a string on the handlers line of /proc/bus/input/devices.
+    Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
+
+    Returns a list of integer indexes N, where /dev/input/eventN is the event
+    stream for each device.
+
+    If butNot is given it holds a list of tuples which the returned values
+    should not match.
+
+    All devices of each type are returned; if you have two mice, they will both
+    be used.
+    """
+    with open("/proc/bus/input/devices", "r") as filehandle:
+      for line in filehandle:
+        if line[0] == "H":
+          for type in types:
+            if type in line:
+              match = re.search("event([0-9]+)", line)
+              index = match and match.group(1)
+              if index:
+                yield int(index), type, self.get_dev_name(index)
+              break
+
+
   def process_udev_event(self):
     action, device = self.udevMon.receive_device()
     if device is None: return
@@ -145,9 +155,13 @@ class InEvent(object):
     devIndex = int(devIndex)
 
     if action == 'add':
-      for index, devType in find_devices(self.types):
+      devName = None
+      for name, value in device.attributes.items():
+        if name == 'name': devName = value
+
+      for index, devType, devName in self.find_devices(self.types):
         if index == devIndex:
-          self.add_stream(devIndex, devType)
+          self.add_stream(devIndex, devType, devName)
           break
 
     if action == 'remove':
@@ -159,7 +173,7 @@ class InEvent(object):
       if stream.filehandle == fd:
         while True:
           event = stream.next()
-          if event: self.handler.event(event, self.cb)
+          if event: self.handler.event(event, self.cb, stream.devName)
           else: break
 
 
@@ -167,15 +181,15 @@ class InEvent(object):
     self.process_udev_event()
 
 
-  def add_stream(self, devIndex, devType):
+  def add_stream(self, devIndex, devType, devName):
     try:
-      stream = EventStream(devIndex, devType)
+      stream = EventStream(devIndex, devType, devName)
       self.streams.append(stream)
 
       self.ioloop.add_handler(stream.filehandle, self.stream_handler,
                               self.ioloop.READ)
 
-      log.info('Added %s[%d]', devType, devIndex)
+      log.info('Added %s[%d] %s', devType, devIndex, devName)
 
     except OSError as e:
       log.warning('Failed to add %s[%d]: %s', devType, devIndex, e)
@@ -187,6 +201,7 @@ class InEvent(object):
         self.streams.remove(stream)
         self.ioloop.remove_handler(stream.filehandle)
         stream.release()
+        self.cb.clear()
 
         log.info('Removed %s[%d]', stream.devType, devIndex)
 
index 62023e54846d19e1cd2ce26e7453ebe8879245d2..47d9f2518b8e1a11a9fba6d8c80c5b7c7a22d07f 100644 (file)
@@ -32,9 +32,7 @@ def event_to_string(event, state):
 class JogHandler:
     def __init__(self, config):
         self.config = config
-        self.axes = [0.0, 0.0, 0.0, 0.0]
-        self.speed = 3
-        self.activate = 0
+        self.reset()
 
 
     def changed(self):
@@ -47,17 +45,35 @@ class JogHandler:
     def right(self): log.debug('right')
 
 
-    def __call__(self, event, state):
+    def reset(self):
+        self.axes = [0.0, 0.0, 0.0, 0.0]
+        self.speed = 3
+        self.vertical_lock = 0
+        self.horizontal_lock = 0
+
+
+    def clear(self):
+        self.reset()
+        self.changed()
+
+
+    def get_config(self, name):
+        if name in self.config: return self.config[name]
+        return self.config['default']
+
+
+    def event(self, event, state, dev_name):
         if event.type not in [EV_ABS, EV_REL, EV_KEY]: return
 
+        config = self.get_config(dev_name)
         changed = False
 
         # Process event
-        if event.type == EV_ABS and event.code in self.config['axes']:
+        if event.type == EV_ABS and event.code in config['axes']:
             pass
 
-        elif event.type == EV_ABS and event.code in self.config['arrows']:
-            axis = self.config['arrows'].index(event.code)
+        elif event.type == EV_ABS and event.code in config['arrows']:
+            axis = config['arrows'].index(event.code)
 
             if event.value < 0:
                 if axis == 1: self.up()
@@ -67,16 +83,19 @@ class JogHandler:
                 if axis == 1: self.down()
                 else: self.right()
 
-        elif event.type == EV_KEY and event.code in self.config['speed']:
+        elif event.type == EV_KEY and event.code in config['speed']:
             old_speed = self.speed
-            self.speed = self.config['speed'].index(event.code) + 1
+            self.speed = config['speed'].index(event.code) + 1
             if self.speed != old_speed: changed = True
 
-        elif event.type == EV_KEY and event.code in self.config['activate']:
-            index = self.config['activate'].index(event.code)
+        elif event.type == EV_KEY and event.code in config['lock']:
+            index = config['lock'].index(event.code)
 
-            if event.value: self.activate |= 1 << index
-            else: self.activate &= ~(1 << index)
+            self.horizontal_lock, self.vertical_lock = False, False
+
+            if event.value:
+                if index == 0: self.horizontal_lock = True
+                if index == 1: self.vertical_lock = True
 
         log.debug(event_to_string(event, state))
 
@@ -84,10 +103,16 @@ class JogHandler:
         old_axes = list(self.axes)
 
         for axis in range(4):
-            self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]]
-            if abs(self.axes[axis]) < self.config['deadband']:
+            self.axes[axis] = event.stream.state.abs[config['axes'][axis]]
+            self.axes[axis] *= config['dir'][axis]
+
+            if abs(self.axes[axis]) < config['deadband']:
                 self.axes[axis] = 0
-            if not (1 << axis) & self.activate and self.activate:
+
+            if self.horizontal_lock and axis not in [0, 3]:
+                self.axes[axis] = 0
+
+            if self.vertical_lock and axis not in [1, 2]:
                 self.axes[axis] = 0
 
         if old_axes != self.axes: changed = True
diff --git a/src/resources/config-defaults.json b/src/resources/config-defaults.json
deleted file mode 100644 (file)
index 8593c1d..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-  "motors": [
-    {
-      "axis": "X"
-    },
-    {
-      "axis": "Y"
-    },
-    {
-      "axis": "Z"
-    },
-    {
-      "axis": "A"
-    },
-  ],
-
-  "spindle": {},
-}
index f48a9c5eef6c5ff06f48174977371e9e44c6225c..cf472336ca17f2d034d0dc03e55ce97d1d87daca 100644 (file)
@@ -21,7 +21,7 @@
         "min": 0,
         "max": 8,
         "unit": "amps",
-        "default": 2,
+        "default": 1.5,
         "code": "dc"
       },
       "idle-current": {
     },
 
     "motion": {
-      "step-angle": {
-        "type": "float",
-        "min": 0,
-        "max": 360,
-        "step": 0.1,
-        "unit": "degrees",
-        "default": 1.8,
-        "code": "sa"
-      },
-      "travel-per-rev": {
-        "type": "float",
-        "unit": "mm",
-        "default": 5,
-        "code": "tr"
+      "reverse": {
+        "type": "bool",
+        "default": false,
+        "code": "rv"
       },
       "microsteps": {
         "type": "int",
         "default": 32,
         "code": "mi"
       },
-      "reverse": {
-        "type": "bool",
-        "default": false,
-        "code": "rv"
-      },
       "max-velocity": {
         "type": "float",
         "min": 0,
         "unit": "mm/min",
-        "default": 5000,
+        "default": 6000,
         "code": "vm"
       },
       "max-jerk": {
         "type": "float",
         "min": 0,
         "unit": "mm/min³",
-        "default": 10,
+        "default": 50,
         "code": "jm"
+      },
+      "step-angle": {
+        "type": "float",
+        "min": 0,
+        "max": 360,
+        "step": 0.1,
+        "unit": "degrees",
+        "default": 1.8,
+        "code": "sa"
+      },
+      "travel-per-rev": {
+        "type": "float",
+        "unit": "mm",
+        "default": 5,
+        "code": "tr"
       }
     },
 
     }
   },
 
-  "spindle": {
+  "tool": {
     "spindle-type": {
       "type": "enum",
       "values": ["HUANYANG", "PWM"],
index 39151540a78b71770732d17db02baa81e0c06796..c729d3f2f61dbed0195fbe2cc0467cf98f4d6b67 100644 (file)
@@ -5,7 +5,16 @@ body
   display none
 
 .button-success:not([disabled])
-  background rgb(28, 184, 65)
+  background-color #1cb841
+
+.button-error:not([disabled])
+  background-color #ca3c3c
+
+.button-warning:not([disabled])
+  background-color #df7514
+
+.button-secondary:not([disabled])
+  background-color #42b8dd
 
 .header, .content
   padding 0
@@ -55,6 +64,15 @@ body
 .success
   background green
 
+@keyframes attention
+  50%
+    opacity 0.5
+
+.attention
+    background-color #f5e138
+    color #000
+    animation attention 2s step-start 0s infinite
+
 .status
   color #eee
   text-align center
@@ -259,6 +277,7 @@ body
     clear right
 
   .override
+    display none /* Hidden for now */
     margin 0.5em 0
     white-space nowrap