NanoPB: Comment gérer les champs optionnels en C
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 en C avec NanoPB.
Définition Proto
Créez d’abord un fichier .proto avec des champs optionnels :
syntax = "proto3";
package example;
message OptionalMessage {
optional uint32 id = 1;
optional string name = 2;
optional float temperature = 3;
bool active = 4; // Regular (non-optional) field
}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 optional.options :
example.OptionalMessage.name max_size:64Générez ensuite :
protoc --nanopb_out=. optional.protoCela générera optional.pb.h et optional.pb.c.
Exemple C
Voici un exemple C complet gérant les champs optionnels :
#include <stdio.h>
#include <stdint.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 régulier (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", (unsigned int)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 :
gcc -o optional_example optional_example.c optional.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 :
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}")Compilez d’abord les définitions protobuf Python :
protoc --python_out=. optional.protoModifiez ensuite l’exemple C pour enregistrer les données encodées dans un fichier :
// Après l'encodage, ajoutez ceci :
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);Exemple avec champs optionnels manquants
Voici un exemple où certains champs optionnels ne sont pas définis :
#include <stdio.h>
#include <stdint.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 régulier 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
- Champs optionnels : Utilisez le mot-clé
optionalen proto3 - Drapeaux has_* : NanoPB génère des drapeaux booléens
has_fieldnamepour chaque champ optionnel - Encodage : Définissez à la fois la valeur du champ ET le drapeau
has_*à true - Décodage : Vérifiez le drapeau
has_*avant d’utiliser les valeurs des champs optionnels - Champs réguliers : Les champs non optionnels n’ont pas de drapeaux
has_* - Valeurs par défaut : Les champs optionnels non définis ont des valeurs par défaut (0, chaîne vide, etc.)
- Économie d’espace : Les champs optionnels non définis ne sont pas inclus dans le message encodé
Quand utiliser les champs optionnels
- Lorsqu’un champ peut être présent ou absent selon les instances du message
- Lorsque vous voulez distinguer entre « non défini » et « défini à la valeur par défaut »
- Lorsque vous voulez économiser de la bande passante en omettant les champs inutilisés
- Lorsqu’un champ n’est pertinent que dans certains contextes
Sortie attendue (exemple complet)
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: trueSortie attendue (exemple partiel)
Encoded 4 bytes (partial)
Encoded data: 08 2a 30 01
Decoded values:
id: set
name: not set
temperature: not set
active: trueNotez que l’encodage partiel est beaucoup plus petit (4 octets contre 19 octets) car les champs optionnels sont omis.
Plus d’articles sur NanoPB
- Types scalaires de base en C++
- Types scalaires de base en C
- Types chaîne en C++
- Types chaîne en C
- Types bytes en C++
- Types bytes en C
- Champs optionnels en C++
- Champs répétés/tableaux en C++
- Champs répétés/tableaux en C
- Enums en C++
- Enums en C
- Messages imbriqués en C++
- Messages imbriqués en C
- Types oneof/union en C++
- Types oneof/union en C
- Convertisseurs de tableaux personnalisés en C++
- Convertisseurs de tableaux personnalisés en C