ATC pneumatics in g-code (drop tool / grab tool / release clamp / engage clamp) are expressed as M100..M103. AuxPreprocessor rewrites those into (MSG,HOOK:droptool:) etc on file upload + on planner load + on MDI input, so the Hooks layer (B1) can dispatch them via registered ATC handlers in Ctrl. - AuxPreprocessor.py: regex-based file rewriter, idempotent. - FileHandler: invoke preprocessor on every upload. - Planner.init: also re-preprocess on load (catches files written before this version). - Mach.mdi: same rewrite for ad-hoc MDI input so M101 typed at the console produces a HOOK message. - Ctrl: register the four ATC hooks (droptool/grabtool/release/clamp) with block_unpause + auto_resume so programs using them pause at the right point and resume cleanly. aux_home retained as a legacy alias for older preprocessed files.
206 lines
8.7 KiB
Python
206 lines
8.7 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)
|
|
with Trace.span('ctrl.ext_axis'):
|
|
# ExternalAxis exposes the auxcnc ESP stepper as a
|
|
# virtual A axis that gplan handles natively. Created
|
|
# unconditionally so State sees the synthetic motor
|
|
# vars even when aux is disabled (kept inert in that
|
|
# case via ext_axis.enabled).
|
|
axis_letter = self.aux._cfg.get('axis_letter', 'a')
|
|
self.ext_axis = bbctrl.ExternalAxis(
|
|
self, self.aux, axis_letter=axis_letter)
|
|
# Hook AuxAxis post-publish callback so homed flag
|
|
# mirrors into State after homing.
|
|
self.aux.set_state_observer(
|
|
self.ext_axis.refresh_homed)
|
|
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 auxcnc HOOK: events to AuxAxis methods.
|
|
|
|
v2: motion hooks (aux/aux_rel/aux_home/aux_setzero) are
|
|
retired now that the W axis is integrated through gplan as
|
|
a virtual A axis (see ExternalAxis). Only the ATC pneumatic
|
|
hooks remain - those are events, not motion.
|
|
|
|
For backwards compatibility with files that still contain
|
|
(MSG,HOOK:aux_home:) (e.g. older preprocessed gcode), keep
|
|
an aux_home alias that routes to the standard ext_axis homing
|
|
path."""
|
|
log = self.log.get('AuxAxis')
|
|
|
|
def _hook_aux_home(ctx):
|
|
# Legacy: route to the standard external-axis homing.
|
|
if self.ext_axis is not None and self.ext_axis.enabled:
|
|
self.ext_axis.home()
|
|
else:
|
|
self.aux.home()
|
|
|
|
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()
|
|
|
|
# Legacy alias for older gcode that used aux_home.
|
|
self.hooks.register_internal('aux_home', _hook_aux_home,
|
|
block_unpause=True, auto_resume=True,
|
|
timeout=180)
|
|
|
|
# ATC pneumatics. block_unpause + auto_resume so a program
|
|
# using M100/M101/M102/M103 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.ext_axis.close()
|
|
except Exception: pass
|
|
try: self.aux.close()
|
|
except Exception: pass
|