@@ -272,6 +272,26 @@ def is_rp2040_env(pio_env: str) -> bool:
272272 return "_rp2040" in pio_env
273273
274274
275+ def _generate_nrf52_dfu (hex_src : Path , dfu_dst : Path ) -> None :
276+ """Generate a DFU zip from an Intel HEX via adafruit-nrfutil.
277+
278+ Called when PlatformIO did not produce firmware.zip (which only happens
279+ during ``pio run -t upload``, never during a plain ``pio run`` build).
280+ """
281+ subprocess .run (
282+ [
283+ "adafruit-nrfutil" , "dfu" , "genpkg" ,
284+ "--dev-type" , "0x0052" , # nRF52840
285+ "--sd-req" , "0x00B6" , # S140 6.1.1 – FWID shared by all Adafruit nRF52 boards
286+ "--application" , str (hex_src ),
287+ "--application-version" , "0xFFFF" ,
288+ str (dfu_dst ),
289+ ],
290+ check = True ,
291+ )
292+ print (f" generated DFU → { dfu_dst } ({ dfu_dst .stat ().st_size // 1024 } KB)" )
293+
294+
275295def package_nrf52 (build_dir : Path , pkg_dir : Path , variant : str ) -> None :
276296 """Package an nRF52 variant.
277297
@@ -295,11 +315,15 @@ def package_nrf52(build_dir: Path, pkg_dir: Path, variant: str) -> None:
295315 shutil .copy (hex_src , hex_dst )
296316 print (f" copied firmware.hex → { hex_dst } " )
297317
298- # adafruit-nrfutil DFU zip – used for serial / USB bootloader flashing
318+ # adafruit-nrfutil DFU zip – used for serial / USB bootloader flashing.
319+ # pio run (plain build) never invokes adafruit-nrfutil, so firmware.zip is
320+ # absent in CI. Generate it explicitly from the HEX in that case.
321+ dfu_dst = pkg_dir / f"rivr_{ variant } _dfu.zip"
299322 if dfu_src .exists ():
300- dfu_dst = pkg_dir / f"rivr_{ variant } _dfu.zip"
301323 shutil .copy (dfu_src , dfu_dst )
302324 print (f" copied firmware.zip → { dfu_dst } " )
325+ else :
326+ _generate_nrf52_dfu (hex_src , dfu_dst )
303327
304328 # Sidecar JSON (minimal — no ESP flash segments)
305329 manifest = {
0 commit comments