From 64469a1e4368794c9ef4cd7daebd3ff759b21a45 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Sat, 12 Oct 2019 16:42:41 -0700 Subject: [PATCH] Automatically reload Web view when file changes, Acknowledging a message on one browser clears it for all, Log GCode messages to Messages tab, Changed Message -> Reason in Web interface --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- src/js/app.js | 26 ++++++++------------------ src/js/control-view.js | 23 +++++++++++++++-------- src/pug/index.pug | 4 ++-- src/pug/templates/control-view.pug | 2 +- src/py/bbctrl/Log.py | 12 ++++++++---- src/py/bbctrl/Planner.py | 16 ++++++++++++++-- src/py/bbctrl/State.py | 22 +++++++++++++++++++++- src/py/bbctrl/Web.py | 6 ++++++ 10 files changed, 85 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 707e4e8..80989af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/package.json b/package.json index cf48ab4..2d06ce8 100644 --- a/package.json +++ b/package.json @@ -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+", diff --git a/src/js/app.js b/src/js/app.js index aeb185f..9c77215 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -103,8 +103,7 @@ module.exports = new Vue({ motors: [{}, {}, {}, {}], version: '' }, - 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'); + } } } }) diff --git a/src/js/control-view.js b/src/js/control-view.js index 6ba4fcf..5b777f1 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -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) { diff --git a/src/pug/index.pug b/src/pug/index.pug index 34e56b5..06a8430 100644 --- a/src/pug/index.pug +++ b/src/pug/index.pug @@ -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'", diff --git a/src/pug/templates/control-view.pug b/src/pug/templates/control-view.pug index 3ff1d3a..232224b 100644 --- a/src/pug/templates/control-view.pug +++ b/src/pug/templates/control-view.pug @@ -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 diff --git a/src/py/bbctrl/Log.py b/src/py/bbctrl/Log.py index 6bcfbb3..0436375 100644 --- a/src/py/bbctrl/Log.py +++ b/src/py/bbctrl/Log.py @@ -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: diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py index e41ef17..0b0a7ef 100644 --- a/src/py/bbctrl/Planner.py +++ b/src/py/bbctrl/Planner.py @@ -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 = '' 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() diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py index 484ba82..bbfbbf8 100644 --- a/src/py/bbctrl/State.py +++ b/src/py/bbctrl/State.py @@ -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): diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index 6c9da55..d90f063 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -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), -- 2.27.0