Split AVR.py in to Mach.py and Comm.py
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Thu, 22 Feb 2018 08:04:37 +0000 (00:04 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Thu, 22 Feb 2018 08:04:37 +0000 (00:04 -0800)
src/py/bbctrl/AVR.py [deleted file]
src/py/bbctrl/Comm.py [new file with mode: 0644]
src/py/bbctrl/Ctrl.py
src/py/bbctrl/Jog.py
src/py/bbctrl/Mach.py [new file with mode: 0644]
src/py/bbctrl/Planner.py
src/py/bbctrl/State.py
src/py/bbctrl/Web.py
src/py/bbctrl/__init__.py

diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py
deleted file mode 100644 (file)
index ab7c8df..0000000
+++ /dev/null
@@ -1,392 +0,0 @@
-################################################################################
-#                                                                              #
-#                This file is part of the Buildbotics firmware.                #
-#                                                                              #
-#                  Copyright (c) 2015 - 2018, Buildbotics LLC                  #
-#                             All rights reserved.                             #
-#                                                                              #
-#     This file ("the software") is free software: you can redistribute it     #
-#     and/or modify it under the terms of the GNU General Public License,      #
-#      version 2 as published by the Free Software Foundation. You should      #
-#      have received a copy of the GNU General Public License, version 2       #
-#     along with the software. If not, see <http://www.gnu.org/licenses/>.     #
-#                                                                              #
-#     The software is distributed in the hope that it will be useful, but      #
-#          WITHOUT ANY WARRANTY; without even the implied warranty of          #
-#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       #
-#               Lesser General Public License for more details.                #
-#                                                                              #
-#       You should have received a copy of the GNU Lesser General Public       #
-#                License along with the software.  If not, see                 #
-#                       <http://www.gnu.org/licenses/>.                        #
-#                                                                              #
-#                For information regarding this software email:                #
-#                  "Joseph Coffland" <joseph@buildbotics.com>                  #
-#                                                                              #
-################################################################################
-
-import re
-import serial
-import json
-import time
-import logging
-import traceback
-from collections import deque
-
-import bbctrl
-import bbctrl.Cmd as Cmd
-
-log = logging.getLogger('AVR')
-
-
-# Axis homing procedure:
-#
-#   Mark axis unhomed
-#   Seek closed (home_dir * (travel_max - travel_min) * 1.5) at search_velocity
-#   Seek open (home_dir * -latch_backoff) at latch_vel
-#   Seek closed (home_dir * latch_backoff * 1.5) at latch_vel
-#   Rapid to (home_dir * -zero_backoff + position)
-#   Mark axis homed and set absolute position
-
-axis_homing_procedure = '''
-  G28.2 %(axis)s0 F[#<_%(axis)s_sv> * 1000]
-  G38.6 %(axis)s[#<_%(axis)s_hd> * [#<_%(axis)s_tm> - #<_%(axis)s_tn>] * 1.5]
-  G38.8 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_lb>] F[#<_%(axis)s_lv> * 1000]
-  G38.6 %(axis)s[#<_%(axis)s_hd> * #<_%(axis)s_lb> * 1.5]
-  G91 G0 G53 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_zb>]
-  G90 G28.3 %(axis)s[#<_%(axis)s_hp>]
-'''
-
-
-
-class AVR():
-    def __init__(self, ctrl):
-        self.ctrl = ctrl
-
-        self.queue = deque()
-        self.in_buf = ''
-        self.command = None
-        self.stopping = False
-
-        self.lcd_page = ctrl.lcd.add_new_page()
-        self.install_page = True
-
-        ctrl.state.add_listener(self._update_state)
-
-        try:
-            self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud,
-                                    rtscts = 1, timeout = 0, write_timeout = 0)
-            self.sp.nonblocking()
-
-        except Exception as e:
-            self.sp = None
-            log.warning('Failed to open serial port: %s', e)
-
-        if self.sp is not None:
-            ctrl.ioloop.add_handler(self.sp, self._serial_handler,
-                                    ctrl.ioloop.READ)
-
-        self.i2c_addr = ctrl.args.avr_addr
-        self._queue_command(Cmd.REBOOT)
-
-
-    def _is_busy(self): return self.ctrl.planner.is_running()
-
-
-    def _i2c_command(self, cmd, byte = None, word = None):
-        log.info('I2C: ' + cmd)
-        retry = 5
-        cmd = ord(cmd[0])
-
-        while True:
-            try:
-                self.ctrl.i2c.write(self.i2c_addr, cmd, byte, word)
-                break
-
-            except Exception as e:
-                retry -= 1
-
-                if retry:
-                    log.warning('AVR I2C failed, retrying: %s' % e)
-                    time.sleep(0.1)
-                    continue
-
-                else:
-                    log.error('AVR I2C failed: %s' % e)
-                    raise
-
-
-    def _start_sending_gcode(self, path):
-        self.ctrl.planner.load(path)
-        self._set_write(True)
-
-
-    def _stop_sending_gcode(self): self.ctrl.planner.reset()
-
-
-    def _set_write(self, enable):
-        if self.sp is None: return
-
-        flags = self.ctrl.ioloop.READ
-        if enable: flags |= self.ctrl.ioloop.WRITE
-        self.ctrl.ioloop.update_handler(self.sp, flags)
-
-
-    def _load_next_command(self, cmd):
-        log.info('< ' + json.dumps(cmd).strip('"'))
-        self.command = bytes(cmd.strip() + '\n', 'utf-8')
-
-
-    def _queue_command(self, cmd):
-        self.queue.append(cmd)
-        self._set_write(True)
-
-
-    def _serial_handler(self, fd, events):
-        try:
-            if self.ctrl.ioloop.READ & events: self._serial_read()
-            if self.ctrl.ioloop.WRITE & events: self._serial_write()
-        except Exception as e:
-            log.warning('Serial handler error: %s', traceback.format_exc())
-
-
-    def _serial_write(self):
-        # Finish writing current command
-        if self.command is not None:
-            try:
-                count = self.sp.write(self.command)
-
-            except Exception as e:
-                self._set_write(False)
-                raise e
-
-            self.command = self.command[count:]
-            if len(self.command): return # There's more
-            self.command = None
-
-        # Load next command from queue
-        if len(self.queue): self._load_next_command(self.queue.popleft())
-
-        # Load next GCode command, if running or paused
-        elif self._is_busy():
-            cmd = self.ctrl.planner.next()
-
-            if cmd is None: self._set_write(False)
-            else: self._load_next_command(cmd)
-
-        # Else stop writing
-        else: self._set_write(False)
-
-
-    def _serial_read(self):
-        try:
-            data = self.sp.read(self.sp.in_waiting)
-            self.in_buf += data.decode('utf-8')
-
-        except Exception as e:
-            log.warning('%s: %s', e, data)
-
-        update = {}
-
-        # Parse incoming serial data into lines
-        while True:
-            i = self.in_buf.find('\n')
-            if i == -1: break
-            line = self.in_buf[0:i].strip()
-            self.in_buf = self.in_buf[i + 1:]
-
-            if line:
-                log.info('> ' + line)
-
-                try:
-                    msg = json.loads(line)
-
-                except Exception as e:
-                    log.warning('%s, data: %s', e, line)
-                    continue
-
-                if 'variables' in msg:
-                    try:
-                        self.ctrl.state.machine_cmds_and_vars(msg)
-                        self._queue_command('D') # Refresh all vars
-
-                    except Exception as e:
-                        log.warning('AVR reload failed: %s',
-                                    traceback.format_exc())
-                        self.ctrl.ioloop.call_later(1, self.connect)
-
-                    continue
-
-                # AVR logging
-                if 'msg' in msg:
-                    level = msg.get('level', 'info')
-                    if 'where' in msg: extra = {'where': msg['where']}
-                    else: extra = None
-                    msg = msg['msg']
-
-                    if   level == 'info':    log.info(msg,    extra = extra)
-                    elif level == 'debug':   log.debug(msg,   extra = extra)
-                    elif level == 'warning': log.warning(msg, extra = extra)
-                    elif level == 'error':   log.error(msg,   extra = extra)
-
-                    continue
-
-                update.update(msg)
-
-        if update:
-            if 'firmware' in update:
-                log.warning('firmware rebooted')
-                self.connect()
-
-            if self.stopping and 'xx' in update and update['xx'] == 'HOLDING':
-                self._stop_sending_gcode()
-                # Resume once current queue of GCode commands has flushed
-                self._i2c_command(Cmd.FLUSH)
-                self._queue_command(Cmd.RESUME)
-                self.stopping = False
-
-            self.ctrl.state.update(update)
-
-            # Must be after AVR vars have loaded
-            if self.install_page:
-                self.install_page = False
-                self.ctrl.lcd.set_current_page(self.lcd_page.id)
-
-
-    def _update_state(self, update):
-        if 'xx' in update and update['xx'] == 'ESTOPPED':
-            self._stop_sending_gcode()
-
-        self._update_lcd(update)
-
-
-    def _update_lcd(self, update):
-        if 'xx' in update:
-            self.lcd_page.text('%-9s' % self.ctrl.state.get('xx'), 0, 0)
-
-        # Show enabled axes
-        row = 0
-        for axis in 'xyzabc':
-            motor = self.ctrl.state.find_motor(axis)
-            if motor is not None:
-                if (axis + 'p') in update:
-                    self.lcd_page.text('% 10.3f%s' % (
-                            update[axis + 'p'], axis.upper()), 9, row)
-
-                row += 1
-
-        # Show tool, units, feed and speed
-        if 'tool'  in update: self.lcd_page.text('%2uT' % update['tool'],  6, 1)
-        if 'units' in update: self.lcd_page.text('%-6s' % update['units'], 0, 1)
-        if 'feed'  in update: self.lcd_page.text('%8uF' % update['feed'],  0, 2)
-        if 'speed' in update: self.lcd_page.text('%8dS' % update['speed'], 0, 3)
-
-
-    def connect(self):
-        try:
-            # Reset AVR communication
-            self._stop_sending_gcode()
-            # Resume once current queue of GCode commands has flushed
-            self._queue_command(Cmd.RESUME)
-            self._queue_command('h') # Load AVR commands and variables
-
-        except Exception as e:
-            log.warning('Connect failed: %s', e)
-            self.ctrl.ioloop.call_later(1, self.connect)
-
-
-    def set(self, code, value):
-        self._queue_command('${}={}'.format(code, value))
-
-
-    def mdi(self, cmd):
-        if len(cmd) and cmd[0] == '$':
-            equal = cmd.find('=')
-            if equal == -1:
-                log.info('%s=%s' % (cmd, self.ctrl.state.get(cmd[1:])))
-
-            else:
-                name, value = cmd[1:equal], cmd[equal + 1:]
-
-                if value.lower() == 'true': value = True
-                elif value.lower() == 'false': value = False
-                else:
-                    try:
-                        value = float(value)
-                    except: pass
-
-                self.ctrl.state.config(name, value)
-
-        elif len(cmd) and cmd[0] == '\\': self._queue_command(cmd[1:])
-
-        else:
-            self.ctrl.planner.mdi(cmd)
-            self._set_write(True)
-
-
-    def jog(self, axes):
-        if self._is_busy(): raise Exception('Busy, cannot jog')
-        self._queue_command(Cmd.jog(axes))
-
-
-    def home(self, axis, position = None):
-        if self._is_busy(): raise Exception('Busy, cannot home')
-
-        if position is not None:
-            self.mdi('G28.3 %c%f' % (axis, position))
-
-        else:
-            if axis is None: axes = 'zxyabc' # TODO This should be configurable
-            else: axes = '%c' % axis
-
-            for axis in axes:
-                if not self.ctrl.state.axis_can_home(axis):
-                    log.info('Cannot home ' + axis)
-                    continue
-
-                log.info('Homing %s axis' % axis)
-                self.mdi(axis_homing_procedure % {'axis': axis})
-
-
-    def estop(self): self._i2c_command(Cmd.ESTOP)
-    def clear(self): self._i2c_command(Cmd.CLEAR)
-
-
-    def start(self, path):
-        if path: self._start_sending_gcode(path)
-
-
-    def step(self, path):
-        self._i2c_command(Cmd.STEP)
-        if not self._is_busy() and path and \
-                self.ctrl.state.get('xx', '') == 'READY':
-            self._start_sending_gcode(path)
-
-
-    def stop(self):
-        self.pause()
-        self.stopping = True
-
-
-    def pause(self): self._i2c_command(Cmd.PAUSE, byte = 0)
-
-
-    def unpause(self):
-        if self.ctrl.state.get('xx', '') != 'HOLDING': return
-
-        self._i2c_command(Cmd.FLUSH)
-        self._queue_command(Cmd.RESUME)
-        self.ctrl.planner.restart()
-        self._set_write(True)
-        self._i2c_command(Cmd.UNPAUSE)
-
-
-    def optional_pause(self): self._i2c_command(Cmd.PAUSE, byte = 1)
-
-
-    def set_position(self, axis, position):
-        if self._is_busy(): raise Exception('Busy, cannot set position')
-
-        if self.ctrl.state.is_axis_homed('%c' % axis):
-            self.mdi('G92 %c%f' % (axis, position))
-        else: self._queue_command('$%cp=%f' % (axis, position))
diff --git a/src/py/bbctrl/Comm.py b/src/py/bbctrl/Comm.py
new file mode 100644 (file)
index 0000000..4422c1b
--- /dev/null
@@ -0,0 +1,240 @@
+################################################################################
+#                                                                              #
+#                This file is part of the Buildbotics firmware.                #
+#                                                                              #
+#                  Copyright (c) 2015 - 2018, Buildbotics LLC                  #
+#                             All rights reserved.                             #
+#                                                                              #
+#     This file ("the software") is free software: you can redistribute it     #
+#     and/or modify it under the terms of the GNU General Public License,      #
+#      version 2 as published by the Free Software Foundation. You should      #
+#      have received a copy of the GNU General Public License, version 2       #
+#     along with the software. If not, see <http://www.gnu.org/licenses/>.     #
+#                                                                              #
+#     The software is distributed in the hope that it will be useful, but      #
+#          WITHOUT ANY WARRANTY; without even the implied warranty of          #
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       #
+#               Lesser General Public License for more details.                #
+#                                                                              #
+#       You should have received a copy of the GNU Lesser General Public       #
+#                License along with the software.  If not, see                 #
+#                       <http://www.gnu.org/licenses/>.                        #
+#                                                                              #
+#                For information regarding this software email:                #
+#                  "Joseph Coffland" <joseph@buildbotics.com>                  #
+#                                                                              #
+################################################################################
+
+import serial
+import logging
+import json
+import time
+import traceback
+from collections import deque
+
+import bbctrl
+import bbctrl.Cmd as Cmd
+
+log = logging.getLogger('Comm')
+
+
+class Comm():
+    def __init__(self, ctrl):
+        self.ctrl = ctrl
+
+        self.listeners = []
+        self.queue = deque()
+        self.in_buf = ''
+        self.command = None
+
+        ctrl.state.add_listener(self._update_state)
+
+        try:
+            self.sp = serial.Serial(ctrl.args.serial, ctrl.args.baud,
+                                    rtscts = 1, timeout = 0, write_timeout = 0)
+            self.sp.nonblocking()
+
+        except Exception as e:
+            self.sp = None
+            log.warning('Failed to open serial port: %s', e)
+
+        if self.sp is not None:
+            ctrl.ioloop.add_handler(self.sp, self._serial_handler,
+                                    ctrl.ioloop.READ)
+
+        self.i2c_addr = ctrl.args.avr_addr
+
+
+    def add_listener(self, listener):
+        self.listeners.append(listener)
+
+
+    def start_sending_gcode(self, path):
+        self.ctrl.planner.load(path)
+        self.set_write(True)
+
+
+    def stop_sending_gcode(self): self.ctrl.planner.reset()
+
+
+    def i2c_command(self, cmd, byte = None, word = None):
+        log.info('I2C: ' + cmd)
+        retry = 5
+        cmd = ord(cmd[0])
+
+        while True:
+            try:
+                self.ctrl.i2c.write(self.i2c_addr, cmd, byte, word)
+                break
+
+            except Exception as e:
+                retry -= 1
+
+                if retry:
+                    log.warning('AVR I2C failed, retrying: %s' % e)
+                    time.sleep(0.1)
+                    continue
+
+                else:
+                    log.error('AVR I2C failed: %s' % e)
+                    raise
+
+
+    def set_write(self, enable):
+        if self.sp is None: return
+
+        flags = self.ctrl.ioloop.READ
+        if enable: flags |= self.ctrl.ioloop.WRITE
+        self.ctrl.ioloop.update_handler(self.sp, flags)
+
+
+    def _update_state(self, update):
+        if 'xx' in update and update['xx'] == 'ESTOPPED':
+            self.stop_sending_gcode()
+
+
+    def _load_next_command(self, cmd):
+        log.info('< ' + json.dumps(cmd).strip('"'))
+        self.command = bytes(cmd.strip() + '\n', 'utf-8')
+
+
+    def queue_command(self, cmd):
+        self.queue.append(cmd)
+        self.set_write(True)
+
+
+    def _serial_write(self):
+        # Finish writing current command
+        if self.command is not None:
+            try:
+                count = self.sp.write(self.command)
+
+            except Exception as e:
+                self.set_write(False)
+                raise e
+
+            self.command = self.command[count:]
+            if len(self.command): return # There's more
+            self.command = None
+
+        # Load next command from queue
+        if len(self.queue): self._load_next_command(self.queue.popleft())
+
+        # Load next GCode command, if running or paused
+        elif self.ctrl.planner.is_running():
+            cmd = self.ctrl.planner.next()
+
+            if cmd is None: self.set_write(False)
+            else: self._load_next_command(cmd)
+
+        # Else stop writing
+        else: self.set_write(False)
+
+
+    def _update_vars(self, msg):
+        try:
+            self.ctrl.state.machine_cmds_and_vars(msg)
+            self.queue_command(Cmd.DUMP) # Refresh all vars
+
+        except Exception as e:
+            log.warning('AVR reload failed: %s', traceback.format_exc())
+            self.ctrl.ioloop.call_later(1, self.connect)
+
+
+    def _log_msg(self, msg):
+        level = msg.get('level', 'info')
+        if 'where' in msg: extra = {'where': msg['where']}
+        else: extra = None
+        msg = msg['msg']
+
+        if   level == 'info':    log.info(msg,    extra = extra)
+        elif level == 'debug':   log.debug(msg,   extra = extra)
+        elif level == 'warning': log.warning(msg, extra = extra)
+        elif level == 'error':   log.error(msg,   extra = extra)
+
+
+    def _serial_read(self):
+        try:
+            data = self.sp.read(self.sp.in_waiting)
+            self.in_buf += data.decode('utf-8')
+
+        except Exception as e:
+            log.warning('%s: %s', e, data)
+
+        update = {}
+
+        # Parse incoming serial data into lines
+        while True:
+            i = self.in_buf.find('\n')
+            if i == -1: break
+            line = self.in_buf[0:i].strip()
+            self.in_buf = self.in_buf[i + 1:]
+
+            if line:
+                log.info('> ' + line)
+
+                try:
+                    msg = json.loads(line)
+
+                except Exception as e:
+                    log.warning('%s, data: %s', e, line)
+                    continue
+
+                if 'variables' in msg:
+                    self._update_vars(msg)
+                    continue
+
+                if 'msg' in msg:
+                    self._log_msg(msg)
+                    continue
+
+                if 'firmware' in msg:
+                    log.warning('firmware rebooted')
+                    self.connect()
+
+                update.update(msg)
+
+        if update:
+            for listener in self.listeners: listener(update)
+
+
+    def _serial_handler(self, fd, events):
+        try:
+            if self.ctrl.ioloop.READ & events: self._serial_read()
+            if self.ctrl.ioloop.WRITE & events: self._serial_write()
+        except Exception as e:
+            log.warning('Serial handler error: %s', traceback.format_exc())
+
+
+    def connect(self):
+        try:
+            # Reset AVR communication
+            self.stop_sending_gcode()
+
+            # Resume once current queue of GCode commands has flushed
+            self.queue_command(Cmd.RESUME)
+            self.queue_command(Cmd.HELP) # Load AVR commands and variables
+
+        except Exception as e:
+            log.warning('Connect failed: %s', e)
+            self.ctrl.ioloop.call_later(1, self.connect)
index 9a1ca5881575bad473f1a2aa8bbb587abcb24187..c327a865f3f0190e13bc1850827f2693d1de3759 100644 (file)
@@ -68,11 +68,11 @@ class Ctrl(object):
             self.planner = bbctrl.Planner(self)
             self.i2c = bbctrl.I2C(args.i2c_port)
             self.lcd = bbctrl.LCD(self)
