mcu: Initial support for multiple micro-controllers

Add initial support for controlling multiple independent
micro-controllers from a single Klippy host instance.  Add basic
support for synchronizing the clocks of the additional mcus to the
main mcu's clock.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2017-08-14 11:46:35 -04:00
parent cee200e509
commit 3ccecc568d
5 changed files with 172 additions and 53 deletions

View File

@ -42,6 +42,17 @@
# the fan is disabled. The default is 50 Celsius. # the fan is disabled. The default is 50 Celsius.
# Additional micro-controllers (one may define any number of sections
# with an "mcu" prefix). Additional micro-controllers introduce
# additional pins that may be configured as heaters, steppers, fans,
# etc.. For example, if an "[mcu extra_mcu]" section is introduced,
# then pins such as "extra_mcu:ar9" may then be used elsewhere in the
# config (where "ar9" is a hardware pin name or alias name on the
# given mcu).
#[mcu my_extra_mcu]
# See the "mcu" section in example.cfg for configuration parameters.
# Statically configured digital output pins (one may define any number # Statically configured digital output pins (one may define any number
# of sections with a "static_digital_output" prefix). Pins configured # of sections with a "static_digital_output" prefix). Pins configured
# here will be setup as a GPIO output during MCU configuration. # here will be setup as a GPIO output during MCU configuration.

View File

@ -51,6 +51,8 @@ class ClockSync:
self.last_clock, self.last_clock_time) self.last_clock, self.last_clock_time)
def is_active(self, eventtime): def is_active(self, eventtime):
return self.queries_pending <= 4 return self.queries_pending <= 4
def calibrate_clock(self, print_time, eventtime):
return (0., self.mcu_freq)
def get_clock(self, eventtime): def get_clock(self, eventtime):
with self.lock: with self.lock:
last_clock = self.last_clock last_clock = self.last_clock
@ -106,9 +108,8 @@ class ClockSync:
new_min_freq = ( new_min_freq = (
clock_delta / (sent_time - self.last_clock_time)) clock_delta / (sent_time - self.last_clock_time))
logging.warning( logging.warning(
"High clock drift! Now %.0f:%.0f was %.0f:%.0f" % ( "High clock drift! Now %.0f:%.0f was %.0f:%.0f",
new_min_freq, new_max_freq, new_min_freq, new_max_freq, self.min_freq, self.max_freq)
self.min_freq, self.max_freq))
self.min_freq, self.max_freq = new_min_freq, new_max_freq self.min_freq, self.max_freq = new_min_freq, new_max_freq
min_time, max_time = sent_time, receive_time min_time, max_time = sent_time, receive_time
# Update variables # Update variables
@ -116,3 +117,54 @@ class ClockSync:
self.last_clock_time = max_time self.last_clock_time = max_time
self.last_clock_time_min = min_time self.last_clock_time_min = min_time
self.serial.set_clock_est(self.min_freq, max_time + 0.001, clock) self.serial.set_clock_est(self.min_freq, max_time + 0.001, clock)
# Clock synching code for secondary MCUs (whose clocks are sync'ed to
# a primary MCU)
class SecondarySync(ClockSync):
def __init__(self, reactor, main_sync):
ClockSync.__init__(self, reactor)
self.main_sync = main_sync
self.clock_adj = (0., 0.)
def connect(self, serial):
ClockSync.connect(self, serial)
self.clock_adj = (0., self.mcu_freq)
curtime = self.reactor.monotonic()
main_print_time = self.main_sync.estimated_print_time(curtime)
local_print_time = self.estimated_print_time(curtime)
self.clock_adj = (main_print_time - local_print_time, self.mcu_freq)
self.calibrate_clock(0., curtime)
def connect_file(self, serial, pace=False):
ClockSync.connect_file(self, serial, pace)
self.clock_adj = (0., self.mcu_freq)
def print_time_to_clock(self, print_time):
adjusted_offset, adjusted_freq = self.clock_adj
return int((print_time - adjusted_offset) * adjusted_freq)
def clock_to_print_time(self, clock):
adjusted_offset, adjusted_freq = self.clock_adj
return clock / adjusted_freq + adjusted_offset
def get_adjusted_freq(self):
adjusted_offset, adjusted_freq = self.clock_adj
return adjusted_freq
def calibrate_clock(self, print_time, eventtime):
#logging.debug("calibrate: %.3f: %.6f vs %.6f",
# eventtime,
# self.estimated_print_time(eventtime),
# self.main_sync.estimated_print_time(eventtime))
with self.main_sync.lock:
ser_clock = self.main_sync.last_clock
ser_clock_time = self.main_sync.last_clock_time
ser_freq = self.main_sync.min_freq
main_mcu_freq = self.main_sync.mcu_freq
main_clock = (eventtime - ser_clock_time) * ser_freq + ser_clock
print_time = max(print_time, main_clock / main_mcu_freq)
main_sync_clock = (print_time + 2.) * main_mcu_freq
sync_time = ser_clock_time + (main_sync_clock - ser_clock) / ser_freq
print_clock = self.print_time_to_clock(print_time)
sync_clock = self.get_clock(sync_time)
adjusted_freq = .5 * (sync_clock - print_clock)
adjusted_offset = print_time - print_clock / adjusted_freq
self.clock_adj = (adjusted_offset, adjusted_freq)
return self.clock_adj

