From 008d32568ce6b52fc87c6d3b110a5faca9d84cff Mon Sep 17 00:00:00 2001 From: Rob Lauer Date: Thu, 2 Jul 2026 12:05:12 -0500 Subject: [PATCH 1/3] move odfu example from note-outboard-dfu and update readmes --- README.md | 1 + examples/outboard-dfu/README.md | 51 ++++++++++++ examples/outboard-dfu/circuit-python/code.py | 83 ++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 examples/outboard-dfu/README.md create mode 100644 examples/outboard-dfu/circuit-python/code.py diff --git a/README.md b/README.md index 85de162..8bb28f6 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ library with: - [RaspberryPi](examples/notecard-basics/rpi_example.py) - [CircuitPython](examples/notecard-basics/cpy_example.py) - [MicroPython](examples/notecard-basics/mpy_example.py) +- [Notecard Outboard Firmware Update](examples/outboard-dfu/circuit-python/code.py) ## Contributing diff --git a/examples/outboard-dfu/README.md b/examples/outboard-dfu/README.md new file mode 100644 index 0000000..ad03e65 --- /dev/null +++ b/examples/outboard-dfu/README.md @@ -0,0 +1,51 @@ +# Notecard Outboard Firmware Update Example + +This example demonstrates [Notecard Outboard Firmware Update][odfu], which lets +you update a host MCU's firmware over-the-air from Notehub with no host code +required to perform the download. The Notecard receives the new firmware image +and reprograms the host over the DFU signals on a Notecarrier F. + +The [`circuit-python/code.py`](circuit-python/code.py) sample: + +1. Configures the Notecard to enable Outboard Firmware Update for the host + (`card.dfu`, `card.aux`, and `dfu.status`). +2. Blinks the built-in LED with a recognizable pattern. This is the "payload" + you update over-the-air. + +This example targets the [Blues Swan][swan] on a +[Notecarrier F][notecarrier-f]. Cygnet does not support CircuitPython. + +## Prerequisites + +- A [Blues Swan][swan] running CircuitPython +- A [Notecarrier F][notecarrier-f] with a [Notecard][notecard] +- A [Notehub](https://notehub.io) account and project +- The `notecard` package copied into the `lib/notecard` directory of your + device (copy the contents of this repo's `notecard/` directory), as described + in the repo [README](../../README.md) + +## Running the example + +1. Set the `ProductUID` on your Notecard (via the [in-browser terminal][repl] + or the [Notecard CLI][cli]) so it reports to your Notehub project, or set + `productUID` in `code.py`. +2. Copy `circuit-python/code.py` to your Swan (CircuitPython auto-runs `code.py` + on boot). The LED will blink and the Notecard will report firmware version + `1.0.0` to Notehub. + +## Demonstrating an over-the-air update + +1. In `code.py`, change `BLINK_DELAY` (for example to `0.1` for a fast blink) + and bump `FIRMWARE_VERSION` (for example to `2.0.0`). +2. Build a firmware image and upload it to Notehub, then apply the DFU action + to the host. See the [Outboard Firmware Update guide][odfu] for the full + build-and-upload walkthrough. +3. Once the update is applied, the LED blink speed changes and Notehub reports + the new firmware version — confirming the over-the-air update succeeded. + +[odfu]: https://dev.blues.io/notehub/host-firmware-updates/notecard-outboard-firmware-update/ +[swan]: https://shop.blues.com/collections/feather-mcu/products/swan +[notecard]: https://shop.blues.io/collections/notecard +[notecarrier-f]: https://shop.blues.io/collections/notecarrier/products/notecarrier-f +[repl]: https://dev.blues.io/terminal/ +[cli]: https://dev.blues.io/tools-and-sdks/notecard-cli/ diff --git a/examples/outboard-dfu/circuit-python/code.py b/examples/outboard-dfu/circuit-python/code.py new file mode 100644 index 0000000..d6c2e74 --- /dev/null +++ b/examples/outboard-dfu/circuit-python/code.py @@ -0,0 +1,83 @@ +"""note-python CircuitPython Outboard Firmware Update example. + +This file contains a complete working sample for demonstrating Notecard +Outboard Firmware Update from CircuitPython. It configures the Notecard to +enable Outboard Firmware Update for the host, then blinks the built-in LED with +a recognizable pattern that serves as the "payload" you update over-the-air. + +To demonstrate an update: change BLINK_DELAY (and bump FIRMWARE_VERSION) below, +build a new firmware image, upload it to Notehub, and apply the DFU action to +the host. The blink speed (and reported version) will change once the update +is applied. + +This example targets the Blues Swan on a Notecarrier F. Cygnet does not support +CircuitPython. +""" +import time +import board +import digitalio +import notecard + +# The unique Product Identifier for your device. Claim one in a Notehub project. +productUID = "com.your-company.your-project" + +# The firmware version reported to Notehub via dfu.status. Bump this (and change +# BLINK_DELAY) when you build a new image to update to. +FIRMWARE_VERSION = "1.0.0" + +# Seconds the LED stays on/off. Change this to make the update visually obvious. +BLINK_DELAY = 0.5 + + +def configure_outboard_dfu(card): + """Put the Notecard online and enable Outboard Firmware Update for the host. + + Outboard Firmware Update requires the Notecard to be in "continuous" or + "periodic" mode. On a Notecarrier F the DFU signals are routed over the + Notecard's shared AUX pins, so card.dfu uses mode "aux" and card.aux is set + to "off" to free those pins for DFU. + """ + req = {"req": "hub.set"} + req["product"] = productUID + req["mode"] = "continuous" + req["sn"] = "circuitpython-notecard" + card.Transaction(req) + + # Enable Outboard Firmware Update and tell the Notecard the host MCU type. + req = {"req": "card.dfu"} + req["name"] = "stm32" + req["on"] = True + req["mode"] = "aux" + card.Transaction(req) + + # Free the AUX pins so they can be used for Outboard Firmware Update. + req = {"req": "card.aux"} + req["mode"] = "off" + card.Transaction(req) + + # Enable host DFU and report the running firmware version to Notehub. + req = {"req": "dfu.status"} + req["on"] = True + req["version"] = FIRMWARE_VERSION + card.Transaction(req) + + +def main(): + """Enable Outboard DFU, then blink the built-in LED as the update payload.""" + i2c = board.I2C() + card = notecard.OpenI2C(i2c, 0, 0, debug=True) + + configure_outboard_dfu(card) + + led = digitalio.DigitalInOut(board.LED) + led.direction = digitalio.Direction.OUTPUT + + print("Running firmware version {}. Hit CTRL-C to stop.".format(FIRMWARE_VERSION)) + while True: + led.value = True + time.sleep(BLINK_DELAY) + led.value = False + time.sleep(BLINK_DELAY) + + +main() From 37eb9f44daf85fcdf2885e691f7015a1fe72e29f Mon Sep 17 00:00:00 2001 From: Rob Lauer Date: Fri, 3 Jul 2026 08:47:30 -0500 Subject: [PATCH 2/3] switch to fluent api --- examples/outboard-dfu/circuit-python/code.py | 25 +++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/examples/outboard-dfu/circuit-python/code.py b/examples/outboard-dfu/circuit-python/code.py index d6c2e74..816f282 100644 --- a/examples/outboard-dfu/circuit-python/code.py +++ b/examples/outboard-dfu/circuit-python/code.py @@ -17,6 +17,9 @@ import board import digitalio import notecard +from notecard import card as card_helper +from notecard import dfu as dfu_helper +from notecard import hub as hub_helper # The unique Product Identifier for your device. Claim one in a Notehub project. productUID = "com.your-company.your-project" @@ -37,29 +40,17 @@ def configure_outboard_dfu(card): Notecard's shared AUX pins, so card.dfu uses mode "aux" and card.aux is set to "off" to free those pins for DFU. """ - req = {"req": "hub.set"} - req["product"] = productUID - req["mode"] = "continuous" - req["sn"] = "circuitpython-notecard" - card.Transaction(req) + hub_helper.set(card, product=productUID, mode="continuous", + sn="circuitpython-notecard") # Enable Outboard Firmware Update and tell the Notecard the host MCU type. - req = {"req": "card.dfu"} - req["name"] = "stm32" - req["on"] = True - req["mode"] = "aux" - card.Transaction(req) + card_helper.dfu(card, name="stm32", on=True, mode="aux") # Free the AUX pins so they can be used for Outboard Firmware Update. - req = {"req": "card.aux"} - req["mode"] = "off" - card.Transaction(req) + card_helper.aux(card, mode="off") # Enable host DFU and report the running firmware version to Notehub. - req = {"req": "dfu.status"} - req["on"] = True - req["version"] = FIRMWARE_VERSION - card.Transaction(req) + dfu_helper.status(card, on=True, version=FIRMWARE_VERSION) def main(): From 60390395a617b121ca3ffc8a8c3b98a193747d3a Mon Sep 17 00:00:00 2001 From: Rob Lauer Date: Fri, 3 Jul 2026 08:51:19 -0500 Subject: [PATCH 3/3] update github actions to latest versions --- .github/workflows/hil-circuitpython.yml | 2 +- .github/workflows/hil-micropython.yml | 2 +- .github/workflows/python-ci.yml | 4 ++-- .github/workflows/python-publish.yml | 4 ++-- .github/workflows/update-notecard-schema.yml | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/hil-circuitpython.yml b/.github/workflows/hil-circuitpython.yml index 3de7a97..0e538da 100644 --- a/.github/workflows/hil-circuitpython.yml +++ b/.github/workflows/hil-circuitpython.yml @@ -42,7 +42,7 @@ jobs: CIRCUITPYTHON_VERSION: ${{ matrix.CIRCUITPYTHON_VERSION}} steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v7 - name: Set Env Vars run: | diff --git a/.github/workflows/hil-micropython.yml b/.github/workflows/hil-micropython.yml index 1af3ab5..504cf8a 100644 --- a/.github/workflows/hil-micropython.yml +++ b/.github/workflows/hil-micropython.yml @@ -48,7 +48,7 @@ jobs: MPY_BOARD: ${{matrix.MPY_BOARD}} steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v7 - name: Set Environment Variables run: | diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index c27ceb0..9a61f0c 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -41,9 +41,9 @@ jobs: --header 'Content-Type: application/json' \ --header 'X-Session-Token: ${{ secrets.NOTEHUB_SESSION_TOKEN }}' \ --data '{"req":"note.add","file":"build_results.qi","body":{"result":"building"}}' - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install pipenv diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index a5e9620..6535946 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -12,11 +12,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 # setuptools-scm needs full git history - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/update-notecard-schema.yml b/.github/workflows/update-notecard-schema.yml index 461ec67..c6e3c7c 100644 --- a/.github/workflows/update-notecard-schema.yml +++ b/.github/workflows/update-notecard-schema.yml @@ -11,10 +11,10 @@ jobs: update-notecard-api: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Set up Python 3.12 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.12