• Goose on weekend side projects

    Mar 10

    I spent some of my limited spare time between childcare duties working on a project: creating llvm-binary-releases using Goose. The goal was straightforward: take an LLVM release package, extract the specific clang tool binary I needed, and produce a new platform/OS-specific release. I planned to set up a hermit package for clang-format because finding pre-built clang binaries is surprisingly difficult. While native package managers are available, they tend to complicate dependency management in build systems, so I decided to solve the problem myself.

    Starting with Goose was simple. I asked it to generate a script to download a tarball from a given URL and extract the required binary. It produced about a 100-line bash script that worked well for a task confined to a single source file. However, when I moved on to more complex tasks—such as incorporating bash functions—issues began to appear. Instead of returning proper error codes, Goose used echo to return strings, which caused problems when other parts of the program attempted to use these values. It seemed that Goose had difficulty adhering to the API contracts it had designed. I wondered how other AI tools would handle this situation, but I managed to resolve the problem by asking Goose to rewrite the code in Python. The Python version maintained the API contracts properly.

    Next, I requested that Goose create a GitHub workflow to handle the output from the Python script. This task proved challenging. After several rounds of error reports and fixes, Goose made some adjustments, but the changes were sometimes suboptimal, and it often failed to interpret the error messages correctly. In the end, I fixed the issue myself—it turned out to be a one-line problem after I reviewed the documentation.

    Overall, the project took about three hours over the weekend, even with interruptions from childcare. Although the final product isn’t perfect, I’m satisfied with the result considering the time and effort involved. In previous experiences, similar projects might have required 3-4 hours of focused work, and I might have abandoned the project when faced with obstacles. For me, using LLMs has proven valuable in jumpstarting and completing these side projects, automating tedious tasks and even producing solid documentation.

    Now, I’ll copy this draft into another LLM for a final polish.

    PS: I copied this draft into the chatgpt for polishing, and did NOT add the “another LLM” part, just the line of copying this into LLM. And chatgpt recognized Goose as a different LLM and differentiated itself. Anyways just thought it was an interesting detail.

  • Dev notes from Feb 2025

    Feb 28

    Git performance tracing

    I noticed that some git commands, especially git status, were taking a long time to execute. Since it’s tied to my command prompt, this also slowed down my prompt. By using GIT_TRACE=1, I could see what git was doing. The issue was that the repository had too many submodules, causing git status to run on each one, which took time.

    $ GIT_TRACE=1 git status
    14:40:25.548035 git.c:476               trace: built-in: git status
    14:40:25.550695 run-command.c:667       trace: run_command: cd themes/hello-friend-ng; unset GIT_PREFIX; GIT_DIR=.git git status --porcelain=2
    14:40:25.550763 run-command.c:759       trace: start_command: /opt/homebrew/opt/git/libexec/git-core/git status --porcelain=2
    14:40:25.557470 git.c:476               trace: built-in: git status --porcelain=2
    On branch main
    Your branch is up to date with 'origin/main'.
    
    14:40:25.567125 run-command.c:667       trace: run_command: GIT_INDEX_FILE=.git/index git submodule summary --cached --for-status --summary-limit -1 HEAD
    14:40:25.567200 run-command.c:759       trace: start_command: /opt/homebrew/opt/git/libexec/git-core/git submodule summary --cached --for-status --summary-limit -1 HEAD
    14:40:25.573148 git.c:769               trace: exec: git-submodule summary --cached --for-status --summary-limit -1 HEAD
    ...
    

    Fish shell performance tracing

    Similarly, the fish shell has a profiling feature:

           -p or --profile=PROFILE_FILE
                  when fish exits, output timing information on all executed commands to the specified file.  This excludes time spent starting up and reading the configuration.
    
           --profile-startup=PROFILE_FILE
                  Will write timing for fish startup to specified file.
    

    The profile file looks like this:

    Time    Sum     Command
    115     10404135        > __fish_print_help fish
    435     437     -> source /opt/homebrew/Cellar/fish/4.0.0/share/fish/functions/__fish_print_help.fish
    2       2       --> function __fish_print_help --description "Print help message for the specified fish function or builtin" --argument-names item error_message...
    2       2       -> switch $item...
    1       21      -> if not test -e "$__fish_data_dir/man/man1/$item.1" -o -e "$__fish_data_dir/man/man1/$item.1.gz"...
    20      20      --> not test -e "$__fish_data_dir/man/man1/$item.1" -o -e "$__fish_data_dir/man/man1/$item.1.gz"
    2       2       -> set -l help
    1       1       -> set -l format
    0       0       -> set -l cols
    1       72      -> if test -n "$COLUMNS"...
    

    Avante.nvim

    I started using Avante.nvim with Copilot, and the results were surprisingly good. I avoided using the RAG service because it spins up a local Docker container, which drains resources.

    This setup was nearly perfect for working through rustlings problems. The suggested answers were correct 9 out of 10 times. Paired with rust-lsp and Clippy, I completed the rustlings problems in a weekend.

    This experience is very similar to using Cursor AI. Overall, I’m very pleased with the setup.

  • Trigger DFU via USB on nRF52840 Dongle

    Apr 6

    The NRF5280 dongle is a great embedded development platform, especially for hobbyist project or quick prototyping. There is also a very similar product from Make Diary with slightly different PCBA layout. It is generally a decent utility tool for BLE related work. These functionality can all be done out of box with the nrf desktop tool without doing any programming.

    There are plenty of blog post on this already that covers introductory programming with the nordic part. But one interesting feature that I run into while working with this dongle was how to trigger DFU programming quickly.

    The nRF52840 dongle support DFU programming via it’s bootloader, where one can use nrfutil program the hardware. The dongle can be reboot into DFU mode via the side reset button. NRF52840 dongle pcb black white The dongle’s red LED goes into this slow glow effect, and the usb device is re-enumerated as ID 1915:521f Nordic Semiconductor ASA Open DFU Bootloader All this mean the dongle is ready for DFU programming now!

    Now you can a command like to issue the programming:

    $ nrfutil device program --firmware dfu_package.zip --traits nordicDfu 
    

    In addition you can create the dfu_package.zip file with:

    $ nrfutil nrf5sdk-tools pkg generate --hw-version 52 --sd-req=0x00 --application application.zip --application-version 1 dfu_package.zip
    

    This flow works great for 90% of development flows. You will need to be careful not to accidentally erase the UICR region, or else the bootloader will no longer boot into DFU. I learned this the hardware. If you do end up get the hardware into a state where bootloader don’t enter DFU mode, you will need to get a jtag programmer and connect the jtag pins to the dongle to programming the old fashion way.

    But these are not what I want to focus on here, I want to focus on how to trigger the DFU mode to begin with.

    I am a lazy person, the idea of having to manually press a button on the hardware to start FW programming is just very tiresome to me. Not to mention doing this tens or hundreds times a day.

    Triggering DFU mode via USB

    Essentially on the nRF52840 dongle, just setting the reset pins cause the MCU to reboot into bootloader and bootloader code detect that reset pin are set and enter into DFU mode.

    nrf_gpio_cfg_output(BSP_SELF_PINRESET_PIN);
    nrf_gpio_pin_clear(BSP_SELF_PINRESET_PIN);
    

    The hard part is somehow getting a signal from the host machine to the target device to reset the pin.

    But luckily this is all reasonably documented! In addition there is a official standard for this, Nordic implementation isn’t fully compliant but it’s close enough. The nrfsdk provide this helpful component anyone can simply include in their FW, some configuration with sdk_config.h is required, as with everything in nrfsdk.

    If setup correct it should show up as new vendor interface similar to.

    DEVICE ID 05ac:0256 on Bus 000 Address 007 =================
    ...
      CONFIGURATION 1: 100 mA ==================================
      ...
        INTERFACE 0: Vendor Specific ===========================
         bLength            :    0x9 (9 bytes)
         bDescriptorType    :    0x4 Interface
         bInterfaceNumber   :    0x0
         bAlternateSetting  :    0x0
         bNumEndpoints      :    0x0
         bInterfaceClass    :   0xff Vendor Specific
         bInterfaceSubClass :    0x1
         bInterfaceProtocol :    0x1
         iInterface         :    0x0
    

    Therefore by setting up a control transfer to the usb device like below, it should in theory trigger the dongle to reboot in DFU mode.

    libusb1_backend = usb.backend.libusb1.get_backend(
        find_library=lambda x: "/opt/homebrew/lib/libusb-1.0.0.dylib"
    )
    
    dev = usb.core.find(idVendor=0x1915, idProduct=0x521f, backend=libusb1_backend)
    
    bmRequestType = usb.util.build_request_type(
        usb.util.CTRL_OUT, usb.util.CTRL_TYPE_CLASS, usb.util.CTRL_RECIPIENT_DEVICE
    )
    
    try:
        dev.ctrl_transfer(bmRequestType, 0x00, timeout=1)
    except usb.core.USBError:
        pass
    

    If you noticed the dongle rebooting but not entering DFU, it’s likely resetting or crashing somewhere before the reset pin is set. Just check the code path to dfu_trigger_evt_handler() in components/libraries/bootloader/dfu/nrf_dfu_trigger_usb.c function free of other issue. For example I had used the log backend, but didn’t set it up correctly and it was causing the dongle to crash on NRF_LOG_FINAL_FLUSH() right before the reset pin is set.