-            self.avr = bbctrl.AVR(self)
+            self.mach = bbctrl.Mach(self)
             self.jog = bbctrl.Jog(self)
             self.pwr = bbctrl.Pwr(self)
 
-            self.avr.connect()
+            self.mach.comm.connect()
 
             self.lcd.add_new_page(IPPage(self.lcd))
 
index dbcdd6ce6030c9c2b9248601389e24b34c66dc55..c16efa768a517b791f7214b73fc39e3ddb4e6672 100644 (file)
@@ -79,7 +79,7 @@ class Jog(inevent.JogHandler):
             try:
                 axes = {}
                 for i in range(len(self.v)): axes["xyzabc"[i]] = self.v[i]
-                self.ctrl.avr.jog(axes)
+                self.ctrl.mach.jog(axes)
             except Exception as e: log.warning('Jog: %s', e)
 
         self.ctrl.ioloop.call_later(0.25, self.callback)
diff --git a/src/py/bbctrl/Mach.py b/src/py/bbctrl/Mach.py
new file mode 100644 (file)
index 0000000..f99b4a8
--- /dev/null
@@ -0,0 +1,213 @@
+################################################################################
+#                                                                              #
+#                This file is part of the Buildbotics firmware.                #
+#                                                                              #
+#                  Copyright (c) 2015 - 2018, Buildbotics LLC                  #
+#                             All rights reserved.                             #
+#                                                                              #
+#     This file ("the software") is free software: you can redistribute it     #
+#     and/or modify it under the terms of the GNU General Public License,      #
+#      version 2 as published by the Free Software Foundation. You should      #
+#      have received a copy of the GNU General Public License, version 2       #
+#     along with the software. If not, see <http://www.gnu.org/licenses/>.     #
+#                                                                              #
+#     The software is distributed in the hope that it will be useful, but      #
+#          WITHOUT ANY WARRANTY; without even the implied warranty of          #
+#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       #
+#               Lesser General Public License for more details.                #
+#                                                                              #
+#       You should have received a copy of the GNU Lesser General Public       #
+#                License along with the software.  If not, see                 #
+#                       <http://www.gnu.org/licenses/>.                        #
+#                                                                              #
+#                For information regarding this software email:                #
+#                  "Joseph Coffland" <joseph@buildbotics.com>                  #
+#                                                                              #
+################################################################################
+
+import logging
+
+import bbctrl
+import bbctrl.Cmd as Cmd
+import bbctrl.Comm
+
+log = logging.getLogger('Mach')
+
+
+# Axis homing procedure:
+#
+#   Mark axis unhomed
+#   Seek closed (home_dir * (travel_max - travel_min) * 1.5) at search_velocity
+#   Seek open (home_dir * -latch_backoff) at latch_vel
+#   Seek closed (home_dir * latch_backoff * 1.5) at latch_vel
+#   Rapid to (home_dir * -zero_backoff + position)
+#   Mark axis homed and set absolute position
+
+axis_homing_procedure = '''
+  G28.2 %(axis)s0 F[#<_%(axis)s_sv> * 1000]
+  G38.6 %(axis)s[#<_%(axis)s_hd> * [#<_%(axis)s_tm> - #<_%(axis)s_tn>] * 1.5]
+  G38.8 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_lb>] F[#<_%(axis)s_lv> * 1000]
+  G38.6 %(axis)s[#<_%(axis)s_hd> * #<_%(axis)s_lb> * 1.5]
+  G91 G0 G53 %(axis)s[#<_%(axis)s_hd> * -#<_%(axis)s_zb>]
+  G90 G28.3 %(axis)s[#<_%(axis)s_hp>]
+'''
+
+
+
+class Mach():
+    def __init__(self, ctrl):
+        self.ctrl = ctrl
+        self.comm = bbctrl.Comm(ctrl)
+
+        self.stopping = False
+
+        self.comm.add_listener(self._comm_update)
+        self.comm.add_listener(self.ctrl.state.update)
+        ctrl.state.add_listener(self._update_lcd)
+
+        self.lcd_page = ctrl.lcd.add_new_page()
+        self.install_page = True
+
+        self.comm.queue_command(Cmd.REBOOT)
+
+
+    def _is_busy(self): return self.ctrl.planner.is_running()
+
+
+    def _comm_update(self, update):
+        if self.stopping and 'xx' in update and update['xx'] == 'HOLDING':
+            self.comm.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.stopping = False
+
+        # Must be after machine vars have loaded
+        if self.install_page:
+            self.install_page = False
+            self.ctrl.lcd.set_current_page(self.lcd_page.id)
+
+
+    def _update_lcd(self, update):
+        if 'xx' in update:
+            self.lcd_page.text('%-9s' % self.ctrl.state.get('xx'), 0, 0)
+
+         # Automatically unpause on seek hold
+        if self.ctrl.state.get('xx', '') == 'HOLDING' and \
+                self.ctrl.state.get('pr', '') == 'Switch found' and \
+                self.ctrl.planner.is_synchronizing():
+            self.ctrl.mach.unpause()
+
+       # Show enabled axes
+        row = 0
+        for axis in 'xyzabc':
+            motor = self.ctrl.state.find_motor(axis)
+            if motor is not None:
+                if (axis + 'p') in update:
+                    self.lcd_page.text('% 10.3f%s' % (
+                            update[axis + 'p'], axis.upper()), 9, row)
+
+                row += 1
+
+        # Show tool, units, feed and speed
+        if 'tool'  in update: self.lcd_page.text('%2uT' % update['tool'],  6, 1)
+        if 'units' in update: self.lcd_page.text('%-6s' % update['units'], 0, 1)
+        if 'feed'  in update: self.lcd_page.text('%8uF' % update['feed'],  0, 2)
+        if 'speed' in update: self.lcd_page.text('%8dS' % update['speed'], 0, 3)
+
+
+    def set(self, code, value):
+        self.comm.queue_command('${}={}'.format(code, value))
+
+
+    def mdi(self, cmd):
+        if len(cmd) and cmd[0] == '$':
+            equal = cmd.find('=')
+            if equal == -1:
+                log.info('%s=%s' % (cmd, self.ctrl.state.get(cmd[1:])))
+
+            else:
+                name, value = cmd[1:equal], cmd[equal + 1:]
+
+                if value.lower() == 'true': value = True
+                elif value.lower() == 'false': value = False
+                else:
+                    try:
+                        value = float(value)
+                    except: pass
+
+                self.ctrl.state.config(name, value)
+
+        elif len(cmd) and cmd[0] == '\\': self.comm.queue_command(cmd[1:])
+
+        else:
+            self.ctrl.planner.mdi(cmd)
+            self.comm.set_write(True)
+
+
+    def jog(self, axes):
+        if self._is_busy(): raise Exception('Busy, cannot jog')
+        self.comm.queue_command(Cmd.jog(axes))
+
+
+    def home(self, axis, position = None):
+        if self._is_busy(): raise Exception('Busy, cannot home')
+
+        if position is not None:
+            self.mdi('G28.3 %c%f' % (axis, position))
+
+        else:
+            if axis is None: axes = 'zxyabc' # TODO This should be configurable
+            else: axes = '%c' % axis
+
+            for axis in axes:
+                if not self.ctrl.state.axis_can_home(axis):
+                    log.info('Cannot home ' + axis)
+                    continue
+
+                log.info('Homing %s axis' % axis)
+                self.mdi(axis_homing_procedure % {'axis': axis})
+
+
+    def estop(self): self.comm.i2c_command(Cmd.ESTOP)
+    def clear(self): self.comm.i2c_command(Cmd.CLEAR)
+
+
+    def start(self, path):
+        if path: self.comm.start_sending_gcode(path)
+
+
+    def step(self, path):
+        self.comm.i2c_command(Cmd.STEP)
+        if not self._is_busy() and path and \
+                self.ctrl.state.get('xx', '') == 'READY':
+            self.comm.start_sending_gcode(path)
+
+
+    def stop(self):
+        self.pause()
+        self.stopping = True
+
+
+    def pause(self): self.comm.i2c_command(Cmd.PAUSE, byte = 0)
+
+
+    def unpause(self):
+        if self.ctrl.state.get('xx', '') != 'HOLDING': return
+
+        self.comm.i2c_command(Cmd.FLUSH)
+        self.comm.queue_command(Cmd.RESUME)
+        self.ctrl.planner.restart()
+        self.comm.set_write(True)
+        self.comm.i2c_command(Cmd.UNPAUSE)
+
+
+    def optional_pause(self): self.comm.i2c_command(Cmd.PAUSE, byte = 1)
+
+
+    def set_position(self, axis, position):
+        if self._is_busy(): raise Exception('Busy, cannot set position')
+
+        if self.ctrl.state.is_axis_homed('%c' % axis):
+            self.mdi('G92 %c%f' % (axis, position))
+        else: self.comm.queue_command('$%cp=%f' % (axis, position))
index 7b6b96086b16865008e61c35f85063b70d03e27d..c731e5329ecceabb34a4508960753c10e7e8e2d9 100644 (file)
@@ -51,6 +51,7 @@ class Planner():
 
 
     def is_running(self): return self.planner.is_running()
