Correctly implemented start/stop/pause
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Fri, 2 Sep 2016 09:07:48 +0000 (02:07 -0700)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Fri, 2 Sep 2016 09:07:48 +0000 (02:07 -0700)
scripts/setup_rpi.sh
src/jade/templates/control-view.jade
src/js/control-view.js
src/py/bbctrl/AVR.py
src/py/bbctrl/FileHandler.py
src/py/bbctrl/GCodeStream.py
src/py/bbctrl/Web.py
src/stylus/style.styl

index d2c5be9acac740404078ae8c1a0c7ca76f7f61df..0b8545e6e6eb731ff93dc10a3752ab14faad7c50 100755 (executable)
@@ -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
index 0bee1d647402d76d5be5e7b783dd00b80dc4ea10..6e877d84537221be7b45514262a596fa14fb8d71 100644 (file)
@@ -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}}
 
 
index 9441bafd4682acdecfe70515a9d906c1e3f35429..d169c3741170cd52a0d2a3d3b435a6700fd3e83c 100644 (file)
@@ -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)},
index c5abb3d6860b99f040ead863aeb0ac6f2b896102..23c210889600af08f5e9cb81668adc817d3ce1aa 100644 (file)
@@ -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)
index c672acd80ab686581c1e4a72a7044d919478be1b..8f9704add6a0a2efbd009f515a9d38b847f46a54 100644 (file)
@@ -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 = []
index 5745f4c4a379c7c05ebabbf25e2cf7a00c991c57..d2d2e5fed01e16bee9d76cf3d2c1c254c4ed8163 100644 (file)
@@ -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
index 7c55efd9e4b7ebeedc88dd55434809d85eab1520..436c02777785270ce338b8ceaed701b723f03cd5 100644 (file)
@@ -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),
index 085484bc2a84dcba83d581109f2537f7ca0cffa2..efa621aae4745f1acb6d67a466e96682e929d91e 100644 (file)
@@ -204,10 +204,11 @@ body
 
     th, td
       padding 3px
-
-    th
       text-align right
 
+    td
+      min-width 8em
+
   .overrides
     clear both