Skip to content

feat(uavcan): upload firmware to skynode and flash on CAN node#27043

Draft
Phil-Engljaehringer wants to merge 2 commits intoPX4:mainfrom
Phil-Engljaehringer:CANnode_flashing
Draft

feat(uavcan): upload firmware to skynode and flash on CAN node#27043
Phil-Engljaehringer wants to merge 2 commits intoPX4:mainfrom
Phil-Engljaehringer:CANnode_flashing

Conversation

@Phil-Engljaehringer
Copy link
Copy Markdown
Contributor

Solved Problem

The existing CAN node firmware update mechanism worked, but had limitations:

-> The upload scripts only accepted a single external firmware file, preventing multi-target deployments bundling both FMU and CAN node firmware in one update.

-> After migrating new firmware to the SD card, the UAVCAN subsystem was not automatically notified to re-check and flash connected nodes — requiring a manual trigger or reboot.

Solution

UAVCAN firmware flashing from SD card (uavcan_servers, firmware_version_checker):
When new firmware files are detected on the SD card and migrated to /fs/microsd/ufw/ , checkForNewFirmware() now returns true to signal that node info should be re-invalidated, triggering the UAVCAN firmware upgrade flow immediately without requiring a reboot.

Firmware database (FW.db):
When firmware is migrated from the staging directory to /fs/microsd/ufw, it is renamed to <board_id>.bin — discarding the original filename which typically encodes version and product information (e.g. auterion-canio-v2.1.0.uavcan.bin). FW.db preserves this mapping in a simple [<board_id>.bin=<original_source_filename>] flat-file format.

--> updateFwDatabase(): after each successful firmware migration, appends or updates the corresponding entry in UAVCAN_FIRMWARE_PATH/FW.db

--> validateFwDatabase(): called at UAVCAN server init, reads FW.db and checks that the binary referenced by each entry still exists on disk. Stale entries (e.g. from a manually deleted file) are removed, keeping the database consistent.

