NanoPB: Gérer les champs optionnels en C++

English Français

Voir aussi : Version C : Comment gérer les champs optionnels 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 champs optionnels (proto3 optional) en C++ avec NanoPB.

Définition Proto

Créez d’abord un fichier .proto avec des champs optionnels :

optional.proto
syntax = "proto3";

package example;

message OptionalMessage {
  optional uint32 id = 1;
  optional string name = 2;
  optional float temperature = 3;
  bool active = 4;  // Champ normal (non optionnel)
}

Génération du code NanoPB

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

Créez optional.options :

optional.options
example.OptionalMessage.name max_size:64

Puis générez :

generate_nanopb_optional.sh
protoc --nanopb_out=. optional.proto

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

Exemple C++

Voici un exemple C++ complet gérant les champs optionnels :

optional_example.cpp
#include <stdio.h>
#include <string.h>
#include "optional.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_OptionalMessage message = example_OptionalMessage_init_zero;
    
    // Définir les champs optionnels
    message.id = 42;
    message.has_id = true;  // Important : définir le drapeau has_*
    
    strncpy(message.name, "Sensor1", sizeof(message.name) - 1);
    message.has_name = true;  // Important : définir le drapeau has_*
    
    message.temperature = 23.5f;
    message.has_temperature = true;  // Important : définir le drapeau has_*
    
    // Champ normal (pas de drapeau has_* nécessaire)
    message.active = true;
    
    // 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_OptionalMessage_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_OptionalMessage decoded = example_OptionalMessage_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_OptionalMessage_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");
    if (decoded.has_id) {
        printf("  id : %u\n", decoded.id);
    } else {
        printf("  id : (non défini)\n");
    }
    
    if (decoded.has_name) {
        printf("  name : %s\n", decoded.name);
    } else {
        printf("  name : (non défini)\n");
    }
    
    if (decoded.has_temperature) {
        printf("  temperature : %f\n", decoded.temperature);
    } else {
        printf("  temperature : (non défini)\n");
    }
    
    printf("  active : %s\n", decoded.active ? "true" : "false");
    
    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_optional_example.sh
g++ -o optional_example optional_example.cpp optional.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.

Script de test Python

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

test_optional.py
import optional_pb2

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

# Décoder
msg = optional_pb2.OptionalMessage()
msg.ParseFromString(data)

print("Valeurs décodées par Python :")
print(f"  id : {msg.id if msg.HasField('id') else '(non défini)'}")
print(f"  name : {msg.name if msg.HasField('name') else '(non défini)'}")
print(f"  temperature : {msg.temperature if msg.HasField('temperature') else '(non défini)'}")
print(f"  active : {msg.active}")

D’abord, compilez les définitions protobuf Python :

compile_python_optional.sh
protoc --python_out=. optional.proto

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

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

Exemple avec des champs optionnels manquants

Voici un exemple où certains champs optionnels ne sont pas définis :

optional_partial_example.cpp
#include <stdio.h>
#include <string.h>
#include "optional.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

int main() {
    uint8_t buffer[256];
    size_t message_length;
    
    // --- ENCODAGE ---
    example_OptionalMessage message = example_OptionalMessage_init_zero;
    
    // Définir uniquement certains champs optionnels
    message.id = 42;
    message.has_id = true;
    
    // name et temperature non définis (les drapeaux has_* restent à false)
    
    // Champ normal toujours défini
    message.active = true;
    
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    if (!pb_encode(&ostream, example_OptionalMessage_fields, &message)) {
        printf("Échec de l'encodage : %s\n", PB_GET_ERROR(&ostream));
        return 1;
    }
    
    message_length = ostream.bytes_written;
    printf("Encodé %zu octets (partiel)\n", message_length);
    
    // --- DÉCODAGE ---
    example_OptionalMessage decoded = example_OptionalMessage_init_zero;
    
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    if (!pb_decode(&istream, example_OptionalMessage_fields, &decoded)) {
        printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    printf("Valeurs décodées :\n");
    printf("  id : %s\n", decoded.has_id ? "défini" : "non défini");
    printf("  name : %s\n", decoded.has_name ? "défini" : "non défini");
    printf("  temperature : %s\n", decoded.has_temperature ? "défini" : "non défini");
    printf("  active : %s\n", decoded.active ? "true" : "false");
    
    return 0;
}

Points clés

Quand utiliser les champs optionnels

Sortie attendue (exemple complet)

optional_full_expected_output.txt
Encoded 19 bytes
Encoded data: 08 2a 12 07 53 65 6e 73 6f 72 31 1d 00 00 bc 41 30 01 
Decoded values:
  id: 42
  name: Sensor1
  temperature: 23.500000
  active: true

Sortie attendue (exemple partiel)

optional_partial_expected_output.txt
Encoded 4 bytes (partial)
Encoded data: 08 2a 30 01 
Decoded values:
  id: set
  name: not set
  temperature: not set
  active: true

Notez que l’encodage partiel est beaucoup plus petit (4 octets contre 19 octets) car les champs optionnels sont omis.

Plus d’articles sur NanoPB


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