From ba2205f5d1c1b3e854e54d5cd36c5af0c3a1d849 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Thu, 22 Feb 2018 00:04:37 -0800 Subject: [PATCH] Split AVR.py in to Mach.py and Comm.py --- src/py/bbctrl/AVR.py | 392 -------------------------------------- src/py/bbctrl/Comm.py | 240 +++++++++++++++++++++++ src/py/bbctrl/Ctrl.py | 4 +- src/py/bbctrl/Jog.py | 2 +- src/py/bbctrl/Mach.py | 213 +++++++++++++++++++++ src/py/bbctrl/Planner.py | 8 +- src/py/bbctrl/State.py | 4 +- src/py/bbctrl/Web.py | 30 +-- src/py/bbctrl/__init__.py | 3 +- 9 files changed, 477 insertions(+), 419 deletions(-) delete mode 100644 src/py/bbctrl/AVR.py create mode 100644 src/py/bbctrl/Comm.py create mode 100644 src/py/bbctrl/Mach.py diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py deleted file mode 100644 index ab7c8df..0000000 --- a/src/py/bbctrl/AVR.py +++ /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 . # -# # -# 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 # -# . # -# # -# For information regarding this software email: # -# "Joseph Coffland" # -# # -################################################################################ - -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 index 0000000..4422c1b --- /dev/null +++ b/src/py/bbctrl/Comm.py @@ -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 . # +# # +# 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 # +# . # +# # +# For information regarding this software email: # +# "Joseph Coffland" # +# # +################################################################################ + +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) diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py index 9a1ca58..c327a86 100644 --- a/src/py/bbctrl/Ctrl.py +++ b/src/py/bbctrl/Ctrl.py @@ -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)) diff --git a/src/py/bbctrl/Jog.py b/src/py/bbctrl/Jog.py index dbcdd6c..c16efa7 100644 --- a/src/py/bbctrl/Jog.py +++ b/src/py/bbctrl/Jog.py @@ -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 index 0000000..f99b4a8 --- /dev/null +++ b/src/py/bbctrl/Mach.py @@ -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 . # +# # +# 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 # +# . # +# # +# For information regarding this software email: # +# "Joseph Coffland" # +# # +################################################################################ + +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)) diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py index 7b6b960..c731e53 100644 --- a/src/py/bbctrl/Planner.py +++ b/src/py/bbctrl/Planner.py @@ -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): diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py index 64478e3..20e836a 100644 --- a/src/py/bbctrl/State.py +++ b/src/py/bbctrl/State.py @@ -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): diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index c6c2f00..b9be542 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -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 diff --git a/src/py/bbctrl/__init__.py b/src/py/bbctrl/__init__.py index 398000b..51c7542 100644 --- a/src/py/bbctrl/__init__.py +++ b/src/py/bbctrl/__init__.py @@ -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 -- 2.27.0