diff --git a/build.sh b/build.sh
index a5ea518e2..086554845 100755
--- a/build.sh
+++ b/build.sh
@@ -197,6 +197,12 @@ if [ "$BUILD_TYPE" = "all" ]; then
     if [ $? -ne 0 ]; then exit 1; fi
 fi
 
+# Generate PlatformIO manifest file
+if [ "$BUILD_TYPE" = "all" ]; then
+    python3 ./tools/gen_platformio_manifest.py -o "$TOOLS_JSON_OUT/"
+    if [ $? -ne 0 ]; then exit 1; fi
+fi
+
 # copy everything to arduino-esp32 installation
 if [ $COPY_OUT -eq 1 ] && [ -d "$ESP32_ARDUINO" ]; then
     ./tools/copy-to-arduino.sh
@@ -212,4 +218,4 @@ fi
 if [ "$BUILD_TYPE" = "all" ]; then
     ./tools/archive-build.sh
     if [ $? -ne 0 ]; then exit 1; fi
-fi
+fi 
diff --git a/components/arduino_tinyusb/CMakeLists.txt b/components/arduino_tinyusb/CMakeLists.txt
index 0872021eb..97591440f 100755
--- a/components/arduino_tinyusb/CMakeLists.txt
+++ b/components/arduino_tinyusb/CMakeLists.txt
@@ -28,6 +28,7 @@ if(CONFIG_TINYUSB_ENABLED)
       "${COMPONENT_DIR}/tinyusb/src/class/msc/msc_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/video/video_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/dfu/dfu_rt_device.c"
+      "${COMPONENT_DIR}/tinyusb/src/class/dfu/dfu_device.c"
       "${COMPONENT_DIR}/tinyusb/src/class/vendor/vendor_device.c"
       "${COMPONENT_DIR}/tinyusb/src/common/tusb_fifo.c"
       "${COMPONENT_DIR}/tinyusb/src/device/usbd_control.c"
diff --git a/components/arduino_tinyusb/Kconfig.projbuild b/components/arduino_tinyusb/Kconfig.projbuild
index a6abd4d8a..80983657f 100755
--- a/components/arduino_tinyusb/Kconfig.projbuild
+++ b/components/arduino_tinyusb/Kconfig.projbuild
@@ -175,6 +175,31 @@ menu "Arduino TinyUSB"
 
     endmenu
 
+    menu "DFU driver"
+        depends on TINYUSB_ENABLED
+
+        config TINYUSB_DFU_ENABLED
+            bool "Enable USB DFU TinyUSB driver"
+            default y
+            help
+                Enable USB DFU TinyUSB driver.
+
+        config TINYUSB_DESC_DFU_STRING
+            string "DFU Device String"
+            default "Espressif DFU Device"
+            depends on TINYUSB_DFU_ENABLED
+            help
+                Specify name of the DFU device
+
+        config TINYUSB_DFU_BUFSIZE
+            int "DFU buffer size"
+            default 4096
+            depends on TINYUSB_DFU_ENABLED
+            help
+                DFU buffer size
+
+    endmenu
+
     menu "VENDOR driver"
         depends on TINYUSB_ENABLED
 