The flightstack uploader fetches FW.db from the FMU over MAVLink FTP before uploading CAN firmware files. It compares the original source filenames in the DB against the firmware files bundled in the update tarball, if a filename is already recorded, the upload is skipped. This avoids redundant transfers.
[Flighstack-uploader PR: https://github.com/Auterion/flightstack-uploader/pull/16]

Upload scripts (remote_update_fmu.sh, upload_skynode.sh):
--ext-fw repeatable flag was added, allowing multiple CAN firmware files to be bundled into a single update-dev tarball under ufw/.

Changelog Entry

For release notes:

Feature: CAN node firmware flashing from SD card
Feature: Support bundling multiple CAN firmware files in FMU update tarballs (--ext-fw, repeatable)

Alternatives

Test coverage

Manual integration test: place a .uavcan.bin firmware file in the ufw/ staging directory, trigger an update, and verify the CAN node is flashed automatically without reboot.
Verify FW.db is created/updated correctly after migration and stale entries are pruned on next boot.
Upload script: verify upload_skynode.sh --ext-fw= --ext-fw= correctly bundles both files.

Critical bug that needs backporting?

Context

The full pipeline:

  1. upload_skynode.sh (or CI) calls remote_update_fmu.sh with one or more [--ext-fw] paths → CAN firmware binaries are bundled in ufw/ inside the update-dev tarball.
  2. The flighstack uploader extracts the tarball and copies any ufw/ firmware files to the SD card root (/fs/microsd/) via MAVLink FTP — but only if the filename is not already recorded in FW.db, avoiding redundant transfers.
  3. On the FMU, migrateFWFromRoot() detects the new files in the SD card root and moves them to UAVCAN_FIRMWARE_PATH, recording each in FW.db.
  4. checkForNewFirmware() returns true → UavcanNode invalidates all node info → the UAVCAN firmware upgrade trigger re-checks all nodes and flashes any that are outdated.

#include <px4_platform_common/px4_config.h>
#include <drivers/drv_hrt.h>

#include <uavcan/protocol/dynamic_node_id_server/centralized.hpp>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[error] clang-diagnostic-error [error]
uavcan/protocol/dynamic_node_id_server/centralized.hpp file not found

#include <px4_platform_common/px4_config.h>
#include <drivers/drv_hrt.h>

#include <uavcan/protocol/dynamic_node_id_server/centralized.hpp>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[error] clang-diagnostic-error [error]
uavcan/protocol/dynamic_node_id_server/centralized.hpp file not found

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 10, 2026

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: 984 byte (0.05 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.0%    +984  +0.0%    +984    .text
    [NEW]    +544  [NEW]    +544    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverER10UavcanNode
    [NEW]    +348  [NEW]    +348    UavcanServers::updateFwDatabase()
    [NEW]    +196  [NEW]    +196    UavcanServers::validateFwDatabase()
    [NEW]    +156  [NEW]    +156    UavcanServers::validateFwDatabase()::{lambda()#1}::operator()()
    +0.1%    +136  +0.1%    +136    [section .text]
    [NEW]     +80  [NEW]     +80    UavcanServers::checkForNewFirmware()
    +5.5%     +40  +5.5%     +40    UavcanServers::UavcanServers()
    [NEW]     +26  [NEW]     +26    FW_DB_TMP_PATH
    [NEW]     +22  [NEW]     +22    FW_DB_PATH
    +1.0%     +16  +1.0%     +16    UavcanNode::Run()
    +1.0%      +8  +1.0%      +8    UavcanServers::init()
    +0.1%      +2  +0.1%      +2    uavcan::GenericSubscriber<>::TransferForwarder::~TransferForwarder()
    +4.8%      +2  +4.8%      +2    uavcan::dynamic_node_id_server::centralized::Storage::getNodeIDForUniqueID()
   -18.2%      -4 -18.2%      -4    uavcan::BitSet<>::BitSet()
   -13.4%     -44 -13.4%     -44    UavcanServers::migrateFWFromRoot()
    [DEL]    -544  [DEL]    -544    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverE
  +0.0%    +174  [ = ]       0    .debug_abbrev
  +0.0%     +40  [ = ]       0    .debug_aranges
  +0.0%    +196  [ = ]       0    .debug_frame
  +0.0% +1.45Ki  [ = ]       0    .debug_info
  +0.0% +1.02Ki  [ = ]       0    .debug_line
   -80.0%      -4  [ = ]       0    [Unmapped]
    +0.0% +1.03Ki  [ = ]       0    [section .debug_line]
  +0.0%    +480  [ = ]       0    .debug_loclists
  +0.0%     +33  [ = ]       0    .debug_rnglists
    [DEL]      -3  [ = ]       0    [Unmapped]
    +0.0%     +36  [ = ]       0    [section .debug_rnglists]
  +0.0%    +467  [ = ]       0    .debug_str
  +0.4%      +1  [ = ]       0    .shstrtab
  +0.0%    +243  [ = ]       0    .strtab
    [NEW]     +16  [ = ]       0    FW_DB_PATH
    [NEW]     +20  [ = ]       0    FW_DB_TMP_PATH
     +21%     +13  [ = ]       0    UavcanServers::UavcanServers()
    [NEW]     +42  [ = ]       0    UavcanServers::checkForNewFirmware()
    [NEW]     +44  [ = ]       0    UavcanServers::updateFwDatabase()
    [NEW]     +41  [ = ]       0    UavcanServers::validateFwDatabase()
    [NEW]     +54  [ = ]       0    UavcanServers::validateFwDatabase()::{lambda()#1}::operator()()
    [DEL]     -92  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverE
    [NEW]    +105  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverER10UavcanNode
     +39%     +16  [ = ]       0    ___ZN7Mavlink16update_rate_multEv_veneer
   -38.1%     -16  [ = ]       0    __perf_set_elapsed_veneer
  +0.0%    +256  [ = ]       0    .symtab
    [NEW]     +32  [ = ]       0    FW_DB_PATH
    [NEW]     +32  [ = ]       0    FW_DB_TMP_PATH
    [NEW]     +48  [ = ]       0    UavcanServers::checkForNewFirmware()
    [NEW]     +48  [ = ]       0    UavcanServers::updateFwDatabase()
    [NEW]     +48  [ = ]       0    UavcanServers::validateFwDatabase()
    [NEW]     +48  [ = ]       0    UavcanServers::validateFwDatabase()::{lambda()#1}::operator()()
    [DEL]     -80  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverE
    [NEW]     +80  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverER10UavcanNode
     +67%     +32  [ = ]       0    ___ZN7Mavlink16update_rate_multEv_veneer
   -25.0%     -16  [ = ]       0    __arp_ipin_veneer
     +50%     +16  [ = ]       0    __nxsem_restore_baseprio_irq_veneer
   -40.0%     -32  [ = ]       0    __perf_set_elapsed_veneer
     +33%     +16  [ = ]       0    __up_clean_dcache_veneer
   -33.3%     -16  [ = ]       0    __up_flush_dcache_veneer
  -9.7%    -984  [ = ]       0    [Unmapped]
  +0.0% +4.32Ki  +0.0%    +984    TOTAL

px4_fmu-v6x [Total VM Diff: 984 byte (0.05 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.1%    +984  +0.1%    +984    .text
    [NEW]    +544  [NEW]    +544    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverER10UavcanNode
    [NEW]    +348  [NEW]    +348    UavcanServers::updateFwDatabase()
    [NEW]    +196  [NEW]    +196    UavcanServers::validateFwDatabase()
    [NEW]    +156  [NEW]    +156    UavcanServers::validateFwDatabase()::{lambda()#1}::operator()()
    +0.1%    +136  +0.1%    +136    [section .text]
    [NEW]     +80  [NEW]     +80    UavcanServers::checkForNewFirmware()
    +5.5%     +40  +5.5%     +40    UavcanServers::UavcanServers()
    [NEW]     +26  [NEW]     +26    FW_DB_TMP_PATH
    [NEW]     +22  [NEW]     +22    FW_DB_PATH
    +1.0%     +16  +1.0%     +16    UavcanNode::Run()
    +1.0%      +8  +1.0%      +8    UavcanServers::init()
    +0.1%      +2  +0.1%      +2    uavcan::GenericSubscriber<>::TransferForwarder::~TransferForwarder()
    +4.8%      +2  +4.8%      +2    uavcan::dynamic_node_id_server::centralized::Storage::getNodeIDForUniqueID()
   -18.2%      -4 -18.2%      -4    uavcan::BitSet<>::BitSet()
   -13.4%     -44 -13.4%     -44    UavcanServers::migrateFWFromRoot()
    [DEL]    -544  [DEL]    -544    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverE
  +0.0%    +174  [ = ]       0    .debug_abbrev
  +0.0%     +40  [ = ]       0    .debug_aranges
  +0.0%    +196  [ = ]       0    .debug_frame
  +0.0% +1.46Ki  [ = ]       0    .debug_info
  +0.0% +1.02Ki  [ = ]       0    .debug_line
   -57.1%      -4  [ = ]       0    [Unmapped]
    +0.0% +1.03Ki  [ = ]       0    [section .debug_line]
  +0.0%    +469  [ = ]       0    .debug_loclists
  +0.0%     +36  [ = ]       0    .debug_rnglists
  +0.0%    +467  [ = ]       0    .debug_str
  +0.4%      +1  [ = ]       0    .shstrtab
  +0.0%    +243  [ = ]       0    .strtab
    [NEW]     +16  [ = ]       0    FW_DB_PATH
    [NEW]     +20  [ = ]       0    FW_DB_TMP_PATH
     +21%     +13  [ = ]       0    UavcanServers::UavcanServers()
    [NEW]     +42  [ = ]       0    UavcanServers::checkForNewFirmware()
    [NEW]     +44  [ = ]       0    UavcanServers::updateFwDatabase()
    [NEW]     +41  [ = ]       0    UavcanServers::validateFwDatabase()
    [NEW]     +54  [ = ]       0    UavcanServers::validateFwDatabase()::{lambda()#1}::operator()()
    [DEL]     -92  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverE
    [NEW]    +105  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverER10UavcanNode
  +0.0%    +256  [ = ]       0    .symtab
    [NEW]     +32  [ = ]       0    FW_DB_PATH
    [NEW]     +32  [ = ]       0    FW_DB_TMP_PATH
    [NEW]     +48  [ = ]       0    UavcanServers::checkForNewFirmware()
    [NEW]     +48  [ = ]       0    UavcanServers::updateFwDatabase()
    [NEW]     +48  [ = ]       0    UavcanServers::validateFwDatabase()
    [NEW]     +48  [ = ]       0    UavcanServers::validateFwDatabase()::{lambda()#1}::operator()()
    [DEL]     -80  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverE
    [NEW]     +80  [ = ]       0    _GLOBAL__sub_I__ZN13UavcanServersC2ERN6uavcan5INodeERNS0_17NodeInfoRetrieverER10UavcanNode
 -15.5%    -984  [ = ]       0    [Unmapped]
  +0.0% +4.32Ki  +0.0%    +984    TOTAL

Updated: 2026-04-10T11:25:34

@Phil-Engljaehringer Phil-Engljaehringer changed the title Ca nnode flashing feat(uavcan): upload firmware to skynode and flash on CAN node Apr 10, 2026
@alexcekay alexcekay marked this pull request as draft April 10, 2026 14:47
@hamishwillee
Copy link
Copy Markdown
Contributor

When/if this goes in, can we have docs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants