NanoPB : Comment gérer les champs répétés (tableaux) en C++

English Français

Voir aussi : Version C : Comment gérer les champs répétés/tableaux en C

NanoPB est une implémentation de Protocol Buffers optimisée pour la taille du code, destinée aux systèmes embarqués. Ce post montre comment gérer les champs répétés (tableaux) en C++ avec NanoPB.

Définition Proto

Créez d’abord un fichier .proto avec des champs répétés :

repeated.proto
syntax = "proto3";

package example;

message RepeatedMessage {
  repeated uint32 values = 1;
  repeated float temperatures = 2;
}

Génération du code NanoPB

Générez le code NanoPB avec un fichier .options pour spécifier les tailles des tableaux :

Créez repeated.options :

repeated.options
example.RepeatedMessage.values max_count:10

Générez ensuite :

generate_nanopb_repeated.sh
protoc --nanopb_out=. repeated.proto

Cela générera repeated.pb.h et repeated.pb.c.

Exemple C++ avec tableaux de taille fixe

Voici un exemple C++ complet utilisant des tableaux de taille fixe :

repeated_example.cpp
#include <stdio.h>
#include "repeated.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

int main() {
    // Tampon pour le message encodé
    uint8_t buffer[256];
    size_t message_length;
    
    // --- ENCODAGE ---
    example_RepeatedMessage message = example_RepeatedMessage_init_zero;
    
    // Définir les valeurs des champs répétés
    message.values[0] = 10;
    message.values[1] = 20;
    message.values[2] = 30;
    message.values_count = 3;  // Important : définir le compte
    
    message.temperatures[0] = 20.5f;
    message.temperatures[1] = 21.0f;
    message.temperatures[2] = 21.5f;
    message.temperatures_count = 3;  // Important : définir le compte
    
    // 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_RepeatedMessage_fields, &message)) {
        printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
        return 1;
    }
    
    message_length = ostream.bytes_written;
    printf("Encodé %zu octets\n", message_length);
    
    // Afficher le dump hexadécimal des données encodées
    printf("Données encodées : ");
    for (size_t i = 0; i < message_length; i++) {
        printf("%02x ", buffer[i]);
    }
    printf("\n");
    
    // --- DÉCODAGE ---
    example_RepeatedMessage decoded = example_RepeatedMessage_init_zero;
    
    // Créer le flux pour le décodage
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    // Décoder le message
    if (!pb_decode(&istream, example_RepeatedMessage_fields, &decoded)) {
        printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    // Afficher les valeurs décodées
    printf("Valeurs décodées :\n");
    printf("  values (%zu éléments) : ", decoded.values_count);
    for (size_t i = 0; i < decoded.values_count; i++) {
        printf("%u ", decoded.values[i]);
    }
    printf("\n");
    
    printf("  temperatures (%zu éléments) : ", decoded.temperatures_count);
    for (size_t i = 0; i < decoded.temperatures_count; i++) {
        printf("%.1f ", decoded.temperatures[i]);
    }
    printf("\n");
    
    return 0;
}

Commande de compilation

Compilez l’exemple avec nanopb. NanoPB est généralement utilisé en incluant directement les fichiers source dans votre projet :

compile_repeated_example.sh
g++ -o repeated_example repeated_example.cpp repeated.pb.c pb_common.c pb_encode.c pb_decode.c -I.

Remarque : Les fichiers source 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 NanoPB.

Script de test Python

Pour vérifier l’encodage, vous pouvez utiliser la bibliothèque protobuf de Python :

test_repeated.py
import repeated_pb2

# Lire les données binaires
with open('encoded.bin', 'rb') as f:
    data = f.read()

# Décoder
msg = repeated_pb2.RepeatedMessage()
msg.ParseFromString(data)

print("Valeurs décodées par Python :")
print(f"  values : {list(msg.values)}")
print(f"  temperatures : {list(msg.temperatures)}")

Compilez d’abord les définitions protobuf Python :

compile_python_repeated.sh
protoc --python_out=. repeated.proto

Modifiez ensuite l’exemple C++ pour enregistrer les données encodées dans un fichier :

save_encoded_repeated.cpp
// Après l'encodage, ajoutez ceci :
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);

