Skip to content

Add MAVLink device class (traffic_type = 6) - #2

Open
mrpollo wants to merge 1 commit into
Neros-Technologies:mainfrom
Dronecode:mrpollo/mavlink-device-class
Open

Add MAVLink device class (traffic_type = 6)#2
mrpollo wants to merge 1 commit into
Neros-Technologies:mainfrom
Dronecode:mrpollo/mavlink-device-class

Conversation

@mrpollo

@mrpollo mrpollo commented Jul 16, 2026

Copy link
Copy Markdown

This proposes a new device class for payloads that are MAVLink endpoints: a microcontroller on the payload side running its own MAVLink stack, with the flight controller as the APEX Host. APEX negotiates the MAVLink wire version (1 or 2) once during class setup, then becomes a transparent bidirectional byte tunnel (MAVLINK_DATA). The class is deliberately opaque to MAVLink content: no parsing, no sysid/compid rewriting, no routing. Those stay with the endpoints.

Because the tunnel is a byte stream rather than frame-aligned, signed MAVLink 2 frames up to 280 bytes cross the 255-byte APEX payload cap without a fragmentation scheme; the receiver's MAVLink parser recovers frame boundaries from the MAVLink framing itself. This is the main design difference from the frame-aligned Analog HMI class, which has to cap MAVLink 2 frames at 254 bytes (Analog HMI 5.1).

New device class spec for payloads that are MAVLink endpoints: version
negotiation (MAVLink 1/2) at class setup, then a transparent bidirectional
byte tunnel (MAVLINK_DATA). Not frame-aligned, so signed MAVLink 2 frames
up to 280 bytes cross the 255-byte APEX payload cap without a fragmentation
scheme. Registered as traffic_type 6 in the device class registry.
@joseph-neros

Copy link
Copy Markdown
Collaborator

Hello!

I've looked through this and so far, most things seem fine, but we need to account for updating the standard library as well.

Thanks!

@joseph-neros

Copy link
Copy Markdown
Collaborator

Hello! I went through this with a finer toothed comb and found a few areas of improvement to make. See below and the suggested changes comments for my findings.

Findings

  • #​1 — the Host can't reject a bad advertisement. §4.3 tells the Host to reject a malformed CAPABILITY with REJECT_MALFORMED, but that code lives only in the ACK frame (Device→Host). The Host's only message is CONFIG, so the rejection is unimplementable. Fix: give CONFIG a result verdict, mirroring Core's CONFIG_REPLY.
  • #​2 — link loss is terminal. §4.7 sends ACTIVE → FAULT (terminal) on Core watchdog / link loss, contradicting Core §3.5, which restarts discovery. Fix: transport loss → re-discovery; reserve FAULT for unfixable negotiation failures.
  • #​3 — lost ACK/CONFIG → half-open session. The Device goes ACTIVE and streams on ACK, but the Host only goes ACTIVE on receiving that ACK, with no retransmit and no host timeout — and the Core watchdog is masked by the data flow. Fix: Host goes ACTIVE on first ACK or data (implicit ACK) + idempotent CONFIG retransmit.
  • #​4 — keep-alive delegated to the MAVLink app. §4.8 relies on the app's MAVLink HEARTBEAT for the Core 1 Hz floor; a briefly-quiet app then trips the watchdog on a healt
    hy link. Fix: fall back to the Core implicit heartbeat (empty CONFIG).
  • #​5 — §5.1 arithmetic. 12 B header → the MAVLink 2 header is 10 B (10 + 255 + 2 + 13 = 280).

