Added console in Web, Don't fail to start when LCD is not found, Retry LCD connection...
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Wed, 21 Dec 2016 00:35:49 +0000 (16:35 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Wed, 21 Dec 2016 00:35:49 +0000 (16:35 -0800)
setup.py
src/jade/templates/control-view.jade
src/js/app.js
src/js/control-view.js
src/py/bbctrl/AVR.py
src/py/bbctrl/Jog.py
src/py/bbctrl/LCD.py
src/py/lcd/__init__.py
src/stylus/style.styl

index 00285284197a331ea5ef6ce2dd468fcfe54c817e..b6105b37699e4be4d392cb882043bf9a0c153093 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -21,6 +21,6 @@ setup(
             'bbctrl = bbctrl:run'
             ]
         },
-    install_requires = 'tornado sockjs-tornado pyserial smbus2'.split(),
+    install_requires = 'tornado sockjs-tornado pyserial pyudev smbus2'.split(),
     zip_safe = False,
     )
index 247b29680fd9ae85ec94ed1f1a38a66b9209b9d6..36d3e70df2706af5fcc4cb4473af45ea763c15de 100644 (file)
@@ -114,6 +114,9 @@ script#control-view-template(type="text/x-template")
       input#tab3(type="radio", name="tabs")
       label(for="tab3") Manual
 
