NanoPB: Benutzerdefinierte Array-Konverter mit Offset und Größe in C++
Siehe auch: C-Version: Umgang mit benutzerdefinierten Array-Konvertern in C
NanoPB ist eine codegrößenoptimierte Protocol-Buffers-Implementierung für Embedded-Systeme. Dieser Beitrag zeigt, wie man benutzerdefinierte Array-Konverter in C++ mit NanoPB handhabt, basierend auf der KKS-Firmware-Implementierung.
Proto-Definition
Erstellen Sie zunächst eine .proto-Datei mit wiederholten Feldern:
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}NanoPB-Code generieren
Generieren Sie den NanoPB-Code mit einer .options-Datei, um die maximale Anzahl anzugeben:
Erstellen Sie custom_array.options:
example.CustomArrayMessage.values max_count:20Dann generieren:
protoc --nanopb_out=. custom_array.protoDies generiert custom_array.pb.h und custom_array.pb.c.
C++-Beispiel mit benutzerdefiniertem Array-Konverter
Hier ist ein vollständiges C++-Beispiel, das einen benutzerdefinierten Array-Konverter mit Offset und Größe implementiert:
#include <stdio.h>
#include <algorithm>
#include <array>
#include <limits>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Custom array converter with offset and size template parameters
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;
// Use for loop to iterate through container
for (const auto &item : container) {
i++; // First index we check is 1
if(i <= OFFSET) { // <=, not "<", since we ++ before this line
continue;
}
if(i > OFFSET + SIZE) {
break;
}
// Encode the 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) {
// Add item to container
// Note: This is a simplified example - real implementation depends on container type
return false; // Not implemented in this example
}
};
// Simple uint32 converter
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 for encoded message
uint8_t buffer[256];
size_t message_length;
// Create array with 10 elements
std::array<uint32_t, 10> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- ENCODING ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Use custom converter: encode elements 3-7 (OFFSET=2, SIZE=5)
using CustomConverter = ArrayConverterWithOffsetAndSize<UInt32Converter, std::array<uint32_t, 10>, 2, 5>;
// Set up 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;
// Create stream for encoding
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Encode the message
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);
// Print hex dump
printf("Encoded data: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Print what was encoded
printf("Encoded values: ");
for (size_t i = 2; i < 2 + 5 && i < values.size(); i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Kompilierbefehl
Kompilieren Sie das Beispiel mit nanopb. NanoPB wird typischerweise verwendet, indem die Quelldateien direkt in Ihr Projekt eingebunden werden:
g++ -o custom_array_example custom_array_example.cpp custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.Hinweis: NanoPB-Quelldateien (pb_common.c, pb_encode.c, pb_decode.c) müssen direkt mit Ihrem Projekt kompiliert werden. Sie können diese aus dem NanoPB GitHub-Repository beziehen.
Wichtige Punkte
- Template-Parameter: OFFSET und SIZE steuern, welche Elemente kodiert werden
- Offset-Logik: Die ersten OFFSET-Elemente überspringen (1-basierte Indizierung in der Schleife)
- Größenbegrenzung: Höchstens SIZE-Elemente nach dem Offset kodieren
- Callback-Muster: pb_callback_t mit benutzerdefinierten Kodier-/Dekodierfunktionen verwenden
- Flexibilität: Kann jeden Ausschnitt eines Arrays ohne Kopieren kodieren
- Speichereffizienz: Keine Notwendigkeit, temporäre Arrays zu erstellen
- KKS-Firmware-Muster: Basiert auf ArrayConverterWithOffsetAndSize aus der KKS-Firmware
Wann benutzerdefinierte Array-Konverter verwendet werden
- Wenn Sie eine Teilmenge eines großen Arrays kodieren müssen
- Wenn Sie das Kopieren von Daten vermeiden möchten
- Bei der Implementierung von Paginierung oder Chunking
- Wenn das Array-Layout nicht den Protocol-Buffers-Anforderungen entspricht
- Wenn Sie benutzerdefinierte Kodierlogik für Arrays benötigen
Erwartete Ausgabe
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 Beachten Sie, dass nur die Elemente 3, 4, 5, 6, 7 kodiert werden (5 Elemente ab Offset 2).
Erweitert: Dekodierungsunterstützung
Um die Dekodierung zu unterstützen, erweitern Sie den Konverter:
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;
// Add to container (implementation depends on container type)
// For std::array, you might track a separate index
return true;
}Praxisnahe Verwendung
Dieses Muster wird in der KKS-Firmware verwendet für:
- Kodieren nur aktiver Kanäle aus einem größeren Kanal-Array
- Senden partieller Sensordaten zur Bandbreitenersparnis
- Implementieren von differentiellen Aktualisierungen
- Effizientes Handhaben von dünnbesetzten Arrays
Weitere NanoPB-Beiträge
- Basistypen (Skalare) in C++
- Basistypen (Skalare) in C
- String-Typen in C++
- String-Typen in C
- Bytes-Typen in C++
- Bytes-Typen in C
- Optionale Felder in C++
- Optionale Felder in C
- Wiederholte Felder/Arrays in C++
- Wiederholte Felder/Arrays in C
- Enums in C++
- Enums in C
- Verschachtelte Nachrichten in C++
- Verschachtelte Nachrichten in C
- Oneof/Union-Typen in C++
- Oneof/Union-Typen in C
- Benutzerdefinierte Array-Konverter in C