From: Joseph Coffland Date: Sat, 17 Nov 2018 01:04:58 +0000 (-0800) Subject: Allow jogging during program or user pause. X-Git-Url: https://git.buildbotics.com/?a=commitdiff_plain;h=00efc37921ecc0774ab9a55698269d96fab3aae7;p=bbctrl-firmware Allow jogging during program or user pause. --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d53fb6..c90fb20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/src/avr/src/jog.c b/src/avr/src/jog.c index a8ba510..16e5bf7 100644 --- a/src/avr/src/jog.c +++ b/src/avr/src/jog.c @@ -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] = diff --git a/src/avr/src/state.c b/src/avr/src/state.c index 4afbeaa..4b4b3c1 100644 --- a/src/avr/src/state.c +++ b/src/avr/src/state.c @@ -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 diff --git a/src/avr/src/state.h b/src/avr/src/state.h index e746994..871a8d1 100644 --- a/src/avr/src/state.h +++ b/src/avr/src/state.h @@ -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(); diff --git a/src/py/bbctrl/Comm.py b/src/py/bbctrl/Comm.py index 81c6ebc..4515576 100644 --- a/src/py/bbctrl/Comm.py +++ b/src/py/bbctrl/Comm.py @@ -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): diff --git a/src/py/bbctrl/Mach.py b/src/py/bbctrl/Mach.py index 8d83503..fdb07ca 100644 --- a/src/py/bbctrl/Mach.py +++ b/src/py/bbctrl/Mach.py @@ -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() diff --git a/src/py/bbctrl/Preplanner.py b/src/py/bbctrl/Preplanner.py index 129565d..7b31727 100644 --- a/src/py/bbctrl/Preplanner.py +++ b/src/py/bbctrl/Preplanner.py @@ -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):