NanoPB: Convertisseurs de tableaux personnalisés avec offset et taille en C++
Voir aussi : Version C : Comment gérer les convertisseurs de tableaux personnalisés en C
NanoPB est une implémentation de Protocol Buffers optimisée pour la taille du code, destinée aux systèmes embarqués. Cet article montre comment gérer les convertisseurs de tableaux personnalisés en C++ avec NanoPB, basé sur l’implémentation KKS-Firmware.
Définition Proto
Créez d’abord un fichier .proto avec des champs répétés :
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Génération du code NanoPB
Générez le code NanoPB avec un fichier .options pour spécifier le nombre maximal :
Créez custom_array.options :
example.CustomArrayMessage.values max_count:20Puis générez :
protoc --nanopb_out=. custom_array.protoCela générera custom_array.pb.h et custom_array.pb.c.
Exemple C++ avec convertisseur de tableau personnalisé
Voici un exemple C++ complet implémentant un convertisseur de tableau personnalisé avec offset et taille :
#include <stdio.h>
#include <algorithm>
#include <array>
#include <limits>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Convertisseur de tableau personnalisé avec paramètres de modèle offset et taille
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;
// Utiliser une boucle for pour parcourir le conteneur
for (const auto &item : container) {
i++; // Le premier index que nous vérifions est 1
if(i <= OFFSET) { // <=, pas "<", car on ++ avant cette ligne
continue;
}
if(i > OFFSET + SIZE) {
break;
}
// Encoder l'élément
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) {
// Ajouter l'élément au conteneur
// Note : Ceci est un exemple simplifié - l'implémentation réelle dépend du type de conteneur
return false; // Non implémenté dans cet exemple
}
};
// Convertisseur uint32 simple
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() {
// Tampon pour le message encodé
uint8_t buffer[256];
size_t message_length;
// Créer un tableau avec 10 éléments
std::array<uint32_t, 10> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- ENCODAGE ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Utiliser le convertisseur personnalisé : encoder les éléments 3-7 (OFFSET=2, SIZE=5)
using CustomConverter = ArrayConverterWithOffsetAndSize<UInt32Converter, std::array<uint32_t, 10>, 2, 5>;
// Configurer le 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;
// Créer le flux pour l'encodage
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Encoder le message
if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Encodé %zu octets (éléments 3-7)\n", message_length);
// Afficher le dump hexadécimal
printf("Données encodées : ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Afficher ce qui a été encodé
printf("Valeurs encodées : ");
for (size_t i = 2; i < 2 + 5 && i < values.size(); i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Commande de compilation
Compilez l’exemple avec nanopb. NanoPB est généralement utilisé en incluant directement les fichiers sources dans votre projet :
g++ -o custom_array_example custom_array_example.cpp custom_array.pb.c pb_common.c pb_encode.c pb_decode.c -I.Note : Les fichiers sources NanoPB (pb_common.c, pb_encode.c, pb_decode.c) doivent être compilés directement avec votre projet. Vous pouvez les obtenir depuis le dépôt GitHub de NanoPB.
Points clés
- Paramètres de modèle : OFFSET et SIZE contrôlent quels éléments encoder
- Logique d’offset : Ignorer les OFFSET premiers éléments (indexation à partir de 1 dans la boucle)
- Limite de taille : Encoder au plus SIZE éléments après l’offset
- Motif de callback : Utiliser pb_callback_t avec des fonctions d’encodage/décodage personnalisées
- Flexibilité : Permet d’encoder n’importe quelle tranche d’un tableau sans copie
- Efficacité mémoire : Pas besoin de créer des tableaux temporaires
- Motif KKS-Firmware : Basé sur ArrayConverterWithOffsetAndSize de KKS-Firmware
Quand utiliser les convertisseurs de tableaux personnalisés
- Quand vous avez besoin d’encoder un sous-ensemble d’un grand tableau
- Quand vous voulez éviter de copier des données
- Quand vous implémentez de la pagination ou du découpage en blocs
- Quand la disposition du tableau ne correspond pas aux exigences protobuf
- Quand vous avez besoin d’une logique d’encodage personnalisée pour les tableaux
Sortie attendue
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 Notez que seuls les éléments 3, 4, 5, 6, 7 sont encodés (5 éléments à partir de l’offset 2).
Avancé : Support du décodage
Pour supporter le décodage, étendez le convertisseur :
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;
// Ajouter au conteneur (l'implémentation dépend du type de conteneur)
// Pour std::array, vous pourriez suivre un index séparé
return true;
}Utilisation dans le monde réel
Ce motif est utilisé dans KKS-Firmware pour :
- Encoder uniquement les canaux actifs d’un tableau de canaux plus grand
- Envoyer des données de capteur partielles pour économiser de la bande passante
- Implémenter des mises à jour différentielles
- Gérer efficacement les tableaux creux
Plus d’articles sur NanoPB
- Types scalaires de base en C++
- Types scalaires de base en C
- Types chaîne en C++
- Types chaîne en C
- Types bytes en C++
- Types bytes en C
- Champs optionnels en C++
- Champs optionnels en C
- Champs répétés/tableaux en C++
- Champs répétés/tableaux en C
- Enums en C++
- Enums en C
- Messages imbriqués en C++
- Messages imbriqués en C
- Types oneof/union en C++
- Types oneof/union en C
- Convertisseurs de tableaux personnalisés en C