Allow jogging during program or user pause.
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 17 Nov 2018 01:04:58 +0000 (17:04 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 17 Nov 2018 01:04:58 +0000 (17:04 -0800)
CHANGELOG.md
src/avr/src/jog.c
src/avr/src/state.c
src/avr/src/state.h
src/py/bbctrl/Comm.py
src/py/bbctrl/Mach.py
src/py/bbctrl/Preplanner.py

index 8d53fb617b6055fd2ea3c289190de23956b2fa42..c90fb20c6eed328bd1acaf6c77ebcae034d1e18c 100644 (file)
@@ -5,6 +5,7 @@ Buildbotics CNC Controller Firmware Changelog
  - Suppress ``Auto-creating missing tool`` warning.
  - Prevent ``Stream is closed`` error.
  - Suppress ``WebGL not supported`` warning.
+ - Allow jogging during program or user pause.
 
 ## v0.4.1
  - Fix toolpath view axes bug.
index a8ba510e433b860bd8178dc5e39f5c03da87f2ed..16e5bf79b473db550f02951959fc17d136bf1da1 100644 (file)
@@ -43,6 +43,7 @@
 
 
 static struct {
+  bool holding;
   bool writing;
 
   SCurve scurves[AXES];
@@ -102,7 +103,8 @@ stat_t jog_exec() {
     command_reset_position();
     exec_set_velocity(0);
     exec_set_cb(0);
-    state_idle();
+    if (jr.holding) state_holding();
+    else state_idle();
 
     return STAT_NOP; // Done, no move executed
   }
@@ -124,8 +126,9 @@ void jog_stop() {
 
 
 stat_t command_jog(char *cmd) {
-  // Ignore jog commands when not READY and not JOGGING
-  if (state_get() != STATE_READY && state_get() != STATE_JOGGING)
+  // Ignore jog commands when not READY, HOLDING or JOGGING
+  if (state_get() != STATE_READY && state_get() != STATE_HOLDING &&
+      state_get() != STATE_JOGGING)
     return STAT_NOP;
 
   // Skip over command code
@@ -143,6 +146,8 @@ stat_t command_jog(char *cmd) {
   if (state_get() != STATE_JOGGING) {
     memset(&jr, 0, sizeof(jr));
 
+    jr.holding = state_get() == STATE_HOLDING;
+
     for (int axis = 0; axis < AXES; axis++)
       if (axis_is_enabled(axis))
         jr.scurves[axis] =
index 4afbeaa4ad1680ec1043e206f7d2253f0228e25f..4b4b3c17849ef71f5df8cfd9d0541e6bbd48682a 100644 (file)
@@ -90,17 +90,12 @@ static void _set_state(state_t state) {
 }
 
 
-static void _set_hold_reason(hold_reason_t reason) {
-  if (s.hold_reason == reason) return; // No change
-  s.hold_reason = reason;
-}
-
-
+static void _set_hold_reason(hold_reason_t reason) {s.hold_reason = reason;}
 bool state_is_flushing() {return s.flushing && !s.resuming;}
 bool state_is_resuming() {return s.resuming;}
 
 
-bool state_is_quiescent() {
+static bool _is_idle() {
   return (state_get() == STATE_READY || state_get() == STATE_HOLDING) &&
     !st_is_busy();
 }
@@ -156,7 +151,8 @@ void state_running() {
 
 
 void state_jogging() {
-  if (state_get() == STATE_READY) _set_state(STATE_JOGGING);
+  if (state_get() == STATE_READY || state_get() == STATE_HOLDING)
+    _set_state(STATE_JOGGING);
 }
 
 
@@ -188,15 +184,12 @@ void state_callback() {
     s.stop_requested = false;
   }
 
-  // Only flush queue when idle or holding
-  if (s.flushing && state_is_quiescent()) {
+  // Only flush queue when idle (READY or HOLDING)
+  if (s.flushing && _is_idle()) {
     command_flush_queue();
 
     // Resume
-    if (s.resuming) {
-      s.flushing = s.resuming = false;
-      _set_state(STATE_READY);
-    }
+    if (s.resuming) s.flushing = s.resuming = false;
   }
 
   // Unpause
index e7469943d9a28f683c65be0cc1dbe4c6ab74fb02..871a8d1afeb0e5a11ac38903e7465179e1fcfa04 100644 (file)
@@ -64,7 +64,6 @@ state_t state_get();
 
 bool state_is_flushing();
 bool state_is_resuming();
-bool state_is_quiescent();
 
 void state_seek_hold();
 void state_holding();
index 81c6ebc5b96dba9f65fb3706c2a550fbd707bb0b..451557600c20077198cd4f93fa3ad7eb81a72c90 100644 (file)
@@ -202,7 +202,9 @@ class Comm(object):
 
                 else:
                     self.ctrl.state.update(msg)
-                    if 'xx' in msg: self.ctrl.ready()
+                    if 'xx' in msg:           # State change
+                        self.ctrl.ready()     # We've received data from AVR
+                        self._set_write(True) # May have more data to send now
 
 
     def _serial_handler(self, fd, events):
index 8d83503a609b4b88146d54e0a1dff514eef7899d..fdb07ca427a22353fa35c255cae655c4c15ab2bb 100644 (file)
@@ -80,6 +80,7 @@ class Mach(Comm):
         self.ctrl = ctrl
         self.planner = bbctrl.Planner(ctrl)
         self.unpausing = False
+        self.last_cycle = 'idle'
 
         ctrl.state.set('cycle', 'idle')
         self._update_cycle_cb(False)
@@ -90,16 +91,25 @@ class Mach(Comm):
 
 
     def _get_state(self): return self.ctrl.state.get('xx', '')
+    def _is_holding(self): return self._get_state() == 'HOLDING'
+    def _get_pause_reason(self): return self.ctrl.state.get('pr', '')
     def _get_cycle(self): return self.ctrl.state.get('cycle')
 
 
+    def _can_jog(self):
+        return (self._get_cycle() == 'idle' or
+                (self._is_holding() and
+                 self._get_pause_reason() in ('User pause', 'Program pause')))
+
+
     def _begin_cycle(self, cycle):
         current = self._get_cycle()
         if current == cycle: return # No change
 
-        if current == 'idle':
+        if (current == 'idle' or (cycle == 'jogging' and self._can_jog())):
             self.planner.update_position()
             self.ctrl.state.set('cycle', cycle)
+            self.last_cycle = current
 
         else:
             raise Exception('Cannot enter %s cycle while in %s cycle' %
@@ -121,50 +131,43 @@ class Mach(Comm):
         # Handle EStop
         if update.get('xx', '') == 'ESTOPPED': self.planner.reset()
 
-        # Unpause sync
-        if update.get('xx', '') != 'HOLDING': self.unpausing = False
-
-        # Update cycle now, if it has changed
-        self._update_cycle()
-
         # Detect motor faults
         for motor in range(4):
             key = '%ddf' % motor
             if key in update and update[key] & 0x1f:
                 log.error(motor_fault_error % motor)
 
-        # Unpause
-        if (('xx' in update or 'pr' in update) and
-            self.ctrl.state.get('xx', '') == 'HOLDING'):
-            pause_reason = self.ctrl.state.get('pr', '')
+        # Update cycle now, if it has changed
+        self._update_cycle()
 
-            # Continue after seek hold
-            if (pause_reason == 'Switch found' and
-                self.planner.is_synchronizing()):
-                self._unpause()
+        # Unpause sync
+        if update.get('xx', 'HOLDING') != 'HOLDING': self.unpausing = False
 
-            # Continue after stop hold
-            if pause_reason == 'User stop':
-                self.planner.stop()
-                self.planner.update_position()
-                self.ctrl.state.set('line', 0)
-                self._unpause()
+        # Entering HOLDING state
+        if update.get('xx', '') == 'HOLDING':
+            # Always flush queue after pause
+            super().i2c_command(Cmd.FLUSH)
+            super().resume()
 
+            # Return from jogging cycle
+            if self._get_cycle() == 'jogging':
+                self.ctrl.state.set('cycle', self.last_cycle)
 
-    def _unpause(self):
-        if self._get_state() != 'HOLDING' or self.unpausing: return
-        self.unpausing = True
+        # Automatically unpause after seek or stop hold
+        if (('xx' in update or 'pr' in update) and self._is_holding() and
+            self._get_pause_reason() in ('Switch found', 'User stop')):
+            self._unpause()
 
-        pause_reason = self.ctrl.state.get('pr', '')
-        log.info('Unpause: ' + pause_reason)
 
-        if pause_reason in ['User pause', 'Switch found']:
-            self.planner.restart()
+    def _unpause(self):
+        pause_reason = self._get_pause_reason()
+        log.info('Unpause: ' + pause_reason)
 
-        if pause_reason in ['User pause', 'User stop', 'Switch found']:
-            super().i2c_command(Cmd.FLUSH)
-            super().resume()
+        if pause_reason == 'User stop':
+            self.planner.stop()
+            self.ctrl.state.set('line', 0)
 
+        else: self.planner.restart()
 
         super().i2c_command(Cmd.UNPAUSE)
 
@@ -183,7 +186,8 @@ class Mach(Comm):
 
     @overrides(Comm)
     def comm_next(self):
-        if self.planner.is_running(): return self.planner.next()
+        if self.planner.is_running() and not self._is_holding():
+            return self.planner.next()
 
 
     @overrides(Comm)
@@ -306,10 +310,10 @@ class Mach(Comm):
 
 
     def unpause(self):
-        if self._get_state() != 'HOLDING': return
+        if self._get_state() != 'HOLDING' or self.unpausing: return
 
-        pause_reason = self.ctrl.state.get('pr', '')
-        if pause_reason in ['User pause', 'Program pause']:
+        if self._get_pause_reason() in ('User pause', 'Program pause'):
+            self.unpausing = True
             self._unpause()
 
 
index 129565d5116889e5a720110e0dfa352420ba5cdd..7b317274116331eb63e3fdb30f3d53e4c09da961 100644 (file)
@@ -179,8 +179,9 @@ class Preplanner(object):
 
 
     def start(self):
-        log.info('Preplanner started')
-        self.started.set_result(True)
+        if not self.started.done():
+            log.info('Preplanner started')
+            self.started.set_result(True)
 
 
     def invalidate(self, filename):