- Fixed dwell (G4)
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Wed, 14 Feb 2018 00:39:15 +0000 (16:39 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Wed, 14 Feb 2018 00:39:15 +0000 (16:39 -0800)
 - Always show limit switch indicators regardless of motor enable
 - Fixed feed rate display
 - Added current GCode unit display
 - Fixed homed axis zeroing
 - Fixed probe pin input
 - Added reload button to video tab
 - Don't open error dialog on repeat messages
 - Handle large GCode files in browser
 - Added max lookahead limit to planner
 - Fixed GCode stopping/pausing where ramp down needs more than is in the queue
 - Added breakout box diagram to indicators
 - Initialize axes offsets to zero on startup
 - Fixed conflict between x state variable and x axis variable
 - Don't show ipv6 addresses on LCD.  They don't fit.

28 files changed:
CHANGELOG.md
package.json
src/avr/src/command.c
src/avr/src/exec.c
src/avr/src/exec.h
src/avr/src/jog.c
src/avr/src/state.c
src/avr/src/stepper.c
src/avr/src/vars.def
src/jade/templates/console.jade
src/jade/templates/control-view.jade
src/jade/templates/indicators.jade
src/js/app.js
src/js/console.js
src/js/control-view.js
src/js/gcode-view.js
src/py/bbctrl/AVR.py
src/py/bbctrl/Cmd.py
src/py/bbctrl/Config.py
src/py/bbctrl/Ctrl.py
src/py/bbctrl/Jog.py
src/py/bbctrl/Planner.py
src/py/bbctrl/State.py
src/py/inevent/EventStream.py
src/py/inevent/InEvent.py
src/resources/images/DB25_breakout_box.png [new file with mode: 0644]
src/resources/images/reload.png [new file with mode: 0644]
src/stylus/style.styl

index 745d8f5181c24543d35930f3502c6febd89c7562..00c77195831f5affe0f7543eb2deca2861d592ca 100644 (file)
@@ -1,6 +1,23 @@
 Buildbotics CNC Controller Firmware Change Log
 ==============================================
 
+## v0.3.5
+ - Fixed dwell (G4)
+ - Always show limit switch indicators regardless of motor enable
+ - Fixed feed rate display
+ - Added current GCode unit display
+ - Fixed homed axis zeroing
+ - Fixed probe pin input
+ - Added reload button to video tab
+ - Don't open error dialog on repeat messages
+ - Handle large GCode files in browser
+ - Added max lookahead limit to planner
+ - Fixed GCode stopping/pausing where ramp down needs more than is in the queue
+ - Added breakout box diagram to indicators
+ - Initialize axes offsets to zero on startup
+ - Fixed conflict between ``x`` state variable and ``x`` axis variable
+ - Don't show ipv6 addresses on LCD.  They don't fit.
+
 ## v0.3.4
  - Added alternate units for motor parameters
  - Automatic config file upgrading
index 8cbb856314727fa09168d197de5163b5c150dbbd..fc0d42f2d9ede1323cfd14d4df1a17e9f53652f7 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "bbctrl",
-  "version": "0.3.4",
+  "version": "0.3.5",
   "homepage": "http://buildbotics.com/",
   "repository": "https://github.com/buildbotics/bbctrl-firmware",
   "license": "GPL-3.0+",
index 4fcea2c2d727186db9c559d2d62a050e23771e7f..ed446c4c5527664dabefedcb7ad1e57e2c8012e6 100644 (file)
@@ -244,6 +244,7 @@ void command_reset_position() {
 bool command_exec() {
   if (!cmd.count) {
     cmd.last_empty = rtc_get_time();
+    exec_set_velocity(0);
     state_idle();
     return false;
   }
index 5315a242b74b20aadabbf06eed75326dd227f290..3aec68bd3c6af31d056e7fbc1652c06277554839 100644 (file)
@@ -47,7 +47,6 @@ static struct {
   float jerk;
 
   int tool;
-  int line;
 
   float feed_override;
   float spindle_override;
@@ -75,8 +74,6 @@ float exec_get_velocity() {return ex.velocity;}
 void exec_set_acceleration(float a) {ex.accel = a;}
 float exec_get_acceleration() {return ex.accel;}
 void exec_set_jerk(float j) {ex.jerk = j;}
-void exec_set_line(int32_t line) {ex.line = line;}
-int32_t exec_get_line() {return ex.line;}
 void exec_set_cb(exec_cb_t cb) {ex.cb = cb;}
 
 
@@ -107,7 +104,6 @@ stat_t exec_next() {
 
 
 // Variable callbacks
-int32_t get_line() {return ex.line;}
 uint8_t get_tool() {return ex.tool;}
 float get_velocity() {return ex.velocity / VELOCITY_MULTIPLIER;}
 float get_acceleration() {return ex.accel / ACCEL_MULTIPLIER;}
@@ -116,7 +112,6 @@ float get_feed_override() {return ex.feed_override;}
 float get_speed_override() {return ex.spindle_override;}
 float get_axis_position(int axis) {return ex.position[axis];}
 
-void set_line(int32_t line) {ex.line = line;}
 void set_tool(uint8_t tool) {ex.tool = tool;}
 void set_feed_override(float value) {ex.feed_override = value;}
 void set_speed_override(float value) {ex.spindle_override = value;}
index 03f7b7f0b1b96ec1b63e091df73202ef78a61a1f..e512b1407dbc946d124a6799c6fefe9a752917c5 100644 (file)
@@ -47,8 +47,6 @@ float exec_get_velocity();
 void exec_set_acceleration(float a);
 float exec_get_acceleration();
 void exec_set_jerk(float j);
-void exec_set_line(int32_t line);
-int32_t exec_get_line();
 
 void exec_set_cb(exec_cb_t cb);
 
index a5faacd31a889b106a68156e6f990334194c8448..bc5705b29dda45057faa88518d47e0bea803b853 100644 (file)
@@ -60,9 +60,6 @@ typedef struct {
   bool writing;
   bool done;
 
-  float Vi;
-  float Vt;
-
   jog_axis_t axes[AXES];
 } jog_runtime_t;
 
index 4d3649562d3884f5925adfee3475f5a9a586db3b..68fafb8a6e50cedbb4b828aa421215c74bca1023 100644 (file)
@@ -85,7 +85,6 @@ state_t state_get() {return s.state;}
 static void _set_state(state_t state) {
   if (s.state == state) return; // No change
   if (s.state == STATE_ESTOPPED) return; // Can't leave EStop state
-  if (state == STATE_READY) exec_set_line(0);
   s.state = state;
   report_request();
 }
index 11b8677caf7a2d156c0d5455f36fd0e0855f77f7..abaa9c463d7dd715310e719bbd168b793c0cd4fc 100644 (file)
@@ -115,7 +115,7 @@ ISR(STEP_LOW_LEVEL_ISR) {
     case STAT_NOP: st.busy = false;  break; // No command executed
     case STAT_AGAIN: continue;              // No command executed, try again
 
-    case STAT_OK:                            // Move executed
+    case STAT_OK:                           // Move executed
       if (!st.move_queued) ALARM(STAT_EXPECTED_MOVE); // No move was queued
       st.move_queued = false;
       st.move_ready = true;
index 2a12f8bcfc15c1ad1f3c5fe38b2429603da2ef7e..fe38672864e63e835d82d3fada2d5f580b4bf21e 100644 (file)
@@ -99,7 +99,6 @@ VAR(hy_connected,    he, bool,  0,      0, 1, "Huanyang connected")
 
 // Machine state
 VAR(id,              id, u32,   0,      1, 1, "Last executed command ID")
-VAR(line,            ln, s32,   0,      1, 1, "Last line executed")
 VAR(speed,           s,  f32,   0,      1, 1, "Current spindle speed")
 VAR(tool,            t,  u8,    0,      1, 1, "Current tool")
 VAR(feed_override,   fo, f32,   0,      1, 1, "Feed rate override")
index ec86cba119d3e3b26d5f951d28cd86efa750fb67..e546e2ed151843a82e521abc732831a904f8a3c1 100644 (file)
@@ -12,7 +12,7 @@ script#console-template(type="text/x-template")
         th Repeat
         th Message
 
-      tr(v-for="msg in messages.reverse()", :class="msg.level || 'info'")
+      tr(v-for="msg in messages", :class="msg.level || 'info'")
         td {{msg.level  || 'info'}}
         td {{msg.source || ''}}
         td {{msg.where  || ''}}
index 8d741ffb124729157161a5f175d1822b55b84c80..0e8ddcc7516fb97f1d4831a2dc402f45f260174a 100644 (file)
@@ -17,16 +17,16 @@ script#control-view-template(type="text/x-template")
           td.absolute {{state.#{axis}p || 0 | fixed 3}}
           td.offset {{get_offset('#{axis}') | fixed 3}}
           th.actions
-            button.pure-button(:disabled="state.x != 'READY'",
+            button.pure-button(:disabled="state.xx != 'READY'",
               title="Set {{'#{axis}' | upper}} axis position.",
               @click="show_set_position('#{axis}')")
               .fa.fa-cog
 
-            button.pure-button(:disabled="state.x != 'READY'",
+            button.pure-button(:disabled="state.xx != 'READY'",
               title="Zero {{'#{axis}' | upper}} axis offset.",
               @click="zero_axis('#{axis}')") &empty;
 
-            button.pure-button(:disabled="state.x != 'READY'",
+            button.pure-button(:disabled="state.xx != 'READY'",
               title="Home {{'#{axis}' | upper}} axis.",
               @click="home('#{axis}')")
               .fa.fa-home
@@ -80,18 +80,18 @@ script#control-view-template(type="text/x-template")
         th Message
         td.reason(:class="{attention: highlight_reason()}") {{get_reason()}}
         td
+      tr
+        th Units
+        td {{state.imperial ? 'IMPERIAL' : 'METRIC'}}
+        td
       tr
         th Feed
-        td {{state.f || 0 | fixed 0}}
+        td {{state.feed || 0 | fixed 0}}
         td mm/min
       tr
         th Speed
-        td {{state.s || 0 | fixed 0}}
+        td {{state.speed || 0 | fixed 0}}
         td RPM
-      tr
-        th Direction
-        td {{state.ss || 'Off'}}
-        td
 
     table.info
       tr
@@ -100,19 +100,19 @@ script#control-view-template(type="text/x-template")
         td m/min
       tr
         th Line
-        td {{0 <= state.ln ? state.ln : '-'}}
+        td {{0 <= state.line ? state.line : '-'}}
         td
       tr
         th Tool
-        td {{state.t || 0}}
+        td {{state.tool || 0}}
         td
       tr
         th Mist
-        td {{state.mist || 'Off'}}
+        td {{state.mist ? 'On' : 'Off'}}
         td
       tr
         th Coolant
-        td {{state.coolant || 'Off'}}
+        td {{state.coolant ? 'On' : 'Off'}}
         td
 
     .override(title="Feed rate override.")
@@ -130,16 +130,16 @@ script#control-view-template(type="text/x-template")
 
     .toolbar
       button.pure-button(title="Home the machine.", @click="home()",
-        :disabled="state.x != 'READY'")
+        :disabled="state.xx != 'READY'")
         .fa.fa-home
 
       button.pure-button(
-        title="{{state.x == 'RUNNING' ? 'Pause' : 'Start'}} program.",
+        title="{{state.xx == 'RUNNING' ? 'Pause' : 'Start'}} program.",
         @click="start_pause", :disabled="!file")
-        .fa(:class="state.x == 'RUNNING' ? 'fa-pause' : 'fa-play'")
+        .fa(:class="state.xx == 'RUNNING' ? 'fa-pause' : 'fa-play'")
 
       button.pure-button(title="Stop program.", @click="stop",
-        :disabled="state.x == 'READY'")
+        :disabled="state.xx == 'READY'")
         .fa.fa-stop
 
       button.pure-button(title="Pause program at next optional stop (M1).",
@@ -147,7 +147,7 @@ script#control-view-template(type="text/x-template")
         .fa.fa-stop-circle-o
 
       button.pure-button(title="Execute one program step.", @click="step",
-        :disabled="(state.x != 'READY' && state.x != 'HOLDING') || !file")
+        :disabled="(state.xx != 'READY' && state.xx != 'HOLDING') || !file")
         .fa.fa-step-forward
 
     .tabs
@@ -172,7 +172,7 @@ script#control-view-template(type="text/x-template")
       section#content1.tab-content
         .toolbar
           button.pure-button(title="Upload a new GCode program.", @click="open",
-            :disabled="state.x == 'RUNNING' || state.x == 'STOPPING'")
+            :disabled="state.xx == 'RUNNING' || state.xx == 'STOPPING'")
             .fa.fa-folder-open
 
           input.gcode-file-input(type="file", @change="upload",
@@ -196,15 +196,15 @@ script#control-view-template(type="text/x-template")
 
           select(title="Select previously uploaded GCode programs.",
             v-model="file", @change="load",
-            :disabled="state.x == 'RUNNING' || state.x == 'STOPPING'")
+            :disabled="state.xx == 'RUNNING' || state.xx == 'STOPPING'")
             option(v-for="file in files", :value="file") {{file}}
 
-        .gcode(:class="{placeholder: !gcode}")
+        .gcode(:class="{placeholder: !gcode}", @scroll="gcode_scroll")
           span(v-if="!gcode.length") GCode displays here.
           ul
             li(v-for="item in gcode", id="gcode-line-{{$index}}",
                track-by="$index")
-              span {{$index + 1}}
+              span {{$index + 1 + gcode_offset}}
               | {{item}}
 
       section#content2.tab-content
@@ -248,4 +248,6 @@ script#control-view-template(type="text/x-template")
 
       section#content6.tab-content
         .video
+          img.reload(src="/images/reload.png", @click="load_video",
+            title="Reload video")
           img.mjpeg(:src="video_url")
index 8d94da37e33c2b4093d2ed397acc8d758c89a41c..36f9ea7b171ab6ef32429f98cee886dedb3af150 100644 (file)
@@ -2,33 +2,34 @@ script#indicators-template(type="text/x-template")
   .indicators
     table.inputs
       tr
-        th(colspan=6) Switch Inputs
+        th.header(colspan=7) Inputs
       tr
         th
         th Pin
         th Name
+        th.separator
         th
         th Pin
         th Name
 
-      each motor in '01234'
-        tr(v-if="is_motor_enabled(#{motor})")
+      each motor in '0123'
+        tr
           td
             .fa(:class="get_class('#{motor}lw')")
           th {{get_min_pin(#{motor})}}
-          th #{motor} Min
-
+          th Motor #{motor} Min
+          th.separator
           td
             .fa(:class="get_class('#{motor}xw')")
           th {{get_max_pin(#{motor})}}
-          th #{motor} Max
+          th Motor #{motor} Max
 
       tr
         td
           .fa(:class="get_class('ew')")
         th 23
         th EStop
-
+        th.separator
         td
           .fa(:class="get_class('pw')")
         th 22
@@ -36,12 +37,13 @@ script#indicators-template(type="text/x-template")
 
     table.outputs
       tr
-        th(colspan=6) Outputs
+        th.header(colspan=7) Outputs
 
       tr
         th
         th Pin
         th Name
+        th.separator
         th
         th Pin
         th Name
@@ -51,7 +53,7 @@ script#indicators-template(type="text/x-template")
           .fa(:class="get_class('eos')")
         th 15
         th Tool Enable
-
+        th.separator
         td
           .fa(:class="get_class('1os')")
         th 2
@@ -62,7 +64,7 @@ script#indicators-template(type="text/x-template")
           .fa(:class="get_class('dos')")
         th 16
         th Tool Direction
-
+        th.separator
         td
           .fa(:class="get_class('2os')")
         th 1
@@ -72,7 +74,7 @@ script#indicators-template(type="text/x-template")
         td {{state['pd'] | percent 0}}
         th 17
         th Tool PWM
-
+        th.separator
         td
           .fa(:class="get_class('fos')")
         th 21
@@ -80,68 +82,69 @@ script#indicators-template(type="text/x-template")
 
     table.measurements
       tr
-        th(colspan=4) Measurements
+        th.header(colspan=5) Measurements
 
       tr
         td {{state['vin'] | fixed 1}} V
         th Input
-
+        th.separator
         td {{state['vout'] | fixed 1}} V
         th Output
 
       tr
         td {{state['motor'] | fixed 2}} A
         th Motor
-
+        th.separator
         td {{state['temp'] | fixed 0}} â„ƒ
         th Temp
 
       tr
         td {{state['load1'] | fixed 2}} A
         th Load 1
-
+        th.separator
         td {{state['load2'] | fixed 2}} A
         th Load 2
 
     table.rs485
       tr
-        th(colspan=4) RS485 Spindle
+        th.header(colspan=5) RS485 Spindle
 
       tr
-        th(colspan=4) {{state['he'] ? "" : "Not "}}Connected
+        th(colspan=5) {{state['he'] ? "" : "Not "}}Connected
 
       tr
         td {{state['hz']}} Hz
         th Frequency
-
+        th.separator
         td {{state['hc']}} A
         th Current
 
       tr
         td {{state['hr']}} RPM
         th Speed
-
+        th.separator
         td {{state['ht']}} â„ƒ
         th Temp
 
     table.legend
       tr
-        th(colspan=10) Legend
+        th.header(colspan=10) Legend
 
       tr
         td
          .fa.fa-circle.logic-hi
         th Logic Hi
 
-      tr
         td
          .fa.fa-circle.logic-lo
         th Logic Lo
 
-      tr
         td
          .fa.fa-circle-o.logic-tri
         th Tristated / Disabled
 
+    h2 DB25 breakout box
+    img(width=700, src="/images/DB25_breakout_box.png")
+
     h2 DB25-M2 breakout
-    center: img(width=400, src="/images/DB25-M2_breakout.png")
+    img(width=400, src="/images/DB25-M2_breakout.png")
index 58947ad01ac817e32efeed47bc7a85df976a2aef..5d39577899443479a716b5cdade9379c4ae2dfa7 100644 (file)
@@ -109,7 +109,7 @@ module.exports = new Vue({
 
   methods: {
     estop: function () {
-      if (this.state.x == 'ESTOPPED') api.put('clear');
+      if (this.state.xx == 'ESTOPPED') api.put('clear');
       else api.put('estop');
     },
 
index d8cb148c96d3f7f46019df7eaeff7d0418d138e7..62b90eb767d9e812dbbd90f8ab64c4cb33e5d39a 100644 (file)
@@ -31,12 +31,11 @@ module.exports = {
       msg.level = msg.level || 'info';
 
       // Add to message log and count and collapse repeats
-      if (messages.length && _msg_equal(msg, messages[messages.length - 1]))
-        messages[messages.length - 1].repeat++;
-
+      var repeat = messages.length && _msg_equal(msg, messages[0]);
+      if (repeat) messages[0].repeat++;
       else {
         msg.repeat = 1;
-        messages.push(msg);
+        messages.unshift(msg);
       }
 
       // Write message to browser console for debugging
@@ -47,7 +46,7 @@ module.exports = {
       else console.log(text);
 
       // Event on errors
-      if (msg.level == 'error' || msg.level == 'critical')
+      if (!repeat && (msg.level == 'error' || msg.level == 'critical'))
         this.$dispatch('error', msg);
     }
   },
index 32a647d53a3aa80d8dcf9f2417e0436f02f90980..4e7005ea322680da5ab147461bfdacae15490899 100644 (file)
@@ -2,6 +2,9 @@
 
 var api = require('./api');
 
+var maxLines = 1000;
+var pageSize = Math.round(maxLines / 10);
+
 
 function _is_array(x) {
   return Object.prototype.toString.call(x) === '[object Array]';
@@ -26,7 +29,9 @@ module.exports = {
       last_file: '',
       files: [],
       axes: 'xyzabc',
+      all_gcode: [],
       gcode: [],
+      gcode_offset: 0,
       history: [],
       speed_override: 1,
       feed_override: 1,
@@ -46,7 +51,7 @@ module.exports = {
 
 
   watch: {
-    'state.ln': function () {this.update_gcode_line();}
+    'state.line': function () {this.update_gcode_line();}
   },
 
 
@@ -63,12 +68,12 @@ module.exports = {
 
 
   methods: {
-    get_state: function () {return this.state.x || ''},
+    get_state: function () {return this.state.xx || ''},
 
 
     get_reason: function () {
-      if (this.state.x == 'ESTOPPED') return this.state.er;
-      if (this.state.x == 'HOLDING') return this.state.pr;
+      if (this.state.xx == 'ESTOPPED') return this.state.er;
+      if (this.state.xx == 'HOLDING') return this.state.pr;
       return '';
     },
 
@@ -119,20 +124,80 @@ module.exports = {
     },
 
 
+    gcode_move_up: function (count) {
+      var lines = 0;
+
+      for (var i = 0; i < count; i++) {
+        if (!this.gcode_offset) break;
+
+        this.gcode.unshift(this.all_gcode[this.gcode_offset - 1])
+        this.gcode.pop();
+        this.gcode_offset--;
+        lines++;
+      }
+
+      return lines;
+    },
+
+
+    gcode_move_down: function (count) {
+      var lines = 0;
+
+      for (var i = 0; i < count; i++) {
+        if (this.all_gcode.length <= this.gcode_offset + this.gcode.length)
+          break;
+
+        this.gcode.push(this.all_gcode[this.gcode_offset + this.gcode.length])
+        this.gcode.shift();
+        this.gcode_offset++;
+        lines++
+      }
+
+      return lines;
+    },
+
+
+    gcode_scroll: function (e) {
+      if (this.gcode.length == this.all_gcode.length) return;
+
+      var t = e.target;
+      var percentScroll = t.scrollTop / (t.scrollHeight - t.clientHeight);
+
+      var lines = 0;
+      if (percentScroll < 0.2) lines = this.gcode_move_up(pageSize);
+      else if (0.8 < percentScroll) lines = -this.gcode_move_down(pageSize);
+      else return;
+
+      if (lines) t.scrollTop += t.scrollHeight * lines / maxLines;
+    },
+
+
     update_gcode_line: function () {
       if (typeof this.last_line != 'undefined') {
         $('#gcode-line-' + this.last_line).removeClass('highlight');
         this.last_line = undefined;
       }
 
-      if (0 <= this.state.ln) {
-        var line = this.state.ln - 1;
-        var e = $('#gcode-line-' + line);
-        if (e.length)
-          e.addClass('highlight')[0]
-          .scrollIntoView({behavior: 'smooth'});
+      if (0 <= this.state.line) {
+        var line = this.state.line - 1;
+
+        // Make sure the current GCode is loaded
+        if (line < this.gcode_offset ||
+            this.gcode_offset + this.gcode.length <= line) {
+          this.gcode_offset = line - pageSize;
+          if (this.gcode_offset < 0) this.gcode_offset = 0;
 
-        this.last_line = line;
+          this.gcode = this.all_gcode.slice(this.gcode_offset, maxLines);
+        }
+
+        Vue.nextTick(function () {
+          var e = $('#gcode-line-' + line);
+          if (e.length)
+            e.addClass('highlight')[0]
+            .scrollIntoView({behavior: 'smooth'});
+
+          this.last_line = line;
+        }.bind(this));
       }
     },
 
@@ -190,6 +255,7 @@ module.exports = {
 
       if (!file || this.files.indexOf(file) == -1) {
         this.file = '';
+        this.all_gcode = [];
         this.gcode = [];
         return;
       }
@@ -198,7 +264,9 @@ module.exports = {
 
       api.get('file/' + file)
         .done(function (data) {
-          this.gcode = data.trimRight().split(/\r?\n/);
+          this.all_gcode = data.trimRight().split(/\r?\n/);
+          this.gcode = this.all_gcode.slice(0, maxLines);
+          this.gcode_offset = 0;
           this.last_file = file;
 
           Vue.nextTick(this.update_gcode_line);
@@ -254,9 +322,9 @@ module.exports = {
 
 
     start_pause: function () {
-      if (this.state.x == 'RUNNING') this.pause();
+      if (this.state.xx == 'RUNNING') this.pause();
 
-      else if (this.state.x == 'STOPPING' || this.state.x == 'HOLDING')
+      else if (this.state.xx == 'STOPPING' || this.state.xx == 'HOLDING')
         this.unpause();
 
       else this.start();
index b12d5f8588f01d8329204cb8639fab5fc882f060..bdd66f9dbe70b71a294f2c63707139ba93c1d7ac 100644 (file)
@@ -21,9 +21,7 @@ module.exports = {
   },
 
 
-  ready: function () {
-    this.update();
-  },
+  ready: function () {this.update()},
 
 
   methods: {
index 478d9bf15aaafd1e39530d012e698c45645249c4..34bc64e98bbc2c8a72e40aa01ea951fa948cffaf 100644 (file)
@@ -39,6 +39,7 @@ class AVR():
         self.queue = deque()
         self.in_buf = ''
         self.command = None
+        self.stopping = False
 
         self.lcd_page = ctrl.lcd.add_new_page()
         self.install_page = True
@@ -195,6 +196,13 @@ class AVR():
                 log.warning('firmware rebooted')
                 self.connect()
 
+            if self.stopping and 'xx' in update and update['xx'] == 'HOLDING':
+                self._stop_sending_gcode()
+                # Resume once current queue of GCode commands has flushed
+                self._i2c_command(Cmd.FLUSH)
+                self._queue_command(Cmd.RESUME)
+                self.stopping = False
+
             self.ctrl.state.update(update)
 
             # Must be after AVR vars have loaded
@@ -204,15 +212,15 @@ class AVR():
 
 
     def _update_state(self, update):
-        if 'x' in update and update['x'] == 'ESTOPPED':
+        if 'xx' in update and update['xx'] == 'ESTOPPED':
             self._stop_sending_gcode()
 
         self._update_lcd(update)
 
 
     def _update_lcd(self, update):
-        if 'x' in update:
-            self.lcd_page.text('%-9s' % self.ctrl.state.get('x'), 0, 0)
+        if 'xx' in update:
+            self.lcd_page.text('%-9s' % self.ctrl.state.get('xx'), 0, 0)
 
         # Show enabled axes
         row = 0
@@ -236,7 +244,9 @@ class AVR():
     def connect(self):
         try:
             # Reset AVR communication
-            self.stop();
+            self._stop_sending_gcode()
+            # Resume once current queue of GCode commands has flushed
+            self._queue_command(Cmd.RESUME)
             self._queue_command('h') # Load AVR commands and variables
 
         except Exception as e:
@@ -282,19 +292,19 @@ class AVR():
         if self._is_busy(): raise Exception('Busy, cannot home')
 
         if position is not None:
-            self.ctrl.planner.mdi('G28.3 %c%f' % (axis, position))
+            self.mdi('G28.3 %c%f' % (axis, position))
 
         else:
             if axis is None: axes = 'zxyabc' # TODO This should be configurable
             else: axes = '%c' % axis
 
             for axis in axes:
-                if not self.ctrl.state.axis_can_home(axis): continue
+                if not self.ctrl.state.axis_can_home(axis):
+                    log.info('Cannot home ' + axis)
+                    continue
 
                 log.info('Homing %s axis' % axis)
-                gcode = axis_homing_procedure % {'axis': axis}
-                self.ctrl.planner.mdi(gcode)
-                self._set_write(True)
+                self.mdi(axis_homing_procedure % {'axis': axis})
 
 
     def estop(self): self._i2c_command(Cmd.ESTOP)
@@ -308,22 +318,20 @@ class AVR():
     def step(self, path):
         self._i2c_command(Cmd.STEP)
         if not self._is_busy() and path and \
-                self.ctrl.state.get('x', '') == 'READY':
+                self.ctrl.state.get('xx', '') == 'READY':
             self._start_sending_gcode(path)
 
 
     def stop(self):
-        self._i2c_command(Cmd.FLUSH)
-        self._stop_sending_gcode()
-        # Resume processing once current queue of GCode commands has flushed
-        self._queue_command(Cmd.RESUME)
+        self.pause()
+        self.stopping = True
 
 
     def pause(self): self._i2c_command(Cmd.PAUSE, byte = 0)
 
 
     def unpause(self):
-        if self.ctrl.state.get('x', '') != 'HOLDING' or not self._is_busy():
+        if self.ctrl.state.get('xx', '') != 'HOLDING' or not self._is_busy():
             return
 
         self._i2c_command(Cmd.FLUSH)
@@ -340,5 +348,5 @@ class AVR():
         if self._is_busy(): raise Exception('Busy, cannot set position')
 
         if self.ctrl.state.is_axis_homed('%c' % axis):
-            self.ctrl.planner.mdi('G92 %c%f' % (axis, position))
+            self.mdi('G92 %c%f' % (axis, position))
         else: self._queue_command('$%cp=%f' % (axis, position))
index c35f7408b4bfd345fec087ee20ca5c21dfc85c85..43c87b92853d5d214967e23eea84c2c39333701d 100755 (executable)
@@ -45,11 +45,11 @@ def encode_axes(axes):
     return data
 
 
-def line_number(line): return '#ln=%d' % line
+def set(name, value): return '#%s=%s' % (name, value)
 
 
-def line(id, target, exitVel, maxAccel, maxJerk, times):
-    cmd = '#id=%u\n%c' % (id, LINE)
+def line(target, exitVel, maxAccel, maxJerk, times):
+    cmd = LINE
 
     cmd += encode_float(exitVel)
     cmd += encode_float(maxAccel)
index 5ac4f8f0523b4cba60040ece5feb4a8b8f2dd52b..11e8239f19f79eaaf762c8fa0a410ef633e43eca 100644 (file)
@@ -19,6 +19,7 @@ default_config = {
     "outputs": {},
     "tool": {},
     "gcode": {},
+    "planner": {},
     "admin": {},
     }
 
index 7a228178fcefffa334238b5c96918c44dde08874..94bb9bbc0f995ab3d98acc242f774d8ea73fd322 100644 (file)
@@ -20,7 +20,8 @@ class IPPage(bbctrl.LCDPage):
         self.text('Host: %s' % hostname[0:14], 0, 0)
 
         for i in range(min(3, len(ips))):
-            self.text('IP: %s' % ips[i], 0, i + 1)
+            if len(ips[i]) <= 16:
+                self.text('IP: %s' % ips[i], 0, i + 1)
 
 
     def activate(self): self.update()
index 6740b4d6d344d450a30754f1128df803b722cad9..c3818534f195f83940d3e34e40f6e367775cd419 100644 (file)
@@ -37,7 +37,7 @@ class Jog(inevent.JogHandler):
         self.callback()
 
         self.processor = inevent.InEvent(ctrl.ioloop, self,
-                                         types = "js kbd".split())
+                                         types = 'js kbd'.split())
 
 
     def up(self): self.ctrl.lcd.page_up()
index d7bcd4318773ecce6a67383dbf6f003854fb2185..cd9a9872c6d20a68e4f431eef6c94cba0fc92ccc 100644 (file)
@@ -1,6 +1,7 @@
 import json
 import re
 import logging
+from collections import deque
 import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
 import bbctrl.Cmd as Cmd
 
@@ -15,6 +16,7 @@ class Planner():
         self.ctrl = ctrl
         self.lastID = -1
         self.mode = 'idle'
+        self.setq = deque()
 
         ctrl.state.add_listener(self.update)
 
@@ -65,14 +67,36 @@ class Planner():
 
 
     def update(self, update):
-        if 'id' in update: self.planner.set_active(update['id'])
+        if 'id' in update:
+            id = update['id']
+            self.planner.set_active(id)
 
-        if self.ctrl.state.get('x', '') == 'HOLDING' and \
+            # Syncronize planner variables with execution id
+            self.release_set_cmds(id)
+
+        # Automatically unpause on seek hold
+        if self.ctrl.state.get('xx', '') == 'HOLDING' and \
                 self.ctrl.state.get('pr', '') == 'Switch found' and \
                 self.planner.is_synchronizing():
             self.ctrl.avr.unpause()
 
 
+    def release_set_cmds(self, id):
+        self.lastID = id
+
+        # Apply all set commands <= to ID and those that follow consecutively
+        while len(self.setq) and self.setq[0][0] - 1 <= self.lastID:
+            id, name, value = self.setq.popleft()
+            self.ctrl.state.set(name, value)
+            if id == self.lastID + 1: self.lastID = id
+
+
+    def queue_set_cmd(self, id, name, value):
+        log.info('Planner set(#%d, %s, %s)', id, name, value)
+        self.setq.append((id, name, value))
+        self.release_set_cmds(self.lastID)
+
+
     def restart(self):
         state = self.ctrl.state
         id = state.get('id')
@@ -136,20 +160,21 @@ class Planner():
         self.planner = gplan.Planner()
         self.planner.set_resolver(self.get_var)
         self.planner.set_logger(self.log, 1, 'LinePlanner:3')
+        self.setq.clear()
 
 
-    def encode(self, block):
+    def _encode(self, block):
         type = block['type']
 
         if type == 'line':
-            return Cmd.line(block['id'], block['target'], block['exit-vel'],
+            return Cmd.line(block['target'], block['exit-vel'],
                             block['max-accel'], block['max-jerk'],
                             block['times'])
 
         if type == 'set':
             name, value = block['name'], block['value']
 
-            if name == 'line': return Cmd.line_number(value)
+            if name == 'line': self.queue_set_cmd(block['id'], name, value)
             if name == 'tool': return Cmd.tool(value)
             if name == 'speed': return Cmd.speed(value)
             if name[0:1] == '_' and name[1:2] in 'xyzabc' and \
@@ -157,7 +182,7 @@ class Planner():
                 return Cmd.set_position(name[1], value)
 
             if len(name) and name[0] == '_':
-                self.ctrl.state.set(name[1:], value)
+                self.queue_set_cmd(block['id'], name[1:], value)
 
             return
 
@@ -172,13 +197,17 @@ class Planner():
         raise Exception('Unknown planner type "%s"' % type)
 
 
+    def encode(self, block):
+        cmd = self._encode(block)
+        if cmd is not None: return Cmd.set('id', block['id']) + '\n' + cmd
+
+
     def has_move(self): return self.planner.has_more()
 
 
     def next(self):
         while self.planner.has_more():
             cmd = self.planner.next()
-            self.lastID = cmd['id']
             cmd = self.encode(cmd)
             if cmd is not None: return cmd
 
index d98e0fddefae0e7c4ba0f61d40982ac8d80b1dd8..523e4889adf6fa611b0ffdf2d896463b8d241a2a 100644 (file)
@@ -6,7 +6,7 @@ import bbctrl
 log = logging.getLogger('State')
 
 
-machine_state_vars = 'xp yp zp ap bp cp t fo so'.split()
+machine_state_vars = '''xp yp zp ap bp cp t fo so'''.split()
 
 
 class State(object):
@@ -33,6 +33,12 @@ class State(object):
             # Set not homed
             self.set('%dhomed' % i, False)
 
+        # Zero offsets
+        for axis in 'xyzabc': self.vars['offset_' + axis] = 0
+
+        # No line
+        self.vars['line'] = -1
+
 
     def _notify(self):
         if not self.changes: return
index 404815e03205dcb5450e599d82ad6fa6efb6c393..90fb8b2810f3186dd72df039d5cae8a2fb7a1a5c 100644 (file)
@@ -168,7 +168,7 @@ class EventStream(object):
         return event
 
     except Exception as e:
-      log.warning('Reading event: %s' % e)
+      log.info('Reading event: %s' % e)
 
 
   def __enter__(self): return self
index 8f01355e83edadbf2261be87b64095a4a048bb4c..df0822a0d445c34d2e0987a6a13d1893c3799d03 100644 (file)
@@ -90,7 +90,7 @@ class InEvent(object):
   The keys are listed in inevent.Constants.py or /usr/include/linux/input.h
   Note that the key names refer to a US keyboard.
   """
-  def __init__(self, ioloop, cb, types = ["kbd", "mouse", "js"]):
+  def __init__(self, ioloop, cb, types = 'kbd mouse js'.split()):
     self.ioloop = ioloop
     self.cb = cb
     self.streams = []
diff --git a/src/resources/images/DB25_breakout_box.png b/src/resources/images/DB25_breakout_box.png
new file mode 100644 (file)
index 0000000..5cfa26f
Binary files /dev/null and b/src/resources/images/DB25_breakout_box.png differ
diff --git a/src/resources/images/reload.png b/src/resources/images/reload.png
new file mode 100644 (file)
index 0000000..9f417cd
Binary files /dev/null and b/src/resources/images/reload.png differ
index 388add592b2aab711784e05c4bf200ab474ef7ec..420befcd93a3fbe6e9ce8d708c7c5ae5f5a506cf 100644 (file)
@@ -392,11 +392,15 @@ body
         color green
 
 .indicators
+  padding 1em
+  text-align center
+
   table
     display inline-block
     vertical-align top
     margin 0.5em
     empty-cells show
+    border 3px solid #bbb
 
     td, th
       padding 4px
@@ -420,6 +424,15 @@ body
       th:nth-child(3), th:nth-child(6)
         text-align left
 
+    th.header
+      height 2.5em
+      border-bottom 3px solid #ccc
+
+    th.separator
+      background #ccc
+      border 1px solid #ccc
+      padding 1px
+
   .logic-lo
     color black
 
@@ -433,7 +446,14 @@ body
 .video
   text-align center
   line-height 0
+  min-height 200px
+
+  .reload
+    float left
+    margin-right -48px
 
+    &:hover
+      opacity 0.5
 
 .tabs
   clear both