Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
53 changes: 53 additions & 0 deletions docs/development/msp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
[8305 - MSP2_INAV_EZ_TUNE_SET](#msp2_inav_ez_tune_set)
[8320 - MSP2_INAV_SELECT_MIXER_PROFILE](#msp2_inav_select_mixer_profile)
[8336 - MSP2_ADSB_VEHICLE_LIST](#msp2_adsb_vehicle_list)
[8339 - MSP2_ADSB_VEHICLE](#msp2_adsb_vehicle)
[8340 - MSP2_ADSB_VEHICLE_COUNT](#msp2_adsb_vehicle_count)
[8448 - MSP2_INAV_CUSTOM_OSD_ELEMENTS](#msp2_inav_custom_osd_elements)
[8449 - MSP2_INAV_CUSTOM_OSD_ELEMENT](#msp2_inav_custom_osd_element)
[8450 - MSP2_INAV_SET_CUSTOM_OSD_ELEMENTS](#msp2_inav_set_custom_osd_elements)
Expand Down Expand Up @@ -458,6 +460,7 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:
[8743 - MSP2_INAV_ARM_DISARM](#msp2_inav_arm_disarm)
[8744 - MSP2_INAV_TIMESYNC](#msp2_inav_timesync)
[8752 - MSP2_INAV_SET_AUX_RC](#msp2_inav_set_aux_rc)
[8753 - MSP2_INAV_WIND](#msp2_inav_wind)
[12288 - MSP2_BETAFLIGHT_BIND](#msp2_betaflight_bind)
[12289 - MSP2_RX_BIND](#msp2_rx_bind)

Expand Down Expand Up @@ -4402,6 +4405,42 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:

**Notes:** Requires `USE_ADSB`. Only a subset of `adsbVehicle_t` is transmitted (callsign, core values, heading in whole degrees, TSLC, emitter type, TTL).

## <a id="msp2_adsb_vehicle"></a>`MSP2_ADSB_VEHICLE (8339 / 0x2093)`
**Description:** Retrieves a single tracked ADSB (Automatic Dependent Surveillance-Broadcast) vehicle by slot index. Intended for polling one slot at a time: query `MSP2_ADSB_VEHICLE_COUNT` for the iteration bound, then request indices `0 .. count-1`, skipping slots with `ttl == 0`, and identify each aircraft by its `icao`. See `adsbVehicle_t` / `adsbVehicleValues_t` in `io/adsb.h`.

**Request Payload:**
|Field|C Type|Size (Bytes)|Description|
|---|---|---|---|
| `index` | `uint8_t` | 1 | Slot index to read, `0 .. (MSP2_ADSB_VEHICLE_COUNT - 1)`. WARNING: this is an iteration cursor over fixed slots, NOT a stable identifier. The same index may return a different aircraft (or an empty slot) on a later poll. Always identify the aircraft by the `icao` field in the reply; never cache or correlate data by index. Returns an error result if the index is out of range. |

**Reply Payload:**
|Field|C Type|Size (Bytes)|Units|Description|
|---|---|---|---|---|
| `icao` | `uint32_t` | 4 | - | ICAO 24-bit address (`vehicleValues.icao`). This is the stable per-aircraft identifier; use it to correlate replies, not the request index. An empty slot reports `icao == 0` and `ttl == 0`. |
| `lat` | `int32_t` | 4 | 1e-7 deg | Latitude (`vehicleValues.gps.lat`). |
| `lon` | `int32_t` | 4 | 1e-7 deg | Longitude (`vehicleValues.gps.lon`). |
| `alt` | `int32_t` | 4 | cm | Altitude above sea level (`vehicleValues.alt`). |
| `heading` | `uint16_t` | 2 | 1e-2 deg | Course over ground at full resolution (`vehicleValues.heading`). Unlike `MSP2_ADSB_VEHICLE_LIST`, this is in centidegrees, not whole degrees. |
| `horVelocity` | `uint16_t` | 2 | cm/s | Horizontal (ground) speed (`vehicleValues.horVelocity`). Not present in `MSP2_ADSB_VEHICLE_LIST`. |
| `tslc` | `uint8_t` | 1 | s | Time since last communication (`vehicleValues.tslc`). |
| `emitterType` | `uint8_t` | 1 | - | Emitter category (`vehicleValues.emitterType`). |
| `ttl` | `uint8_t` | 1 | s | Remaining time-to-live for this slot (`adsbVehicle->ttl`). `ttl == 0` means the slot is empty/expired and its contents are stale; skip such entries. |
| `callsign` | `char[ADSB_CALL_SIGN_MAX_LENGTH]` | 9 (ADSB_CALL_SIGN_MAX_LENGTH) | - | Fixed-length callsign (`vehicleValues.callsign`), padded with NULs if shorter. |

**Notes:** Requires `USE_ADSB`. Reads a single ADSB vehicle slot by index. THE INDEX IS NOT A STABLE HANDLE: slots are reused, so a given index may hold a different aircraft (or be empty, `ttl == 0`) between polls. Correlate aircraft by the `icao` field in the reply, never by index. Compared with the bulk `MSP2_ADSB_VEHICLE_LIST`, this message adds horizontal velocity and reports heading at full (centidegree) resolution, and orders the callsign last. Returns an error result for an out-of-range index.

## <a id="msp2_adsb_vehicle_count"></a>`MSP2_ADSB_VEHICLE_COUNT (8340 / 0x2094)`
**Description:** Returns the number of ADSB vehicle slots available to iterate with `MSP2_ADSB_VEHICLE`.

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

**Reply Payload:**
|Field|C Type|Size (Bytes)|Description|
|---|---|---|---|
| `count` | `uint8_t` | 1 | Number of vehicle slots to iterate (`MAX_ADSB_VEHICLES`). This is the slot capacity / iteration bound, not the number of currently active aircraft - some slots may be empty (`ttl == 0`). 0 if `USE_ADSB` is disabled. |

**Notes:** Requires `USE_ADSB`. Returns the iteration bound for `MSP2_ADSB_VEHICLE`: request indices `0 .. count-1` and skip any slot whose `ttl == 0`.

## <a id="msp2_inav_custom_osd_elements"></a>`MSP2_INAV_CUSTOM_OSD_ELEMENTS (8448 / 0x2100)`
**Description:** Retrieves counts related to custom OSD elements defined by the programming framework.

Expand Down Expand Up @@ -4825,6 +4864,20 @@ When the MSP JSON specification changes, bump `msp_messages.json` version:

**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.

## <a id="msp2_inav_wind"></a>`MSP2_INAV_WIND (8753 / 0x2231)`
**Description:** Retrieves the estimated horizontal wind speed and direction from the internal wind estimator.

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

**Reply Payload:**
|Field|C Type|Size (Bytes)|Units|Description|
|---|---|---|---|---|
| `windSpeed` | `uint16_t` | 2 | cm/s | Estimated horizontal wind speed (`getEstimatedHorizontalWindSpeed()`). 0 if unavailable. |
| `windAngle` | `uint16_t` | 2 | degrees | Estimated wind direction in degrees (0–359, 0 = North). Derived from centidegree value divided by 100. 0 if unavailable. |
| `flags` | `uint8_t` | 1 | - | Validity flags. Bit 0: wind estimate valid (`isEstimatedWindSpeedValid()`). Remaining bits reserved. |

**Notes:** Requires `USE_WIND_ESTIMATOR`; returns zeroes when wind estimation is not compiled in or not yet valid. Check bit 0 of `flags` before using speed/angle values.

## <a id="msp2_betaflight_bind"></a>`MSP2_BETAFLIGHT_BIND (12288 / 0x3000)`
**Description:** Initiates the receiver binding procedure for supported serial protocols (CRSF, SRXL2).

Expand Down
105 changes: 84 additions & 21 deletions docs/development/msp/gen_enum_md.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
* If no assignment -> auto-increment.
- If auto-increment occurs inside an active preprocessor condition, wrap the number
in parentheses to indicate conditional numbering: e.g., 3, 4, #ifdef, (5), (6).
- Mutually-exclusive branches (#ifdef X / #ifndef X siblings, or #else/#elif within
one #if family) restart numbering from the branch base, because only one branch's
members exist in any given build.
- Tracks nested #if/#ifdef/#ifndef/#elif/#else/#endif and shows Condition text.
- Handles multiline enumerators (split at the first top-level comma).
"""
Expand Down Expand Up @@ -81,24 +84,68 @@ def normalize_condition_text(text: str) -> str:
return t

class ConditionStack:
"""Tracks preprocessor conditionals and the auto-increment base each branch
started from, so mutually-exclusive branches (#ifdef X / #ifndef X siblings,
or #else / #elif within one family) restart numbering from the shared base
instead of continuing through the other branch's members.
"""
def __init__(self):
self.stack: List[str] = []
def push_ifdef(self, sym: str): self.stack.append(sym)
def push_ifndef(self, sym: str): self.stack.append(f'!{sym}')
def push_if(self, expr: str): self.stack.append(normalize_condition_text(expr))
def elif_(self, expr: str):
if self.stack: self.stack.pop()
self.stack.append(normalize_condition_text(expr))
def else_(self):
if not self.stack: return
top = self.stack.pop()
if top.startswith('!'): self.stack.append(top[1:])
elif top and all(ch.isalnum() or ch == '_' for ch in top): self.stack.append(f'!{top}')
else: self.stack.append(f'NOT({top})')
# Open frames: {'text', 'base', 'sym', 'polarity'}
self.stack: List[dict] = []
# Most recently closed frame per nesting level, for sibling detection
self.closed: dict = {}

def push_ifdef(self, sym: str, base: Optional[int] = None) -> Optional[int]:
return self._push({'text': sym, 'base': base, 'sym': sym, 'polarity': True})

def push_ifndef(self, sym: str, base: Optional[int] = None) -> Optional[int]:
return self._push({'text': f'!{sym}', 'base': base, 'sym': sym, 'polarity': False})

def push_if(self, expr: str, base: Optional[int] = None) -> Optional[int]:
return self._push({'text': normalize_condition_text(expr), 'base': base, 'sym': None, 'polarity': None})

def _push(self, frame: dict) -> Optional[int]:
prev = self.closed.get(len(self.stack))
if prev and frame['sym'] is not None and prev['sym'] == frame['sym'] \
and prev['polarity'] != frame['polarity']:
# mutually-exclusive sibling: restart numbering from the sibling's base
frame['base'] = prev['base']
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
self.stack.append(frame)
return frame['base']

def elif_(self, expr: str) -> Optional[int]:
if not self.stack:
return None
base = self.stack[-1]['base']
self.stack[-1] = {'text': normalize_condition_text(expr), 'base': base, 'sym': None, 'polarity': None}
return base

def else_(self) -> Optional[int]:
if not self.stack:
return None
base = self.stack[-1]['base']
text = self.stack[-1]['text']
if text.startswith('!'):
text = text[1:]
elif text and all(ch.isalnum() or ch == '_' for ch in text):
text = f'!{text}'
else:
text = f'NOT({text})'
self.stack[-1] = {'text': text, 'base': base, 'sym': None, 'polarity': None}
return base

def endif(self):
if self.stack: self.stack.pop()
if not self.stack:
return
top = self.stack.pop()
if top['sym'] is not None:
self.closed[len(self.stack)] = top
else:
self.closed.pop(len(self.stack), None)

def current(self) -> str:
return " AND ".join(self.stack) if self.stack else ""
return " AND ".join(f['text'] for f in self.stack) if self.stack else ""

def has_active(self) -> bool:
return bool(self.stack)

Expand Down Expand Up @@ -164,12 +211,28 @@ def parse_files(paths: List[Path]) -> List[EnumDef]:
while idx < len(body_lines):
bl = body_lines[idx]

# inner preproc
if m := RE_IFDEF.match(bl): inner.push_ifdef(m.group(1)); idx += 1; continue
if m := RE_IFNDEF.match(bl): inner.push_ifndef(m.group(1)); idx += 1; continue
if m := RE_IF.match(bl): inner.push_if(m.group(1)); idx += 1; continue
if m := RE_ELIF.match(bl): inner.elif_(m.group(1)); idx += 1; continue
if RE_ELSE.match(bl): inner.else_(); idx += 1; continue
# inner preproc — reset counter when entering an
# exclusive alternate branch (returns new base)
if m := RE_IFDEF.match(bl):
new_base = inner.push_ifdef(m.group(1), current_numeric)
if new_base is not None: current_numeric = new_base
idx += 1; continue
if m := RE_IFNDEF.match(bl):
new_base = inner.push_ifndef(m.group(1), current_numeric)
if new_base is not None: current_numeric = new_base
idx += 1; continue
if m := RE_IF.match(bl):
new_base = inner.push_if(m.group(1), current_numeric)
if new_base is not None: current_numeric = new_base
idx += 1; continue
if m := RE_ELIF.match(bl):
new_base = inner.elif_(m.group(1))
if new_base is not None: current_numeric = new_base
idx += 1; continue
if RE_ELSE.match(bl):
new_base = inner.else_()
if new_base is not None: current_numeric = new_base
idx += 1; continue
if RE_ENDIF.match(bl): inner.endif(); idx += 1; continue

# accumulate one item across lines
Expand Down
24 changes: 18 additions & 6 deletions docs/development/msp/inav_enums.json
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,8 @@
"BOXGIMBALCENTER": "58",
"BOXGIMBALHTRK": "59",
"BOXAUTOSPEED": "60",
"CHECKBOX_ITEM_COUNT": "61"
"BOXTERRAINAGLHOLD": "61",
"CHECKBOX_ITEM_COUNT": "62"
},
"busIndex_e": {
"_source": "inav/src/main/drivers/bus.h",
Expand Down Expand Up @@ -806,7 +807,8 @@
"CURRENT_SENSOR_SMARTPORT": "5",
"CURRENT_SENSOR_CRSF": "6",
"CURRENT_SENSOR_CAN": "7",
"CURRENT_SENSOR_MAX": "CURRENT_SENSOR_CAN"
"CURRENT_SENSOR_INA226": "8",
"CURRENT_SENSOR_MAX": "CURRENT_SENSOR_INA226"
},
"devHardwareType_e": {
"_source": "inav/src/main/drivers/bus.h",
Expand Down Expand Up @@ -869,7 +871,8 @@
"DEVHW_UG2864": "56",
"DEVHW_SDCARD": "57",
"DEVHW_IRLOCK": "58",
"DEVHW_PCF8574": "59"
"DEVHW_PCF8574": "59",
"DEVHW_INA226": "60"
},
"deviceFlags_e": {
"_source": "inav/src/main/drivers/bus.h",
Expand Down Expand Up @@ -944,6 +947,13 @@
"DJI_OSD_CN_ADJUSTEMNTS": "6",
"DJI_OSD_CN_MAX_ELEMENTS": "7"
},
"dronecanAsyncState_e": {
"_source": "inav/src/main/drivers/dronecan/dronecan.h",
"DRONECAN_ASYNC_IDLE": "0",
"DRONECAN_ASYNC_PENDING": "1",
"DRONECAN_ASYNC_READY": "2",
"DRONECAN_ASYNC_ERROR": "3"
},
"dronecanBitrate_e": {
"_source": "inav/src/main/drivers/dronecan/dronecan.h",
"DRONECAN_BITRATE_125KBPS": "0",
Expand Down Expand Up @@ -2442,7 +2452,7 @@
"USE_AUTO_TRANSITION"
],
"MIXERAT_PHASE_DONE": [
"(5)",
"(3)",
"!USE_AUTO_TRANSITION"
]
},
Expand Down Expand Up @@ -3650,7 +3660,8 @@
"OWNER_PINIO": "32",
"OWNER_IRLOCK": "33",
"OWNER_DRONECAN": "34",
"OWNER_TOTAL_COUNT": "35"
"OWNER_CURRENT_METER": "35",
"OWNER_TOTAL_COUNT": "36"
},
"resourceType_e": {
"_source": "inav/src/main/drivers/resource.h",
Expand Down Expand Up @@ -4355,7 +4366,8 @@
"VOLTAGE_SENSOR_SMARTPORT": "4",
"VOLTAGE_SENSOR_CRSF": "5",
"VOLTAGE_SENSOR_CAN": "6",
"VOLTAGE_SENSOR_MAX": "VOLTAGE_SENSOR_CAN"
"VOLTAGE_SENSOR_INA226": "7",
"VOLTAGE_SENSOR_MAX": "VOLTAGE_SENSOR_INA226"
},
"vs600Band_e": {
"_source": "inav/src/main/io/smartport_master.h",
Expand Down
28 changes: 23 additions & 5 deletions docs/development/msp/inav_enums_ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
- [displayTransactionOption_e](#enum-displaytransactionoption_e)
- [displayWidgetType_e](#enum-displaywidgettype_e)
- [DjiCraftNameElements_t](#enum-djicraftnameelements_t)
- [dronecanAsyncState_e](#enum-dronecanasyncstate_e)
- [dronecanBitrate_e](#enum-dronecanbitrate_e)
- [dronecanState_e](#enum-dronecanstate_e)
- [dshotCommands_e](#enum-dshotcommands_e)
Expand Down Expand Up @@ -1198,7 +1199,8 @@
| `BOXGIMBALCENTER` | 58 | |
| `BOXGIMBALHTRK` | 59 | |
| `BOXAUTOSPEED` | 60 | |
| `CHECKBOX_ITEM_COUNT` | 61 | |
| `BOXTERRAINAGLHOLD` | 61 | |
| `CHECKBOX_ITEM_COUNT` | 62 | |

---
## <a id="enum-busindex_e"></a>`busIndex_e`
Expand Down Expand Up @@ -1425,7 +1427,8 @@
| `CURRENT_SENSOR_SMARTPORT` | 5 | |
| `CURRENT_SENSOR_CRSF` | 6 | |
| `CURRENT_SENSOR_CAN` | 7 | |
| `CURRENT_SENSOR_MAX` | CURRENT_SENSOR_CAN | |
| `CURRENT_SENSOR_INA226` | 8 | |
| `CURRENT_SENSOR_MAX` | CURRENT_SENSOR_INA226 | |

---
## <a id="enum-devhardwaretype_e"></a>`devHardwareType_e`
Expand Down Expand Up @@ -1494,6 +1497,7 @@
| `DEVHW_SDCARD` | 57 | |
| `DEVHW_IRLOCK` | 58 | |
| `DEVHW_PCF8574` | 59 | |
| `DEVHW_INA226` | 60 | |

---
## <a id="enum-deviceflags_e"></a>`deviceFlags_e`
Expand Down Expand Up @@ -1613,6 +1617,18 @@
| `DJI_OSD_CN_ADJUSTEMNTS` | 6 | |
| `DJI_OSD_CN_MAX_ELEMENTS` | 7 | |

---
## <a id="enum-dronecanasyncstate_e"></a>`dronecanAsyncState_e`

> Source: ../../../src/main/drivers/dronecan/dronecan.h

| Enumerator | Value | Condition |
|---|---:|---|
| `DRONECAN_ASYNC_IDLE` | 0 | |
| `DRONECAN_ASYNC_PENDING` | 1 | |
| `DRONECAN_ASYNC_READY` | 2 | |
| `DRONECAN_ASYNC_ERROR` | 3 | |

---
## <a id="enum-dronecanbitrate_e"></a>`dronecanBitrate_e`

Expand Down Expand Up @@ -3593,7 +3609,7 @@
| `MIXERAT_PHASE_TRANSITIONING` | 2 | |
| `MIXERAT_PHASE_POST_SWITCH_FADE` | (3) | USE_AUTO_TRANSITION |
| `MIXERAT_PHASE_TAILSITTER_TO_MC_CAPTURE` | (4) | USE_AUTO_TRANSITION |
| `MIXERAT_PHASE_DONE` | (5) | !USE_AUTO_TRANSITION |
| `MIXERAT_PHASE_DONE` | (3) | !USE_AUTO_TRANSITION |

---
## <a id="enum-mixerprofileatwaitreason_e"></a>`mixerProfileATWaitReason_e`
Expand Down Expand Up @@ -5247,7 +5263,8 @@
| `OWNER_PINIO` | 32 | |
| `OWNER_IRLOCK` | 33 | |
| `OWNER_DRONECAN` | 34 | |
| `OWNER_TOTAL_COUNT` | 35 | |
| `OWNER_CURRENT_METER` | 35 | |
| `OWNER_TOTAL_COUNT` | 36 | |

---
## <a id="enum-resourcetype_e"></a>`resourceType_e`
Expand Down Expand Up @@ -6333,7 +6350,8 @@
| `VOLTAGE_SENSOR_SMARTPORT` | 4 | |
| `VOLTAGE_SENSOR_CRSF` | 5 | |
| `VOLTAGE_SENSOR_CAN` | 6 | |
| `VOLTAGE_SENSOR_MAX` | VOLTAGE_SENSOR_CAN | |
| `VOLTAGE_SENSOR_INA226` | 7 | |
| `VOLTAGE_SENSOR_MAX` | VOLTAGE_SENSOR_INA226 | |

---
## <a id="enum-vs600band_e"></a>`vs600Band_e`
Expand Down
Loading