Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
2d7e92b
dronecan/h7: reduce FDCAN SJW from 8 to 3
daijoubu May 23, 2026
be17792
dronecan/h7: extend PLL2 block to cover USE_DRONECAN, fix PLL2Q for F…
daijoubu May 28, 2026
98d76dc
dronecan/h7: use system PLL2 clock for FDCAN; add KAKUTEH7WING CAN su…
daijoubu May 29, 2026
b165d39
dronecan/h7: remove BusOff/ErrorPassive LOG_DEBUG spam from GetProtoc…
daijoubu May 29, 2026
d3cc8e4
dronecan/h7: fix FDCAN timing clock source and restore SJW=3
daijoubu May 29, 2026
f294f3f
dronecan/h7: remove LOG_DEBUG spam and fix PLL2 VCI range
daijoubu May 29, 2026
a1f4c99
dronecan: remove LOG_DEBUG spam from H7/F7 drivers and dronecan.c
daijoubu May 29, 2026
c21c7a2
dronecan/h7: set SJW=1 for ISO 11898-1 conformance and verified 1Mbps…
daijoubu May 29, 2026
2ea077e
dronecan: set SJW=1 in F7 driver; clean up stale comments in H7/F7 dr…
daijoubu May 29, 2026
5e07d1d
dronecan: gate GPS fix handlers on GPS_DRONECAN provider
daijoubu May 29, 2026
173b909
dronecan/f7: restore SJW=3 (hardware 4 tq); document register encodin…
daijoubu May 29, 2026
dbe3ace
dronecan: add STATE_DRONECAN_FAILED; set on CAN peripheral init failure
daijoubu May 29, 2026
5f55bec
dronecan: add FAILED to CLI state name array
daijoubu May 29, 2026
a814040
dronecan/h7: flush TX FIFO before clearing CCCR.INIT on bus-off recovery
daijoubu May 29, 2026
3865228
dronecan: disable AutoRetransmission on H7/F7 to prevent TX FIFO stall
daijoubu May 29, 2026
57f8536
dronecan/f7: check canardSTM32ComputeTimings return value
daijoubu May 29, 2026
8fa8233
dronecan: increase bus-off recovery delay from 1ms to 20ms
daijoubu May 29, 2026
af897d8
dronecan: move GPS provider guard to dispatch layer in dronecan.c
daijoubu May 29, 2026
5390248
dronecan: rate-limit protocol status check to 1Hz in NORMAL state
daijoubu May 29, 2026
da72c03
dronecan/f7: implement RecoverFromBusOff to clear sticky ESR.BOFF flag
daijoubu May 29, 2026
f1fa4c7
dronecan/h7: fix static_assert comment for PLL2M HSE divisibility check
daijoubu May 29, 2026
ef0fe3a
dronecan: rate-limit protocol status check in BUS_OFF state to 20ms c…
daijoubu May 29, 2026
cca23b3
dronecan/f7: revert RecoverFromBusOff HAL_CAN_Stop/Start — causes lockup
daijoubu May 29, 2026
5a47b87
dronecan: only log CAN status when BusOff or ErrorPassive is non-zero
daijoubu May 29, 2026
fe8d859
dronecan/f7: remove TX failure log spam
daijoubu May 29, 2026
69f6570
dronecan/gps: guard GNSSAuxiliary DOP fields against NaN and overflow
daijoubu May 29, 2026
e6e2c7c
dronecan/gps: remove lastVDOP — no vdop field in gpsSol and not EPV-c…
daijoubu May 29, 2026
3aa922f
dronecan: add STATE_DRONECAN_COUNT sentinel and assert dronecanStateN…
daijoubu May 29, 2026
d2be273
dronecan: handle STATE_DRONECAN_COUNT in switch to satisfy -Werror=sw…
daijoubu May 29, 2026
9747c8e
dronecan: fix ARRAYLEN macro name in dronecanStateNames assert
daijoubu May 30, 2026
97c939b
dronecan: clamp state index before dronecanStateNames lookup in CLI
daijoubu May 30, 2026
03b1614
dronecan/f7: document why RecoverFromBusOff is a no-op
daijoubu May 31, 2026
f916fc1
dronecan: fix sign-compare in CLI state name clamp
daijoubu May 31, 2026
48c1d7c
dronecan: fix %lu format specifier for uint32_t fields
daijoubu May 31, 2026
60b8f45
fix: use PRIu32 for uint32_t format specifiers in dronecan LOG_DEBUG
daijoubu May 31, 2026
9c1b352
fix(dronecan): send elapsed ms since last seen in MSP2_INAV_DRONECAN_…
daijoubu Jun 1, 2026
1d53383
fix(dronecan): send elapsed ms since last seen in MSP2_INAV_DRONECAN_…
daijoubu Jun 1, 2026
f52241c
dronecan/h7: TX queue mode, depth 3, ISR pump, NVIC masking
daijoubu Jun 12, 2026
8d34897
dronecan: F7 ISR-driven TX, NVIC masking, TEC/REC/LEC counters, cliDr…
daijoubu Jun 12, 2026
da08d0d
dronecan: fix H7 ISR pump, ECR TEC field, narrow TX mask window
daijoubu Jun 12, 2026
3c889c0
dronecan: reorder driver files to public API before private helpers
daijoubu Jun 12, 2026
b24900d
dronecan: address code review findings
daijoubu Jun 12, 2026
5da300c
dronecan: address third code review findings
daijoubu Jun 12, 2026
484e46f
dronecan: address fourth code review findings
daijoubu Jun 12, 2026
9c038c4
dronecan: address fifth code review findings
daijoubu Jun 12, 2026
85183b3
dronecan: address sixth code review findings
daijoubu Jun 12, 2026
9dd06ae
dronecan: fix H7 FDCAN filter type to accept all extended IDs
daijoubu Jun 12, 2026
e0b5f82
Enforce FIFO ordering to prevent spin errors in multi frame messages …
daijoubu Jun 13, 2026
cf3f98b
Allow FDCAN targets to use 18 quanta per bit solution as well at 1 MBps.
daijoubu Jun 13, 2026
6079e86
test(dronecan): add bxCAN timing unit tests for max_quanta=18 change
daijoubu Jun 13, 2026
c1b52a5
dronecan: reduce flash waste, improve GPIO config, add diagnostics
daijoubu Jun 15, 2026
08c4768
dronecan: guard STM32 CAN drivers with USE_DRONECAN
daijoubu Jun 15, 2026
37ec2ba
Set max_quanta_per_bit back to 10 for H7 targets as well
daijoubu Jun 16, 2026
67c787d
test(dronecan): add GetNodeInfo, SoftwareVersion, HardwareVersion, RT…
daijoubu Jun 1, 2026
4b8ae43
test(dronecan): add node table and shouldAcceptTransfer unit tests
daijoubu Jun 1, 2026
265d965
test(dronecan): fix fixture for Phase 2 — pass real CanardInstance to…
daijoubu Jun 2, 2026
f408fdf
feat(dronecan): Phase 1-3 — extend node struct, request and accept Ge…
daijoubu Jun 2, 2026
b820b95
test(dronecan): add GAP-S2 dispatch test for GetNodeInfo response (red)
daijoubu Jun 2, 2026
0b73657
refactor(dronecan): standardise on nodeID for local variables
daijoubu Jun 2, 2026
df53604
feat(dronecan): Phase 5 — extend MSP2_INAV_DRONECAN_NODE_INFO to 71 b…
daijoubu Jun 2, 2026
af02bfb
docs(dronecan): update msp_messages.json for 71-byte NODE_INFO response
daijoubu Jun 2, 2026
cb81864
fix(dronecan): commit missing dronecan.h struct extension
daijoubu Jun 2, 2026
beac9c2
fix(dronecan): address code review findings
daijoubu Jun 2, 2026
9a26e3d
chore(dronecan): strip trailing whitespace from dronecan.c/.h, tidy c…
daijoubu Jun 2, 2026
d002ffc
docs(msp): correct last_seen_ms field semantics in DRONECAN messages
daijoubu Jun 2, 2026
8d367b0
fix(dronecan): use per-node transfer_id for GetNodeInfo requests
daijoubu Jun 2, 2026
98fc56b
fix(msp): replace magic 71 with documented field sum in NODE_INFO guard
daijoubu Jun 2, 2026
72289e0
fix(dronecan): limit bus-off recovery to 50 attempts before entering …
daijoubu Jun 2, 2026
76d8ac7
refactor(dronecan): add dronecanGetNodeByID() and eliminate duplicate…
daijoubu Jun 2, 2026
8940597
refactor(msp): replace inline field sum with MSP2_DRONECAN_NODE_INFO_…
daijoubu Jun 2, 2026
2d72d1e
refactor(msp): move MSP2_DRONECAN_NODE_INFO_SIZE to msp_protocol_v2_i…
daijoubu Jun 2, 2026
b0837e6
fix(dronecan): use memset for new-node slot init; fix stale test comment
daijoubu Jun 2, 2026
b8f036a
fix(dronecan): extend node name to 80 bytes; log table overflow
daijoubu Jun 3, 2026
e91fb80
fixup: remove orphaned TX loop and duplicate process1HzTasks from reb…
daijoubu Jun 26, 2026
5f53f6a
fix(dronecan): restore shouldAcceptTransfer response case; expose tes…
daijoubu Jun 26, 2026
2d73d75
fix(dronecan): address code review findings on getnodeinfo branch
daijoubu Jul 1, 2026
42dabdc
feat(dronecan): shared async slot for on-demand GetNodeInfo and GetSet
daijoubu Jun 3, 2026
caf93e4
feat(dronecan): ExecuteOpcode, RestartNode, GetSet write fixes, node …
daijoubu Jun 3, 2026
8e7fb52
fix(dronecan): code review cleanup — types, comments, indentation
daijoubu Jun 3, 2026
481140f
fix(dronecan): code review pass 2 — null termination, named constant,…
daijoubu Jun 3, 2026
15355b9
fix(dronecan): code review pass 3 — transfer_id guard, buf_ptr commen…
daijoubu Jun 3, 2026
b5bfa9f
fix(dronecan): code review pass 5 — guard, comment, modeLabel
daijoubu Jun 3, 2026
639febe
feat(dronecan): expose param GetSet min/max range through MSP
daijoubu Jun 3, 2026
7489f51
fix(dronecan): code review pass — explicit NumericValue mapping, stal…
daijoubu Jun 4, 2026
61b3d3a
fix(dronecan): correct struct field order and comment in dronecanPara…
daijoubu Jun 4, 2026
ab87dda
docs(msp): update DroneCAN MSP docs for async request/result API
daijoubu Jun 7, 2026
9fc518f
docs(msp): update DroneCAN entries in msp_messages.json for async API
daijoubu Jun 7, 2026
98583d5
docs(msp): regenerate README.md from msp_messages.json
daijoubu Jun 7, 2026
62c9d4a
fix(dronecan): mask TX ISR around dronecanAsyncRequest canardRequestO…
daijoubu Jun 30, 2026
8eab76b
test(dronecan): update GAP-S2 test for async slot architecture
daijoubu Jul 1, 2026
e9bc832
fix(dronecan): address code review findings on param-getset branch
daijoubu Jul 1, 2026
41b1d5e
test(dronecan): add unit tests for PARAM_GETSET, EXECUTE_OPCODE, REST…
daijoubu Jul 1, 2026
1f59089
fix(dronecan): address code review findings from param-getset pass 2
daijoubu Jul 1, 2026
219e467
fix(dronecan): address code review findings from param-getset pass 3
daijoubu Jul 2, 2026
b2f2507
fix(dronecan): address code review findings from param-getset pass 4
daijoubu Jul 2, 2026
35bd455
Initial DNA server code. Handles dynamicNodeAllocation broadcasts an…
daijoubu Jun 6, 2026
56ffa42
Added the ability to dynamically assign unique node IDs.
daijoubu Jun 6, 2026
e89671e
Removed dead fixed assignment.
daijoubu Jun 6, 2026
2a029f5
fix(dronecan): DNA server code review fixes
daijoubu Jun 6, 2026
b29d8c1
refactor(dronecan): extract DNA server into separate files
daijoubu Jun 6, 2026
e5f87bd
fix(dronecan): guard against invalid node ID when allocation table is…
daijoubu Jun 6, 2026
ff945f2
fix(dronecan): upgrade decode/operational errors from LOG_DEBUG to LO…
daijoubu Jun 6, 2026
ea652b4
feat(dronecan): add dronecan_use_dna_server setting with enable guard
daijoubu Jun 6, 2026
b98d82c
Moved node allocation table to Parameter Groups to handle presistent …
daijoubu Jun 6, 2026
7793157
Persist node allocations to EEPROM when disarmed.
daijoubu Jun 6, 2026
093006a
test(dronecan): add DNA server unit tests (DNA-1 through DNA-9)
daijoubu Jun 6, 2026
91f9e79
Honor the requested Node ID if it is available
daijoubu Jun 6, 2026
ff1122d
feat(dronecan): honour peripheral's preferred node ID in DNA allocation
daijoubu Jun 6, 2026
1765bb0
docs(dronecan): update docs to reflect DNA server implementation
daijoubu Jun 6, 2026
cf55835
docs(dronecan): clarify DNA requires peripheral node ID = 0 (anonymous)
daijoubu Jun 6, 2026
88a83d1
DNA is supposed to start from Node ID 125 and work down. 126 and 127…
daijoubu Jun 6, 2026
9ab77e9
Ensure we don't assign or re use a node assignment that is in use on …
daijoubu Jun 6, 2026
968511d
fix(dronecan): correct DNA node ID allocation per UAVCAN spec
daijoubu Jun 6, 2026
411b8c9
fix(dronecan): code review cleanup and expanded unit tests
daijoubu Jun 6, 2026
f97fd75
fix(dronecan): implement spec-correct preferred node ID search order
daijoubu Jun 6, 2026
6d5d45c
fix(dronecan): use explicit constant in Stage 3 detection
daijoubu Jun 6, 2026
2a4fe0d
fix(dronecan): guard DNA server with USE_DRONECAN like dronecan.c
daijoubu Jun 6, 2026
d5da320
refactor(dronecan): clean up DNA server logging
daijoubu Jun 7, 2026
9dfeb09
fix(dronecan): restore busOffCount, dronecanGetBusOffCount, dronecanG…
daijoubu Jun 30, 2026
9fef0f5
fix(dronecan): restore NVIC masking lost during rebase conflict resol…
daijoubu Jun 30, 2026
b4e0a86
fix(dronecan): restore dronecanMaskTxISR/UnmaskTxISR definitions lost…
daijoubu Jun 30, 2026
c487877
test(dronecan): fix application unittest link on dna-server branch
daijoubu Jul 1, 2026
92fc694
fix(dronecan): restore txErrCount/bc_res declarations dropped during …
daijoubu Jul 5, 2026
bf4c7f1
fix(dronecan): restore vendor_specific_status_code masking and memory…
daijoubu Jul 5, 2026
e37f00d
test(dronecan): make DNA-8 actually test the FC-node-ID skip guard
daijoubu Jul 5, 2026
9a48d9b
feat(dronecan): accept single-frame full-UID stage-1 delivery for DNA…
daijoubu Jul 5, 2026
d427fad
test(dronecan): add differential regression test for the 4-byte stage…
daijoubu Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 68 additions & 11 deletions docs/DroneCAN-Driver.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# DroneCAN Driver Documentation