diff --git a/components/arduino_tinyusb/include/tusb_config.h b/components/arduino_tinyusb/include/tusb_config.h
index a5a0afd32..ee1e5d270 100755
--- a/components/arduino_tinyusb/include/tusb_config.h
+++ b/components/arduino_tinyusb/include/tusb_config.h
@@ -64,6 +64,10 @@ extern "C" {
 #   define CONFIG_TINYUSB_DFU_RT_ENABLED 0
 #endif
 
+#ifndef CONFIG_TINYUSB_DFU_ENABLED
+#   define CONFIG_TINYUSB_DFU_ENABLED 0
+#endif
+
 #ifndef CONFIG_TINYUSB_VENDOR_ENABLED
 #   define CONFIG_TINYUSB_VENDOR_ENABLED 0
 #endif
@@ -106,6 +110,7 @@ extern "C" {
 #define CFG_TUD_VIDEO               CONFIG_TINYUSB_VIDEO_ENABLED
 #define CFG_TUD_CUSTOM_CLASS 		CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED
 #define CFG_TUD_DFU_RUNTIME			CONFIG_TINYUSB_DFU_RT_ENABLED
+#define CFG_TUD_DFU					CONFIG_TINYUSB_DFU_ENABLED
 #define CFG_TUD_VENDOR 				CONFIG_TINYUSB_VENDOR_ENABLED
 
 // CDC FIFO size of TX and RX
@@ -126,6 +131,9 @@ extern "C" {
 #define CFG_TUD_VIDEO_STREAMING     CONFIG_TINYUSB_VIDEO_STREAMING_IFS
 #define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE  CONFIG_TINYUSB_VIDEO_STREAMING_BUFSIZE
 
+// DFU buffer size
+#define CFG_TUD_DFU_XFER_BUFSIZE	CONFIG_TINYUSB_DFU_BUFSIZE
+
 // VENDOR FIFO size of TX and RX
 #define CFG_TUD_VENDOR_RX_BUFSIZE 	CONFIG_TINYUSB_VENDOR_RX_BUFSIZE
 #define CFG_TUD_VENDOR_TX_BUFSIZE 	CONFIG_TINYUSB_VENDOR_TX_BUFSIZE
diff --git a/configs/pio_end.txt b/configs/pio_end.txt
index 3190217a1..e7544f1dd 100644
--- a/configs/pio_end.txt
+++ b/configs/pio_end.txt
@@ -2,9 +2,9 @@
         "ESP32",
         ("F_CPU", "$BOARD_F_CPU"),
         ("ARDUINO", 10812),
-        ("ARDUINO_VARIANT", '\\"%s\\"' % env.BoardConfig().get("build.variant").replace('"', "")),
-        ("ARDUINO_BOARD", '\\"%s\\"' % env.BoardConfig().get("name").replace('"', "")),
-        "ARDUINO_PARTITION_%s" % basename(env.BoardConfig().get(
+        ("ARDUINO_VARIANT", '\\"%s\\"' % board_config.get("build.variant").replace('"', "")),
+        ("ARDUINO_BOARD", '\\"%s\\"' % board_config.get("name").replace('"', "")),
+        "ARDUINO_PARTITION_%s" % basename(board_config.get(
             "build.partitions", "default.csv")).replace(".csv", "").replace("-", "_")
     ]
 )
diff --git a/configs/pio_start.txt b/configs/pio_start.txt
index 010ba60aa..9a38020fb 100644
--- a/configs/pio_start.txt
+++ b/configs/pio_start.txt
@@ -31,5 +31,10 @@ from SCons.Script import DefaultEnvironment
 env = DefaultEnvironment()
 
 FRAMEWORK_DIR = env.PioPlatform().get_package_dir("framework-arduinoespressif32")
+FRAMEWORK_SDK_DIR = env.PioPlatform().get_package_dir(
+    "framework-arduinoespressif32-libs"
+)
+
+board_config = env.BoardConfig()
 
 env.Append(
diff --git a/tools/config.sh b/tools/config.sh
index 96d04d054..9b2fa3133 100755
--- a/tools/config.sh
+++ b/tools/config.sh
@@ -45,7 +45,7 @@ AR_TOOLS="$AR_OUT/tools"
 AR_PLATFORM_TXT="$AR_OUT/platform.txt"
 AR_GEN_PART_PY="$AR_TOOLS/gen_esp32part.py"
 AR_SDK="$AR_TOOLS/esp32-arduino-libs/$IDF_TARGET"
-PIO_SDK="FRAMEWORK_DIR, \"tools\", \"esp32-arduino-libs\", \"$IDF_TARGET\""
+PIO_SDK="FRAMEWORK_SDK_DIR, \"$IDF_TARGET\""
 TOOLS_JSON_OUT="$AR_TOOLS/esp32-arduino-libs"
 IDF_LIBS_DIR="$AR_ROOT/../esp32-arduino-libs"
 
diff --git a/tools/copy-libs.sh b/tools/copy-libs.sh
index 32992d624..fad66279e 100755
--- a/tools/copy-libs.sh
+++ b/tools/copy-libs.sh
@@ -404,8 +404,8 @@ for item; do
 		done
 	fi
 done
-echo "        join($PIO_SDK, env.BoardConfig().get(\"build.arduino.memory_type\", (env.BoardConfig().get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")), \"include\")," >> "$AR_PLATFORMIO_PY"
-echo "        join(FRAMEWORK_DIR, \"cores\", env.BoardConfig().get(\"build.core\"))" >> "$AR_PLATFORMIO_PY"
+echo "        join($PIO_SDK, board_config.get(\"build.arduino.memory_type\", (board_config.get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")), \"include\")," >> "$AR_PLATFORMIO_PY"
+echo "        join(FRAMEWORK_DIR, \"cores\", board_config.get(\"build.core\"))" >> "$AR_PLATFORMIO_PY"
 echo "    ]," >> "$AR_PLATFORMIO_PY"
 echo "" >> "$AR_PLATFORMIO_PY"
 
