NanoPB: Як обробляти необов'язкові поля в C
Див. також: C++ version: How to handle optional fields in C++
NanoPB — це оптимізована за розміром коду реалізація Protocol Buffers для вбудованих систем. Ця публікація показує, як обробляти необов’язкові поля в C за допомогою NanoPB.
Визначення Proto
Спочатку створіть файл .proto з необов’язковими полями:
syntax = "proto3";
package example;
message OptionalMessage {
optional uint32 id = 1;
optional string name = 2;
optional float temperature = 3;
bool active = 4; // Звичайне (необов'язкове) поле
}Генерація коду NanoPB
Згенеруйте код NanoPB за допомогою файлу .options, щоб указати розміри строкових буферів:
Створіть optional.options:
example.OptionalMessage.name max_size:64Потім згенеруйте:
protoc --nanopb_out=. optional.protoЦе згенерує optional.pb.h та optional.pb.c.
Приклад на C
Ось повний приклад на C для обробки необов’язкових полів:
#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;
// --- КОДУВАННЯ ---
example_OptionalMessage message = example_OptionalMessage_init_zero;
// Встановлення необов'язкових полів
message.id = 42;
message.has_id = true; // Важливо: встановіть прапорець has_*
strncpy(message.name, "Sensor1", sizeof(message.name) - 1);
message.has_name = true; // Важливо: встановіть прапорець has_*
message.temperature = 23.5f;
message.has_temperature = true; // Важливо: встановіть прапорець has_*
// Звичайне поле (прапорець has_* не потрібен)
message.active = true;
// Створення потоку для кодування
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Кодування повідомлення
if (!pb_encode(&ostream, example_OptionalMessage_fields, &message)) {
printf("Помилка кодування: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Закодовано %zu байтів\n", message_length);
// Друк шістнадцяткового дампу закодованих даних
printf("Закодовані дані: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// --- ДЕКОДУВАННЯ ---
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("Помилка декодування: %s\n", PB_GET_ERROR(&istream));
return 1;
}
// Друк декодованих значень
printf("Декодовані значення:\n");
if (decoded.has_id) {
printf(" id: %u\n", (unsigned int)decoded.id);
} else {
printf(" id: (не встановлено)\n");
}
if (decoded.has_name) {
printf(" name: %s\n", decoded.name);
} else {
printf(" name: (не встановлено)\n");
}
if (decoded.has_temperature) {
printf(" temperature: %f\n", decoded.temperature);
} else {
printf(" temperature: (не встановлено)\n");
}
printf(" active: %s\n", decoded.active ? "true" : "false");
return 0;
}Команда компіляції
Скомпілюйте приклад за допомогою nanopb. NanoPB зазвичай використовується шляхом прямого включення вихідних файлів у ваш проєкт:
gcc -o optional_example optional_example.c optional.pb.c pb_common.c pb_encode.c pb_decode.c -I.Примітка: Вихідні файли NanoPB (pb_common.c, pb_encode.c, pb_decode.c) потрібно компілювати разом із вашим проєктом. Їх можна отримати з NanoPB GitHub repository.
Скрипт тестування на Python
Щоб перевірити кодування, ви можете використати бібліотеку protobuf для Python:
import optional_pb2
# Читання двійкових даних
with open('encoded.bin', 'rb') as f:
data = f.read()
# Декодування
msg = optional_pb2.OptionalMessage()
msg.ParseFromString(data)
print("Декодовані Python значення:")
print(f" id: {msg.id if msg.HasField('id') else '(не встановлено)'}")
print(f" name: {msg.name if msg.HasField('name') else '(не встановлено)'}")
print(f" temperature: {msg.temperature if msg.HasField('temperature') else '(не встановлено)'}")
print(f" active: {msg.active}")Спочатку скомпілюйте визначення protobuf для Python:
protoc --python_out=. optional.protoПотім змініть приклад на C, щоб зберегти закодовані дані у файл:
// Після кодування додайте це:
FILE *f = fopen("encoded.bin", "wb");
fwrite(buffer, 1, message_length, f);
fclose(f);Приклад з відсутніми необов’язковими полями
Ось приклад, де деякі необов’язкові поля не встановлено:
#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;
// --- КОДУВАННЯ ---
example_OptionalMessage message = example_OptionalMessage_init_zero;
// Встановлення лише деяких необов'язкових полів
message.id = 42;
message.has_id = true;
// name та temperature не встановлено (прапорці has_* залишаються false)
// Звичайне поле завжди встановлено
message.active = true;
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
if (!pb_encode(&ostream, example_OptionalMessage_fields, &message)) {
printf("Помилка кодування: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Закодовано %zu байтів (частково)\n", message_length);
// --- ДЕКОДУВАННЯ ---
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("Помилка декодування: %s\n", PB_GET_ERROR(&istream));
return 1;
}
printf("Декодовані значення:\n");
printf(" id: %s\n", decoded.has_id ? "встановлено" : "не встановлено");
printf(" name: %s\n", decoded.has_name ? "встановлено" : "не встановлено");
printf(" temperature: %s\n", decoded.has_temperature ? "встановлено" : "не встановлено");
printf(" active: %s\n", decoded.active ? "true" : "false");
return 0;
}Ключові моменти
- Необов’язкові поля: Використовуйте ключове слово
optionalу proto3 - Прапорці has_*: NanoPB генерує булеві прапорці
has_fieldnameдля кожного необов’язкового поля - Кодування: Встановіть і значення поля, і прапорець
has_*у true - Декодування: Перевіряйте прапорець
has_*перед використанням значень необов’язкових полів - Звичайні поля: Необов’язкові поля не мають прапорців
has_* - Значення за замовчуванням: Необов’язкові поля, які не встановлено, мають значення за замовчуванням (0, порожній рядок тощо)
- Економія простору: Необов’язкові поля, які не встановлено, не включаються до закодованого повідомлення
Коли використовувати необов’язкові поля
- Коли поле може бути присутнім або відсутнім у різних екземплярах повідомлення
- Коли ви хочете розрізняти «не встановлено» та «встановлено у значення за замовчуванням»
- Коли ви хочете зекономити пропускну здатність, пропускаючи невикористані поля
- Коли поле релевантне лише у певних контекстах
Очікуваний вивід (повний приклад)
Закодовано 19 байтів
Закодовані дані: 08 2a 12 07 53 65 6e 73 6f 72 31 1d 00 00 bc 41 30 01
Декодовані значення:
id: 42
name: Sensor1
temperature: 23.500000
active: trueОчікуваний вивід (частковий приклад)
Закодовано 4 байтів (частково)
Закодовані дані: 08 2a 30 01
Декодовані значення:
id: встановлено
name: не встановлено
temperature: не встановлено
active: trueЗверніть увагу, що часткове кодування набагато менше (4 байти проти 19 байтів), оскільки необов’язкові поля пропущено.
More NanoPB posts
- Basic scalar types in C++
- Basic scalar types in C
- String types in C++
- String types in C
- Bytes types in C++
- Bytes types in C
- Optional fields in C++
- Repeated fields/arrays in C++
- Repeated fields/arrays in C
- Enums in C++
- Enums in C
- Nested messages in C++
- Nested messages in C
- Oneof/union types in C++
- Oneof/union types in C
- Custom array converters in C++
- Custom array converters in C