**Last Updated:** 2026-02-16
**Last Updated:** 2026-06-06
**Status:** Complete
**Branch:** feature-dronecan-sitl
**Branch:** feature/dronecan-dna-server

---

Expand Down Expand Up @@ -115,6 +115,7 @@ CAN Bus ──[CAN interrupt]──> canardSTM32Receive() ──> shouldAcceptTr
| `UAVCAN_EQUIPMENT_GNSS_RTCMSTREAM_ID` | `handle_GNSSRCTMStream()` | RTCM Stream | Broadcast | Variable | RTK correction data |
| `UAVCAN_EQUIPMENT_POWER_BATTERYINFO_ID` | `handle_BatteryInfo()` | Battery Info | Broadcast | 1-10 Hz | Integrated with battery system |
| `UAVCAN_PROTOCOL_GETNODEINFO_ID` | `handle_GetNodeInfo()` | GetNodeInfo | Request/Response | On demand | Responds with FC firmware version |
| `UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID` | `dronecanDnaHandleAllocation()` | Dynamic Node Allocation | Broadcast | On connect | Assigns node IDs to anonymous peripherals via 3-stage UID handshake |

### Handler-Based Architecture

Expand Down Expand Up @@ -282,15 +283,9 @@ The driver uses the following settings from `settings.yaml`:

| Setting | Description | Type | Valid Range | Default |
|---|---|---|---|---|
| `dronecan_mode` | Enable/disable DroneCAN | bool | 0/1 | 0 |
| `dronecan_node_id` | This FC's CAN node ID | uint8 | 1-125 | 0 |
| `dronecan_baudrate` | CAN bus bitrate | enum | 0-3 | 2 (500kbps) |

**Bitrate Mapping:**
- 0 = 125 kbps
- 1 = 250 kbps
- 2 = 500 kbps (default)
- 3 = 1000 kbps
| `dronecan_node_id` | This FC's CAN node ID | uint8 | 1–127 | 1 |
| `dronecan_bitrate_kbps` | CAN bus bitrate in kbps | enum | 125, 250, 500, 1000 | 1000 |
| `dronecan_use_dna_server` | Enable automatic node ID assignment | bool | ON/OFF | ON |

#### Accessing Configuration

Expand Down Expand Up @@ -549,6 +544,67 @@ Total: 1 + nodeCount × 30 bytes.

---

## DNA Server

The DNA server (`dronecan_dna_server.c`) implements a non-redundant (single-master) Dynamic Node Allocation allocator per UAVCAN Specification §6.4.9. It is guarded by `dronecanConfig()->dronecanUseDNAServer` and can be disabled via `set dronecan_use_dna_server = OFF`.

