... | ... | @@ -26,8 +26,7 @@ message Foo { |
|
|
required int id = 1;
|
|
|
}
|
|
|
```
|
|
|
To use Nanopb Headers(.h) and source files(.c) generation from `.proto` file is required.
|
|
|
Headers(.h) and source files(.c) are generated using a python script provided by Nanopb.
|
|
|
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
|
|
|
```
|
... | ... | @@ -39,7 +38,7 @@ Follow the [instructions here](ihttps://github.com/nanopb/nanopb#generating-the- |
|
|
|
|
|
## Simple Getting started Example
|
|
|
|
|
|
### Protofiles:
|
|
|
### Proto Files
|
|
|
Let's say you want to transmit a Can message in a serialized protobuf format. Then we need to define the can message and its contents inside a proto file as follows:
|
|
|
|
|
|
```
|
... | ... | @@ -78,13 +77,13 @@ Now we can use this generated structure to populate our data to be serialized as |
|
|
proto_msg->data_byte = can_msg->data[0];
|
|
|
```
|
|
|
|
|
|
We then create an output stream to write data to the buffer:
|
|
|
We then create an output stream for writing the data to the buffer:
|
|
|
|
|
|
```
|
|
|
pb_ostream_t stream = pb_ostream_from_buffer(encoded_packet->buffer, encoded_packet->encoded_packet_size);
|
|
|
```
|
|
|
|
|
|
The message is can we Encoded using Nanopb using the Nanopb's encoding function
|
|
|
The Protobuf message is then encoded to a serialized format using Nanopb's `pb_encode()` API.
|
|
|
```
|
|
|
if (!pb_encode(&stream, proto_can_message_fields, proto_msg)) {
|
|
|
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
|
... | ... | @@ -111,7 +110,7 @@ Finally we re-populate the can message using the newly populated `proto_msg` ie. |
|
|
can_msg->data = proto_msg->data_byte;
|
|
|
```
|
|
|
|
|
|
## Using Callback for Nanopb
|
|
|
## Using Callbacks for Nanopb
|
|
|
|
|
|
A CAN message usually has a 8 byte data field (if using CAN-FD the data field is 64 bytes). Thus we modify the can message in the `proto` file such that the CAN proto message can encode 8 byte of data at once.
|
|
|
|
... | ... | @@ -125,8 +124,6 @@ message can_message { |
|
|
}
|
|
|
```
|
|
|
|
|
|
### Encoding:
|
|
|
|
|
|
After generating the (.h) file again the message is modified as follows:
|
|
|
|
|
|
```
|
... | ... | @@ -140,10 +137,14 @@ 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.
|
|
|
|
|
|
Callbacks are used when the members of a message have variable length and storage is not statically allocated to it. For example if a message contains a string such as `string name` instead of generating `char *name` Nanopb generates the `name` variable of `pb_callback_t` datatype. This allows the user to allocated a name with N number of chars using a custom call back function. Thus callback instance of message member are generated for variable length arrays, strings, repeated specifier
|
|
|
messages or data structures (which is demonstrated shortly). More Information [here.](https://jpa.kapsi.fi/nanopb/docs/concepts.html#field-callbacks)
|
|
|
Thus, members of a message are generated as `pb_callback_t` datatypes for variable length arrays/strings and repeated member messages(This is demonstrated below).
|
|
|
|
|
|
For more information on callbacks in Nanopb visit [here](https://jpa.kapsi.fi/nanopb/docs/concepts.html#field-callbacks).
|
|
|
|
|
|
The callback structure is declared as follows:
|
|
|
```
|
... | ... | @@ -157,32 +158,58 @@ struct _pb_callback_t { |
|
|
void *arg;
|
|
|
};
|
|
|
```
|
|
|
The pb_callback_t structure contains a function pointer and a void pointer called arg you can use for passing data to the callback. If the function pointer is NULL, the field will be skipped. A pointer to the arg is passed to the function, so that it can modify it and retrieve the value.
|
|
|
The `pb_callback_t` structure consists of two members:
|
|
|
* Function pointer: This points to callback function for processing variable length member of a message.
|
|
|
* `void` pointer `arg`: It is used to pass a pointer to the data structure which is processed by the callback function.
|
|
|
If the function pointer is `NULL`, the corresponding message field will be skipped.
|
|
|
|
|
|
### Encoding a callback message
|
|
|
|
|
|
Let's consider we would like to encode a CAN message as follows:
|
|
|
|
|
|
```
|
|
|
can_message_s can_msgs_to_encode = {.message_id = 111U, .timestamp_ms = 5U, .dlc = 8U, .bus_id = 2};
|
|
|
memset(can_msgs_to_encode.data, 0x0E, 8);
|
|
|
```
|
|
|
Here, the data field of CAN message has 8 bytes, and each byte is set to the value `0x0E`. The CAN message is sent to the encode function as follows:
|
|
|
|
|
|
The proto message is now populated and encoded follows:
|
|
|
```
|
|
|
size_t encode_packet(proto_can_message *proto_msg, serial_data_packet_s *packet, can_message_s *can_msg)
|
|
|
```
|
|
|
|
|
|
Since the Nanopb's `pb_encode` function requires the data to be encoded in `proto` message format, the `can_message_s *can_msg` to encode is populated into the `proto_can_message *proto_msg`. The Callback structure of the `data_bytes` member is populated as follows:
|
|
|
* `proto_msg->data_byte.arg = can_msg`
|
|
|
This is required since the data field of CAN message has variable amount of bytes which is handled by the callback function by passing it as an argument.
|
|
|
* `proto_msg->data_byte.funcs.encode = &callback_encode_can_bytes`
|
|
|
Will hold the function pointer for the callback funtion.
|
|
|
|
|
|
```
|
|
|
/* Fill the proto packet */
|
|
|
proto_msg->timestamp_ms = can_msg->timestamp_ms;
|
|
|
proto_msg->bus_id = can_msg->bus_id;
|
|
|
proto_msg->message_id = can_msg->message_id;
|
|
|
proto_msg->data_byte = can_msg->data[0];
|
|
|
|
|
|
// Populate `pb_callback_t data_bytes` field
|
|
|
proto_msg->data_bytes.arg = (void *)can_msg->data;
|
|
|
proto_msg->data_bytes.funcs.encode = &encode_can_bytes;
|
|
|
// Callback structure
|
|
|
proto_msg->data_byte.arg = can_msg;
|
|
|
proto_msg->data_byte.funcs.encode = &callback_encode_can_bytes;
|
|
|
```
|
|
|
We can then the encode the data using Nanopb's `pb_encode` function. The `pb_encode` will internally call the callback registered above to encode all the data bytes inside the CAN message.
|
|
|
|
|
|
```
|
|
|
if (!pb_encode(&stream, proto_can_message_fields, proto_msg)) {
|
|
|
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
|
|
|
return 1;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
A callback function is created to encode the variable amount of bytes in the can message.
|
|
|
A callback function is created to encode the variable amount of bytes in the CAN message.
|
|
|
|
|
|
```
|
|
|
bool callback_decode_can_bytes(pb_ostream_t *ostream, const pb_field_t *field, void *const *arg) {}
|
|
|
bool callback_encode_can_bytes(pb_ostream_t *ostream, const pb_field_t *field, void *const *arg) {}
|
|
|
```
|
|
|
We deference the `arg` pointer which points to the array of CAN messages, get the count of CAN messages inside the array and encode all bytes at once using Nanopb's `pb_encode_string` [API](https://jpa.kapsi.fi/nanopb/docs/reference.html#pb-encode-string).
|
|
|
|
|
|
We deference the `arg` pointer which points to the array of CAN messages, get the count of CAN messages inside the array and encode all bytes at once using Nanopb's `pb_encode_string` [API](https://jpa.kapsi.fi/nanopb/docs/reference.html#pb-encode-string).
|
|
|
|
|
|
```
|
|
|
const can_message_s *const can_messages = (const can_message_s *)(*arg);
|
|
|
const uint8_t *const can_byte = (const uint8_t *)can_messages->data;
|
... | ... | |