diff --git a/cspell.json b/cspell.json index ea40d19ec..3f57c3596 100644 --- a/cspell.json +++ b/cspell.json @@ -31,6 +31,7 @@ "COLORMODE", "compid", "configurators", + "constexpr", "CRCS", "CREAT", "cubepilot", @@ -100,6 +101,7 @@ "multicopter", "multicopters", "mydescriptivebranchname", + "nullptr", "Nutt", "nuttx", "OCTOROTOR", diff --git a/en/SUMMARY.md b/en/SUMMARY.md index cc79ca262..30f6df18d 100644 --- a/en/SUMMARY.md +++ b/en/SUMMARY.md @@ -12,6 +12,9 @@ - [Examples](mavgen_c/examples.md) - [UART Interface (C)](mavgen_c/example_c_uart.md) - [UDP Example (C)](mavgen_c/example_c_udp.md) + - [C++ 11 (mavgen)](mavgen_cpp/index.md) + - [Examples](mavgen_cpp/examples.md) + - [UDP Example (C++)](mavgen_cpp/example_cpp_udp.md) - [Pymavlink (Python-mavgen)](mavgen_python/index.md) - [How to Request Messages/Set Message Rates](mavgen_python/howto_requestmessages.md) - [Message Signing](mavgen_python/message_signing.md) diff --git a/en/mavgen_cpp/example_cpp_udp.md b/en/mavgen_cpp/example_cpp_udp.md new file mode 100644 index 000000000..a885bdaac --- /dev/null +++ b/en/mavgen_cpp/example_cpp_udp.md @@ -0,0 +1,74 @@ +# MAVLink C++ UDP Example + +The [MAVLink C++ UDP Example](https://github.com/mavlink/mavlink/tree/master/examples/cpp) is a simple C++ 11 example that sends and receives MAVLink HEARTBEATs over UDP. + +It is the C++ 11 counterpart to the [C UDP example](../mavgen_c/example_c_udp.md): instead of the C pack/decode functions, it uses the generated message structs (e.g. `mavlink::minimal::msg::HEARTBEAT`) together with `mavlink::MsgMap` to serialize and deserialize to and from a `mavlink_message_t`. + +::: info +The example should work on any Unix-like system (Linux, MacOS, BSD, etc.). +These instructions were tested on a _Ubuntu LTS 22.04_ installation with either PX4 or ArduPilot dependencies installed (such as cmake). +::: + +::: warning +The C++ 11 library requires you to provide a `mavlink::mavlink_get_msg_entry()` function so that `mavlink_parse_char()` can look up the length and CRC extra of incoming messages. +This example builds it from the dialect's `MESSAGE_ENTRIES` table (see the top of `udp_example.cpp`, and the [Parsing Packets](index.md#parsing-packets) section for more detail). +::: + +## Building/Running the Example + +The following instructions show how to build and run the example. + +1. Clone the [mavlink/mavlink](https://github.com/mavlink/mavlink/) repository +2. Open a terminal in the repository root. +3. Use `cmake` to install MAVLink locally: + + ```sh + cmake -Bbuild -H. -DCMAKE_INSTALL_PREFIX=install + cmake --build build --target install + ``` + + This installs both the C (`*.h`) and C++ 11 (`*.hpp`) headers. + +4. Navigate to [examples/cpp](https://github.com/mavlink/mavlink/tree/master/examples/cpp) + + ```sh + cd examples/cpp + ``` + +5. Use `cmake` to compile and build the example: + + ```sh + cmake -Bbuild -H. -DCMAKE_PREFIX_PATH=$(pwd)/../../install + cmake --build build + ``` + +6. Run the executable from the terminal: + + ```sh + ./build/udp_example + ``` + + By default, the example will listen for data on the localhost IP address, port 14550. + +7. Open another terminal on the same machine and start either PX4 or ArduPilot. + These publish to port 14550 on localhost by default. +8. The example should start displaying messages about sent and received HEARTBEAT messages in the terminal. + The following output is displayed if you connect to PX4: + + ```sh + ~/github/mavlink/mavlink/examples/cpp$ ./build/udp_example + + Sent heartbeat + Got heartbeat from PX4 autopilot + Sent heartbeat + Got heartbeat from PX4 autopilot + Sent heartbeat + Got heartbeat from PX4 autopilot + Sent heartbeat + Got heartbeat from PX4 autopilot + Sent heartbeat + Got heartbeat from PX4 autopilot + ... + ``` + +Note that the build and installation instructions are from [examples/cpp/README.md](https://github.com/mavlink/mavlink/blob/master/examples/cpp/README.md). diff --git a/en/mavgen_cpp/examples.md b/en/mavgen_cpp/examples.md new file mode 100644 index 000000000..ec283e667 --- /dev/null +++ b/en/mavgen_cpp/examples.md @@ -0,0 +1,3 @@ +# Examples + +- [UDP Example](../mavgen_cpp/example_cpp_udp.md): Simple C++ 11 example of a MAVLink UDP interface for Unix-like systems (Linux, MacOS, BSD, etc.). diff --git a/en/mavgen_cpp/index.md b/en/mavgen_cpp/index.md new file mode 100644 index 000000000..ed5aeeda2 --- /dev/null +++ b/en/mavgen_cpp/index.md @@ -0,0 +1,372 @@ +# Using C++ 11 (mavgen) + +The MAVLink C++ 11 library generated by _mavgen_ is a header-only implementation that is highly optimized for resource-constrained systems with limited RAM and flash memory. + +The C++ 11 library is a thin wrapper around the [C library](../mavgen_c/index.md): parsing/framing (`mavlink_parse_char()`, channels, MAVLink 1 interop) is unchanged, but messages are represented as C++ structs with `serialize()`/`deserialize()` methods instead of being packed/decoded via free functions, and everything is scoped under the `mavlink` namespace. + +This topic explains how to get and use the library. + +## Getting the C++ 11 MAVLink Library {#get_libraries} + +Unlike the C library, there is no pre-built C++ 11 library repository to download. +You will always need to [install mavgen](../getting_started/installation.md) and [generate](../getting_started/generate_libraries.md) the headers yourself (using `--lang=C++11`), for whichever dialect(s) you need. + +The libraries can be placed/generated anywhere in your project tree. + +## Adding Library to C++ Project + +This example below assumes the MAVLink headers to be located in: **generated/include/mavlink/**. + +To use MAVLink in your C++ project, include the **common.hpp** header file for your dialect: + +```cpp +#include +``` + +This will automatically add the header files for all messages in your dialect, and for any dialect files that it includes. +Everything the library provides — message structs, enums, and the framing functions inherited from the C layer (e.g. `mavlink_parse_char()`) — is scoped under the `mavlink` namespace, with each dialect getting its own nested namespace (e.g. `mavlink::common`). + +::: warning +Only include the header file for a single dialect. +If you need to support messages from a _number of dialects_ then create a new "parent" dialect XML file that includes them (and use its generated header as shown above). +::: + +::: tip +_Do not include the individual message files_. +If you generate your own headers, you will have to add their output location to your C++ compiler's search path. +::: + +When compiling the project, make sure to add the include directory and enable C++11 (or later): + +```sh +g++ -std=c++11 ... -I generated/include ... +``` + +## Adding Library to Cmake Project + +To include the headers in cmake, install them locally, e.g. into the directory `install`: + +```sh +cmake -Bbuild -H. -DCMAKE_INSTALL_PREFIX=install -DMAVLINK_DIALECT=common -DMAVLINK_VERSION=2.0 +cmake --build build --target install +``` + +This single install step generates and installs _both_ the C (`*.h`) and C++ 11 (`*.hpp`) headers (controlled by the `MAVLINK_ENABLE_CXX` cmake option, which defaults to `ON`) — there's no separate install command for the C++ headers. + +Then use `find_package` to get the dependency in `CMakeLists.txt`: + +```cmake +find_package(MAVLink REQUIRED) + +add_executable(my_program my_program.cpp) +target_compile_features(my_program PRIVATE cxx_std_11) + +target_link_libraries(my_program PRIVATE MAVLink::mavlink) +``` + +And pass the local install directory to cmake (adapt to your directory structure): + +```sh +cd ../my_program +cmake -Bbuild -H. -DCMAKE_PREFIX_PATH=../mavlink/install +``` + +### Build Warnings + +#### `-Waddress-of-packed-member` + +Building the headers may result in warnings like: + +``` +mavlink/common/../mavlink_helpers.h:86:24: warning: taking address of packed member of ‘__mavlink_message’ may result in an unaligned pointer value [-Waddress-of-packed-member] + 86 | crc_accumulate_buffer(&msg->checksum, _MAV_PAYLOAD(msg), msg->len); +``` + +The warning indicates the potential for hard faults caused by unaligned access to packed data. +This does not happen on most of the common architectures on which MAVLink is run, and generally the warning can be suppressed. + +You can suppress the warnings using `-Wno-address-of-packed-member`. + +::: info +The issue causes hard faults on [Cortex-M0](https://github.com/ArduPilot/pymavlink/issues/5) and other platforms [listed here](https://github.com/ArduPilot/pymavlink/issues/836#issue-1788623502). +Please raise issues in [ArduPilot/pymavlink](https://github.com/ArduPilot/pymavlink/) if you find other hardware that is affected. +::: + +## MAVLink 1 + +The C++ 11 library only ever generates _MAVLink 2_ headers — there is no C++ 11 equivalent of the old MAVLink-1-only [c_library_v1](https://github.com/mavlink/c_library_v1) to upgrade from. + +This doesn't stop you interoperating with MAVLink-1-only systems: the generated _MAVLink 2_ headers can send/receive _MAVLink 1_ framed packets on a channel-by-channel basis, in exactly the same way as the C library. +See [Working with MAVLink 1](#mavlink_1) below. + +## Multiple Streams ("channels") {#channels} + +The MAVLink library utilizes a "channel" metaphor to allow for simultaneous processing of multiple, independent MAVLink streams in the same program. +All receiving and transmitting functions provided by this library require a channel, and it is important to use the correct channel for each operation. + +By default up to 16 channels may be defined on Windows, Linux and macOS, and up to 4 channels may be define on other systems. +Systems can specify a different maximum number of channels/comms buffers using `MAVLINK_COMM_NUM_BUFFERS` (for example, this might be reduced to 1 if running MAVLink on very memory constrained hardware). + +If only one MAVLink stream exists, channel 0 should be used by specifying the `mavlink::MAVLINK_COMM_0` constant. + +## Receiving + +MAVLink reception/decoding is done in a number of phases: + +1. Parse the incoming stream into objects representing each packet (`mavlink::mavlink_message_t`). +2. Deserialize the MAVLink message contained in the packet payload into a generated C++ struct (that has fields mapping the original XML definition). + +These steps are demonstrated below. + +### Parsing Packets + +The `mavlink::mavlink_parse_char(...)` convenience function (inherited from [mavlink_helpers.h](https://github.com/mavlink/c_library_v2/blob/master/mavlink_helpers.h)) is used to parse incoming MAVLink data. +The function parses the data one byte at a time, returning 0 (`MAVLINK_FRAMING_INCOMPLETE`) as it progresses, and 1 (`MAVLINK_FRAMING_OK`) on successful decoding of a packet. +The `r_mavlink_status` parameter is updated with the channel status/errors as decoding progresses (you can check `mavlink_status_t.msg_received` to get the current byte's decode status/error and `mavlink_status_t.parse_state` for the current parse state). +On successful decoding of a packet, the `r_message` argument is populated with an object representing the decoded packet. + +The function prototype and parameters are shown below: + +```cpp +MAVLINK_HELPER uint8_t mavlink_parse_char(uint8_t chan, uint8_t c, mavlink_message_t* r_message, mavlink_status_t* r_mavlink_status) +``` + +Parameters: + +- `uint8_t chan`: ID of the current channel. +- `uint8_t c`: The char to parse. +- `mavlink_message_t* r_message`: On success, the decoded message. NULL if the message couldn't be decoded. +- `mavlink_status_t* r_mavlink_status`: The channel statistics, including information about the current parse state. + +Returns: `0` if the packet decoding is incomplete. `1` if the packet successfully decoded. + +The code fragment below shows the typical use of this function when reading data from a socket/serial port (`socket_fd`): + +```cpp +#include + +mavlink::mavlink_status_t status; +mavlink::mavlink_message_t message; +int chan = mavlink::MAVLINK_COMM_0; + +for (int i = 0; i < num_bytes_read; ++i) { + if (mavlink::mavlink_parse_char(chan, buffer[i], &message, &status) == 1) { + printf("Received message with ID %d, sequence: %d from component %d of system %d\n", + message.msgid, message.seq, message.compid, message.sysid); + // ... DECODE THE MESSAGE PAYLOAD HERE ... + } +} +``` + +::: warning +Unlike the C library, the C++ 11 generator does **not** emit a `mavlink::mavlink_get_msg_entry()` lookup function. +`mavlink_parse_char()` needs this internally to find the length and CRC extra of an incoming message id, so you must provide it yourself before parsing will work. + +The simplest approach is to build it from the dialect's `MESSAGE_ENTRIES` table (which is sorted by message id), as shown in the [UDP example](example_cpp_udp.md): + +```cpp +namespace mavlink { + +const mavlink_msg_entry_t* mavlink_get_msg_entry(uint32_t msgid) +{ + for (const auto& entry : common::MESSAGE_ENTRIES) { + if (entry.msgid == msgid) { + return &entry; + } + } + return nullptr; +} + +} // namespace mavlink +``` + +Omitting this results in a linker error, since `mavlink_parse_char()` declares (but doesn't define) `mavlink_get_msg_entry()`. +::: + +### Decoding the Payload {#decode_payload} + +The message/packet object retrieved in the previous section (`mavlink::mavlink_message_t`) contains fields in the [MAVLink packet/serialization](../guide/serialization.md) format - including the message id (`msgid`) and the payload (`payload64`). + +To get the fields of the _specific message_ in the packet you need to further decode the payload. +This is typically done by providing a `switch` statement that maps the _ids_ of the messages you wish to decode to a generated message struct, then calling `.deserialize()` on it via a `mavlink::MsgMap`: + +```cpp +if (mavlink::mavlink_parse_char(chan, byte, &message, &status) == 1) { + switch (message.msgid) { + case mavlink::common::msg::GLOBAL_POSITION_INT::MSG_ID: + { + // Deserialize the whole payload into the generated struct. + mavlink::common::msg::GLOBAL_POSITION_INT global_position; + mavlink::MsgMap map(&message); + global_position.deserialize(map); + } + break; + default: + break; + } +} +``` + +Each generated message struct is a plain C++ class with fields mapping to the original XML message definition, plus a handful of static members used by the framing layer: + +```cpp +namespace mavlink { +namespace common { +namespace msg { + +struct GLOBAL_POSITION_INT : mavlink::Message { + static constexpr msgid_t MSG_ID = 33; + static constexpr size_t LENGTH = 28; + static constexpr size_t MIN_LENGTH = 28; + static constexpr uint8_t CRC_EXTRA = 104; + + uint32_t time_boot_ms; /*< [ms] Timestamp (time since system boot). */ + int32_t lat; /*< [degE7] Latitude, expressed */ + int32_t lon; /*< [degE7] Longitude, expressed */ + int32_t alt; /*< [mm] Altitude (MSL). */ + int32_t relative_alt; /*< [mm] Altitude above ground */ + int16_t vx; /*< [cm/s] Ground X Speed (Latitude, positive north) */ + int16_t vy; /*< [cm/s] Ground Y Speed (Longitude, positive east) */ + int16_t vz; /*< [cm/s] Ground Z Speed (Altitude, positive down) */ + uint16_t hdg; /*< [cdeg] Vehicle heading (yaw angle) */ + + void serialize(MsgMap& map) const override { ... } + void deserialize(MsgMap& map) override { ... } +}; + +} // namespace msg +} // namespace common +} // namespace mavlink +``` + +There are no separate "get a single field" decoder functions as there are in C — deserialize the whole struct and read the field you need. + +### Command Decoding {#decode_command} + +A [MAVLink Command](../services/command.md) encodes a command defined in a [MAV_CMD](../messages/common.md#mav_commands) enum value into a [COMMAND_INT](../messages/common.md#COMMAND_INT) or [COMMAND_LONG](../messages/common.md#COMMAND_LONG) message. + +Command packets are parsed and decoded in the same way as [any other payload](#decode_payload) - i.e. you switch on `mavlink::common::msg::COMMAND_INT::MSG_ID`/`mavlink::common::msg::COMMAND_LONG::MSG_ID` and deserialize into the corresponding generated struct. + +::: info +The message types differ in that `COMMAND_INT` has `int32` types for parameter fields 6 and 7 (instead of `float`) and also includes a field for the geometric frame of reference of any positional information in the command. +::: + +To decode the specific command you then switch on the value of the deserialized struct's `command` field, cast to `mavlink::common::MAV_CMD`. + +Further interpretation and handling of the message then has to be done manually (there are no convenience functions). + +### Additional Checks + +The library have a number of `#define` values that you can set to enable various features: + +- `MAVLINK_CHECK_MESSAGE_LENGTH`: + Enable this option to check the length of each message. + This allows invalid messages to be caught much sooner. + Use if the transmission medium is prone to missing (or extra) characters (e.g. a radio that fades in and out). + Only use if the channel will only contain messages types listed in the headers. + +## Transmitting + +Transmitting messages means filling in a generated message struct, serializing it, then finalizing and sending the resulting packet: + +```cpp +// Fill in the generated HEARTBEAT struct. +mavlink::minimal::msg::HEARTBEAT heartbeat; +heartbeat.type = static_cast(mavlink::minimal::MAV_TYPE::GENERIC); +heartbeat.autopilot = static_cast(mavlink::minimal::MAV_AUTOPILOT::GENERIC); +heartbeat.base_mode = 0; +heartbeat.custom_mode = 0; +heartbeat.system_status = static_cast(mavlink::minimal::MAV_STATE::STANDBY); + +// Serialize it into a mavlink_message_t ... +mavlink::mavlink_message_t message; +mavlink::MsgMap map(message); +heartbeat.serialize(map); + +// ... and finalize it by adding the header and CRC. This is where the +// system and component id as well as the sequence number get filled in. +mavlink::mavlink_finalize_message_chan( + &message, + system_id, + static_cast(mavlink::minimal::MAV_COMPONENT::COMP_ID_PERIPHERAL), + mavlink::MAVLINK_COMM_0, + heartbeat.MIN_LENGTH, + heartbeat.LENGTH, + heartbeat.CRC_EXTRA); + +// Now convert the message into a byte buffer to send it out. +uint8_t buffer[MAVLINK_MAX_PACKET_LEN]; +const int len = mavlink::mavlink_msg_to_send_buffer(buffer, &message); +// ... write buffer[0..len) to your transport (UART, UDP socket, etc.) +``` + +Note that the message struct's own `MIN_LENGTH`, `LENGTH` and `CRC_EXTRA` static members are passed to `mavlink_finalize_message_chan()` — there's no separate table to look these up from, unlike raw use of the C API. + +Enum fields (e.g. `MAV_TYPE`, `MAV_AUTOPILOT`) are C++ 11 scoped enums (`enum class`), so they need an explicit `static_cast` when writing them into a struct field, and a `static_cast` back to the enum type when reading a field for comparison (see [Decoding the Payload](#decode_payload)). + +## Message Signing + +[Message Signing (authentication)](../guide/message_signing.md) allows a _MAVLink 2_ system to verify that messages originate from a trusted source. + +The signing mechanism is implemented in the shared C layer that the C++ 11 library builds on, so it works the same way as for the C library. +The topic [C Message Signing](../mavgen_c/message_signing_c.md) explains the remaining code that a system must implement to enable signing (the API used is namespaced under `mavlink::` as elsewhere in this topic). + +## Connection/Heartbeat + +The sections above explain how you can send and receive messages. +What messages are sent/received depends on the systems that you're working with. +The set of messages that most systems can send are documented in [common.xml](../messages/common.md) and there are various microservices [microservices](../services/index.md) that you may want to use. + +Minimally MAVLink components should implement the [HEARTBEAT/Connection protocol](../services/heartbeat.md) as this is used by other systems as proof-of-life for the component, and also for [routing](../guide/routing.md). + +## Working with MAVLink 1 {#mavlink_1} + +This section explains how to use the MAVLink 2 C++ 11 library to work with MAVLink 1 systems. + +### Version Handshaking/Negotiation + +[MAVLink Versions](../guide/mavlink_version.md) explains the [handshaking](../guide/mavlink_version.md#version_handshaking) used to determine the supported MAVLink version of either end of the channel, and how to [negotiate the version to use](../guide/mavlink_version.md#negotiating_versions). + +### Sending and Receiving MAVLink 1 Packets + +The library will send packets in _MAVLink 2_ framing by default. +To force sending _MAVLink 1_ packets on a particular channel you change the flags field of the status object. + +For example, the following code causes subsequent packets on the given channel to be sent as _MAVLink 1_: + +```cpp +mavlink::mavlink_status_t* chan_state = mavlink::mavlink_get_channel_status(mavlink::MAVLINK_COMM_0); +chan_state->flags |= mavlink::MAVLINK_STATUS_FLAG_OUT_MAVLINK1; +``` + +Incoming _MAVLink 1_ packets will be automatically handled as _MAVLink 1_. If you need to determine if a particular message was received as _MAVLink 1_ or _MAVLink 2_ then you can use the `magic` field of the message: + +```cpp +if (message.magic == mavlink::MAVLINK_STX_MAVLINK1) { + printf("This is a MAVLink 1 message\n"); +} +``` + +In most cases this should not be necessary as the XML message definition files for _MAVLink 1_ and _MAVLink 2_ are the same, so you can treat incoming _MAVLink 1_ messages the same as _MAVLink 2_ messages. + +::: info +_MAVLink 1_ is restricted to message IDs less than 256, so any messages with a higher message ID won't be received as _MAVLink 1_. +::: + +It is advisable to switch to _MAVLink 2_ when the communication partner sends _MAVLink 2_ (see [Version Handshaking](../guide/mavlink_version.md#version_handshaking)). The minimal solution is to watch incoming packets using code similar to this: + +```cpp +if (mavlink::mavlink_parse_char(mavlink::MAVLINK_COMM_0, buf[i], &message, &status)) { + // check if we received version 2 and request a switch. + if (!(mavlink::mavlink_get_channel_status(mavlink::MAVLINK_COMM_0)->flags & mavlink::MAVLINK_STATUS_FLAG_IN_MAVLINK1)) { + // this will only switch to proto version 2 + chan_state->flags &= ~(mavlink::MAVLINK_STATUS_FLAG_OUT_MAVLINK1); + } +} +``` + +## Examples + +The following examples show the use of the API. + +- [UDP Example](../mavgen_cpp/example_cpp_udp.md): Simple C++ example of a MAVLink UDP interface for Unix-like systems (Linux, MacOS, BSD, etc.).