- Fixed "Flood" display, changed to "Load 1" and "Load 2". #108
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Fri, 23 Feb 2018 11:00:18 +0000 (03:00 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Fri, 23 Feb 2018 11:00:18 +0000 (03:00 -0800)
 - Highlight loads when on.
 - Fixed axis zeroing.
 - Fixed bug in home position set after successful home.  #109
 - Fixed ugly Web error dumps.
 - Allow access to log file from Web.
 - Rotate log so it does not grow too big.
 - Keep same GCode file through browser reload.  #20

23 files changed:
CHANGELOG.md
src/avr/src/command.c
src/avr/src/command.def
src/avr/src/exec.c
src/avr/src/status.c
src/avr/src/status.h
src/avr/src/stepper.c
src/avr/src/stepper.h
src/avr/src/vars.c
src/avr/src/vars.def
src/jade/templates/admin-view.jade
src/jade/templates/console.jade
src/jade/templates/control-view.jade
src/js/control-view.js
src/py/bbctrl/APIHandler.py
src/py/bbctrl/Cmd.py
src/py/bbctrl/FileHandler.py
src/py/bbctrl/Jog.py
src/py/bbctrl/Mach.py
src/py/bbctrl/Planner.py
src/py/bbctrl/Web.py
src/py/bbctrl/__init__.py
src/stylus/style.styl

index ebaa8c9e7299169fa5e1ad44f1d4fe1414f73204..bff502f65206f61aec47404d35c0754ab4c24667 100644 (file)
@@ -3,6 +3,13 @@ Buildbotics CNC Controller Firmware Change Log
 
 ## v0.3.10
  - Fixed "Flood" display, changed to "Load 1" and "Load 2".  #108
+ - Highlight loads when on.
+ - Fixed axis zeroing.
+ - Fixed bug in home position set after successful home.  #109
+ - Fixed ugly Web error dumps.
+ - Allow access to log file from Web.
+ - Rotate log so it does not grow too big.
+ - Keep same GCode file through browser reload.  #20
 
 ## v0.3.9
  - Fixed bug in move exec that was causing bumping between moves.
index bf37fa8e2d709995a812d334c9c33591140a3c82..8688abd9c2e54d3f8ea341e9141fb2029189b19f 100644 (file)
@@ -196,7 +196,7 @@ bool command_callback() {
   // Special processing for synchronous commands
   if (_is_synchronous(*block)) {
     if (estop_triggered()) status = STAT_MACHINE_ALARMED;
-    else if (state_is_flushing()) status = STAT_NOP; // Flush
+    else if (state_is_flushing()) status = STAT_NOP; // Flush command
     else if (state_is_resuming() || _space() < _size(*block))
       return false; // Wait
   }
@@ -208,7 +208,7 @@ bool command_callback() {
 
   // Reporting
   report_request();
-  if (status && status != STAT_NOP) status_error(status);
+  if (status && status != STAT_NOP) STATUS_ERROR(status, "");
 
   return true;
 }
index e9de9b99ddb2822ea9d3bf305f556bd1e0c05c4d..b18dbfd3c668fee0bf4de515a56e8b4318f24c34 100644 (file)
@@ -28,6 +28,7 @@
 CMD('$', var,       0, "Set or get variable")
 CMD('#', sync_var,  1, "Set variable synchronous")
 CMD('s', seek,      1, "[switch][flags:active|error]")
+CMD('a', set_axis,  1, "[axis][position] Set axis position")
 CMD('l', line,      1, "[targetVel][maxJerk][axes][times]")
 CMD('d', dwell,     1, "[seconds]")
 CMD('o', out,       1, "Output")
index ac9227bfe396e2171afa44b7b137bc251fca58ed..5c30f958c0aedfb98212bcfbb7fd780f91eccb9f 100644 (file)
 \******************************************************************************/
 
 #include "exec.h"
-#include "jog.h"
+
 #include "stepper.h"
+#include "motor.h"
 #include "axis.h"
-#include "spindle.h"
 #include "util.h"
 #include "command.h"
+#include "report.h"
 #include "config.h"
 
-#include <string.h>
-#include <float.h>
-
 
 static struct {
   exec_cb_t cb;
@@ -112,10 +110,58 @@ float get_axis_position(int axis) {return ex.position[axis];}
 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;}
-void set_axis_position(int axis, float p) {ex.position[axis] = p;}
 
 
 // Command callbacks
+typedef struct {
+  uint8_t axis;
+  float position;
+} set_axis_t;
+
+
+stat_t command_set_axis(char *cmd) {
+  cmd++; // Skip command name
+
+  // Decode axis
+  int axis = axis_get_id(*cmd++);
+  if (axis < 0) return STAT_INVALID_ARGUMENTS;
+
+  // Decode position
+  float position;
+  if (!decode_float(&cmd, &position)) return STAT_BAD_FLOAT;
+
+  // Check for end of command
+  if (*cmd) return STAT_INVALID_ARGUMENTS;
+
+  // Update command
+  command_set_axis_position(axis, position);
+
+  // Queue
+  set_axis_t set_axis = {axis, position};
+  command_push(COMMAND_set_axis, &set_axis);
+
+  return STAT_OK;
+}
+
+
+unsigned command_set_axis_size() {return sizeof(set_axis_t);}
+
+
+void command_set_axis_exec(void *data) {
+  set_axis_t *cmd = (set_axis_t *)data;
+
+  // Update exec
+  ex.position[cmd->axis] = cmd->position;
+
+  // Update motors
+  int motor = axis_get_motor(cmd->axis);
+  if (0 <= motor) motor_set_position(motor, cmd->position);
+
+  // Report
+  report_request();
+}
+
+
 stat_t command_opt_pause(char *cmd) {command_push(*cmd, 0); return STAT_OK;}
 unsigned command_opt_pause_size() {return 0;}
 void command_opt_pause_exec(void *data) {} // TODO pause if requested
index a4922092787d8ce9ce3581dc305738de662b921c..4241eb55369bd742e68dcf96bbdecf8384b8bd44 100644 (file)
@@ -63,11 +63,6 @@ const char *status_level_pgmstr(status_level_t level) {
 }
 
 
-stat_t status_error(stat_t code) {
-  return status_message_P(0, STAT_LEVEL_ERROR, code, 0);
-}
-
-
 stat_t status_message_P(const char *location, status_level_t level,
                         stat_t code, const char *msg, ...) {
   va_list args;
@@ -77,7 +72,7 @@ stat_t status_message_P(const char *location, status_level_t level,
            status_level_pgmstr(level));
 
   // Message
-  if (msg) {
+  if (msg && pgm_read_byte(msg)) {
     va_start(args, msg);
     vfprintf_P(stdout, msg, args);
     va_end(args);
index 4523804fcab7a60fbf6c1c38364ade978ca73110..54581682a324085cdb65e373f5092ce882ec98e8 100644 (file)
@@ -56,7 +56,6 @@ extern stat_t status_code;
 
 const char *status_to_pgmstr(stat_t code);
 const char *status_level_pgmstr(status_level_t level);
-stat_t status_error(stat_t code);
 stat_t status_message_P(const char *location, status_level_t level,
                         stat_t code, const char *msg, ...);
 
index 4603a14cde758e5457b3f216ceb3ce831b45ebae..003964919ede00c7d9e4acc0caab9370185498e4 100644 (file)
@@ -82,12 +82,6 @@ void stepper_init() {
 }
 
 
-void st_set_position(const float position[]) {
-  for (int motor = 0; motor < MOTORS; motor++)
-    motor_set_position(motor, position[motor_get_axis(motor)]);
-}
-
-
 void st_shutdown() {
   OUTCLR_PIN(MOTOR_ENABLE_PIN);
   st.dwell = 0;
index 424e69f692d3465102310606d1b9f80a0cfd3d5b..4ede34b67e57123def0beec0ea35a67dda882199 100644 (file)
@@ -32,7 +32,6 @@
 
 
 void stepper_init();
-void st_set_position(const float position[]);
 void st_shutdown();
 void st_enable();
 bool st_is_busy();
index f50ae7816f8dfb55a773f26fc23f7665137f716c..1c8426aea26311afc77f9adeb77742fb29f98981 100644 (file)
@@ -398,10 +398,6 @@ stat_t command_sync_var(char *cmd) {
 
   command_push(*cmd, &buffer);
 
-  // Special case for synchronizing axis position in command queue
-  if (info.set.set_f32_index == set_axis_position)
-    command_set_axis_position(buffer.index, buffer.value._f32);
-
   return STAT_OK;
 }
 
index b56c6a476b41c8a952759c483902314c8963dd59..d3c1247b16215eeddb53a9bbd9f8b9cb4062397d 100644 (file)
@@ -64,7 +64,7 @@ VAR(estop_switch,    ew, u8,    0,      0, 1, "Estop switch state")
 VAR(probe_switch,    pw, u8,    0,      0, 1, "Probe switch state")
 
 // Axis
-VAR(axis_position,    p, f32,   AXES,   1, 1, "Axis position")
+VAR(axis_position,    p, f32,   AXES,   0, 1, "Axis position")
 
 // Outputs
 VAR(output_active,   oa, bool,  OUTS,   1, 1, "Output pin active")
index 236e7a4850ebd4f61752a4aa2d8b073c15288740..ff0a9ac21d97b474ca9fc1597804d6f95466ed35 100644 (file)
@@ -53,6 +53,10 @@ script#admin-view-template(type="text/x-template")
         input(name="pass2", v-model="password2", type="password")
         button.pure-button.pure-button-primary(@click="set_password") Set
 
+    h2 Debugging
+    a(href="/api/log", target="_blank")
+      button.pure-button.pure-button-primary View Log
+
     h2 Configuration
     button.pure-button.pure-button-primary(@click="backup") Backup
 
index 1e5747c46ee4d417b44d9378c912e4f982ea2c77..bc8df6c8ca9cece5e22589b42473f6a04d931c54 100644 (file)
@@ -46,4 +46,4 @@ script#console-template(type="text/x-template")
           td {{msg.source || ''}}
           td {{msg.where  || ''}}
           td {{msg.repeat}}
-          td {{msg.msg}}
+          td.message {{msg.msg}}
index bc0011a703f61af235709e9e78d10627162c2768..ac9ff60088bd335ddd2490f12ffeb91fd1e3aefc 100644 (file)
@@ -164,7 +164,7 @@ script#control-view-template(type="text/x-template")
 
       button.pure-button(
         title="{{state.xx == 'RUNNING' ? 'Pause' : 'Start'}} program.",
-        @click="start_pause", :disabled="!file")
+        @click="start_pause", :disabled="!state.selected")
         .fa(:class="state.xx == 'RUNNING' ? 'fa-pause' : 'fa-play'")
 
       button.pure-button(title="Stop program.", @click="stop",
@@ -176,8 +176,8 @@ 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.xx != 'READY' && state.xx != 'HOLDING') || !file",
-        v-if="false")
+        :disabled="(state.xx != 'READY' && state.xx != 'HOLDING') || " +
+          "!state.selected", v-if="false")
         .fa.fa-step-forward
 
     .tabs
@@ -209,7 +209,7 @@ script#control-view-template(type="text/x-template")
             style="display:none", accept=".nc,.gcode,.gc,.ngc")
 
           button.pure-button(title="Delete current GCode program.",
-            @click="deleteGCode = true", :disabled="!file")
+            @click="deleteGCode = true", :disabled="!state.selected")
             .fa.fa-trash
 
           message(:show.sync="deleteGCode")
@@ -225,7 +225,7 @@ script#control-view-template(type="text/x-template")
                 | &nbsp;selected
 
           select(title="Select previously uploaded GCode programs.",
-            v-model="file", @change="load",
+            v-model="state.selected", @change="load",
             :disabled="state.xx == 'RUNNING' || state.xx == 'STOPPING'")
             option(v-for="file in files", :value="file") {{file}}
 
index 5fadc4510a0c06b67120f89b3f7b04c8699cbc23..ff7f8787b664a5f9e0d76a5a2070101ba82f79c7 100644 (file)
@@ -52,7 +52,6 @@ module.exports = {
   data: function () {
     return {
       mdi: '',
-      file: '',
       last_file: '',
       files: [],
       axes: 'xyzabc',
@@ -78,7 +77,8 @@ module.exports = {
 
 
   watch: {
-    'state.line': function () {this.update_gcode_line();}
+    'state.line': function () {this.update_gcode_line()},
+    'state.selected': function () {this.load()}
   },
 
 
@@ -87,11 +87,16 @@ module.exports = {
       var data = {};
       data[axis] = power;
       api.put('jog', data);
-    }
+    },
+
+    connected: function () {this.update()}
   },
 
 
-  ready: function () {this.update()},
+  ready: function () {
+    this.update();
+    this.load();
+  },
 
 
   methods: {
@@ -236,15 +241,7 @@ module.exports = {
 
     update: function () {
       // Update file list
-      api.get('file')
-        .done(function (files) {
-          var index = files.indexOf(this.file);
-          if (index == -1 && files.length) this.file = files[0];
-
-          this.files = files;
-
-          this.load()
-        }.bind(this))
+      api.get('file').done(function (files) {this.files = files}.bind(this))
     },
 
 
@@ -275,7 +272,7 @@ module.exports = {
 
       api.upload('file', fd)
         .done(function () {
-          this.file = file.name;
+          file.name;
           if (file.name == this.last_file) this.last_file = '';
           this.update();
         }.bind(this));
@@ -283,15 +280,7 @@ module.exports = {
 
 
     load: function () {
-      var file = this.file;
-
-      if (!file || this.files.indexOf(file) == -1) {
-        this.file = '';
-        this.all_gcode = [];
-        this.gcode = [];
-        return;
-      }
-
+      var file = this.state.selected;
       if (file == this.last_file) return;
 
       api.get('file/' + file)
@@ -307,7 +296,8 @@ module.exports = {
 
 
     deleteCurrent: function () {
-      if (this.file) api.delete('file/' + this.file).done(this.update);
+      if (this.state.selected)
+        api.delete('file/' + this.state.selected).done(this.update);
       this.deleteGCode = false;
     },
 
@@ -363,12 +353,12 @@ module.exports = {
     },
 
 
-    start: function () {api.put('start/' + this.file).done(this.update)},
+    start: function () {api.put('start')},
     pause: function () {api.put('pause')},
     unpause: function () {api.put('unpause')},
     optional_pause: function () {api.put('pause/optional')},
     stop: function () {api.put('stop')},
-    step: function () {api.put('step/' + this.file).done(this.update)},
+    step: function () {api.put('step')},
 
 
     override_feed: function () {api.put('override/feed/' + this.feed_override)},
index 4f2f0b54f3a4ff0cdc65259521e16bdd194bcd1d..de25a0c71028308bbf246a45ea2f4bab6228b608 100644 (file)
 ################################################################################
 
 import json
+import traceback
+import logging
+
 from tornado.web import RequestHandler, HTTPError
 import tornado.httpclient
 
 
+log = logging.getLogger('API')
+
+
 class APIHandler(RequestHandler):
     def __init__(self, app, request, **kwargs):
         super(APIHandler, self).__init__(app, request, **kwargs)
         self.ctrl = app.ctrl
 
 
+    # Override exception logging
+    def log_exception(self, typ, value, tb):
+        log.error(str(value))
+        trace = ''.join(traceback.format_exception(typ, value, tb))
+        log.debug(trace)
+
+
     def delete(self, *args, **kwargs):
         self.delete_ok(*args, **kwargs)
         self.write_json('ok')
index 8f978bd15ddb0b8211442ea8b0d54cd41fa1e672..79e3f31ef6e5c7507be91fe645452cd40464bd36 100644 (file)
@@ -38,6 +38,7 @@ log = logging.getLogger('Cmd')
 SET       = '$'
 SET_SYNC  = '#'
 SEEK      = 's'
+SET_AXIS  = 'a'
 LINE      = 'l'
 DWELL     = 'd'
 OUTPUT    = 'o'
@@ -80,6 +81,7 @@ def encode_axes(axes):
 
 
 def set(name, value): return '#%s=%s' % (name, value)
+def set_axis(axis, position): return SET_AXIS + axis + encode_float(position)
 
 
 def line(target, exitVel, maxAccel, maxJerk, times):
@@ -100,7 +102,6 @@ def line(target, exitVel, maxAccel, maxJerk, times):
 
 def tool(tool): return '#t=%d' % tool
 def speed(speed): return '#s=:' + encode_float(speed)
-def set_position(axis, value): return '#%sp=:%s' % (axis, encode_float(value))
 
 
 def output(port, value):
index 9c96a6de3d089c0e033aec54daef43c846512ab0..a8541535865637c1ace87f684861ac52a6ffe8de 100644 (file)
@@ -57,6 +57,9 @@ class FileHandler(bbctrl.APIHandler):
 
     def get(self, path):
         if path:
+            path = path[1:]
+            self.ctrl.mach.select(path)
+
             with open('upload/' + path, 'r') as f:
                 self.write_json(f.read())
             return
@@ -68,4 +71,8 @@ class FileHandler(bbctrl.APIHandler):
                 if os.path.isfile('upload/' + path):
                     files.append(path)
 
+        selected = self.ctrl.state.get('selected', '')
+        if not selected in files:
+            if len(files): self.ctrl.state.set('selected', files[0])
+
         self.write_json(files)
index c16efa768a517b791f7214b73fc39e3ddb4e6672..e8950a0550e49ba245a6b711f9c06042430e24b0 100644 (file)
@@ -80,7 +80,9 @@ class Jog(inevent.JogHandler):
                 axes = {}
                 for i in range(len(self.v)): axes["xyzabc"[i]] = self.v[i]
                 self.ctrl.mach.jog(axes)
-            except Exception as e: log.warning('Jog: %s', e)
+
+            except Exception as e:
+                log.warning('Jog: %s', e)
 
         self.ctrl.ioloop.call_later(0.25, self.callback)
 
index 418b3754e5c941429d7c3e21d5502e1e4db2e59f..722a477f59dbc59c0e4bfe9f13470477a6d2c91a 100644 (file)
@@ -77,7 +77,6 @@ class Mach():
             self.planner.update_position()
             self.ctrl.state.set('cycle', cycle)
 
-        elif current == 'homing' and cycle == 'mdi': pass
         elif current != cycle:
             raise Exception('Cannot enter %s cycle during %s' %
                             (cycle, current))
@@ -96,6 +95,7 @@ class Mach():
             # Resume once current queue of GCode commands has flushed
             self.comm.i2c_command(Cmd.FLUSH)
             self.comm.queue_command(Cmd.RESUME)
+            self.ctrl.state.set('line', 0)
             self.stopping = False
 
         # Update cycle
@@ -107,7 +107,7 @@ class Mach():
         if (state == 'HOLDING' and
             self.ctrl.state.get('pr', '') == 'Switch found' and
             self.planner.is_synchronizing()):
-            self.ctrl.mach.unpause()
+            self.unpause()
 
 
     def _comm_next(self):
@@ -118,7 +118,7 @@ class Mach():
 
 
     def _start_sending_gcode(self, path):
-        self.planner.load(path)
+        self.planner.load('upload/' + path)
         self.comm.set_write(True)
 
 
@@ -163,37 +163,47 @@ class Mach():
 
 
     def home(self, axis, position = None):
-        self._begin_cycle('homing')
-
         if position is not None:
             self.mdi('G28.3 %c%f' % (axis, position))
 
         else:
+            self._begin_cycle('homing')
+
             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):
-                    log.info('Cannot home ' + axis)
+                    log.warning('Cannot home ' + axis)
                     continue
 
                 log.info('Homing %s axis' % axis)
-                self.mdi(axis_homing_procedure % {'axis': axis})
+                self.planner.mdi(axis_homing_procedure % {'axis': axis})
+                self.comm.set_write(True)
 
 
     def estop(self): self.comm.i2c_command(Cmd.ESTOP)
     def clear(self): self.comm.i2c_command(Cmd.CLEAR)
 
 
-    def start(self, path):
+    def select(self, path):
+        if self.ctrl.state.get('selected', '') == path: return
+
+        if self._get_cycle() != 'idle':
+            raise Exception('Cannot select file during ' + self._get_cycle())
+
+        self.ctrl.state.set('selected', path)
+
+
+    def start(self):
         self._begin_cycle('running')
-        self._start_sending_gcode(path)
+        self._start_sending_gcode(self.ctrl.state.get('selected'))
 
 
-    def step(self, path):
+    def step(self):
         raise Exception('NYI') # TODO
         self.comm.i2c_command(Cmd.STEP)
-        if self._get_cycle() != 'running': self.start(path)
+        if self._get_cycle() != 'running': self.start()
 
 
     def stop(self):
@@ -220,6 +230,16 @@ class Mach():
 
 
     def set_position(self, axis, position):
-        if self.ctrl.state.is_axis_homed('%c' % axis):
-            self.mdi('G92 %c%f' % (axis, position))
-        else: self.comm.queue_command('$%cp=%f' % (axis, position))
+        axis = axis.lower()
+
+        if self.ctrl.state.is_axis_homed(axis):
+            self.mdi('G92 %s%f' % (axis, position))
+
+        else:
+            if self._get_cycle() != 'idle':
+                raise Exception('Cannot zero position during ' +
+                                self._get_cycle())
+
+            self._begin_cycle('mdi')
+            self.planner.set_position({axis: position})
+            self.comm.queue_command(Cmd.set_axis(axis, position))
index 44bdbca05c6466f365fd3b684cac0d1cac8984f6..b336b088df966d0c14ce4703e18c3477cabbf3aa 100644 (file)
@@ -54,6 +54,10 @@ class Planner():
     def is_synchronizing(self): return self.planner.is_synchronizing()
 
 
+    def set_position(self, position):
+        self.planner.set_position(position)
+
+
     def update_position(self):
         position = {}
 
@@ -62,7 +66,7 @@ class Planner():
             value = self.ctrl.state.get(axis + 'p', None)
             if value is not None: position[axis] = value
 
-        self.planner.set_position(position)
+        self.set_position(position)
 
 
     def _get_config_vector(self, name, scale):
@@ -166,7 +170,7 @@ class Planner():
                 self._queue_set_cmd(block['id'], 'load2state', value)
             if name[0:1] == '_' and name[1:2] in 'xyzabc' and \
                     name[2:] == '_home':
-                return Cmd.set_position(name[1], value)
+                return Cmd.set_axis(name[1], value)
 
             if len(name) and name[0] == '_':
                 self._queue_set_cmd(block['id'], name[1:], value)
@@ -216,7 +220,7 @@ class Planner():
 
     def load(self, path):
         log.info('GCode:' + path)
-        self.planner.load('upload' + path, self._get_config())
+        self.planner.load(path, self._get_config())
 
 
     def has_move(self): return self.planner.has_more()
index b9be542341a1598c3cd6ed061689cae510e49ba3..4f0c1a85cd96442bf6472412d3e4583280a04844 100644 (file)
@@ -82,6 +82,24 @@ class RebootHandler(bbctrl.APIHandler):
     def put_ok(self): subprocess.Popen('reboot')
 
 
+class LogHandler(tornado.web.RequestHandler):
+    def __init__(self, app, request, **kwargs):
+        super(LogHandler, self).__init__(app, request, **kwargs)
+        self.filename = app.ctrl.args.log
+
+
+    def get(self):
+        with open(self.filename, 'r') as f:
+            self.write(f.read())
+
+
+    def set_default_headers(self):
+        fmt = socket.gethostname() + '-%Y%m%d.log'
+        filename = datetime.date.today().strftime(fmt)
+        self.set_header('Content-Disposition', 'filename="%s"' % filename)
+        self.set_header('Content-Type', 'text/plain')
+
+
 class HostnameHandler(bbctrl.APIHandler):
     def get(self): self.write_json(socket.gethostname())
 
@@ -191,7 +209,7 @@ class HomeHandler(bbctrl.APIHandler):
 
 
 class StartHandler(bbctrl.APIHandler):
-    def put_ok(self, path): self.ctrl.mach.start(path)
+    def put_ok(self): self.ctrl.mach.start()
 
 
 class EStopHandler(bbctrl.APIHandler):
@@ -219,12 +237,12 @@ class OptionalPauseHandler(bbctrl.APIHandler):
 
 
 class StepHandler(bbctrl.APIHandler):
-    def put_ok(self, path): self.ctrl.mach.step(path)
+    def put_ok(self): self.ctrl.mach.step()
 
 
 class PositionHandler(bbctrl.APIHandler):
     def put_ok(self, axis):
-        self.ctrl.mach.set_position(ord(axis.lower()), self.json['position'])
+        self.ctrl.mach.set_position(axis, float(self.json['position']))
 
 
 class OverrideFeedHandler(bbctrl.APIHandler):
@@ -310,6 +328,7 @@ class Web(tornado.web.Application):
 
         handlers = [
             (r'/websocket', WSConnection),
+            (r'/api/log', LogHandler),
             (r'/api/reboot', RebootHandler),
             (r'/api/hostname', HostnameHandler),
             (r'/api/remote/username', UsernameHandler),
@@ -322,14 +341,14 @@ class Web(tornado.web.Application):
             (r'/api/upgrade', UpgradeHandler),
             (r'/api/file(/.+)?', bbctrl.FileHandler),
             (r'/api/home(/[xyzabcXYZABC](/set)?)?', HomeHandler),
-            (r'/api/start(/.+)', StartHandler),
+            (r'/api/start', StartHandler),
             (r'/api/estop', EStopHandler),
             (r'/api/clear', ClearHandler),
             (r'/api/stop', StopHandler),
             (r'/api/pause', PauseHandler),
             (r'/api/unpause', UnpauseHandler),
             (r'/api/pause/optional', OptionalPauseHandler),
-            (r'/api/step(/.+)', StepHandler),
+            (r'/api/step', StepHandler),
             (r'/api/position/([xyzabcXYZABC])', PositionHandler),
             (r'/api/override/feed/([\d.]+)', OverrideFeedHandler),
             (r'/api/override/speed/([\d.]+)', OverrideSpeedHandler),
@@ -354,3 +373,8 @@ class Web(tornado.web.Application):
             sys.exit(1)
 
         log.info('Listening on http://%s:%d/', ctrl.args.addr, ctrl.args.port)
+
+
+    # Override default logger
+    def log_request(self, handler):
+        log.info("%d %s", handler.get_status(), handler._request_summary())
index 65607a03b8448093101d907cfb998cfdaa92de6e..bd0fd5c3b87545a1ff5e659f4c868794a4e416a3 100644 (file)
@@ -33,6 +33,7 @@ import signal
 import tornado
 import argparse
 import logging
+import datetime
 
 from pkg_resources import Requirement, resource_filename
 
@@ -107,11 +108,16 @@ def run():
     root.addHandler(h)
 
     if args.log:
-        h = logging.FileHandler(args.log)
+        h = logging.handlers.RotatingFileHandler(args.log, maxBytes = 1000000,
+                                                 backupCount = 5)
         h.setLevel(level)
         h.setFormatter(f)
         root.addHandler(h)
 
+    # Log header
+    now = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
+    root.info('Log started ' + now)
+
     # Set signal handler
     signal.signal(signal.SIGTERM, on_exit)
 
index 8d93980389fb5a7d27668177e44e332b95d1ae01..ff31f1d7d28f60b1504316be4f71bf90cbdf4f56 100644 (file)
@@ -377,6 +377,9 @@ body
     max-height 400px
     overflow-y auto
 
+  .message
+    white-space pre
+
   table
     width 100%
     margin 0.5em 0