### Three-stage handshake

A peripheral that has no node ID broadcasts anonymous Allocation messages carrying up to 6 bytes of its 16-byte hardware unique ID per stage:

| Stage | `first_part_of_unique_id` | Bytes sent |
|-------|--------------------------|------------|
| 1 | `true` | bytes 0–5 (+ optional preferred node ID) |
| 2 | `false` | bytes 6–11 |
| 3 | `false` | bytes 12–15 |

Each stage must arrive within 500 ms (`FOLLOWUP_TIMEOUT_MS`) of the previous one, or the accumulator resets and the peripheral must start over from Stage 1.

A transport capable of a larger single frame (e.g. CAN-FD) may instead send the full 16-byte UID in one message with `first_part_of_unique_id = true` and `unique_id.len = 16`; the server accepts this as Stage 1 and completes the allocation immediately without waiting for Stage 2/3.

After the full 16-byte UID has been assembled — whether over three stages or in one single-frame message — the server calls `dnaLookupOrAssignNode()`.

### Node ID assignment

`dnaLookupOrAssignNode()` applies the following priority order:

1. **Existing entry** — if the UID is already in the allocation table and the stored node ID isn't currently claimed by another live node, return the previously assigned node ID. If it *is* claimed by someone else, fall through to reassignment below.
2. **Preferred ID** — if the peripheral included a preferred node ID in Stage 1 (range 1–125; 126–127 are reserved for network maintenance tools), search upward from that ID first, then downward from `preferred - 1`, assigning the first available ID found either way.
3. **Top-down fallback** — if there's no preference, or the preferred-ID search found nothing free, scan from node ID 125 downward and assign the first available ID.