+    def is_synchronizing(self): return self.planner.is_synchronizing()
 
 
     def get_config(self):
@@ -101,12 +102,6 @@ class Planner():
             # Syncronize planner variables with execution id
             self.release_set_cmds(id)
 
-        # Automatically unpause on seek hold
-        if self.ctrl.state.get('xx', '') == 'HOLDING' and \
-                self.ctrl.state.get('pr', '') == 'Switch found' and \
-                self.planner.is_synchronizing():
-            self.ctrl.avr.unpause()
-
 
     def release_set_cmds(self, id):
         self.lastID = id
@@ -181,6 +176,7 @@ class Planner():
 
         log.info('GCode:' + path)
         self.planner.load('upload' + path, self.get_config())
+        self.mode = 'gcode'
 
 
     def reset(self):
index 64478e38f594b37b446cfa9c7a2f2ac5b5e24706..20e836aaffa6e85a7833815e930f7db710d390e1 100644 (file)
@@ -122,7 +122,7 @@ class State(object):
 
 
     def config(self, code, value):
-        if code in self.machine_var_set: self.ctrl.avr.set(code, value)
+        if code in self.machine_var_set: self.ctrl.mach.set(code, value)
         else: self.set(code, value)
 
 
@@ -157,7 +157,7 @@ class State(object):
                 if isinstance(value, str): value = '"' + value + '"'
                 if isinstance(value, bool): value = int(value)
 
