How to use
===========
.. toctree::
:maxdepth: 3
:caption: Contents:
Platform support
----------------
Framework:
- Arduino
This library is made to be used with:
- ESP32 family
- Teensy 4.X family
Library framing
---------------
To allow easy decoding for multiple messages on bus, bytes are added to the begining of the message. Two start bytes and
message size are added. This allow to decode multiple chunks of data on the same bus, and to to distinguish between protobuf
messages and other protocols.
- start byte 1: 77
- start byte 2: 88
The length of the message is added by nanopb.
.. code-block:: c++
constexpr uint8_t PROTOBUF_FRAME_START_BYTE_1 = 77U;
constexpr uint8_t PROTOBUF_FRAME_START_BYTE_2 = 88U;
Generate headers for custom messages
------------------------------------
The protobuf headers are generated using nanopb protoc generator. A shell script is provided for easy generation.
The |protodoc| is already generated and files can be found in the libDM_protobuf/autogen_protobuf folder.
.. |protodoc| raw:: html
basic Protobuf messages set
If you need to add messages to the set, you can modify files present in the libDM_protobuf/message_definitions folder or
add your own proto files following `Google Protocol Buffers documentation `_.
.. important::
any .proto file found in the libDM_protobuf/message_definitions folder will be included in the generation process.
.. attention::
As all the protobuf messages are transmitted through hardware interfaces using a |protoframe|, every message that has
to transit through a hardware interface has to be included in the protoframe defined in the message_definitions/frame.proto file.
.. |protoframe| raw:: html
ProtoFrame
You can then use:
.. code-block:: bash
./generate_protobuf.sh
More informations about the protobuf langage can be found here: |protobuf|
.. |protobuf| raw:: html
https://protobuf.dev/overview/
Necessary inclusions
--------------------
If all the messages are included in the protoframe, you can just include the following files in your project:
.. code-block:: c++
#include "libDM_protobuf.hpp"
The inclusion of "autogen_protobuf/frame.pb.h" should be sufficient. If files have been added without the inclusion in the
protoframe, you will have to include the corresponding .pb.h file in your code.
Message reception
-----------------
1. Create a PROTOBUF_UTILS object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. important::
API ref: :cpp:class:`PROTOBUF_UTILS`
.. admonition:: Example
.. code-block:: c++
PROTOBUF_UTILS protobuf(&streamObj, &myTimer);
2. (optional) Add a PROTOBUF_STREAM object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If no interface has already been specified, you can configure an interface and add it to the :cpp:class:`PROTOBUF_UTILS`
object
.. important::
API ref: :cpp:func:`PROTOBUF_UTILS::addStream`
.. admonition:: Example
.. code-block:: c++
:linenos:
// Initialize serial link
Serial2.setRxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
Serial2.setTxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
Serial2.begin(921600, SERIAL_8N1, pinProtocolRx, pinProtocolTx);
protobuf.addStream(&Serial2);
3. Get refs to the messages you want to receive and to the frame
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To manipulate data, it is interesting to grab references of the ProtoFrame
.. important::
API ref: :cpp:func:`PROTOBUF_UTILS::getReceivedMsg`, :cpp:func:`PROTOBUF_MSG::getFrame`
.. admonition:: Example
.. code-block:: c++
:linenos:
// Get protobuf frame reference
PROTOBUF_MSG* protobufMsg = protobuf.getReceivedMsg();
ProtoFrame* protobufFrame = &protobufMsg->getFrame();
4. Call updateReceivedMessages(buffer, bufferLength) at every step
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :cpp:func:`PROTOBUF_UTILS::updateReceivedMessages` has to be called periodically and feeded with the received buffer
on some serial interface.
It will update the ProtoFrame with the received informations
.. important::
API ref: :cpp:func:`PROTOBUF_UTILS::updateReceivedMessages`
With the example of a serial stream:
.. admonition:: Example
.. code-block:: c++
:linenos:
uint8_t receptionSerialBuffer[PROTOBUF_MAX_MESSAGE_SIZE];
size_t size = Serial2.available();
if (size > PROTOBUF_MIN_MESSAGE_SIZE)
{
// Copy serial buffer to be able to use it for mavlink and protobuf
OP::UART::readBytesBuffer(&Serial2, receptionSerialBuffer, size);
protobuf.updateReceivedMessages(receptionSerialBuffer, size);
}
.. attention::
As the size of the protoframe can be quite large, PROTOBUF_MAX_MESSAGE_SIZE can be quite large (more than 2000 bytes).
Ensure stack can handle it or allocate one time the reception buffer in the heap.
5. Get the last ProtoFrame
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ProtoFrame fields have been automatically updated if received on some hardware interface.
To check if the message has been updated, you can use :cpp:func:`PROTOBUF_UTILS::isNewMsgReceived` and grab the messages
received
.. admonition:: Example
.. code-block:: c++
:linenos:
if (protobuf.isNewMsgReceived())
{
Serial.println("Message received received");
protobufMsg->setIsNew(false);
Serial.println(protobufFrame->timestamp);
// Inspect frame to see what has been sent:
if (protobufFrame->attitude.attitude_local.has_ypr)
{
Serial.print("Yaw = " + String(protobufFrame->attitude.attitude_local.ypr.yaw));
Serial.print(" Pitch = "
+ String(protobufFrame->attitude.attitude_local.ypr.pitch));
Serial.println(" Roll = "
+ String(protobufFrame->attitude.attitude_local.ypr.roll));
}
}
.. note::
The use of the has_xxx flags can help to determine what kind of messages inside the frame has been received.
.. attention::
Do not forget to set the isNew flag to false once the message has been read if all the consumers have used it or the
:cpp:func:`PROTOBUF_UTILS::isNewMsgReceived` will always return true.
Message emission
-----------------
1. Create a PROTOBUF_UTILS object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. important::
API ref: :cpp:class:`PROTOBUF_UTILS`
.. admonition:: Example
.. code-block:: c++
PROTOBUF_UTILS protobuf(&streamObj, &myTimer);
2. Add a PROTOBUF_STREAM object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If no interface has already been specified, you can configure an interface and add it to the :cpp:class:`PROTOBUF_UTILS`
object
.. important::
API ref: :cpp:func:`PROTOBUF_UTILS::addStream`
.. admonition:: Example
.. code-block:: c++
:linenos:
// Initialize serial link
Serial2.setRxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
Serial2.setTxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
Serial2.begin(921600, SERIAL_8N1, pinProtocolRx, pinProtocolTx);
protobuf.addStream(&Serial2);
3. Get refs to the messages you want to send and to the frame
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To manipulate data, it is interesting to grab references of the ProtoFrame
.. important::
API ref: :cpp:func:`PROTOBUF_UTILS::getMsgToSend`, :cpp:func:`PROTOBUF_MSG::getFrame`
.. admonition:: Example
.. code-block:: c++
:linenos:
// Get protobuf frame reference
PROTOBUF_MSG* protobufMsg = protobuf.getMsgToSend();
ProtoFrame* protobufFrame = &protobufMsg->getFrame();
4. Fill the ProtoFrame and send it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. attention::
The use of nanopb imposes to set the has_xxx flags to true if the field has to be sent. Setting to top level message has_xxx
to false will not send the message and all the subfields it contains. At reception their has_xxx flags will be read as false, even
if they were set to true at emission.
For enabling messages, the has_xxx flags have to be set to true at the top level message and all the subfields it contains until the
last field to be sent has been reached. For example, to send accelerometer data:
.. code-block:: c++
:linenos:
ProtoFrame frame = ProtoFrame();
frame.has_sensors = true;
frame.sensors.has_accelerometer_raw = true;
frame.sensors.accelerometer_raw.has_xyz = true;
frame.sensors.accelerometer_raw.xyz.x = 0.1F;
frame.sensors.accelerometer_raw.xyz.y = 0.2F;
.. important::
API ref: :cpp:func:`PROTOBUF_UTILS::sendMessages`, :cpp:func:`PROTOBUF_MSG::setIsReadyForSend`,
:cpp:func:`PROTOBUF_MSG::resetFrame`
.. admonition:: Example
.. code-block:: c++
:linenos:
uint8_t counterProtobufSend = 0U;
while (true)
{
delay(5);
counterProtobufSend += 1;
if (counterProtobufSend >= 10)
{
counterProtobufSend = 0;
// Reset all fields
protobufMsg->resetFrame(); // protobufFrame is resetted
// Set new values
// Set booleans for values we want to send (we want to send yaw pitch and not roll)
protobufFrame.has_attitude = true;
protobufFrame.attitude.has_attitude_local = true;
protobufFrame.attitude.attitude_local.has_ypr = true;
protobufFrame.attitude.attitude_local.ypr.has_yaw = true;
protobufFrame.attitude.attitude_local.ypr.has_pitch = true;
protobufFrame.attitude.attitude_local.ypr.has_roll = false; // not necessary
protobufFrame.attitude.attitude_local.ypr.yaw = 0.1;
protobufFrame.attitude.attitude_local.ypr.pitch = 0.2;
protobufFrame.attitude.attitude_local.ypr.roll = 0.3; // Will not be sent
// Send frame
protobufMsg->setIsReadyForSend(true); // for periodic send
protobuf.sendMessages();
}
}