Plus a §3 note pinning down that framing is the only thing this class negotiates (so scope doesn't creep to dialect/signing/routing).

Comment on lines +81 to +86
The Host receives CAPABILITY, intersects the Device's supported versions
against its own, selects exactly one version, and replies with CONFIG
([§4.4](#4-4--config-frame-host--device)). Once the Device's ACK confirms
acceptance, the Host feeds its endpoint's MAVLink byte stream into
MAVLINK_DATA frames toward the Device and reassembles the Device's stream from
inbound MAVLINK_DATA frames.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§2.2 Host — implicit-ACK + reject branch (#​1, #​3):

Suggested change
The Host receives CAPABILITY, intersects the Device's supported versions
against its own, selects exactly one version, and replies with CONFIG
([§4.4](#4-4--config-frame-host--device)). Once the Device's ACK confirms
acceptance, the Host feeds its endpoint's MAVLink byte stream into
MAVLINK_DATA frames toward the Device and reassembles the Device's stream from
inbound MAVLINK_DATA frames.
The Host receives CAPABILITY and intersects the Device's supported versions
against its own. If the intersection is non-empty it selects exactly one
version and replies with an accepting CONFIG; if it is empty (or the
advertisement is malformed) it replies with a rejecting CONFIG
([§4.4](#4-4--config-frame-host--device)). The Host considers the session
ACTIVE on the Device's ACK **or** the first MAVLINK_DATA frame from the Device
— whichever arrives first ([§4.6](#4-6--mavlink_data-frame-bidirectional),
[§4.8](#4-8--reporting-cadence-and-timing)) — after which it feeds its
endpoint's MAVLink byte stream into MAVLINK_DATA frames toward the Device and
reassembles the Device's stream from inbound MAVLINK_DATA frames.

<a id="3--negotiated-parameters" name="3--negotiated-parameters"></a>
## 3. Negotiated Parameters

One parameter is negotiated at the start of every session.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§3 Negotiated Parameters — scope note (scope)

Suggested change
One parameter is negotiated at the start of every session.
One parameter is negotiated at the start of every session — by design, exactly
one.
Version negotiation is the single, deliberate exception to this class's opacity
([§1](#1--overview)). Its only purpose is to spare the endpoints from having to
guess wire framing: rather than each MAVLink stack sniffing start bytes or
falling back heuristically, APEX agrees on MAVLink 1 vs 2 framing up front so
both parsers are configured deterministically from the first byte. That is the
**only** endpoint concern this class takes on. Everything else — dialect,
message set, microservices, `sysid` / `compid`, signing, retransmission, and
rate — stays with the endpoints and rides transparently inside the tunnel
([§1](#1--overview), [§5.3](#5-3--no-mavlink-level-routing)). The class
negotiates framing and nothing more; future revisions extend the tunnel, not
the set of decisions APEX makes on the endpoints' behalf.

| --- | --- | --- | --- |
| `0` | *(reserved)* | — | Reserved; never sent. |
| `1` | **CAPABILITY** | Device → Host | Declares supported MAVLink versions ([§4.3](#4-3--capability-frame-device--host)). |
| `2` | **CONFIG** | Host → Device | Selects one version ([§4.4](#4-4--config-frame-host--device)). |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.1 sub-header — CONFIG row (#​1)

Suggested change
| `2` | **CONFIG** | Host → Device | Selects one version ([§4.4](#4-4--config-frame-host--device)). |
| `2` | **CONFIG** | Host → Device | The Host's verdict on the advertisement: accept and select a version, or reject ([§4.4](#4-4--config-frame-host--device)). |

1. Core discovery completes for `device_class_req = 6` ([APEX — Core §3.3](APEX_Core.md#3-3--startup-discovery-handshake)). Class traffic on `traffic_type = 6` becomes valid.
2. Device immediately emits **CAPABILITY** ([§4.3](#4-3--capability-frame-device--host)) — unprompted, exactly once.
3. Host emits **CONFIG** ([§4.4](#4-4--config-frame-host--device)) with the chosen MAVLink version.
4. Device replies **ACK** ([§4.5](#4-5--ack-frame-device--host)). On `ACCEPTED`, the session enters ACTIVE.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.2 lifecycle — steps 3–4 (#​1)

Suggested change
4. Device replies **ACK** ([§4.5](#4-5--ack-frame-device--host)). On `ACCEPTED`, the session enters ACTIVE.
3. Host evaluates the advertisement and emits **CONFIG** ([§4.4](#4-4--config-frame-host--device)): either `ACCEPTED` with the chosen MAVLink version, or a reject code if it cannot satisfy the advertisement.
4. On an accepting CONFIG the Device adopts the version and replies **ACK** ([§4.5](#4-5--ack-frame-device--host)); on `ACCEPTED` the session enters ACTIVE. On a rejecting CONFIG the Device sends no ACK and transitions to FAULT ([§4.7](#4-7--state-machine-device)).

| `3` | `intended_rate_hz` | `u8` | The Device's intended steady-state MAVLINK_DATA frame rate, in Hz. Informational; the Host MAY use it to size buffers but MUST NOT enforce it. `0` = unspecified / event-driven. |

`supported_versions == 0` is malformed; the Host MUST reject with
`REJECT_MALFORMED` ([§4.5](#4-5--ack-frame-device--host)).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.3 CAPABILITY — reject pointer (#​1) — replace the two lines supported_versions == 0 is malformed; the Host MUST reject with / REJECT_MALFORMED ([§4.5]…).`:

Suggested change
`REJECT_MALFORMED` ([§4.5](#4-5--ack-frame-device--host)).
`supported_versions == 0` is malformed; the Host MUST reject the advertisement
via CONFIG with `REJECT_MALFORMED` ([§4.4](#4-4--config-frame-host--device)).


The Host MUST send exactly one CONFIG in response to each CAPABILITY. If the
Device sees a second CONFIG with a different parameter, it MUST reply
`REJECT_MALFORMED` and remain in its current state.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.4 CONFIG — add result verdict + idempotent retransmit (#​1, #​3)

Suggested change
`REJECT_MALFORMED` and remain in its current state.
CONFIG is the Host's verdict on the Device's CAPABILITY advertisement — the
accept/reject half of the class negotiation. It mirrors Core's `CONFIG_REPLY`
([APEX — Core §3.2.2](APEX_Core.md#3-2-2--config_reply-msg_id--2)): a single
reply that either accepts and selects the parameter, or rejects with a reason.
| Offset | Field | Width | Description |
| --- | --- | --- | --- |
| `0` | `class_msg_id` | `u8` | `2` (CONFIG). |
| `1` | `result` | `u8` | The Host's verdict (see below). |
| `2` | `selected_version` | `u8` | On `ACCEPTED`, exactly one value from [§3.1](#3-1--mavlink-version); it MUST be a value the Device declared as supported. On any reject, `0x00`. |
**`result` values:**
| Value | Name | Meaning |
| --- | --- | --- |
| `0x00` | `ACCEPTED` | The Host supports one of the advertised versions and has selected it in `selected_version`. |
| `0x01` | `REJECT_NO_COMMON_VERSION` | The Host supports none of the versions in the Device's `supported_versions`. |
| `0x02` | `REJECT_MALFORMED` | CAPABILITY could not be parsed (e.g. `supported_versions == 0`). |
A Device that receives a `result` value not enumerated above MUST treat it as
`REJECT_NO_COMMON_VERSION`. On any reject the Device transitions to FAULT
without sending ACK ([§4.7](#4-7--state-machine-device)); the Host SHOULD
surface the specific reject code to the operator, which is the only signal a
passive operator has that setup failed.
The Host sends one CONFIG per CAPABILITY, but MAY **retransmit that identical
CONFIG** if it observes neither ACK nor MAVLINK_DATA within the ACK timeout
([§4.8](#4-8--reporting-cadence-and-timing)). A retransmitted CONFIG is
idempotent: a Device that receives a CONFIG identical to one it has already
accepted MUST re-send its ACK ([§4.5](#4-5--ack-frame-device--host)) and remain
ACTIVE, so a lost ACK is recovered without disturbing the tunnel. A second
CONFIG whose `result` or `selected_version` **differs** from the first is a
protocol violation: the Device MUST reply `REJECT_MALFORMED` and remain in its
current state.

A rejecting ACK is **terminal in v1**: the Device transitions to FAULT
([§4.7](#4-7--state-machine-device)) and the operator must intervene. A Host
that cannot satisfy a Device's CAPABILITY SHOULD surface the specific reject
code to the operator; that's the only signal a passive operator has.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.5 ACK — clarify it's a confirmation; scope reject codes (#​1)

Suggested change
code to the operator; that's the only signal a passive operator has.
ACK is the Device's confirmation of an **accepting** CONFIG — the Core
`CONFIG_ACK` analog ([APEX — Core §3.2.6](APEX_Core.md#3-2-6--config_ack-msg_id--6)).
The Device sends ACK only in response to a CONFIG whose `result` is `ACCEPTED`;
a rejecting CONFIG is not acknowledged ([§4.4](#4-4--config-frame-host--device)).
| Offset | Field | Width | Description |
| --- | --- | --- | --- |
| `0` | `class_msg_id` | `u8` | `3` (ACK). |
| `1` | `result` | `u8` | Result code (see below). |
**`result` values:**
| Value | Name | Meaning |
| --- | --- | --- |
| `0x00` | `ACCEPTED` | The Device has adopted the selected MAVLink version; MAVLINK_DATA exchange may begin. |
| `0x01` | `REJECT_VERSION` | The accepting CONFIG's `selected_version` is not a value the Device declared as supported — a Host error, since the Host MUST only select an advertised version ([§4.4](#4-4--config-frame-host--device)). |
| `0x02` | `REJECT_MALFORMED` | The CONFIG frame could not be parsed. |
A rejecting ACK is **terminal in v1**: the Device transitions to FAULT
([§4.7](#4-7--state-machine-device)) and the operator must intervene. The Host
SHOULD surface the specific reject code to the operator; that's the only signal
a passive operator has.

then discards the truncated message and resynchronizes on the next start byte.
The class layer does **not** retransmit; reliability for messages that need it
is a MAVLink-level concern (e.g. command/ack, param retry) handled by the
endpoints.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.6 MAVLINK_DATA — scope the "no retransmit" statement (#​3, consistency)

Suggested change
endpoints.
The class layer does **not** retransmit dropped MAVLINK_DATA; reliability for
tunnelled messages that need it is a MAVLink-level concern (e.g. command/ack,
param retry) handled by the endpoints. (Setup is the one exception: the Host
retransmits CONFIG to recover a lost ACK — [§4.4](#4-4--config-frame-host--device),
[§4.8](#4-8--reporting-cadence-and-timing).)

endpoints.

MAVLINK_DATA frames are sent **unprompted** in both directions. Each side's
cadence is whatever its MAVLink stack produces; there is no per-frame ACK.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.6 MAVLINK_DATA — implicit-ACK on receive (#​3)

Suggested change
cadence is whatever its MAVLink stack produces; there is no per-frame ACK.
MAVLINK_DATA frames are sent **unprompted** in both directions. Each side's
cadence is whatever its MAVLink stack produces; there is no per-frame ACK.
A MAVLINK_DATA frame from a Device the Host still considers **pre-ACTIVE**
implicitly confirms acceptance: a Device emits MAVLINK_DATA only after it has
accepted CONFIG and entered ACTIVE ([§4.7](#4-7--state-machine-device)), so the
Host MUST transition that session to ACTIVE and reassemble the frame's bytes
rather than discard them. This is what recovers a lost ACK — the tunnel comes
up on the first data frame even if the ACK never arrived
([§4.8](#4-8--reporting-cadence-and-timing)).

The Host does not run an explicit state machine for this class beyond
"pre-CONFIG" / "ACTIVE" per device — the device's Core lifecycle status
([APEX — Core §4](APEX_Core.md#4--device-lifecycle-status)) tells it
everything else it needs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.7 state machine — link loss → re-discovery, scope FAULT (#​2, #​3)

Suggested change
everything else it needs.
| `0xFF` | **FAULT** | Class negotiation failed in a way retrying cannot fix — CONFIG rejected by the Host, or the Device rejected a malformed/impossible CONFIG ([§4.4](#4-4--config-frame-host--device), [§4.5](#4-5--ack-frame-device--host)). Terminal in v1; requires operator intervention. |
```mermaid
stateDiagram-v2
[*] --> WAITING_CONFIG: Class active (Core ACK_OK)
WAITING_CONFIG --> ACTIVE: CONFIG ACCEPTED, ACK ACCEPTED
WAITING_CONFIG --> FAULT: CONFIG reject (Host) / ACK reject (Device)
WAITING_CONFIG --> [*]: No CONFIG in 5 s / link loss → re-discovery
ACTIVE --> ACTIVE: Duplicate identical CONFIG (re-ACK)
ACTIVE --> [*]: Core watchdog / link loss → re-discovery
FAULT --> [*]
```
**Transport loss is not a FAULT.** Loss of bus liveliness — a Core watchdog
expiry or link loss ([APEX — Core §3.5](APEX_Core.md#3-5--heartbeat)) — is
transient and recoverable, so the Device does **not** enter FAULT on it.
Instead it leaves the class state machine, returns to its Core initial state,
and restarts the discovery handshake
([APEX — Core §3.3](APEX_Core.md#3-3--startup-discovery-handshake)); a
successful re-discovery brings it back to WAITING_CONFIG. The same applies if no
CONFIG arrives within the 5 s setup window. FAULT is reserved for a negotiation
outcome that re-discovery would only reproduce — an incompatible or malformed
CONFIG — which genuinely needs a human.
The Host does not run an explicit state machine for this class beyond
"pre-CONFIG" / "ACTIVE" per device — the device's Core lifecycle status
([APEX — Core §4](APEX_Core.md#4--device-lifecycle-status)) tells it
everything else it needs. It enters ACTIVE on the first ACK **or** MAVLINK_DATA
from the device ([§2.2](#2-2--host), [§4.6](#4-6--mavlink_data-frame-bidirectional)),
and retransmits the identical CONFIG if neither arrives in time
([§4.8](#4-8--reporting-cadence-and-timing)).

endpoint's MAVLink stack falls silent, the side MUST still meet the Core
transmit floor — a MAVLink HEARTBEAT (emitted by every conformant MAVLink
endpoint at ≥ 1 Hz) naturally satisfies this; the class layer adds no
keep-alive of its own.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§4.8 timing — host retransmit + heartbeat fallback (#​2, #​3, #​4)

Suggested change
keep-alive of its own.
- **CONFIG.** The Host SHOULD emit CONFIG within **500 ms** of receiving
CAPABILITY. A Device that has not received CONFIG within **5 s** returns to
its initial state and restarts discovery ([§4.7](#4-7--state-machine-device))
rather than faulting — a lost CAPABILITY or CONFIG is recoverable.
- **ACK.** The Device MUST emit ACK within **200 ms** of receiving an accepting
CONFIG. A rejecting CONFIG is not acknowledged ([§4.5](#4-5--ack-frame-device--host)).
- **CONFIG retransmit (Host).** The Host starts the session ACTIVE on the first
ACK **or** MAVLINK_DATA it receives after sending an accepting CONFIG
([§2.2](#2-2--host), [§4.6](#4-6--mavlink_data-frame-bidirectional)). If it
observes neither within **500 ms**, it retransmits the identical CONFIG
([§4.4](#4-4--config-frame-host--device)), for up to **3 attempts** total. If
none elicits a response, the Host treats the device as unresponsive and
applies the Core [§3.5](APEX_Core.md#3-5--heartbeat) recovery — reset via Pin
9 and re-discovery, or mark it FAULT at the Core lifecycle level
([APEX — Core §4](APEX_Core.md#4--device-lifecycle-status)) (this is the
host-side lifecycle FAULT for a dead device, not the class-state FAULT of
[§4.7](#4-7--state-machine-device)). All three attempts fall well inside the
Device's 5 s setup window, so a Device never abandons setup while the Host is
still retrying.
- **MAVLINK_DATA.** Exchange begins immediately after the Device sends
`ACCEPTED`. While MAVLink bytes are flowing, MAVLINK_DATA traffic in each
direction satisfies the Core [§3.5](APEX_Core.md#3-5--heartbeat) 1 Hz transmit
floor on that side — and in normal operation a conformant MAVLink endpoint
emits a HEARTBEAT at ≥ 1 Hz, so the tunnel rarely goes quiet. If a side's
MAVLink stream does fall silent, it MUST still meet the Core transmit floor by
emitting the **Core implicit heartbeat** — an empty `CONFIG` frame
(`traffic_type = 0`, `payload_length = 0`;
[APEX — Core §3.5](APEX_Core.md#3-5--heartbeat)) — not by depending on the
MAVLink application to produce a frame. Liveness is thus a property of the
APEX layer, so a briefly quiet endpoint never trips the watchdog.

<a id="5-1--mavlink-frame-size" name="5-1--mavlink-frame-size"></a>
### 5.1. MAVLink frame size

A MAVLink 2 frame can be up to 280 bytes (12 B header + 255 B payload + 2 B

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§5.1 — header byte count (#​5) — replace the line A MAVLink 2 frame can be up to 280 bytes (12 B header + 255 B payload + 2 B:

Suggested change
A MAVLink 2 frame can be up to 280 bytes (12 B header + 255 B payload + 2 B
A MAVLink 2 frame can be up to 280 bytes (10 B header + 255 B payload + 2 B

| Byte(s) | Hex | Field | Value |
| --- | --- | --- | --- |
| 4 | `02` | `class_msg_id` | `2` (CONFIG) |
| 5 | `01` | `selected_version` | `1` (MAVLINK2) |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§6.3 Step 2 — worked example bytes (#​1)

Suggested change
| 5 | `01` | `selected_version` | `1` (MAVLINK2) |
Inner payload 3 bytes (`LN = 03`).
```
00 06 01 03 02 00 01
```
| Byte(s) | Hex | Field | Value |
| --- | --- | --- | --- |
| 4 | `02` | `class_msg_id` | `2` (CONFIG) |
| 5 | `00` | `result` | `ACCEPTED` |
| 6 | `01` | `selected_version` | `1` (MAVLINK2) |

A MAVLink 2 COMMAND_LONG (one full frame). Identical structure to Step 4; only
the direction and content differ. If the Host had a 280-byte signed frame to
send, it would split across two MAVLINK_DATA frames — the Device's MAVLink
parser reassembles it transparently ([§5.1](#5-1--mavlink-frame-size)).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

§6.4 — new rejected-setup example (#​1) — this is an insertion. Select the last two lines of §6.3 Step 5 (send, it would split across two MAVLINK_DATA frames … and the following parser reassembles it transparently ([§5.1](#5-1--mavlink-frame-size)).) and replace with the same two lines plus the new section (4-backtick wrapper — contains fences):

Suggested change
parser reassembles it transparently ([§5.1](#5-1--mavlink-frame-size)).
send, it would split across two MAVLINK_DATA frames — the Device's MAVLink
parser reassembles it transparently ([§5.1](#5-1--mavlink-frame-size)).
<a id="6-4--rejected-setup" name="6-4--rejected-setup"></a>
### 6.4. Rejected setup (no common version)
If instead the Device advertised MAVLink 1 only (`supported_versions = 0x01`)
while the Host speaks MAVLink 2 only, the Host answers the advertisement with a
rejecting CONFIG and no tunnel opens.
```mermaid
sequenceDiagram
participant H as Host
participant D as Device
D->>H: CAPABILITY (MAVLink 1 only)
H->>D: CONFIG (REJECT_NO_COMMON_VERSION)
Note over H,D: Device → FAULT. No ACK. Operator intervenes.
```
Host CONFIG, inner payload 3 bytes (`LN = 03`):
```
00 06 01 03 02 01 00
```
| Byte(s) | Hex | Field | Value |
| --- | --- | --- | --- |
| 4 | `02` | `class_msg_id` | `2` (CONFIG) |
| 5 | `01` | `result` | `REJECT_NO_COMMON_VERSION` |
| 6 | `00` | `selected_version` | `0` (unused on reject) |

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