lp-dp 2023-05-09T02:32:30 for EON/C2

version: lp-dp v0.9.2 for EON/C2
date: 2023-05-09T02:32:30
commit: cb8adff9d368de2e442e1e7f4309b70a4a2013ad
This commit is contained in:
Vehicle Researcher
2023-05-09 02:20:50 +00:00
parent c6efa8341a
commit c8440ab691
739 changed files with 6704 additions and 334410 deletions
+43 -25
View File
@@ -183,21 +183,31 @@ class Panda:
HW_TYPE_TRES = b'\x09'
CAN_PACKET_VERSION = 4
HEALTH_PACKET_VERSION = 11
HEALTH_PACKET_VERSION = 14
CAN_HEALTH_PACKET_VERSION = 4
# dp - 2 extra "B" at the end:
# "usb_power_mode": a[23],
# "torque_interceptor_detected": a[24],
HEALTH_STRUCT = struct.Struct("<IIIIIIIIIBBBBBBHBBBHfBBBB")
HEALTH_STRUCT = struct.Struct("<IIIIIIIIIBBBBBBHBBBHfBBHBHHBB")
CAN_HEALTH_STRUCT = struct.Struct("<BIBBBBBBBBIIIIIIIHHBBB")
F2_DEVICES = (HW_TYPE_PEDAL, )
F4_DEVICES = (HW_TYPE_WHITE_PANDA, HW_TYPE_GREY_PANDA, HW_TYPE_BLACK_PANDA, HW_TYPE_UNO, HW_TYPE_DOS)
H7_DEVICES = (HW_TYPE_RED_PANDA, HW_TYPE_RED_PANDA_V2, HW_TYPE_TRES)
INTERNAL_DEVICES = (HW_TYPE_UNO, HW_TYPE_DOS)
INTERNAL_DEVICES = (HW_TYPE_UNO, HW_TYPE_DOS, HW_TYPE_TRES)
HAS_OBD = (HW_TYPE_BLACK_PANDA, HW_TYPE_UNO, HW_TYPE_DOS, HW_TYPE_RED_PANDA, HW_TYPE_RED_PANDA_V2, HW_TYPE_TRES)
MAX_FAN_RPMs = {
HW_TYPE_UNO: 5100,
HW_TYPE_DOS: 6500,
HW_TYPE_TRES: 6600,
}
HARNESS_STATUS_NC = 0
HARNESS_STATUS_NORMAL = 1
HARNESS_STATUS_FLIPPED = 2
# first byte is for EPS scaling factor
FLAG_TOYOTA_ALT_BRAKE = (1 << 8)
FLAG_TOYOTA_STOCK_LONGITUDINAL = (2 << 8)
@@ -319,8 +329,9 @@ class Panda:
@staticmethod
def usb_connect(serial, claim=True, wait=False):
handle, usb_serial, bootstub, bcd = None, None, None, None
context = usb1.USBContext()
while 1:
context = usb1.USBContext()
context.open()
try:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee):
@@ -351,7 +362,7 @@ class Panda:
logging.exception("USB connect error")
if not wait or handle is not None:
break
context = usb1.USBContext() # New context needed so new devices show up
context.close()
usb_handle = None
if handle is not None:
@@ -367,21 +378,21 @@ class Panda:
@staticmethod
def usb_list():
context = usb1.USBContext()
ret = []
try:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee):
try:
serial = device.getSerialNumber()
if len(serial) == 24:
ret.append(serial)
else:
warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning)
except Exception:
continue
with usb1.USBContext() as context:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee):
try:
serial = device.getSerialNumber()
if len(serial) == 24:
ret.append(serial)
else:
warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning)
except Exception:
continue
except Exception:
pass
logging.exception("exception while listing pandas")
return ret
@staticmethod
@@ -424,7 +435,7 @@ class Panda:
except Exception:
logging.debug("reconnecting is taking %d seconds...", i + 1)
try:
dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type))
dfu = PandaDFU(self.get_dfu_serial())
dfu.recover()
except Exception:
pass
@@ -495,8 +506,8 @@ class Panda:
if reconnect:
self.reconnect()
def recover(self, timeout: Optional[int] = None, reset: bool = True) -> bool:
dfu_serial = PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type)
def recover(self, timeout: Optional[int] = 60, reset: bool = True) -> bool:
dfu_serial = self.get_dfu_serial()
if reset:
self.reset(enter_bootstub=True)
@@ -561,8 +572,12 @@ class Panda:
"interrupt_load": a[20],
"fan_power": a[21],
"safety_rx_checks_invalid": a[22],
"usb_power_mode": a[23],
"torque_interceptor_detected": a[24],
"spi_checksum_error_count": a[23],
"fan_stall_count": a[24],
"sbu1_voltage_mV": a[25],
"sbu2_voltage_mV": a[26],
"usb_power_mode": a[27],
"torque_interceptor_detected": a[28],
}
@ensure_can_health_packet_version
@@ -617,9 +632,9 @@ class Panda:
@staticmethod
def get_signature_from_firmware(fn) -> bytes:
f = open(fn, 'rb')
f.seek(-128, 2) # Seek from end of file
return f.read(128)
with open(fn, 'rb') as f:
f.seek(-128, 2) # Seek from end of file
return f.read(128)
def get_signature(self) -> bytes:
part_1 = self._handle.controlRead(Panda.REQUEST_IN, 0xd3, 0, 0, 0x40)
@@ -681,6 +696,9 @@ class Panda:
"""
return self._serial
def get_dfu_serial(self):
return PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type)
def get_uid(self):
"""
Returns the UID from the MCU
-19
View File
@@ -1,19 +0,0 @@
import os
BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
BOOTSTUB_ADDRESS = 0x8000000
BLOCK_SIZE_FX = 0x800
APP_ADDRESS_FX = 0x8004000
SECTOR_SIZES_FX = [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)]
DEVICE_SERIAL_NUMBER_ADDR_FX = 0x1FFF79C0
DEFAULT_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda.bin.signed")
DEFAULT_BOOTSTUB_FN = os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin")
BLOCK_SIZE_H7 = 0x400
APP_ADDRESS_H7 = 0x8020000
SECTOR_SIZES_H7 = [0x20000 for _ in range(7)] # there is an 8th sector, but we use that for the provisioning chunk, so don't program over that!
DEVICE_SERIAL_NUMBER_ADDR_H7 = 0x080FFFC0
DEFAULT_H7_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed")
DEFAULT_H7_BOOTSTUB_FN = os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin")
+8 -7
View File
@@ -27,6 +27,7 @@ class PandaDFU:
def usb_connect(dfu_serial: Optional[str]) -> Optional[STBootloaderUSBHandle]:
handle = None
context = usb1.USBContext()
context.open()
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11:
try:
@@ -64,15 +65,15 @@ class PandaDFU:
@staticmethod
def usb_list() -> List[str]:
context = usb1.USBContext()
dfu_serials = []
try:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11:
try:
dfu_serials.append(device.open().getASCIIStringDescriptor(3))
except Exception:
pass
with usb1.USBContext() as context:
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11:
try:
dfu_serials.append(device.open().getASCIIStringDescriptor(3))
except Exception:
pass
except Exception:
pass
return dfu_serials
+28 -16
View File
@@ -28,7 +28,7 @@ CHECKSUM_START = 0xAB
MIN_ACK_TIMEOUT_MS = 100
MAX_XFER_RETRY_COUNT = 5
USB_MAX_SIZE = 0x40
XFER_SIZE = 1000
DEV_PATH = "/dev/spidev0.0"
@@ -58,7 +58,7 @@ class SpiDevice:
"""
Provides locked, thread-safe access to a panda's SPI interface.
"""
def __init__(self, speed=30000000):
def __init__(self, speed):
if not os.path.exists(DEV_PATH):
raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}")
if spidev is None:
@@ -87,7 +87,9 @@ class PandaSpiHandle(BaseHandle):
A class that mimics a libusb1 handle for panda SPI communications.
"""
def __init__(self):
self.dev = SpiDevice()
# 50MHz is the max of the 845. older rev comma three
# may not support the full 50MHz
self.dev = SpiDevice(50000000)
# helpers
def _calc_checksum(self, data: List[int]) -> int:
@@ -96,12 +98,12 @@ class PandaSpiHandle(BaseHandle):
cksum ^= b
return cksum
def _wait_for_ack(self, spi, ack_val: int, timeout: int) -> None:
def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int) -> None:
timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3
start = time.monotonic()
while (timeout == 0) or ((time.monotonic() - start) < timeout_s):
dat = spi.xfer2(b"\x12")[0]
dat = spi.xfer2([tx, ])[0]
if dat == NACK:
raise PandaSpiNackResponse
elif dat == ack_val:
@@ -123,7 +125,7 @@ class PandaSpiHandle(BaseHandle):
spi.xfer2(packet)
logging.debug("- waiting for header ACK")
self._wait_for_ack(spi, HACK, timeout)
self._wait_for_ack(spi, HACK, timeout, 0x11)
# send data
logging.debug("- sending data")
@@ -131,7 +133,7 @@ class PandaSpiHandle(BaseHandle):
spi.xfer2(packet)
logging.debug("- waiting for data ACK")
self._wait_for_ack(spi, DACK, timeout)
self._wait_for_ack(spi, DACK, timeout, 0x13)
# get response length, then response
response_len_bytes = bytes(spi.xfer2(b"\x00" * 2))
@@ -147,7 +149,7 @@ class PandaSpiHandle(BaseHandle):
return dat[:-1]
except PandaSpiException as e:
exc = e
logging.debug("SPI transfer failed, %d retries left", n, exc_info=True)
logging.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True)
raise exc
# libusb1 functions
@@ -165,17 +167,17 @@ class PandaSpiHandle(BaseHandle):
# TODO: implement these properly
def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int:
with self.dev.acquire() as spi:
for x in range(math.ceil(len(data) / USB_MAX_SIZE)):
self._transfer(spi, endpoint, data[USB_MAX_SIZE*x:USB_MAX_SIZE*(x+1)], timeout)
for x in range(math.ceil(len(data) / XFER_SIZE)):
self._transfer(spi, endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout)
return len(data)
def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes:
ret: List[int] = []
with self.dev.acquire() as spi:
for _ in range(math.ceil(length / USB_MAX_SIZE)):
d = self._transfer(spi, endpoint, [], timeout, max_rx_len=USB_MAX_SIZE)
for _ in range(math.ceil(length / XFER_SIZE)):
d = self._transfer(spi, endpoint, [], timeout, max_rx_len=XFER_SIZE)
ret += d
if len(d) < USB_MAX_SIZE:
if len(d) < XFER_SIZE:
break
return bytes(ret)
@@ -199,7 +201,7 @@ class STBootloaderSPIHandle(BaseSTBootloaderHandle):
spi.xfer([self.SYNC, ])
try:
self._get_ack(spi)
except PandaSpiNackResponse:
except (PandaSpiNackResponse, PandaSpiMissingAck):
# NACK ok here, will only ACK the first time
pass
@@ -220,13 +222,13 @@ class STBootloaderSPIHandle(BaseSTBootloaderHandle):
elif data != self.ACK:
raise PandaSpiMissingAck
def _cmd(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes: int = 0, predata=None) -> bytes:
def _cmd_no_retry(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes: int = 0, predata=None) -> bytes:
ret = b""
with self.dev.acquire() as spi:
# sync + command
spi.xfer([self.SYNC, ])
spi.xfer([cmd, cmd ^ 0xFF])
self._get_ack(spi)
self._get_ack(spi, timeout=0.1)
# "predata" - for commands that send the first data without a checksum
if predata is not None:
@@ -250,6 +252,16 @@ class STBootloaderSPIHandle(BaseSTBootloaderHandle):
return bytes(ret)
def _cmd(self, cmd: int, data: Optional[List[bytes]] = None, read_bytes: int = 0, predata=None) -> bytes:
exc = PandaSpiException()
for n in range(MAX_XFER_RETRY_COUNT):
try:
return self._cmd_no_retry(cmd, data, read_bytes, predata)
except PandaSpiException as e:
exc = e
logging.debug("SPI transfer failed, %d retries left", MAX_XFER_RETRY_COUNT - n - 1, exc_info=True)
raise exc
def _checksum(self, data: bytes) -> bytes:
if len(data) == 1:
ret = data[0] ^ 0xFF
+38 -22
View File
@@ -141,6 +141,12 @@ class DYNAMIC_DEFINITION_TYPE(IntEnum):
DEFINE_BY_MEMORY_ADDRESS = 2
CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER = 3
class ISOTP_FRAME_TYPE(IntEnum):
SINGLE = 0
FIRST = 1
CONSECUTIVE = 2
FLOW = 3
class DynamicSourceDefinition(NamedTuple):
data_identifier: int
position: int
@@ -438,38 +444,41 @@ class IsoTpMessage():
timeout = self.timeout
start_time = time.monotonic()
updated = False
rx_in_progress = False
try:
while True:
for msg in self._can_client.recv():
self._isotp_rx_next(msg)
frame_type = self._isotp_rx_next(msg)
start_time = time.monotonic()
updated = True
rx_in_progress = frame_type == ISOTP_FRAME_TYPE.CONSECUTIVE
if self.tx_done and self.rx_done:
return self.rx_dat, updated
return self.rx_dat, False
# no timeout indicates non-blocking
if timeout == 0:
return None, updated
return None, rx_in_progress
if time.monotonic() - start_time > timeout:
raise MessageTimeoutError("timeout waiting for response")
finally:
if self.debug and self.rx_dat:
print(f"ISO-TP: RESPONSE - {hex(self._can_client.rx_addr)} 0x{bytes.hex(self.rx_dat)}")
def _isotp_rx_next(self, rx_data: bytes) -> None:
# single rx_frame
if rx_data[0] >> 4 == 0x0:
def _isotp_rx_next(self, rx_data: bytes) -> ISOTP_FRAME_TYPE:
# ISO 15765-2 specifies an eight byte CAN frame for ISO-TP communication
assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}"
if rx_data[0] >> 4 == ISOTP_FRAME_TYPE.SINGLE:
self.rx_len = rx_data[0] & 0xFF
assert self.rx_len < self.max_len, f"isotp - rx: invalid single frame length: {self.rx_len}"
self.rx_dat = rx_data[1:1 + self.rx_len]
self.rx_idx = 0
self.rx_done = True
if self.debug:
print(f"ISO-TP: RX - single frame - {hex(self._can_client.rx_addr)} idx={self.rx_idx} done={self.rx_done}")
return
return ISOTP_FRAME_TYPE.SINGLE
# first rx_frame
if rx_data[0] >> 4 == 0x1:
elif rx_data[0] >> 4 == ISOTP_FRAME_TYPE.FIRST:
self.rx_len = ((rx_data[0] & 0x0F) << 8) + rx_data[1]
assert self.max_len <= self.rx_len, f"isotp - rx: invalid first frame length: {self.rx_len}"
self.rx_dat = rx_data[2:]
self.rx_idx = 0
self.rx_done = False
@@ -479,10 +488,9 @@ class IsoTpMessage():
print(f"ISO-TP: TX - flow control continue - {hex(self._can_client.tx_addr)}")
# send flow control message
self._can_client.send([self.flow_control_msg])
return
return ISOTP_FRAME_TYPE.FIRST
# consecutive rx frame
if rx_data[0] >> 4 == 0x2:
elif rx_data[0] >> 4 == ISOTP_FRAME_TYPE.CONSECUTIVE:
assert not self.rx_done, "isotp - rx: consecutive frame with no active frame"
self.rx_idx += 1
assert self.rx_idx & 0xF == rx_data[0] & 0xF, "isotp - rx: invalid consecutive frame index"
@@ -495,10 +503,9 @@ class IsoTpMessage():
self._can_client.send([self.flow_control_msg])
if self.debug:
print(f"ISO-TP: RX - consecutive frame - {hex(self._can_client.rx_addr)} idx={self.rx_idx} done={self.rx_done}")
return
return ISOTP_FRAME_TYPE.CONSECUTIVE
# flow control
if rx_data[0] >> 4 == 0x3:
elif rx_data[0] >> 4 == ISOTP_FRAME_TYPE.FLOW:
assert not self.tx_done, "isotp - rx: flow control with no active frame"
assert rx_data[0] != 0x32, "isotp - rx: flow-control overflow/abort"
assert rx_data[0] == 0x30 or rx_data[0] == 0x31, "isotp - rx: flow-control transfer state indicator invalid"
@@ -512,7 +519,7 @@ class IsoTpMessage():
# first frame = 6 bytes, each consecutive frame = 7 bytes
num_bytes = self.max_len - 1
start = 6 + self.tx_idx * num_bytes
start = self.max_len - 2 + self.tx_idx * num_bytes
count = rx_data[1]
end = start + count * num_bytes if count > 0 else self.tx_len
tx_msgs = []
@@ -531,9 +538,16 @@ class IsoTpMessage():
# wait (do nothing until next flow control message)
if self.debug:
print(f"ISO-TP: TX - flow control wait - {hex(self._can_client.tx_addr)}")
return ISOTP_FRAME_TYPE.FLOW
# 4-15 - reserved
else:
raise Exception(f"isotp - rx: invalid frame type: {rx_data[0] >> 4}")
FUNCTIONAL_ADDRS = [0x7DF, 0x18DB33F1]
def get_rx_addr_for_tx_addr(tx_addr, rx_offset=0x8):
if tx_addr in FUNCTIONAL_ADDRS:
return None
@@ -551,15 +565,16 @@ def get_rx_addr_for_tx_addr(tx_addr, rx_offset=0x8):
class UdsClient():
def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, timeout: float = 1, debug: bool = False,
tx_timeout: float = 1, response_pending_timeout: float = 10):
def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, sub_addr: int = None, timeout: float = 1,
debug: bool = False, tx_timeout: float = 1, response_pending_timeout: float = 10):
self.bus = bus
self.tx_addr = tx_addr
self.rx_addr = rx_addr if rx_addr is not None else get_rx_addr_for_tx_addr(tx_addr)
self.sub_addr = sub_addr
self.timeout = timeout
self.debug = debug
can_send_with_timeout = partial(panda.can_send, timeout=int(tx_timeout*1000))
self._can_client = CanClient(can_send_with_timeout, panda.can_recv, self.tx_addr, self.rx_addr, self.bus, debug=self.debug)
self._can_client = CanClient(can_send_with_timeout, panda.can_recv, self.tx_addr, self.rx_addr, self.bus, self.sub_addr, debug=self.debug)
self.response_pending_timeout = response_pending_timeout
# generic uds request
@@ -571,7 +586,8 @@ class UdsClient():
req += data
# send request, wait for response
isotp_msg = IsoTpMessage(self._can_client, timeout=self.timeout, debug=self.debug)
max_len = 8 if self.sub_addr is None else 7
isotp_msg = IsoTpMessage(self._can_client, timeout=self.timeout, debug=self.debug, max_len=max_len)
isotp_msg.send(req)
response_pending = False
while True: