NanoPB: Comment gérer les messages imbriqués en C

English Français

Voir aussi : Version C++ : Comment gérer les messages imbriqué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 messages imbriqués en C avec NanoPB.

Définition Proto

Créez d’abord un fichier .proto avec des messages imbriqués :

nested.proto
syntax = "proto3";

package example;

message Address {
  string street = 1;
  string city = 2;
  string country = 3;
}

message Person {
  string name = 1;
  uint32 age = 2;
  Address address = 3;
}

Générer le code NanoPB

Générez le code NanoPB avec un fichier .options pour spécifier les tailles des tampons de chaîne :

Créez nested.options :

nested.options
example.Address.street max_size:64
example.Address.city max_size:32
example.Address.country max_size:32
example.Person.name max_size:64

Générez ensuite :

generate_nanopb_nested.sh
protoc --nanopb_out=. nested.proto

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

Exemple C

Voici un exemple C complet gérant les messages imbriqués :

nested_example.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "nested.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_Person person = example_Person_init_zero;
    
    // Définir les valeurs du message imbriqué
    strncpy(person.name, "John Doe", sizeof(person.name) - 1);
    person.has_address = true;
    strncpy(person.address.street, "123 Main St", sizeof(person.address.street) - 1);
    strncpy(person.address.city, "Springfield", sizeof(person.address.city) - 1);
    strncpy(person.address.country, "USA", sizeof(person.address.country) - 1);
    
    // 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_Person_fields, &person)) {
        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_Person decoded = example_Person_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_Person_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("  name : %s\n", decoded.name);
    printf("  age : %u\n", (unsigned int)decoded.age);
    printf("  address :\n");
    printf("    street : %s\n", decoded.address.street);
    printf("    city : %s\n", decoded.address.city);
    printf("    country : %s\n", decoded.address.country);
    
    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 :

compile_nested_example.sh
gcc -o nested_example nested_example.c nested.pb.c pb_common.c pb_encode.c pb_decode.c -I.

Remarque : 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.

Script de test Python

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

test_nested.py
import nested_pb2

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

# Décoder
msg = nested_pb2.Person()
msg.ParseFromString(data)

print("Valeurs décodées par Python :")
print(f"  name : {msg.name}")
print(f"  age : {msg.age}")
print(f"  address :")
print(f"    street : {msg.address.street}")
print(f"    city : {msg.address.city}")
print(f"    country : {msg.address.country}")

Compilez d’abord les définitions protobuf Python :

compile_python_nested.sh
protoc --python_out=. nested.proto

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

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

Exemple avec message imbriqué optionnel

Voici un exemple avec un message imbriqué optionnel :

nested_optional.proto
syntax = "proto3";

package example;

message Address {
  string street = 1;
  string city = 2;
}

message Person {
  string name = 1;
  optional Address address = 2;
}

Créez nested_optional.options :

nested_optional.options
example.Address.street max_size:64
example.Address.city max_size:32
example.Person.name max_size:64
nested_optional_example.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "nested_optional.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

int main() {
    uint8_t buffer[256];
    size_t message_length;
    
    // --- ENCODAGE ---
    example_Person person = example_Person_init_zero;
    
    strncpy(person.name, "Jane Doe", sizeof(person.name) - 1);
    
    // Définir le message imbriqué optionnel
    strncpy(person.address.street, "456 Oak Ave", sizeof(person.address.street) - 1);
    strncpy(person.address.city, "Boston", sizeof(person.address.city) - 1);
    person.has_address = true;  // Important : définir le drapeau has_*
    
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    if (!pb_encode(&ostream, example_Person_fields, &person)) {
        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_Person decoded = example_Person_init_zero;
    
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    if (!pb_decode(&istream, example_Person_fields, &decoded)) {
        printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    printf("Valeurs décodées :\n");
    printf("  name : %s\n", decoded.name);
    if (decoded.has_address) {
        printf("  address :\n");
        printf("    street : %s\n", decoded.address.street);
        printf("    city : %s\n", decoded.address.city);
    } else {
        printf("  address : (non défini)\n");
    }
    
    return 0;
}

Points clés

Quand utiliser les messages imbriqués

Sortie attendue

nested_expected_output.txt
Encoded 35 bytes
Encoded data: 0a 08 4a 6f 68 6e 20 44 6f 65 10 1e 1a 1b 0a 0b 31 32 33 20 4d 61 69 6e 20 53 74 12 08 4e 65 77 20 59 6f 72 6b 1a 03 55 53 41 
Decoded values:
  name: John Doe
  age: 30
  address:
    street: 123 Main St
    city: New York
    country: USA

Sortie attendue (imbriqué optionnel)

nested_optional_expected_output.txt
Encoded 27 bytes
Decoded values:
  name: Jane Doe
  address:
    street: 456 Oak Ave
    city: Boston

Différences par rapport au C++

La version C est presque identique à la version C++, avec des différences clés :

Plus d’articles sur NanoPB


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