Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,8 @@ def set_uart_callback(self, uart, install):
# The panda will NAK CAN writes when there is CAN congestion.
# libusb will try to send it again, with a max timeout.
# Timeout is in ms. If set to 0, the timeout is infinite.
CAN_SEND_TIMEOUT_MS = 10
CAN_SEND_TIMEOUT_MS = 5 # 5ms like C++ implementation
CAN_MAX_RETRIES = 3

def can_reset_communications(self):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xc0, 0, 0, b'')
Expand All @@ -701,8 +702,16 @@ def can_reset_communications(self):
def can_send_many(self, arr, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS):
snds = pack_can_buffer(arr, chunk=(not self.spi), fd=fd)
for tx in snds:
retries = 0
while len(tx) > 0:
bs = self._handle.bulkWrite(3, tx, timeout=timeout)
if bs == 0:
retries += 1
if retries > self.CAN_MAX_RETRIES:
logger.warning("CAN send: no progress after retries, dropping")
break
else:
retries = 0
tx = tx[bs:]

def can_send(self, addr, dat, bus, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS):
Expand All @@ -711,13 +720,16 @@ def can_send(self, addr, dat, bus, *, fd=False, timeout=CAN_SEND_TIMEOUT_MS):
@ensure_can_packet_version
def can_recv(self):
dat = bytearray()
while True:
for _ in range(self.CAN_MAX_RETRIES):
try:
dat = self._handle.bulkRead(1, 16384) # Max receive batch size + 2 extra reserve frames
break
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
logger.error("CAN: BAD RECV, RETRYING")
time.sleep(0.1)
time.sleep(0.01) # 10ms sleep, not 100ms
else:
logger.error("CAN: recv failed after retries")
return []
msgs, self.can_rx_overflow_buffer = unpack_can_buffer(self.can_rx_overflow_buffer + dat)
return msgs

Expand Down
13 changes: 12 additions & 1 deletion python/spi.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
CHECKSUM_START = 0xAB

MIN_ACK_TIMEOUT_MS = 100
MAX_ACK_TIMEOUT_MS = 500 # like C++ SPI_ACK_TIMEOUT
DEFAULT_TIMEOUT_MS = 500 # default when timeout=0
MAX_XFER_RETRY_COUNT = 5
MAX_TIMEOUT_RETRIES = 5 # like C++

SPI_BUF_SIZE = 4096 # from panda/board/drivers/spi.h
XFER_SIZE = SPI_BUF_SIZE - 0x40 # give some room for SPI protocol overhead
Expand Down Expand Up @@ -127,6 +130,8 @@ def _calc_checksum(self, data: bytes) -> int:
return cksum

def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes:
# Original behavior preserved - timeout=0 means wait forever within this function
# The caller (_transfer) handles the overall timeout
timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3

start = time.monotonic()
Expand Down Expand Up @@ -182,10 +187,15 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e
logger.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len)
logger.debug("==============================================")

# Fix timeout=0 infinite loop: default to DEFAULT_TIMEOUT_MS
if timeout == 0:
timeout = DEFAULT_TIMEOUT_MS

n = 0
start_time = time.monotonic()
exc = PandaSpiException()
while (timeout == 0) or (time.monotonic() - start_time) < timeout*1e-3:
# Use the timeout for the overall loop, matching original behavior but with timeout=0 fixed
while (time.monotonic() - start_time) < timeout * 1e-3:
n += 1
logger.debug("\ntry #%d", n)
with self.dev.acquire() as spi:
Expand All @@ -209,6 +219,7 @@ def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, e
except PandaSpiException:
nack_cnt = 0

logger.error("SPI transfer failed after %d tries, %.2fms", n, (time.monotonic() - start_time) * 1000)
raise exc

def get_protocol_version(self) -> bytes:
Expand Down
Loading