Improved Web control interface, LCD screen output
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 3 Sep 2016 22:37:42 +0000 (15:37 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 3 Sep 2016 22:37:42 +0000 (15:37 -0700)
src/jade/index.jade
src/jade/templates/control-view.jade
src/jade/templates/estop.jade
src/js/app.js
src/js/control-view.js
src/py/bbctrl/AVR.py
src/py/bbctrl/LCD.py
src/py/lcd/__init__.py
src/resources/css/side-menu.css
src/stylus/style.styl

index eb8a8779bf682920f46bf1561046007f8cf5ddbb..e5c8b9eb92e01bd7182fb4623719d3be0b4136e5 100644 (file)
@@ -65,11 +65,16 @@ html(lang="en")
 
       #main
         .header
-          img(src="/images/buildbotics_logo.png")
-          .logo
-            span.left Build
-            span.right botics
-          h2 Machine Controller
+          .header-content
+            .estop(:class="{active: state.es}")
+              estop(@click="estop")
+
+            .banner
+              img(src="/images/buildbotics_logo.png")
+              .title
+                span.left Build
+                span.right botics
+              .subtitle Machine Controller
 
         .content(class="{{currentView}}-view")
           component(:is="currentView + '-view'", :index="index",
index 6e877d84537221be7b45514262a596fa14fb8d71..7c085e11dd9e4c9f354db03bb61e12d7492be9d2 100644 (file)
@@ -1,58 +1,12 @@
 script#control-view-template(type="text/x-template")
   #control
-    .jog
-      axis-control(axes="XY", :colors="['red', 'green']",
-        :enabled="[enabled('x'), enabled('y')]",
-        v-if="enabled('x') || enabled('y')")
-      axis-control(axes="AZ", :colors="['orange', 'blue']",
-        :enabled="[enabled('a'), enabled('z')]",
-        v-if="enabled('a') || enabled('z')")
-      axis-control(axes="BC", :colors="['cyan', 'purple']",
-        :enabled="[enabled('b'), enabled('c')]",
-        v-if="enabled('b') || enabled('c')")
-
-
-    .estop(:class="{active: state.es}")
-      estop(@click="estop")
-
-
-    table.info
-      tr
-        th Tool
-        td {{state.t || 0}}
-      tr
-        th Velocity
-        td {{state.v || 0 | fixed 0}} mm/min
-      tr
-        th Feed
-        td {{state.f || 0}} mm/min
-      tr
-        th Speed
-        td {{state.s || 0}} RPM
-      tr
-        th Direction
-        td {{state.sd || 'Clockwise'}}
-      tr
-        th Mist
-        td {{state.mist || 'Off'}}
-      tr
-        th Coolant
-        td {{state.coolant || 'Off'}}
-      tr
-        th State
-        td {{state.x || ''}}
-      tr
-        th Cycle
-        td {{state.c || ''}}
-
-
     table.axes
       tr
-        th Axis
-        th Position
-        th Offset
-        th Errors
-        th Actions
+        th.name Axis
+        th.position Position
+        th.offset Offset
+        th.errors Errors
+        th.actions Actions
 
       each axis in 'xyzabc'
         tr.axis(class="axis-#{axis}", v-if="enabled('#{axis}')")
@@ -63,35 +17,60 @@ script#control-view-template(type="text/x-template")
             .fa.fa-hot(v-if="state.#{axis}t", title="Driver overtemp.")
             .fa.fa-ban(v-if="state.#{axis}s", title="Motor stalled.")
           th.actions
-            button.pure-button(title="Zero #{axis} axis.",
+            button.pure-button(title="Zero {{'#{axis}' | upper}} axis.",
               @click="zero('#{axis}')")
               | &empty;
-            button.pure-button(title="Home #{axis} axis.",
+            button.pure-button(title="Home {{'#{axis}' | upper}} axis.",
               @click="home('#{axis}')")
               .fa.fa-home
 
+    table.info
+      tr
+        th State
+        td {{get_state()}}
+        td
+      tr
+        th Feed
+        td {{state.f || 0}}
+        td mm/min
+      tr
+        th Speed
+        td {{state.s || 0}}
+        td RPM
+      tr
+        th Direction
+        td {{state.sd || 'Clockwise'}}
+        td
 
-    .overrides
-      | Override:
-      .override
-        label Feed
-        input(title="Feed rate override.", type="range", min="0", max="2",
-          step="0.01", v-model="feed_override", @change="override_feed")
-        span.percent {{feed_override | percent 0}}
-
-      .override
-        label Speed
-        input(title="Speed override.", type="range", min="0", max="2",
-          step="0.01", v-model="speed_override", @change="override_speed")
-        span.percent {{speed_override | percent 0}}
+    table.info
+      tr
+        th Velocity
+        td {{state.v || 0 | fixed 0}}
+        td mm/min
+      tr
+        th Tool
+        td {{state.t || 0}}
+        td
+      tr
+        th Mist
+        td {{state.mist || 'Off'}}
+        td
+      tr
+        th Coolant
+        td {{state.coolant || 'Off'}}
+        td
 
+    .override(title="Feed rate override.")
+      label Feed
+      input(type="range", min="0", max="2", step="0.01",
+        v-model="feed_override", @change="override_feed")
+      span.percent {{feed_override | percent 0}}
 
-    .mdi.pure-form
-      fieldset
-        button.pure-button.pure-button-primary(
-          title="Manually execute instructions.", @click="submit_mdi",
-          :disabled="state.x != 'ready'") MDI
-        input(v-model="mdi", @keyup.enter="submit_mdi")
+    .override(title="Spindle speed override.")
+      label Speed
+      input(type="range", min="0", max="2", step="0.01",
+        v-model="speed_override", @change="override_speed")
+      span.percent {{speed_override | percent 0}}
 
 
     .toolbar
@@ -117,22 +96,56 @@ script#control-view-template(type="text/x-template")
         :disabled="(state.x != 'ready' && state.x != 'holding') || !file")
         .fa.fa-step-forward
 
-      .spacer
-
-      button.pure-button(title="Upload a new program file.", @click="open",
-        :disabled="state.x != 'ready'")
-        .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")
-        .fa.fa-trash
-
-      select(title="Select previously uploaded program files.", v-model="file",
-        @change="load", :disabled="state.x != 'ready'")
-        option(v-for="file in files", :value="file") {{file}}
-
-
-    .gcode {{gcode || 'GCode displays here.'}}
+    .tabs
+      input#tab1(type="radio", name="tabs" checked)
+      label(for="tab1") Auto
+
+      input#tab2(type="radio", name="tabs")
+      label(for="tab2") MDI
+
+      input#tab3(type="radio", name="tabs")
+      label(for="tab3") Manual
+
+      section#content1.tab-content
+        .toolbar
+          button.pure-button(title="Upload a new program file.", @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")
+            .fa.fa-trash
+
+          select(title="Select previously uploaded program files.",
+            v-model="file", @change="load",
+            :disabled="state.x == 'running' || state.x == 'stopping'")
+            option(v-for="file in files", :value="file") {{file}}
+
+        .gcode(:class="{placeholder: !gcode}")
+          {{gcode || 'GCode displays here.'}}
+
+      section#content2.tab-content
+        .mdi.pure-form
+          fieldset
+            button.pure-button.pure-button-primary(
+              title="Manually execute instructions.", @click="submit_mdi",
+              :disabled="state.x != 'ready'") MDI
+            input(v-model="mdi", @keyup.enter="submit_mdi")
+
+        .history(:class="{placeholder: !history}")
+          {{history || 'MDI history displays here.'}}
+
+      section#content3.tab-content
+        .jog
+          axis-control(axes="XY", :colors="['red', 'green']",
+            :enabled="[enabled('x'), enabled('y')]",
+            v-if="enabled('x') || enabled('y')")
+          axis-control(axes="AZ", :colors="['orange', 'blue']",
+            :enabled="[enabled('a'), enabled('z')]",
+            v-if="enabled('a') || enabled('z')")
+          axis-control(axes="BC", :colors="['cyan', 'purple']",
+            :enabled="[enabled('b'), enabled('c')]",
+            v-if="enabled('b') || enabled('c')")
index 8f9a865441c2be469712430d2251f77ce56f1362..0fc0f4548150d78fe99e2c8485f483184600a2b0 100644 (file)
@@ -2,7 +2,7 @@ script#estop-template(type="text/x-template")
   svg(version="1.1", xmlns:svg="http://www.w3.org/2000/svg",
     xmlns="http://www.w3.org/2000/svg",
     xmlns:xlink="http://www.w3.org/1999/xlink",
-    width="200", height="200")
+    width="130", height="130")
       defs
         path#text-path-1(style="fill:none;stroke:none", d="m 73.735867,673.1299 c 0,55.10749 44.673453,99.78094 99.780973,99.78094 55.10748,0 99.78093,-44.67345 99.78093,-99.78094 0,-55.10749 -44.67345,-99.78094 -99.78093,-99.78094 -55.10752,0 -99.780973,44.67345 -99.780973,99.78094 z")
 
@@ -55,7 +55,7 @@ script#estop-template(type="text/x-template")
           fecomposite(in="offset", in2="SourceGraphic", operator="atop")
 
 
-      g(transform="scale(0.92, 0.92),translate(-65, -526)")
+      g(transform="scale(0.6, 0.6),translate(-65, -526)")
         // Yellow ring
         circle.ring(style="fill:#f5e138;filter:url(#filter5266)",
           cx="173", cy="633", r="100")
index 5fae6aecdd4a22267e82d468f5b20ad3f9d066f6..c92cd4b7266427cc4db2d98c4e59feb40a71184d 100644 (file)
@@ -22,6 +22,7 @@ module.exports = new Vue({
 
 
   components: {
+    'estop': {template: '#estop-template'},
     'loading-view': {template: '<h1>Loading...</h1>'},
     'control-view': require('./control-view'),
     'axis-view': require('./axis-view'),
@@ -56,6 +57,12 @@ module.exports = new Vue({
 
 
   methods: {
+    estop: function () {
+      if (this.state.x == 'estopped') api.put('clear');
+      else api.put('estop');
+    },
+
+
     update: function () {
       $.get('/config-template.json', {cache: false})
         .success(function (data, status, xhr) {
index d169c3741170cd52a0d2a3d3b435a6700fd3e83c..7479a179909c190a6a2eb9c5d8226e2bf6a46e95 100644 (file)
@@ -21,6 +21,7 @@ module.exports = {
       files: [],
       axes: 'xyzabc',
       gcode: '',
+      history: '',
       speed_override: 1,
       feed_override: 1
     }
@@ -28,12 +29,12 @@ module.exports = {
 
 
   components: {
-    'axis-control': require('./axis-control'),
-    'estop': {template: '#estop-template'}
+    'axis-control': require('./axis-control')
   },
 
 
   events: {
+    // TODO These should all be implemented via the API
     jog: function (axis, move) {this.send('g91 g0' + axis + move)},
     home: function (axis) {this.send('$home ' + axis)},
     zero: function (axis) {this.send('$zero ' + axis)}
@@ -46,6 +47,13 @@ module.exports = {
 
 
   methods: {
+    get_state: function () {
+      var state = this.state.x || '';
+      if (state == 'running' && this.state.c) state = this.state.c;
+      return state.toUpperCase();
+    },
+
+
     send: function (msg) {
       this.$dispatch('send', msg);
     },
@@ -58,12 +66,6 @@ module.exports = {
     },
 
 
-    estop: function () {
-      if (this.state.x == 'estopped') api.put('clear').done(this.update);
-      else api.put('estop').done(this.update);
-    },
-
-
     update: function () {
       api.get('file')
         .done(function (files) {
@@ -79,6 +81,7 @@ module.exports = {
 
     submit_mdi: function () {
       this.send(this.mdi);
+      this.history = this.mdi + '\n' + this.history;
     },
 
 
@@ -174,8 +177,7 @@ module.exports = {
     },
 
 
-    fixed: function (value, precision) {
-      return value.toFixed(precision);
-    }
+    fixed: function (value, precision) {return value.toFixed(precision)},
+    upper: function (value) {return value.toUpperCase()}
   }
 }
index 23c210889600af08f5e9cb81668adc817d3ce1aa..0f22bb377a61c5d526023957deee5e8177798a1c 100644 (file)
@@ -154,20 +154,23 @@ class AVR():
                 try:
                     msg = json.loads(line)
 
-                    if 'firmware' in msg:
-                        log.error('AVR rebooted')
-                        self._stop_sending_gcode()
-                        self.report()
+                except Exception as e:
+                    log.error('%s, data: %s', e, line)
 
-                    if 'x' in msg and msg['x'] == 'estopped':
-                        self._stop_sending_gcode()
+                if 'firmware' in msg:
+                    log.error('AVR rebooted')
+                    self._stop_sending_gcode()
+                    self.report()
 
-                    self.vars.update(msg)
-                    self.ctrl.web.broadcast(msg)
-                    log.debug(line)
+                if 'x' in msg and msg['x'] == 'estopped':
+                    self._stop_sending_gcode()
+
+                self.vars.update(msg)
+                self.ctrl.lcd.update(msg)
+                self.ctrl.web.broadcast(msg)
+
+                log.debug(line)
 
-                except Exception as e:
-                    log.error('%s, data: %s', e, line)
 
 
     def queue_command(self, cmd):
index 4e4ab31b2b22fbef9eb7e383df51f1b108d53a2e..bc4e80f283e88ae5ba02aac7a01d1a24d9447877 100644 (file)
@@ -4,16 +4,30 @@ import atexit
 
 class LCD:
     def __init__(self, ctrl):
+        self.ctrl = ctrl
+
         self.lcd = lcd.LCD(ctrl.args.lcd_port, ctrl.args.lcd_addr)
-        self.splash()
         atexit.register(self.goodbye)
 
 
-    def splash(self):
-        self.lcd.clear()
-        self.lcd.display(0, 'Buildbotics', lcd.JUSTIFY_CENTER)
-        self.lcd.display(1, 'Controller', lcd.JUSTIFY_CENTER)
-        self.lcd.display(3, '*Ready*', lcd.JUSTIFY_CENTER)
+    def update(self, msg, force = False):
+        def has(name): return force or name in msg
+
+        if has('x') or has('c'):
+            v = self.ctrl.avr.vars
+            state = v.get('x', 'init')
+            if 'c' in v and state == 'running': state = v['c']
+
+            self.lcd.text('%-9s' % state.upper(), 0, 0)
+
+        if has('xp'): self.lcd.text('% 10.4fX' % msg['xp'], 9, 0)
+        if has('yp'): self.lcd.text('% 10.4fY' % msg['yp'], 9, 1)
+        if has('zp'): self.lcd.text('% 10.4fZ' % msg['zp'], 9, 2)
+        if has('ap'): self.lcd.text('% 10.4fA' % msg['ap'], 9, 3)
+        if has('t'):  self.lcd.text('%2uT'     % msg['t'],  6, 1)
+        if has('u'):  self.lcd.text('%s'       % msg['u'].upper(),  0, 1)
+        if has('f'):  self.lcd.text('%8uF'     % msg['f'],  0, 2)
+        if has('s'):  self.lcd.text('%8dS'     % msg['s'],  0, 3)
 
 
     def goodbye(self):
index b14f3174bfca29895f9b04140897c380799cf801..b7b44ac7a7c5f10ec4fbb56b6cbaa487df04ade9 100755 (executable)
@@ -141,7 +141,9 @@ class LCD:
         self.write(LCD_SET_DDRAM_ADDR | (0, 64, 20, 84)[y] + int(x))
 
 
-    def text(self, msg):
+    def text(self, msg, x = None, y = None):
+        if x is not None and y is not None: self.goto(x, y)
+
         for c in msg:
             self.write(ord(c), REG_SELECT_BIT)
 
@@ -153,8 +155,7 @@ class LCD:
 
         if x < 0: x = 0
 
-        self.goto(x, line)
-        self.text(msg)
+        self.text(msg, x, line)
 
 
     def shift(self, count = 1, right = True, display = True):
index b5ce7ae75b490bfcba46b7cf37d19d8265caa29d..8999d61b277cc80e6bd98eaa9e0e05cc2bd46194 100644 (file)
@@ -245,4 +245,3 @@ Hides the menu at `48em`, but modify this based on your app's needs.
         left: 150px;
     }
 }
-
index efa621aae4745f1acb6d67a466e96682e929d91e..5c27db64201e1e5357da4e30c22452bbca8a5224 100644 (file)
@@ -7,19 +7,44 @@ body
 .button-success:not([disabled])
   background rgb(28, 184, 65)
 
-.header img
-  vertical-align top
+.header, .content
+  padding 0
 
-.logo
-  font-size 30pt
-  font-family Audiowide
-  display inline
-  margin-right 0.5em
+.header
+  height 140px
+  padding 0
 
-  .left
-    color #444
-  .right
-    color #e5aa3d
+  .header-content
+    max-width 800px
+    margin auto
+    text-align left
+
+  .estop
+    float right
+    margin 5px
+
+  .banner
+    padding-top 40px
+    white-space nowrap
+
+    img
+      vertical-align top
+
+    .title
+      font-size 30pt
+      font-family Audiowide
+      display inline
+      margin-right 0.5em
+
+      .left
+        color #444
+      .right
+        color #e5aa3d
+
+    .subtitle
+      font-size 18pt
+      font-weight 100
+      color #aaa
 
 .error
   background red
@@ -101,6 +126,19 @@ body
   50%
     fill #ff9d00
 
+.estop
+  width 130px
+  transition 250ms
+
+  &.active .ring
+    animation blink 2s step-start 0s infinite
+
+  svg
+    cursor pointer
+
+    .button:hover
+      filter brightness(120%)
+
 .control-view
   table
     margin 0.5em 0
@@ -110,6 +148,8 @@ body
       border 1px solid #ddd
 
   .axes
+    width 100%
+
     .axis-x .name
       color #f00
 
@@ -130,6 +170,7 @@ body
 
     td, th
       padding 2px
+      white-space nowrap
 
     th
       text-align center
@@ -144,24 +185,17 @@ body
 
       .name, .position
         font-size 36pt
-        line-height 36pt
+        line-height 30pt
 
-  .estop
-    display inline-block
-    width 190px
-    transition 250ms
-
-    &.active .ring
-      animation blink 2s step-start 0s infinite
-
-    svg
-      cursor pointer
+      .position
+        width 99%
 
-      .button:hover
-        filter brightness(120%)
+      .offset
+        min-width 8em
 
-  .jog
-    float right
+      .errors
+        min-width 6em
+        white-space normal
 
   .jog svg
     text
@@ -199,57 +233,153 @@ body
           text-anchor middle
 
   .info
-    float right
-    clear right
+    empty-cells show
 
     th, td
       padding 3px
       text-align right
 
-    td
+    th
+      width 5.25em
+
+    td:nth-child(2)
       min-width 8em
 
-  .overrides
-    clear both
+    td:nth-child(3)
+      text-align left
 
-    .override
-      margin 0.5em
-      display inline-block
+  .info:nth-of-type(2)
+    float left
+    clear left
 
-      .percent
-        display inline-block
-        width 3em
-
-      input
-        border-radius 0
-        margin -0.4em 0.5em
+  .info:nth-of-type(3)
+    float right
+    clear right
 
-  .mdi
-    clear both
-    white-space nowrap
+  .override
     margin 0.5em 0
+    white-space nowrap
+
+    label
+      font-weight bold
+      min-width 3.5em
+      display inline-block
+
+    .percent
+      display inline-block
+      width 3em
 
     input
-      width 90%
+      border-radius 0
+      margin -0.4em 0.5em
+
+  .override:nth-of-type(1)
+    clear left
+    float left
+
+  .override:nth-of-type(2)
+    clear right
+    float right
 
   .toolbar
     clear both
-    margin 0.5em 0
 
-    .spacer
-      display inline-block
-      width 1px
-      height 1px
-      margin 0 1em
+    > *
+      margin 0.5em 0
 
-  .gcode
+  .gcode, .history
     clear both
-    border 2px inset #ccc
-    border-radius 5px
     overflow auto
     width 100%
-    max-width 100%
-    min-width 100%
+    max-width 99%
+    min-width 99%
     height 200px
     padding 2px
     white-space pre
+
+    &.placeholder
+      color #aaa
+
+  .mdi
+    clear both
+    white-space nowrap
+
+    input
+      width 90%
+
+  .jog
+    text-align center
+
+    > svg
+      margin 1em
+
+
+.tabs
+  clear both
+
+  > input
+    display none
+
+  > label
+    display block
+    float left
+    width 5em
+    font-weight bold
+    cursor pointer
+    text-decoration none
+    text-align center
+    background #f6f6f6
+    border-top-left-radius 5px
+    border-top-right-radius 5px
+    border-top 2px solid #f6f6f6
+    border-left 1px solid #f6f6f6
+    border-right 1px solid #f6f6f6
+    margin-right 2px
+
+  > section
+    display none
+    clear both
+
+  > #tab1:checked ~ #content1,
+  > #tab2:checked ~ #content2,
+  > #tab3:checked ~ #content3
+    display block
+
+  [id^="tab"]:checked + label
+    background #fff
+    border-top 2px solid #ddd
+    border-left 1px solid #ddd
+    border-right 1px solid #ddd
+    border-bottom 1px solid #fff
+    margin-bottom -1px
+
+  .tab-content
+    border 1px solid #ddd
+    padding 0.5em
+
+
+@media only screen and (max-width 48em)
+  .header
+    height auto
+
+    .header-content > .banner
+      padding-top 0
+      clear both
+
+  .control-view #control
+    .axes
+      .axis
+        .name, .position
+          font-size 24pt
+          line-height 24pt
+
+      .offset, .errors
+        display none
+
+    > *:nth-of-type(n)
+      float none
+      clear both
+      width 99%
+
+  .tab_container
+    width 98%