diff --git a/klippy/extras/tmc_uart.py b/klippy/extras/tmc_uart.py index 7eab1240..7191c9b5 100644 --- a/klippy/extras/tmc_uart.py +++ b/klippy/extras/tmc_uart.py @@ -148,7 +148,7 @@ class MCU_TMC_uart_bitbang: if self.analog_mux is not None: self.analog_mux.activate(instance_id) msg = self._encode_read(0xf5, addr, reg) - params = self.tmcuart_send_cmd.send_with_response( + params = self.tmcuart_send_cmd.send_with_async_response( [self.oid, msg, 10], 'tmcuart_response', self.oid) return self._decode_read(reg, params['read']) def reg_write(self, instance_id, addr, reg, val, print_time=None): @@ -158,7 +158,7 @@ class MCU_TMC_uart_bitbang: if self.analog_mux is not None: self.analog_mux.activate(instance_id) msg = self._encode_write(0xf5, addr, reg | 0x80, val) - self.tmcuart_send_cmd.send_with_response( + self.tmcuart_send_cmd.send_with_async_response( [self.oid, msg, 0], 'tmcuart_response', self.oid, minclock=minclock) diff --git a/klippy/mcu.py b/klippy/mcu.py index 09f5d232..4be952f2 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -301,6 +301,35 @@ class MCU_adc: if self._callback is not None: self._callback(last_read_time, last_value) +# Class to retry sending of a query command until a given response is received +class RetryAsyncCommand: + TIMEOUT_TIME = 5.0 + RETRY_TIME = 0.500 + def __init__(self, mcu, serial, name, oid=None): + self.reactor = mcu.get_printer().get_reactor() + self.serial = serial + self.name = name + self.oid = oid + self.completion = self.reactor.completion() + self.min_query_time = self.reactor.monotonic() + self.serial.register_response(self.handle_callback, name, oid) + def handle_callback(self, params): + if params['#sent_time'] >= self.min_query_time: + self.min_query_time = self.reactor.NEVER + self.reactor.async_complete(self.completion, params) + def get_response(self, cmd, cmd_queue, minclock=0, minsystime=0.): + first_query_time = query_time = max(self.min_query_time, minsystime) + while 1: + self.serial.raw_send(cmd, minclock, minclock, cmd_queue) + params = self.completion.wait(query_time + self.RETRY_TIME) + if params is not None: + self.serial.register_response(None, self.name, self.oid) + return params + query_time = self.reactor.monotonic() + if query_time > first_query_time + self.TIMEOUT_TIME: + self.serial.register_response(None, self.name, self.oid) + raise error("Timeout on wait for '%s' response" % (self.name,)) + # Wrapper around command sending class CommandWrapper: def __init__(self, mcu, serial, clocksync, cmd, cmd_queue): @@ -314,16 +343,20 @@ class CommandWrapper: self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) def send_with_response(self, data=(), response=None, response_oid=None, minclock=0): + cmd = self._cmd.encode(data) + src = serialhdl.SerialRetryCommand(self._serial, response, response_oid) + try: + return src.get_response(cmd, self._cmd_queue, minclock=minclock) + except serialhdl.error as e: + raise error(str(e)) + def send_with_async_response(self, data=(), + response=None, response_oid=None, minclock=0): minsystime = 0. if minclock: minsystime = self._clocksync.estimate_clock_systime(minclock) cmd = self._cmd.encode(data) - src = serialhdl.SerialRetryCommand(self._serial, response, response_oid) - try: - return src.get_response([cmd], self._cmd_queue, - minclock=minclock, minsystime=minsystime) - except serialhdl.error as e: - raise error(str(e)) + src = RetryAsyncCommand(self._mcu, self._serial, response, response_oid) + return src.get_response(cmd, self._cmd_queue, minclock, minsystime) class MCU: error = error diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 190fd745..c7c6e4d2 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -58,20 +58,22 @@ class SerialReader: hdl(params) except: logging.exception("Exception in serial callback") - def _get_identify_data(self, timeout): + def _get_identify_data(self, eventtime): # Query the "data dictionary" from the micro-controller identify_data = "" while 1: msg = "identify offset=%d count=%d" % (len(identify_data), 40) - params = self.send_with_response(msg, 'identify_response') + try: + params = self.send_with_response(msg, 'identify_response') + except error as e: + logging.exception("Wait for identify_response") + return None if params['offset'] == len(identify_data): msgdata = params['data'] if not msgdata: # Done return identify_data identify_data += msgdata - if self.reactor.monotonic() > timeout: - raise error("Timeout during identify") def connect(self): # Initial connection logging.info("Starting serial connect") @@ -97,13 +99,12 @@ class SerialReader: self.background_thread = threading.Thread(target=self._bg_thread) self.background_thread.start() # Obtain and load the data dictionary from the firmware - try: - identify_data = self._get_identify_data(connect_time + 5.) - except error as e: - logging.exception("Timeout on serial connect") - self.disconnect() - continue - break + completion = self.reactor.register_callback(self._get_identify_data) + identify_data = completion.wait(connect_time + 5.) + if identify_data is not None: + break + logging.info("Timeout on serial connect") + self.disconnect() msgparser = msgproto.MessageParser() msgparser.process_identify(identify_data) self.msgparser = msgparser @@ -176,7 +177,7 @@ class SerialReader: def send_with_response(self, msg, response): cmd = self.msgparser.create_command(msg) src = SerialRetryCommand(self, response) - return src.get_response([cmd], self.default_cmd_queue) + return src.get_response(cmd, self.default_cmd_queue) def alloc_command_queue(self): return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(), self.ffi_lib.serialqueue_free_commandqueue) @@ -218,34 +219,32 @@ class SerialReader: def __del__(self): self.disconnect() -# Class to retry sending of a query command until a given response is received +# Class to send a query command and return the received response class SerialRetryCommand: - TIMEOUT_TIME = 5.0 - RETRY_TIME = 0.500 def __init__(self, serial, name, oid=None): self.serial = serial self.name = name self.oid = oid - self.completion = serial.reactor.completion() - self.min_query_time = serial.reactor.monotonic() + self.last_params = None self.serial.register_response(self.handle_callback, name, oid) def handle_callback(self, params): - if params['#sent_time'] >= self.min_query_time: - self.min_query_time = self.serial.reactor.NEVER - self.serial.reactor.async_complete(self.completion, params) - def get_response(self, cmds, cmd_queue, minclock=0, minsystime=0.): - first_query_time = query_time = max(self.min_query_time, minsystime) + self.last_params = params + def get_response(self, cmd, cmd_queue, minclock=0): + retries = 5 + retry_delay = .010 while 1: - for cmd in cmds: - self.serial.raw_send(cmd, minclock, minclock, cmd_queue) - params = self.completion.wait(query_time + self.RETRY_TIME) + self.serial.raw_send_wait_ack(cmd, minclock, minclock, cmd_queue) + params = self.last_params if params is not None: self.serial.register_response(None, self.name, self.oid) return params - query_time = self.serial.reactor.monotonic() - if query_time > first_query_time + self.TIMEOUT_TIME: + if retries <= 0: self.serial.register_response(None, self.name, self.oid) - raise error("Timeout on wait for '%s' response" % (self.name,)) + raise error("Unable to obtain '%s' response" % (self.name,)) + reactor = self.serial.reactor + reactor.pause(reactor.monotonic() + retry_delay) + retries -= 1 + retry_delay *= 2. # Attempt to place an AVR stk500v2 style programmer into normal mode def stk500v2_leave(ser, reactor):