From 55ac1287e8c7b99b8b62d0b78b55dabfb28ed242 Mon Sep 17 00:00:00 2001 From: Brandon Nance Date: Wed, 2 Aug 2023 02:18:04 -0400 Subject: [PATCH] noncritical mcus --- klippy/clocksync.py | 3 ++ klippy/mcu.py | 75 +++++++++++++++++++++++++++++++++++++++++++-- klippy/serialhdl.py | 31 ++++++++++++++++++- 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/klippy/clocksync.py b/klippy/clocksync.py index 33859af70..71cc39c6b 100644 --- a/klippy/clocksync.py +++ b/klippy/clocksync.py @@ -29,6 +29,9 @@ def __init__(self, reactor): self.prediction_variance = 0.0 self.last_prediction_time = 0.0 + def disconnect(self): + self.reactor.update_timer(self.get_clock_timer, self.reactor.NEVER) + def connect(self, serial): self.serial = serial self.mcu_freq = serial.msgparser.get_constant_float("CLOCK_FREQ") diff --git a/klippy/mcu.py b/klippy/mcu.py index 966f4c820..b8059edd6 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -746,6 +746,7 @@ class MCU: error = error def __init__(self, config, clocksync): + self._config = config self._printer = printer = config.get_printer() self.danger_options = printer.lookup_object("danger_options") self._clocksync = clocksync @@ -807,6 +808,18 @@ def __init__(self, config, clocksync): self._mcu_tick_avg = 0.0 self._mcu_tick_stddev = 0.0 self._mcu_tick_awake = 0.0 + + # noncritical mcus + self.non_critical_recon_timer = self._reactor.register_timer( + self.non_critical_recon_event + ) + self.is_non_critical = config.getboolean("is_non_critical", False) + self._non_critical_disconnected = False + # self.last_noncrit_recon_eventtime = None + self.reconnect_interval = ( + config.getfloat("reconnect_interval", 2.0) + 0.12 + ) # add small change to not collide with other events + # Register handlers printer.register_event_handler( "klippy:firmware_restart", self._firmware_restart @@ -852,7 +865,7 @@ def _handle_shutdown(self, params): self._printer.invoke_async_shutdown(prefix + msg + error_help(msg)) def _handle_starting(self, params): - if not self._is_shutdown: + if not self._is_shutdown and not self.is_non_critical: self._printer.invoke_async_shutdown( "MCU '%s' spontaneous restart" % (self._name,) ) @@ -892,6 +905,19 @@ def dummy_estimated_print_time(eventtime): self.estimated_print_time = dummy_estimated_print_time + def handle_non_critical_disconnect(self): + self._non_critical_disconnected = True + self._clocksync.disconnect() + self._disconnect() + self._reactor.update_timer( + self.non_critical_recon_timer, self._reactor.NOW + ) + logging.info("mcu: %s disconnected", self._name) + + def non_critical_recon_event(self, eventtime): + self.recon_mcu() + return eventtime + self.reconnect_interval + def _send_config(self, prev_crc): # Build config commands for cb in self._config_callbacks: @@ -899,6 +925,7 @@ def _send_config(self, prev_crc): self._config_cmds.insert( 0, "allocate_oids count=%d" % (self._oid_count,) ) + # Resolve pin names mcu_type = self._serial.get_msgparser().get_constant("MCU") ppins = self._printer.lookup_object("pins") @@ -906,6 +933,7 @@ def _send_config(self, prev_crc): for cmdlist in (self._config_cmds, self._restart_cmds, self._init_cmds): for i, cmd in enumerate(cmdlist): cmdlist[i] = pin_resolver.update_command(cmd) + logging.info("command: %s", cmdlist[i]) # Calculate config CRC encoded_config = "\n".join(self._config_cmds).encode() config_crc = zlib.crc32(encoded_config) & 0xFFFFFFFF @@ -975,7 +1003,35 @@ def _log_info(self): ] return "\n".join(log_info) + def recon_mcu(self): + res = self._mcu_identify() + if not res: + return + self.reset_to_initial_state() + self._connect() + self._reactor.update_timer( + self.non_critical_recon_timer, self._reactor.NEVER + ) + self._reactor.unregister_timer(self.non_critical_recon_timer) + self.last_noncrit_recon_eventtime = None + logging.info("mcu: %s reconnected", self._name) + + def reset_to_initial_state(self): + self._oid_count = 0 + self._config_cmds = [] + self._restart_cmds = [] + self._init_cmds = [] + self._reserved_move_slots = 0 + self._stepqueues = [] + self._steppersync = None + def _connect(self): + if self._non_critical_disconnected: + self.non_critical_recon_timer = self._reactor.register_timer( + self.non_critical_recon_event, + self._reactor.NOW + self.reconnect_interval, + ) + return config_params = self._send_get_config() if not config_params["is_config"]: if self._restart_method == "rpi_usb": @@ -1015,7 +1071,16 @@ def _connect(self): log_info = self._log_info() + "\n" + move_msg self._printer.set_rollover_info(self._name, log_info, log=False) + def _check_serial_exists(self): + rts = self._restart_method != "cheetah" + return self._serial.check_connect(self._serialport, self._baud, rts) + def _mcu_identify(self): + if self.is_non_critical and not self._check_serial_exists(): + self._non_critical_disconnected = True + return False + else: + self._non_critical_disconnected = False if self.is_fileoutput(): self._connect_file() else: @@ -1069,6 +1134,7 @@ def _mcu_identify(self): self.register_response(self._handle_shutdown, "shutdown") self.register_response(self._handle_shutdown, "is_shutdown") self.register_response(self._handle_mcu_stats, "stats") + return True # Config creation helpers def setup_pin(self, pin_type, pin_params): @@ -1222,7 +1288,9 @@ def _restart_rpi_usb(self): chelper.run_hub_ctrl(1) def _firmware_restart(self, force=False): - if self._is_mcu_bridge and not force: + if ( + self._is_mcu_bridge and not force + ) or self._non_critical_disconnected: return if self._restart_method == "rpi_usb": self._restart_rpi_usb() @@ -1269,6 +1337,9 @@ def check_active(self, print_time, eventtime): or self._is_timeout ): return + if self.is_non_critical: + self.handle_non_critical_disconnect() + return self._is_timeout = True logging.info( "Timeout with MCU '%s' (eventtime=%f)", self._name, eventtime diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 8ce5d5a2c..74d369c0a 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -203,7 +203,11 @@ def connect_uart(self, serialport, baud, rts=True): # Initial connection logging.info("%sStarting serial connect", self.warn_prefix) start_time = self.reactor.monotonic() - while True: + while 1: + if ( + self.serialqueue is not None + ): # if we're already connected, don't recon + break if self.reactor.monotonic() > start_time + 90.0: self._error("Unable to connect") try: @@ -224,6 +228,17 @@ def connect_uart(self, serialport, baud, rts=True): if ret: break + def check_connect(self, serialport, baud, rts=True): + serial_dev = serial.Serial(baudrate=baud, timeout=0, exclusive=False) + serial_dev.port = serialport + serial_dev.rts = rts + try: + serial_dev.open() + except Exception: + return False + serial_dev.close() + return True + def connect_file(self, debugoutput, dictionary, pace=False): self.serial_dev = debugoutput self.msgparser.process_identify(dictionary, decompress=False) @@ -280,11 +295,25 @@ def register_response(self, callback, name, oid=None): # Command sending def raw_send(self, cmd, minclock, reqclock, cmd_queue): + if self.serialqueue is None: + logging.info( + "%sSerial connection closed, cmd: %s", + self.warn_prefix, + repr(cmd), + ) + return self.ffi_lib.serialqueue_send( self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock, 0 ) def raw_send_wait_ack(self, cmd, minclock, reqclock, cmd_queue): + if self.serialqueue is None: + logging.info( + "%sSerial connection closed, in wait ack, cmd: %s", + self.warn_prefix, + repr(cmd), + ) + return self.last_notify_id += 1 nid = self.last_notify_id completion = self.reactor.completion()