NanoPB: Conversores de array personalizados com offset e tamanho em C++
Veja também: Versão em C: Como lidar com conversores de array personalizados em C
NanoPB é uma implementação de Protocol Buffers otimizada para tamanho de código em sistemas embarcados. Este post mostra como lidar com conversores de array personalizados em C++ com NanoPB, baseado na implementação do KKS-Firmware.
Definição Proto
Primeiro, crie um arquivo .proto com campos repeated:
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Gerar código NanoPB
Gere o código NanoPB com um arquivo .options para especificar a contagem máxima:
Crie custom_array.options:
example.CustomArrayMessage.values max_count:20Então gere:
protoc --nanopb_out=. custom_array.protoIsso irá gerar custom_array.pb.h e custom_array.pb.c.
Exemplo em C++ com conversor de array personalizado
Aqui está um exemplo completo em C++ implementando um conversor de array personalizado com offset e tamanho:
#include <stdio.h>
#include <algorithm>
#include <array>
#include <limits>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Conversor de array personalizado com parâmetros de template offset e tamanho
template<class ITEM_CONVERTER, class CONTAINER, size_t OFFSET=0, size_t SIZE=std::numeric_limits<size_t>::max()/2>
class ArrayConverterWithOffsetAndSize {
public:
static bool encodeCallback(pb_ostream_t *stream, const pb_field_t *field, const CONTAINER &container) {
size_t i = 0;
// Usa loop for para iterar pelo container
for (const auto &item : container) {
i++; // Primeiro índice que verificamos é 1
if(i <= OFFSET) { // <=, não "<", já que fazemos ++ antes desta linha
continue;
}
if(i > OFFSET + SIZE) {
break;
}
// Codifica o item
if (!ITEM_CONVERTER::encodeCallback(stream, field, item)) {
return false;
}
}
return true;
}
static bool decodeCallback(pb_istream_t *stream, const pb_field_t *field, CONTAINER &container) {
// Adiciona item ao container
// Nota: Este é um exemplo simplificado - a implementação real depende do tipo de container
return false; // Não implementado neste exemplo
}
};
// Conversor uint32 simples
class UInt32Converter {
public:
static bool encodeCallback(pb_ostream_t *stream, const pb_field_t *field, uint32_t value) {
if (!pb_encode_tag_for_field(stream, field))
return false;
return pb_encode_varint(stream, value);
}
};
int main() {
// Buffer para mensagem codificada
uint8_t buffer[256];
size_t message_length;
// Cria array com 10 elementos
std::array<uint32_t, 10> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- CODIFICAÇÃO ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Usa conversor personalizado: codifica elementos 3-7 (OFFSET=2, SIZE=5)
using CustomConverter = ArrayConverterWithOffsetAndSize<UInt32Converter, std::array<uint32_t, 10>, 2, 5>;
// Configura callback
message.values.funcs.encode = [](pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const std::array<uint32_t, 10>* container = (const std::array<uint32_t, 10>*)*arg;
return CustomConverter::encodeCallback(stream, field, *container);
};
message.values.arg = &values;
// Cria stream para codificação
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Codifica a mensagem
if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
printf("Encoding failed: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encoded %zu bytes (elements 3-7)\n", message_length);
// Imprime hex dump
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Imprime o que foi codificado
printf("Encoded values: ");
for (size_t i = 2; i < 2 + 5 && i < values.size(); i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Comando de compilação
Compile o exemplo com nanopb. NanoPB é tipicamente usado incluindo os arquivos fonte diretamente no seu projeto:
g++ -o custom_array_example custom_array_example.cpp custom_array.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.
Pontos principais
- Parâmetros de template: OFFSET e SIZE controlam quais elementos codificar
- Lógica de offset: Pula os primeiros OFFSET elementos (indexação baseada em 1 no loop)
- Limite de tamanho: Codifica no máximo SIZE elementos após o offset
- Padrão de callback: Usa pb_callback_t com funções personalizadas de encode/decode
- Flexibilidade: Pode codificar qualquer fatia de um array sem copiar
- Eficiência de memória: Não precisa criar arrays temporários
- Padrão KKS-Firmware: Baseado em ArrayConverterWithOffsetAndSize do KKS-Firmware
Quando usar conversores de array personalizados
- Quando você precisa codificar um subconjunto de um array grande
- Quando você quer evitar copiar dados
- Ao implementar paginação ou fragmentação
- Quando o layout do array não corresponde aos requisitos do protobuf
- Quando você precisa de lógica de codificação personalizada para arrays
Saída esperada
Encoded 10 bytes (elements 3-7)
Encoded data: 08 03 08 04 08 05 08 06 08 07
Encoded values: 3 4 5 6 7 Note que apenas os elementos 3, 4, 5, 6, 7 são codificados (5 elementos começando no offset 2).
Avançado: Suporte a decodificação
Para suportar decodificação, estenda o conversor:
static bool decodeCallback(pb_istream_t *stream, const pb_field_t *field, CONTAINER &container) {
uint64_t value;
if (!pb_decode_varint(stream, &value))
return false;
// Adiciona ao container (a implementação depende do tipo de container)
// Para std::array, você pode rastrear um índice separado
return true;
}Uso no mundo real
Este padrão é usado no KKS-Firmware para:
- Codificar apenas canais ativos de um array de canais maior
- Enviar dados parciais de sensores para economizar banda
- Implementar atualizações diferenciais
- Lidar com arrays esparsos de forma eficiente
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 opcionais em C
- Campos repeated/arrays em C++
- Campos repeated/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