NanoPB: Convertidores de arreglo personalizados con offset y tamaño en C++
Ver también: C version: How to handle custom array converters in C
NanoPB es una implementación de Protocol Buffers optimizada para el tamaño de código en sistemas embebidos. Esta entrada muestra cómo manejar convertidores de arreglo personalizados en C++ con NanoPB, basado en la implementación de KKS-Firmware.
Definición Proto
Primero, crea un archivo .proto con campos repetidos:
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Generar código NanoPB
Genera el código NanoPB con un archivo .options para especificar el recuento máximo:
Crea custom_array.options:
example.CustomArrayMessage.values max_count:20Luego genera:
protoc --nanopb_out=. custom_array.protoEsto generará custom_array.pb.h y custom_array.pb.c.
Ejemplo de C++ con convertidor de arreglo personalizado
Aquí hay un ejemplo completo de C++ que implementa un convertidor de arreglo personalizado con offset y tamaño:
#include <stdio.h>
#include <algorithm>
#include <array>
#include <limits>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Convertidor de arreglo personalizado con parámetros de plantilla offset y tamaño
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;
// Usar bucle for para iterar a través del contenedor
for (const auto &item : container) {
i++; // El primer índice que verificamos es 1
if(i <= OFFSET) { // <=, no "<", ya que hacemos ++ antes de esta línea
continue;
}
if(i > OFFSET + SIZE) {
break;
}
// Codificar el elemento
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) {
// Añadir elemento al contenedor
// Nota: Este es un ejemplo simplificado - la implementación real depende del tipo de contenedor
return false; // No implementado en este ejemplo
}
};
// Convertidor simple de uint32
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() {
// Búfer para el mensaje codificado
uint8_t buffer[256];
size_t message_length;
// Crear arreglo con 10 elementos
std::array<uint32_t, 10> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- CODIFICACIÓN ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Usar convertidor personalizado: codificar elementos 3-7 (OFFSET=2, SIZE=5)
using CustomConverter = ArrayConverterWithOffsetAndSize<UInt32Converter, std::array<uint32_t, 10>, 2, 5>;
// Configurar 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;
// Crear flujo para codificación
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Codificar el mensaje
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);
// Imprimir volcado hexadecimal
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Imprimir lo que se codificó
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 compilación
Compila el ejemplo con nanopb. NanoPB se usa normalmente incluyendo los archivos fuente directamente en tu proyecto:
g++ -o custom_array_example custom_array_example.cpp custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.Nota: Los archivos fuente de NanoPB (pb_common.c, pb_encode.c, pb_decode.c) deben compilarse directamente con tu proyecto. Puedes obtenerlos del repositorio GitHub de NanoPB.
Puntos clave
- Parámetros de plantilla: OFFSET y SIZE controlan qué elementos codificar
- Lógica de offset: Omite los primeros OFFSET elementos (indexación basada en 1 en el bucle)
- Límite de tamaño: Codifica como máximo SIZE elementos después del offset
- Patrón de callback: Usa pb_callback_t con funciones de codificación/decodificación personalizadas
- Flexibilidad: Puede codificar cualquier porción de un arreglo sin copiar
- Eficiencia de memoria: No es necesario crear arreglos temporales
- Patrón KKS-Firmware: Basado en ArrayConverterWithOffsetAndSize de KKS-Firmware
Cuándo usar convertidores de arreglo personalizados
- Cuando necesitas codificar un subconjunto de un arreglo grande
- Cuando quieres evitar copiar datos
- Cuando implementas paginación o fragmentación
- Cuando el diseño del arreglo no coincide con los requisitos de protobuf
- Cuando necesitas lógica de codificación personalizada para arreglos
Salida 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 Ten en cuenta que solo se codifican los elementos 3, 4, 5, 6, 7 (5 elementos comenzando en el offset 2).
Avanzado: Soporte de decodificación
Para soportar la decodificación, extiende el convertidor:
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;
// Añadir al contenedor (la implementación depende del tipo de contenedor)
// Para std::array, puedes rastrear un índice separado
return true;
}Uso en el mundo real
Este patrón se usa en KKS-Firmware para:
- Codificar solo los canales activos de un arreglo de canales más grande
- Enviar datos parciales de sensores para ahorrar ancho de banda
- Implementar actualizaciones diferenciales
- Manejar arreglos dispersos de manera eficiente
Más entradas de NanoPB
- Basic scalar types in C++
- Basic scalar types in C
- String types in C++
- String types in C
- Bytes types in C++
- Bytes types in C
- Optional fields in C++
- Optional fields in C
- Repeated fields/arrays in C++
- Repeated fields/arrays in C
- Enums in C++
- Enums in C
- Nested messages in C++
- Nested messages in C
- Oneof/union types in C++
- Oneof/union types in C
- Custom array converters in C