+      input#tab4(type="radio", name="tabs")
+      label(for="tab4") Console
+
       section#content1.tab-content
         .toolbar
           button.pure-button(title="Upload a new program file.", @click="open",
@@ -157,3 +160,23 @@ script#control-view-template(type="text/x-template")
           axis-control(axes="BC", :colors="['cyan', 'purple']",
             :enabled="[enabled('b'), enabled('c')]",
             v-if="enabled('b') || enabled('c')")
+
+      section#content4.tab-content
+        .toolbar
+          button.pure-button(title="Clear console.", @click="clear_console")
+            .fa.fa-trash
+
+        table.console
+          tr
+            th Level
+            th Location
+            th Code
+            th Repeat
+            th Message
+
+          tr(v-for="msg in console", :class="msg.level || 'info'")
+            td {{msg.level || 'info'}}
+            td {{msg.where || ''}}
+            td {{msg.code  || '0'}}
+            td {{msg.repeat}}
+            td {{msg.msg}}
index c676bcafaab147b87f25dc6fa75dd8cc67431f90..55d45edd96cc22bee5a6ccb9ed39fd64147f131a 100644 (file)
@@ -82,9 +82,12 @@ module.exports = new Vue({
       this.sock.onmessage = function (e) {
         var msg = e.data;
 
-        if (typeof msg == 'object')
+        if (typeof msg == 'object') {
           for (var key in msg)
             this.$set('state.' + key, msg[key]);
+
+          if ('msg' in msg) this.$broadcast('message', msg);
+        }
       }.bind(this);
 
       this.sock.onopen = function (e) {
index b9260887df1e3066d84f040ca80f38671c5da70e..2524f78fbff0f61e8a8f0627cbea99267f9c6a9a 100644 (file)
@@ -3,11 +3,17 @@
 var api = require('./api');
 
 
-function is_array(x) {
+function _is_array(x) {
   return Object.prototype.toString.call(x) === '[object Array]';
 }
 
 
+function _msg_equal(a, b) {
+  return a.level == b.level && a.location == b.location && a.code == b.code &&
+    a.msg == b.msg;
+}
+
+
 module.exports = {
   template: '#control-view-template',
   props: ['config', 'state'],
@@ -22,6 +28,7 @@ module.exports = {
       axes: 'xyzabc',
       gcode: '',
       history: '',
+      console: [],
       speed_override: 1,
       feed_override: 1
     }
@@ -40,7 +47,19 @@ module.exports = {
 
   events: {
     // TODO These should all be implemented via the API
-    jog: function (axis, move) {this.send('g91 g0' + axis + move)}
+    jog: function (axis, move) {this.send('g91 g0' + axis + move)},
+
+
+    message: function (msg) {
+      if (this.console.length &&
+          _msg_equal(msg, this.console[this.console.length - 1]))
+        this.console[this.console.length - 1].repeat++;
+
+      else {
+        msg.repeat = 1;
+        this.console.push(msg);
+      }
+    }
   },
 
 
@@ -214,7 +233,10 @@ module.exports = {
       var data = {};
       data[axis + 'pl'] = x;
       this.send(JSON.stringify(data));
-    }
+    },
+
+
+    clear_console: function () {this.console = [];}
   },
 
 
index fb6d6f208e8fa8e4635365d922aea3c7729bd5c7..5a534c94d3b62cd855604002d314bd9567f52ef5 100644 (file)
@@ -81,7 +81,8 @@ class AVR():
 
 
     def _i2c_command(self, cmd, byte = None, word = None):
-        if self.i2c_bus is None: return
+        if self.i2c_bus is None or not hasattr(self.i2c_bus, 'write_byte'):
+            return
 
         log.info('I2C: %d' % cmd)
         retry = 5
index f4c48a6c940478c0b0cbe9367abedd9d0ac7b733..cb4f717adddc366732e5f8ea0afc4c510ec521a2 100644 (file)
@@ -40,3 +40,5 @@ 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 1209e7ff8084659bccebe01ccb945c6db602da57..9a6a03721ac74541002dd842a5c570b72bf2ae2f 100644 (file)
@@ -1,17 +1,34 @@
 import lcd
 import atexit
+import logging
+
+
+log = logging.getLogger('LCD')
 
 
 class LCD:
     def __init__(self, ctrl):
         self.ctrl = ctrl
-
-        self.lcd = lcd.LCD(ctrl.args.lcd_port, ctrl.args.lcd_addr)
+        self.force = False
         atexit.register(self.goodbye)
+        self.connect()
+
+
+    def connect(self):
+        try:
+            self.lcd = None
+            self.lcd = lcd.LCD(self.ctrl.args.lcd_port, self.ctrl.args.lcd_addr)
+            self.lcd.clear()
+            self.lcd.display(1, 'Loading', lcd.JUSTIFY_CENTER)
+            self.force = True
+
+        except IOError as e:
+            log.error('Connect failed, retrying: %s' % e)
+            self.ctrl.ioloop.call_later(1, self.connect)
 
 
     def update(self, msg, force = False):
-        def has(name): return force or name in msg
+        def has(name): return self.force or force or name in msg
 
         if has('x') or has('c'):
             v = self.ctrl.avr.vars
@@ -29,7 +46,15 @@ class LCD:
         if has('f'):  self.lcd.text('%8uF'     % msg['f'],  0, 2)
         if has('s'):  self.lcd.text('%8dS'     % msg['s'],  0, 3)
 
+        self.force = False
+
 
     def goodbye(self):
-        self.lcd.clear()
-        self.lcd.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
+        if self.lcd is None: return
+
+        try:
+            self.lcd.clear()
+            self.lcd.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
+
+        except IOError as e:
+            log.error('I2C communication failed: %s' % e)
index b7b44ac7a7c5f10ec4fbb56b6cbaa487df04ade9..64648fec20d1901f3d7e03bfe09c4e5c6eba1d8b 100755 (executable)
@@ -80,7 +80,14 @@ class LCD:
 
 
     def reset(self):
-        self.clear()
+        time.sleep(0.050)
+        self.write_nibble(3 << 4) # Home
+        time.sleep(0.050)
+        self.write_nibble(3 << 4) # Home
+        time.sleep(0.050)
+        self.write_nibble(3 << 4) # Home
+        self.write_nibble(2 << 4) # 4-bit
+
         self.write(LCD_FUNCTION_SET | LCD_2_LINE | LCD_5x8_DOTS |
                    LCD_4_BIT_MODE)
         self.write(LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON)
index dfd5ab026897e48ccc2b60b6b1e6f4e1d9f97f35..a45daddf11f1119a33b5b7c7e7e531f4407e2dd4 100644 (file)
@@ -285,7 +285,15 @@ body
     clear both
 
     > *
-      margin 0.5em 0
+      margin 0.25em
+
+
+  .tabs
+    section
+      max-height 260px
+      overflow auto
+      padding 0
+      margin 0
 
   .gcode, .history
     clear both
@@ -294,7 +302,7 @@ body
     max-width 99%
     min-width 99%
     height 200px
-    padding 2px
+    padding 0.25em
     white-space pre
 
     &.placeholder
@@ -330,6 +338,23 @@ body
     > svg
       margin 1em
 
+  .console
+    width 100%
+
+    tr
+      > td
+        margin 0 0.125em
+        background-color #fff
+
+      &.error td
+        color red
+
+      &.warning td
+        color orange
+
+      &.debug td
+        color green
+
 
 .tabs
   clear both
@@ -359,7 +384,8 @@ body
 
   > #tab1:checked ~ #content1,
   > #tab2:checked ~ #content2,
-  > #tab3:checked ~ #content3
+  > #tab3:checked ~ #content3,
+  > #tab4:checked ~ #content4
     display block
 
   [id^="tab"]:checked + label