Skip to content

Commit abc31a8

Browse files
Merge pull request #11527 from daijoubu/feature/msp-dronecan-support
DroneCAN: Add node table, CLI status, and MSP2 node query commands
2 parents fcbe8e3 + e0c4b41 commit abc31a8

9 files changed

Lines changed: 430 additions & 54 deletions

File tree

docs/DroneCAN-Driver.md

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,38 @@ dronecanUpdate(micros()); // Called repeatedly
242242
243243
---
244244
245+
### Node Table
246+
247+
The driver maintains a table of detected DroneCAN nodes populated from incoming `NodeStatus` broadcasts.
248+
249+
#### `dronecanNodeInfo_t` struct
250+
251+
```c
252+
typedef struct dronecanNodeInfo_s {
253+
uint8_t nodeID; // CAN node ID (1-127)
254+
uint8_t health; // UAVCAN_PROTOCOL_NODESTATUS_HEALTH_*
255+
uint8_t mode; // UAVCAN_PROTOCOL_NODESTATUS_MODE_*
256+
uint32_t uptime_sec; // Node uptime in seconds
257+
uint16_t vendor_status_code; // Vendor-specific status word
258+
uint32_t last_seen_ms; // FC millis() timestamp of last NodeStatus
259+
uint8_t name_len; // Length of name (0 until GetNodeInfo implemented)
260+
char name[32]; // Node name, zero-padded
261+
} dronecanNodeInfo_t;
262+
```
263+
264+
The table holds up to `DRONECAN_MAX_NODES` (32) entries, indexed by arrival order. Entries are never removed at runtime; the table persists from boot until power-off.
265+
266+
#### Accessor Functions
267+
268+
| Function | Returns |
269+
|----------|---------|
270+
| `dronecanGetNodeCount()` | Number of unique nodes seen (`uint8_t`) |
271+
| `dronecanGetNode(uint8_t index)` | Pointer to `dronecanNodeInfo_t` for entry `index`, or `NULL` if out of range |
272+
| `dronecanGetState()` | Current bus state (`dronecanState_e`) |
273+
| `dronecanGetBitrateKbps()` | Configured bitrate as a plain integer (e.g. 1000) |
274+
275+
---
276+
245277
### Configuration
246278

247279
#### Settings
@@ -305,8 +337,8 @@ The driver includes 7 built-in message handlers. Each handler decodes a message
305337

306338
#### `handle_NodeStatus()`
307339
**Receives:** `uavcan_protocol_NodeStatus` (from other nodes)
308-
**Processing:** Decodes and logs node health (OK/WARNING/ERROR/CRITICAL) and mode (OPERATIONAL/INITIALIZATION/MAINTENANCE/SOFTWARE_UPDATE/OFFLINE)
309-
**Status:** Logs health and mode status
340+
**Processing:** Decodes the message and upserts into the node table (`nodeTable[]`). If the source node ID is already in the table, health, mode, uptime, vendor status, and `last_seen_ms` are updated. If it is a new node and the table is not full, a new entry is appended and `activeNodeCount` is incremented. Node names are not available from NodeStatus broadcasts; they remain empty until `GetNodeInfo` service requests are implemented.
341+
**Status:** Node count accessible via `dronecanGetNodeCount()`; per-node detail via `dronecanGetNode()`
310342

311343
### Service Handlers
312344

