- Fixed problem with selecting newly uploaded file.
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 24 Feb 2018 22:12:57 +0000 (14:12 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 24 Feb 2018 22:13:42 +0000 (14:13 -0800)
 - More thorough shutdown of stepper driver in estop.
 - Fixed spindle type specific options.
 - No more Unexpected AVR firmware reboot errors on estop clear.
 - Downgraded Machine alarmed - Command not processed errors to warnings.
 - Suppress unnecessary axis homing warnings.
 - More details for axis homing errors.a
 - Support GCode messages e.g. (MSG, Hello World!)
 - Support programmed pauses.  i.e. M0

28 files changed:
CHANGELOG.md
src/avr/src/command.c
src/avr/src/command.def
src/avr/src/commands.c
src/avr/src/drv8711.c
src/avr/src/drv8711.h
src/avr/src/exec.c
src/avr/src/huanyang.c
src/avr/src/line.c
src/avr/src/state.c
src/avr/src/state.h
src/avr/src/status.h
src/avr/src/stepper.c
src/avr/src/usart.c
src/avr/src/vars.def
src/jade/index.jade
src/js/app.js
src/js/console.js
src/js/control-view.js
src/py/bbctrl/Cmd.py
src/py/bbctrl/Comm.py
src/py/bbctrl/Config.py
src/py/bbctrl/FileHandler.py
src/py/bbctrl/Mach.py
src/py/bbctrl/Messages.py
src/py/bbctrl/Planner.py
src/py/bbctrl/State.py
src/py/bbctrl/Web.py

index 1fe4ecc0df8126d93038f2f278e80c175c61903d..0e1b7725dc7bbcd5cc0adbba74035d9e47cfe3da 100644 (file)
@@ -4,6 +4,15 @@ Buildbotics CNC Controller Firmware Change Log
 ## v0.3.12
  - Updated DB25 M2 breakout diagram.
  - Enabled AVR watchdog.
+ - Fixed problem with selecting newly uploaded file.
+ - More thorough shutdown of stepper driver in estop.
+ - Fixed spindle type specific options.
+ - No more ``Unexpected AVR firmware reboot`` errors on estop clear.
+ - Downgraded ``Machine alarmed - Command not processed`` errors to warnings.
+ - Suppress unnecessary axis homing warnings.
+ - More details for axis homing errors.
+ - Support GCode messages e.g. (MSG, Hello World!)
+ - Support programmed pauses.  i.e. M0
 
 ## v0.3.11
  - Supressed ``firmware rebooted`` warning.
index d11beab090af18502235a682fc3a06c012fa3724..4041f1c8792d5dfb5adf0dbac1dd4e7a35901f29 100644 (file)
@@ -207,7 +207,13 @@ bool command_callback() {
 
   // Reporting
   report_request();
-  if (status && status != STAT_NOP) STATUS_ERROR(status, "");
+
+  switch (status) {
+  case STAT_OK: break;
+  case STAT_NOP: break;
+  case STAT_MACHINE_ALARMED: STATUS_WARNING(status, ""); break;
+  default: STATUS_ERROR(status, ""); break;
+  }
 
   return true;
 }
index b18dbfd3c668fee0bf4de515a56e8b4318f24c34..53eca13aa6d68a51c2bff36195eac22812b1d9ca 100644 (file)
@@ -31,9 +31,7 @@ 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")
-CMD('p', opt_pause, 1, "Set an optional pause")
-CMD('P', pause,     0, "[optional]")
+CMD('P', pause,     1, "[type] Pause control")
 CMD('U', unpause,   0, "Unpause")
 CMD('j', jog,       0, "[axes]")
 CMD('r', report,    0, "<0|1>[var] Enable or disable var reporting")
index 3d5f092a8e835064b66fe17a1bb111bed92feb0e..59ad978d01a1e0da59d72ade52dc6e4a3242b0e2 100644 (file)
 \******************************************************************************/
 
 #include "config.h"
-#include "rtc.h"
 #include "stepper.h"
 #include "command.h"
 #include "vars.h"
 #include "base64.h"
 #include "hardware.h"
 #include "report.h"
-#include "state.h"
 #include "exec.h"
-#include "util.h"
 
-#include <string.h>
 #include <stdio.h>
 
 
@@ -64,12 +60,6 @@ void command_dwell_exec(float *seconds) {
 }
 
 
-// TODO
-stat_t command_out(char *cmd) {return STAT_OK;}
-unsigned command_out_size() {return 0;}
-void command_out_exec(void *data) {}
-
-
 stat_t command_help(char *cmd) {
   printf_P(PSTR("\n{\"commands\":{"));
   command_print_json();
index e9f77e67ce5d7711c62bf3cc14531fba7996519b..9deaf5b90ff99b724fb72a9fcf9ac31b9a5d009b 100644 (file)
@@ -366,6 +366,11 @@ void drv8711_init() {
 }
 
 
+void drv8711_shutdown() {
+  SPIC.INTCTRL = 0; // Disable SPI interrupts
+}
+
+
 drv8711_state_t drv8711_get_state(int driver) {
   if (driver < 0 || DRIVERS <= driver) return DRV8711_DISABLED;
   return drivers[driver].state;
index 3119eb3eb4999e7606abc8171907dff2b6300270..bbcb4ed3ba62b3a4b97480abd8d52f55dff9ceb3 100644 (file)
@@ -173,6 +173,7 @@ typedef void (*stall_callback_t)(int driver);
 
 
 void drv8711_init();
+void drv8711_shutdown();
 drv8711_state_t drv8711_get_state(int driver);
 void drv8711_set_state(int driver, drv8711_state_t state);
 void drv8711_set_microsteps(int driver, uint16_t msteps);
index 5c30f958c0aedfb98212bcfbb7fd780f91eccb9f..71be146cc74a1fb4a8b1d1f5e16feece38a6eb1b 100644 (file)
@@ -44,8 +44,6 @@ static struct {
   float accel;
   float jerk;
 
-  int tool;
-
   float feed_override;
   float spindle_override;
 } ex;
@@ -99,16 +97,13 @@ stat_t exec_next() {
 
 
 // Variable callbacks
-uint8_t get_tool() {return ex.tool;}
+float get_axis_position(int axis) {return ex.position[axis];}
 float get_velocity() {return ex.velocity / VELOCITY_MULTIPLIER;}
 float get_acceleration() {return ex.accel / ACCEL_MULTIPLIER;}
 float get_jerk() {return ex.jerk / JERK_MULTIPLIER;}
 float get_feed_override() {return ex.feed_override;}
-float get_speed_override() {return ex.spindle_override;}
-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;}
+float get_speed_override() {return ex.spindle_override;}
 void set_speed_override(float value) {ex.spindle_override = value;}
 
 
@@ -160,8 +155,3 @@ void command_set_axis_exec(void *data) {
   // 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 9e8c88bb1d0037ea43783a6b26f84bffd3a0cb3d..207f73622b5d639ca0dfb34ef39eaadffb10abdb 100644 (file)
@@ -325,14 +325,15 @@ static bool _check_response() {
     ha.response[ha.response_length - 2];
 
   if (computed != expected) {
-    STATUS_WARNING("huanyang: invalid CRC, expected=0x%04u got=0x%04u",
+    STATUS_WARNING(STAT_OK, "huanyang: invalid CRC, expected=0x%04u got=0x%04u",
                    expected, computed);
     return false;
   }
 
   // Check if response code matches the code we sent
   if (ha.command[1] != ha.response[1]) {
-    STATUS_WARNING("huanyang: invalid function code, expected=%u got=%u",
+    STATUS_WARNING(STAT_OK,
+                   "huanyang: invalid function code, expected=%u got=%u",
                    ha.command[2], ha.response[2]);
     return false;
   }
index 5f656a49b14d6681bb7fbfa229d58ab4dc7f824d..c73fae08f355042204cd1dba477b47b9c63e595c 100644 (file)
@@ -190,6 +190,10 @@ static stat_t _pause() {
     exec_set_jerk(0);
     _done();
 
+    // TODO it's possible to exit here and have no more moves
+    // Apparently this pause method can take longer to pause than the
+    // actual move.  FIX ME!!!
+
     return STAT_AGAIN;
   }
 
index 7a0b545fe864828a4a3627bf4eb762506430d0f2..37dda1760337f153e23babbc9b3544873373e170 100644 (file)
@@ -70,9 +70,6 @@ PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason) {
   switch (reason) {
   case HOLD_REASON_USER_PAUSE:    return PSTR("User paused");
   case HOLD_REASON_PROGRAM_PAUSE: return PSTR("Program paused");
-  case HOLD_REASON_PROGRAM_END:   return PSTR("Program end");
-  case HOLD_REASON_PALLET_CHANGE: return PSTR("Pallet change");
-  case HOLD_REASON_TOOL_CHANGE:   return PSTR("Tool change");
   case HOLD_REASON_STEPPING:      return PSTR("Stepping");
   case HOLD_REASON_SEEK:          return PSTR("Switch found");
   }
@@ -120,9 +117,9 @@ void state_seek_hold() {
 void state_holding() {_set_state(STATE_HOLDING);}
 
 
-void state_optional_pause() {
-  if (s.optional_pause_requested) {
-    _set_hold_reason(HOLD_REASON_USER_PAUSE);
+void state_pause(bool optional) {
+  if (!optional || s.optional_pause_requested) {
+    _set_hold_reason(HOLD_REASON_PROGRAM_PAUSE);
     state_holding();
   }
 }
@@ -215,12 +212,30 @@ PGM_P get_hold_reason() {return state_get_hold_reason_pgmstr(s.hold_reason);}
 
 // Command callbacks
 stat_t command_pause(char *cmd) {
-  if (cmd[1] == '1') s.optional_pause_requested = true;
-  else s.pause_requested = true;
+  pause_t type = (pause_t)(cmd[1] - '0');
+
+  switch (type) {
+  case PAUSE_USER: s.pause_requested = true; break;
+  case PAUSE_OPTIONAL: s.optional_pause_requested = true; break;
+  default: command_push(cmd[0], &type); break;
+  }
+
   return STAT_OK;
 }
 
 
+unsigned command_pause_size() {return sizeof(pause_t);}
+
+
+void command_pause_exec(void *data) {
+  switch (*(pause_t *)data) {
+  case PAUSE_PROGRAM: state_pause(false); break;
+  case PAUSE_PROGRAM_OPTIONAL: state_pause(true); break;
+  default: break;
+  }
+}
+
+
 stat_t command_unpause(char *cmd) {
   s.start_requested = true;
   return STAT_OK;
index 3a254ff529bb10d41c8704757f5937d53fe91b51..3d4059e8e4413ddc55174d87a6cf1f926e28a2c4 100644 (file)
@@ -45,14 +45,19 @@ typedef enum {
 typedef enum {
   HOLD_REASON_USER_PAUSE,
   HOLD_REASON_PROGRAM_PAUSE,
-  HOLD_REASON_PROGRAM_END,
-  HOLD_REASON_PALLET_CHANGE,
-  HOLD_REASON_TOOL_CHANGE,
   HOLD_REASON_STEPPING,
   HOLD_REASON_SEEK,
 } hold_reason_t;
 
 
+typedef enum {
+  PAUSE_USER,
+  PAUSE_OPTIONAL,
+  PAUSE_PROGRAM,
+  PAUSE_PROGRAM_OPTIONAL,
+} pause_t;
+
+
 PGM_P state_get_pgmstr(state_t state);
 PGM_P state_get_hold_reason_pgmstr(hold_reason_t reason);
 
@@ -64,7 +69,6 @@ bool state_is_quiescent();
 
 void state_seek_hold();
 void state_holding();
-void state_optional_pause();
 void state_running();
 void state_jogging();
 void state_idle();
index 54581682a324085cdb65e373f5092ce882ec98e8..d1cab7bbee234f5858d9ba5447b30a86c28fb32e 100644 (file)
@@ -76,8 +76,8 @@ stat_t status_alarm(const char *location, stat_t status, const char *msg);
 #define STATUS_DEBUG(MSG, ...)                                  \
   STATUS_MESSAGE(STAT_LEVEL_DEBUG, STAT_OK, MSG, ##__VA_ARGS__)
 
-#define STATUS_WARNING(MSG, ...)                                \
-  STATUS_MESSAGE(STAT_LEVEL_WARNING, STAT_OK, MSG, ##__VA_ARGS__)
+#define STATUS_WARNING(CODE, MSG, ...)                          \
+  STATUS_MESSAGE(STAT_LEVEL_WARNING, CODE, MSG, ##__VA_ARGS__)
 
 #define STATUS_ERROR(CODE, MSG, ...)                            \
   STATUS_MESSAGE(STAT_LEVEL_ERROR, CODE, MSG, ##__VA_ARGS__)
index 003964919ede00c7d9e4acc0caab9370185498e4..984245d0bd1f1831585ff63ea53d84c9b0d667b9 100644 (file)
@@ -35,6 +35,7 @@
 #include "cpp_magic.h"
 #include "report.h"
 #include "exec.h"
+#include "drv8711.h"
 
 #include <string.h>
 #include <stdio.h>
@@ -82,10 +83,18 @@ void stepper_init() {
 }
 
 
+static void _end_move() {
+  for (int motor = 0; motor < MOTORS; motor++)
+    motor_end_move(motor);
+}
+
+
 void st_shutdown() {
-  OUTCLR_PIN(MOTOR_ENABLE_PIN);
-  st.dwell = 0;
-  st.move_type = MOVE_TYPE_NULL;
+  OUTCLR_PIN(MOTOR_ENABLE_PIN); // Disable motors
+  TIMER_STEP.CTRLA = 0;         // Stop stepper clock
+  _end_move();                  // Stop motor clocks
+  ADCB_CH0_INTCTRL = 0;         // Disable next move interrupt
+  drv8711_shutdown();           // Disable drivers
 }
 
 
@@ -136,21 +145,8 @@ static void _request_exec_move() {
 }
 
 
-static void _end_move() {
-  for (int motor = 0; motor < MOTORS; motor++)
-    motor_end_move(motor);
-}
-
-
 /// Dwell or dequeue and load next move.
 static void _load_move() {
-  // Check EStop
-  if (estop_triggered()) {
-    st.move_type = MOVE_TYPE_NULL;
-    _end_move();
-    return;
-  }
-
   // New clock period
   TIMER_STEP.PER = st.clock_period;
 
index 12aa002d47a2e7ac53fb7e9ac69401c926cf0f9e..06a8e1af816d7c20c0d62f61d4bf7617d249af1d 100644 (file)
@@ -264,8 +264,3 @@ int16_t usart_rx_space() {return rx_buf_space();}
 int16_t usart_rx_fill() {return rx_buf_fill();}
 int16_t usart_tx_space() {return tx_buf_space();}
 int16_t usart_tx_fill() {return tx_buf_fill();}
-
-
-// Var callbacks
-bool get_echo() {return usart_is_set(USART_ECHO);}
-void set_echo(bool value) {return usart_set(USART_ECHO, value);}
index 3193d9f605cc41c59d5d548abb624d41e6254aab..fb859831d7b016ade3691d8a39a2e4fea32a1773 100644 (file)
@@ -101,7 +101,6 @@ VAR(hy_connected,    he, bool,  0,      0, 1, "Huanyang connected")
 // Machine state
 VAR(id,              id, u32,   0,      1, 1, "Last executed command ID")
 VAR(speed,           s,  f32,   0,      1, 1, "Current spindle speed")
-VAR(tool,            t,  u8,    0,      1, 1, "Current tool")
 VAR(feed_override,   fo, f32,   0,      1, 1, "Feed rate override")
 VAR(speed_override,  so, f32,   0,      1, 1, "Spindle speed override")
 
@@ -110,7 +109,6 @@ VAR(velocity,        v,  f32,   0,      0, 1, "Current velocity")
 VAR(acceleration,   ax,  f32,   0,      0, 1, "Current acceleration")
 VAR(jerk,            j,  f32,   0,      0, 1, "Current jerk")
 VAR(hw_id,          hid, str,   0,      0, 1, "Hardware ID")
-VAR(echo,            ec, bool,  0,      1, 1, "Enable or disable echo")
 VAR(estop,           es, bool,  0,      1, 1, "Emergency stop")
 VAR(estop_reason,    er, pstr,  0,      0, 1, "Emergency stop reason")
 VAR(state,           xx, pstr,  0,      0, 1, "Machine state")
index 9aa0f08d58199f873efd99db4dcc1b8166a31b59..a23d5ede4cbc5c6e492d9b2dd93a5ab593397a7e 100644 (file)
@@ -159,6 +159,16 @@ html(lang="en")
       h3(slot="header") Firmware upgrading
       p(slot="body") Please wait...
 
+    message(:show.sync="showMessages")
+      h3(slot="header") GCode message
+
+      div(slot="body")
+        ul
+          li(v-for="msg in messages") {{msg}}
+
+      div(slot="footer")
+        button.pure-button.button-success(@click="close_messages") OK
+
     #templates
       include ../../build/templates.jade
 
index ac15dc6690c6c79588503e7faadb5e38a3d24693..ef8ef552430c95626273c10d4d9ebe261e09339a 100644 (file)
@@ -70,7 +70,8 @@ module.exports = new Vue({
       checkedUpgrade: false,
       firmwareName: '',
       latestVersion: '',
-      password: ''
+      password: '',
+      showMessages: false
     }
   },
 
@@ -237,11 +238,13 @@ 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) {
-            if (key == 'msg') this.$broadcast('message', msg.msg);
+            if (key == 'log') this.$broadcast('log', msg.log);
+            else if (key == 'message') this.add_message(msg.message);
             else Vue.set(this.state, key, msg[key]);
           }
+        }
       }.bind(this)
 
       this.sock.onopen = function (e) {
@@ -279,6 +282,18 @@ module.exports = new Vue({
       }.bind(this)).fail(function (error) {
         alert('Save failed: ' + error);
       });
+    },
+
+
+    add_message: function (msg) {
+      this.messages.unshift(msg);
+      this.showMessages = true;
+    },
+
+
+    close_messages: function () {
+      this.showMessages = false;
+      this.messages.splice(0, this.messages.length);
     }
   }
 })
index f10b44210be989c046ef6fd68342594be04b2177..80659b73d6f710e6793fde19ac04e78f127a67c0 100644 (file)
@@ -48,7 +48,7 @@ module.exports = {
 
 
   events: {
-    message: function (msg) {
+    log: function (msg) {
       // There may be multiple instances of this module so ignore messages
       // that have already been processed.
       if (msg.logged) return;
index ff7f8787b664a5f9e0d76a5a2070101ba82f79c7..5edd3186ed03ad7ea6e61e36ad05e20d661008fb 100644 (file)
@@ -241,7 +241,10 @@ module.exports = {
 
     update: function () {
       // Update file list
-      api.get('file').done(function (files) {this.files = files}.bind(this))
+      api.get('file').done(function (files) {
+        this.files = files;
+        this.load();
+      }.bind(this))
     },
 
 
@@ -281,7 +284,7 @@ module.exports = {
 
     load: function () {
       var file = this.state.selected;
-      if (file == this.last_file) return;
+      if (typeof file == 'undefined' || file == this.last_file) return;
 
       api.get('file/' + file)
         .done(function (data) {
index 79e3f31ef6e5c7507be91fe645452cd40464bd36..6a4e1b4393a97145c001f8e2f514841a47c55eb6 100644 (file)
@@ -41,7 +41,6 @@ SEEK      = 's'
 SET_AXIS  = 'a'
 LINE      = 'l'
 DWELL     = 'd'
-OUTPUT    = 'o'
 OPT_PAUSE = 'p'
 PAUSE     = 'P'
 UNPAUSE   = 'U'
@@ -100,7 +99,6 @@ def line(target, exitVel, maxAccel, maxJerk, times):
     return cmd
 
 
-def tool(tool): return '#t=%d' % tool
 def speed(speed): return '#s=:' + encode_float(speed)
 
 
@@ -111,7 +109,17 @@ def output(port, value):
 
 
 def dwell(seconds): return 'd' + encode_float(seconds)
-def pause(optional = False): 'P' + ('1' if optional else '0')
+
+
+def pause(type):
+    if type == 'program': type = 2
+    elif type == 'optional': type = 3
+    elif type == 'pallet-change': type = 2
+    else: raise Exception('Unknown pause type "%s"' % type)
+
+    return 'P%d' % type
+
+
 def jog(axes): return 'j' + encode_axes(axes)
 
 
index 7183cc2afcb9405aea7df03a29d4d8f0c2830caf..2646a45fcdb7d93400fcbc3670465bd2084e95d2 100644 (file)
@@ -210,6 +210,22 @@ class Comm():
             log.warning('Serial handler error: %s', traceback.format_exc())
 
 
+    def estop(self):
+        if self.ctrl.state.get('xx', '') != 'ESTOPPED':
+            self.i2c_command(Cmd.ESTOP)
+
+
+    def clear(self):
+        if self.ctrl.state.get('xx', '') == 'ESTOPPED':
+            self.i2c_command(Cmd.CLEAR)
+            self.reboot_expected = True
+
+
+    def pause(self, optional = False):
+        data = ord('1' if optional else '0')
+        self.i2c_command(Cmd.PAUSE, byte = data)
+
+
     def reboot(self):
         self.queue_command(Cmd.REBOOT)
         self.reboot_expected = True
index 71c605a7f20849f6a2cf69a0701ccaa4b3c2f664..53627740dd5b02db4d04aca9ceed9f4653bf6b79 100644 (file)
@@ -41,13 +41,7 @@ default_config = {
         {"axis": "Y"},
         {"axis": "Z"},
         {"axis": "A", "power-mode" : "disabled"},
-        ],
-    "switches": {},
-    "outputs": {},
-    "tool": {},
-    "gcode": {},
-    "planner": {},
-    "admin": {},
+        ]
     }
 
 
@@ -64,6 +58,11 @@ class Config(object):
                       encoding = 'utf-8') as f:
                 self.template = json.load(f)
 
+            # Add all sections from template to default config
+            for section in self.template:
+                if not section in default_config:
+                    default_config[section] = {}
+
         except Exception as e: log.exception(e)
 
 
index a8541535865637c1ace87f684861ac52a6ffe8de..6bff7602676af1d97a70132c48b051044ea8e5d2 100644 (file)
@@ -54,14 +54,17 @@ class FileHandler(bbctrl.APIHandler):
         with open(path, 'wb') as f:
             f.write(gcode['body'])
 
+        self.ctrl.state.set('selected', gcode['filename'])
+
 
     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())
+
+            self.ctrl.mach.select(path)
             return
 
         files = []
index 69a261a4d3590e2b90956dd611d8899ea57e79e7..2695d9aaf1188a0cc156aa5c0efb237b177e1eeb 100644 (file)
@@ -88,22 +88,27 @@ class Mach():
         # Handle EStop
         if 'xx' in update and state == 'ESTOPPED':
             self._stop_sending_gcode()
+            self.stopping = False
 
         # Handle stop
-        if self.stopping and 'xx' in update and state == 'HOLDING':
-            self._stop_sending_gcode()
-            # 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
+        if self.stopping:
+            if state == 'READY' and not self.planner.is_running():
+                self.stopping = False
+
+            if state == 'HOLDING':
+                self._stop_sending_gcode()
+                # 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
         if (self._get_cycle() != 'idle' and not self.planner.is_busy() and
             not self.comm.is_active() and state == 'READY'):
             self.ctrl.state.set('cycle', 'idle')
 
-        # Automatically unpause on seek hold
+        # Continue after seek hold
         if (state == 'HOLDING' and
             self.ctrl.state.get('pr', '') == 'Switch found' and
             self.planner.is_synchronizing()):
@@ -163,6 +168,8 @@ class Mach():
 
 
     def home(self, axis, position = None):
+        state = self.ctrl.state
+
         if position is not None:
             self.mdi('G28.3 %c%f' % (axis, position))
 
@@ -173,17 +180,29 @@ class Mach():
             else: axes = '%c' % axis
 
             for axis in axes:
-                if not self.ctrl.state.axis_can_home(axis):
-                    log.warning('Cannot home ' + axis)
+                # If this is not a request to home a specific axis and the
+                # axis is disabled or in manual homing mode, don't show any
+                # warnings
+                if 1 < len(axes) and (
+                        not state.is_axis_enabled(axis) or
+                        state.axis_homing_mode(axis) == 'manual'):
                     continue
 
+                # Error when axes cannot be homed
+                reason = state.axis_home_fail_reason(axis)
+                if reason is not None:
+                    log.error('Cannot home %s axis: %s' % (
+                        axis.upper(), reason))
+                    continue
+
+                # Home axis
                 log.info('Homing %s 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 estop(self): self.comm.estop()
+    def clear(self): self.comm.clear()
 
 
     def select(self, path):
@@ -211,22 +230,24 @@ class Mach():
         self.stopping = True
 
 
-    def pause(self): self.comm.i2c_command(Cmd.PAUSE, byte = 0)
+    def pause(self): self.comm.pause()
 
 
     def unpause(self):
         if self.ctrl.state.get('xx', '') != 'HOLDING': return
 
-        self.comm.i2c_command(Cmd.FLUSH)
-        self.comm.queue_command(Cmd.RESUME)
-        self.planner.restart()
-        self.comm.set_write(True)
+        pause_reason = self.ctrl.state.get('pr', '')
+        if pause_reason in ['User paused', 'Switch found']:
+            self.comm.i2c_command(Cmd.FLUSH)
+            self.comm.queue_command(Cmd.RESUME)
+            self.planner.restart()
+            self.comm.set_write(True)
+
         self.comm.i2c_command(Cmd.UNPAUSE)
 
 
     def optional_pause(self):
-        if self._get_cycle() == 'running':
-            self.comm.i2c_command(Cmd.PAUSE, byte = 1)
+        if self._get_cycle() == 'running': self.comm.pause(True)
 
 
     def set_position(self, axis, position):
index 930b628b9afb837e62427676c19793cf77d6579a..b6ae5955f2a5ab399f628f3dfde399d849e26340 100644 (file)
@@ -50,6 +50,11 @@ class Messages(logging.Handler):
     def remove_listener(self, listener): self.listeners.remove(listener)
 
 
+    def broadcast(self, msg):
+        for listener in self.listeners:
+            listener(msg)
+
+
     # From logging.Handler
     def emit(self, record):
         if record.levelno == logging.INFO: return
@@ -61,5 +66,4 @@ class Messages(logging.Handler):
         if hasattr(record, 'where'): msg['where'] = record.where
         else: msg['where'] = '%s:%d' % (record.filename, record.lineno)
 
-        for listener in self.listeners:
-            listener(msg)
+        self.broadcast({'log': msg})
index b336b088df966d0c14ce4703e18c3477cabbf3aa..bb08e56c98eb2458e4690b5e07b65ce1c93a9ff3 100644 (file)
@@ -111,7 +111,10 @@ class Planner():
         # Apply all set commands <= to ID and those that follow consecutively
         while len(self.setq) and self.setq[0][0] - 1 <= self.lastID:
             id, name, value = self.setq.popleft()
-            self.ctrl.state.set(name, value)
+
+            if name == 'message': self.ctrl.msgs.broadcast({'message': value})
+            else: self.ctrl.state.set(name, value)
+
             if id == self.lastID + 1: self.lastID = id
 
 
@@ -161,16 +164,19 @@ class Planner():
         if type == 'set':
             name, value = block['name'], block['value']
 
-            if name == 'line': self._queue_set_cmd(block['id'], name, value)
-            if name == 'tool': return Cmd.tool(value)
+            if name in ['message', 'line', 'tool']:
+                self._queue_set_cmd(block['id'], name, value)
+
             if name == 'speed': return Cmd.speed(value)
+
             if name == '_mist':
                 self._queue_set_cmd(block['id'], 'load1state', value)
+
             if name == '_flood':
                 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_axis(name[1], value)
+
+            if (name[0:1] == '_' and name[1:2] in 'xyzabc' and
+                name[2:] == '_home'): return Cmd.set_axis(name[1], value)
 
             if len(name) and name[0] == '_':
                 self._queue_set_cmd(block['id'], name[1:], value)
@@ -181,7 +187,7 @@ class Planner():
             return Cmd.output(block['port'], int(float(block['value'])))
 
         if type == 'dwell': return Cmd.dwell(block['seconds'])
-        if type == 'pause': return Cmd.pause(block['optional'])
+        if type == 'pause': return Cmd.pause(block['pause-type'])
         if type == 'seek':
             return Cmd.seek(block['switch'], block['active'], block['error'])
 
index 5229767901b9efc29dc3ed701e6cb4ae0d6d7968..ef794966b61ba42c42a4dce9b572a3bd920bea7f 100644 (file)
@@ -159,8 +159,7 @@ class State(object):
                 return motor
 
 
-    def is_axis_homed(self, axis):
-        return self.get('%s_homed' % axis, False)
+    def is_axis_homed(self, axis): return self.get('%s_homed' % axis, False)
 
 
     def is_axis_enabled(self, axis):
@@ -168,33 +167,50 @@ class State(object):
         return False if motor is None else self.motor_enabled(motor)
 
 
-    def axis_can_home(self, axis):
+    def axis_homing_mode(self, axis):
         motor = self.find_motor(axis)
-        if motor is None: return False
-        if not self.motor_enabled(motor): return False
+        if motor is None: return 'disabled'
+        return self.motor_homing_mode(motor)
 
-        homing_mode = self.motor_homing_mode(motor)
-        if homing_mode == 1: return bool(int(self.get(axis + '_ls'))) # min sw
-        if homing_mode == 2: return bool(int(self.get(axis + '_xs'))) # max sw
-        return False
+
+    def axis_home_fail_reason(self, axis):
+        motor = self.find_motor(axis)
+        if motor is None: return 'Not mapped to motor'
+        if not self.motor_enabled(motor): return 'Motor disabled'
+
+        mode = self.motor_homing_mode(motor)
+
+        if mode == 'manual': return 'Configured for manual homing'
+
+        if mode == 'switch-min' and not int(self.get(axis + '_ls')):
+            return 'Configured for min switch but switch is disabled'
+
+        if mode == 'switch-max' and not int(self.get(axis + '_xs')):
+            return 'Configured for max switch but switch is disabled'
 
 
     def motor_enabled(self, motor):
         return bool(int(self.vars.get('%dpm' % motor, 0)))
 
 
-    def motor_homing_mode(self, motor): return int(self.vars['%dho' % motor])
+    def motor_homing_mode(self, motor):
+        mode = str(self.vars.get('%dho' % motor, 0))
+        if mode == '0': return 'manual'
+        if mode == '1': return 'switch-min'
+        if mode == '2': return 'switch-max'
+        raise Exception('Unrecognized homing mode "%s"' % mode)
 
 
     def motor_home_direction(self, motor):
-        homing_mode = self.motor_homing_mode(motor)
-        if homing_mode == 1: return -1 # Switch min
-        if homing_mode == 2: return 1  # Switch max
+        mode = self.motor_homing_mode(motor)
+        if mode == 'switch-min': return -1
+        if mode == 'switch-max': return 1
         return 0 # Disabled
 
 
     def motor_home_position(self, motor):
-        homing_mode = self.motor_homing_mode(motor)
-        if homing_mode == 1: return self.vars['%dtn' % motor] # Min soft limit
-        if homing_mode == 2: return self.vars['%dtm' % motor] # Max soft limit
+        mode = self.motor_homing_mode(motor)
+        # Return soft limit positions
+        if mode == 'switch-min': return self.vars['%dtn' % motor]
+        if mode == 'switch-max': return self.vars['%dtm' % motor]
         return 0 # Disabled
index 4f0c1a85cd96442bf6472412d3e4583280a04844..29796a3f38b827abe6253e89d161e7f3f9f2f8b1 100644 (file)
@@ -274,21 +274,20 @@ class ClientConnection(object):
         self.count += 1
 
 
-    def notify(self, msg): self.send(dict(msg = msg))
     def send(self, msg): raise HTTPError(400, 'Not implemented')
 
 
     def on_open(self, *args, **kwargs):
         self.timer = self.ctrl.ioloop.call_later(3, self.heartbeat)
         self.ctrl.state.add_listener(self.send)
-        self.ctrl.msgs.add_listener(self.notify)
+        self.ctrl.msgs.add_listener(self.send)
         self.is_open = True
 
 
     def on_close(self):
         self.ctrl.ioloop.remove_timeout(self.timer)
         self.ctrl.state.remove_listener(self.send)
-        self.ctrl.msgs.remove_listener(self.notify)
+        self.ctrl.msgs.remove_listener(self.send)
 
 
     def on_message(self, data): self.ctrl.mach.mdi(data)