View File

@ -140,7 +140,7 @@ class Printer:
self.state_message = message_startup self.state_message = message_startup
self.run_result = None self.run_result = None
self.fileconfig = None self.fileconfig = None
self.mcu = None self.mcus = []
def get_start_args(self): def get_start_args(self):
return self.start_args return self.start_args
def _stats(self, eventtime, force_output=False): def _stats(self, eventtime, force_output=False):
@ -149,7 +149,7 @@ class Printer:
self.gcode.dump_debug() self.gcode.dump_debug()
self.need_dump_debug = False self.need_dump_debug = False
toolhead = self.objects.get('toolhead') toolhead = self.objects.get('toolhead')
if toolhead is None or self.mcu is None: if toolhead is None:
return eventtime + 1. return eventtime + 1.
is_active = toolhead.check_active(eventtime) is_active = toolhead.check_active(eventtime)
if not is_active and not force_output: if not is_active and not force_output:
@ -157,7 +157,8 @@ class Printer:
out = [] out = []
out.append(self.gcode.stats(eventtime)) out.append(self.gcode.stats(eventtime))
out.append(toolhead.stats(eventtime)) out.append(toolhead.stats(eventtime))
out.append(self.mcu.stats(eventtime)) for m in self.mcus:
out.append(m.stats(eventtime))
logging.info("Stats %.1f: %s" % (eventtime, ' '.join(out))) logging.info("Stats %.1f: %s" % (eventtime, ' '.join(out)))
return eventtime + 1. return eventtime + 1.
def add_object(self, name, obj): def add_object(self, name, obj):
@ -175,7 +176,7 @@ class Printer:
config = ConfigWrapper(self, 'printer') config = ConfigWrapper(self, 'printer')
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]: for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
m.add_printer_objects(self, config) m.add_printer_objects(self, config)
self.mcu = self.objects['mcu'] self.mcus = mcu.get_printer_mcus(self)
# Validate that there are no undefined parameters in the config file # Validate that there are no undefined parameters in the config file
valid_sections = { s: 1 for s, o in self.all_config_options } valid_sections = { s: 1 for s, o in self.all_config_options }
for section in self.fileconfig.sections(): for section in self.fileconfig.sections():
@ -193,7 +194,8 @@ class Printer:
self.reactor.unregister_timer(self.connect_timer) self.reactor.unregister_timer(self.connect_timer)
try: try:
self._load_config() self._load_config()
self.mcu.connect() for m in self.mcus:
m.connect()
self.gcode.set_printer_ready(True) self.gcode.set_printer_ready(True)
self.state_message = message_ready self.state_message = message_ready
if self.start_args.get('debugoutput') is None: if self.start_args.get('debugoutput') is None:
@ -227,10 +229,10 @@ class Printer:
run_result = self.run_result run_result = self.run_result
try: try:
self._stats(self.reactor.monotonic(), force_output=True) self._stats(self.reactor.monotonic(), force_output=True)
if self.mcu is not None: for m in self.mcus:
if run_result == 'firmware_restart': if run_result == 'firmware_restart':
self.mcu.microcontroller_restart() m.microcontroller_restart()
self.mcu.disconnect() m.disconnect()
except: except:
logging.exception("Unhandled exception during post run") logging.exception("Unhandled exception during post run")
return run_result return run_result
@ -254,6 +256,15 @@ class Printer:
# Startup # Startup
###################################################################### ######################################################################
def arg_dictionary(option, opt_str, value, parser):
key, fname = "dictionary", value
if '=' in value:
mcu_name, fname = value.split('=', 1)
key = "dictionary_" + mcu_name
if parser.values.dictionary is None:
parser.values.dictionary = {}
parser.values.dictionary[key] = fname
def main(): def main():
usage = "%prog [options] <config file>" usage = "%prog [options] <config file>"
opts = optparse.OptionParser(usage) opts = optparse.OptionParser(usage)
@ -267,7 +278,8 @@ def main():
help="enable debug messages") help="enable debug messages")
opts.add_option("-o", "--debugoutput", dest="debugoutput", opts.add_option("-o", "--debugoutput", dest="debugoutput",
help="write output to file instead of to serial port") help="write output to file instead of to serial port")
opts.add_option("-d", "--dictionary", dest="dictionary", opts.add_option("-d", "--dictionary", dest="dictionary", type="string",
action="callback", callback=arg_dictionary,
help="file to read for mcu protocol dictionary") help="file to read for mcu protocol dictionary")
options, args = opts.parse_args() options, args = opts.parse_args()
if len(args) != 1: if len(args) != 1:
@ -287,7 +299,7 @@ def main():
input_fd = util.create_pty(options.inputtty) input_fd = util.create_pty(options.inputtty)
if options.debugoutput: if options.debugoutput:
start_args['debugoutput'] = options.debugoutput start_args['debugoutput'] = options.debugoutput
start_args['dictionary'] = options.dictionary start_args.update(options.dictionary)
if options.logfile: if options.logfile:
bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel) bglogger = queuelogger.setup_bg_logging(options.logfile, debuglevel)
else: else:

