Files
onefinity-firmware/src/py/bbctrl/__init__.py
Henrik Muehe 6d4c51bd49 ExternalAxis: virtual A axis through gplan, mirrored on the ESP
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.
2026-05-03 15:10:26 +02:00

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()