NanoPB: Como lidar com campos opcionais em C
Veja também: Versão C++: Como lidar com campos opcionais em C++
NanoPB é uma implementação de Protocol Buffers otimizada para tamanho de código para sistemas embarcados. Este post mostra como lidar com campos opcionais em C com NanoPB.
Definição Proto
Primeiro, crie um arquivo .proto com campos opcionais:
syntax = "proto3";
package example;
message OptionalMessage {
optional uint32 id = 1;
optional string name = 2;
optional float temperature = 3;
bool active = 4; // Campo regular (não opcional)
}Gerar código NanoPB
Gere o código NanoPB com um arquivo .options para especificar os tamanhos dos buffers de string:
Crie optional.options:
example.OptionalMessage.name max_size:64Depois gere:
protoc --nanopb_out=. optional.protoIsso irá gerar optional.pb.h e optional.pb.c.
Exemplo em C
Aqui está um exemplo completo em C lidando com campos opcionais:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "optional.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
// Buffer para mensagem codificada
uint8_t buffer[256];
size_t message_length;
// --- CODIFICAÇÃO ---
example_OptionalMessage message = example_OptionalMessage_init_zero;
// Definir campos opcionais
message.id = 42;
message.has_id = true; // Importante: definir flag has_*
strncpy(message.name, "Sensor1", sizeof(message.name) - 1);
message.has_name = true; // Importante: definir flag has_*
message.temperature = 23.5f;
message.has_temperature = true; // Importante: definir flag has_*
// Campo regular (não precisa de flag has_*)
message.active = true;
// Criar stream para codificação
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Codificar a mensagem
if (!pb_encode(&ostream, example_OptionalMessage_fields, &message)) {
printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encoded %zu bytes\n", message_length);
// Imprimir hex dump dos dados codificados
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- DECODIFICAÇÃO ---
example_OptionalMessage decoded = example_OptionalMessage_init_zero;
// Criar stream para decodificação
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
// Decodificar a mensagem
if (!pb_decode(&istream, example_OptionalMessage_fields, &decoded)) {
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Imprimir valores decodificados
printf("Decoded values:\n");
if (decoded.has_id) {
printf(" id: %u\n", (unsigned int)decoded.id);
} else {
printf(" id: (not set)\n");
}
if (decoded.has_name) {
printf(" name: %s\n", decoded.name);
} else {
printf(" name: (not set)\n");
}
if (decoded.has_temperature) {
printf(" temperature: %f\n", decoded.temperature);
} else {
printf(" temperature: (not set)\n");
}
printf(" active: %s\n", decoded.active ? "true" : "false");
return 0;
}Comando de compilação
Compile o exemplo com nanopb. NanoPB é tipicamente usado incluindo os arquivos fonte diretamente no seu projeto:
gcc -o optional_example optional_example.c optional.pb.c pb_common.c pb_encode.c pb_decode.c -I.Nota: Os arquivos fonte do NanoPB (pb_common.c, pb_encode.c, pb_decode.c) precisam ser compilados diretamente com seu projeto. Você pode obtê-los no repositório GitHub do NanoPB.
Script de teste em Python
Para verificar a codificação, você pode usar a biblioteca protobuf do Python:
import optional_pb2
# Ler os dados binários
with open('encoded.bin', 'rb') as f:
data = f.read()
# Decodificar
msg = optional_pb2.OptionalMessage()
msg.ParseFromString(data)
print("Python decoded values:")
print(f" id: {msg.id if msg.HasField('id') else '(not set)'}")
print(f" name: {msg.name if msg.HasField('name') else '(not set)'}")
print(f" temperature: {msg.temperature if msg.HasField('temperature') else '(not set)'}")
print(f" active: {msg.active}")Primeiro, compile as definições protobuf do Python:
protoc --python_out=. optional.protoDepois modifique o exemplo em C para salvar os dados codificados em um arquivo:
// Após a codificação, adicione isto:
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);Exemplo com campos opcionais ausentes
Aqui está um exemplo onde alguns campos opcionais não são definidos:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "optional.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
int main() {
uint8_t buffer[256];
size_t message_length;
// --- CODIFICAÇÃO ---
example_OptionalMessage message = example_OptionalMessage_init_zero;
// Definir apenas alguns campos opcionais
message.id = 42;
message.has_id = true;
// name e temperature não definidos (flags has_* permanecem false)
// Campo regular sempre definido
message.active = true;
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_OptionalMessage_fields, &message)) {
printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encoded %zu bytes (partial)\n", message_length);
// --- DECODIFICAÇÃO ---
example_OptionalMessage decoded = example_OptionalMessage_init_zero;
pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
if (!pb_decode(&istream, example_OptionalMessage_fields, &decoded)) {
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return 1;
}
printf("Decoded values:\n");
printf(" id: %s\n", decoded.has_id ? "set" : "not set");
printf(" name: %s\n", decoded.has_name ? "set" : "not set");
printf(" temperature: %s\n", decoded.has_temperature ? "set" : "not set");
printf(" active: %s\n", decoded.active ? "true" : "false");
return 0;
}Pontos principais
- Campos opcionais: Use a palavra-chave
optionalno proto3 - Flags has_*: NanoPB gera flags booleanas
has_fieldnamepara cada campo opcional - Codificação: Defina tanto o valor do campo quanto a flag
has_*como true - Decodificação: Verifique a flag
has_*antes de usar os valores dos campos opcionais - Campos regulares: Campos não opcionais não têm flags
has_* - Valores padrão: Campos opcionais não definidos têm valores padrão (0, string vazia, etc.)
- Economia de espaço: Campos opcionais não definidos não são incluídos na mensagem codificada
Quando usar campos opcionais
- Quando um campo pode ou não estar presente em diferentes instâncias de mensagem
- Quando você quer distinguir entre “não definido” e “definido com valor padrão”
- Quando você quer economizar banda omitindo campos não utilizados
- Quando um campo é relevante apenas em certos contextos
Saída esperada (exemplo completo)
Encoded 19 bytes
Encoded data: 08 2a 12 07 53 65 6e 73 6f 72 31 1d 00 00 bc 41 30 01
Decoded values:
id: 42
name: Sensor1
temperature: 23.500000
active: trueSaída esperada (exemplo parcial)
Encoded 4 bytes (partial)
Encoded data: 08 2a 30 01
Decoded values:
id: set
name: not set
temperature: not set
active: trueNote como a codificação parcial é muito menor (4 bytes contra 19 bytes) porque os campos opcionais são omitidos.
Mais posts sobre NanoPB
- Tipos escalares básicos em C++
- Tipos escalares básicos em C
- Tipos de string em C++
- Tipos de string em C
- Tipos de bytes em C++
- Tipos de bytes em C
- Campos opcionais em C++
- Campos repetidos/arrays em C++
- Campos repetidos/arrays em C
- Enums em C++
- Enums em C
- Mensagens aninhadas em C++
- Mensagens aninhadas em C
- Tipos oneof/union em C++
- Tipos oneof/union em C
- Conversores de array personalizados em C++
- Conversores de array personalizados em C