BLF/APP/blf.c

590 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file blf.c
* @brief Реализация функций для создания BLF-файлов.
* @details Содержит все функции, объявленные в blf.h. Не использует динамическую память.
* Поддерживает stdio и FatFS через условную компиляцию (USE_FATFS).
*
* @author (Ваше имя)
* @date 2026-03-20
*/
#include "blf.h"
#include <string.h>
/* -------------------------------------------------------------------------
* Вспомогательные функции файлового ввода-вывода (обёртки над stdio/FatFS)
* ------------------------------------------------------------------------- */
/* Открытие файла на запись (создание/перезапись) */
static int blf_file_open(BLFContext *ctx, const char *filename) {
#ifdef USE_FATFS
FRESULT res = f_open(&ctx->fp, filename, FA_WRITE | FA_CREATE_ALWAYS);
if (res != FR_OK) {
BLF_ERROR_PRINTF("ERROR: FatFS f_open failed with code %d\n", res);
return -1;
}
return 0;
#else
ctx->fp = fopen(filename, "w+b");
if (ctx->fp == NULL) {
BLF_ERROR_PRINTF("ERROR: Failed to open file %s\n", filename);
return -1;
}
return 0;
#endif
}
/* Запись блока данных */
static int blf_file_write(BLFContext *ctx, const void *ptr, size_t size) {
#ifdef USE_FATFS
UINT bw;
FRESULT res = f_write(&ctx->fp, ptr, size, &bw);
if (res != FR_OK || bw != size) {
BLF_ERROR_PRINTF("ERROR: FatFS f_write failed (res=%d, bw=%u, size=%u)\n",
res, bw, (unsigned)size);
return -1;
}
return 0;
#else
if (fwrite(ptr, 1, size, ctx->fp) != size) {
BLF_ERROR_PRINTF("ERROR: fwrite failed (size=%u)\n", (unsigned)size);
return -1;
}
return 0;
#endif
}
/* Получение текущей позиции в файле */
static long blf_file_tell(BLFContext *ctx) {
#ifdef USE_FATFS
long pos = (long) f_tell(&ctx->fp);
if (pos < 0) {
BLF_ERROR_PRINTF("ERROR: FatFS f_tell failed\n");
}
return pos;
#else
long pos = ftell(ctx->fp);
if (pos < 0) {
BLF_ERROR_PRINTF("ERROR: ftell failed\n");
}
return pos;
#endif
}
/* Установка позиции в файле (от начала) */
static int blf_file_seek(BLFContext *ctx, long offset) {
#ifdef USE_FATFS
FRESULT res = f_lseek(&ctx->fp, offset);
if (res != FR_OK) {
BLF_ERROR_PRINTF("ERROR: FatFS f_lseek to %ld failed\n", offset);
return -1;
}
return 0;
#else
if (fseek(ctx->fp, offset, SEEK_SET) != 0) {
BLF_ERROR_PRINTF("ERROR: fseek to %ld failed\n", offset);
return -1;
}
return 0;
#endif
}
/* Перемещение в конец файла */
static int blf_file_seek_end(BLFContext *ctx) {
#ifdef USE_FATFS
FRESULT res = f_lseek(&ctx->fp, f_size(&ctx->fp));
if (res != FR_OK) {
BLF_ERROR_PRINTF("ERROR: FatFS f_lseek to end failed\n");
return -1;
}
return 0;
#else
if (fseek(ctx->fp, 0, SEEK_END) != 0) {
BLF_ERROR_PRINTF("ERROR: fseek to end failed\n");
return -1;
}
return 0;
#endif
}
/* Закрытие файла */
static int blf_file_close(BLFContext *ctx) {
#ifdef USE_FATFS
FRESULT res = f_close(&ctx->fp);
if (res != FR_OK) {
BLF_ERROR_PRINTF("ERROR: FatFS f_close failed\n");
return -1;
}
return 0;
#else
if (fclose(ctx->fp) != 0) {
BLF_ERROR_PRINTF("ERROR: fclose failed\n");
return -1;
}
return 0;
#endif
}
/* -------------------------------------------------------------------------
* Вспомогательная функция: добавить миллисекунды к SYSTEMTIME
* ------------------------------------------------------------------------- */
static void systemtime_add_ms(SYSTEMTIME *st, uint64_t ms) {
uint64_t total_ms = st->milliseconds + ms;
st->milliseconds = total_ms % 1000;
uint64_t carry_seconds = total_ms / 1000;
uint64_t total_seconds = st->second + carry_seconds;
st->second = total_seconds % 60;
uint64_t carry_minutes = total_seconds / 60;
uint64_t total_minutes = st->minute + carry_minutes;
st->minute = total_minutes % 60;
uint64_t carry_hours = total_minutes / 60;
uint64_t total_hours = st->hour + carry_hours;
st->hour = total_hours % 24;
/* Дни не меняются предполагаем, что запись укладывается в один день */
}
/* -------------------------------------------------------------------------
* Реализация API-функций
* ------------------------------------------------------------------------- */
int blf_open(BLFContext *ctx, const char *filename) {
memset(ctx, 0, sizeof(*ctx));
if (blf_file_open(ctx, filename) != 0) {
return -1;
}
/* Инициализация заголовка */
ctx->header.signature = BL_FILE_SIGNATURE;
ctx->header.statisticsSize = sizeof(FileHeader);
ctx->header.apiNumber = 4070100;
ctx->header.applicationId = 0;
ctx->header.compressionLevel = 0;
ctx->header.applicationMajor = 0;
ctx->header.applicationMinor = 0;
ctx->header.applicationBuild = 0;
ctx->header.fileSize = 0;
ctx->header.uncompressedFileSize = 0;
ctx->header.objectCount = 0;
ctx->header.restorePointsOffset = 0;
/* Время начала измерения */
ctx->header.measurementStartTime.year = 2026;
ctx->header.measurementStartTime.month = 3;
ctx->header.measurementStartTime.dayOfWeek = 3;
ctx->header.measurementStartTime.day = 18;
ctx->header.measurementStartTime.hour = 12;
ctx->header.measurementStartTime.minute = 0;
ctx->header.measurementStartTime.second = 0;
ctx->header.measurementStartTime.milliseconds = 0;
memset(&ctx->header.lastObjectTime, 0, sizeof(SYSTEMTIME));
/* Запись заголовка в начало файла */
ctx->headerPos = blf_file_tell(ctx);
if (ctx->headerPos < 0) {
blf_file_close(ctx);
return -1;
}
if (blf_file_write(ctx, &ctx->header, sizeof(FileHeader)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write file header\n");
blf_file_close(ctx);
return -1;
}
ctx->objectCount = 0;
ctx->maxTimestamp = 0;
ctx->in_container = 0;
return 0;
}
int blf_start_container(BLFContext *ctx, uint64_t timestamp) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_start_container: null context\n");
return -1;
}
if (ctx->in_container) {
BLF_ERROR_PRINTF("ERROR: Already inside a container (nested containers not supported)\n");
return -1;
}
/* Подготовка заголовка контейнера */
ctx->container_hdr.base.mSignature = BL_OBJ_SIGNATURE;
ctx->container_hdr.base.mHeaderSize = sizeof(VBLObjectHeaderBase);
ctx->container_hdr.base.mHeaderVersion = 1;
ctx->container_hdr.base.mObjectSize = 0; /* будет заполнено позже */
ctx->container_hdr.base.mObjectType = BL_OBJ_TYPE_LOG_CONTAINER;
memset(&ctx->container_hdr.data, 0, sizeof(LogContainerData));
ctx->container_hdr.data.compressionMethod = 0;
/* Запись заголовка контейнера */
ctx->container_hdr_pos = blf_file_tell(ctx);
if (ctx->container_hdr_pos < 0) {
return -1;
}
if (blf_file_write(ctx, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write container header\n");
return -1;
}
ctx->container_timestamp = timestamp;
ctx->in_container = 1;
return 0;
}
int blf_end_container(BLFContext *ctx) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_end_container: null context\n");
return -1;
}
if (!ctx->in_container) {
BLF_ERROR_PRINTF("ERROR: No open container to end\n");
return -1;
}
/* Вычисляем размер данных внутри контейнера */
long current_pos = blf_file_tell(ctx);
if (current_pos < 0) return -1;
long data_size = current_pos - (ctx->container_hdr_pos + sizeof(ContainerHeader));
if (data_size < 0) {
BLF_ERROR_PRINTF("ERROR: Negative container data size\n");
return -1;
}
/* Обновляем заголовок контейнера */
ctx->container_hdr.base.mObjectSize = sizeof(ContainerHeader) + data_size;
ctx->container_hdr.data.uncompressedFileSize = (uint32_t)data_size;
/* Перезаписываем заголовок */
if (blf_file_seek(ctx, ctx->container_hdr_pos) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to seek to container header\n");
return -1;
}
if (blf_file_write(ctx, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to rewrite container header\n");
return -1;
}
/* Возвращаемся в конец файла */
if (blf_file_seek_end(ctx) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to seek to end of file after container\n");
return -1;
}
ctx->objectCount++;
if (ctx->container_timestamp > ctx->maxTimestamp)
ctx->maxTimestamp = ctx->container_timestamp;
ctx->in_container = 0;
return 0;
}
int blf_add_can_message(BLFContext *ctx,
uint16_t channel,
uint32_t id,
uint8_t flags,
uint8_t dlc,
const uint8_t *data,
uint64_t timestamp) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_add_can_message: null context\n");
return -1;
}
if (dlc > 8) {
BLF_ERROR_PRINTF("ERROR: CAN message DLC > 8\n");
return -1;
}
VBLObjectHeader objHdr;
VBLCANMessage canMsg;
canMsg.mChannel = channel;
canMsg.mFlags = flags;
canMsg.mDLC = dlc;
canMsg.mID = id;
if (data && dlc <= 8) {
memcpy(canMsg.mData, data, dlc);
if (dlc < 8) memset(canMsg.mData + dlc, 0, 8 - dlc);
} else {
memset(canMsg.mData, 0, 8);
}
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLCANMessage);
objHdr.mBase.mObjectType = BL_OBJ_TYPE_CAN_MESSAGE;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write CAN message header\n");
return -1;
}
if (blf_file_write(ctx, &canMsg, sizeof(canMsg)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write CAN message body\n");
return -1;
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
int blf_add_lin_message_obsolete(BLFContext *ctx,
uint16_t channel,
uint8_t id,
uint8_t dlc,
const uint8_t *data,
uint8_t dir,
uint64_t timestamp,
uint16_t checksum) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_add_lin_message_obsolete: null context\n");
return -1;
}
if (dlc > 8) {
BLF_ERROR_PRINTF("ERROR: LIN message DLC > 8\n");
return -1;
}
VBLObjectHeader objHdr;
VBLLINMessage linMsg;
memset(&linMsg, 0, sizeof(linMsg));
linMsg.mChannel = channel;
linMsg.mID = id & 0x3F;
linMsg.mDLC = dlc;
if (data && dlc > 0) memcpy(linMsg.mData, data, dlc);
uint8_t header_bits = 34; /* синхроразрыв + синхрополе + идентификатор */
uint8_t response_bits = (dlc + 1) * 10; /* данные + контрольная сумма */
linMsg.mHeaderTime = header_bits;
linMsg.mFullTime = header_bits + response_bits;
linMsg.mCRC = checksum;
linMsg.mDir = dir;
linMsg.mReserved = 0;
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLLINMessage);
objHdr.mBase.mObjectType = BL_OBJ_TYPE_LIN_MESSAGE;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write LIN message header\n");
return -1;
}
if (blf_file_write(ctx, &linMsg, sizeof(linMsg)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write LIN message body\n");
return -1;
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
int blf_add_lin_send_error(BLFContext *ctx,
uint16_t channel,
uint8_t id,
uint8_t dlc,
uint64_t timestamp) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_add_lin_send_error: null context\n");
return -1;
}
VBLObjectHeader objHdr;
VBLLINSendError sendErr;
memset(&sendErr, 0, sizeof(sendErr));
sendErr.mChannel = channel;
sendErr.mID = id & 0x3F;
sendErr.mDLC = dlc;
sendErr.mHeaderTime = 34;
sendErr.mFullTime = 34;
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLLINSendError);
objHdr.mBase.mObjectType = BL_OBJ_TYPE_LIN_SND_ERROR;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write LIN send error header\n");
return -1;
}
if (blf_file_write(ctx, &sendErr, sizeof(sendErr)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write LIN send error body\n");
return -1;
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
int blf_add_env_data(BLFContext *ctx,
const char *name,
const uint8_t *data,
uint32_t data_len,
uint64_t timestamp) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_add_env_data: null context\n");
return -1;
}
if (data == NULL && data_len > 0) {
BLF_ERROR_PRINTF("ERROR: blf_add_env_data: data is NULL but data_len > 0\n");
return -1;
}
uint32_t name_len = (name != NULL) ? (uint32_t)strlen(name) : 0;
uint32_t object_size = sizeof(VBLObjectHeader) + sizeof(uint32_t) * 2 + sizeof(uint64_t) + name_len + data_len;
VBLObjectHeader objHdr;
objHdr.mBase.mSignature = BL_OBJ_SIGNATURE;
objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader);
objHdr.mBase.mHeaderVersion = 1;
objHdr.mBase.mObjectSize = object_size;
objHdr.mBase.mObjectType = BL_OBJ_TYPE_ENV_DATA;
objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS;
objHdr.mClientIndex = 0;
objHdr.mObjectVersion = 0;
objHdr.mObjectTimeStamp = timestamp;
if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA header\n");
return -1;
}
uint32_t nameLength = name_len;
uint32_t dataLength = data_len;
uint64_t reserved = 0;
if (blf_file_write(ctx, &nameLength, sizeof(nameLength)) != 0 ||
blf_file_write(ctx, &dataLength, sizeof(dataLength)) != 0 ||
blf_file_write(ctx, &reserved, sizeof(reserved)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA fields\n");
return -1;
}
if (name_len > 0 && name != NULL) {
if (blf_file_write(ctx, name, name_len) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA name\n");
return -1;
}
}
if (data_len > 0 && data != NULL) {
if (blf_file_write(ctx, data, data_len) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA data\n");
return -1;
}
}
/* Выравнивание до 4 байт (требование формата) */
long pos = blf_file_tell(ctx);
if (pos < 0) return -1;
long pad = (4 - (pos % 4)) % 4;
for (long i = 0; i < pad; i++) {
uint8_t zero = 0;
if (blf_file_write(ctx, &zero, 1) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA padding\n");
return -1;
}
}
if (!ctx->in_container) ctx->objectCount++;
if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp;
return 0;
}
/* Удобные обёртки (преобразование времени) */
int blf_add_can_message_struct(BLFContext *ctx, const CanMessageStruct *msg) {
uint64_t ts_ns = (uint64_t)msg->timestamp * 1000000ULL;
return blf_add_can_message(ctx, msg->channel, msg->id, msg->flags, msg->dlc, msg->data, ts_ns);
}
int blf_add_lin_message_struct(BLFContext *ctx, const LinMessageStruct *msg) {
uint64_t ts_ns = (uint64_t)msg->timestamp * 1000000ULL;
return blf_add_lin_message_obsolete(ctx, msg->channel, msg->id, msg->dlc, msg->data, msg->dir, ts_ns, msg->checksum);
}
int blf_add_lin_send_error_struct(BLFContext *ctx, const LinSendErrorStruct *err) {
uint64_t ts_ns = (uint64_t)err->timestamp * 1000000ULL;
return blf_add_lin_send_error(ctx, err->channel, err->id, err->dlc, ts_ns);
}
int blf_add_env_data_struct(BLFContext *ctx, const EnvDataStruct *env) {
uint64_t ts_ns = (uint64_t)env->timestamp * 1000000ULL;
return blf_add_env_data(ctx, env->name, env->data, env->data_len, ts_ns);
}
int blf_close(BLFContext *ctx) {
if (!ctx) {
BLF_ERROR_PRINTF("ERROR: blf_close: null context\n");
return -1;
}
/* Закрываем незавершённый контейнер, если есть */
if (ctx->in_container) {
if (blf_end_container(ctx) != 0) {
BLF_ERROR_PRINTF("WARNING: Failed to close remaining container\n");
}
}
/* Выравнивание файла до 4 байт */
long endPos = blf_file_tell(ctx);
if (endPos < 0) {
blf_file_close(ctx);
return -1;
}
long padding = (4 - (endPos % 4)) % 4;
for (long i = 0; i < padding; i++) {
uint8_t zero = 0;
if (blf_file_write(ctx, &zero, 1) != 0) {
blf_file_close(ctx);
return -1;
}
}
endPos += padding;
/* Вычисляем время последнего объекта */
ctx->header.lastObjectTime = ctx->header.measurementStartTime;
uint64_t total_ms = ctx->maxTimestamp / 1000000;
systemtime_add_ms(&ctx->header.lastObjectTime, total_ms);
/* Обновляем поля заголовка файла */
ctx->header.fileSize = (uint64_t)endPos;
ctx->header.uncompressedFileSize = (uint64_t)endPos;
ctx->header.objectCount = ctx->objectCount;
/* Перезаписываем заголовок */
if (blf_file_seek(ctx, ctx->headerPos) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to seek to file header\n");
blf_file_close(ctx);
return -1;
}
if (blf_file_write(ctx, &ctx->header, sizeof(FileHeader)) != 0) {
BLF_ERROR_PRINTF("ERROR: Failed to rewrite file header\n");
blf_file_close(ctx);
return -1;
}
return blf_file_close(ctx);
}