How to use

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.

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 basic Protobuf messages set is already generated and files can be found in the libDM_protobuf/autogen_protobuf folder.

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.

You can then use:

./generate_protobuf.sh

More informations about the protobuf langage can be found here: 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:

#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: PROTOBUF_UTILS

Example

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 PROTOBUF_UTILS object

Important

API ref: PROTOBUF_UTILS::addStream()

Example

1  // Initialize serial link
2  Serial2.setRxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
3  Serial2.setTxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
4  Serial2.begin(921600, SERIAL_8N1, pinProtocolRx, pinProtocolTx);
5  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: PROTOBUF_UTILS::getReceivedMsg(), PROTOBUF_MSG::getFrame()

Example

1  // Get protobuf frame reference
2  PROTOBUF_MSG* protobufMsg = protobuf.getReceivedMsg();
3  ProtoFrame* protobufFrame = &protobufMsg->getFrame();

4. Call updateReceivedMessages(buffer, bufferLength) at every step

The 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

With the example of a serial stream:

Example

1uint8_t receptionSerialBuffer[PROTOBUF_MAX_MESSAGE_SIZE];
2size_t size = Serial2.available();
3if (size > PROTOBUF_MIN_MESSAGE_SIZE)
4{
5  // Copy serial buffer to be able to use it for mavlink and protobuf
6  OP::UART::readBytesBuffer(&Serial2, receptionSerialBuffer, size);
7  protobuf.updateReceivedMessages(receptionSerialBuffer, size);
8}

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 PROTOBUF_UTILS::isNewMsgReceived() and grab the messages received

Example

 1  if (protobuf.isNewMsgReceived())
 2  {
 3      Serial.println("Message received received");
 4      protobufMsg->setIsNew(false);
 5
 6      Serial.println(protobufFrame->timestamp);
 7      // Inspect frame to see what has been sent:
 8      if (protobufFrame->attitude.attitude_local.has_ypr)
 9      {
10          Serial.print("Yaw = " + String(protobufFrame->attitude.attitude_local.ypr.yaw));
11          Serial.print(" Pitch = "
12                       + String(protobufFrame->attitude.attitude_local.ypr.pitch));
13          Serial.println(" Roll = "
14                         + String(protobufFrame->attitude.attitude_local.ypr.roll));
15      }
16  }

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 PROTOBUF_UTILS::isNewMsgReceived() will always return true.

Message emission

1. Create a PROTOBUF_UTILS object

Important

API ref: PROTOBUF_UTILS

Example

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 PROTOBUF_UTILS object

Important

API ref: PROTOBUF_UTILS::addStream()

Example

1  // Initialize serial link
2  Serial2.setRxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
3  Serial2.setTxBufferSize(PROTOBUF_MAX_MESSAGE_SIZE);
4  Serial2.begin(921600, SERIAL_8N1, pinProtocolRx, pinProtocolTx);
5  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: PROTOBUF_UTILS::getMsgToSend(), PROTOBUF_MSG::getFrame()

Example

1  // Get protobuf frame reference
2  PROTOBUF_MSG* protobufMsg = protobuf.getMsgToSend();
3  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:

1   ProtoFrame frame  = ProtoFrame();
2   frame.has_sensors                               = true;
3   frame.sensors.has_accelerometer_raw             = true;
4   frame.sensors.accelerometer_raw.has_xyz         = true;
5   frame.sensors.accelerometer_raw.xyz.x           = 0.1F;
6   frame.sensors.accelerometer_raw.xyz.y           = 0.2F;

Important

API ref: PROTOBUF_UTILS::sendMessages(), PROTOBUF_MSG::setIsReadyForSend(), PROTOBUF_MSG::resetFrame()

Example

 1  uint8_t counterProtobufSend = 0U;
 2
 3  while (true)
 4  {
 5      delay(5);
 6      counterProtobufSend += 1;
 7      if (counterProtobufSend >= 10)
 8      {
 9          counterProtobufSend = 0;
10          // Reset all fields
11          protobufMsg->resetFrame(); // protobufFrame is resetted
12          // Set new values
13          // Set booleans for values we want to send (we want to send yaw pitch and not roll)
14          protobufFrame.has_attitude                          = true;
15          protobufFrame.attitude.has_attitude_local           = true;
16          protobufFrame.attitude.attitude_local.has_ypr       = true;
17          protobufFrame.attitude.attitude_local.ypr.has_yaw   = true;
18          protobufFrame.attitude.attitude_local.ypr.has_pitch = true;
19          protobufFrame.attitude.attitude_local.ypr.has_roll  = false; // not necessary
20
21          protobufFrame.attitude.attitude_local.ypr.yaw   = 0.1;
22          protobufFrame.attitude.attitude_local.ypr.pitch = 0.2;
23          protobufFrame.attitude.attitude_local.ypr.roll  = 0.3; // Will not be sent
24
25          // Send frame
26          protobufMsg->setIsReadyForSend(true); // for periodic send
27          protobuf.sendMessages();
28      }
29  }