NanoPB: Convertisseurs de tableaux personnalisés avec offset et taille en C++

English Français

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 :

custom_array.proto
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 :

custom_array.options
example.CustomArrayMessage.values max_count:20

Puis générez :

generate_nanopb_custom_array.sh
protoc --nanopb_out=. custom_array.proto

Cela 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 :

custom_array_example.cpp
#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 :

compile_custom_array_example.sh
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

Quand utiliser les convertisseurs de tableaux personnalisés

Sortie attendue

custom_array_expected_output.txt
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 :

decode_callback_snippet.cpp
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 :

Plus d’articles sur NanoPB


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