Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
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
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
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
149 changes: 93 additions & 56 deletions docs/development/msp/msp_messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -9830,86 +9830,123 @@
"units": ""
},
{
"name": "nodeData",
"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.",
"ctype": "dronecanNodeStatus_t",
"name": "nodeID",
"ctype": "uint8_t",
"desc": "[per node] DroneCAN node ID (1-127)",
"units": "",
"array": true,
"array_size": 0,
"array_size": 0
},
{
"name": "health",
"ctype": "uint8_t",
"desc": "[per node] Node health: 0=OK, 1=WARNING, 2=ERROR, 3=CRITICAL",
"units": ""
},
{
"name": "mode",
"ctype": "uint8_t",
"desc": "[per node] Node mode: 0=OPERATIONAL, 1=INITIALIZATION, 2=MAINTENANCE, 3=SOFTWARE_UPDATE, 7=OFFLINE",
"units": ""
},
{
"name": "last_seen_ms",
"ctype": "uint32_t",
"desc": "[per node] Milliseconds since this node was last seen (FC-local timestamp delta)",
"units": "ms"
},
{
"name": "uptime_sec",
"ctype": "uint32_t",
"desc": "[per node] Node uptime in seconds (from NodeStatus broadcast)",
"units": "s"
},
{
"name": "vendor_status_code",
"ctype": "uint16_t",
"desc": "[per node] Vendor-specific status code",
"units": ""
}
]
},
"variable_len": true,
"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).",
"description": "Returns the list of all detected DroneCAN nodes with their current status."
},
"MSP2_INAV_DRONECAN_NODE_INFO": {
"code": 8259,
"mspv": 2,
},
"MSP2_INAV_DRONECAN_ASYNC_REQUEST": {
"code": 8259,
"mspv": 2,
"request": {
"payload": [
{
"payload": [
{
"name": "service_id",
"ctype": "uint16_t",
"desc": "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.",
"units": ""
},
{
"name": "nodeID",
"ctype": "uint8_t",
"desc": "DroneCAN node ID to query (1-127)",
"units": ""
"desc": "Target DroneCAN node ID (1-127)",
"units": ""
}
]
},
]
},
"reply": {
"payload": [
{
"name": "nodeID",
"ctype": "uint8_t",
"desc": "DroneCAN node ID",
"units": ""
},
"name": "accepted",
"ctype": "uint8_t",
"desc": "0=request accepted; 1=busy (slot in use) or unrecognised service_id; 0xFF=bus not in STATE_DRONECAN_NORMAL (not ready)",
"units": ""
},
{
"name": "health",
"name": "seq",
"ctype": "uint8_t",
"desc": "Sequence number; correlate with MSP2_INAV_DRONECAN_ASYNC_RESULT to verify the result belongs to this request",
"units": ""
}
]
},
"variable_len": true,
"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.",
"description": "Initiates an asynchronous DroneCAN service request (GetNodeInfo, ParamGetSet, ExecuteOpcode, RestartNode) to a specific node. Result retrieved via MSP2_INAV_DRONECAN_ASYNC_RESULT."
},
"MSP2_INAV_DRONECAN_ASYNC_RESULT": {
"code": 8260,
"mspv": 2,
"request": null,
"reply": {
"payload": [
{
"name": "state",
"ctype": "uint8_t",
"desc": "Node health: 0=OK, 1=WARNING, 2=ERROR, 3=CRITICAL",
"desc": "Async slot state: 0=IDLE, 1=PENDING, 2=READY, 3=ERROR",
"units": ""
},
{
"name": "mode",
"ctype": "uint8_t",
"desc": "Node mode: 0=OPERATIONAL, 1=INITIALIZATION, 2=MAINTENANCE, 3=SOFTWARE_UPDATE, 7=OFFLINE",
"units": ""
},
{
"name": "uptime_sec",
"ctype": "uint32_t",
"desc": "Node uptime in seconds",
"units": "s"
"name": "seq",
"ctype": "uint8_t",
"desc": "Sequence number matching the originating MSP2_INAV_DRONECAN_ASYNC_REQUEST reply",
"units": ""
},
{
"name": "vendor_status_code",
"name": "service_id",
"ctype": "uint16_t",
"desc": "Vendor-specific status code",
"units": ""
"desc": "Service ID of the in-flight or just-completed request",
"units": ""
},
{
"name": "last_seen_ms",
"ctype": "uint32_t",
"desc": "FC millisecond timestamp when this node was last seen",
"units": "ms"
},
{
"name": "name_len",
"ctype": "uint8_t",
"desc": "Length of node name string (0 if unknown)",
"units": ""
},
{
"name": "name",
"ctype": "char[32]",
"desc": "Node name up to 32 bytes, zero-padded",
"units": ""
}
]
},
"notes": "Requires `USE_DRONECAN`. Returns `MSP_RESULT_ERROR` if the requested node ID is not in the node table.",
"description": "Returns full status detail for a single DroneCAN node by ID."
"name": "node_id",
"ctype": "uint8_t",
"desc": "Node ID of the target",
"units": ""
}
]
},
"variable_len": true,
"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.",
"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)."
},
"MSP2_INAV_LED_STRIP_CONFIG_EX": {
"code": 8264,
Expand Down
Loading
Loading