View File

@ -180,7 +180,7 @@ class MCU_endstop:
while self._check_busy(eventtime): while self._check_busy(eventtime):
eventtime = self._mcu.pause(eventtime + 0.1) eventtime = self._mcu.pause(eventtime + 0.1)
def _handle_end_stop_state(self, params): def _handle_end_stop_state(self, params):
logging.debug("end_stop_state %s" % (params,)) logging.debug("end_stop_state %s", params)
self._last_state = params self._last_state = params
def _check_busy(self, eventtime): def _check_busy(self, eventtime):
# Check if need to send an end_stop_query command # Check if need to send an end_stop_query command
@ -393,6 +393,9 @@ class MCU:
def __init__(self, printer, config, clocksync): def __init__(self, printer, config, clocksync):
self._printer = printer self._printer = printer
self._clocksync = clocksync self._clocksync = clocksync
self._name = config.section
if self._name.startswith('mcu '):
self._name = self._name[4:]
# Serial port # Serial port
self._serialport = config.get('serial', '/dev/ttyS0') self._serialport = config.get('serial', '/dev/ttyS0')
baud = 0 baud = 0
@ -411,9 +414,9 @@ class MCU:
self._is_shutdown = False self._is_shutdown = False
self._shutdown_msg = "" self._shutdown_msg = ""
if printer.bglogger is not None: if printer.bglogger is not None:
printer.bglogger.set_rollover_info("mcu", None) printer.bglogger.set_rollover_info(self._name, None)
# Config building # Config building
pins.get_printer_pins(printer).register_chip("mcu", self) pins.get_printer_pins(printer).register_chip(self._name, self)
self._oid_count = 0 self._oid_count = 0
self._config_objects = [] self._config_objects = []
self._init_cmds = [] self._init_cmds = []
@ -447,26 +450,32 @@ class MCU:
return return
self._is_shutdown = True self._is_shutdown = True
self._shutdown_msg = msg = params['#msg'] self._shutdown_msg = msg = params['#msg']
logging.info("%s: %s" % (params['#name'], self._shutdown_msg)) logging.info("%s: %s", params['#name'], self._shutdown_msg)
self._serial.dump_debug() self._serial.dump_debug()
prefix = "MCU shutdown: " prefix = "MCU '%s' shutdown: " % (self._name,)
if params['#name'] == 'is_shutdown': if params['#name'] == 'is_shutdown':
prefix = "Previous MCU shutdown: " prefix = "Previous MCU '%s' shutdown: " % (self._name,)
self._printer.note_shutdown(prefix + msg + error_help(msg)) self._printer.note_shutdown(prefix + msg + error_help(msg))
# Connection phase # Connection phase
def _check_restart(self, reason): def _check_restart(self, reason):
start_reason = self._printer.get_start_args().get("start_reason") start_reason = self._printer.get_start_args().get("start_reason")
if start_reason == 'firmware_restart': if start_reason == 'firmware_restart':
return return
logging.info("Attempting automated firmware restart: %s" % (reason,)) logging.info("Attempting automated MCU '%s' restart: %s",
self._name, reason)
self._printer.request_exit('firmware_restart') self._printer.request_exit('firmware_restart')
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000) self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
raise error("Attempt firmware restart failed") raise error("Attempt MCU '%s' restart failed" % (self._name,))
def _connect_file(self, pace=False): def _connect_file(self, pace=False):
# In a debugging mode. Open debug output file and read data dictionary # In a debugging mode. Open debug output file and read data dictionary
out_fname = self._printer.get_start_args().get('debugoutput') start_args = self._printer.get_start_args()
if self._name == 'mcu':
out_fname = start_args.get('debugoutput')
dict_fname = start_args.get('dictionary')
else:
out_fname = start_args.get('debugoutput') + "-" + self._name
dict_fname = start_args.get('dictionary_' + self._name)
outfile = open(out_fname, 'wb') outfile = open(out_fname, 'wb')
dict_fname = self._printer.get_start_args().get('dictionary')
dfile = open(dict_fname, 'rb') dfile = open(dict_fname, 'rb')
dict_data = dfile.read() dict_data = dfile.read()
dfile.close() dfile.close()
@ -520,34 +529,36 @@ class MCU:
# Only configure mcu after usb power reset # Only configure mcu after usb power reset
self._check_restart("full reset before config") self._check_restart("full reset before config")
# Send config commands # Send config commands
logging.info("Sending printer configuration...") logging.info("Sending MCU '%s' printer configuration...",
self._name)
for c in self._config_cmds: for c in self._config_cmds:
self.send(self.create_command(c)) self.send(self.create_command(c))
if not self.is_fileoutput(): if not self.is_fileoutput():
config_params = self.send_with_response(msg, 'config') config_params = self.send_with_response(msg, 'config')
if not config_params['is_config']: if not config_params['is_config']:
if self._is_shutdown: if self._is_shutdown:
raise error("Firmware error during config: %s" % ( raise error("MCU '%s' error during config: %s" % (
self._shutdown_msg,)) self._name, self._shutdown_msg))
raise error("Unable to configure printer") raise error("Unable to configure MCU '%s'" % (self._name,))
else: else:
start_reason = self._printer.get_start_args().get("start_reason") start_reason = self._printer.get_start_args().get("start_reason")
if start_reason == 'firmware_restart': if start_reason == 'firmware_restart':
raise error("Failed automated reset of micro-controller") raise error("Failed automated reset of MCU '%s'" % (self._name,))
if self._config_crc != config_params['crc']: if self._config_crc != config_params['crc']:
self._check_restart("CRC mismatch") self._check_restart("CRC mismatch")
raise error("Printer CRC does not match config") raise error("MCU '%s' CRC does not match config" % (self._name,))
move_count = config_params['move_count'] move_count = config_params['move_count']
logging.info("Configured (%d moves)" % (move_count,)) logging.info("Configured MCU '%s' (%d moves)", self._name, move_count)
if self._printer.bglogger is not None: if self._printer.bglogger is not None:
msgparser = self._serial.msgparser msgparser = self._serial.msgparser
info = [ info = [
"Configured (%d moves)" % (move_count,), "Configured MCU '%s' (%d moves)" % (self._name, move_count),
"Loaded %d commands (%s)" % ( "Loaded MCU '%s' %d commands (%s)" % (
len(msgparser.messages_by_id), msgparser.version), self._name, len(msgparser.messages_by_id),
"MCU config: %s" % (" ".join( msgparser.version),
"MCU '%s' config: %s" % (self._name, " ".join(
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))] ["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
self._printer.bglogger.set_rollover_info("mcu", "\n".join(info)) self._printer.bglogger.set_rollover_info(self._name, "\n".join(info))
self._steppersync = self._ffi_lib.steppersync_alloc( self._steppersync = self._ffi_lib.steppersync_alloc(
self._serial.serialqueue, self._stepqueues, len(self._stepqueues), self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
move_count) move_count)
@ -647,25 +658,36 @@ class MCU:
if self._steppersync is None: if self._steppersync is None:
return return
clock = self.print_time_to_clock(print_time) clock = self.print_time_to_clock(print_time)
if clock < 0:
return
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock) ret = self._ffi_lib.steppersync_flush(self._steppersync, clock)
if ret: if ret:
raise error("Internal error in stepcompress") raise error("Internal error in MCU '%s' stepcompress" % (
self._name,))
def check_active(self, print_time, eventtime): def check_active(self, print_time, eventtime):
if self._steppersync is None:
return
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
if self._clocksync.is_active(eventtime): if self._clocksync.is_active(eventtime):
return return
logging.info("Timeout with firmware (eventtime=%f)", eventtime) logging.info("Timeout with MCU '%s' (eventtime=%f)",
self._printer.note_mcu_error("Lost communication with firmware") self._name, eventtime)
self._printer.note_mcu_error("Lost communication with MCU '%s'" % (
self._name,))
def stats(self, eventtime): def stats(self, eventtime):
msg = "mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % ( msg = "%s: mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
self._mcu_tick_awake, self._mcu_tick_avg, self._mcu_tick_stddev) self._name, self._mcu_tick_awake, self._mcu_tick_avg,
return ' '.join([self._serial.stats(eventtime), self._mcu_tick_stddev)
self._clocksync.stats(eventtime), msg]) return ' '.join([msg, self._serial.stats(eventtime),
self._clocksync.stats(eventtime)])
def force_shutdown(self): def force_shutdown(self):
self.send(self._emergency_stop_cmd.encode()) self.send(self._emergency_stop_cmd.encode())
def microcontroller_restart(self): def microcontroller_restart(self):
reactor = self._printer.reactor reactor = self._printer.reactor
if self._restart_method == 'rpi_usb': if self._restart_method == 'rpi_usb':
logging.info("Attempting a microcontroller reset via rpi usb power") logging.info("Attempting MCU '%s' reset via rpi usb power",
self._name)
self.disconnect() self.disconnect()
chelper.run_hub_ctrl(0) chelper.run_hub_ctrl(0)
reactor.pause(reactor.monotonic() + 2.000) reactor.pause(reactor.monotonic() + 2.000)
@ -675,11 +697,13 @@ class MCU:
eventtime = reactor.monotonic() eventtime = reactor.monotonic()
if ((self._reset_cmd is None and self._config_reset_cmd is None) if ((self._reset_cmd is None and self._config_reset_cmd is None)
or not self._clocksync.is_active(eventtime)): or not self._clocksync.is_active(eventtime)):
logging.info("Unable to issue reset command") logging.info("Unable to issue reset command on MCU '%s'",
self._name)
return return
if self._reset_cmd is None: if self._reset_cmd is None:
# Attempt reset via config_reset command # Attempt reset via config_reset command
logging.info("Attempting a microcontroller config_reset command") logging.info("Attempting MCU '%s' config_reset command",
self._name)
self._is_shutdown = True self._is_shutdown = True
self.force_shutdown() self.force_shutdown()
reactor.pause(reactor.monotonic() + 0.015) reactor.pause(reactor.monotonic() + 0.015)
@ -688,13 +712,13 @@ class MCU:
self.disconnect() self.disconnect()
return return
# Attempt reset via reset command # Attempt reset via reset command
logging.info("Attempting a microcontroller reset command") logging.info("Attempting MCU '%s' reset command", self._name)
self.send(self._reset_cmd.encode()) self.send(self._reset_cmd.encode())
reactor.pause(reactor.monotonic() + 0.015) reactor.pause(reactor.monotonic() + 0.015)
self.disconnect() self.disconnect()
return return
# Attempt reset via arduino mechanism # Attempt reset via arduino mechanism
logging.info("Attempting a microcontroller reset") logging.info("Attempting MCU '%s' reset", self._name)
self.disconnect() self.disconnect()
serialhdl.arduino_reset(self._serialport, reactor) serialhdl.arduino_reset(self._serialport, reactor)
def disconnect(self): def disconnect(self):
@ -731,3 +755,18 @@ def error_help(msg):
def add_printer_objects(printer, config): def add_printer_objects(printer, config):
mainsync = clocksync.ClockSync(printer.reactor) mainsync = clocksync.ClockSync(printer.reactor)
printer.add_object('mcu', MCU(printer, config.getsection('mcu'), mainsync)) printer.add_object('mcu', MCU(printer, config.getsection('mcu'), mainsync))
for s in config.get_prefix_sections('mcu '):
printer.add_object(s.section, MCU(
printer, s, clocksync.SecondarySync(printer.reactor, mainsync)))
def get_printer_mcus(printer):
return [printer.objects[n] for n in sorted(printer.objects)
if n.startswith('mcu')]
def get_printer_mcu(printer, name):
mcu_name = name
if name != 'mcu':
mcu_name = 'mcu ' + name
if mcu_name not in printer.objects:
raise printer.config_error("Unknown MCU %s" % (name,))
return printer.objects[mcu_name]

View File

@ -4,7 +4,7 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging import math, logging
import homing, cartesian, corexy, delta, extruder import mcu, homing, cartesian, corexy, delta, extruder
# Common suffixes: _d is distance (in mm), _v is velocity (in # Common suffixes: _d is distance (in mm), _v is velocity (in
# mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in # mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in
@ -184,7 +184,8 @@ class ToolHead:
def __init__(self, printer, config): def __init__(self, printer, config):
self.printer = printer self.printer = printer
self.reactor = printer.reactor self.reactor = printer.reactor
self.mcu = printer.objects['mcu'] self.all_mcus = mcu.get_printer_mcus(printer)
self.mcu = self.all_mcus[0]
self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_velocity = config.getfloat('max_velocity', above=0.)
self.max_accel = config.getfloat('max_accel', above=0.) self.max_accel = config.getfloat('max_accel', above=0.)
self.max_accel_to_decel = config.getfloat( self.max_accel_to_decel = config.getfloat(
@ -227,7 +228,8 @@ class ToolHead:
def update_move_time(self, movetime): def update_move_time(self, movetime):
self.print_time += movetime self.print_time += movetime
flush_to_time = self.print_time - self.move_flush_time flush_to_time = self.print_time - self.move_flush_time
self.mcu.flush_moves(flush_to_time) for m in self.all_mcus:
m.flush_moves(flush_to_time)
def get_next_move_time(self): def get_next_move_time(self):
if not self.sync_print_time: if not self.sync_print_time:
return self.print_time return self.print_time
@ -248,9 +250,10 @@ class ToolHead:
if sync_print_time or must_sync: if sync_print_time or must_sync:
self.sync_print_time = True self.sync_print_time = True
self.move_queue.set_flush_time(self.buffer_time_high) self.move_queue.set_flush_time(self.buffer_time_high)
self.mcu.flush_moves(self.print_time)
self.need_check_stall = -1. self.need_check_stall = -1.
self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) self.reactor.update_timer(self.flush_timer, self.reactor.NEVER)
for m in self.all_mcus:
m.flush_moves(self.print_time)
def get_last_move_time(self): def get_last_move_time(self):
self._flush_lookahead() self._flush_lookahead()
return self.get_next_move_time() return self.get_next_move_time()
@ -357,7 +360,8 @@ class ToolHead:
self.commanded_pos[3] = extrude_pos self.commanded_pos[3] = extrude_pos
# Misc commands # Misc commands
def check_active(self, eventtime): def check_active(self, eventtime):
self.mcu.check_active(self.print_time, eventtime) for m in self.all_mcus:
m.check_active(self.print_time, eventtime)
if not self.sync_print_time: if not self.sync_print_time:
return True return True
return self.print_time + 60. > self.mcu.estimated_print_time(eventtime) return self.print_time + 60. > self.mcu.estimated_print_time(eventtime)
@ -368,7 +372,8 @@ class ToolHead:
self.print_time, buffer_time, self.print_stall) self.print_time, buffer_time, self.print_stall)
def force_shutdown(self): def force_shutdown(self):
try: try:
self.mcu.force_shutdown() for m in self.all_mcus:
m.force_shutdown()
self.move_queue.reset() self.move_queue.reset()
self.reset_print_time() self.reset_print_time()
except: except: