Automatically reload Web view when file changes, Acknowledging a message on one brows...
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 12 Oct 2019 23:42:41 +0000 (16:42 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 12 Oct 2019 23:42:41 +0000 (16:42 -0700)
CHANGELOG.md
package.json
src/js/app.js
src/js/control-view.js
src/pug/index.pug
src/pug/templates/control-view.pug
src/py/bbctrl/Log.py
src/py/bbctrl/Planner.py
src/py/bbctrl/State.py
src/py/bbctrl/Web.py

index 707e4e8b6b6d9e78089b125552fdff1f800baabe..80989afcbc95c0c732b412fc146e1f8b284b3975 100644 (file)
@@ -1,6 +1,15 @@
 Buildbotics CNC Controller Firmware Changelog
 =============================================
 
+## v0.4.12
+ - Segments straddle arc in linearization.
+ - Control max-arc-error with GCode var.
+ - Implemented path modes G61, G61.1 & G64 with naive CAM but not blending, yet.
+ - Log GCode messages to "Messages" tab.
+ - Acknowledging a message on one browser clears it for all.
+ - Automatically reload Web view when file changes.
+ - Changed "Message" field to "Reason" in Web interface.
+
 ## v0.4.11
  - Don't reset global offsets on M2.
  - Test shunt and show error on failure.
index cf48ab41c2ab5f7d86fe8e246f8040d79acc4847..2d06ce87ec40ae76f9662c6ddbb733721462f93f 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "bbctrl",
-  "version": "0.4.11",
+  "version": "0.4.12",
   "homepage": "http://buildbotics.com/",
   "repository": "https://github.com/buildbotics/bbctrl-firmware",
   "license": "GPL-3.0+",
index aeb185f1a8ee382c4a418a4493440724f9edd4bb..9c772151c17f236325e1b439f168b3edab195113 100644 (file)
@@ -103,8 +103,7 @@ module.exports = new Vue({
         motors: [{}, {}, {}, {}],
         version: '<loading>'
       },
-      state: {},
-      messages: [],
+      state: {messages: []},
       video_size: cookie.get('video-size', 'small'),
       crosshair: cookie.get('crosshair', false),
       errorTimeout: 30,
@@ -117,8 +116,7 @@ module.exports = new Vue({
       checkedUpgrade: false,
       firmwareName: '',
       latestVersion: '',
-      password: '',
-      showMessages: false
+      password: ''
     }
   },
 
@@ -308,11 +306,6 @@ module.exports = new Vue({
           delete e.data.log;
         }
 
-        if ('message' in e.data) {
-          this.add_message(e.data.message);
-          delete e.data.message;
-        }
-
         // Check for session ID change on controller
         if ('sid' in e.data) {
           if (typeof this.sid == 'undefined') this.sid = e.data.sid;
@@ -369,18 +362,15 @@ module.exports = new Vue({
     },
 
 
-    add_message: function (msg) {
-      this.messages.unshift(msg);
-      this.showMessages = true;
-    },
-
-
     close_messages: function (action) {
-      this.showMessages = false;
-      this.messages.splice(0, this.messages.length);
-
       if (action == 'stop') api.put('stop');
       if (action == 'continue') api.put('unpause');
+
+      // Acknowledge messages
+      if (this.state.messages.length) {
+        var id = this.state.messages.slice(-1)[0].id
+        api.put('message/' + id + '/ack');
+      }
     }
   }
 })
index 6ba4fcf989814b341f34bcd73a95b939433f1e5c..5b777f1bb28d8b77a35e5990163feb595d3ba9c0 100644 (file)
@@ -51,6 +51,7 @@ module.exports = {
       mach_units: 'METRIC',
       mdi: '',
       last_file: undefined,
+      last_file_time: undefined,
       toolpath: {},
       toolpath_progress: 0,
       axes: 'xyzabc',
@@ -96,7 +97,7 @@ module.exports = {
     },
 
 
-    'state.selected': function () {this.load()}
+    'state.selected_time': function () {this.load()}
   },
 
 
@@ -153,7 +154,11 @@ module.exports = {
     },
 
 
-    highlight_reason: function () {return this.reason != ''},
+    highlight_reason: function () {
+      return this.mach_state == 'ESTOPPED' || this.mach_state == 'HOLDING';
+    },
+
+
     plan_time: function () {return this.state.plan_time},
 
 
@@ -197,24 +202,26 @@ module.exports = {
 
 
     load: function () {
+      var file_time = this.state.selected_time;
       var file = this.state.selected;
-      if (this.last_file == file) return;
+      if (this.last_file == file && this.last_file_time == file_time) return;
       this.last_file = file;
+      this.last_file_time = file_time;
 
       this.$broadcast('gcode-load', file);
       this.$broadcast('gcode-line', this.state.line);
       this.toolpath_progress = 0;
-      this.load_toolpath(file);
+      this.load_toolpath(file, file_time);
     },
 
 
-    load_toolpath: function (file) {
+    load_toolpath: function (file, file_time) {
       this.toolpath = {};
 
       if (!file) return;
 
       api.get('path/' + file).done(function (toolpath) {
-        if (this.last_file != file) return;
+        if (this.last_file_time != file_time) return;
 
         if (typeof toolpath.progress == 'undefined') {
           toolpath.filename = file;
@@ -230,7 +237,7 @@ module.exports = {
 
         } else {
           this.toolpath_progress = toolpath.progress;
-          this.load_toolpath(file); // Try again
+          this.load_toolpath(file, file_time); // Try again
         }
       }.bind(this));
     },
@@ -276,7 +283,7 @@ module.exports = {
 
       api.upload('file', fd)
         .done(function () {
-          this.last_file = undefined; // Force reload
+          this.last_file_time = undefined; // Force reload
           this.$broadcast('gcode-reload', file.name);
 
         }.bind(this)).fail(function (error) {
index 34e56b5f43918c28473f5bd995dd5384e7dd416f..06a843008d3dc97be7929d5ae5f857de1776403b 100644 (file)
@@ -182,12 +182,12 @@ html(lang="en")
         p Loss of power during an upgrade may damage the controller.
       div(slot="footer")
 
-    message(:show.sync="showMessages")
+    message(v-if="state.messages.length", :show="true")
       h3(slot="header") GCode message
 
       div(slot="body")
         ul
-          li(v-for="msg in messages", track-by="$index") {{msg}}
+          li(v-for="msg in state.messages", track-by="$index") {{msg.text}}
 
       div(slot="footer")
         button.pure-button.button-success(v-if="state.xx != 'HOLDING'",
index 3ff1d3ac77a64eb629ce474f1f46c229ae5a9075..232224b2e2735cfbb8f2acf9e7bfed869a45c121 100644 (file)
@@ -116,7 +116,7 @@ script#control-view-template(type="text/x-template")
         th State
         td(:class="{attention: highlight_reason}") {{mach_state}}
       tr
-        th Message
+        th Reason
         td.reason(:class="{attention: highlight_reason}") {{reason}}
       tr(title="Active machine units")
         th Units
index 6bcfbb3ad9f65f3237186fce19a37c2aa03264ca..04363754125ffdd098443c3a9c57d4a0ad40370f 100644 (file)
@@ -37,11 +37,14 @@ import bbctrl
 
 DEBUG    = 0
 INFO     = 1
-WARNING  = 2
-ERROR    = 3
+MESSAGE  = 2
+WARNING  = 3
+ERROR    = 4
 
 
-def get_level_name(level): return 'debug info warning error'.split()[level]
+level_names = 'debug info message warning error'.split()
+
+def get_level_name(level): return level_names[level]
 
 
 # Get this file's name
@@ -89,6 +92,7 @@ class Logger(object):
 
 
     def debug  (self, *args, **kwargs): self._log(DEBUG,   *args, **kwargs)
+    def message(self, *args, **kwargs): self._log(MESSAGE, *args, **kwargs)
     def info   (self, *args, **kwargs): self._log(INFO,    *args, **kwargs)
     def warning(self, *args, **kwargs): self._log(WARNING, *args, **kwargs)
     def error  (self, *args, **kwargs): self._log(ERROR,   *args, **kwargs)
@@ -142,7 +146,7 @@ class Log(object):
     def _log(self, msg, level = INFO, prefix = '', where = None):
         if not msg: return
 
-        hdr = '%s:%s:' % ('DIWE'[level], prefix)
+        hdr = '%s:%s:' % ('DIMWE'[level], prefix)
         s = hdr + ('\n' + hdr).join(msg.split('\n'))
 
         if self.f is not None:
index e41ef1767807517247a0a64043b5d5dd9abc7b2c..0b0a7ef5cdcd0182695600345735107c5543d854 100644 (file)
@@ -60,6 +60,7 @@ class Planner():
         self.cmdq = CommandQueue(ctrl)
         self.planner = None
         self._position_dirty = False
+        self.where = ''
 
         ctrl.state.add_listener(self._update)
 
@@ -165,6 +166,16 @@ class Planner():
         else: self.log.error('Could not parse planner log line: ' + line)
 
 
+    def _add_message(self, text):
+        self.ctrl.state.add_message(text)
+
+        line = self.ctrl.state.get('line', 0)
+        if 0 <= line: where = '%s:%d' % (self.where, line)
+        else: where = self.where
+
+        self.log.message(text, where = where)
+
+
     def _enqueue_set_cmd(self, id, name, value):
         self.log.info('set(#%d, %s, %s)', id, name, value)
         self.cmdq.enqueue(id, self.ctrl.state.set, name, value)
@@ -228,8 +239,7 @@ class Planner():
             name, value = block['name'], block['value']
 
             if name == 'message':
-                msg = dict(message = value)
-                self.cmdq.enqueue(id, self.ctrl.log.broadcast, msg)
+                self.cmdq.enqueue(id, self._add_message, value)
 
             if name in ['line', 'tool']: self._enqueue_set_cmd(id, name, value)
 
@@ -313,6 +323,7 @@ class Planner():
 
 
     def mdi(self, cmd, with_limits = True):
+        self.where = '<mdi>'
         self.log.info('MDI:' + cmd)
         self._sync_position()
         self.planner.load_string(cmd, self.get_config(True, with_limits))
@@ -320,6 +331,7 @@ class Planner():
 
 
     def load(self, path):
+        self.where = path
         path = self.ctrl.get_path('upload', path)
         self.log.info('GCode:' + path)
         self._sync_position()
index 484ba8296dbef7e01858889a8b95b5098a0384e3..bbfbbf8be197648148c05d33aefe8fd096e94a52 100644 (file)
@@ -42,10 +42,12 @@ class State(object):
         self.listeners = []
         self.timeout = None
         self.machine_var_set = set()
+        self.message_id = 0
 
         # Defaults
         self.vars = {
             'line': -1,
+            'messages': [],
             'tool': 0,
             'feed': 0,
             'speed': 0,
@@ -137,7 +139,25 @@ class State(object):
             else: self.select_file('')
 
 
-    def select_file(self, filename): self.set('selected', filename)
+    def select_file(self, filename):
+        self.set('selected', filename)
+        time = os.path.getmtime(self.ctrl.get_upload(filename))
+        self.set('selected_time', time)
+
+
+    def ack_message(self, id):
+        self.log.info('Message %d acknowledged' % id)
+        msgs = self.vars['messages']
+        msgs = list(filter(lambda m: id < m['id'], msgs))
+        self.set('messages', msgs)
+
+
+    def add_message(self, text):
+        msg = dict(text = text, id = self.message_id)
+        self.message_id += 1
+        msgs = self.vars['messages']
+        msgs = msgs + [msg] # It's important we make a new list here
+        self.set('messages', msgs)
 
 
     def _notify(self):
index 6c9da551ae49895d2f065530b1709ca7ede52982..d90f0638e316ecfe691df5b5e8ef977b57123164 100644 (file)
@@ -95,6 +95,11 @@ class LogHandler(bbctrl.RequestHandler):
         self.set_header('Content-Type', 'text/plain')
 
 
+class MessageAckHandler(bbctrl.APIHandler):
+    def put_ok(self, id):
+        self.get_ctrl().state.ack_message(int(id))
+
+
 class BugReportHandler(bbctrl.RequestHandler):
     def get(self):
         import tarfile, io
@@ -507,6 +512,7 @@ class Web(tornado.web.Application):
         handlers = [
             (r'/websocket', WSConnection),
             (r'/api/log', LogHandler),
+            (r'/api/message/(\d+)/ack', MessageAckHandler),
             (r'/api/bugreport', BugReportHandler),
             (r'/api/reboot', RebootHandler),
             (r'/api/hostname', HostnameHandler),