Потоковая передача данных с помощью ProtoBuf в файл журнала с заголовком

Я пытаюсь передать данные в файл журнала на SD-карте микроконтроллера, который считывает данные с некоторых датчиков и сохраняет значение в файле.

Для сериализации данных я буду использовать NanoPB, реализацию protobuf для C, которая довольно ресурсоэффективна.

Файл журнала имеет следующую структуру: он должен записывать короткий заголовок, состоящий из GUID и версии прошивки. После заголовка поток данных должен быть непрерывным, и он должен регистрировать поля от датчиков, но не значения заголовка (это должно происходить только один раз и в начале).

Ограничение состоит в том, что я могу использовать только один файл .proto для сериализации и десериализации, и я хочу избежать функций Pb_callback, которые возникают из-за использования «повторяющихся» полей в .proto и последующего использования C-реализации nanopb. https://jpa.kapsi.fi/nanopb/docs/concepts.html.

Я пробовал следующую реализацию (поля - это всего лишь примеры):

syntax = "proto3";

import "timestamp.proto";
import "nanopb.proto";

message LogHeader {
    string firmware = 1 [(nanopb).max_size = 11];  
    string GUID = 2 [(nanopb).max_size = 11];       
}

message Sensors {
    int32 TimeStamp = 3;        
    // Sensory data
    int32 Sens1 = 4;
    int32 Sens2 = 5;
    int32 Sens3 = 6;
    int32 Sens4 = 7;
    int32 Sense5 = 8;

}

Идея заключалась бы в том, чтобы иметь файл журнала, который после обработки выглядел бы так:

firmware "1.0.0"
GUID "1231214211321" (example)
Timestamp 123123
Sens1 2343
Sens2 13123
Sens3 13443
Sens4 1231
Sens5 190
Timestamp 123124
Sens1 2345
Sens2 2312
...

Но если все поля должны быть в одном сообщении, GUID и прошивка будут регистрироваться при каждом повторении. Хотя, если я разделю его на 2 сообщения, я не смогу десериализовать их за один раз с одним прото-файлом. Мне нужно было бы узнать длину первых двух сообщений, десериализовать их, а затем начать с этого журнала.


person Asan    schedule 23.10.2019    source источник


Ответы (1)


Я хочу избежать функций Pb_callback, которые возникают из-за использования "повторяющихся" полей в .proto

Обратите внимание, что вы можете указать max_count для повторяющихся полей так же, как вы указали max_size для строк, и тогда вы получите простой массив вместо обратного вызова.

Хотя, если я разделю его на 2 сообщения, я не смогу десериализовать их за один раз с одним прото-файлом.

Для десериализации Protobuf необходимо знать тип сообщения. Самый распространенный способ справиться с этим - создать одно сообщение верхнего уровня с вложенными сообщениями:

message LogMessage {
   optional LogHeader header = 1;
   optional Sensors sensors = 2;
}

Затем вы можете установить одно или оба поля заголовка и датчика, а также has_header и has_sensors в значение true или false, чтобы указать, хотите ли вы включить это подполе. Но независимо от содержимого вы всегда сериализуете и десериализуете как LogMessage, поэтому не возникает путаницы между различными типами сообщений.

Мне нужно было бы узнать длину первых двух сообщений, десериализовать их, а затем начать с этого журнала.

Да, это обычная проблема для новичков и с protobuf. Сами сообщения Protobuf не кодируют свою длину, поэтому, если у вас есть несколько сообщений в одном файле, вам нужно как-то разделить их.

довольно распространенный способ - добавить префикс длины, как это сделано в pb_encode_delimited() nanopb и pb_decode_delimited(). Этот формат также поддерживается библиотекой C ++ protobuf. Однако недостатком этого является то, что многие инструменты командной строки, такие как protoc, не поддерживают формат с разделителями, и, например, Библиотека Python protobuf делает их декодирование несколько сложным.

Другой вариант - сделать так, чтобы весь файл выглядел как одно сообщение, но записать его в нескольких частях. В Protobuf есть функция слияния, то есть, если вы просто добавляете сообщения одно за другим, они объединяются вместе. Это можно сделать, включив повторяющиеся поля в LogMessage:

message LogMessage {
   optional LogHeader header = 1;
   repeated Sensors sensors = 2 [(nanopb).max_count = 1];
}

Теперь, если вы закодируете несколько копий LogMessage, каждая с одной записью sensors, они сольются вместе. Затем, если вы декодируете файл, он будет выглядеть как один LogMessage с несколькими sensors записями.

person jpa    schedule 23.10.2019
comment
Если я правильно понял, разве .max_count не относится к максимальному количеству повторений, которое вы ожидаете (чего я не знаю)? Я не понимаю, почему вы установили его на 1. Я знаю, что это нужно для того, чтобы избежать обратного вызова, но почему на 1? Спасибо за объяснение @jpa. - person Asan; 23.10.2019
comment
@ A.San Если вы хотите кодировать только одно сообщение датчика за раз, достаточно 1. Но да, вы не сможете декодировать комбинированное сообщение таким образом с помощью nanopb, для этого вам потребуются обратные вызовы. - person jpa; 23.10.2019
comment
Хорошо, в этом случае мне просто нужно кодировать с помощью nanopb и декодировать в другом месте. Таким образом, это должно выполнять работу по кодированию. - person Asan; 23.10.2019