NanoPB: Comment gérer les types bytes en C++

English Français

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

Définition Proto

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

bytes.proto
syntax = "proto3";

package example;

message BytesMessage {
  bytes data = 1;
  bytes signature = 2;
}

Générer le code NanoPB

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

Créez bytes.options :

bytes.options
example.BytesMessage.data max_size:32
example.BytesMessage.signature max_size:16

Générez ensuite :

generate_nanopb_bytes.sh
protoc --nanopb_out=. bytes.proto

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

Exemple C++ avec tampons de taille fixe

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

bytes_example.cpp
#include <stdio.h>
#include <string.h>
#include "bytes.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_BytesMessage message = example_BytesMessage_init_zero;
    
    // Définir les données bytes
    const uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
    const uint8_t signature[] = {0xAA, 0xBB, 0xCC, 0xDD};
    
    message.data.size = sizeof(data);
    memcpy(message.data.bytes, data, sizeof(data));
    
    message.signature.size = sizeof(signature);
    memcpy(message.signature.bytes, signature, sizeof(signature));
    
    // 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_BytesMessage_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_BytesMessage decoded = example_BytesMessage_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_BytesMessage_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("  data : ");
    for (size_t i = 0; i < decoded.data.size; i++) {
        printf("%02x ", decoded.data.bytes[i]);
    }
    printf("\n");
    printf("  signature : ");
    for (size_t i = 0; i < decoded.signature.size; i++) {
        printf("%02x ", decoded.signature.bytes[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 :

compile_bytes_example.sh
g++ -o bytes_example bytes_example.cpp bytes.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_bytes.py
import bytes_pb2

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

# Décoder
msg = bytes_pb2.BytesMessage()
msg.ParseFromString(data)

print("Valeurs décodées par Python :")
print(f"  data : {msg.data.hex()}")
print(f"  signature : {msg.signature.hex()}")

Compilez d’abord les définitions protobuf Python :

compile_python_bytes.sh
protoc --python_out=. bytes.proto

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

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

Alternative : bytes basés sur des callbacks

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

bytes_callback.options
# Use callback for dynamic bytes
msg.BytesMessage.data callback
msg.BytesMessage.signature callback

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

bytes_callback_example.cpp
#include <stdio.h>
#include <vector>
#include "bytes.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"

// Callback d'encodage pour les bytes
bool bytes_encode_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) {
    const std::vector<uint8_t>* data = (const std::vector<uint8_t>*)*arg;
    
    if (!pb_encode_tag_for_field(stream, field))
        return false;
    
    return pb_encode_string(stream, data->data(), data->size());
}

// Callback de décodage pour les bytes
bool bytes_decode_callback(pb_istream_t *stream, const pb_field_t *field, void **arg) {
    std::vector<uint8_t>* data = (std::vector<uint8_t>*)*arg;
    
    // Redimensionner le vecteur pour contenir les données
    size_t size = stream->bytes_left;
    data->resize(size);
    
    // Lire les données dans le vecteur
    return pb_read(stream, data->data(), size);
}

int main() {
    uint8_t buffer[256];
    size_t message_length;
    
    std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04, 0x05};
    std::vector<uint8_t> signature = {0xAA, 0xBB, 0xCC, 0xDD};
    
    // --- ENCODAGE ---
    example_BytesMessage message = example_BytesMessage_init_zero;
    
    message.data.funcs.encode = bytes_encode_callback;
    message.data.arg = &data;
    message.signature.funcs.encode = bytes_encode_callback;
    message.signature.arg = &signature;
    
    pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
    
    if (!pb_encode(&ostream, example_BytesMessage_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_BytesMessage decoded = example_BytesMessage_init_zero;
    std::vector<uint8_t> decoded_data, decoded_signature;
    
    decoded.data.funcs.decode = bytes_decode_callback;
    decoded.data.arg = &decoded_data;
    decoded.signature.funcs.decode = bytes_decode_callback;
    decoded.signature.arg = &decoded_signature;
    
    pb_istream_t istream = pb_istream_from_buffer(buffer, message_length);
    
    if (!pb_decode(&istream, example_BytesMessage_fields, &decoded)) {
        printf("Échec du décodage : %s\n", PB_GET_ERROR(&istream));
        return 1;
    }
    
    printf("Valeurs décodées :\n");
    printf("  data : ");
    for (uint8_t byte : decoded_data) {
        printf("%02x ", byte);
    }
    printf("\n");
    printf("  signature : ");
    for (uint8_t byte : decoded_signature) {
        printf("%02x ", byte);
    }
    printf("\n");
    
    return 0;
}

Points clés

Quand utiliser quelle approche

Sortie attendue

bytes_expected_output.txt
Encoded 14 bytes
Encoded data: 0a 05 01 02 03 04 05 12 04 aa bb cc dd 
Decoded values:
  data: 01 02 03 04 05 
  signature: aa bb cc dd 

Cas d’usage des bytes

Plus d’articles sur NanoPB


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