ExternalAxis exposes the auxcnc-driven ESP stepper as motor 4 (a
synthetic, host-only motor that gplan sees but the AVR doesn't). The
result is a virtual A axis that is fully integrated with the planner:
G1 A25 F1500 schedules a coordinated S-curve and the ESP runs the
exact same 7-segment trajectory the AVR would have run if A were a
real motor.
- ExternalAxis.py: synthetic-motor state, S-curve LINE block forward
to the ESP, soft-limit enforcement, option-(b) homing (user A=0
at the home limit).
- State: walk motors 0..4 in find_motor; clear both homed and h on
reset; expose synthetic motor vars.
- axis-vars.js: motor-4 guard so the JS computed axis bindings don't
throw when motor 4 has no entry in config.motors; resolve motor_id
for the synthetic axis by scanning state['4an'].
- Ctrl: instantiate ExternalAxis after AuxAxis, share the axis_letter
setting, wire AuxAxis state observer.
- Web: route /api/aux/{home,jog,move} through ExternalAxis when it
is enabled so the DRO and synthetic-motor flags stay in sync.
212 lines
7.9 KiB
Python
212 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
################################################################################
|
|
# #
|
|
# 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 sys
|
|
import signal
|
|
import tornado
|
|
import argparse
|
|
import datetime
|
|
|
|
from pkg_resources import Requirement, resource_filename
|
|
|
|
# Trace must be imported before the rest of bbctrl so its monotonic
|
|
# anchor is the earliest reasonable point and so import-time costs of
|
|
# heavy submodules (camotics gplan.so, sockjs, tornado, etc.) are
|
|
# attributable in /api/diag/timing.
|
|
import bbctrl.Trace as Trace
|
|
Trace.mark('imports.bbctrl.start')
|
|
|
|
from bbctrl.RequestHandler import RequestHandler
|
|
from bbctrl.APIHandler import APIHandler
|
|
from bbctrl.FileHandler import FileHandler
|
|
from bbctrl.Config import Config
|
|
from bbctrl.LCD import LCD, LCDPage
|
|
from bbctrl.Mach import Mach
|
|
from bbctrl.Web import Web
|
|
from bbctrl.Jog import Jog
|
|
from bbctrl.Ctrl import Ctrl
|
|
from bbctrl.Pwr import Pwr
|
|
from bbctrl.I2C import I2C
|
|
from bbctrl.Planner import Planner
|
|
from bbctrl.Preplanner import Preplanner
|
|
from bbctrl.State import State
|
|
from bbctrl.Comm import Comm
|
|
from bbctrl.CommandQueue import CommandQueue
|
|
from bbctrl.MainLCDPage import MainLCDPage
|
|
from bbctrl.IPLCDPage import IPLCDPage
|
|
from bbctrl.Camera import Camera, VideoHandler
|
|
from bbctrl.AVR import AVR
|
|
from bbctrl.AVREmu import AVREmu
|
|
from bbctrl.IOLoop import IOLoop
|
|
from bbctrl.MonitorTemp import MonitorTemp
|
|
from bbctrl.Hooks import Hooks
|
|
from bbctrl.AuxAxis import AuxAxis
|
|
from bbctrl.ExternalAxis import ExternalAxis
|
|
import bbctrl.Cmd as Cmd
|
|
import bbctrl.v4l2 as v4l2
|
|
import bbctrl.Log as log
|
|
import bbctrl.ObjGraph as ObjGraph
|
|
|
|
Trace.mark('imports.bbctrl.end')
|
|
|
|
|
|
ctrl = None
|
|
|
|
|
|
def get_resource(path):
|
|
return resource_filename(Requirement.parse('bbctrl'), 'bbctrl/' + path)
|
|
|
|
|
|
def on_exit(sig = 0, func = None):
|
|
global ctrl
|
|
|
|
print('Exit handler triggered: signal = %d', sig)
|
|
|
|
if ctrl is not None:
|
|
ctrl.close()
|
|
ctrl = None
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
def time_str():
|
|
return datetime.datetime.now().strftime('%Y%m%d-%H:%M:%S')
|
|
|
|
|
|
|
|
class Debugger:
|
|
def __init__(self, ioloop, freq = 60 * 15, depth = 100):
|
|
self.ioloop = ioloop
|
|
self.freq = freq
|
|
self.depth = depth
|
|
self._callback()
|
|
|
|
|
|
def _callback(self):
|
|
with open('bbctrl-debug-%s.log' % time_str(), 'w') as log:
|
|
def line(name):
|
|
log.write('==== ' + name + ' ' + '=' * (74 - len(name)) + '\n')
|
|
|
|
line('Common')
|
|
ObjGraph.show_most_common_types(limit = self.depth, file = log)
|
|
|
|
log.write('\n')
|
|
line('Growth')
|
|
ObjGraph.show_growth(limit = self.depth, file = log)
|
|
|
|
log.write('\n')
|
|
line('New IDs')
|
|
ObjGraph.get_new_ids(limit = self.depth, file = log)
|
|
|
|
log.flush()
|
|
self.ioloop.call_later(self.freq, self._callback)
|
|
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(
|
|
description = 'Buildbotics Machine Controller')
|
|
|
|
parser.add_argument('-p', '--port', default = 80,
|
|
type = int, help = 'HTTP port')
|
|
parser.add_argument('-a', '--addr', metavar = 'IP', default = '0.0.0.0',
|
|
help = 'HTTP address to bind')
|
|
parser.add_argument('-s', '--serial', default = '/dev/ttyAMA0',
|
|
help = 'Serial device')
|
|
parser.add_argument('-b', '--baud', default = 230400, type = int,
|
|
help = 'Serial baud rate')
|
|
parser.add_argument('--i2c-port', default = 1, type = int,
|
|
help = 'I2C port')
|
|
parser.add_argument('--lcd-addr', default = [0x27, 0x3f], type = int,
|
|
help = 'LCD I2C address')
|
|
parser.add_argument('--avr-addr', default = 0x2b, type = int,
|
|
help = 'AVR I2C address')
|
|
parser.add_argument('--pwr-addr', default = 0x60, type = int,
|
|
help = 'Power AVR I2C address')
|
|
parser.add_argument('-v', '--verbose', action = 'store_true',
|
|
help = 'Verbose output')
|
|
parser.add_argument('-l', '--log', metavar = "FILE",
|
|
help = 'Set a log file')
|
|
parser.add_argument('--disable-camera', action = 'store_true',
|
|
help = 'Disable the camera')
|
|
parser.add_argument('--width', default = 640, type = int,
|
|
help = 'Camera width')
|
|
parser.add_argument('--height', default = 480, type = int,
|
|
help = 'Camera height')
|
|
parser.add_argument('--fps', default = 15, type = int,
|
|
help = 'Camera frames per second')
|
|
parser.add_argument('--camera-clients', default = 4,
|
|
help = 'Maximum simultaneous camera clients')
|
|
parser.add_argument('--demo', action = 'store_true',
|
|
help = 'Enter demo mode')
|
|
parser.add_argument('--debug', default = 0, type = int,
|
|
help = 'Enable debug mode and set frequency in seconds')
|
|
parser.add_argument('--fast-emu', action = 'store_true',
|
|
help = 'Enter demo mode')
|
|
parser.add_argument('--client-timeout', default = 5 * 60, type = int,
|
|
help = 'Demo client timeout in seconds')
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def run():
|
|
global ctrl
|
|
|
|
Trace.mark('run.enter')
|
|
args = parse_args()
|
|
Trace.mark('args.parsed')
|
|
|
|
# Set signal handler
|
|
signal.signal(signal.SIGTERM, on_exit)
|
|
|
|
# Create ioloop
|
|
ioloop = tornado.ioloop.IOLoop.current()
|
|
Trace.mark('ioloop.created')
|
|
|
|
# Set ObjGraph signal handler
|
|
if args.debug: Debugger(ioloop, args.debug)
|
|
|
|
# Start server
|
|
with Trace.span('web.init'):
|
|
web = Web(args, ioloop)
|
|
Trace.mark('listen', port=args.port, addr=args.addr)
|
|
|
|
# Notify systemd we are ready (no-op when not under systemd).
|
|
Trace.sd_notify('READY=1\nSTATUS=listening on %s:%d\n' %
|
|
(args.addr, args.port))
|
|
|
|
try:
|
|
ioloop.start()
|
|
|
|
except KeyboardInterrupt: on_exit()
|
|
|
|
|
|
if __name__ == '__main__': run()
|