From 3547642ff6a575f172dabd96b3188abb1cd23e94 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Fri, 2 Sep 2016 02:07:48 -0700 Subject: [PATCH] Correctly implemented start/stop/pause --- scripts/setup_rpi.sh | 7 +- src/jade/templates/control-view.jade | 28 +++-- src/js/control-view.js | 15 ++- src/py/bbctrl/AVR.py | 146 +++++++++++++-------------- src/py/bbctrl/FileHandler.py | 8 +- src/py/bbctrl/GCodeStream.py | 2 +- src/py/bbctrl/Web.py | 13 ++- src/stylus/style.styl | 5 +- 8 files changed, 119 insertions(+), 105 deletions(-) diff --git a/scripts/setup_rpi.sh b/scripts/setup_rpi.sh index d2c5be9..0b8545e 100755 --- a/scripts/setup_rpi.sh +++ b/scripts/setup_rpi.sh @@ -3,8 +3,7 @@ ID=2 # Update the system -apt-get update -apt-get dist-upgrade -y +apt-get update && apt-get dist-upgrade -y || exit 1 # Resize FS # TODO no /dev/root in Jessie @@ -93,6 +92,10 @@ cp bbctrl.init.d /etc/init.d/bbctrl chmod +x /etc/init.d/bbctrl update-rc.d bbctrl defaults +# Disable Pi 3 USART BlueTooth swap +echo -e "\ndtoverlay=pi3-disable-bt" >> /boot/config.txt +# sudo systemctl disable hciuart + # TODO setup input and serial device permissions in udev & forward 80 -> 8080 reboot diff --git a/src/jade/templates/control-view.jade b/src/jade/templates/control-view.jade index 0bee1d6..6e877d8 100644 --- a/src/jade/templates/control-view.jade +++ b/src/jade/templates/control-view.jade @@ -38,6 +38,12 @@ script#control-view-template(type="text/x-template") tr th Coolant td {{state.coolant || 'Off'}} + tr + th State + td {{state.x || ''}} + tr + th Cycle + td {{state.c || ''}} table.axes @@ -84,35 +90,37 @@ script#control-view-template(type="text/x-template") fieldset button.pure-button.pure-button-primary( title="Manually execute instructions.", @click="submit_mdi", - :disabled="running") MDI + :disabled="state.x != 'ready'") MDI input(v-model="mdi", @keyup.enter="submit_mdi") .toolbar button.pure-button(title="Home the machine.", @click="home", - :disabled="running") + :disabled="state.x != 'ready'") .fa.fa-home - button.pure-button(title="{{running ? 'Pause' : 'Start'}} program.", - @click="start_pause", :disabled="!file") - .fa(:class="running ? 'fa-pause' : 'fa-play'") + button.pure-button( + title="{{state.x == 'running' ? 'Pause' : 'Start'}} program.", + @click="start_pause", + :disabled="state.c == 'homing'") + .fa(:class="state.x == 'running' ? 'fa-pause' : 'fa-play'") button.pure-button(title="Stop program.", @click="stop", - :disabled="!running") + :disabled="state.x == 'ready'") .fa.fa-stop button.pure-button(title="Pause program at next optional stop (M1).", - @click="optional_pause", :disabled="!file") + @click="optional_pause", :disabled="state.c == 'homing'") .fa.fa-stop-circle-o button.pure-button(title="Execute one program step.", @click="step", - :disabled="running || !file") + :disabled="(state.x != 'ready' && state.x != 'holding') || !file") .fa.fa-step-forward .spacer button.pure-button(title="Upload a new program file.", @click="open", - :disabled="running") + :disabled="state.x != 'ready'") .fa.fa-folder-open input.gcode-file-input(type="file", @change="upload", @@ -123,7 +131,7 @@ script#control-view-template(type="text/x-template") .fa.fa-trash select(title="Select previously uploaded program files.", v-model="file", - @change="load", :disabled="running") + @change="load", :disabled="state.x != 'ready'") option(v-for="file in files", :value="file") {{file}} diff --git a/src/js/control-view.js b/src/js/control-view.js index 9441baf..d169c37 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -36,7 +36,8 @@ module.exports = { events: { jog: function (axis, move) {this.send('g91 g0' + axis + move)}, home: function (axis) {this.send('$home ' + axis)}, - zero: function (axis) {this.send('$zero ' + axis)} }, + zero: function (axis) {this.send('$zero ' + axis)} + }, ready: function () { @@ -58,7 +59,8 @@ module.exports = { estop: function () { - this.send('$es=' + (this.state.es ? 0 : 1)); + if (this.state.x == 'estopped') api.put('clear').done(this.update); + else api.put('estop').done(this.update); }, @@ -132,17 +134,12 @@ module.exports = { start_pause: function () { - if (this.running) this.pause(); + if (this.state.x == 'running') this.pause(); else this.start(); }, - start: function () { - if (!this.file) return; - api.put('start/' + this.file).done(this.update); - }, - - + start: function () {api.put('start').done(this.update)}, pause: function () {api.put('pause').done(this.update)}, optional_pause: function () {api.put('pause/optional').done(this.update)}, stop: function () {api.put('stop').done(this.update)}, diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py index c5abb3d..23c2108 100644 --- a/src/py/bbctrl/AVR.py +++ b/src/py/bbctrl/AVR.py @@ -4,6 +4,11 @@ import json import logging from collections import deque +try: + import smbus +except: + import smbus2 as smbus + import bbctrl @@ -12,26 +17,27 @@ log = logging.getLogger('AVR') # These constants must be kept in sync with i2c.h from the AVR code I2C_NULL = 0 I2C_ESTOP = 1 -I2C_PAUSE = 2 -I2C_OPTIONAL_PAUSE = 3 -I2C_RUN = 4 -I2C_FLUSH = 5 +I2C_CLEAR = 2 +I2C_PAUSE = 3 +I2C_OPTIONAL_PAUSE = 4 +I2C_RUN = 5 I2C_STEP = 6 -I2C_REPORT = 7 -I2C_HOME = 8 +I2C_FLUSH = 7 +I2C_REPORT = 8 +I2C_HOME = 9 +I2C_REBOOT = 10 class AVR(): def __init__(self, ctrl): self.ctrl = ctrl + self.state = 'init' self.vars = {} - self.state = 'idle' self.stream = None self.queue = deque() self.in_buf = '' self.command = None - self.flush_id = 1 try: self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud, @@ -48,82 +54,46 @@ class AVR(): self.i2c_bus = smbus.SMBus(ctrl.args.avr_port) self.i2c_addr = ctrl.args.avr_addr - except FileNotFoundError as e: + except FileNotFoundError as e: self.i2c_bus = None log.warning('Failed to open device: %s', e) self.report() - def _state_transition_error(self, state): - raise Exception('Cannot %s in %s state' % (state, self.state)) - - - def _state_transition(self, state, optional = False, step = False): - if state == self.state: return - - if state == 'idle': - if self.stream is not None: self.stream.reset() - - elif state == 'run': - if self.state in ['idle', 'pause'] and self.stream is not None: - self.set_write(True) - - if step: - self._i2c_command(I2C_STEP) - state = 'pause' - - else: self._i2c_command(I2C_RUN) - - else: self._state_transition_error(state) - - elif state == 'pause' - if self.state == 'run': - if optional: self._i2c_command(I2C_OPTIONAL_PAUSE) - else: self._i2c_command(I2C_PAUSE) - - else: self._state_transition_error(state) - - elif state == 'stop': - if self.state in ['run', 'pause']: self._flush() - else: self._state_transition_error(state) + def _start_sending_gcode(self): + if self.state == 'init': raise Exception('No file loaded') + self.state = 'streaming' + self.set_write(True) - elif state == 'estop': self._i2c_command(I2C_ESTOP) - elif state == 'home': - if self.state == 'idle': self._i2c_command(I2C_HOME) - else: self._state_transition_error(state) + def _stop_sending_gcode(self): + if self.state != 'streaming': return + if self.stream is not None: self.stream.reset() + self.state = 'idle' - else: raise Exception('Unrecognized state "%s"' % state) - self.state = state + def _i2c_command(self, cmd, word = None): + if not hasattr(self, 'i2c_bus'): return + log.info('I2C: %d' % cmd) - def _i2c_command(self, cmd, word = None): if word is not None: self.i2c_bus.write_word_data(self.i2c_addr, cmd, word) self.i2c_bus.write_byte(self.i2c_addr, cmd) - def _flush(self): - if self.stream is not None: self.stream.reset() - - self._i2c_command(I2C_FLUSH, word = self.flush_id) - self.queue_command('$end_flush %u' % self.flush_id) - - self.flush_id += 1 - if 1 << 16 <= self.flush_id: self.flush_id = 1 - - def report(self): self._i2c_command(I2C_REPORT) def load_next_command(self, cmd): - log.info(cmd) + log.info('Serial: ' + cmd) self.command = bytes(cmd.strip() + '\n', 'utf-8') def set_write(self, enable): + if not hasattr(self, 'sp'): return + flags = self.ctrl.ioloop.READ if enable: flags |= self.ctrl.ioloop.WRITE self.ctrl.ioloop.update_handler(self.sp, flags) @@ -152,10 +122,13 @@ class AVR(): if len(self.queue): self.load_next_command(self.queue.pop()) # Load next GCode command, if running or paused - elif self.state in ['run', 'pause'] and self.stream is not None: + elif self.state == 'streaming': cmd = self.stream.next() - if cmd is None: self.set_write(False) + if cmd is None: + self.set_write(False) + self.state = 'idle' + else: self.load_next_command(cmd) # Else stop writing @@ -181,8 +154,13 @@ class AVR(): try: msg = json.loads(line) - if 'firmware' in msg: self.report() - if 'es' in msg and msg['es']: self.estop() + if 'firmware' in msg: + log.error('AVR rebooted') + self._stop_sending_gcode() + self.report() + + if 'x' in msg and msg['x'] == 'estopped': + self._stop_sending_gcode() self.vars.update(msg) self.ctrl.web.broadcast(msg) @@ -197,22 +175,25 @@ class AVR(): self.set_write(True) - def load(self, path): - if self.stream is None: - self.stream = bbctrl.GCodeStream(path) + def open(self, path): + if self.state not in ['idle', 'init']: + raise Exception('Busy, cannot open new file') + + self.stream = bbctrl.GCodeStream(path) + self.state = 'idle' def mdi(self, cmd): - if self.state != 'idle': - raise Exception('Busy, cannot run MDI command') + if self.state == 'streaming': + raise Exception('Busy, cannot queue MDI command') self.queue_command(cmd) def jog(self, axes): - # TODO jogging via I2C + if self.state == 'streaming': raise Exception('Busy, cannot jog') - if self.state != 'idle': raise Exception('Busy, cannot jog') + # TODO jogging via I2C axes = ["{:6.5f}".format(x) for x in axes] self.queue_command('$jog ' + ' '.join(axes)) @@ -222,9 +203,22 @@ class AVR(): self.queue_command('${}{}={}'.format(index, code, value)) - def home(self): self._state_transition('home') - def start(self): self._state_transition('run') - def estop(self): self._state_transition('estop') - def stop(self): self._state_transition('stop') - def pause(self, opt): self._state_transition('pause', optional = opt) - def step(self): self._state_transition('run', step = True) + def home(self): self._i2c_command(I2C_HOME) + def estop(self): self._i2c_command(I2C_ESTOP) + def clear(self): self._i2c_command(I2C_CLEAR) + + + def start(self): + if self.state == 'idle': self._start_sending_gcode() + self._i2c_command(I2C_RUN) + + + def stop(self): + self._i2c_command(I2C_FLUSH) + self._stop_sending_gcode() + # Resume processing once current queue of GCode commands has flushed + self.queue_command('$resume') + + def pause(self): self._i2c_command(I2C_PAUSE) + def optional_pause(self): self._i2c_command(I2C_OPTIONAL_PAUSE) + def step(self): self._i2c_command(I2C_STEP) diff --git a/src/py/bbctrl/FileHandler.py b/src/py/bbctrl/FileHandler.py index c672acd..8f9704a 100644 --- a/src/py/bbctrl/FileHandler.py +++ b/src/py/bbctrl/FileHandler.py @@ -16,14 +16,20 @@ class FileHandler(bbctrl.APIHandler): if not os.path.exists('upload'): os.mkdir('upload') - with open('upload/' + gcode['filename'], 'wb') as f: + path ='upload/' + gcode['filename'] + + with open(path, 'wb') as f: f.write(gcode['body']) + self.ctrl.avr.open(path) + def get(self, path): if path: with open('upload/' + path, 'r') as f: self.write_json(f.read()) + + self.ctrl.avr.open(path) return files = [] diff --git a/src/py/bbctrl/GCodeStream.py b/src/py/bbctrl/GCodeStream.py index 5745f4c..d2d2e5f 100644 --- a/src/py/bbctrl/GCodeStream.py +++ b/src/py/bbctrl/GCodeStream.py @@ -53,7 +53,7 @@ class GCodeStream(): line = line.strip() # Append line number - line += ' N%d' % self.line self.line += 1 + line += ' N%d' % self.line return line diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index 7c55efd..436c027 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -25,23 +25,27 @@ class HomeHandler(bbctrl.APIHandler): class StartHandler(bbctrl.APIHandler): - def put_ok(self, path): self.ctrl.avr.start(path) + def put_ok(self): self.ctrl.avr.start() class EStopHandler(bbctrl.APIHandler): def put_ok(self): self.ctrl.avr.estop() +class ClearHandler(bbctrl.APIHandler): + def put_ok(self): self.ctrl.avr.clear() + + class StopHandler(bbctrl.APIHandler): def put_ok(self): self.ctrl.avr.stop() class PauseHandler(bbctrl.APIHandler): - def put_ok(self): self.ctrl.avr.pause(False) + def put_ok(self): self.ctrl.avr.pause() class OptionalPauseHandler(bbctrl.APIHandler): - def put_ok(self): self.ctrl.avr.pause(True) + def put_ok(self): self.ctrl.avr.optional_pause() class StepHandler(bbctrl.APIHandler): @@ -94,8 +98,9 @@ class Web(tornado.web.Application): (r'/api/save', SaveHandler), (r'/api/file(/.+)?', bbctrl.FileHandler), (r'/api/home', 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/pause/optional', OptionalPauseHandler), diff --git a/src/stylus/style.styl b/src/stylus/style.styl index 085484b..efa621a 100644 --- a/src/stylus/style.styl +++ b/src/stylus/style.styl @@ -204,10 +204,11 @@ body th, td padding 3px - - th text-align right + td + min-width 8em + .overrides clear both -- 2.27.0