From 5728bc499c45c980662ae5b880d944136c819fad Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Tue, 22 Jul 2025 09:24:07 -0600 Subject: [PATCH 1/9] Add automatic builds that contain examples on release --- .github/workflows/build.yml | 23 +---- .github/workflows/release.yml | 23 +++++ build.sh | 185 ++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 build.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91ec7d8..fbb9696 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build firmware +name: Build Firmware on: pull_request: @@ -7,7 +7,6 @@ on: push: branches: - features_for_launch - workflow_dispatch: jobs: build: @@ -17,21 +16,5 @@ jobs: uses: actions/checkout@v4 with: submodules: true - - name: Install packages - run: | - sudo apt install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib - - name: Build MPY Cross - run: make -C micropython/mpy-cross - - name: MicroPython submodules - run: make -C micropython/ports/rp2 BOARD=SPARKFUN_XRP_CONTROLLER submodules - - name: Set Pico SDK path - run: echo "PICO_SDK_PATH=$GITHUB_WORKSPACE/micropython/lib/pico-sdk" >> "$GITHUB_ENV" - - name: Build OpenCV - run: make -C src/opencv PLATFORM=rp2350 --no-print-directory -j4 - - name: Build firmware - run: make BOARD=SPARKFUN_XRP_CONTROLLER -j4 - - name: Upload UF2 - uses: actions/upload-artifact@v4 - with: - name: firmware.uf2 - path: micropython/ports/rp2/build-SPARKFUN_XRP_CONTROLLER-LARGE_BINARY/firmware.uf2 + - name: Build Firmware + run: source build.sh && build_micropython_opencv diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cf1e5b0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Build and Deploy Firmware Release + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + - name: Build Firmware + run: source build.sh && build_micropython_opencv + - name: Upload Release Assets + uses: shogo82148/actions-upload-release-asset@v1 + with: + asset_path: "micropython/ports/rp2/build-SPARKFUN_XRP_CONTROLLER-LARGE_BINARY/firmware.uf2" + github_token: ${{ secrets.GITHUB_TOKEN }} + upload_url: ${{ github.event.release.upload_url }} + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..166446c --- /dev/null +++ b/build.sh @@ -0,0 +1,185 @@ +if which nproc > /dev/null; then + MAKEOPTS="-j$(nproc)" +else + MAKEOPTS="-j$(sysctl -n hw.ncpu)" +fi + +# TODO: Could also make these opts into the build_micropython_opencv function if we care... +FROZEN_MODULES_DIR="$(dirname "$0")/frozen_modules" +FROZEN_EXAMPLES_ARCHIVE_SCRIPT="frozen_examples.py" +FROZEN_EXAMPLES_UNPACKED_DIR="micropython-opencv-examples" + +# Uses freezefs to create a frozen filesystem archive for the provided directory. +# See https://github.com/bixb922/freezefs for more details on freezefs +# Options: + # $1: The directory to freeze + # $2: The name that you want the frozen directory to have once unpacked on the board + # $3: The output file name for the frozen archive .py file +function create_frozen_fs { + local DIR_TO_FREEZE=$1 + local DIR_NAME_ON_BOARD=$2 + local OUTPUT_FILE=$3 + + echo "Creating frozen filesystem for directory: $DIR_TO_FREEZE" + echo "The frozen directory will be named: $DIR_NAME_ON_BOARD" + echo "The output file will be: $OUTPUT_FILE" + + cp -r $DIR_TO_FREEZE $DIR_NAME_ON_BOARD + + python -m freezefs $DIR_NAME_ON_BOARD $OUTPUT_FILE +} + +# Options: +# $1: The directory to add to the manifest +# $2: The port (e.g. rp2) +# $3: The board (e.g. SPARKFUN_XRP_CONTROLLER) +# $4: The mpconfigboard file name (e.g. mpconfigboard.cmake or mpconfigboard.m) Default: mpconfigboard.cmake +function add_to_manifest { + local DIR=$1 + local PORT=$2 + local BOARD=$3 + local MPCONFIG_FILE="${4:-mpconfigboard.cmake}" + + # Add the directory to the manifest file + echo "Adding $DIR to the manifest for $PORT on $BOARD using $MPCONFIG_FILE" + local BOARD_DIR="micropython/ports/${PORT}/boards/${BOARD}" + + # Create manifest.py if it doesn't exist + if [ ! -f ${BOARD_DIR}/manifest.py ]; then + echo "include(\"\$(PORT_DIR)/boards/manifest.py\")" > ${BOARD_DIR}/manifest.py + + # also add the necessary frozen manifest line to mpconfigboard.cmake: set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) + # We will use the optional MPCONFIG_FILE argument to determine if we should add this line + + if [ -n "$MPCONFIG_FILE" ]; then + if [[ $MPCONFIG_FILE == *.mk ]]; then + # e.g. for TEENSY which uses mpconfigboard.mk instead of mpconfigboard.cmake + echo "Adding frozen manifest line to mpconfigboard.mk for $BOARD" + printf "\nFROZEN_MANIFEST ?= \$(BOARD_DIR)/manifest.py" >> ${BOARD_DIR}/$MPCONFIG_FILE + elif [[ $MPCONFIG_FILE == *.cmake ]]; then + echo "Adding frozen manifest line to mpconfigboard.cmake for $BOARD" + printf "\nset(MICROPY_FROZEN_MANIFEST \"\${MICROPY_BOARD_DIR}/manifest.py\")" >> ${BOARD_DIR}/$MPCONFIG_FILE + fi + fi + fi + + # Add the freeze line to the manifest.py for the board + echo "Adding freeze line to manifest.py for $BOARD" + printf "\nfreeze(\"${DIR}\")" >> ${BOARD_DIR}/manifest.py + + # Helpful for debugging during the build process, but can be removed if we'd rather not see this output... + echo "Manifest.py for $BOARD:" + cat ${BOARD_DIR}/manifest.py +} + +# Adds the frozen data filesystem to the boot.py file for the given port +# Options: + # $1: Port name + # $2: Frozen data file path + # $3: Copy Source: If copying imported frozen data to a mutable location, this is the directory name of the source (optional) + # $4: Copy Destination: If copying imported frozen data to a mutable location, this is the directory name of the destination (optional) + # $5: Add destination to sys.path? If true, the destination directory will be added to sys.path in _boot.py (optional) + # NOTE: By providing the source and destination, the frozen data filesystem will be copied to a mutable location on the board + # If they are not provided, the frozen data filesystem will still be accessible, but will be read-only. +function add_frozen_data_to_boot_for_port { + local TARGET_PORT_NAME=$1 + local FROZEN_DATA_FILE=$2 + local SOURCE_DIR=$3 + local DESTINATION_DIR=$4 + local ADD_TO_SYSPATH=${5:-false} + + # Remove the ".py" extension from the frozen data file + local FROZEN_DATA_BASENAME=$(basename $FROZEN_DATA_FILE .py) + + # Check if the _boot.py file exists in the port's modules directory and error out if it does not + if [ ! -f micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py ]; then + echo "Error: _boot.py file not found in ports/${TARGET_PORT_NAME}/modules/" + exit 1 + fi + + # Add the frozen data filesystem to the _boot.py file + local BOOT_FILE="micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py" + + echo "Adding frozen data filesystem to ${BOOT_FILE}" + echo "import ${FROZEN_DATA_BASENAME}" >> ${BOOT_FILE} + + # Now, copy the unpacked frozen data filesystem to a mutable location if the source and destination are provided + if [ -n "$SOURCE_DIR" ] && [ -n "$DESTINATION_DIR" ]; then + echo "Copying frozen data from ${SOURCE_DIR} to ${DESTINATION_DIR} in _boot.py" + local BOOT_FILE="micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py" + echo "import os" >> ${BOOT_FILE} + # Simple recursive function to copy the directory tree (since i.e. shutil.copytree is not available on MicroPython) + echo "def copytree(src, dst):" >> ${BOOT_FILE} + echo " try:" >> ${BOOT_FILE} + echo " os.mkdir(dst)" >> ${BOOT_FILE} + echo " except OSError:" >> ${BOOT_FILE} + echo " pass" >> ${BOOT_FILE} + echo " for entry in os.ilistdir(src):" >> ${BOOT_FILE} + echo " fname, typecode, _, _ = entry" >> ${BOOT_FILE} + echo " src_path = src + '/' + fname" >> ${BOOT_FILE} + echo " dst_path = dst + '/' + fname" >> ${BOOT_FILE} + echo " if typecode == 0x4000:" >> ${BOOT_FILE} # typecode == 0x4000 means directory + echo " copytree(src_path, dst_path)" >> ${BOOT_FILE} + echo " else:" >> ${BOOT_FILE} + echo " with open(src_path, 'rb') as fsrc:" >> ${BOOT_FILE} + echo " with open(dst_path, 'wb') as fdst:" >> ${BOOT_FILE} + echo " fdst.write(fsrc.read())" >> ${BOOT_FILE} + echo "copytree('${SOURCE_DIR}', '${DESTINATION_DIR}')" >> ${BOOT_FILE} + fi + + # If the ADD_TO_SYSPATH flag is true, add the destination directory to sys.path + if [ "$ADD_TO_SYSPATH" = true ]; then + echo "Adding ${DESTINATION_DIR} to sys.path in _boot.py" + echo "import sys" >> ${BOOT_FILE} + echo "sys.path.append('/${DESTINATION_DIR}')" >> ${BOOT_FILE} + fi + + # Helpful for debugging during the build process, but can be removed if we'd rather not see this output... + echo "Content of _boot.py after adding frozen data filesystem:" + cat micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py +} + +# Installs necessary dependencies and builds OpenCV and the firmware +# Also freezes the examples directory in a filesystem archive on the board +function build_micropython_opencv { + # Install necessary packages (Could move into an install_dependencies.sh if we want this to be more explicit/modular) + sudo apt-get update + sudo apt install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib + # Install necessary python packages (could also move this to a requirements.txt file) + pip install freezefs + + # Create a directory for frozen modules, we can add arbitrary .py files to this directory in the future. + # For now it will just contain the archived examples script. + mkdir "$FROZEN_MODULES_DIR" + + # Create our frozen filesystem archive for the examples directory + # Note the "." to make the read-only version of the examples directory hidden in IDEs like Thonny + create_frozen_fs "examples" ".$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_MODULES_DIR/$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" + + # Add necessary content to the manifest file to freeze the modules in the provided directory + add_to_manifest "$FROZEN_MODULES_DIR" "rp2" "SPARKFUN_XRP_CONTROLLER" "mpconfigvariant_LARGE_BINARY.cmake" + + # Add necessary content to the boot.py file to unpack the frozen data filesystem on boot + # Provide the source and destination directories to copy the frozen data filesystem to a mutable (and non-hidden) location + # Provide "true" as the last argument to add the destination directory to sys.path (since our examples directory contains modules that we want to be importable...) + add_frozen_data_to_boot_for_port "rp2" "$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" ".$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_EXAMPLES_UNPACKED_DIR" true + + # Set Pico SDK path to $GITHUB_WORKSPACE/micropython/lib/pico-sdk if $GITHUB_WORKSPACE is set, otherwise use the current directory + if [ -n "$GITHUB_WORKSPACE" ]; then + export PICO_SDK_PATH="$GITHUB_WORKSPACE/micropython/lib/pico-sdk" + else + export PICO_SDK_PATH=$(dirname "$0")/micropython/lib/pico-sdk + fi + + # Build MPY Cross compiler + make -C micropython/mpy-cross + + # Update necessary MicroPython submodules + make -C micropython/ports/rp2 BOARD=SPARKFUN_XRP_CONTROLLER submodules + + # Build OpenCV + make -C src/opencv PLATFORM=rp2350 --no-print-directory ${MAKEOPTS} + + # Build firmware + make BOARD=SPARKFUN_XRP_CONTROLLER ${MAKEOPTS} +} From 5de809f928ed999365f16cc834d8b745b54cd89b Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Tue, 22 Jul 2025 13:05:06 -0600 Subject: [PATCH 2/9] Add unmount of immutable directory and persistent tracking so expanding examples only happens once --- build.sh | 49 +++++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/build.sh b/build.sh index 166446c..a315248 100644 --- a/build.sh +++ b/build.sh @@ -8,6 +8,7 @@ fi FROZEN_MODULES_DIR="$(dirname "$0")/frozen_modules" FROZEN_EXAMPLES_ARCHIVE_SCRIPT="frozen_examples.py" FROZEN_EXAMPLES_UNPACKED_DIR="micropython-opencv-examples" +PERSISTENT_FILE_FOR_UNPACK="/keep_opencv_example_changes" # Uses freezefs to create a frozen filesystem archive for the provided directory. # See https://github.com/bixb922/freezefs for more details on freezefs @@ -100,31 +101,43 @@ function add_frozen_data_to_boot_for_port { # Add the frozen data filesystem to the _boot.py file local BOOT_FILE="micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py" + # Create our "persistent file for unpack" that will be used to check if the frozen data filesystem has already been unpacked + # If it has not been unpacked, we will import the frozen data filesystem echo "Adding frozen data filesystem to ${BOOT_FILE}" - echo "import ${FROZEN_DATA_BASENAME}" >> ${BOOT_FILE} + echo "import os" >> ${BOOT_FILE} + echo "try:" >> ${BOOT_FILE} + echo " os.stat('${PERSISTENT_FILE_FOR_UNPACK}')" >> ${BOOT_FILE} + echo "except OSError:" >> ${BOOT_FILE} + echo " import ${FROZEN_DATA_BASENAME}" >> ${BOOT_FILE} + echo " with open('${PERSISTENT_FILE_FOR_UNPACK}', 'w') as f:" >> ${BOOT_FILE} + echo " f.write('Hi! Delete this file to restore the ${FROZEN_EXAMPLES_UNPACKED_DIR} to its default state. WARNING: This will override ALL of your changes to that directory.')" >> ${BOOT_FILE} # Now, copy the unpacked frozen data filesystem to a mutable location if the source and destination are provided + # Simple recursive function to copy the directory tree (since i.e. shutil.copytree is not available on MicroPython) if [ -n "$SOURCE_DIR" ] && [ -n "$DESTINATION_DIR" ]; then echo "Copying frozen data from ${SOURCE_DIR} to ${DESTINATION_DIR} in _boot.py" local BOOT_FILE="micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py" - echo "import os" >> ${BOOT_FILE} - # Simple recursive function to copy the directory tree (since i.e. shutil.copytree is not available on MicroPython) - echo "def copytree(src, dst):" >> ${BOOT_FILE} + echo " def copytree(src, dst):" >> ${BOOT_FILE} + echo " try:" >> ${BOOT_FILE} + echo " os.mkdir(dst)" >> ${BOOT_FILE} + echo " except OSError:" >> ${BOOT_FILE} + echo " pass" >> ${BOOT_FILE} + echo " for entry in os.ilistdir(src):" >> ${BOOT_FILE} + echo " fname, typecode, _, _ = entry" >> ${BOOT_FILE} + echo " src_path = src + '/' + fname" >> ${BOOT_FILE} + echo " dst_path = dst + '/' + fname" >> ${BOOT_FILE} + echo " if typecode == 0x4000:" >> ${BOOT_FILE} # typecode == 0x4000 means directory + echo " copytree(src_path, dst_path)" >> ${BOOT_FILE} + echo " else:" >> ${BOOT_FILE} + echo " with open(src_path, 'rb') as fsrc:" >> ${BOOT_FILE} + echo " with open(dst_path, 'wb') as fdst:" >> ${BOOT_FILE} + echo " fdst.write(fsrc.read())" >> ${BOOT_FILE} + echo " copytree('${SOURCE_DIR}', '${DESTINATION_DIR}')" >> ${BOOT_FILE} + # Finally, unmount the source directory if it is mounted echo " try:" >> ${BOOT_FILE} - echo " os.mkdir(dst)" >> ${BOOT_FILE} - echo " except OSError:" >> ${BOOT_FILE} - echo " pass" >> ${BOOT_FILE} - echo " for entry in os.ilistdir(src):" >> ${BOOT_FILE} - echo " fname, typecode, _, _ = entry" >> ${BOOT_FILE} - echo " src_path = src + '/' + fname" >> ${BOOT_FILE} - echo " dst_path = dst + '/' + fname" >> ${BOOT_FILE} - echo " if typecode == 0x4000:" >> ${BOOT_FILE} # typecode == 0x4000 means directory - echo " copytree(src_path, dst_path)" >> ${BOOT_FILE} - echo " else:" >> ${BOOT_FILE} - echo " with open(src_path, 'rb') as fsrc:" >> ${BOOT_FILE} - echo " with open(dst_path, 'wb') as fdst:" >> ${BOOT_FILE} - echo " fdst.write(fsrc.read())" >> ${BOOT_FILE} - echo "copytree('${SOURCE_DIR}', '${DESTINATION_DIR}')" >> ${BOOT_FILE} + echo " os.umount('/${SOURCE_DIR}')" >> ${BOOT_FILE} + echo " except Exception as e:" >> ${BOOT_FILE} + echo " print('umount failed:', e)" >> ${BOOT_FILE} fi # If the ADD_TO_SYSPATH flag is true, add the destination directory to sys.path From 0885da71d72cbedc8bafdd90723eb9bd03ba3be8 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Thu, 24 Jul 2025 10:18:04 -0600 Subject: [PATCH 3/9] update names for frozen examples --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index a315248..ff0e607 100644 --- a/build.sh +++ b/build.sh @@ -7,8 +7,8 @@ fi # TODO: Could also make these opts into the build_micropython_opencv function if we care... FROZEN_MODULES_DIR="$(dirname "$0")/frozen_modules" FROZEN_EXAMPLES_ARCHIVE_SCRIPT="frozen_examples.py" -FROZEN_EXAMPLES_UNPACKED_DIR="micropython-opencv-examples" -PERSISTENT_FILE_FOR_UNPACK="/keep_opencv_example_changes" +FROZEN_EXAMPLES_UNPACKED_DIR="opencv-examples" +PERSISTENT_FILE_FOR_UNPACK="/${FROZEN_EXAMPLES_UNPACKED_DIR}/reset_examples.txt" # Uses freezefs to create a frozen filesystem archive for the provided directory. # See https://github.com/bixb922/freezefs for more details on freezefs From fa2d97aeb949cd471f786824ac3746bd76ce884d Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Thu, 24 Jul 2025 10:23:25 -0600 Subject: [PATCH 4/9] pedantic fixes --- build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index ff0e607..be4161f 100644 --- a/build.sh +++ b/build.sh @@ -53,12 +53,12 @@ function add_to_manifest { # We will use the optional MPCONFIG_FILE argument to determine if we should add this line if [ -n "$MPCONFIG_FILE" ]; then + echo "Attempting to add frozen manifest line to $MPCONFIG_FILE for $BOARD" + if [[ $MPCONFIG_FILE == *.mk ]]; then # e.g. for TEENSY which uses mpconfigboard.mk instead of mpconfigboard.cmake - echo "Adding frozen manifest line to mpconfigboard.mk for $BOARD" printf "\nFROZEN_MANIFEST ?= \$(BOARD_DIR)/manifest.py" >> ${BOARD_DIR}/$MPCONFIG_FILE elif [[ $MPCONFIG_FILE == *.cmake ]]; then - echo "Adding frozen manifest line to mpconfigboard.cmake for $BOARD" printf "\nset(MICROPY_FROZEN_MANIFEST \"\${MICROPY_BOARD_DIR}/manifest.py\")" >> ${BOARD_DIR}/$MPCONFIG_FILE fi fi @@ -73,7 +73,7 @@ function add_to_manifest { cat ${BOARD_DIR}/manifest.py } -# Adds the frozen data filesystem to the boot.py file for the given port +# Adds the frozen data filesystem to the _boot.py file for the given port # Options: # $1: Port name # $2: Frozen data file path From fe4cc0d233d08f75ca2e8c6a99f1453f9716b855 Mon Sep 17 00:00:00 2001 From: Malcolm McKellips Date: Thu, 24 Jul 2025 11:35:55 -0600 Subject: [PATCH 5/9] Use built in extraction of freezefs to simplify build script --- build.sh | 62 ++++++++++++++++---------------------------------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/build.sh b/build.sh index be4161f..7bf2d5f 100644 --- a/build.sh +++ b/build.sh @@ -27,9 +27,14 @@ function create_frozen_fs { cp -r $DIR_TO_FREEZE $DIR_NAME_ON_BOARD - python -m freezefs $DIR_NAME_ON_BOARD $OUTPUT_FILE + # Use on-import=extract so our frozen filesystem is unpacked to '/' in flash on import + # Use --compress to compress the frozen filesystem archive + # Use --overwrite always to ensure that the frozen filesystem is returned to factory state if the persistent file is deleted + + python -m freezefs $DIR_NAME_ON_BOARD $OUTPUT_FILE --on-import=extract --compress --overwrite always } +# Adds the provided directory to the manifest file for the specified port and board. # Options: # $1: The directory to add to the manifest # $2: The port (e.g. rp2) @@ -77,17 +82,11 @@ function add_to_manifest { # Options: # $1: Port name # $2: Frozen data file path - # $3: Copy Source: If copying imported frozen data to a mutable location, this is the directory name of the source (optional) - # $4: Copy Destination: If copying imported frozen data to a mutable location, this is the directory name of the destination (optional) - # $5: Add destination to sys.path? If true, the destination directory will be added to sys.path in _boot.py (optional) - # NOTE: By providing the source and destination, the frozen data filesystem will be copied to a mutable location on the board - # If they are not provided, the frozen data filesystem will still be accessible, but will be read-only. + # $3: Unpacked directory name on the board (optional). If provided, the modules in this directory will be made importable function add_frozen_data_to_boot_for_port { local TARGET_PORT_NAME=$1 local FROZEN_DATA_FILE=$2 - local SOURCE_DIR=$3 - local DESTINATION_DIR=$4 - local ADD_TO_SYSPATH=${5:-false} + local UNPACKED_DIR=$3 # Remove the ".py" extension from the frozen data file local FROZEN_DATA_BASENAME=$(basename $FROZEN_DATA_FILE .py) @@ -110,41 +109,13 @@ function add_frozen_data_to_boot_for_port { echo "except OSError:" >> ${BOOT_FILE} echo " import ${FROZEN_DATA_BASENAME}" >> ${BOOT_FILE} echo " with open('${PERSISTENT_FILE_FOR_UNPACK}', 'w') as f:" >> ${BOOT_FILE} - echo " f.write('Hi! Delete this file to restore the ${FROZEN_EXAMPLES_UNPACKED_DIR} to its default state. WARNING: This will override ALL of your changes to that directory.')" >> ${BOOT_FILE} - - # Now, copy the unpacked frozen data filesystem to a mutable location if the source and destination are provided - # Simple recursive function to copy the directory tree (since i.e. shutil.copytree is not available on MicroPython) - if [ -n "$SOURCE_DIR" ] && [ -n "$DESTINATION_DIR" ]; then - echo "Copying frozen data from ${SOURCE_DIR} to ${DESTINATION_DIR} in _boot.py" - local BOOT_FILE="micropython/ports/${TARGET_PORT_NAME}/modules/_boot.py" - echo " def copytree(src, dst):" >> ${BOOT_FILE} - echo " try:" >> ${BOOT_FILE} - echo " os.mkdir(dst)" >> ${BOOT_FILE} - echo " except OSError:" >> ${BOOT_FILE} - echo " pass" >> ${BOOT_FILE} - echo " for entry in os.ilistdir(src):" >> ${BOOT_FILE} - echo " fname, typecode, _, _ = entry" >> ${BOOT_FILE} - echo " src_path = src + '/' + fname" >> ${BOOT_FILE} - echo " dst_path = dst + '/' + fname" >> ${BOOT_FILE} - echo " if typecode == 0x4000:" >> ${BOOT_FILE} # typecode == 0x4000 means directory - echo " copytree(src_path, dst_path)" >> ${BOOT_FILE} - echo " else:" >> ${BOOT_FILE} - echo " with open(src_path, 'rb') as fsrc:" >> ${BOOT_FILE} - echo " with open(dst_path, 'wb') as fdst:" >> ${BOOT_FILE} - echo " fdst.write(fsrc.read())" >> ${BOOT_FILE} - echo " copytree('${SOURCE_DIR}', '${DESTINATION_DIR}')" >> ${BOOT_FILE} - # Finally, unmount the source directory if it is mounted - echo " try:" >> ${BOOT_FILE} - echo " os.umount('/${SOURCE_DIR}')" >> ${BOOT_FILE} - echo " except Exception as e:" >> ${BOOT_FILE} - echo " print('umount failed:', e)" >> ${BOOT_FILE} - fi + echo " f.write('Hi! Delete this file and reset your board to restore the ${FROZEN_EXAMPLES_UNPACKED_DIR} directory to its default state. WARNING: This will override ALL of your changes to that directory.')" >> ${BOOT_FILE} - # If the ADD_TO_SYSPATH flag is true, add the destination directory to sys.path - if [ "$ADD_TO_SYSPATH" = true ]; then - echo "Adding ${DESTINATION_DIR} to sys.path in _boot.py" + # If a destination directory is provided, we will add it to the sys.path so that the modules in the unpacked directory can be imported + if [ -n "$UNPACKED_DIR" ]; then + echo "Adding ${UNPACKED_DIR} to sys.path in _boot.py" echo "import sys" >> ${BOOT_FILE} - echo "sys.path.append('/${DESTINATION_DIR}')" >> ${BOOT_FILE} + echo "sys.path.append('/${UNPACKED_DIR}')" >> ${BOOT_FILE} fi # Helpful for debugging during the build process, but can be removed if we'd rather not see this output... @@ -156,7 +127,7 @@ function add_frozen_data_to_boot_for_port { # Also freezes the examples directory in a filesystem archive on the board function build_micropython_opencv { # Install necessary packages (Could move into an install_dependencies.sh if we want this to be more explicit/modular) - sudo apt-get update + sudo apt update sudo apt install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib # Install necessary python packages (could also move this to a requirements.txt file) pip install freezefs @@ -167,7 +138,7 @@ function build_micropython_opencv { # Create our frozen filesystem archive for the examples directory # Note the "." to make the read-only version of the examples directory hidden in IDEs like Thonny - create_frozen_fs "examples" ".$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_MODULES_DIR/$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" + create_frozen_fs "examples" "$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_MODULES_DIR/$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" # Add necessary content to the manifest file to freeze the modules in the provided directory add_to_manifest "$FROZEN_MODULES_DIR" "rp2" "SPARKFUN_XRP_CONTROLLER" "mpconfigvariant_LARGE_BINARY.cmake" @@ -175,7 +146,8 @@ function build_micropython_opencv { # Add necessary content to the boot.py file to unpack the frozen data filesystem on boot # Provide the source and destination directories to copy the frozen data filesystem to a mutable (and non-hidden) location # Provide "true" as the last argument to add the destination directory to sys.path (since our examples directory contains modules that we want to be importable...) - add_frozen_data_to_boot_for_port "rp2" "$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" ".$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_EXAMPLES_UNPACKED_DIR" true + # add_frozen_data_to_boot_for_port "rp2" "$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" ".$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_EXAMPLES_UNPACKED_DIR" true + add_frozen_data_to_boot_for_port "rp2" "$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" "$FROZEN_EXAMPLES_UNPACKED_DIR" true # Set Pico SDK path to $GITHUB_WORKSPACE/micropython/lib/pico-sdk if $GITHUB_WORKSPACE is set, otherwise use the current directory if [ -n "$GITHUB_WORKSPACE" ]; then From 95c72eb0075052896affb4fd9dd6bd1e0566f3a5 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 30 Jul 2025 12:14:08 -0600 Subject: [PATCH 6/9] Update content of PERSISTENT_FILE_FOR_UNPACK --- build.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 7bf2d5f..966535a 100644 --- a/build.sh +++ b/build.sh @@ -109,7 +109,14 @@ function add_frozen_data_to_boot_for_port { echo "except OSError:" >> ${BOOT_FILE} echo " import ${FROZEN_DATA_BASENAME}" >> ${BOOT_FILE} echo " with open('${PERSISTENT_FILE_FOR_UNPACK}', 'w') as f:" >> ${BOOT_FILE} - echo " f.write('Hi! Delete this file and reset your board to restore the ${FROZEN_EXAMPLES_UNPACKED_DIR} directory to its default state. WARNING: This will override ALL of your changes to that directory.')" >> ${BOOT_FILE} + echo " f.write('Hi! The firmware has this directory frozen into the firmware, and the _boot.py\\n')" >> ${BOOT_FILE} + echo " f.write('file has been modified to automatically unpack this directory if needed. As long\\n')" >> ${BOOT_FILE} + echo " f.write('as this file exists, it will not unpack the directory, meaning you can safely\\n')" >> ${BOOT_FILE} + echo " f.write('edit the files here or delete all other files to free up storage space. If you\\n')" >> ${BOOT_FILE} + echo " f.write('want to restore this directory to its default state, delete this file and the\\n')" >> ${BOOT_FILE} + echo " f.write('directory will be unpacked again on the next boot.\\n')" >> ${BOOT_FILE} + echo " f.write('\\n')" >> ${BOOT_FILE} + echo " f.write('WARNING: Deleting this file will override ALL changes to this directory!')" >> ${BOOT_FILE} # If a destination directory is provided, we will add it to the sys.path so that the modules in the unpacked directory can be imported if [ -n "$UNPACKED_DIR" ]; then From 4fc3bbec02700cce9ae11f5252b70587ae8be699 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 30 Jul 2025 15:34:47 -0600 Subject: [PATCH 7/9] Update build.sh 18c4039 changed the examples folder name --- build.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 966535a..ed1f31e 100644 --- a/build.sh +++ b/build.sh @@ -144,8 +144,7 @@ function build_micropython_opencv { mkdir "$FROZEN_MODULES_DIR" # Create our frozen filesystem archive for the examples directory - # Note the "." to make the read-only version of the examples directory hidden in IDEs like Thonny - create_frozen_fs "examples" "$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_MODULES_DIR/$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" + create_frozen_fs "opencv-examples" "$FROZEN_EXAMPLES_UNPACKED_DIR" "$FROZEN_MODULES_DIR/$FROZEN_EXAMPLES_ARCHIVE_SCRIPT" # Add necessary content to the manifest file to freeze the modules in the provided directory add_to_manifest "$FROZEN_MODULES_DIR" "rp2" "SPARKFUN_XRP_CONTROLLER" "mpconfigvariant_LARGE_BINARY.cmake" From c37d7b32e7c1739fced9e31ab557d5f635f14af8 Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 30 Jul 2025 15:48:37 -0600 Subject: [PATCH 8/9] Change release workflow to rename firmware file --- .github/workflows/release.yml | 2 +- build.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf1e5b0..df235a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - name: Upload Release Assets uses: shogo82148/actions-upload-release-asset@v1 with: - asset_path: "micropython/ports/rp2/build-SPARKFUN_XRP_CONTROLLER-LARGE_BINARY/firmware.uf2" + asset_path: "micropython/ports/rp2/build-SPARKFUN_XRP_CONTROLLER-LARGE_BINARY/MICROPYTHON_OPENCV_SPARKFUN_XRP_CONTROLLER.uf2" github_token: ${{ secrets.GITHUB_TOKEN }} upload_url: ${{ github.event.release.upload_url }} diff --git a/build.sh b/build.sh index ed1f31e..9dd0071 100644 --- a/build.sh +++ b/build.sh @@ -173,4 +173,7 @@ function build_micropython_opencv { # Build firmware make BOARD=SPARKFUN_XRP_CONTROLLER ${MAKEOPTS} + + # Rename firmware file to identify it as the OpenCV build and which board it's for + mv micropython/ports/rp2/build-SPARKFUN_XRP_CONTROLLER-LARGE_BINARY/firmware.uf2 micropython/ports/rp2/build-SPARKFUN_XRP_CONTROLLER-LARGE_BINARY/MICROPYTHON_OPENCV_SPARKFUN_XRP_CONTROLLER.uf2 } From b6b595841726bc0e2d93cf654bdf9c389c35271c Mon Sep 17 00:00:00 2001 From: Dryw Wade Date: Wed, 30 Jul 2025 16:04:19 -0600 Subject: [PATCH 9/9] build.sh: only copy frozen directory if different --- build.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 9dd0071..ca7ff35 100644 --- a/build.sh +++ b/build.sh @@ -25,7 +25,9 @@ function create_frozen_fs { echo "The frozen directory will be named: $DIR_NAME_ON_BOARD" echo "The output file will be: $OUTPUT_FILE" - cp -r $DIR_TO_FREEZE $DIR_NAME_ON_BOARD + if [ $DIR_TO_FREEZE != $DIR_NAME_ON_BOARD ]; then + cp -r $DIR_TO_FREEZE $DIR_NAME_ON_BOARD + fi # Use on-import=extract so our frozen filesystem is unpacked to '/' in flash on import # Use --compress to compress the frozen filesystem archive