... | ... | @@ -17,7 +17,16 @@ Protocol buffer can serialize data from variety of languages such as Java, Pytho |
|
|
# Installation and Setup
|
|
|
|
|
|
## Setting up Nanopb
|
|
|
To use Nanopb, protocol compiler needs to be installed. Follow the [installation instructions](https://github.com/protocolbuffers/protobuf#protocol-compiler-installation) here.
|
|
|
|
|
|
To use Nanopb, protocol compiler needs to be installed.
|
|
|
Install `Python3` and `Python3-Pip` package:
|
|
|
```
|
|
|
apt-get install python3 python3-pip
|
|
|
```
|
|
|
Install `protobuf` and `grpcio-tools` packages need by Nanopb
|
|
|
```
|
|
|
pip3 install protobuf grpcio-tools
|
|
|
```
|
|
|
|
|
|
Protocol Buffers messages are defined in a `proto file` as follows:
|
|
|
```
|
... | ... | @@ -28,11 +37,10 @@ message Foo { |
|
|
```
|
|
|
This `.proto` file is used to generate the Nanopb headers(.h) and source files(.c) using the python script provided by Nanopb.
|
|
|
```
|
|
|
python generator/nanopb_generator.py foo.proto
|
|
|
python3 ../../generator/nanopb_generator.py foo.proto
|
|
|
```
|
|
|
Follow the [instructions here](ihttps://github.com/nanopb/nanopb#generating-the-headers).
|
|
|
|
|
|
**Note**: Sibros repository Integrates Nanopb library in [Bazel](https://www.bazel.build/). Refer to [Sibros Nanopb Bazel]() article for more information.
|
|
|
**Note**: Sibros repository Integrates Nanopb library in [Bazel](https://www.bazel.build/). Refer to the Bazel `BUILD` file in the Nanopb examples to generate the headers(.h) and source files(.c).
|
|
|
|
|
|
# Implementation and Examples
|
|
|
|
... | ... | @@ -137,7 +145,7 @@ typedef struct _proto_can_message { |
|
|
} proto_can_message;
|
|
|
```
|
|
|
|
|
|
**Callbacks**
|
|
|
### Callbacks
|
|
|
|
|
|
Callbacks are used when the members of a message have variable length and storage is not statically allocated for it.
|
|
|
For example if a message contains a string member such as `string name` instead of generating `char *name` Nanopb generates the variable `name` of `pb_callback_t` datatype. This allows the user to allocated the variable `name` with any number of chars using a custom call back function.
|
... | ... | @@ -253,6 +261,130 @@ static bool callback_decode_can_bytes(pb_istream_t *istream, const pb_field_t *f |
|
|
memcpy(can_messages->data, pb_bytes, sizeof(can_messages->data));
|
|
|
```
|
|
|
|
|
|
## Example: Nested Callback Structures
|
|
|
|
|
|
Consider we have the following `.proto` file:
|
|
|
```
|
|
|
message proto_can_message {
|
|
|
....
|
|
|
required bytes data_byte = 4; // Usually 8 bytes but modified for simplified example
|
|
|
}
|
|
|
|
|
|
....
|
|
|
|
|
|
message proto_message{
|
|
|
....
|
|
|
repeated proto_can_message can_msgs = 3;
|
|
|
}
|
|
|
```
|
|
|
After generating the `.c` and `.h` files we will now have have two `pb_callback_t` structures inside the messages.
|
|
|
|
|
|
```
|
|
|
/* Struct definitions */
|
|
|
typedef struct _proto_can_message {
|
|
|
...
|
|
|
pb_callback_t data_byte;
|
|
|
/* @@protoc_insertion_point(struct:proto_can_message) */
|
|
|
} proto_can_message;
|
|
|
|
|
|
....
|
|
|
|
|
|
typedef struct _proto_message {
|
|
|
....
|
|
|
pb_callback_t can_msgs;
|
|
|
} proto_message;
|
|
|
```
|
|
|
So we need to have 2 callback functions:
|
|
|
* First callback function `callback_encode_can_messages` will encode multiple (variable amount of) CAN messages
|
|
|
* Second callback function `callback_encode_can_bytes` will encode multiple data bytes of the CAN message.
|
|
|
|
|
|
We start by passing `N` number of CAN messages to `encode_packet` function. The `can_msg_count` variable passes the number of CAN messages in the `can_msg` array.
|
|
|
```
|
|
|
size_t encode_packet(proto_message *proto_pkt, serial_data_packet_s *packet, can_message_s *can_msg,
|
|
|
size_t can_msg_count)
|
|
|
```
|
|
|
|
|
|
We register the array of CAN messages to be encoded to the `arg` member of `pb_callback_t` structure along with the `callback_encode_can_messages` callback function. We then pass the entire `proto_pkt` to the Nanopb's `pb_encode` API for encoding.
|
|
|
|
|
|
```
|
|
|
proto_pkt->can_msgs.arg = (void *)(&encode_can_messages_callback_parameter);
|
|
|
proto_pkt->can_msgs.funcs.encode = &callback_encode_can_messages;
|
|
|
|
|
|
/* Now encode the message! and check of error */
|
|
|
if (!pb_encode(&stream, proto_message_fields, proto_pkt)) {
|
|
|
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
|
|
|
return 1;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The callback function `callback_encode_can_messages` is called internally by `pb_encode` function.
|
|
|
```
|
|
|
bool callback_encode_can_messages(pb_ostream_t *ostream, const pb_field_t *field, void *const *arg)
|
|
|
```
|
|
|
We then deference the array of CAN messages passed into the `void *const *arg` argument and iteratively encode individual CAN messages. Since the `pb_callback_t can_msgs` is a member field of `proto_message` we use the Nanopb's `pb_encode_submessage()` API to encode individual CAN messages.
|
|
|
|
|
|
```
|
|
|
bool callback_encode_can_messages(pb_ostream_t *ostream, const pb_field_t *field, void *const *arg) {
|
|
|
bool is_success = true;
|
|
|
|
|
|
if ((NULL == arg) || (NULL == *arg)) {
|
|
|
is_success = false;
|
|
|
} else {
|
|
|
const pb_callback_parameter_s *callback_parameter = (const pb_callback_parameter_s *)(*arg);
|
|
|
can_message_s *can_msgs = callback_parameter->can_msgs;
|
|
|
size_t msg_count = callback_parameter->can_msg_count;
|
|
|
|
|
|
for (size_t count = 0; count < msg_count; count++) {
|
|
|
proto_can_message proto_msg_can = proto_can_message_init_zero;
|
|
|
|
|
|
proto_msg_can.timestamp_ms = can_msgs[count].timestamp_ms;
|
|
|
proto_msg_can.bus_id = can_msgs[count].bus_id;
|
|
|
proto_msg_can.message_id = can_msgs[count].message_id;
|
|
|
|
|
|
proto_msg_can.data_byte.arg = (void *)(&can_msgs[count]);
|
|
|
proto_msg_can.data_byte.funcs.encode = &callback_encode_can_bytes;
|
|
|
|
|
|
/* This encodes the header for the field, based on the constant info from pb_field_t. */
|
|
|
if (!pb_encode_tag_for_field(ostream, field)) {
|
|
|
is_success = false;
|
|
|
}
|
|
|
|
|
|
/* This encodes the data for the field, based on our can_message structure. */
|
|
|
if (!pb_encode_submessage(ostream, proto_can_message_fields, &proto_msg_can)) {
|
|
|
is_success = false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return is_success;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Nanopb's `pb_encode_submessage()` function will internally call `callback_encode_can_bytes` callback function to encoded individual CAN data bytes
|
|
|
|
|
|
```
|
|
|
static bool callback_encode_can_bytes(pb_ostream_t *ostream, const pb_field_t *field, void *const *arg) {
|
|
|
bool is_success = true;
|
|
|
|
|
|
if ((NULL == arg) || (NULL == *arg)) {
|
|
|
is_success = false;
|
|
|
} else {
|
|
|
const can_message_s *const can_messages = (const can_message_s *)(*arg);
|
|
|
const uint8_t *const can_bytes = (const uint8_t *)can_messages->data;
|
|
|
const size_t can_message_byte_count = can_messages->dlc;
|
|
|
|
|
|
if (!pb_encode_tag_for_field(ostream, field)) {
|
|
|
is_success = false;
|
|
|
}
|
|
|
if (!pb_encode_string(ostream, can_bytes, can_message_byte_count)) {
|
|
|
is_success = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return is_success;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Thus subsequently all our CAN messages are encoded.
|
|
|
|
|
|
# External Resources
|
|
|
* [Protocol Buffer](https://developers.google.com/protocol-buffers/)
|
... | ... | |