0

is there a way to encode structs or classes with nanopb? I want to hold a struct an encode it with nanopb.

protofile

syntax = "proto2";

package genericgateway;

message Config {
  required string deviceId = 1; 
  repeated DataConnection data = 2;

  message DataConnection {
    required string connectionName = 1; 
    required uint32 sampleTime = 2; 
    required uint32 uploadInterval = 3; 
    repeated Field fields = 4; 

    message Field {
      required string name = 1;
      required Datatype datatype = 2;
      required uint32 dimension = 3; 
      optional string description = 4;
    }

    enum Datatype {
      INTEGER = 1;
      FLOAT = 2;
      STRING = 3;
      BYTES = 4;
      TIMESTAMP = 5;
      BOOLEAN = 6;
    }
  }
}

output generation from nanopb

/* Enum definitions */
typedef enum _genericgateway_Config_DataConnection_Datatype {
    genericgateway_Config_DataConnection_Datatype_INTEGER = 1,
    genericgateway_Config_DataConnection_Datatype_FLOAT = 2,
    genericgateway_Config_DataConnection_Datatype_STRING = 3,
    genericgateway_Config_DataConnection_Datatype_BYTES = 4,
    genericgateway_Config_DataConnection_Datatype_TIMESTAMP = 5,
    genericgateway_Config_DataConnection_Datatype_BOOLEAN = 6
} genericgateway_Config_DataConnection_Datatype;

/* Struct definitions */
typedef struct _genericgateway_Config {
    pb_callback_t deviceId; 
    pb_callback_t data; 
} genericgateway_Config;

typedef struct _genericgateway_Config_DataConnection {
    pb_callback_t connectionName;
    uint32_t sampleTime; 
    uint32_t uploadInterval; 
    pb_callback_t fields;
} genericgateway_Config_DataConnection;

typedef struct _genericgateway_Config_DataConnection_Field {
    pb_callback_t name; 
    genericgateway_Config_DataConnection_Datatype datatype; 
    uint32_t dimension;
    pb_callback_t description;
} genericgateway_Config_DataConnection_Field;

i want to hold the data in a struct like this

enum datatype_e {
      INTEGER,
      FLOAT,
      STRING,
      BYTES,
      TIMESTAMP,
      BOOLEAN,
};

typedef struct
{
 const char *name;
 datatype_e datatype;
 uint32_t dimension;
 const char *description;
} fields_t;

typedef struct
{
  const char *deviceId;
  const char *connectionName;
  uint32_t sampleTime;
  uint32_t uploadInterval;
  fields_t fields[2];
} callback_context_t;

fill the struct

  callback_context_t ctx;
  ctx.deviceId = "iot-device";
  ctx.connectionName = "dev";
  ctx.sampleTime = 5000;
  ctx.uploadInterval = 10;
  ctx.fields[0].name = "sample_float";
  ctx.fields[0].datatype = datatype_e::FLOAT;
  ctx.fields[0].dimension = 1;
  ctx.fields[0].description = "sample_description";
  ctx.fields[1].name = "sample_integer";
  ctx.fields[1].datatype = datatype_e::INTEGER;
  ctx.fields[1].dimension = 1;
  ctx.fields[1].description = "sample_description";

  _genericgateway_Config config = genericgateway_Config_init_zero;
  pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

  config.deviceId.arg = &ctx;
  config.deviceId.funcs.encode = &encode_function???? how can look this

  config.data.arg = &ctx;
  config.data.funcs.encode = &encode_function;??? how can look this

  if (!pb_encode(&stream, genericgateway_Config_fields, &config))
  {
    Serial.print("Decoding failed: ");
    Serial.println(PB_GET_ERROR(&stream));
  }

how can look the encode function? somthings like that... but how can i encode the rest?

bool encode_function(pb_ostream_t *stream, const pb_field_t *field, void *const *arg)
{
  callback_context_t *ctx = (callback_context_t *)(*arg);

  if (!pb_encode_tag_for_field(stream, field))
    return false;

  return pb_encode_string(stream, (uint8_t *)ctx->deviceId, strlen(ctx->deviceId));
}

Thanks a lot,

Regards Markus

2 Answers2

0

It's not clear from your description whether there is actually a reason to use callbacks at all. But in general, nanopb is easiest to use when you specify field options to avoid callback usage unless necessary.

For example, add a file genericgateway.options with:

*.Config.deviceId       type:FT_POINTER
*.Config.data           max_count:1
*.DataConnection.fields max_count:2
*.Field.name            type:FT_POINTER
*.Field.description     type:FT_POINTER

and the generated structures become:

typedef struct _genericgateway_Config_DataConnection_Field {
    char *name;
    genericgateway_Config_DataConnection_Datatype datatype;
    uint32_t dimension;
    char *description;
} genericgateway_Config_DataConnection_Field;

typedef struct _genericgateway_Config_DataConnection {
    pb_callback_t connectionName;
    uint32_t sampleTime;
    uint32_t uploadInterval;
    pb_size_t fields_count;
    genericgateway_Config_DataConnection_Field fields[2];
} genericgateway_Config_DataConnection;

typedef struct _genericgateway_Config {
    char *deviceId;
    pb_size_t data_count;
    genericgateway_Config_DataConnection data[1];
} genericgateway_Config;

This is quite close to the structure you were building manually, while not needing any callbacks.

Note that you can encode strings from a char* with FT_POINTER, but for decoding you would usually allocate a specific size buffer by specifying max_size:123.

jpa
  • 10,351
  • 1
  • 28
  • 45
0

thanks, now my config looks like this

genericgateway_Config config = genericgateway_Config_init_zero;

  config.deviceId = "iot-test-device";
  config.data[0].sampleTime = 5000;
  config.data[0].uploadInterval = 10;
  config.data[0].connectionName = "dev";
  config.data[0].fields[0].name = "sample";
  config.data[0].fields[0].datatype = genericgateway_Config_DataConnection_Datatype_INTEGER;
  config.data[0].fields[0].dimension = 1;
  config.data[0].fields[0].description = "sample_integer_description";
  config.data[0].fields[1].name = "sample3";
  config.data[0].fields[1].datatype = genericgateway_Config_DataConnection_Datatype_INTEGER;
  config.data[0].fields[1].dimension = 1;
  config.data[0].fields[1].description = "sample_integer1_description";

  pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
  if (!pb_encode(&stream, genericgateway_Config_fields, &config))
  {
    Serial.print("Enocde failed: ");
    Serial.println(PB_GET_ERROR(&stream));
  }

  Serial.println(stream.bytes_written);
  for (size_t i = 0; i < stream.bytes_written; i++)
  {
    Serial.print(buffer[i], HEX);
  }
  Serial.println();

after encoding the message bytes written cnt is 17 and the message is hex "0A0F696F742D746573742D646576696365"

if i decode the message online https://www.protobufpal.com/

decoded json looks

{
  "data": [],
  "deviceId": "iot-test-device"
}

where is the other stuff?

regards Markus