Adds atc_droptool/atc_grabtool/atc_release/atc_clamp wrappers in
AuxAxis (each just an RPC waiting on the matching terminal reply
line from the firmware), and registers them as internal hook
handlers in Ctrl. Macros and gcode programs can now invoke the
tool changer with:
(MSG,HOOK:droptool:)
(MSG,HOOK:grabtool:)
(MSG,HOOK:release:)
(MSG,HOOK🗜️)
block_unpause + auto_resume mirrors the W-axis hooks: the program
pauses while the ESP runs the pneumatic sequence and resumes when
done. Soft timeouts match the worst-case ESP sequence durations.
198 lines
8.2 KiB
Python
198 lines
8.2 KiB
Python
################################################################################
|
|
# #
|
|
# 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 os
|
|
import time
|
|
import bbctrl
|
|
import bbctrl.Trace as Trace
|
|
|
|
|
|
class Ctrl(object):
|
|
def __init__(self, args, ioloop, id):
|
|
Trace.mark('ctrl.init.start', id=id or '<default>')
|
|
self.args = args
|
|
self.ioloop = bbctrl.IOLoop(ioloop)
|
|
self.id = id
|
|
self.timeout = None # Used in demo mode
|
|
|
|
if id and not os.path.exists(id): os.mkdir(id)
|
|
|
|
# Start log
|
|
if args.demo: log_path = self.get_path(filename = 'bbctrl.log')
|
|
else: log_path = args.log
|
|
self.log = bbctrl.log.Log(args, self.ioloop, log_path)
|
|
Trace.mark('ctrl.log_open')
|
|
|
|
self.state = bbctrl.State(self)
|
|
self.config = bbctrl.Config(self)
|
|
Trace.mark('ctrl.state_config')
|
|
|
|
self.log.get('Ctrl').info('Starting %s' % self.id)
|
|
|
|
try:
|
|
with Trace.span('ctrl.avr'):
|
|
if args.demo: self.avr = bbctrl.AVREmu(self)
|
|
else: self.avr = bbctrl.AVR(self)
|
|
|
|
with Trace.span('ctrl.i2c'):
|
|
self.i2c = bbctrl.I2C(args.i2c_port, args.demo)
|
|
with Trace.span('ctrl.lcd'):
|
|
self.lcd = bbctrl.LCD(self)
|
|
with Trace.span('ctrl.mach'):
|
|
self.mach = bbctrl.Mach(self, self.avr)
|
|
with Trace.span('ctrl.preplanner'):
|
|
self.preplanner = bbctrl.Preplanner(self)
|
|
if not args.demo:
|
|
with Trace.span('ctrl.jog'):
|
|
self.jog = bbctrl.Jog(self)
|
|
with Trace.span('ctrl.pwr'):
|
|
self.pwr = bbctrl.Pwr(self)
|
|
with Trace.span('ctrl.hooks'):
|
|
self.hooks = bbctrl.Hooks(self)
|
|
with Trace.span('ctrl.aux'):
|
|
self.aux = bbctrl.AuxAxis(self)
|
|
self._register_aux_hooks()
|
|
|
|
with Trace.span('ctrl.mach.connect'):
|
|
self.mach.connect()
|
|
|
|
self.lcd.add_new_page(bbctrl.MainLCDPage(self))
|
|
self.lcd.add_new_page(bbctrl.IPLCDPage(self.lcd))
|
|
|
|
os.environ['GCODE_SCRIPT_PATH'] = self.get_upload()
|
|
|
|
Trace.mark('ctrl.init.end')
|
|
Trace.sd_notify('STATUS=ctrl initialized\n')
|
|
|
|
except Exception:
|
|
Trace.mark('ctrl.init.error')
|
|
self.log.get('Ctrl').exception('Internal error: Control initialization failed')
|
|
|
|
|
|
def __del__(self): print('Ctrl deleted')
|
|
|
|
|
|
def clear_timeout(self):
|
|
if self.timeout is not None: self.ioloop.remove_timeout(self.timeout)
|
|
self.timeout = None
|
|
|
|
|
|
def set_timeout(self, cb, *args, **kwargs):
|
|
self.clear_timeout()
|
|
t = self.args.client_timeout
|
|
self.timeout = self.ioloop.call_later(t, cb, *args, **kwargs)
|
|
|
|
|
|
def get_path(self, dir = None, filename = None):
|
|
path = './' + self.id if self.id else '.'
|
|
path = path if dir is None else (path + '/' + dir)
|
|
return path if filename is None else (path + '/' + filename)
|
|
|
|
|
|
def get_upload(self, filename = None):
|
|
return self.get_path('upload', filename)
|
|
|
|
|
|
def get_plan(self, filename = None):
|
|
return self.get_path('plans', filename)
|
|
|
|
|
|
def configure(self):
|
|
# Indirectly configures state via calls to config() and the AVR
|
|
self.config.reload()
|
|
self.state.init()
|
|
|
|
|
|
def ready(self):
|
|
# This is used to synchronize the start of the preplanner
|
|
self.preplanner.start()
|
|
|
|
|
|
def _register_aux_hooks(self):
|
|
"""Wire up the auxcnc HOOK: events to AuxAxis methods."""
|
|
log = self.log.get('AuxAxis')
|
|
|
|
def _hook_move(ctx):
|
|
data = (ctx.get('data') or '').strip()
|
|
if not data:
|
|
raise Exception('aux hook missing target')
|
|
self.aux.move_abs_mm(float(data))
|
|
|
|
def _hook_move_rel(ctx):
|
|
data = (ctx.get('data') or '').strip()
|
|
if not data:
|
|
raise Exception('aux_rel hook missing delta')
|
|
self.aux.move_rel_mm(float(data))
|
|
|
|
def _hook_home(ctx):
|
|
self.aux.home()
|
|
|
|
def _hook_setzero(ctx):
|
|
data = (ctx.get('data') or '').strip()
|
|
mm = float(data) if data else 0.0
|
|
self.aux.set_position_mm(mm)
|
|
|
|
def _hook_droptool(ctx): self.aux.atc_droptool()
|
|
def _hook_grabtool(ctx): self.aux.atc_grabtool()
|
|
def _hook_release(ctx): self.aux.atc_release()
|
|
def _hook_clamp(ctx): self.aux.atc_clamp()
|
|
|
|
self.hooks.register_internal('aux', _hook_move,
|
|
block_unpause=True, auto_resume=True)
|
|
self.hooks.register_internal('aux_rel', _hook_move_rel,
|
|
block_unpause=True, auto_resume=True)
|
|
self.hooks.register_internal('aux_home', _hook_home,
|
|
block_unpause=True, auto_resume=True,
|
|
timeout=180)
|
|
self.hooks.register_internal('aux_setzero', _hook_setzero,
|
|
block_unpause=True, auto_resume=True)
|
|
# ATC pneumatics. block_unpause + auto_resume so a program
|
|
# using M6 - implemented as (MSG,HOOK:droptool:) etc - pauses
|
|
# at the right point and resumes once the sequence is done.
|
|
self.hooks.register_internal('droptool', _hook_droptool,
|
|
block_unpause=True, auto_resume=True,
|
|
timeout=60)
|
|
self.hooks.register_internal('grabtool', _hook_grabtool,
|
|
block_unpause=True, auto_resume=True,
|
|
timeout=60)
|
|
self.hooks.register_internal('release', _hook_release,
|
|
block_unpause=True, auto_resume=True,
|
|
timeout=10)
|
|
self.hooks.register_internal('clamp', _hook_clamp,
|
|
block_unpause=True, auto_resume=True,
|
|
timeout=15)
|
|
log.info('Aux hooks registered')
|
|
|
|
|
|
def close(self):
|
|
self.log.get('Ctrl').info('Closing %s' % self.id)
|
|
self.ioloop.close()
|
|
self.avr.close()
|
|
self.mach.planner.close()
|
|
try: self.aux.close()
|
|
except Exception: pass
|