import os
import usb1
import struct
import binascii
from . base import BaseSTBootloaderHandle
from . spi import STBootloaderSPIHandle , PandaSpiException
from . usb import STBootloaderUSBHandle
from . constants import FW_PATH , McuType
class PandaDFU :
def __init__ ( self , dfu_serial : str | None ) :
# try USB, then SPI
handle : BaseSTBootloaderHandle | None
self . _context , handle = PandaDFU . usb_connect ( dfu_serial )
if handle is None :
self . _context , handle = PandaDFU . spi_connect ( dfu_serial )
if handle is None :
raise Exception ( f " failed to open DFU device { dfu_serial } " )
self . _handle : BaseSTBootloaderHandle = handle
self . _mcu_type : McuType = self . _handle . get_mcu_type ( )
def __enter__ ( self ) :
return self
def __exit__ ( self , * args ) :
self . close ( )
def close ( self ) :
if self . _handle is not None :
self . _handle . close ( )
self . _handle = None
if self . _context is not None :
self . _context . close ( )
@staticmethod
def usb_connect ( dfu_serial : str | None ) :
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 :
this_dfu_serial = device . open ( ) . getASCIIStringDescriptor ( 3 )
except Exception :
continue
if this_dfu_serial == dfu_serial or dfu_serial is None :
handle = STBootloaderUSBHandle ( device , device . open ( ) )
break
return context , handle
@staticmethod
def spi_connect ( dfu_serial : str | None ) :
handle = None
this_dfu_serial = None
try :
handle = STBootloaderSPIHandle ( )
this_dfu_serial = PandaDFU . st_serial_to_dfu_serial ( handle . get_uid ( ) , handle . get_mcu_type ( ) )
except PandaSpiException :
handle = None
if dfu_serial is not None and dfu_serial != this_dfu_serial :
handle = None
return None , handle
@staticmethod
def usb_list ( ) - > list [ str ] :
dfu_serials = [ ]
try :
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
@staticmethod
def spi_list ( ) - > list [ str ] :
try :
_ , h = PandaDFU . spi_connect ( None )
if h is not None :
dfu_serial = PandaDFU . st_serial_to_dfu_serial ( h . get_uid ( ) , h . get_mcu_type ( ) )
return [ dfu_serial , ]
except PandaSpiException :
pass
return [ ]
@staticmethod
def st_serial_to_dfu_serial ( st : str , mcu_type : McuType = McuType . F4 ) :
if st is None or st == " none " :
return None
uid_base = struct . unpack ( " H " * 6 , bytes . fromhex ( st ) )
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 " )
def get_mcu_type ( self ) - > McuType :
return self . _mcu_type
def reset ( self ) :
self . _handle . jump ( self . _mcu_type . config . bootstub_address )
def program_bootstub ( self , code_bootstub ) :
self . _handle . clear_status ( )
# erase all sectors
for i in range ( len ( self . _mcu_type . config . sector_sizes ) ) :
self . _handle . erase_sector ( i )
self . _handle . program ( self . _mcu_type . config . bootstub_address , code_bootstub )
def recover ( self ) :
fn = os . path . join ( FW_PATH , self . _mcu_type . config . bootstub_fn )
with open ( fn , " rb " ) as f :
code = f . read ( )
self . program_bootstub ( code )
self . reset ( )
@staticmethod
def list ( ) - > list [ str ] :
ret = PandaDFU . usb_list ( )
ret + = PandaDFU . spi_list ( )
return list ( set ( ret ) )