-                self.ctrl.avr.set(name, value)
+                self.ctrl.mach.set(name, value)
 
 
     def find_motor(self, axis):
index c6c2f00d46080d4307d04230776d60a92b7c46f3..b9be542341a1598c3cd6ed061689cae510e49ba3 100644 (file)
@@ -185,58 +185,58 @@ class HomeHandler(bbctrl.APIHandler):
             if not 'position' in self.json:
                 raise HTTPError(400, 'Missing "position"')
 
-            self.ctrl.avr.home(axis, self.json['position'])
+            self.ctrl.mach.home(axis, self.json['position'])
 
-        else: self.ctrl.avr.home(axis)
+        else: self.ctrl.mach.home(axis)
 
 
 class StartHandler(bbctrl.APIHandler):
-    def put_ok(self, path): self.ctrl.avr.start(path)
+    def put_ok(self, path): self.ctrl.mach.start(path)
 
 
 class EStopHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.estop()
+    def put_ok(self): self.ctrl.mach.estop()
 
 
 class ClearHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.clear()
+    def put_ok(self): self.ctrl.mach.clear()
 
 
 class StopHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.stop()
+    def put_ok(self): self.ctrl.mach.stop()
 
 
 class PauseHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.pause()
+    def put_ok(self): self.ctrl.mach.pause()
 
 
 class UnpauseHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.unpause()
