Recently I have been working with .FIT file format and SDK provided by garmin. In a nutshell I need to send Message Definition and Message Data that corresponds to Message Definition. There are many types of messages and almost all the time I need to use few of them - and SDK does not provide that, it sends all messages that are supported. I need to store variables of different data types and relate them to specific message. Currently variables are stored in structures that may look like this:
//Example
typedef struct
{
uint32_t timestamp; // 1 * s + 0,
uint16_t speed; // 1000 * m/s + 0,
uint8_t heart_rate; // 1 * bpm + 0,
} FIT_RECORD_MESG;
However number of variables/arrays as well as data types is different for each FIT_(x)_MESG. This is example of FIT_RECORD_MESG.
I have created function that creates array which contains information which variables/arrays should be sent.
void write_field(FIT_UINT8 field)
{
fields_mesg.fields_number++; //number of messages
fields_mesg.fields_array = (uint8_t*) realloc(fields_mesg.fields_array, sizeof(uint8_t)*fields_mesg.fields_number); //append message type
fields_mesg.fields_array[fields_mesg.fields_number-1] = field; //array of gloabl field types
}
/*
Example array:
[0,2];
0 - timestamp
2 - heart_rate
variable speed should not be sent
*/
Each structure can contain different number of variables/arrays as well as data types. If all data types in structure were the same type (and without arrays) I could create a pointer to the data structure and fields_mesg.fields_array that has been created in write_field() would be an offset from the structure pointer. Unfortunately in my case that cannot be done becouse data types may vary and structure can contain arrays. I was thinking about creating 3 arrays/structures of corresponding data type but it would be more complex (and would need to figure out how to store arrays from structure). I came up with something like lookup table where each position is offset (in bytes) to corresponding data from structure pointer.
#define FIT_RECORD_MESG_32_BIT_START 0+3
#define FIT_RECORD_MESG_16_BIT_START 1+3
#define FIT_RECORD_MESG_8_BIT_START 2+3
#define FIT_RECORD_MESG_ARRAY_SIZE_CNT 51//48+3
#define offsetof(s,m) ((size_t)&(((s*)0)->m))
/*Table of offsets to structure*/
const FIT_UINT8 FIT_RECORD_MESG_SIZES[FIT_RECORD_MESG_ARRAY_SIZE_CNT] =
{
FIT_RECORD_MESG_32_BIT_START,
FIT_RECORD_MESG_16_BIT_START,
FIT_RECORD_MESG_8_BIT_START,
offsetof(FIT_RECORD_MESG, timestamp),
offsetof(FIT_RECORD_MESG, speed),
offsetof(FIT_RECORD_MESG, heart_rate),
}
First three positions are informations where data type is changing. Now by refering to fields_mesg.fields_array I can find pointer to variable in structure and print only 1/2/4bytes or array of 1/2/4bytes - depending on whats in structure FIT_(X)_MESG.
/*
local_mesg_number - used by FIT protocol
*array_sizes - pointer to array of offsets from structure address (example. FIT_RECORD_MESG_SIZES)
*mesg_pointer - pointer to data structure (example. FIT_RECORD_MESG)
*fields_mesg - pointer to structure which contains information which messages to send
*/
void WriteMessageRecord(FIT_UINT8 local_mesg_number, FIT_UINT8* array_sizes, const void* mesg_pointer, FIELDS_TO_SEND* fields_mesg, FILE* fp)
{
void *pointer_offset = mesg_pointer; //begining of the structure
uint16_t message_to_send;
uint16_t offset_diference=0;
/*Write begining of the message*/
WriteData(&local_mesg_number, FIT_HDR_SIZE, fp);
/*Go through every message in fields_mesg.fields_array*/
for (uint16_t cnt = 0; cnt < fields_mesg->fields_number;cnt++)
{
message_to_send = fields_mesg->fields_array[cnt];
/*If data type is 1 byte*/
if (message_to_send >= (array_sizes[2]-3))
{
offset_diference = array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3];
/*Check if this isn't array and print as many as array length*/
if (offset_diference > sizeof(uint8_t))
{
for (uint8_t x = 0;x < offset_diference; x++)
{
pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3]+x;
WriteData(((uint8_t*)pointer_offset), 1, fp);
printf("\nARRAY: %d, %d", (*(uint8_t*)pointer_offset), message_to_send);
}
}
else
{
pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
WriteData(((uint16_t*)pointer_offset), 1, fp);
printf("\nNOT : %d, %d", (*(uint8_t*)pointer_offset), message_to_send);
}
}
/*If data type is 2 bytes*/
else if (message_to_send >= (array_sizes[1] - 3))
{
offset_diference = (array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3]);
/*Check if this isn't array and print as many as array length*/
if (offset_diference > sizeof(uint16_t))
{
for (uint8_t x = 0;x < offset_diference; x+=2)
{
pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3]+x;
WriteData(((uint16_t*)pointer_offset), 2, fp);
printf("\nARRAY : %d, %d", (*(uint16_t*)pointer_offset), message_to_send);
}
}
else //Print only 2 bytes
{
pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
WriteData(((uint16_t*)pointer_offset), 2, fp);
printf("\nNOT : %d, %d", (*(uint16_t*)pointer_offset), message_to_send);
}
}
/*If data type is 4 bytes*/
else if (message_to_send >= (array_sizes[0]-3))
{
offset_diference = (array_sizes[message_to_send + 4] - array_sizes[message_to_send + 3]);
/*Check if this isn't array and print as many as array length*/
if (offset_diference > sizeof(uint32_t))
{
for (uint8_t x = 0;x < offset_diference; x += 4)
{
pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3] + x;
WriteData(((uint32_t*)pointer_offset), 4, fp);
printf("\nARRAY : %d, %d", (*(uint32_t*)pointer_offset), message_to_send);
}
}
else //Print only 4 bytes
{
pointer_offset = ((char*)mesg_pointer) + array_sizes[message_to_send + 3];
WriteData(((uint32_t*)pointer_offset), 4, fp);
printf("\nNOT : %d, %d", (*(uint32_t*)pointer_offset), message_to_send);
}
}
}
}
And in main function:
// Write message of type: Record
FIT_RECORD_MESG record_mesg;
write_field(FIT_RECORD_MESG_TIMESTAMP); //will be sending timestamp variable
write_field(FIT_RECORD_MESG_ENHANCED_SPEED); //will be sending enhanced_speed variable
record_mesg.timestamp = 5;
record_mesg.enhanced_speed = 6;
WriteMessageDefinitionSelection(0, fit_mesg_defs[FIT_MESG_RECORD], &fields_mesg, fp); //send message definition
WriteMessageRecord(0, FIT_RECORD_MESG_SIZES ,&record_mesg, &fields_mesg, fp); //send message data
clear_fields();
This works but I don't like few things about it:
- I need to create additional offset arrays which takes extra memory in ROM.
- I need to calculate where each data type begins.
- Any changes to structure would create an error.
So.. Is there any more elegant way to store variables/arrays of different data types and easily access them?