Alternative : Champs répétés basés sur des callbacks

Pour une gestion dynamique des tableaux, vous pouvez utiliser des callbacks. Créez repeated_callback.options :

repeated_callback.options
# Utiliser un callback pour les tableaux dynamiques
msg.RepeatedMessage.values callback
msg.RepeatedMessage.temperatures callback

Régénérez ensuite et utilisez cette approche :

repeated_callback_example.cpp
#include <stdio.h>
#include <vector>
#include "repeated.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

// Callback d'encodage pour le tableau uint32
bool uint32_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    const std::vector<uint32_t>* arr = (const std::vector<uint32_t>*)*arg;
    
    for (uint32_t value : *arr) {
        if (!pb_encode_tag_for_field(stream, field))
            return false;
        
        if (!pb_encode_varint(stream, value))
            return false;
    }
    
    return true;
}

// Callback de décodage pour le tableau uint32
bool uint32_array_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
    std::vector<uint32_t>* arr = (std::vector<uint32_t>*)*arg;
    
    uint64_t value;
    if (!pb_decode_varint(stream, &value))
        return false;
    
    arr->push_back((uint32_t)value);
    return true;
}

// Callback d'encodage pour le tableau float
bool float_array_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    const std::vector<float>* arr = (const std::vector<float>*)*arg;
    
    for (float value : *arr) {
        if (!pb_encode_tag_for_field(stream, field))
            return false;
        
        // Encoder le float comme 4 octets
        union { float f; uint32_t u; } u;
        u.f = value;
        if (!pb_encode_varint(stream, u.u))
            return false;
    }
    
    return true;
}

// Callback de décodage pour le tableau float
bool float_array_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
    std::vector<float>* arr = (std::vector<float>*)*arg;
    
    uint64_t value;
    if (!pb_decode_varint(stream, &value))
        return false;
    
    union { float f; uint32_t u; } u;
    u.u = (uint32_t)value;
    arr->push_back(u.f);
    return true;
}

int main() {
    uint8_t buffer[256];
    size_t message_length;
    
    std::vector<uint32_t> values = {10, 20, 30};
    std::vector<float> temperatures = {20.5f, 21.0f, 21.5f};
    
    // --- ENCODAGE ---
    example_RepeatedMessage message = example_RepeatedMessage_init_zero;
    
    message.values.funcs.encode = uint32_array_encode_callback;
    message.values.arg = &values;
    
    message.temperatures.funcs.encode = float_array_encode_callback;
    message.temperatures.arg = &temperatures;
    
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    if (!pb_encode(&ostream, example_RepeatedMessage_fields, &message)) {
        printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
        return 1;
    }
    
    message_length = ostream.bytes_written;
    printf("Encodé %zu octets\n", message_length);
    
    // --- DÉCODAGE ---
    example_RepeatedMessage decoded = example_RepeatedMessage_init_zero;
    std::vector<uint32_t> decoded_values;
    std::vector<float> decoded_temperatures;
    
    decoded.values.funcs.decode = uint32_array_decode_callback;
    decoded.values.arg = &decoded_values;
    
    decoded.temperatures.funcs.decode = float_array_decode_callback;
    decoded.temperatures.arg = &decoded_temperatures;
    
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    if (!pb_decode(&istream, example_RepeatedMessage_fields, &decoded)) {
        printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    printf("Valeurs décodées :\n");
    printf("  values (%zu éléments) : ", decoded_values.size());
    for (uint32_t value : decoded_values) {
        printf("%u ", value);
    }
    printf("\n");
    
    printf("  temperatures (%zu éléments) : ", decoded_temperatures.size());
    for (float temp : decoded_temperatures) {
        printf("%.1f ", temp);
    }
    printf("\n");
    
    return 0;
}

Points clés

Quand utiliser quelle approche

Sortie attendue

repeated_expected_output.txt
Encoded 15 bytes
Encoded data: 08 0a 08 14 08 1e 15 00 00 a4 41 15 00 00 a8 41 15 00 00 ac 41 
Decoded values:
  values (3 items): 10 20 30 
  temperatures (3 items): 20.5 21.0 21.5 

Plus de posts sur NanoPB


Check out similar posts by category: Embedded, C/C++, Protocol Buffers