From b2738c0dfaddf71aa330d97c14cc307043be310f Mon Sep 17 00:00:00 2001 From: B Date: Tue, 21 May 2024 22:20:30 +0100 Subject: [PATCH] noncritical mcus --- klippy/extras/adxl345.py | 8 +++ klippy/extras/display/display.py | 8 +++ klippy/extras/display/st7920.py | 1 + klippy/extras/display/uc1701.py | 2 + klippy/extras/lis2dw.py | 8 +++ klippy/extras/mpu9250.py | 8 +++ klippy/extras/neopixel.py | 3 + klippy/extras/temperature_mcu.py | 4 ++ klippy/extras/tmc.py | 9 ++- klippy/extras/tmc2130.py | 2 + klippy/extras/tmc2660.py | 2 + klippy/extras/tmc_uart.py | 2 + klippy/mcu.py | 106 +++++++++++++++++++++---------- klippy/serialhdl.py | 19 ++++-- klippy/stepper.py | 2 +- 15 files changed, 142 insertions(+), 42 deletions(-) diff --git a/klippy/extras/adxl345.py b/klippy/extras/adxl345.py index bbc9e32b86cb..5bdefbafcf34 100644 --- a/klippy/extras/adxl345.py +++ b/klippy/extras/adxl345.py @@ -223,6 +223,13 @@ def _build_config(self): "query_adxl345 oid=%c rest_ticks=%u", cq=cmdqueue) self.ffreader.setup_query_command("query_adxl345_status oid=%c", oid=self.oid, cq=cmdqueue) + + def check_connected(self): + if self.mcu.non_critical_disconnected: + raise self.printer.command_error( + f"ADXL: {self.name} could not connect because mcu: {self.mcu.get_name()} is non_critical_disconnected!" + ) + def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -237,6 +244,7 @@ def set_reg(self, reg, val, minclock=0): "(e.g. faulty wiring) or a faulty adxl345 chip." % ( reg, val, stored_val)) def start_internal_client(self): + self.check_connected() aqh = AccelQueryHelper(self.printer) self.batch_bulk.add_client(aqh.handle_batch) return aqh diff --git a/klippy/extras/display/display.py b/klippy/extras/display/display.py index 2ce352de430a..521d527d9222 100644 --- a/klippy/extras/display/display.py +++ b/klippy/extras/display/display.py @@ -199,6 +199,10 @@ def __init__(self, config): raise config.error("Unknown display_data group '%s'" % (dgroup,)) # Screen updating self.printer.register_event_handler("klippy:ready", self.handle_ready) + self.printer.register_event_handler( + self.lcd_chip.mcu.get_non_critical_reconnect_event_name(), + self.handle_reconnect, + ) self.screen_update_timer = self.reactor.register_timer( self.screen_update_event) self.redraw_request_pending = False @@ -213,6 +217,10 @@ def __init__(self, config): self.cmd_SET_DISPLAY_GROUP) def get_dimensions(self): return self.lcd_chip.get_dimensions() + + def handle_reconnect(self): + self.lcd_chip.init() + def handle_ready(self): self.lcd_chip.init() # Start screen update timer diff --git a/klippy/extras/display/st7920.py b/klippy/extras/display/st7920.py index 5b2a943c62ca..9e6ac45865fd 100644 --- a/klippy/extras/display/st7920.py +++ b/klippy/extras/display/st7920.py @@ -223,6 +223,7 @@ def __init__(self, config): sw_pins = tuple([pin_params['pin'] for pin_params in sw_pin_params]) speed = config.getint('spi_speed', 1000000, minval=100000) self.spi = bus.MCU_SPI(mcu, None, None, 0, speed, sw_pins) + self.mcu = mcu # create enable helper self.en_helper = EnableHelper(config.get("en_pin"), self.spi) self.en_set = False diff --git a/klippy/extras/display/uc1701.py b/klippy/extras/display/uc1701.py index 8e877cf2ec87..d9d54db65dda 100644 --- a/klippy/extras/display/uc1701.py +++ b/klippy/extras/display/uc1701.py @@ -167,6 +167,7 @@ def init(self): class UC1701(DisplayBase): def __init__(self, config): io = SPI4wire(config, "a0_pin") + self.mcu = io.spi.get_mcu() DisplayBase.__init__(self, io) self.contrast = config.getint('contrast', 40, minval=0, maxval=63) self.reset = ResetHelper(config.get("rst_pin", None), io.spi) @@ -204,6 +205,7 @@ def __init__(self, config, columns=128, x_offset=0): else: io = SPI4wire(config, "dc_pin") io_bus = io.spi + self.mcu = io_bus.get_mcu() self.reset = ResetHelper(config.get("reset_pin", None), io_bus) DisplayBase.__init__(self, io, columns, x_offset) self.contrast = config.getint('contrast', 239, minval=0, maxval=255) diff --git a/klippy/extras/lis2dw.py b/klippy/extras/lis2dw.py index 45c12ea1aba1..584cc78ccebe 100644 --- a/klippy/extras/lis2dw.py +++ b/klippy/extras/lis2dw.py @@ -68,6 +68,13 @@ def _build_config(self): "query_lis2dw oid=%c rest_ticks=%u", cq=cmdqueue) self.ffreader.setup_query_command("query_lis2dw_status oid=%c", oid=self.oid, cq=cmdqueue) + + def check_connected(self): + if self.mcu.non_critical_disconnected: + raise self.printer.command_error( + f"LIS2DW: {self.name} could not connect because mcu: {self.mcu.get_name()} is non_critical_disconnected!" + ) + def read_reg(self, reg): params = self.spi.spi_transfer([reg | REG_MOD_READ, 0x00]) response = bytearray(params['response']) @@ -82,6 +89,7 @@ def set_reg(self, reg, val, minclock=0): "(e.g. faulty wiring) or a faulty lis2dw chip." % ( reg, val, stored_val)) def start_internal_client(self): + self.check_connected() aqh = adxl345.AccelQueryHelper(self.printer) self.batch_bulk.add_client(aqh.handle_batch) return aqh diff --git a/klippy/extras/mpu9250.py b/klippy/extras/mpu9250.py index ff15fed41be6..f356287bd5e9 100644 --- a/klippy/extras/mpu9250.py +++ b/klippy/extras/mpu9250.py @@ -93,12 +93,20 @@ def _build_config(self): "query_mpu9250 oid=%c rest_ticks=%u", cq=cmdqueue) self.ffreader.setup_query_command("query_mpu9250_status oid=%c", oid=self.oid, cq=cmdqueue) + + def check_connected(self): + if self.mcu.non_critical_disconnected: + raise self.printer.command_error( + f"MPU: {self.name} could not connect because mcu: {self.mcu.get_name()} is non_critical_disconnected!" + ) + def read_reg(self, reg): params = self.i2c.i2c_read([reg], 1) return bytearray(params['response'])[0] def set_reg(self, reg, val, minclock=0): self.i2c.i2c_write([reg, val & 0xFF], minclock=minclock) def start_internal_client(self): + self.check_connected() aqh = adxl345.AccelQueryHelper(self.printer) self.batch_bulk.add_client(aqh.handle_batch) return aqh diff --git a/klippy/extras/neopixel.py b/klippy/extras/neopixel.py index b6daeb4d29e3..e3b4f1876a65 100644 --- a/klippy/extras/neopixel.py +++ b/klippy/extras/neopixel.py @@ -48,6 +48,9 @@ def __init__(self, config): self.old_color_data = bytearray([d ^ 1 for d in self.color_data]) # Register callbacks printer.register_event_handler("klippy:connect", self.send_data) + printer.register_event_handler( + self.mcu.get_non_critical_reconnect_event_name(), self.send_data + ) def build_config(self): bmt = self.mcu.seconds_to_clock(BIT_MAX_TIME) rmt = self.mcu.seconds_to_clock(RESET_MIN_TIME) diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index bc96724a4e4b..3bbbcd5e29d2 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -40,6 +40,7 @@ def __init__(self, config): return self.printer.register_event_handler("klippy:mcu_identify", self.handle_mcu_identify) + self.mcu_adc.get_mcu().register_config_callback(self._build_config) # Temperature interface def setup_callback(self, temperature_callback): self.temperature_callback = temperature_callback @@ -59,6 +60,9 @@ def calc_adc(self, temp): def calc_base(self, temp, adc): return temp - adc * self.slope def handle_mcu_identify(self): + self._build_config() + + def _build_config(self): # Obtain mcu information mcu = self.mcu_adc.get_mcu() self.debug_read_cmd = mcu.lookup_query_command( diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 1d8599e2ed2d..ad075ad2bade 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -403,7 +403,14 @@ def _handle_connect(self): self.stepper_name) # Send init try: - self._init_registers() + if self.mcu_tmc.mcu.non_critical_disconnected: + logging.info( + "TMC %s failed to init - non_critical_mcu: %s is disconnected!", + self.name, + self.mcu_tmc.mcu.get_name(), + ) + else: + self._init_registers() except self.printer.command_error as e: logging.info("TMC %s failed to init: %s", self.name, str(e)) # get_status information export diff --git a/klippy/extras/tmc2130.py b/klippy/extras/tmc2130.py index 20a25c66c514..37f966e71023 100644 --- a/klippy/extras/tmc2130.py +++ b/klippy/extras/tmc2130.py @@ -256,6 +256,8 @@ def __init__(self, config, name_to_reg, fields, tmc_frequency): self.name_to_reg = name_to_reg self.fields = fields self.tmc_frequency = tmc_frequency + self.mcu = self.tmc_spi.spi.get_mcu() + def get_fields(self): return self.fields def get_register(self, reg_name): diff --git a/klippy/extras/tmc2660.py b/klippy/extras/tmc2660.py index dcffac759fbd..1cd2a5eae548 100644 --- a/klippy/extras/tmc2660.py +++ b/klippy/extras/tmc2660.py @@ -196,6 +196,8 @@ def __init__(self, config, name_to_reg, fields): self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=4000000) self.name_to_reg = name_to_reg self.fields = fields + self.mcu = self.spi.get_mcu() + def get_fields(self): return self.fields def get_register(self, reg_name): diff --git a/klippy/extras/tmc_uart.py b/klippy/extras/tmc_uart.py index 4d5ec1d5a257..83fac71f85b4 100644 --- a/klippy/extras/tmc_uart.py +++ b/klippy/extras/tmc_uart.py @@ -220,6 +220,8 @@ def __init__(self, config, name_to_reg, fields, max_addr, tmc_frequency): config, max_addr) self.mutex = self.mcu_uart.mutex self.tmc_frequency = tmc_frequency + self.mcu = self.mcu_uart.mcu + def get_fields(self): return self.fields def _do_get_register(self, reg_name): diff --git a/klippy/mcu.py b/klippy/mcu.py index ec0b494b34bc..eb7133f80c6e 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -550,6 +550,7 @@ class MCU: def __init__(self, config, clocksync): self._config = config self._printer = printer = config.get_printer() + self.gcode = printer.lookup_object("gcode") self._clocksync = clocksync self._reactor = printer.get_reactor() self._name = config.get_name() @@ -606,15 +607,31 @@ def __init__(self, config, clocksync): self._mcu_tick_stddev = 0. self._mcu_tick_awake = 0. - #noncritical mcus - self.non_critical_recon_timer = self._reactor.register_timer( - self.non_critical_recon_event - ) + # noncritical mcus 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 - + if self.is_non_critical and self.get_name() == "mcu": + raise error("Primary MCU cannot be marked as non-critical!") + if self.is_non_critical: + self.non_critical_recon_timer = self._reactor.register_timer( + self.non_critical_recon_event + ) + if canbus_uuid: + raise error("CAN MCUs can't be non-critical yet!") + self.non_critical_disconnected = False + self._non_critical_reconnect_event_name = ( + f"danger:non_critical_mcu_{self.get_name()}:reconnected" + ) + self._non_critical_disconnect_event_name = ( + f"danger:non_critical_mcu_{self.get_name()}:disconnected" + ) + self.reconnect_interval = ( + config.getfloat("reconnect_interval", 2.0) + 0.12 + ) # add small change to not collide with other events + self._cached_init_state = False + self._oid_count_post_inits = 0 + self._config_cmds_post_inits = [] + self._init_cmds_post_inits = [] + self._restart_cmds_post_inits = [] # Register handlers printer.load_object(config, "error_mcu") printer.register_event_handler("klippy:firmware_restart", @@ -687,32 +704,45 @@ 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.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) + self._printer.send_event(self._non_critical_disconnect_event_name) + self.gcode.respond_info(f"mcu: '{self._name}' disconnected!", log=True) def non_critical_recon_event(self, eventtime): - self.recon_mcu() - return eventtime + self.reconnect_interval + success = self.recon_mcu() + if success: + self.gcode.respond_info( + f"mcu: '{self._name}' reconnected!", log=True + ) + return self._reactor.NEVER + else: + return eventtime + self.reconnect_interval def _send_config(self, prev_crc): + if not self._cached_init_state: + # first time config, we haven't created callback oids yet + # so save the oid count for state reset later + self._oid_count_post_inits = self._oid_count + self._config_cmds_post_inits = self._config_cmds.copy() + self._init_cmds_post_inits = self._init_cmds.copy() + self._restart_cmds_post_inits = self._restart_cmds.copy() + self._cached_init_state = True # Build config commands for cb in self._config_callbacks: cb() self._config_cmds.insert(0, "allocate_oids count=%d" % (self._oid_count,)) - # Resolve pin names ppins = self._printer.lookup_object('pins') pin_resolver = ppins.get_pin_resolver(self._name) 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 @@ -770,30 +800,27 @@ def _log_info(self): def recon_mcu(self): res = self._mcu_identify() if not res: - return + return False self.reset_to_initial_state() - + self.non_critical_disconnected = False 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) + self._printer.send_event(self._non_critical_reconnect_event_name) + return True def reset_to_initial_state(self): - self._oid_count = 0 - self._config_cmds = [] - self._restart_cmds = [] - self._init_cmds = [] + if self._cached_init_state: + self._oid_count = self._oid_count_post_inits + self._config_cmds = self._config_cmds_post_inits.copy() + self._init_cmds = self._init_cmds_post_inits.copy() + self._restart_cmds = self._restart_cmds_post_inits.copy() 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 + if self.non_critical_disconnected: + self._reactor.update_timer( + self.non_critical_recon_timer, + self._reactor.NOW + self.reconnect_interval, ) return config_params = self._send_get_config() @@ -829,15 +856,17 @@ def _connect(self): logging.info(move_msg) 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) + 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 + self.non_critical_disconnected = True return False else: - self._non_critical_disconnected = False + self.non_critical_disconnected = False if self.is_fileoutput(): self._connect_file() else: @@ -937,6 +966,13 @@ def get_printer(self): return self._printer def get_name(self): return self._name + + def get_non_critical_reconnect_event_name(self): + return self._non_critical_reconnect_event_name + + def get_non_critical_disconnect_event_name(self): + return self._non_critical_disconnect_event_name + def register_response(self, cb, msg, oid=None): self._serial.register_response(cb, msg, oid) def alloc_command_queue(self): @@ -1009,7 +1045,7 @@ def _restart_rpi_usb(self): self._reactor.pause(self._reactor.monotonic() + 2.) 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() @@ -1051,10 +1087,10 @@ def check_active(self, print_time, eventtime): if (self._clocksync.is_active() or self.is_fileoutput() or self._is_timeout): return - self._is_timeout = True 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) self._printer.invoke_shutdown("Lost communication with MCU '%s'" % ( diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 8cbd9a53a847..d48e03bf6ed4 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -12,9 +12,10 @@ class error(Exception): pass class SerialReader: - def __init__(self, reactor, warn_prefix=""): + def __init__(self, reactor, warn_prefix="", mcu=None): self.reactor = reactor self.warn_prefix = warn_prefix + self.mcu = mcu # Serial port self.serial_dev = None self.msgparser = msgproto.MessageParser(warn_prefix=warn_prefix) @@ -198,6 +199,7 @@ def connect_uart(self, serialport, baud, rts=True): ret = self._start_session(serial_dev) 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 @@ -251,16 +253,23 @@ def register_response(self, callback, name, oid=None): del self.handlers[name, oid] else: self.handlers[name, oid] = callback + + def _check_noncritical_disconnected(self): + if self.mcu is not None and self.mcu.non_critical_disconnected: + self._error("non-critical MCU is disconnected") + # Command sending def raw_send(self, cmd, minclock, reqclock, cmd_queue): + self._check_noncritical_disconnected() 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) + 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): + self._check_noncritical_disconnected() 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 diff --git a/klippy/stepper.py b/klippy/stepper.py index 9b692904dcf9..7835096e2ddd 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -184,7 +184,7 @@ def note_homing_end(self): raise error("Internal error in stepcompress") self._query_mcu_position() def _query_mcu_position(self): - if self._mcu.is_fileoutput(): + if self._mcu.is_fileoutput() or self._mcu.non_critical_disconnected: return params = self._get_position_cmd.send([self._oid]) last_pos = params['pos']