The FC's own node ID is never assigned to a peripheral. If all 32 table slots are full and no existing entry matches, the allocation fails silently.

### Allocation table persistence

The allocation table (`dnaServerData_t`) is stored in a Parameter Group (`PG_DRONECAN_DNA_SERVER`) backed by flash. A new entry calls `saveConfig()` (deferred, silent, only while disarmed) so the same peripheral receives the same node ID on every boot without re-negotiating.

The table holds up to `DRONECAN_MAX_NODES` (32) entries, each storing:

```c
typedef struct {
uint8_t uniqueId[16]; // Full 16-byte hardware UID
uint8_t nodeId; // Assigned CAN node ID (1-127)
} dnaAllocationEntry_t;
```

### Entry point

`dronecanDnaHandleAllocation()` is called from `onTransferReceived()` when a `UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID` broadcast arrives, but only while `dronecanUseDNAServer` is true:

```c
case UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID:
if (dronecanConfig()->dronecanUseDNAServer) {
dronecanDnaHandleAllocation(ins, transfer);
}
break;
```

### Unit tests

Sixteen tests in `src/test/unit/dronecan_dna_server_unittest.cc` cover the full handshake, UID re-use, table-full rejection, non-broadcast source rejection, stage ordering, timeout reset, FC node ID exclusion, preferred node ID honouring, top-down sequential assignment, reserved-range and taken-preferred-ID fallback, live-network reassignment of a stored ID, single-frame (e.g. CAN-FD) full-UID delivery, and rejection of a malformed 4-byte stage-1 message without poisoning a legitimate follow-up handshake (DNA-1 through DNA-16).

---

## Adding New Message Types

