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.
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 <stdint.h>
#include <stddef.h>
#include <limits.h>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Contexto del convertidor de arreglo personalizado
typedef struct {
const uint32_t* data;
size_t size;
size_t offset;
size_t count;
} array_context_t;
// Callback de codificación para arreglo uint32 con offset y tamaño
bool uint32_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const array_context_t* ctx = (const array_context_t*)*arg;
size_t start = ctx->offset;
size_t end = (ctx->offset + ctx->count < ctx->size) ?
ctx->offset + ctx->count : ctx->size;
for (size_t i = start; i < end; i++) {
if (!pb_encode_tag_for_field(stream, field))
return false;
if (!pb_encode_varint(stream, ctx->data[i]))
return false;
}
return true;
}
int main() {
// Búfer para el mensaje codificado
uint8_t buffer[256];
size_t message_length;
// Crear arreglo con 10 elementos
uint32_t values[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- CODIFICACIÓN ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Configurar contexto: codificar elementos 3-7 (offset=2, count=5)
array_context_t ctx = {
.data = values,
.size = 10,
.offset = 2,
.count = 5
};
// Configurar callback
message.values.funcs.encode = uint32_array_encode_callback;
message.values.arg = &ctx;
// 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 < 10; 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:
gcc -o custom_array_example custom_array_example.c 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
- Estructura de contexto: Usa un struct para pasar offset, count y puntero de datos
- Lógica de offset: Comienza la codificación en el offset especificado
- Límite de recuento: Codifica como máximo count elementos después del offset
- Patrón de callback: Usa pb_callback_t con función de codificación personalizada
- 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: Implementación genérica similar a plantillas
Para una implementación de C más genérica (simulando plantillas):
#include <stddef.h>
#include <limits.h>
#define MAX_OFFSET_SIZE (SIZE_MAX / 2)
typedef struct {
const void* data;
size_t element_size;
size_t size;
size_t offset;
size_t count;
bool (*encode_element)(pb_ostream_t*, const pb_field_t*, const void*);
} generic_array_context_t;
bool generic_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
const generic_array_context_t* ctx = (const generic_array_context_t*)*arg;
size_t start = ctx->offset;
size_t end = (ctx->offset + ctx->count < ctx->size) ?
ctx->offset + ctx->count : ctx->size;
const uint8_t* data = (const uint8_t*)ctx->data;
for (size_t i = start; i < end; i++) {
const void* element = data + (i * ctx->element_size);
if (!ctx->encode_element(stream, field, element)) {
return false;
}
}
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
Diferencias con C++
La versión de C difiere de C++ en varios aspectos:
- Sin plantillas: Usa structs y punteros de función en su lugar
- Gestión manual de contexto: Debes pasar el contexto explícitamente
- Sin std::array: Usa arreglos de C simples con seguimiento de tamaño
- Sin std::algorithm: Implementa la iteración manualmente
- Mismo patrón de callback: Ambos usan pb_callback_t
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++