2424 SPIKE_PRIME_DFU_USB_PID ,
2525)
2626
27- FIRMWARE_ADDRESS = 0x08008000
28- FIRMWARE_SIZE = 1 * 1024 * 1024 - 32 * 1024 # 1MiB - 32KiB
27+ BOOTLOADER_SIZE_32K = 32 * 1024
28+ BOOTLOADER_SIZE_64K = 64 * 1024
29+ FLASH_BASE_ADDRESS = 0x08000000
30+ FLASH_SIZE = 1 * 1024 * 1024
2931
3032
3133ALL_PIDS = {
3638ALL_DEVICES = [f"{ LEGO_USB_VID :04x} :{ pid :04x} " for pid in ALL_PIDS .keys ()]
3739
3840
41+ def _get_bootloader_size (pid : int , bcd_device : int | None ) -> int :
42+ """Gets bootloader size for the connected DFU device."""
43+ # New hardware revision of SPIKE Prime released in 2026 has a larger bootloader.
44+ if pid == SPIKE_PRIME_DFU_USB_PID and bcd_device == 0x0300 :
45+ return BOOTLOADER_SIZE_64K
46+
47+ return BOOTLOADER_SIZE_32K
48+
49+
50+ def _get_firmware_region (bootloader_size : int ) -> tuple [int , int ]:
51+ """Gets firmware flash address and size from bootloader size."""
52+ return FLASH_BASE_ADDRESS + bootloader_size , FLASH_SIZE - bootloader_size
53+
54+
3955def _get_dfu_util () -> ContextManager [os .PathLike [str ] | str ]:
4056 """Gets ``dfu-util`` command line tool path.
4157
@@ -62,9 +78,9 @@ def _get_dfu_util() -> ContextManager[os.PathLike[str] | str]:
6278 return nullcontext (dfu_util )
6379
6480
65- def _get_vid_pid (dfu_util : os .PathLike [str ] | str ) -> str :
81+ def _get_dfu_device_info (dfu_util : os .PathLike [str ] | str ) -> tuple [ str , int ] :
6682 """
67- Gets the VID and PID of a connected LEGO DFU device.
83+ Gets the VID:PID and bootloader size of a connected LEGO DFU device.
6884
6985 Returns: The first matching LEGO DFU device from ``dfu-util --list``
7086
@@ -79,7 +95,16 @@ def _get_vid_pid(dfu_util: os.PathLike[str] | str) -> str:
7995 dev_id = line [line .index (b"[" ) + 1 : line .index (b"]" )].decode ()
8096
8197 if dev_id in ALL_DEVICES :
82- return dev_id
98+ pid = int (dev_id .split (":" , maxsplit = 1 )[1 ], 16 )
99+ bcd_device = None
100+
101+ try :
102+ i = line .index (b"ver=" ) + 4
103+ bcd_device = int (line [i : i + 4 ].decode (), 16 )
104+ except (ValueError , IndexError ):
105+ pass
106+
107+ return dev_id , _get_bootloader_size (pid , bcd_device )
83108
84109 raise RuntimeError ("No LEGO DFU USB device found" )
85110
@@ -98,6 +123,8 @@ def backup_dfu(file: BinaryIO) -> None:
98123 # if libusb was not found, try using dfu-util command line tool
99124
100125 with _get_dfu_util () as dfu_util :
126+ dev_id , bootloader_size = _get_dfu_device_info (dfu_util )
127+ firmware_address , firmware_size = _get_firmware_region (bootloader_size )
101128 file .close ()
102129
103130 # dfu-util won't overwrite existing files so we have to do that first
@@ -108,11 +135,11 @@ def backup_dfu(file: BinaryIO) -> None:
108135 [
109136 dfu_util ,
110137 "--device" ,
111- f",{ _get_vid_pid ( dfu_util ) } " ,
138+ f",{ dev_id } " ,
112139 "--alt" ,
113140 "0" ,
114141 "--dfuse-address" ,
115- f"{ FIRMWARE_ADDRESS } :{ FIRMWARE_SIZE } " ,
142+ f"{ firmware_address } :{ firmware_size } " ,
116143 "--upload" ,
117144 file .name ,
118145 ]
@@ -140,18 +167,20 @@ def restore_dfu(file: BinaryIO) -> None:
140167 # if libusb was not found, try using dfu-util command line tool
141168
142169 with _get_dfu_util () as dfu_util :
170+ dev_id , bootloader_size = _get_dfu_device_info (dfu_util )
171+ firmware_address , _ = _get_firmware_region (bootloader_size )
143172 file .close ()
144173
145174 exit (
146175 call (
147176 [
148177 dfu_util ,
149178 "--device" ,
150- f",{ _get_vid_pid ( dfu_util ) } " ,
179+ f",{ dev_id } " ,
151180 "--alt" ,
152181 "0" ,
153182 "--dfuse-address" ,
154- f"{ FIRMWARE_ADDRESS } :leave" ,
183+ f"{ firmware_address } :leave" ,
155184 "--download" ,
156185 file .name ,
157186 ]
@@ -164,7 +193,6 @@ def flash_dfu(firmware_bin: bytes, metadata: AnyFirmwareMetadata) -> None:
164193
165194 with TemporaryDirectory () as out_dir :
166195 outfile = os .path .join (out_dir , "firmware.dfu" )
167- target : dfu_create .Image = {"address" : FIRMWARE_ADDRESS , "data" : firmware_bin }
168196
169197 try :
170198 # Determine correct product ID
@@ -178,7 +206,8 @@ def flash_dfu(firmware_bin: bytes, metadata: AnyFirmwareMetadata) -> None:
178206 )
179207 exit (1 )
180208
181- product_id = devices [0 ].idProduct
209+ product_id = int (devices [0 ].idProduct )
210+ bcd_device = int (devices [0 ].bcdDevice )
182211 if product_id not in ALL_PIDS :
183212 print (f"Unknown USB product ID: { product_id :04X} " , file = sys .stderr )
184213 exit (1 )
@@ -187,6 +216,13 @@ def flash_dfu(firmware_bin: bytes, metadata: AnyFirmwareMetadata) -> None:
187216 print ("Incorrect firmware type for this hub" , file = sys .stderr )
188217 exit (1 )
189218
219+ bootloader_size = _get_bootloader_size (product_id , bcd_device )
220+ firmware_address , _ = _get_firmware_region (bootloader_size )
221+ target : dfu_create .Image = {
222+ "address" : firmware_address ,
223+ "data" : firmware_bin ,
224+ }
225+
190226 # Create dfu file
191227 device = "0x{0:04x}:0x{1:04x}" .format (LEGO_USB_VID , product_id )
192228 dfu_create .build (outfile , [[target ]], device )
@@ -225,6 +261,9 @@ def flash_dfu(firmware_bin: bytes, metadata: AnyFirmwareMetadata) -> None:
225261 # if libusb was not found, try using dfu-util command line tool
226262
227263 with _get_dfu_util () as dfu_util :
264+ dev_id , bootloader_size = _get_dfu_device_info (dfu_util )
265+ firmware_address , _ = _get_firmware_region (bootloader_size )
266+
228267 with open (os .path .join (out_dir , "firmware.bin" ), "wb" ) as bin_file :
229268 bin_file .write (firmware_bin )
230269
@@ -233,14 +272,14 @@ def flash_dfu(firmware_bin: bytes, metadata: AnyFirmwareMetadata) -> None:
233272 [
234273 dfu_util ,
235274 "--device" ,
236- f",{ _get_vid_pid ( dfu_util ) } " ,
275+ f",{ dev_id } " ,
237276 "--alt" ,
238277 "0" ,
239278 # We have to use dfuse option to be able to use
240279 # "leave" to exit DFU mode after flashing. --reset
241280 # doesn't work on Windows, so we can't use a .dfu file
242281 "--dfuse-address" ,
243- f"{ target [ 'address' ] } :leave" ,
282+ f"{ firmware_address } :leave" ,
244283 "--download" ,
245284 bin_file .name ,
246285 ]
0 commit comments