diff --git a/esphome/__main__.py b/esphome/__main__.py index 228b46aaa2..225a18cc1c 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -736,8 +736,21 @@ def upload_using_esptool( def upload_using_platformio(config: ConfigType, port: str) -> int: + import shutil + from esphome import platformio_api + # RP2040 platform-raspberrypi build recipe expects firmware.bin.signed for + # the upload target, but 'nobuild' skips the build phase that creates it. + # Create it here so the upload doesn't fail. + if CORE.data.get(KEY_CORE, {}).get(KEY_TARGET_PLATFORM) == PLATFORM_RP2040: + idedata = platformio_api.get_idedata(config) + build_dir = Path(idedata.firmware_elf_path).parent + firmware_bin = build_dir / "firmware.bin" + signed_bin = build_dir / "firmware.bin.signed" + if firmware_bin.is_file() and not signed_bin.is_file(): + shutil.copy2(firmware_bin, signed_bin) + upload_args = ["-t", "upload", "-t", "nobuild"] if port is not None: upload_args += ["--upload-port", port] diff --git a/esphome/components/rp2040/post_build.py.script b/esphome/components/rp2040/post_build.py.script index 1f000ac78e..7dcd7e52a6 100644 --- a/esphome/components/rp2040/post_build.py.script +++ b/esphome/components/rp2040/post_build.py.script @@ -18,21 +18,6 @@ def rp2040_copy_ota_bin(source, target, env): shutil.copyfile(firmware_name, new_file_name) -def rp2040_copy_signed_bin(source, target, env): - """Create firmware.bin.signed so that 'nobuild' upload target can find it. - - The platform-raspberrypi build recipe creates firmware.bin.signed as a build - target, but the 'nobuild' upload flag skips the build phase. Without this - file, the upload fails with 'firmware.bin.signed not found'. - ESPHome does not use signing for RP2040, so this is just a copy. - """ - firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") - signed_name = env.subst("$BUILD_DIR/${PROGNAME}.bin.signed") - - shutil.copyfile(firmware_name, signed_name) - - # pylint: disable=E0602 env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", rp2040_copy_factory_uf2) # noqa env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", rp2040_copy_ota_bin) # noqa -env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", rp2040_copy_signed_bin) # noqa diff --git a/tests/unit_tests/test_main.py b/tests/unit_tests/test_main.py index 115cdfe9d9..483dc4a23e 100644 --- a/tests/unit_tests/test_main.py +++ b/tests/unit_tests/test_main.py @@ -40,6 +40,7 @@ from esphome.__main__ import ( show_logs, upload_program, upload_using_esptool, + upload_using_platformio, upload_using_uf2_copy, ) from esphome.components.esp32 import KEY_ESP32, KEY_VARIANT, VARIANT_ESP32 @@ -1203,6 +1204,46 @@ def test_upload_program_serial_platformio_platforms( mock_upload_using_platformio.assert_called_once_with(config, device) +def test_upload_using_platformio_creates_signed_bin_for_rp2040( + tmp_path: Path, +) -> None: + """Test that upload_using_platformio creates firmware.bin.signed for RP2040.""" + setup_core(platform=PLATFORM_RP2040) + + build_dir = tmp_path / "build" + build_dir.mkdir() + firmware_bin = build_dir / "firmware.bin" + firmware_bin.write_bytes(b"test firmware content") + firmware_elf = build_dir / "firmware.elf" + firmware_elf.write_bytes(b"elf") + + mock_idedata = MagicMock() + mock_idedata.firmware_elf_path = str(firmware_elf) + + with ( + patch("esphome.platformio_api.get_idedata", return_value=mock_idedata), + patch("esphome.platformio_api.run_platformio_cli_run", return_value=0), + ): + result = upload_using_platformio({}, "/dev/ttyACM0") + + assert result == 0 + signed_bin = build_dir / "firmware.bin.signed" + assert signed_bin.is_file() + assert signed_bin.read_bytes() == b"test firmware content" + + +def test_upload_using_platformio_skips_signed_bin_for_non_rp2040( + tmp_path: Path, +) -> None: + """Test that upload_using_platformio doesn't create signed bin for non-RP2040.""" + setup_core(platform=PLATFORM_ESP32) + + with patch("esphome.platformio_api.run_platformio_cli_run", return_value=0): + result = upload_using_platformio({}, "/dev/ttyUSB0") + + assert result == 0 + + def test_upload_program_serial_upload_failed( mock_upload_using_esptool: Mock, mock_get_port_type: Mock,