openpilot v0.9.1 release

date: 2023-02-17T21:02:50
master commit: 89f68bf0cbf53a81b0553d3816fdbe522f941fa1
This commit is contained in:
Vehicle Researcher
2023-02-17 21:02:51 +00:00
committed by Adeeb Shihadeh
parent 75c97aaf3f
commit bcd4bb4821
509 changed files with 11784 additions and 4912 deletions
+237 -173
View File
@@ -5,20 +5,19 @@ import time
import usb1
import struct
import hashlib
import binascii
import datetime
import traceback
import warnings
import logging
from functools import wraps
from typing import Optional
from itertools import accumulate
from .dfu import PandaDFU, MCU_TYPE_F2, MCU_TYPE_F4, MCU_TYPE_H7 # pylint: disable=import-error
from .flash_release import flash_release # noqa pylint: disable=import-error
from .update import ensure_st_up_to_date # noqa pylint: disable=import-error
from .serial import PandaSerial # noqa pylint: disable=import-error
from .isotp import isotp_send, isotp_recv # pylint: disable=import-error
from .config import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, SECTOR_SIZES_FX, SECTOR_SIZES_H7 # noqa pylint: disable=import-error
from .constants import McuType
from .dfu import PandaDFU
from .isotp import isotp_send, isotp_recv
from .spi import PandaSpiHandle, PandaSpiException
from .usb import PandaUsbHandle
__version__ = '0.0.10'
@@ -27,82 +26,70 @@ LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper()
logging.basicConfig(level=LOGLEVEL, format='%(message)s')
BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
DEBUG = os.getenv("PANDADEBUG") is not None
CANPACKET_HEAD_SIZE = 0x5
USBPACKET_MAX_SIZE = 0x40
CANPACKET_HEAD_SIZE = 0x6
DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64]
LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)}
def calculate_checksum(data):
res = 0
for b in data:
res ^= b
return res
def pack_can_buffer(arr):
snds = [b'']
idx = 0
for address, _, dat, bus in arr:
assert len(dat) in LEN_TO_DLC
if DEBUG:
print(f" W 0x{address:x}: 0x{dat.hex()}")
#logging.debug(" W 0x%x: 0x%s", address, dat.hex())
extended = 1 if address >= 0x800 else 0
data_len_code = LEN_TO_DLC[len(dat)]
header = bytearray(5)
header = bytearray(CANPACKET_HEAD_SIZE)
word_4b = address << 3 | extended << 2
header[0] = (data_len_code << 4) | (bus << 1)
header[1] = word_4b & 0xFF
header[2] = (word_4b >> 8) & 0xFF
header[3] = (word_4b >> 16) & 0xFF
header[4] = (word_4b >> 24) & 0xFF
snds[idx] += header + dat
if len(snds[idx]) > 256: # Limit chunks to 256 bytes
snds.append(b'')
idx += 1
header[5] = calculate_checksum(header[:5] + dat)
snds[-1] += header + dat
if len(snds[-1]) > 256: # Limit chunks to 256 bytes
snds.append(b'')
# Apply counter to each 64 byte packet
for idx in range(len(snds)):
tx = b''
counter = 0
for i in range (0, len(snds[idx]), 63):
tx += bytes([counter]) + snds[idx][i:i+63]
counter += 1
snds[idx] = tx
return snds
def unpack_can_buffer(dat):
ret = []
counter = 0
tail = bytearray()
for i in range(0, len(dat), 64):
if counter != dat[i]:
print("CAN: LOST RECV PACKET COUNTER")
while len(dat) >= CANPACKET_HEAD_SIZE:
data_len = DLC_TO_LEN[(dat[0]>>4)]
header = dat[:CANPACKET_HEAD_SIZE]
bus = (header[0] >> 1) & 0x7
address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3
if (header[1] >> 1) & 0x1:
# returned
bus += 128
if header[1] & 0x1:
# rejected
bus += 192
# we need more from the next transfer
if data_len > len(dat) - CANPACKET_HEAD_SIZE:
break
counter+=1
chunk = tail + dat[i+1:i+64]
tail = bytearray()
pos = 0
while pos<len(chunk):
data_len = DLC_TO_LEN[(chunk[pos]>>4)]
pckt_len = CANPACKET_HEAD_SIZE + data_len
if pckt_len <= len(chunk[pos:]):
header = chunk[pos:pos+CANPACKET_HEAD_SIZE]
if len(header) < 5:
print("CAN: MALFORMED USB RECV PACKET")
break
bus = (header[0] >> 1) & 0x7
address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3
returned = (header[1] >> 1) & 0x1
rejected = header[1] & 0x1
data = chunk[pos + CANPACKET_HEAD_SIZE:pos + CANPACKET_HEAD_SIZE + data_len]
if returned:
bus += 128
if rejected:
bus += 192
if DEBUG:
print(f" R 0x{address:x}: 0x{data.hex()}")
ret.append((address, 0, data, bus))
pos += pckt_len
else:
tail = chunk[pos:]
break
return ret
assert calculate_checksum(dat[:(CANPACKET_HEAD_SIZE+data_len)]) == 0, "CAN packet checksum incorrect"
data = dat[CANPACKET_HEAD_SIZE:(CANPACKET_HEAD_SIZE+data_len)]
dat = dat[(CANPACKET_HEAD_SIZE+data_len):]
ret.append((address, 0, data, bus))
return (ret, dat)
def ensure_health_packet_version(fn):
@wraps(fn)
@@ -174,6 +161,7 @@ class Panda:
SERIAL_ESP = 1
SERIAL_LIN1 = 2
SERIAL_LIN2 = 3
SERIAL_SOM_DEBUG = 4
GMLAN_CAN2 = 1
GMLAN_CAN3 = 2
@@ -192,11 +180,11 @@ class Panda:
HW_TYPE_RED_PANDA_V2 = b'\x08'
HW_TYPE_TRES = b'\x09'
CAN_PACKET_VERSION = 2
CAN_PACKET_VERSION = 4
HEALTH_PACKET_VERSION = 11
CAN_HEALTH_PACKET_VERSION = 3
CAN_HEALTH_PACKET_VERSION = 4
HEALTH_STRUCT = struct.Struct("<IIIIIIIIIBBBBBBHBBBHfBB")
CAN_HEALTH_STRUCT = struct.Struct("<BIBBBBBBBBIIIIIIHHBBB")
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)
@@ -205,9 +193,6 @@ class Panda:
INTERNAL_DEVICES = (HW_TYPE_UNO, HW_TYPE_DOS)
HAS_OBD = (HW_TYPE_BLACK_PANDA, HW_TYPE_UNO, HW_TYPE_DOS, HW_TYPE_RED_PANDA, HW_TYPE_RED_PANDA_V2, HW_TYPE_TRES)
CLOCK_SOURCE_MODE_DISABLED = 0
CLOCK_SOURCE_MODE_FREE_RUNNING = 1
# first byte is for EPS scaling factor
FLAG_TOYOTA_ALT_BRAKE = (1 << 8)
FLAG_TOYOTA_STOCK_LONGITUDINAL = (2 << 8)
@@ -223,6 +208,7 @@ class Panda:
FLAG_HYUNDAI_CAMERA_SCC = 8
FLAG_HYUNDAI_CANFD_HDA2 = 16
FLAG_HYUNDAI_CANFD_ALT_BUTTONS = 32
FLAG_HYUNDAI_ALT_LIMITS = 64
FLAG_TESLA_POWERTRAIN = 1
FLAG_TESLA_LONG_CONTROL = 2
@@ -237,17 +223,25 @@ class Panda:
FLAG_GM_HW_CAM = 1
FLAG_GM_HW_CAM_LONG = 2
def __init__(self, serial: Optional[str] = None, claim: bool = True, spi: bool = False, disable_checks: bool = True):
def __init__(self, serial: Optional[str] = None, claim: bool = True, disable_checks: bool = True):
self._serial = serial
self._disable_checks = disable_checks
self._handle = None
self._bcd_device = None
self.can_rx_overflow_buffer = b''
# connect and set mcu type
self._spi = spi
self.connect(claim)
# reset comms
self.can_reset_communications()
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def close(self):
self._handle.close()
self._handle = None
@@ -257,29 +251,64 @@ class Panda:
self.close()
self._handle = None
if self._spi:
# TODO: move this back. need to wait until next AGNOS build
from .spi import SpiHandle # noqa pylint: disable=import-error
self._handle = SpiHandle()
# try USB first, then SPI
self._handle, serial, self.bootstub, bcd = self.usb_connect(self._serial, claim=claim, wait=wait)
if self._handle is None:
self._handle, serial, self.bootstub, bcd = self.spi_connect(self._serial)
# TODO implement
self._serial = "SPIDEV"
self.bootstub = False
if self._handle is None:
raise Exception("failed to connect to panda")
else:
self.usb_connect(claim=claim, wait=wait)
# Some fallback logic to determine panda and MCU type for old bootstubs,
# since we now support multiple MCUs and need to know which fw to flash.
# Three cases to consider:
# A) oldest bootstubs don't have any way to distinguish
# MCU or panda type
# B) slightly newer (~2 weeks after first C3's built) bootstubs
# have the panda type set in the USB bcdDevice
# C) latest bootstubs also implement the endpoint for panda type
self._bcd_hw_type = None
ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40)
missing_hw_type_endpoint = self.bootstub and ret.startswith(b'\xff\x00\xc1\x3e\xde\xad\xd0\x0d')
if missing_hw_type_endpoint and bcd is not None:
self._bcd_hw_type = bcd
assert self._handle is not None
# For case A, we assume F4 MCU type, since all H7 pandas should be case B at worst
self._assume_f4_mcu = (self._bcd_hw_type is None) and missing_hw_type_endpoint
self._serial = serial
self._mcu_type = self.get_mcu_type()
self.health_version, self.can_version, self.can_health_version = self.get_packets_versions()
print("connected")
logging.debug("connected")
# disable openpilot's heartbeat checks
if self._disable_checks:
self.set_heartbeat_disabled()
self.set_power_save(0)
def usb_connect(self, claim=True, wait=False):
@staticmethod
def spi_connect(serial):
# get UID to confirm slave is present and up
handle = None
spi_serial = None
try:
handle = PandaSpiHandle()
dat = handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12)
spi_serial = binascii.hexlify(dat).decode()
except PandaSpiException:
pass
# no connection or wrong panda
if spi_serial is None or (serial is not None and (spi_serial != serial)):
handle = None
spi_serial = None
# TODO: detect bootstub
return handle, spi_serial, False, None
@staticmethod
def usb_connect(serial, claim=True, wait=False):
handle, usb_serial, bootstub, bcd = None, None, None, None
context = usb1.USBContext()
while 1:
try:
@@ -289,30 +318,69 @@ class Panda:
this_serial = device.getSerialNumber()
except Exception:
continue
if self._serial is None or this_serial == self._serial:
self._serial = this_serial
print("opening device", self._serial, hex(device.getProductID()))
self.bootstub = device.getProductID() == 0xddee
self._handle = device.open()
if serial is None or this_serial == serial:
logging.debug("opening device %s %s", this_serial, hex(device.getProductID()))
usb_serial = this_serial
bootstub = device.getProductID() == 0xddee
handle = device.open()
if sys.platform not in ("win32", "cygwin", "msys", "darwin"):
self._handle.setAutoDetachKernelDriver(True)
handle.setAutoDetachKernelDriver(True)
if claim:
self._handle.claimInterface(0)
# self._handle.setInterfaceAltSetting(0, 0) # Issue in USB stack
handle.claimInterface(0)
# handle.setInterfaceAltSetting(0, 0) # Issue in USB stack
# bcdDevice wasn't always set to the hw type, ignore if it's the old constant
bcd = device.getbcdDevice()
if bcd is not None and bcd != 0x2300:
self._bcd_device = bytearray([bcd >> 8, ])
this_bcd = device.getbcdDevice()
if this_bcd is not None and this_bcd != 0x2300:
bcd = bytearray([this_bcd >> 8, ])
break
except Exception as e:
print("exception", e)
traceback.print_exc()
if not wait or self._handle is not None:
except Exception:
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
usb_handle = None
if handle is not None:
usb_handle = PandaUsbHandle(handle)
return usb_handle, usb_serial, bootstub, bcd
@staticmethod
def list():
ret = Panda.usb_list()
ret += Panda.spi_list()
return list(set(ret))
@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
except Exception:
pass
return ret
@staticmethod
def spi_list():
_, serial, _, _ = Panda.spi_connect(None)
if serial is not None:
return [serial, ]
return []
def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True):
try:
if enter_bootloader:
@@ -340,7 +408,7 @@ class Panda:
success = True
break
except Exception:
print("reconnecting is taking %d seconds..." % (i + 1))
logging.debug("reconnecting is taking %d seconds...", i + 1)
try:
dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type))
dfu.recover()
@@ -350,8 +418,6 @@ class Panda:
if not success:
raise Exception("reconnect failed")
@staticmethod
def flash_static(handle, code, mcu_type):
assert mcu_type is not None, "must set valid mcu_type to flash"
@@ -361,28 +427,28 @@ class Panda:
assert fr[4:8] == b"\xde\xad\xd0\x0d"
# determine sectors to erase
apps_sectors_cumsum = accumulate(SECTOR_SIZES_H7[1:] if mcu_type == MCU_TYPE_H7 else SECTOR_SIZES_FX[1:])
apps_sectors_cumsum = accumulate(mcu_type.config.sector_sizes[1:])
last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1)
assert last_sector >= 1, "Binary too small? No sector to erase."
assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk."
# unlock flash
print("flash: unlocking")
logging.warning("flash: unlocking")
handle.controlWrite(Panda.REQUEST_IN, 0xb1, 0, 0, b'')
# erase sectors
print(f"flash: erasing sectors 1 - {last_sector}")
logging.warning(f"flash: erasing sectors 1 - {last_sector}")
for i in range(1, last_sector + 1):
handle.controlWrite(Panda.REQUEST_IN, 0xb2, i, 0, b'')
# flash over EP2
STEP = 0x10
print("flash: flashing")
logging.warning("flash: flashing")
for i in range(0, len(code), STEP):
handle.bulkWrite(2, code[i:i + STEP])
# reset
print("flash: resetting")
logging.warning("flash: resetting")
try:
handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'')
except Exception:
@@ -390,9 +456,9 @@ class Panda:
def flash(self, fn=None, code=None, reconnect=True):
if not fn:
fn = DEFAULT_H7_FW_FN if self._mcu_type == MCU_TYPE_H7 else DEFAULT_FW_FN
fn = self._mcu_type.config.app_path
assert os.path.isfile(fn)
print("flash: main version is " + self.get_version())
logging.debug("flash: main version is %s", self.get_version())
if not self.bootstub:
self.reset(enter_bootstub=True)
assert(self.bootstub)
@@ -402,7 +468,7 @@ class Panda:
code = f.read()
# get version
print("flash: bootstub version is " + self.get_version())
logging.debug("flash: bootstub version is %s", self.get_version())
# do flash
Panda.flash_static(self._handle, code, mcu_type=self._mcu_type)
@@ -433,31 +499,12 @@ class Panda:
def wait_for_dfu(dfu_serial: str, timeout: Optional[int] = None) -> bool:
t_start = time.monotonic()
while dfu_serial not in PandaDFU.list():
print("waiting for DFU...")
logging.debug("waiting for DFU...")
time.sleep(0.1)
if timeout is not None and (time.monotonic() - t_start) > timeout:
return False
return True
@staticmethod
def 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
except Exception:
pass
return ret
def call_control_api(self, msg):
self._handle.controlWrite(Panda.REQUEST_OUT, msg, 0, 0, b'')
@@ -524,11 +571,12 @@ class Panda:
"total_tx_cnt": a[13],
"total_rx_cnt": a[14],
"total_fwd_cnt": a[15],
"can_speed": a[16],
"can_data_speed": a[17],
"canfd_enabled": a[18],
"brs_enabled": a[19],
"canfd_non_iso": a[20],
"total_tx_checksum_error_cnt": a[16],
"can_speed": a[17],
"can_data_speed": a[18],
"canfd_enabled": a[19],
"brs_enabled": a[20],
"canfd_non_iso": a[21],
}
# ******************* control *******************
@@ -536,8 +584,8 @@ class Panda:
def enter_bootloader(self):
try:
self._handle.controlWrite(Panda.REQUEST_OUT, 0xd1, 0, 0, b'')
except Exception as e:
print(e)
except Exception:
logging.exception("exception while entering bootloader")
def get_version(self):
return self._handle.controlRead(Panda.REQUEST_IN, 0xd6, 0, 0, 0x40).decode('utf8')
@@ -556,10 +604,9 @@ class Panda:
def get_type(self):
ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40)
# bootstub doesn't implement this call, so fallback to bcdDevice
invalid_type = self.bootstub and (ret is None or len(ret) != 1)
if invalid_type and self._bcd_device is not None:
ret = self._bcd_device
# old bootstubs don't implement this endpoint, see comment in Panda.device
if self._bcd_hw_type is not None and (ret is None or len(ret) != 1):
ret = self._bcd_hw_type
return ret
@@ -572,15 +619,20 @@ class Panda:
else:
return (0, 0, 0)
def get_mcu_type(self):
def get_mcu_type(self) -> McuType:
hw_type = self.get_type()
if hw_type in Panda.F2_DEVICES:
return MCU_TYPE_F2
return McuType.F2
elif hw_type in Panda.F4_DEVICES:
return MCU_TYPE_F4
return McuType.F4
elif hw_type in Panda.H7_DEVICES:
return MCU_TYPE_H7
return None
return McuType.H7
else:
# have to assume F4, see comment in Panda.connect
if self._assume_f4_mcu:
return McuType.F4
raise ValueError(f"unknown HW type: {hw_type}")
def has_obd(self):
return self.get_type() in Panda.HAS_OBD
@@ -589,22 +641,33 @@ class Panda:
return self.get_type() in Panda.INTERNAL_DEVICES
def get_serial(self):
"""
Returns the comma-issued dongle ID from our provisioning
"""
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 0, 0, 0x20)
hashsig, calc_hash = dat[0x1c:], hashlib.sha1(dat[0:0x1c]).digest()[0:4]
assert(hashsig == calc_hash)
return [dat[0:0x10].decode("utf8"), dat[0x10:0x10 + 10].decode("utf8")]
def get_usb_serial(self):
"""
Returns the serial number reported from the USB descriptor;
matches the MCU UID
"""
return self._serial
def get_uid(self):
"""
Returns the UID from the MCU
"""
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12)
return binascii.hexlify(dat).decode()
def get_secret(self):
return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10)
# ******************* configuration *******************
def set_usb_power(self, on):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, int(on), 0, b'')
def set_power_save(self, power_save_enabled=0):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'')
@@ -666,6 +729,9 @@ class Panda:
# Timeout is in ms. If set to 0, the timeout is infinite.
CAN_SEND_TIMEOUT_MS = 10
def can_reset_communications(self):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xc0, 0, 0, b'')
@ensure_can_packet_version
def can_send_many(self, arr, timeout=CAN_SEND_TIMEOUT_MS):
snds = pack_can_buffer(arr)
@@ -677,10 +743,10 @@ class Panda:
tx = tx[bs:]
if len(tx) == 0:
break
print("CAN: PARTIAL SEND MANY, RETRYING")
logging.error("CAN: PARTIAL SEND MANY, RETRYING")
break
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
print("CAN: BAD SEND MANY, RETRYING")
logging.error("CAN: BAD SEND MANY, RETRYING")
def can_send(self, addr, dat, bus, timeout=CAN_SEND_TIMEOUT_MS):
self.can_send_many([[addr, None, dat, bus]], timeout=timeout)
@@ -693,9 +759,10 @@ class Panda:
dat = self._handle.bulkRead(1, 16384) # Max receive batch size + 2 extra reserve frames
break
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
print("CAN: BAD RECV, RETRYING")
logging.error("CAN: BAD RECV, RETRYING")
time.sleep(0.1)
return unpack_can_buffer(dat)
msgs, self.can_rx_overflow_buffer = unpack_can_buffer(self.can_rx_overflow_buffer + dat)
return msgs
def can_clear(self, bus):
"""Clears all messages from the specified internal CAN ringbuffer as
@@ -729,6 +796,8 @@ class Panda:
def serial_write(self, port_number, ln):
ret = 0
if type(ln) == str:
ln = bytes(ln, 'utf-8')
for i in range(0, len(ln), 0x20):
ret += self._handle.bulkWrite(2, struct.pack("B", port_number) + ln[i:i + 0x20])
return ret
@@ -748,19 +817,15 @@ class Panda:
# pulse low for wakeup
def kline_wakeup(self, k=True, l=True):
assert k or l, "must specify k-line, l-line, or both"
if DEBUG:
print("kline wakeup...")
logging.debug("kline wakeup...")
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf0, 2 if k and l else int(l), 0, b'')
if DEBUG:
print("kline wakeup done")
logging.debug("kline wakeup done")
def kline_5baud(self, addr, k=True, l=True):
assert k or l, "must specify k-line, l-line, or both"
if DEBUG:
print("kline 5 baud...")
logging.debug("kline 5 baud...")
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf4, 2 if k and l else int(l), addr, b'')
if DEBUG:
print("kline 5 baud done")
logging.debug("kline 5 baud done")
def kline_drain(self, bus=2):
# drain buffer
@@ -769,8 +834,7 @@ class Panda:
ret = self._handle.controlRead(Panda.REQUEST_IN, 0xe0, bus, 0, 0x40)
if len(ret) == 0:
break
elif DEBUG:
print(f"kline drain: 0x{ret.hex()}")
logging.debug(f"kline drain: 0x{ret.hex()}")
bret += ret
return bytes(bret)
@@ -778,8 +842,8 @@ class Panda:
echo = bytearray()
while len(echo) != cnt:
ret = self._handle.controlRead(Panda.REQUEST_OUT, 0xe0, bus, 0, cnt - len(echo))
if DEBUG and len(ret) > 0:
print(f"kline recv: 0x{ret.hex()}")
if len(ret) > 0:
logging.debug(f"kline recv: 0x{ret.hex()}")
echo += ret
return bytes(echo)
@@ -789,14 +853,13 @@ class Panda:
x += bytes([sum(x) % 0x100])
for i in range(0, len(x), 0xf):
ts = x[i:i + 0xf]
if DEBUG:
print(f"kline send: 0x{ts.hex()}")
logging.debug(f"kline send: 0x{ts.hex()}")
self._handle.bulkWrite(2, bytes([bus]) + ts)
echo = self.kline_ll_recv(len(ts), bus=bus)
if echo != ts:
print(f"**** ECHO ERROR {i} ****")
print(f"0x{echo.hex()}")
print(f"0x{ts.hex()}")
logging.error(f"**** ECHO ERROR {i} ****")
logging.error(f"0x{echo.hex()}")
logging.error(f"0x{ts.hex()}")
assert echo == ts
def kline_recv(self, bus=2, header_len=4):
@@ -829,6 +892,11 @@ class Panda:
a = struct.unpack("HBBBBBB", dat)
return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6])
# ****************** Timer *****************
def get_microsecond_timer(self):
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa8, 0, 0, 4)
return struct.unpack("I", dat)[0]
# ******************* IR *******************
def set_ir_power(self, percentage):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb0, int(percentage), 0, b'')
@@ -846,10 +914,6 @@ class Panda:
def set_phone_power(self, enabled):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, int(enabled), 0, b'')
# ************** Clock Source **************
def set_clock_source_mode(self, mode):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf5, int(mode), 0, b'')
# ****************** Siren *****************
def set_siren(self, enabled):
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf6, int(enabled), 0, b'')
+25
View File
@@ -0,0 +1,25 @@
from abc import ABC, abstractmethod
from typing import List
# This mimics the handle given by libusb1 for easy interoperability
class BaseHandle(ABC):
@abstractmethod
def close(self) -> None:
...
@abstractmethod
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = 0) -> int:
...
@abstractmethod
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = 0) -> bytes:
...
@abstractmethod
def bulkWrite(self, endpoint: int, data: List[int], timeout: int = 0) -> int:
...
@abstractmethod
def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes:
...
+6 -6
View File
@@ -100,9 +100,9 @@ class CcpClient():
msgs = self._panda.can_recv() or []
if len(msgs) >= 256:
print("CAN RX buffer overflow!!!", file=sys.stderr)
for rx_addr, _, rx_data, rx_bus in msgs:
for rx_addr, _, rx_data_bytearray, rx_bus in msgs:
if rx_bus == self.can_bus and rx_addr == self.rx_addr:
rx_data = bytes(rx_data) # convert bytearray to bytes
rx_data = bytes(rx_data_bytearray)
if self.debug:
print(f"CAN-RX: {hex(rx_addr)} - 0x{bytes.hex(rx_data)}")
assert len(rx_data) == 8, f"message length not 8: {len(rx_data)}"
@@ -183,7 +183,7 @@ class CcpClient():
resp = self._recv_dto(0.025)
# mta_addr_ext = resp[0]
mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0]
return mta_addr
return mta_addr # type: ignore
def download_6_bytes(self, data: bytes) -> int:
if len(data) != 6:
@@ -192,7 +192,7 @@ class CcpClient():
resp = self._recv_dto(0.025)
# mta_addr_ext = resp[0]
mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0]
return mta_addr
return mta_addr # type: ignore
def upload(self, size: int) -> bytes:
if size > 5:
@@ -296,7 +296,7 @@ class CcpClient():
resp = self._recv_dto(0.1)
# mta_addr_ext = resp[0]
mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0]
return mta_addr
return mta_addr # type: ignore
def program_6_bytes(self, data: bytes) -> int:
if len(data) != 6:
@@ -305,7 +305,7 @@ class CcpClient():
resp = self._recv_dto(0.1)
# mta_addr_ext = resp[0]
mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0]
return mta_addr
return mta_addr # type: ignore
def move_memory_block(self, size: int) -> None:
self._send_cro(COMMAND_CODE.MOVE, struct.pack(f"{self.byte_order.value}I", size))
-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")
+52
View File
@@ -0,0 +1,52 @@
import os
import enum
from typing import List, NamedTuple
BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
class McuConfig(NamedTuple):
mcu: str
mcu_idcode: int
block_size: int
sector_sizes: List[int]
serial_number_address: int
app_address: int
app_path: str
bootstub_address: int
bootstub_path: str
Fx = (
0x800,
[0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)],
0x1FFF79C0,
0x8004000,
os.path.join(BASEDIR, "board", "obj", "panda.bin.signed"),
0x8000000,
os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin"),
)
F2Config = McuConfig("STM32F2", 0x411, *Fx)
F4Config = McuConfig("STM32F4", 0x463, *Fx)
H7Config = McuConfig(
"STM32H7",
0x483,
0x400,
# there is an 8th sector, but we use that for the provisioning chunk, so don't program over that!
[0x20000 for _ in range(7)],
0x080FFFC0,
0x8020000,
os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed"),
0x8000000,
os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin"),
)
@enum.unique
class McuType(enum.Enum):
F2 = F2Config
F4 = F4Config
H7 = H7Config
@property
def config(self):
return self.value
+19 -26
View File
@@ -1,12 +1,9 @@
import usb1
import struct
import binascii
from .config import BOOTSTUB_ADDRESS, APP_ADDRESS_H7, APP_ADDRESS_FX, BLOCK_SIZE_H7, BLOCK_SIZE_FX, DEFAULT_H7_BOOTSTUB_FN, DEFAULT_BOOTSTUB_FN
from .constants import McuType
MCU_TYPE_F2 = 0
MCU_TYPE_F4 = 1
MCU_TYPE_H7 = 2
# *** DFU mode ***
DFU_DNLOAD = 1
@@ -15,8 +12,9 @@ DFU_GETSTATUS = 3
DFU_CLRSTATUS = 4
DFU_ABORT = 6
class PandaDFU(object):
class PandaDFU:
def __init__(self, dfu_serial):
self._handle = None
context = usb1.USBContext()
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11:
@@ -25,10 +23,12 @@ class PandaDFU(object):
except Exception:
continue
if this_dfu_serial == dfu_serial or dfu_serial is None:
self._mcu_type = self.get_mcu_type(device)
self._handle = device.open()
return
raise Exception("failed to open " + dfu_serial if dfu_serial is not None else "DFU device")
self._mcu_type = self.get_mcu_type(device)
break
if self._handle is None:
raise Exception(f"failed to open DFU device {dfu_serial}")
@staticmethod
def list():
@@ -46,18 +46,19 @@ class PandaDFU(object):
return dfu_serials
@staticmethod
def st_serial_to_dfu_serial(st, mcu_type=MCU_TYPE_F4):
def st_serial_to_dfu_serial(st, mcu_type=McuType.F4):
if st is None or st == "none":
return None
uid_base = struct.unpack("H" * 6, bytes.fromhex(st))
if mcu_type == MCU_TYPE_H7:
if mcu_type == McuType.H7:
return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4], uid_base[3])).upper().decode("utf-8")
else:
return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4] + 0xA, uid_base[3])).upper().decode("utf-8")
# TODO: Find a way to detect F4 vs F2
def get_mcu_type(self, dev):
return MCU_TYPE_H7 if dev.getbcdDevice() == 512 else MCU_TYPE_F4
def get_mcu_type(self, dev) -> McuType:
# TODO: Find a way to detect F4 vs F2
# TODO: also check F4 BCD, don't assume in else
return McuType.H7 if dev.getbcdDevice() == 512 else McuType.F4
def status(self):
while 1:
@@ -97,26 +98,18 @@ class PandaDFU(object):
def program_bootstub(self, code_bootstub):
self.clear_status()
self.erase(BOOTSTUB_ADDRESS)
if self._mcu_type == MCU_TYPE_H7:
self.erase(APP_ADDRESS_H7)
self.program(BOOTSTUB_ADDRESS, code_bootstub, BLOCK_SIZE_H7)
else:
self.erase(APP_ADDRESS_FX)
self.program(BOOTSTUB_ADDRESS, code_bootstub, BLOCK_SIZE_FX)
self.erase(self._mcu_type.config.bootstub_address)
self.erase(self._mcu_type.config.app_address)
self.program(self._mcu_type.config.bootstub_address, code_bootstub, self._mcu_type.config.block_size)
self.reset()
def recover(self):
fn = DEFAULT_H7_BOOTSTUB_FN if self._mcu_type == MCU_TYPE_H7 else DEFAULT_BOOTSTUB_FN
with open(fn, "rb") as f:
with open(self._mcu_type.config.bootstub_path, "rb") as f:
code = f.read()
self.program_bootstub(code)
def reset(self):
# **** Reset ****
self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", BOOTSTUB_ADDRESS))
self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", self._mcu_type.config.bootstub_address))
self.status()
try:
self._handle.controlWrite(0x21, DFU_DNLOAD, 2, 0, b"")
-74
View File
@@ -1,74 +0,0 @@
#!/usr/bin/env python3
import sys
import time
import requests
import json
import io
def flash_release(path=None, st_serial=None):
from panda import Panda, PandaDFU
from zipfile import ZipFile
def status(x):
print("\033[1;32;40m" + x + "\033[00m")
if st_serial is not None:
# look for Panda
panda_list = Panda.list()
if len(panda_list) == 0:
raise Exception("panda not found, make sure it's connected and your user can access it")
elif len(panda_list) > 1:
raise Exception("Please only connect one panda")
st_serial = panda_list[0]
print("Using panda with serial %s" % st_serial)
if path is None:
print("Fetching latest firmware from github.com/commaai/panda-artifacts")
r = requests.get("https://raw.githubusercontent.com/commaai/panda-artifacts/master/latest.json")
url = json.loads(r.text)['url']
r = requests.get(url)
print("Fetching firmware from %s" % url)
path = io.BytesIO(r.content)
zf = ZipFile(path)
zf.printdir()
version = zf.read("version").decode().strip()
status("0. Preparing to flash " + str(version))
code_bootstub = zf.read("bootstub.panda.bin")
code_panda = zf.read("panda.bin")
# enter DFU mode
status("1. Entering DFU mode")
panda = Panda(st_serial)
panda.reset(enter_bootstub=True)
panda.reset(enter_bootloader=True)
time.sleep(1)
# program bootstub
status("2. Programming bootstub")
dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(st_serial))
dfu.program_bootstub(code_bootstub)
time.sleep(1)
# flash main code
status("3. Flashing main code")
panda = Panda(st_serial)
panda.flash(code=code_panda)
panda.close()
# check for connection
status("4. Verifying version")
panda = Panda(st_serial)
my_version = panda.get_version()
print("dongle id: %s" % panda.get_serial()[0])
print(my_version, "should be", version)
assert(str(version) == str(my_version))
# done!
status("6. Success!")
if __name__ == "__main__":
flash_release(*sys.argv[1:])
+113 -44
View File
@@ -1,10 +1,21 @@
import os
import fcntl
import math
import time
import struct
import spidev
import logging
import threading
from contextlib import contextmanager
from functools import reduce
from typing import List
from .base import BaseHandle
try:
import spidev
except ImportError:
spidev = None
# Constants
SYNC = 0x5A
HACK = 0x79
@@ -12,17 +23,69 @@ DACK = 0x85
NACK = 0x1F
CHECKSUM_START = 0xAB
MAX_RETRY_COUNT = 5
ACK_TIMEOUT_SECONDS = 0.1
MAX_XFER_RETRY_COUNT = 5
USB_MAX_SIZE = 0x40
# This mimics the handle given by libusb1 for easy interoperability
class SpiHandle:
def __init__(self):
self.spi = spidev.SpiDev() # pylint: disable=c-extension-no-member
self.spi.open(0, 0)
DEV_PATH = "/dev/spidev0.0"
self.spi.max_speed_hz = 30000000
class PandaSpiException(Exception):
pass
class PandaSpiUnavailable(PandaSpiException):
pass
class PandaSpiNackResponse(PandaSpiException):
pass
class PandaSpiMissingAck(PandaSpiException):
pass
class PandaSpiBadChecksum(PandaSpiException):
pass
class PandaSpiTransferFailed(PandaSpiException):
pass
SPI_LOCK = threading.Lock()
class SpiDevice:
"""
Provides locked, thread-safe access to a panda's SPI interface.
"""
def __init__(self, speed=30000000):
if not os.path.exists(DEV_PATH):
raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}")
if spidev is None:
raise PandaSpiUnavailable("spidev is not installed")
self._spidev = spidev.SpiDev() # pylint: disable=c-extension-no-member
self._spidev.open(0, 0)
self._spidev.max_speed_hz = speed
@contextmanager
def acquire(self):
try:
SPI_LOCK.acquire()
fcntl.flock(self._spidev, fcntl.LOCK_EX)
yield self._spidev
finally:
fcntl.flock(self._spidev, fcntl.LOCK_UN)
SPI_LOCK.release()
def close(self):
self._spidev.close()
class PandaSpiHandle(BaseHandle):
"""
A class that mimics a libusb1 handle for panda SPI communications.
"""
def __init__(self):
self.dev = SpiDevice()
# helpers
def _calc_checksum(self, data: List[int]) -> int:
@@ -31,75 +94,81 @@ class SpiHandle:
cksum ^= b
return cksum
def _transfer(self, endpoint: int, data, max_rx_len: int = 1000) -> bytes:
def _wait_for_ack(self, spi, ack_val: int) -> None:
start = time.monotonic()
while (time.monotonic() - start) < ACK_TIMEOUT_SECONDS:
dat = spi.xfer2(b"\x12")[0]
if dat == NACK:
raise PandaSpiNackResponse
elif dat == ack_val:
return
raise PandaSpiMissingAck
def _transfer(self, spi, endpoint: int, data, max_rx_len: int = 1000) -> bytes:
logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len)
logging.debug("==============================================")
for n in range(MAX_RETRY_COUNT):
exc = PandaSpiException()
for n in range(MAX_XFER_RETRY_COUNT):
logging.debug("\ntry #%d", n+1)
try:
logging.debug("- send header")
packet = struct.pack("<BBHH", SYNC, endpoint, len(data), max_rx_len)
packet += bytes([reduce(lambda x, y: x^y, packet) ^ CHECKSUM_START])
self.spi.xfer2(packet)
spi.xfer2(packet)
logging.debug("- waiting for ACK")
# TODO: add timeout?
dat = b"\x00"
while dat[0] not in [HACK, NACK]:
dat = self.spi.xfer2(b"\x12")
if dat[0] == NACK:
raise Exception("Got NACK response for header")
logging.debug("- waiting for header ACK")
self._wait_for_ack(spi, HACK)
# send data
logging.debug("- sending data")
packet = bytes([*data, self._calc_checksum(data)])
self.spi.xfer2(packet)
spi.xfer2(packet)
logging.debug("- waiting for ACK")
dat = b"\x00"
while dat[0] not in [DACK, NACK]:
dat = self.spi.xfer2(b"\xab")
if dat[0] == NACK:
raise Exception("Got NACK response for data")
logging.debug("- waiting for data ACK")
self._wait_for_ack(spi, DACK)
# get response length, then response
response_len_bytes = bytes(self.spi.xfer2(b"\x00" * 2))
response_len_bytes = bytes(spi.xfer2(b"\x00" * 2))
response_len = struct.unpack("<H", response_len_bytes)[0]
logging.debug("- receiving response")
dat = bytes(self.spi.xfer2(b"\x00" * (response_len + 1)))
dat = bytes(spi.xfer2(b"\x00" * (response_len + 1)))
if self._calc_checksum([DACK, *response_len_bytes, *dat]) != 0:
raise Exception("SPI got bad checksum")
raise PandaSpiBadChecksum
return dat[:-1]
except Exception:
logging.exception("SPI transfer failed, %d retries left", n)
raise Exception(f"SPI transaction failed {MAX_RETRY_COUNT} times")
except PandaSpiException as e:
exc = e
logging.debug("SPI transfer failed, %d retries left", n, exc_info=True)
raise exc
# libusb1 functions
def close(self):
self.spi.close()
self.dev.close()
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = 0):
return self._transfer(0, struct.pack("<BHHH", request, value, index, 0))
with self.dev.acquire() as spi:
return self._transfer(spi, 0, struct.pack("<BHHH", request, value, index, 0))
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = 0):
return self._transfer(0, struct.pack("<BHHH", request, value, index, length))
with self.dev.acquire() as spi:
return self._transfer(spi, 0, struct.pack("<BHHH", request, value, index, length))
# TODO: implement these properly
def bulkWrite(self, endpoint: int, data: List[int], timeout: int = 0) -> int:
for x in range(math.ceil(len(data) / USB_MAX_SIZE)):
self._transfer(endpoint, data[USB_MAX_SIZE*x:USB_MAX_SIZE*(x+1)])
return len(data)
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)])
return len(data)
def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes:
ret: List[int] = []
for _ in range(math.ceil(length / USB_MAX_SIZE)):
d = self._transfer(endpoint, [], max_rx_len=USB_MAX_SIZE)
ret += d
if len(d) < USB_MAX_SIZE:
break
with self.dev.acquire() as spi:
for _ in range(math.ceil(length / USB_MAX_SIZE)):
d = self._transfer(spi, endpoint, [], max_rx_len=USB_MAX_SIZE)
ret += d
if len(d) < USB_MAX_SIZE:
break
return bytes(ret)
+118
View File
@@ -0,0 +1,118 @@
import time
import struct
from functools import reduce
from .constants import McuType
from .spi import SpiDevice
SYNC = 0x5A
ACK = 0x79
NACK = 0x1F
# https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf
class PandaSpiDFU:
def __init__(self, dfu_serial):
self.dev = SpiDevice(speed=1000000)
# say hello
with self.dev.acquire() as spi:
try:
spi.xfer([SYNC, ])
self._get_ack(spi)
except Exception:
raise Exception("failed to connect to panda") # pylint: disable=W0707
self._mcu_type = self.get_mcu_type()
def _get_ack(self, spi, timeout=1.0):
data = 0x00
start_time = time.monotonic()
while data not in (ACK, NACK) and (time.monotonic() - start_time < timeout):
data = spi.xfer([0x00, ])[0]
time.sleep(0.001)
spi.xfer([ACK, ])
if data == NACK:
raise Exception("Got NACK response")
elif data != ACK:
raise Exception("Missing ACK")
def _cmd(self, cmd, data=None, read_bytes=0) -> bytes:
ret = b""
with self.dev.acquire() as spi:
# sync
spi.xfer([SYNC, ])
# send command
spi.xfer([cmd, cmd ^ 0xFF])
self._get_ack(spi)
# send data
if data is not None:
for d in data:
spi.xfer(self.add_checksum(d))
self._get_ack(spi, timeout=20)
# receive
if read_bytes > 0:
# send busy byte
ret = spi.xfer([0x00, ]*(read_bytes + 1))[1:]
self._get_ack(spi)
return ret
def add_checksum(self, data):
return data + bytes([reduce(lambda a, b: a ^ b, data)])
# ***** ST Bootloader functions *****
def get_bootloader_version(self) -> int:
return self._cmd(0x01, read_bytes=1)[0]
def get_id(self) -> int:
ret = self._cmd(0x02, read_bytes=3)
assert ret[0] == 1
return ((ret[1] << 8) + ret[2])
def go_cmd(self, address: int) -> None:
self._cmd(0x21, data=[struct.pack('>I', address), ])
def erase(self, address: int) -> None:
d = struct.pack('>H', address)
self._cmd(0x44, data=[d, ])
# ***** panda api *****
def get_mcu_type(self) -> McuType:
mcu_by_id = {mcu.config.mcu_idcode: mcu for mcu in McuType}
return mcu_by_id[self.get_id()]
def global_erase(self):
self.erase(0xFFFF)
def program_file(self, address, fn):
with open(fn, 'rb') as f:
code = f.read()
i = 0
while i < len(code):
#print(i, len(code))
block = code[i:i+256]
if len(block) < 256:
block += b'\xFF' * (256 - len(block))
self._cmd(0x31, data=[
struct.pack('>I', address + i),
bytes([len(block) - 1]) + block,
])
#print(f"Written {len(block)} bytes to {hex(address + i)}")
i += 256
def program_bootstub(self):
self.program_file(self._mcu_type.config.bootstub_address, self._mcu_type.config.bootstub_path)
def program_app(self):
self.program_file(self._mcu_type.config.app_address, self._mcu_type.config.app_path)
def reset(self):
self.go_cmd(self._mcu_type.config.bootstub_address)
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env python3
import os
import time
def ensure_st_up_to_date():
from panda import Panda, PandaDFU, BASEDIR
with open(os.path.join(BASEDIR, "VERSION")) as f:
repo_version = f.read()
repo_version += "-EON" if os.path.isfile('/EON') else "-DEV"
panda = None
panda_dfu = None
while 1:
# break on normal mode Panda
panda_list = Panda.list()
if len(panda_list) > 0:
panda = Panda(panda_list[0])
break
# flash on DFU mode Panda
panda_dfu = PandaDFU.list()
if len(panda_dfu) > 0:
panda_dfu = PandaDFU(panda_dfu[0])
panda_dfu.recover()
print("waiting for board...")
time.sleep(1)
if panda.bootstub or not panda.get_version().startswith(repo_version):
panda.flash()
if panda.bootstub:
panda.recover()
assert(not panda.bootstub)
version = str(panda.get_version())
print("%s should be %s" % (version, repo_version))
assert(version.startswith(repo_version))
if __name__ == "__main__":
ensure_st_up_to_date()
+23
View File
@@ -0,0 +1,23 @@
from typing import List
from .base import BaseHandle
class PandaUsbHandle(BaseHandle):
def __init__(self, libusb_handle):
self._libusb_handle = libusb_handle
def close(self):
self._libusb_handle.close()
def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = 0):
return self._libusb_handle.controlWrite(request_type, request, value, index, data, timeout)
def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = 0):
return self._libusb_handle.controlRead(request_type, request, value, index, length, timeout)
def bulkWrite(self, endpoint: int, data: List[int], timeout: int = 0) -> int:
return self._libusb_handle.bulkWrite(endpoint, data, timeout) # type: ignore
def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes:
return self._libusb_handle.bulkRead(endpoint, length, timeout) # type: ignore