@@ -477,6 +509,46 @@ if (uavcan_equipment_gnss_Fix_decode(transfer, &gnssFix) == 0) {
477509

478510
---
479511

512+
## MSP Commands
513+
514+
Two MSP2 commands expose the node table to external tools (configurator, GCS, test scripts):
515+
516+
### `MSP2_INAV_DRONECAN_NODES` (0x2042)
517+
518+
**Direction:** Request (no payload) → Reply
519+
**Handler location:** `mspFcProcessOutCommand()` in `fc_msp.c`
520+
**Guard:** `#ifdef USE_DRONECAN`
521+
522+
**Reply layout:**
523+
524+
| Offset | Size | Field |
525+
|--------|------|-------|
526+
| 0 | 1 | `nodeCount` |
527+
| 1 + N×30 | 1 | `nodeID` |
528+
| 2 + N×30 | 1 | `health` |
529+
| 3 + N×30 | 1 | `mode` |
530+
| 4 + N×30 | 4 | `uptime_sec` (little-endian) |
531+
| 8 + N×30 | 2 | `vendor_status_code` |
532+
| 10 + N×30 | 4 | `last_seen_ms` |
533+
| 14 + N×30 | 1 | `name_len` |
534+
| 15 + N×30 | 16 | `name` (zero-padded) |
535+
536+
Total: 1 + nodeCount × 30 bytes.
537+
538+
---
539+
540+
### `MSP2_INAV_DRONECAN_NODE_INFO` (0x2043)
541+
542+
**Direction:** Request (1 byte node ID) → Reply
543+
**Handler location:** `mspFCProcessInOutCommand()` in `fc_msp.c`
544+
**Guard:** `#ifdef USE_DRONECAN`
545+
546+
**Request:** 1 byte — target `nodeID`
547+
548+
**Reply layout:** same fields as above but with a 32-byte `name` field (46 bytes total). Returns an empty response if the requested node ID is not in the table.
549+
550+
---
551+
480552
## Adding New Message Types
481553

482554
To add support for a new DroneCAN message type:
@@ -837,6 +909,7 @@ void broadcastNodeStatus(void) {
837909
838910
| Date | Version | Changes |
839911
|---|---|---|
912+
| 2026-04-30 | 1.2 | Added node table (`dronecanNodeInfo_t`), accessor functions, CLI status output, and MSP commands (0x2042/0x2043) |
840913
| 2026-02-18 | 1.1 | Added error recovery, graceful disable behavior, and safe initialization documentation |
841914
| 2026-02-16 | 1.0 | Initial version - handler-based architecture documentation |
842915

docs/DroneCAN.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,40 @@ Connect the CAN bus lines between your flight controller and DroneCAN peripheral
318318
120R (pass-through) 120R
319319
```
320320

321+
## Diagnostics
322+
323+
### CLI Status
324+
325+
The `status` CLI command includes a DroneCAN summary line showing the current bus state and how many nodes have been detected:
326+
327+
```
328+
DroneCAN: nodeID=10, bitrate=1000 kbps, status=NORMAL, nodes=2
329+
```
330+
331+
| Field | Description |
332+
|-------|-------------|
333+
| `nodeID` | This FC's CAN node ID |
334+
| `bitrate` | Configured CAN bus bitrate |
335+
| `status` | `INIT`, `NORMAL`, or `BUS_OFF` |
336+
| `nodes` | Number of unique nodes seen since boot |
337+
338+
### MSP Commands
339+
340+
Two MSP2 commands are available for querying detected DroneCAN nodes programmatically (e.g. from a GCS or configurator):
341+
342+
| Command | Code | Description |
343+
|---------|------|-------------|
344+
| `MSP2_INAV_DRONECAN_NODES` | `0x2042` | Returns count and status of all detected nodes |
345+
| `MSP2_INAV_DRONECAN_NODE_INFO` | `0x2043` | Returns full detail for a single node by ID |
346+
347+
**`MSP2_INAV_DRONECAN_NODES` response:** one byte node count followed by 7-byte records — nodeID(1), health(1), mode(1), last\_seen\_ms(4). Maximum payload is 1 + 32×7 = 225 bytes, well within the 512-byte MSP output buffer.
348+
349+
**`MSP2_INAV_DRONECAN_NODE_INFO` request/response:** send a single-byte node ID; receive full node detail — nodeID, health, mode, uptime\_sec, vendor\_status\_code, last\_seen\_ms, name\_len, and name (32 bytes). Returns `MSP_RESULT_ERROR` if the node ID is not in the table.
350+
351+
See `docs/development/msp/README.md` for the full MSP field layout.
352+
353+
---
354+
321355
## Troubleshooting
322356

323357
### No data from DroneCAN devices

docs/development/msp/README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
417417
[8251 - MSP2_INAV_LOGIC_CONDITIONS_SINGLE](#msp2_inav_logic_conditions_single)
418418
[8256 - MSP2_INAV_ESC_RPM](#msp2_inav_esc_rpm)
419419
[8257 - MSP2_INAV_ESC_TELEM](#msp2_inav_esc_telem)
420+
[8258 - MSP2_INAV_DRONECAN_NODES](#msp2_inav_dronecan_nodes)
421+
[8259 - MSP2_INAV_DRONECAN_NODE_INFO](#msp2_inav_dronecan_node_info)
420422
[8264 - MSP2_INAV_LED_STRIP_CONFIG_EX](#msp2_inav_led_strip_config_ex)
421423
[8265 - MSP2_INAV_SET_LED_STRIP_CONFIG_EX](#msp2_inav_set_led_strip_config_ex)
422424
[8266 - MSP2_INAV_FW_APPROACH](#msp2_inav_fw_approach)
@@ -450,6 +452,7 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
450452
[8736 - MSP2_INAV_FULL_LOCAL_POSE](#msp2_inav_full_local_pose)
451453
[8737 - MSP2_INAV_SET_WP_INDEX](#msp2_inav_set_wp_index)
452454
[8739 - MSP2_INAV_SET_CRUISE_HEADING](#msp2_inav_set_cruise_heading)
455+
[8752 - MSP2_INAV_SET_AUX_RC](#msp2_inav_set_aux_rc)
453456
[12288 - MSP2_BETAFLIGHT_BIND](#msp2_betaflight_bind)
454457
[12289 - MSP2_RX_BIND](#msp2_rx_bind)
455458

@@ -2311,7 +2314,7 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
23112314
| `hdop` | `uint16_t` | 2 | HDOP * 100 | Horizontal Dilution of Precision (`gpsSol.hdop`) |
23122315
| `eph` | `uint16_t` | 2 | cm | Estimated Horizontal Position Accuracy (`gpsSol.eph`) |
23132316
| `epv` | `uint16_t` | 2 | cm | Estimated Vertical Position Accuracy (`gpsSol.epv`) |
2314-
| `hwVersion` | `uint32_t` | 4 | Version code | GPS hardware version (`gpsState.hwVersion`). Values: 500=UBLOX5, 600=UBLOX6, 700=UBLOX7, 800=UBLOX8, 900=UBLOX9, 1000=UBLOX10, 0=UNKNOWN |
2317+
| `hwVersion` | `uint8_t` | 1 | - | GPS hardware version bit-field: bits[7:6]=series (0b01=u-blox Neo/M), bits[5:0]=generation. E.g. 0x48=M8, 0x49=M9, 0x4A=M10, 0=unknown. |
23152318

23162319
**Notes:** Requires `USE_GPS`.
23172320

@@ -4156,6 +4159,41 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
41564159

41574160
**Notes:** Requires `USE_ESC_SENSOR`. See `escSensorData_t` in `sensors/esc_sensor.h` for the exact structure fields.
41584161

4162+
## <a id="msp2_inav_dronecan_nodes"></a>`MSP2_INAV_DRONECAN_NODES (8258 / 0x2042)`
4163+
**Description:** Returns the list of all detected DroneCAN nodes with their current status.
4164+
4165+
**Request Payload:** **None**
4166+
4167+
**Reply Payload:**
4168+
|Field|C Type|Size (Bytes)|Description|
4169+
|---|---|---|---|
4170+
| `nodeCount` | `uint8_t` | 1 | Number of detected DroneCAN nodes |
4171+
| `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. |
4172+
4173+
**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.
4174+
4175+
## <a id="msp2_inav_dronecan_node_info"></a>`MSP2_INAV_DRONECAN_NODE_INFO (8259 / 0x2043)`
4176+
**Description:** Returns full status detail for a single DroneCAN node by ID.
4177+
4178+
**Request Payload:**
4179+
|Field|C Type|Size (Bytes)|Description|
4180+
|---|---|---|---|
4181+
| `nodeID` | `uint8_t` | 1 | DroneCAN node ID to query (1-127) |
4182+
4183+
**Reply Payload:**
4184+
|Field|C Type|Size (Bytes)|Units|Description|
4185+
|---|---|---|---|---|
4186+
| `nodeID` | `uint8_t` | 1 | - | DroneCAN node ID |
4187+
| `health` | `uint8_t` | 1 | - | Node health: 0=OK, 1=WARNING, 2=ERROR, 3=CRITICAL |
4188+
| `mode` | `uint8_t` | 1 | - | Node mode: 0=OPERATIONAL, 1=INITIALIZATION, 2=MAINTENANCE, 3=SOFTWARE_UPDATE, 7=OFFLINE |
4189+
| `uptime_sec` | `uint32_t` | 4 | s | Node uptime in seconds |
4190+
| `vendor_status_code` | `uint16_t` | 2 | - | Vendor-specific status code |
4191+
| `last_seen_ms` | `uint32_t` | 4 | ms | FC millisecond timestamp when this node was last seen |
4192+
| `name_len` | `uint8_t` | 1 | - | Length of node name string (0 if unknown) |
4193+
| `name` | `char[32]` | 32 | - | Node name up to 32 bytes, zero-padded |
4194+
4195+
**Notes:** Requires `USE_DRONECAN`. Returns `MSP_RESULT_ERROR` if the requested node ID is not in the node table.
4196+
41594197
## <a id="msp2_inav_led_strip_config_ex"></a>`MSP2_INAV_LED_STRIP_CONFIG_EX (8264 / 0x2048)`
41604198
**Description:** Retrieves the full configuration for each LED on the strip using the `ledConfig_t` structure. Supersedes `MSP_LED_STRIP_CONFIG`.
41614199

@@ -4710,6 +4748,19 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
47104748

47114749
**Notes:** Returns error if the aircraft is not armed or `NAV_COURSE_HOLD_MODE` is not active. On success, sets both `posControl.cruise.course` and `posControl.cruise.previousCourse` to the normalised value, preventing spurious heading adjustments from `getCruiseHeadingAdjustment()` on the next control cycle.
47124750

4751+
## <a id="msp2_inav_set_aux_rc"></a>`MSP2_INAV_SET_AUX_RC (8752 / 0x2230)`
4752+
**Description:** Bandwidth-efficient auxiliary RC channel update. Sets CH13-CH32 with configurable resolution (2/4/8/16-bit) without affecting primary flight controls. Designed for extending channel count beyond native RC link capacity via MSP passthrough.
4753+
4754+
**Request Payload:**
4755+
|Field|C Type|Size (Bytes)|Units|Description|
4756+
|---|---|---|---|---|
4757+
| `definitionByte` | `uint8_t` | 1 | - | Packed start channel and resolution. Bits 7-3: start channel index (valid range 12-31 for CH13-CH32; 0-11 rejected as error). Bits 2-0: resolution mode (0=2-bit, 1=4-bit, 2=8-bit, 3=16-bit; 4-7 reserved/error). |
4758+
| `channelData` | `uint8_t[]` | array | PWM (encoded) | Packed channel values, sequential from start channel. Number of channels is derived from data size and resolution. Value 0 means skip (no update). Sub-byte modes (2-bit, 4-bit) are packed MSB-first. 2-bit values 1-3 map to 1000/1500/2000us. 4-bit values 1-15 map to 1000 + (val-1)*1000/14 us. 8-bit values 1-255 map to 1000 + (val-1)*1000/254 us. 16-bit values are direct PWM, clamped to 750-2250us. |
4759+
4760+
**Reply Payload:** **None**
4761+
4762+
**Notes:** CH1-CH12 (index 0-11) are protected and will return `MSP_RESULT_ERROR`. Payload size must be 2-49 bytes. Constraint: `startChannel + channelCount <= 32`. Values persist until overwritten; no timeout. Applied as a post-RX overlay in `calculateRxChannelsAndUpdateFailsafe()` after MSP RC Override but before failsafe. Does not require `USE_RX_MSP` or MSP-RC-OVERRIDE flight mode. Does not affect failsafe detection. When MSP is the primary RX provider, channels covered by `MSP_SET_RAW_RC` are automatically skipped. Channels in the `mspOverrideChannels` bitmask are skipped when MSP RC Override mode is active. Recommended to send with `MSP_FLAG_DONT_REPLY` (flags=0x01) to save bandwidth on telemetry passthrough links. 16-bit mode requires even number of data bytes and values are clamped to 750-2250us.
4763+
47134764
## <a id="msp2_betaflight_bind"></a>`MSP2_BETAFLIGHT_BIND (12288 / 0x3000)`
47144765
**Description:** Initiates the receiver binding procedure for supported serial protocols (CRSF, SRXL2).
47154766

docs/development/msp/msp_messages.json

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9817,6 +9817,100 @@
98179817
"notes": "Requires `USE_ESC_SENSOR`. See `escSensorData_t` in `sensors/esc_sensor.h` for the exact structure fields.",
98189818
"description": "Retrieves the full telemetry data structure reported by each ESC."
98199819
},
9820+
"MSP2_INAV_DRONECAN_NODES": {
9821+
"code": 8258,
9822+
"mspv": 2,
9823+
"request": null,
9824+
"reply": {
9825+
"payload": [
9826+
{
9827+
"name": "nodeCount",
9828+
"ctype": "uint8_t",
9829+
"desc": "Number of detected DroneCAN nodes",
9830+
"units": ""
9831+
},
9832+
{
9833+
"name": "nodeData",
9834+
"desc": "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.",
9835+
"ctype": "dronecanNodeStatus_t",
9836+
"array": true,
9837+
"array_size": 0,
9838+
"units": ""
9839+
}
9840+
]
9841+
},
9842+
"variable_len": true,
9843+
"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.",
9844+
"description": "Returns the list of all detected DroneCAN nodes with their current status."
9845+
},
9846+
"MSP2_INAV_DRONECAN_NODE_INFO": {
9847+
"code": 8259,
9848+
"mspv": 2,
9849+
"request": {
9850+
"payload": [
9851+
{
9852+
"name": "nodeID",
9853+
"ctype": "uint8_t",
9854+
"desc": "DroneCAN node ID to query (1-127)",
9855+
"units": ""
9856+
}
9857+
]
9858+
},
9859+
"reply": {
9860+
"payload": [
9861+
{
9862+
"name": "nodeID",
9863+
"ctype": "uint8_t",
9864+
"desc": "DroneCAN node ID",
9865+
"units": ""
9866+
},
9867+
{
9868+
"name": "health",
9869+
"ctype": "uint8_t",
9870+
"desc": "Node health: 0=OK, 1=WARNING, 2=ERROR, 3=CRITICAL",
9871+
"units": ""
9872+
},
9873+
{
9874+
"name": "mode",
9875+
"ctype": "uint8_t",
9876+
"desc": "Node mode: 0=OPERATIONAL, 1=INITIALIZATION, 2=MAINTENANCE, 3=SOFTWARE_UPDATE, 7=OFFLINE",
9877+
"units": ""
9878+
},
9879+
{
9880+
"name": "uptime_sec",
9881+
"ctype": "uint32_t",
9882+
"desc": "Node uptime in seconds",
9883+
"units": "s"
9884+
},
9885+
{
9886+
"name": "vendor_status_code",
9887+
"ctype": "uint16_t",
9888+
"desc": "Vendor-specific status code",
9889+
"units": ""
9890+
},
9891+
{
9892+
"name": "last_seen_ms",
9893+
"ctype": "uint32_t",
9894+
"desc": "FC millisecond timestamp when this node was last seen",
9895+
"units": "ms"
9896+
},
9897+
{
9898+
"name": "name_len",
9899+
"ctype": "uint8_t",
9900+
"desc": "Length of node name string (0 if unknown)",
9901+
"units": ""
9902+
},
9903+
{
9904+
"name": "name",
9905+
"ctype": "char[32]",
9906+
"desc": "Node name up to 32 bytes, zero-padded",
9907+
"units": ""
9908+
}
9909+
]
9910+
},
9911+
"notes": "Requires `USE_DRONECAN`. Returns `MSP_RESULT_ERROR` if the requested node ID is not in the node table.",
9912+
"description": "Returns full status detail for a single DroneCAN node by ID."
9913+
},
98209914
"MSP2_INAV_LED_STRIP_CONFIG_EX": {
98219915
"code": 8264,
98229916
"mspv": 2,

0 commit comments

Comments
 (0)