@@ -429,7 +429,7 @@ done
 echo "    LIBPATH=[" >> "$AR_PLATFORMIO_PY"
 echo "        join($PIO_SDK, \"lib\")," >> "$AR_PLATFORMIO_PY"
 echo "        join($PIO_SDK, \"ld\")," >> "$AR_PLATFORMIO_PY"
-echo "        join($PIO_SDK, env.BoardConfig().get(\"build.arduino.memory_type\", (env.BoardConfig().get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")))" >> "$AR_PLATFORMIO_PY"
+echo "        join($PIO_SDK, board_config.get(\"build.arduino.memory_type\", (board_config.get(\"build.flash_mode\", \"dio\") + \"_$OCT_PSRAM\")))" >> "$AR_PLATFORMIO_PY"
 echo "    ]," >> "$AR_PLATFORMIO_PY"
 echo "" >> "$AR_PLATFORMIO_PY"
 
diff --git a/tools/gen_platformio_manifest.py b/tools/gen_platformio_manifest.py
new file mode 100644
index 000000000..a0a1d162b
--- /dev/null
+++ b/tools/gen_platformio_manifest.py
@@ -0,0 +1,95 @@
+import argparse
+import json
+import os
+import re
+import sys
+
+MANIFEST_DATA = {
+    "name": "framework-arduinoespressif32-libs",
+    "description": "Precompiled libraries for Arduino Wiring-based Framework for the Espressif ESP32 series of SoCs",
+    "keywords": ["framework", "arduino", "espressif", "esp32"],
+    "license": "LGPL-2.1-or-later",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/espressif/esp32-arduino-libs",
+    },
+}
+
+
+def convert_version(version_line):
+    """A helper function that converts a custom IDF version string
+    to a suitable SemVer alternative. For example:
+    'release/v5.1 420ebd208a' becomes '5.1.0+sha.420ebd208a'
+    """
+
+    regex_pattern = r"^esp-idf:\s*release\/v(?P<IDF_VERSION>[\d\.]{3,5})\s*(?P<COMMIT_HASH>[0-9a-f]{5,40})"
+    match = re.search(regex_pattern, version_line)
+    if not match:
+        sys.stderr.write(
+            f"Failed to find a regex match for '{regex_pattern}' in '{version_line}'\n"
+        )
+        return ""
+
+    version = match.group("IDF_VERSION")
+    commit = match.group("COMMIT_HASH")
+
+    assert version, f"Failed to parse version value from '{version_line}'"
+    assert commit, f"Failed to parse commit hash value from '{version_line}'"
+
+    if version.count(".") < 2:
+        # The most basic casting to a SemVer with three digits
+        version = version + ".0"
+
+    return f"{version}+sha.{commit}"
+
+
+def main(dst_dir):
+    # The "version.txt" file is expected to contain IDF version in the following format
+    # "esp-idf: release/v$VERSION COMMIT_HASH".
+    version_file = os.path.join("version.txt")
+
+    if not os.path.isfile(version_file):
+        sys.stderr.write("Missing the 'version.txt' file.\n")
+        return -1
+
+    version_line = ""
+    with open(version_file, encoding="utf8") as fp:
+        for line in fp.readlines():
+            if not line.startswith("esp-idf"):
+                continue
+            version_line = line.strip()
+
+    if not version_line:
+        sys.stderr.write("Failed to find ESP-IDF version in the 'version.txt' file!\n")
+        return -1
+
+    converted_version = convert_version(version_line)
+    if not converted_version:
+        sys.stderr.write(
+            f"Failed to convert version '{version_line}' from version.txt\n"
+        )
+        return -1
+
+    manifest_file_path = os.path.join(dst_dir, "package.json")
+    with open(manifest_file_path, "w", encoding="utf8") as fp:
+        MANIFEST_DATA["version"] = converted_version
+        json.dump(MANIFEST_DATA, fp, indent=2)
+
+    print(
+        f"Generated PlatformIO manifest file '{manifest_file_path}' with '{converted_version}' version"
+    )
+    return 0
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "-o",
+        "--dst-dir",
+        dest="dst_dir",
+        required=True,
+        help="Destination folder where the 'package.json' manifest will be located",
+    )
+    args = parser.parse_args()
+
+    sys.exit(main(args.dst_dir))