Див. також: C version: How to handle custom array converters in C
NanoPB — це оптимізована за розміром коду реалізація Protocol Buffers для вбудованих систем. Ця публікація показує, як обробляти користувацькі конвертери масивів у C++ за допомогою NanoPB, на основі реалізації KKS-Firmware.
Визначення Proto
Спочатку створіть файл .proto з repeated-полями:
syntax = "proto3";
package example;
message CustomArrayMessage {
repeated uint32 values = 1;
}Генерація коду NanoPB
Згенеруйте код NanoPB за допомогою файлу .options, щоб указати максимальну кількість:
Створіть custom_array.options:
example.CustomArrayMessage.values max_count:20Потім згенеруйте:
protoc --nanopb_out=. custom_array.protoЦе згенерує custom_array.pb.h та custom_array.pb.c.
Приклад на C++ з користувацьким конвертером масиву
Ось повний приклад на C++, що реалізує користувацький конвертер масиву з параметрами offset та size:
#include <stdio.h>
#include <algorithm>
#include <array>
#include <limits>
#include "custom_array.pb.h"
#include "pb_encode.h"
#include "pb_decode.h"
// Користувацький конвертер масиву з шаблонними параметрами offset та size
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;
// Використовуємо цикл for для ітерації по контейнеру
for (const auto &item : container) {
i++; // Перший індекс, який ми перевіряємо, — 1
if(i <= OFFSET) { // <=, а не "<", оскільки ми ++ перед цим рядком
continue;
}
if(i > OFFSET + SIZE) {
break;
}
// Кодуємо елемент
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) {
// Додаємо елемент до контейнера
// Примітка: Це спрощений приклад — реальна реалізація залежить від типу контейнера
return false; // Не реалізовано в цьому прикладі
}
};
// Простий конвертер uint32
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() {
// Буфер для закодованого повідомлення
uint8_t buffer[256];
size_t message_length;
// Створюємо масив з 10 елементами
std::array<uint32_t, 10> values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// --- КОДУВАННЯ ---
example_CustomArrayMessage message = example_CustomArrayMessage_init_zero;
// Використовуємо користувацький конвертер: кодуємо елементи 3-7 (OFFSET=2, SIZE=5)
using CustomConverter = ArrayConverterWithOffsetAndSize<UInt32Converter, std::array<uint32_t, 10>, 2, 5>;
// Налаштовуємо 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;
// Створюємо потік для кодування
pb_ostream_t ostream = pb_ostream_from_buffer(buffer, sizeof(buffer));
// Кодуємо повідомлення
if (!pb_encode(&ostream, example_CustomArrayMessage_fields, &message)) {
printf("Помилка кодування: %s\n", PB_GET_ERROR(&ostream));
return 1;
}
message_length = ostream.bytes_written;
printf("Закодовано %zu байтів (елементи 3-7)\n", message_length);
// Виводимо hex-дамп
printf("Закодовані дані: ");
for (size_t i = 0; i < message_length; i++) {
printf("%02x ", buffer[i]);
}
printf("\n");
// Виводимо, що було закодовано
printf("Закодовані значення: ");
for (size_t i = 2; i < 2 + 5 && i < values.size(); i++) {
printf("%u ", values[i]);
}
printf("\n");
return 0;
}Команда компіляції
Скомпілюйте приклад з nanopb. NanoPB зазвичай використовується шляхом безпосереднього включення вихідних файлів у ваш проєкт:
g++ -o custom_array_example custom_array_example.cpp custom_array.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.
Ключові моменти
- Шаблонні параметри: OFFSET та SIZE керують тим, які елементи кодувати
- Логіка зсуву: Пропустити перші OFFSET елементів (індексація з 1 у циклі)
- Обмеження розміру: Закодувати не більше SIZE елементів після зсуву
- Патерн callback: Використовувати pb_callback_t з користувацькими функціями encode/decode
- Гнучкість: Можна кодувати будь-який зріз масиву без копіювання
- Ефективність пам’яті: Не потрібно створювати тимчасові масиви
- Патерн KKS-Firmware: Базується на ArrayConverterWithOffsetAndSize з KKS-Firmware
Коли використовувати користувацькі конвертери масивів
- Коли потрібно закодувати підмножину великого масиву
- Коли ви хочете уникнути копіювання даних
- Коли реалізуєте пагінацію або розбиття на частини
- Коли структура масиву не відповідає вимогам protobuf
- Коли потрібна користувацька логіка кодування для масивів
Очікуваний вивід
Закодовано 10 байтів (елементи 3-7)
Закодовані дані: 08 03 08 04 08 05 08 06 08 07
Закодовані значення: 3 4 5 6 7 Зверніть увагу, що закодовуються лише елементи 3, 4, 5, 6, 7 (5 елементів, починаючи зі зсуву 2).
Додатково: Підтримка декодування
Щоб підтримати декодування, розширте конвертер:
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;
// Додаємо до контейнера (реалізація залежить від типу контейнера)
// Для std::array можна відстежувати окремий індекс
return true;
}Реальне використання
Цей патерн використовується в KKS-Firmware для:
- Кодування лише активних каналів з більшого масиву каналів
- Надсилання часткових даних датчиків для економії пропускної здатності
- Реалізації диференціальних оновлень
- Ефективної обробки розріджених масивів
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++
- 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