To add support for a new DroneCAN message type:
Expand Down Expand Up @@ -909,6 +965,7 @@ void broadcastNodeStatus(void) {

| Date | Version | Changes |
|---|---|---|
| 2026-06-06 | 1.3 | Added DNA server section; corrected settings table (`dronecan_bitrate_kbps`, `dronecan_use_dna_server`); added DNA allocation to message type table |
| 2026-04-30 | 1.2 | Added node table (`dronecanNodeInfo_t`), accessor functions, CLI status output, and MSP commands (0x2042/0x2043) |
| 2026-02-18 | 1.1 | Added error recovery, graceful disable behavior, and safe initialization documentation |
| 2026-02-16 | 1.0 | Initial version - handler-based architecture documentation |
Expand Down
63 changes: 51 additions & 12 deletions docs/DroneCAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DroneCAN (formerly UAVCAN v0) is a lightweight protocol designed for reliable co
| Battery Current | Supported | Current sensing from DroneCAN battery monitors |
| Parameter Get/Set | Planned | Remote parameter configuration |
| ESC Control | Planned | Motor control via DroneCAN ESCs |
| Dynamic Node Assignment | Planned | Manage node IDs dynamically to minimize first time configuration |
| Dynamic Node Assignment | Supported | Automatically assign CAN node IDs to plug-and-play DroneCAN peripherals |

## Supported Hardware

Expand All @@ -38,10 +38,11 @@ save

| Setting | Values | Default | Description |
|---------|--------|---------|-------------|
| `dronecan_node_id` | 1-127 | 10 | CAN node ID for the flight controller |
| `dronecan_bitrate` | 125KBPS, 250KBPS, 500KBPS, 1000KBPS | 1000KBPS | CAN bus bitrate |
| `dronecan_node_id` | 1-127 | 1 | CAN node ID for the flight controller |
| `dronecan_bitrate_kbps` | 125, 250, 500, 1000 | 1000 | CAN bus bitrate in kbps |
| `dronecan_use_dna_server` | ON, OFF | ON | Enable automatic node ID assignment for plug-and-play peripherals |

All peripherals need to have the node ID and the bitrate set manually through the dronecan_gui for now. You can use your flight controller as a CAN interface by loading an Ardupilot image on it. Once the set up is complete, you can reflash it to Inav.
With `dronecan_use_dna_server = ON` (the default), peripherals that support Dynamic Node Allocation and have their node ID set to 0 (anonymous/unset) negotiate their node IDs automatically at power-up. Only the CAN bitrate needs to match across the bus. Peripherals that do not support DNA, or that already have a static node ID configured, will not send Allocation requests and must have their node ID set manually via a tool such as dronecan_gui. You can use your flight controller as a CAN interface by loading an ArduPilot image on it; once configuration is complete, reflash to INAV.

### GPS via DroneCAN

Expand Down Expand Up @@ -154,24 +155,28 @@ Setting up multiple DroneCAN peripherals on a single CAN bus:

```
# Flight Controller Configuration
set dronecan_node_id = 10 # Flight controller = node 10
set dronecan_bitrate = 1000KBPS
set dronecan_node_id = 1 # Flight controller = node 1
set dronecan_bitrate_kbps = 1000
set dronecan_use_dna_server = ON # Enable plug-and-play node assignment (default)

# Configure GPS (from node 1)
# Configure GPS
set gps_provider = DRONECAN

# Configure battery monitor (from node 2)
# Configure battery monitor
set bat_voltage_src = CAN
feature CURRENT_METER
set current_meter_type = CAN

save
```

**Peripheral Configuration (using dronecan_gui or similar tool):**
- **GPS Receiver:** Node ID = 1, Bitrate = 1000 KBPS
- **Battery Monitor:** Node ID = 2, Bitrate = 1000 KBPS
- **Potential Future Peripheral:** Node ID = 3, etc.
**Peripheral Configuration:**

With `dronecan_use_dna_server = ON`, peripherals that support Dynamic Node Allocation negotiate their node IDs automatically — no manual per-device configuration needed. Just set the CAN bitrate to match on every device.

For peripherals that do not support DNA, use dronecan_gui to assign a static node ID:
- **GPS Receiver:** Node ID = 2, Bitrate = 1000 KBPS
- **Battery Monitor:** Node ID = 3, Bitrate = 1000 KBPS

**CAN Bus Layout:**
```
Expand Down Expand Up @@ -295,6 +300,40 @@ bat_voltage_src = CAN
current_meter_type = CAN
```

## Dynamic Node Allocation (Plug and Play)

DroneCAN supports a three-stage handshake protocol (defined in UAVCAN Specification §6.4.9) that lets peripherals negotiate a unique node ID at power-up without any manual configuration. INAV implements a non-redundant (single-master) DNA server.

### How it works

1. The peripheral powers on without a node ID (anonymous mode).
2. It broadcasts an Allocation request containing the first 6 bytes of its 16-byte hardware unique ID, optionally including a preferred node ID.
3. INAV responds with an echo of the bytes received so far, prompting the peripheral to send the next 6 bytes.
4. After three stages (6 + 6 + 4 bytes), INAV has the full UID and assigns a node ID.
5. INAV stores the UID→node ID mapping in flash so the same peripheral receives the same ID on every power cycle.

All three stages must arrive within 500 ms of each other, or the handshake resets.

### Preferred node IDs

A peripheral may include a preferred node ID in its Stage 1 request. INAV honours the request if the ID is in the valid range (1–127) and not already assigned. If the requested ID is unavailable, INAV falls back to the next free ID in sequence.

### Allocation table

INAV stores up to 32 UID→node ID mappings in persistent flash storage. The table survives power cycles, so peripherals receive consistent node IDs across reboots. The table is managed automatically and is not directly user-configurable.

The flight controller's own node ID is never assigned to a peripheral.

### Enabling / disabling

```
set dronecan_use_dna_server = ON # default — plug-and-play enabled
set dronecan_use_dna_server = OFF # static node IDs only
save
```

---

## Hardware Setup

### Wiring
Expand Down
10 changes: 10 additions & 0 deletions docs/Settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,16 @@ Unique identifier for this device. Valid values are 1 to 127. 126 and 127 are re

---

### dronecan_use_dna_server

Enable the DNA server to manage plug and play dronecan devices

| Default | Min | Max |
| --- | --- | --- |
| ON | OFF | ON |

---

### dshot_beeper_enabled

Whether using DShot motors as beepers is enabled
Expand Down
56 changes: 36 additions & 20 deletions docs/development/msp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,8 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
[8256 - MSP2_INAV_ESC_RPM](#msp2_inav_esc_rpm)
[8257 - MSP2_INAV_ESC_TELEM](#msp2_inav_esc_telem)
[8258 - MSP2_INAV_DRONECAN_NODES](#msp2_inav_dronecan_nodes)
[8259 - MSP2_INAV_DRONECAN_NODE_INFO](#msp2_inav_dronecan_node_info)
[8259 - MSP2_INAV_DRONECAN_ASYNC_REQUEST](#msp2_inav_dronecan_async_request)
[8260 - MSP2_INAV_DRONECAN_ASYNC_RESULT](#msp2_inav_dronecan_async_result)
[8264 - MSP2_INAV_LED_STRIP_CONFIG_EX](#msp2_inav_led_strip_config_ex)
[8265 - MSP2_INAV_SET_LED_STRIP_CONFIG_EX](#msp2_inav_set_led_strip_config_ex)
[8266 - MSP2_INAV_FW_APPROACH](#msp2_inav_fw_approach)
Expand Down Expand Up @@ -4165,34 +4166,49 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
**Request Payload:** **None**

**Reply Payload:**
|Field|C Type|Size (Bytes)|Description|
|---|---|---|---|
| `nodeCount` | `uint8_t` | 1 | Number of detected DroneCAN nodes |
| `nodeData` | `dronecanNodeStatus_t[]` | array | Array of per-node status records, one per detected node. Each record: nodeID(1)+health(1)+mode(1)+last_seen_ms(4) = 7 bytes. Full detail available via MSP2_INAV_DRONECAN_NODE_INFO. |
|Field|C Type|Size (Bytes)|Units|Description|
|---|---|---|---|---|
| `nodeCount` | `uint8_t` | 1 | - | Number of detected DroneCAN nodes |
| `nodeID` | `uint8_t[]` | array | - | [per node] DroneCAN node ID (1-127) |
| `health` | `uint8_t` | 1 | - | [per node] Node health: 0=OK, 1=WARNING, 2=ERROR, 3=CRITICAL |
| `mode` | `uint8_t` | 1 | - | [per node] Node mode: 0=OPERATIONAL, 1=INITIALIZATION, 2=MAINTENANCE, 3=SOFTWARE_UPDATE, 7=OFFLINE |
| `last_seen_ms` | `uint32_t` | 4 | ms | [per node] Milliseconds since this node was last seen (FC-local timestamp delta) |
| `uptime_sec` | `uint32_t` | 4 | s | [per node] Node uptime in seconds (from NodeStatus broadcast) |
| `vendor_status_code` | `uint16_t` | 2 | - | [per node] Vendor-specific status code |

**Notes:** Requires `USE_DRONECAN`. Response is `nodeCount` followed by `nodeCount` records of 7 bytes each: nodeID(1)+health(1)+mode(1)+last_seen_ms(4). Maximum payload 1 + (DRONECAN_MAX_NODES * 7) = 225 bytes. Full node detail including uptime, vendor status, and name is available via MSP2_INAV_DRONECAN_NODE_INFO.
**Notes:** Requires `USE_DRONECAN`. Response is `nodeCount` followed by `nodeCount` records of 13 bytes each: nodeID(1)+health(1)+mode(1)+last_seen_ms(4)+uptime_sec(4)+vendor_status_code(2). Maximum payload 1 + (DRONECAN_MAX_NODES * 13) = 417 bytes. For full node detail (name, SW/HW version, unique ID) use MSP2_INAV_DRONECAN_ASYNC_REQUEST with service_id=DRONECAN_SERVICE_GETNODEINFO(1).

## <a id="msp2_inav_dronecan_node_info"></a>`MSP2_INAV_DRONECAN_NODE_INFO (8259 / 0x2043)`
**Description:** Returns full status detail for a single DroneCAN node by ID.
## <a id="msp2_inav_dronecan_async_request"></a>`MSP2_INAV_DRONECAN_ASYNC_REQUEST (8259 / 0x2043)`
**Description:** Initiates an asynchronous DroneCAN service request (GetNodeInfo, ParamGetSet, ExecuteOpcode, RestartNode) to a specific node. Result retrieved via MSP2_INAV_DRONECAN_ASYNC_RESULT.

**Request Payload:**
|Field|C Type|Size (Bytes)|Description|
|---|---|---|---|
| `nodeID` | `uint8_t` | 1 | DroneCAN node ID to query (1-127) |
| `service_id` | `uint16_t` | 2 | Service to invoke: 1=GETNODEINFO, 5=RESTART_NODE, 10=EXECUTE_OPCODE, 11=PARAM_GETSET. Transmitted as u16 for MSP alignment; only low 8 bits used. |
| `nodeID` | `uint8_t` | 1 | Target DroneCAN node ID (1-127) |

**Reply Payload:**
|Field|C Type|Size (Bytes)|Units|Description|
|---|---|---|---|---|
| `nodeID` | `uint8_t` | 1 | - | DroneCAN node ID |
| `health` | `uint8_t` | 1 | - | Node health: 0=OK, 1=WARNING, 2=ERROR, 3=CRITICAL |
| `mode` | `uint8_t` | 1 | - | Node mode: 0=OPERATIONAL, 1=INITIALIZATION, 2=MAINTENANCE, 3=SOFTWARE_UPDATE, 7=OFFLINE |
| `uptime_sec` | `uint32_t` | 4 | s | Node uptime in seconds |
| `vendor_status_code` | `uint16_t` | 2 | - | Vendor-specific status code |
| `last_seen_ms` | `uint32_t` | 4 | ms | FC millisecond timestamp when this node was last seen |
| `name_len` | `uint8_t` | 1 | - | Length of node name string (0 if unknown) |
| `name` | `char[32]` | 32 | - | Node name up to 32 bytes, zero-padded |
|Field|C Type|Size (Bytes)|Description|
|---|---|---|---|
| `accepted` | `uint8_t` | 1 | 0=request accepted; 1=busy (slot in use) or unrecognised service_id; 0xFF=bus not in STATE_DRONECAN_NORMAL (not ready) |
| `seq` | `uint8_t` | 1 | Sequence number; correlate with MSP2_INAV_DRONECAN_ASYNC_RESULT to verify the result belongs to this request |

**Notes:** Requires `USE_DRONECAN`. Initiates an async DroneCAN service request; poll MSP2_INAV_DRONECAN_ASYNC_RESULT at ~100ms intervals until state=READY(2) or ERROR(3). Only one request in-flight at a time. Service-specific request fields follow the common header in the request payload: EXECUTE_OPCODE appends opcode(u8); PARAM_GETSET appends index(u16)+is_write(u8) and optionally value_type(u8)+value(variable) for writes, then req_name_len(u8)+req_name(bytes) for named lookup. Param value encoding: INT=lo(u32)+hi(u32), FLOAT=raw(u32), BOOL=u8, STRING=len(u8)+data. Requests time out after DRONECAN_ASYNC_TIMEOUT_MS (2000ms). If bus is not in STATE_DRONECAN_NORMAL, returns accepted=0xFF without dispatching.

## <a id="msp2_inav_dronecan_async_result"></a>`MSP2_INAV_DRONECAN_ASYNC_RESULT (8260 / 0x2044)`
**Description:** Polls the result of the most recent MSP2_INAV_DRONECAN_ASYNC_REQUEST. Poll at ~100ms intervals until state is READY(2) or ERROR(3).

**Request Payload:** **None**

**Reply Payload:**
|Field|C Type|Size (Bytes)|Description|
|---|---|---|---|
| `state` | `uint8_t` | 1 | Async slot state: 0=IDLE, 1=PENDING, 2=READY, 3=ERROR |
| `seq` | `uint8_t` | 1 | Sequence number matching the originating MSP2_INAV_DRONECAN_ASYNC_REQUEST reply |
| `service_id` | `uint16_t` | 2 | Service ID of the in-flight or just-completed request |
| `node_id` | `uint8_t` | 1 | Node ID of the target |

**Notes:** Requires `USE_DRONECAN`. Returns `MSP_RESULT_ERROR` if the requested node ID is not in the node table.
**Notes:** Requires `USE_DRONECAN`. When state=READY(2), service-specific result fields follow the 5-byte common header. GETNODEINFO: name_len(u8)+name(bytes)+sw_major(u8)+sw_minor(u8)+sw_optional_field_flags(u8)+sw_vcs_commit(u32)+hw_major(u8)+hw_minor(u8)+hw_unique_id(u8[16]). PARAM_GETSET: name_len(u8)+name(bytes)+type(u8)+value(variable)+min_type(u8)+min(variable)+max_type(u8)+max(variable); value/min/max encoding: INT=lo(u32)+hi(u32), FLOAT=raw(u32), BOOL=u8, STRING=len(u8)+data; EMPTY(0) min/max type means no bound is present. EXECUTE_OPCODE and RESTART_NODE: ok(u8) where 1=success. Reading result when state=READY transitions slot back to IDLE.

## <a id="msp2_inav_led_strip_config_ex"></a>`MSP2_INAV_LED_STRIP_CONFIG_EX (8264 / 0x2048)`
**Description:** Retrieves the full configuration for each LED on the strip using the `ledConfig_t` structure. Supersedes `MSP_LED_STRIP_CONFIG`.
Expand Down
Loading
Loading