+    def put_ok(self): self.ctrl.mach.unpause()
 
 
 class OptionalPauseHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.optional_pause()
+    def put_ok(self): self.ctrl.mach.optional_pause()
 
 
 class StepHandler(bbctrl.APIHandler):
-    def put_ok(self, path): self.ctrl.avr.step(path)
+    def put_ok(self, path): self.ctrl.mach.step(path)
 
 
 class PositionHandler(bbctrl.APIHandler):
     def put_ok(self, axis):
-        self.ctrl.avr.set_position(ord(axis.lower()), self.json['position'])
+        self.ctrl.mach.set_position(ord(axis.lower()), self.json['position'])
 
 
 class OverrideFeedHandler(bbctrl.APIHandler):
-    def put_ok(self, value): self.ctrl.avr.override_feed(float(value))
+    def put_ok(self, value): self.ctrl.mach.override_feed(float(value))
 
 
 class OverrideSpeedHandler(bbctrl.APIHandler):
-    def put_ok(self, value): self.ctrl.avr.override_speed(float(value))
+    def put_ok(self, value): self.ctrl.mach.override_speed(float(value))
 
 
 class JogHandler(bbctrl.APIHandler):
-    def put_ok(self): self.ctrl.avr.jog(self.json)
+    def put_ok(self): self.ctrl.mach.jog(self.json)
 
 
 class VideoReloadHandler(bbctrl.APIHandler):
@@ -273,7 +273,7 @@ class ClientConnection(object):
         self.ctrl.msgs.remove_listener(self.notify)
 
 
-    def on_message(self, data): self.ctrl.avr.mdi(data)
+    def on_message(self, data): self.ctrl.mach.mdi(data)
 
 
 # Used by CAMotics
index 398000bc737ab26178d0d893e643936e2a0583f8..51c75422756895e2f927899853edae104be9fcab 100644 (file)
@@ -41,7 +41,7 @@ from bbctrl.FileHandler import FileHandler
 from bbctrl.GCodeStream import GCodeStream
 from bbctrl.Config import Config
 from bbctrl.LCD import LCD, LCDPage
-from bbctrl.AVR import AVR
+from bbctrl.Mach import Mach
 from bbctrl.Web import Web
 from bbctrl.Jog import Jog
 from bbctrl.Ctrl import Ctrl
@@ -50,6 +50,7 @@ from bbctrl.I2C import I2C
 from bbctrl.Planner import Planner
 from bbctrl.State import State
 from bbctrl.Messages import Messages
+from bbctrl.Comm import Comm
 import bbctrl.Cmd as Cmd