This commit is contained in:
cfif 2026-03-25 14:01:07 +03:00
commit a5cc146c1c
8 changed files with 1481 additions and 0 deletions

648
APP/blf.c Normal file
View File

@ -0,0 +1,648 @@
/**
* @file blf.c
* @brief Реализация функций для создания BLF-файлов (разделяемая библиотека).
*/
#include "blf.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* -------------------------------------------------------------------------
* Упакованные структуры BLF (повторяем из оригинального заголовка)
* ------------------------------------------------------------------------- */
#if defined(_MSC_VER)
#pragma pack(push, 1)
#else
#define PACKED __attribute__((packed))
#endif
typedef struct PACKED {
uint16_t year;
uint16_t month;
uint16_t dayOfWeek;
uint16_t day;
uint16_t hour;
uint16_t minute;
uint16_t second;
uint16_t milliseconds;
} SYSTEMTIME_PACKED;
typedef struct PACKED {
uint32_t signature;
uint32_t statisticsSize;
uint32_t apiNumber;
uint8_t applicationId;
uint8_t compressionLevel;
uint8_t applicationMajor;
uint8_t applicationMinor;
uint64_t fileSize;
uint64_t uncompressedFileSize;
uint32_t objectCount;
uint32_t applicationBuild;
SYSTEMTIME_PACKED measurementStartTime;
SYSTEMTIME_PACKED lastObjectTime;
uint64_t restorePointsOffset;
uint32_t reservedFileStatistics[16];
} FileHeader;
typedef struct PACKED {
uint32_t mSignature;
uint16_t mHeaderSize;
uint16_t mHeaderVersion;
uint32_t mObjectSize;
uint32_t mObjectType;
} VBLObjectHeaderBase;
typedef struct PACKED {
VBLObjectHeaderBase mBase;
uint32_t mObjectFlags;
uint16_t mClientIndex;
uint16_t mObjectVersion;
uint64_t mObjectTimeStamp;
} VBLObjectHeader;
typedef struct PACKED {
uint16_t mChannel;
uint8_t mFlags;
uint8_t mDLC;
uint32_t mID;
uint8_t mData[8];
} VBLCANMessage;
typedef struct PACKED {
uint16_t mChannel;
uint8_t mID;
uint8_t mDLC;
uint8_t mData[8];
uint8_t mFSMId;
uint8_t mFSMState;
uint8_t mHeaderTime;
uint8_t mFullTime;
uint16_t mCRC;
uint8_t mDir;
uint8_t mReserved;
} VBLLINMessage;
typedef struct PACKED {
uint16_t mChannel;
uint8_t mID;
uint8_t mDLC;
uint8_t mFSMId;
uint8_t mFSMState;
uint8_t mHeaderTime;
uint8_t mFullTime;
} VBLLINSendError;
typedef struct PACKED {
uint16_t compressionMethod;
uint16_t reserved1;
uint32_t reserved2;
uint32_t uncompressedFileSize;
uint32_t reserved3;
} LogContainerData;
typedef struct PACKED {
VBLObjectHeaderBase base;
LogContainerData data;
} ContainerHeader;
#if defined(_MSC_VER)
#pragma pack(pop)
#else
#undef PACKED
#endif
/* -------------------------------------------------------------------------
* Контекст (полное определение)
* ------------------------------------------------------------------------- */
typedef struct {
FILE* fp;
FileHeader header;
long headerPos;
int objectCount;
uint64_t maxTimestamp;
int in_container;
ContainerHeader container_hdr;
long container_hdr_pos;
uint64_t container_timestamp;
} BLFContext;
/* -------------------------------------------------------------------------
* Вспомогательные функции
* ------------------------------------------------------------------------- */
static int systemtime_to_packed(const SYSTEMTIME* src, SYSTEMTIME_PACKED* dst) {
if (!src || !dst) return -1;
dst->year = src->year;
dst->month = src->month;
dst->dayOfWeek = src->dayOfWeek;
dst->day = src->day;
dst->hour = src->hour;
dst->minute = src->minute;
dst->second = src->second;
dst->milliseconds = src->milliseconds;
return 0;
}
static void systemtime_packed_to_unpacked(const SYSTEMTIME_PACKED* src, SYSTEMTIME* dst) {
dst->year = src->year;
dst->month = src->month;
dst->dayOfWeek = src->dayOfWeek;
dst->day = src->day;
dst->hour = src->hour;
dst->minute = src->minute;
dst->second = src->second;
dst->milliseconds = src->milliseconds;
}
static void systemtime_add_ms(SYSTEMTIME_PACKED* st, uint64_t ms) {
uint64_t total_ms = st->milliseconds + ms;
st->milliseconds = (uint16_t)(total_ms % 1000);
uint64_t carry_seconds = total_ms / 1000;
uint64_t total_seconds = st->second + carry_seconds;
st->second = (uint16_t)(total_seconds % 60);
uint64_t carry_minutes = total_seconds / 60;
uint64_t total_minutes = st->minute + carry_minutes;
st->minute = (uint16_t)(total_minutes % 60);
uint64_t carry_hours = total_minutes / 60;
uint64_t total_hours = st->hour + carry_hours;
st->hour = (uint16_t)(total_hours % 24);
/* Дни не меняем считаем, что запись укладывается в один день */
}
static void get_current_systemtime(SYSTEMTIME* st) {
time_t t = time(NULL);
struct tm* tm = localtime(&t);
st->year = tm->tm_year + 1900;
st->month = tm->tm_mon + 1;
st->dayOfWeek = tm->tm_wday;
st->day = tm->tm_mday;
st->hour = tm->tm_hour;
st->minute = tm->tm_min;
st->second = tm->tm_sec;
st->milliseconds = 0; /* точность до секунды */
}
/* -------------------------------------------------------------------------
* Файловые операции (только stdio)
* ------------------------------------------------------------------------- */
static int blf_file_write(FILE* fp, const void* ptr, size_t size) {
if (fwrite(ptr, 1, size, fp) != size) {
fprintf(stderr, "ERROR: fwrite failed (size=%zu)\n", size);
return -1;
}
return 0;
}
static long blf_file_tell(FILE* fp) {
long pos = ftell(fp);
if (pos < 0) {
fprintf(stderr, "ERROR: ftell failed\n");
}
return pos;
}
static int blf_file_seek(FILE* fp, long offset) {
if (fseek(fp, offset, SEEK_SET) != 0) {
fprintf(stderr, "ERROR: fseek to %ld failed\n", offset);
return -1;
}
return 0;
}
static int blf_file_seek_end(FILE* fp) {
if (fseek(fp, 0, SEEK_END) != 0) {
fprintf(stderr, "ERROR: fseek to end failed\n");
return -1;
}
return 0;
}
/* -------------------------------------------------------------------------
* Реализация API
* ------------------------------------------------------------------------- */
BLF_API void* blf_open(const char* filename, const SYSTEMTIME* startTime) {
BLFContext* ctx = (BLFContext*)calloc(1, sizeof(BLFContext));
if (!ctx) {
fprintf(stderr, "ERROR: Failed to allocate context\n");
return NULL;
}
ctx->fp = fopen(filename, "w+b");
if (!ctx->fp) {
fprintf(stderr, "ERROR: Failed to open file %s\n", filename);
free(ctx);
return NULL;
}
/* Инициализация заголовка */
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;
/* Время начала */
SYSTEMTIME st;
if (startTime) {
st = *startTime;
} else {
get_current_systemtime(&st);
}
if (systemtime_to_packed(&st, &ctx->header.measurementStartTime) != 0) {
fclose(ctx->fp);
free(ctx);
return NULL;
}
/* lastObjectTime пока равно startTime */
ctx->header.lastObjectTime = ctx->header.measurementStartTime;
/* Запись заголовка */
ctx->headerPos = blf_file_tell(ctx->fp);
if (ctx->headerPos < 0) {
fclose(ctx->fp);
free(ctx);
return NULL;
}
if (blf_file_write(ctx->fp, &ctx->header, sizeof(FileHeader)) != 0) {
fclose(ctx->fp);
free(ctx);
return NULL;
}
ctx->objectCount = 0;
ctx->maxTimestamp = 0;
ctx->in_container = 0;
return ctx;
}
BLF_API int blf_set_start_time(void* handle, const SYSTEMTIME* startTime) {
if (!handle || !startTime) return -1;
BLFContext* ctx = (BLFContext*)handle;
return systemtime_to_packed(startTime, &ctx->header.measurementStartTime);
}
BLF_API int blf_start_container(void* handle, uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (ctx->in_container) {
fprintf(stderr, "ERROR: Already inside a container\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->fp);
if (ctx->container_hdr_pos < 0) return -1;
if (blf_file_write(ctx->fp, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) {
fprintf(stderr, "ERROR: Failed to write container header\n");
return -1;
}
ctx->container_timestamp = timestamp;
ctx->in_container = 1;
return 0;
}
BLF_API int blf_end_container(void* handle) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (!ctx->in_container) {
fprintf(stderr, "ERROR: No open container to end\n");
return -1;
}
long current_pos = blf_file_tell(ctx->fp);
if (current_pos < 0) return -1;
long data_size = current_pos - (ctx->container_hdr_pos + sizeof(ContainerHeader));
if (data_size < 0) {
fprintf(stderr, "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->fp, ctx->container_hdr_pos) != 0) {
fprintf(stderr, "ERROR: Failed to seek to container header\n");
return -1;
}
if (blf_file_write(ctx->fp, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) {
fprintf(stderr, "ERROR: Failed to rewrite container header\n");
return -1;
}
if (blf_file_seek_end(ctx->fp) != 0) {
fprintf(stderr, "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;
}
BLF_API int blf_add_can_message(void* handle,
uint16_t channel,
uint32_t id,
uint8_t flags,
uint8_t dlc,
const uint8_t* data,
uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (dlc > 8) {
fprintf(stderr, "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->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write CAN message header\n");
return -1;
}
if (blf_file_write(ctx->fp, &canMsg, sizeof(canMsg)) != 0) {
fprintf(stderr, "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;
}
BLF_API int blf_add_lin_message_obsolete(void* handle,
uint16_t channel,
uint8_t id,
uint8_t dlc,
const uint8_t* data,
uint8_t dir,
uint64_t timestamp,
uint16_t checksum) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (dlc > 8) {
fprintf(stderr, "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->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write LIN message header\n");
return -1;
}
if (blf_file_write(ctx->fp, &linMsg, sizeof(linMsg)) != 0) {
fprintf(stderr, "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;
}
BLF_API int blf_add_lin_send_error(void* handle,
uint16_t channel,
uint8_t id,
uint8_t dlc,
uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
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->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "ERROR: Failed to write LIN send error header\n");
return -1;
}
if (blf_file_write(ctx->fp, &sendErr, sizeof(sendErr)) != 0) {
fprintf(stderr, "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;
}
BLF_API int blf_add_env_data(void* handle,
const char* name,
const uint8_t* data,
uint32_t data_len,
uint64_t timestamp) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
if (data == NULL && data_len > 0) {
fprintf(stderr, "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->fp, &objHdr, sizeof(objHdr)) != 0) {
fprintf(stderr, "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->fp, &nameLength, sizeof(nameLength)) != 0 ||
blf_file_write(ctx->fp, &dataLength, sizeof(dataLength)) != 0 ||
blf_file_write(ctx->fp, &reserved, sizeof(reserved)) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA fields\n");
return -1;
}
if (name_len > 0 && name != NULL) {
if (blf_file_write(ctx->fp, name, name_len) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA name\n");
return -1;
}
}
if (data_len > 0 && data != NULL) {
if (blf_file_write(ctx->fp, data, data_len) != 0) {
fprintf(stderr, "ERROR: Failed to write ENV_DATA data\n");
return -1;
}
}
/* Выравнивание до 4 байт */
long pos = blf_file_tell(ctx->fp);
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->fp, &zero, 1) != 0) {
fprintf(stderr, "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;
}
BLF_API int blf_close(void* handle) {
if (!handle) return -1;
BLFContext* ctx = (BLFContext*)handle;
/* Закрываем незавершённый контейнер */
if (ctx->in_container) {
if (blf_end_container(ctx) != 0) {
fprintf(stderr, "WARNING: Failed to close remaining container\n");
}
}
/* Выравнивание файла до 4 байт */
long endPos = blf_file_tell(ctx->fp);
if (endPos < 0) {
fclose(ctx->fp);
free(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->fp, &zero, 1) != 0) {
fclose(ctx->fp);
free(ctx);
return -1;
}
}
endPos += padding;
/* Вычисляем время последнего объекта */
SYSTEMTIME_PACKED lastObj = ctx->header.measurementStartTime;
uint64_t total_ms = ctx->maxTimestamp / 1000000;
systemtime_add_ms(&lastObj, total_ms);
ctx->header.lastObjectTime = lastObj;
/* Обновляем поля заголовка */
ctx->header.fileSize = (uint64_t)endPos;
ctx->header.uncompressedFileSize = (uint64_t)endPos;
ctx->header.objectCount = ctx->objectCount;
/* Перезаписываем заголовок */
if (blf_file_seek(ctx->fp, ctx->headerPos) != 0) {
fprintf(stderr, "ERROR: Failed to seek to file header\n");
fclose(ctx->fp);
free(ctx);
return -1;
}
if (blf_file_write(ctx->fp, &ctx->header, sizeof(FileHeader)) != 0) {
fprintf(stderr, "ERROR: Failed to rewrite file header\n");
fclose(ctx->fp);
free(ctx);
return -1;
}
fclose(ctx->fp);
free(ctx);
return 0;
}

185
APP/blf.h Normal file
View File

@ -0,0 +1,185 @@
/**
* @file blf.h
* @brief Заголовочный файл библиотеки для создания BLF-файлов (экспортная версия).
*/
#ifndef BLF_H
#define BLF_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* -------------------------------------------------------------------------
* Экспорт символов для разделяемой библиотеки
* ------------------------------------------------------------------------- */
#if defined(_WIN32) || defined(_WIN64)
#ifdef BLF_BUILD_DLL
#define BLF_API __declspec(dllexport)
#else
#define BLF_API __declspec(dllimport)
#endif
#else
#ifdef BLF_BUILD_DLL
#define BLF_API __attribute__((visibility("default")))
#else
#define BLF_API
#endif
#endif
/* -------------------------------------------------------------------------
* Константы формата BLF
* ------------------------------------------------------------------------- */
#define BL_FILE_SIGNATURE 0x47474F4Cu /* "LOGG" */
#define BL_OBJ_SIGNATURE 0x4A424F4Cu /* "LOBJ" */
#define BL_OBJ_TYPE_CAN_MESSAGE 1
#define BL_OBJ_TYPE_LIN_MESSAGE 11
#define BL_OBJ_TYPE_LIN_SND_ERROR 15
#define BL_OBJ_TYPE_LOG_CONTAINER 10
#define BL_OBJ_TYPE_ENV_DATA 9
#define BL_OBJ_FLAG_TIME_ONE_NANS 0x00000002
/* Флаги CAN-сообщения */
#define CAN_MSG_FLAGS(dir, rtr) \
((uint8_t)(((uint8_t)(rtr & 0x01) << 7) | ((uint8_t)(dir & 0x0F))))
#define CAN_DIR_TX 1
#define CAN_DIR_RX 0
/* Направления LIN */
#define LIN_DIR_RX 0
#define LIN_DIR_TX 1
/* -------------------------------------------------------------------------
* Структура времени (SYSTEMTIME) такая же, как в оригинале
* ------------------------------------------------------------------------- */
typedef struct {
uint16_t year;
uint16_t month;
uint16_t dayOfWeek;
uint16_t day;
uint16_t hour;
uint16_t minute;
uint16_t second;
uint16_t milliseconds;
} SYSTEMTIME;
/* -------------------------------------------------------------------------
* API библиотеки
* ------------------------------------------------------------------------- */
/**
* @brief Открывает (создаёт) BLF-файл.
* @param filename Имя файла.
* @param startTime Время начала измерения (может быть NULL будет использовано текущее системное время).
* @return Указатель на контекст (handle) или NULL в случае ошибки.
*/
BLF_API void* blf_open(const char* filename, const SYSTEMTIME* startTime);
/**
* @brief Устанавливает время начала измерения (если не было задано при открытии).
* @param handle Указатель на контекст.
* @param startTime Время начала.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_set_start_time(void* handle, const SYSTEMTIME* startTime);
/**
* @brief Начинает новый контейнер (объект LOG_CONTAINER).
* @param handle Указатель на контекст.
* @param timestamp Временная метка контейнера в наносекундах.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_start_container(void* handle, uint64_t timestamp);
/**
* @brief Завершает текущий контейнер.
* @param handle Указатель на контекст.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_end_container(void* handle);
/**
* @brief Добавляет CAN-сообщение.
* @param handle Указатель на контекст.
* @param channel Номер канала.
* @param id Идентификатор.
* @param flags Флаги (направление, RTR).
* @param dlc Длина данных (0-8).
* @param data Указатель на данные (если NULL, заполняет нулями).
* @param timestamp Временная метка в наносекундах.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_add_can_message(void* handle,
uint16_t channel,
uint32_t id,
uint8_t flags,
uint8_t dlc,
const uint8_t* data,
uint64_t timestamp);
/**
* @brief Добавляет LIN-сообщение (устаревший тип).
* @param handle Указатель на контекст.
* @param channel Номер канала.
* @param id Идентификатор (6 бит).
* @param dlc Длина данных.
* @param data Указатель на данные.
* @param dir Направление (LIN_DIR_RX / LIN_DIR_TX).
* @param timestamp Временная метка в наносекундах.
* @param checksum Контрольная сумма.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_add_lin_message_obsolete(void* handle,
uint16_t channel,
uint8_t id,
uint8_t dlc,
const uint8_t* data,
uint8_t dir,
uint64_t timestamp,
uint16_t checksum);
/**
* @brief Добавляет событие отсутствия ответа LIN.
* @param handle Указатель на контекст.
* @param channel Номер канала.
* @param id Идентификатор.
* @param dlc Ожидаемая длина данных.
* @param timestamp Временная метка в наносекундах.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_add_lin_send_error(void* handle,
uint16_t channel,
uint8_t id,
uint8_t dlc,
uint64_t timestamp);
/**
* @brief Добавляет данные окружения (ENV_DATA).
* @param handle Указатель на контекст.
* @param name Имя переменной (строка).
* @param data Указатель на данные.
* @param data_len Длина данных в байтах.
* @param timestamp Временная метка в наносекундах.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_add_env_data(void* handle,
const char* name,
const uint8_t* data,
uint32_t data_len,
uint64_t timestamp);
/**
* @brief Закрывает BLF-файл, обновляет заголовок и освобождает память контекста.
* @param handle Указатель на контекст.
* @return 0 при успехе, -1 при ошибке.
*/
BLF_API int blf_close(void* handle);
#ifdef __cplusplus
}
#endif
#endif /* BLF_H */

52
APP/main.c Normal file
View File

@ -0,0 +1,52 @@
/**
* @file main.c
* @brief Пример использования библиотеки blf (C).
*/
#include "blf.h"
#include <stdio.h>
int main() {
// Задаём время начала (1 января 2025, 00:00:00.000)
SYSTEMTIME startTime = {
.year = 2025,
.month = 1,
.dayOfWeek = 3,
.day = 1,
.hour = 0,
.minute = 0,
.second = 0,
.milliseconds = 0
};
void* ctx = blf_open("example_c.blf", &startTime);
if (!ctx) {
fprintf(stderr, "Failed to open file\n");
return 1;
}
if (blf_start_container(ctx, 0) != 0) {
fprintf(stderr, "Failed to start container\n");
blf_close(ctx);
return 1;
}
// CAN сообщение
uint8_t canData[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
blf_add_can_message(ctx, 1, 0x538, CAN_MSG_FLAGS(CAN_DIR_RX, 0), 8,
canData, 10000000ULL); // 10 мс
if (blf_end_container(ctx) != 0) {
fprintf(stderr, "Failed to end container\n");
blf_close(ctx);
return 1;
}
if (blf_close(ctx) != 0) {
fprintf(stderr, "Failed to close file\n");
return 1;
}
printf("File example_c.blf created successfully.\n");
return 0;
}

12
APP/modular.json Normal file
View File

@ -0,0 +1,12 @@
{
"cmake": {
"inc_dirs": [
"./"
],
"srcs": [
"./ert_main.c",
"./*.c"
]
}
}

37
CMakeLists.txt Normal file
View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.17)
project(conv)
set(CMAKE_C_STANDARD 17)
# Опции сборки
option(BUILD_SHARED_LIB "Build shared library" ON)
option(BUILD_EXAMPLE "Build example executable" ON)
# Собираем все .c файлы в APP (исключая main.c для библиотеки)
file(GLOB_RECURSE ALL_C_FILES "APP/*.c")
set(LIB_SOURCES ${ALL_C_FILES})
# Удаляем main.c из списка библиотеки
list(REMOVE_ITEM LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/APP/main.c")
if(BUILD_SHARED_LIB)
add_library(conv SHARED ${LIB_SOURCES})
target_compile_definitions(conv PRIVATE BLF_BUILD_DLL)
target_compile_options(conv PRIVATE -Os -std=c99 -D_GNU_SOURCE)
target_link_libraries(conv PRIVATE m)
endif()
if(BUILD_EXAMPLE)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/APP/main.c")
add_executable(conv_example APP/main.c)
target_link_libraries(conv_example PRIVATE conv)
target_compile_options(conv_example PRIVATE -Os -std=c99 -D_GNU_SOURCE)
else()
message(WARNING "APP/main.c not found, example will not be built")
endif()
endif()
# Установка
install(TARGETS conv
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
install(FILES blf.h DESTINATION include)

483
blf_wrapper.py Normal file
View File

@ -0,0 +1,483 @@
"""
Python wrapper for BLF library with guaranteed file closing
"""
import ctypes
import os
import atexit
from ctypes import c_void_p, c_int, c_uint8, c_uint16, c_uint32, c_uint64, c_char_p, Structure, POINTER
from typing import Optional
import weakref
# Константы
BL_FILE_SIGNATURE = 0x47474F4C # "LOGG"
BL_OBJ_SIGNATURE = 0x4A424F4C # "LOBJ"
BL_OBJ_TYPE_CAN_MESSAGE = 1
BL_OBJ_TYPE_LIN_MESSAGE = 11
BL_OBJ_TYPE_LIN_SND_ERROR = 15
BL_OBJ_TYPE_LOG_CONTAINER = 10
BL_OBJ_TYPE_ENV_DATA = 9
BL_OBJ_FLAG_TIME_ONE_NANS = 0x00000002
CAN_DIR_TX = 1
CAN_DIR_RX = 0
LIN_DIR_RX = 0
LIN_DIR_TX = 1
class SYSTEMTIME(Structure):
"""Структура времени"""
_fields_ = [
("year", c_uint16),
("month", c_uint16),
("dayOfWeek", c_uint16),
("day", c_uint16),
("hour", c_uint16),
("minute", c_uint16),
("second", c_uint16),
("milliseconds", c_uint16)
]
def __init__(self, year=0, month=0, dayOfWeek=0, day=0,
hour=0, minute=0, second=0, milliseconds=0):
super().__init__()
self.year = year
self.month = month
self.dayOfWeek = dayOfWeek
self.day = day
self.hour = hour
self.minute = minute
self.second = second
self.milliseconds = milliseconds
@classmethod
def from_datetime(cls, dt):
"""Создать из datetime объекта"""
return cls(
year=dt.year,
month=dt.month,
dayOfWeek=dt.weekday(),
day=dt.day,
hour=dt.hour,
minute=dt.minute,
second=dt.second,
milliseconds=dt.microsecond // 1000
)
@classmethod
def now(cls):
"""Создать с текущим временем"""
import datetime
return cls.from_datetime(datetime.datetime.now())
class BLFLibrary:
"""Singleton для загрузки и управления библиотекой"""
_instance = None
_lib = None
_loaded = False
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def load(self):
"""Загрузка библиотеки"""
if self._loaded:
return True
lib_paths = [
"./libconv.so", # Linux текущая директория
"./libconv.dylib", # macOS текущая директория
"./conv.dll", # Windows текущая директория
os.path.expanduser("~/lib/libconv.so"),
"/usr/local/lib/libconv.so",
"/usr/lib/libconv.so",
# Добавляем пути из LD_LIBRARY_PATH
*[os.path.join(p, "libconv.so") for p in os.environ.get("LD_LIBRARY_PATH", "").split(":") if p]
]
for path in lib_paths:
try:
self._lib = ctypes.CDLL(path)
self._loaded = True
self._setup_functions()
return True
except OSError:
continue
raise RuntimeError("Could not load BLF library. Please build it first.")
def _setup_functions(self):
"""Настройка типов функций"""
self._lib.blf_open.argtypes = [c_char_p, POINTER(SYSTEMTIME)]
self._lib.blf_open.restype = c_void_p
self._lib.blf_set_start_time.argtypes = [c_void_p, POINTER(SYSTEMTIME)]
self._lib.blf_set_start_time.restype = c_int
self._lib.blf_start_container.argtypes = [c_void_p, c_uint64]
self._lib.blf_start_container.restype = c_int
self._lib.blf_end_container.argtypes = [c_void_p]
self._lib.blf_end_container.restype = c_int
self._lib.blf_add_can_message.argtypes = [
c_void_p, c_uint16, c_uint32, c_uint8, c_uint8,
POINTER(c_uint8), c_uint64
]
self._lib.blf_add_can_message.restype = c_int
self._lib.blf_add_lin_message_obsolete.argtypes = [
c_void_p, c_uint16, c_uint8, c_uint8, POINTER(c_uint8),
c_uint8, c_uint64, c_uint16
]
self._lib.blf_add_lin_message_obsolete.restype = c_int
self._lib.blf_add_lin_send_error.argtypes = [
c_void_p, c_uint16, c_uint8, c_uint8, c_uint64
]
self._lib.blf_add_lin_send_error.restype = c_int
self._lib.blf_add_env_data.argtypes = [
c_void_p, c_char_p, POINTER(c_uint8), c_uint32, c_uint64
]
self._lib.blf_add_env_data.restype = c_int
self._lib.blf_close.argtypes = [c_void_p]
self._lib.blf_close.restype = c_int
@property
def lib(self):
"""Получить библиотеку"""
if not self._loaded:
self.load()
return self._lib
class BLFWriter:
"""Класс для записи BLF файлов с гарантированным закрытием"""
_all_instances = weakref.WeakSet() # Следим за всеми экземплярами
def __init__(self, filename: str, start_time: Optional[SYSTEMTIME] = None):
"""
Инициализация BLF writer
Args:
filename: имя файла для записи
start_time: время начала измерения (если None - текущее время)
"""
self.filename = filename
self._handle = None
self._is_open = False
self._lib_loader = BLFLibrary()
self._lib = self._lib_loader.lib
# Открываем файл
self._open(start_time)
# Регистрируем в глобальном списке
self.__class__._all_instances.add(self)
# Регистрируем финализатор
self._finalizer = weakref.finalize(self, self._cleanup, self._handle, self.filename)
@staticmethod
def _cleanup(handle, filename):
"""Статический метод для очистки ресурсов"""
if handle is not None and handle != 0:
try:
lib_loader = BLFLibrary()
lib_loader.lib.blf_close(handle)
print(f"DEBUG: Auto-closed file {filename} during cleanup")
except Exception as e:
print(f"WARNING: Failed to auto-close {filename}: {e}")
@classmethod
def close_all(cls):
"""Закрыть все открытые экземпляры (для аварийного завершения)"""
for instance in list(cls._all_instances):
try:
if instance._is_open:
instance.close()
except:
pass
def _open(self, start_time: Optional[SYSTEMTIME] = None):
"""Открытие файла"""
filename_bytes = self.filename.encode('utf-8')
if start_time is None:
self._handle = self._lib.blf_open(filename_bytes, None)
else:
self._handle = self._lib.blf_open(filename_bytes, ctypes.byref(start_time))
if self._handle is None or self._handle == 0:
raise RuntimeError(f"Failed to open file: {self.filename}")
self._is_open = True
def set_start_time(self, start_time: SYSTEMTIME) -> bool:
"""
Установить время начала измерения
Args:
start_time: время начала
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
result = self._lib.blf_set_start_time(self._handle, ctypes.byref(start_time))
return result == 0
def start_container(self, timestamp_ns: int) -> bool:
"""
Начать контейнер
Args:
timestamp_ns: временная метка в наносекундах
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
result = self._lib.blf_start_container(self._handle, timestamp_ns)
return result == 0
def end_container(self) -> bool:
"""
Закончить контейнер
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
result = self._lib.blf_end_container(self._handle)
return result == 0
def add_can_message(self, channel: int, can_id: int, dlc: int, data: bytes,
flags: int = 0, timestamp_ns: int = 0) -> bool:
"""
Добавить CAN сообщение
Args:
channel: номер канала
can_id: идентификатор CAN
dlc: длина данных (0-8)
data: данные сообщения
flags: флаги (направление, RTR)
timestamp_ns: временная метка в наносекундах
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
if dlc > 8:
raise ValueError("DLC cannot exceed 8")
data_array = (c_uint8 * 8)()
for i in range(min(dlc, len(data))):
data_array[i] = data[i]
result = self._lib.blf_add_can_message(
self._handle, channel, can_id, flags, dlc, data_array, timestamp_ns
)
return result == 0
def add_lin_message(self, channel: int, lin_id: int, dlc: int, data: bytes,
direction: int, timestamp_ns: int, checksum: int = 0) -> bool:
"""
Добавить LIN сообщение
Args:
channel: номер канала
lin_id: идентификатор LIN (6 бит)
dlc: длина данных
data: данные сообщения
direction: направление (LIN_DIR_RX или LIN_DIR_TX)
timestamp_ns: временная метка в наносекундах
checksum: контрольная сумма
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
if dlc > 8:
raise ValueError("DLC cannot exceed 8")
data_array = (c_uint8 * 8)()
for i in range(min(dlc, len(data))):
data_array[i] = data[i]
result = self._lib.blf_add_lin_message_obsolete(
self._handle, channel, lin_id, dlc, data_array,
direction, timestamp_ns, checksum
)
return result == 0
def add_lin_send_error(self, channel: int, lin_id: int, dlc: int,
timestamp_ns: int) -> bool:
"""
Добавить ошибку отправки LIN
Args:
channel: номер канала
lin_id: идентификатор LIN
dlc: ожидаемая длина данных
timestamp_ns: временная метка в наносекундах
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
result = self._lib.blf_add_lin_send_error(
self._handle, channel, lin_id, dlc, timestamp_ns
)
return result == 0
def add_env_data(self, name: str, data: bytes, timestamp_ns: int) -> bool:
"""
Добавить данные окружения
Args:
name: имя переменной
data: данные
timestamp_ns: временная метка в наносекундах
Returns:
bool: True при успехе
"""
if not self._is_open:
raise RuntimeError("File is not open")
# Создаем копию данных, чтобы они не были собраны сборщиком мусора
data_array = (c_uint8 * len(data))(*data)
result = self._lib.blf_add_env_data(
self._handle, name.encode('utf-8'), data_array, len(data), timestamp_ns
)
return result == 0
def flush(self) -> bool:
"""
Принудительно сбросить данные на диск
Note: В текущей реализации просто обновляет заголовок,
но не сбрасывает буферы stdio
"""
if not self._is_open:
raise RuntimeError("File is not open")
# В C библиотеке нет отдельной функции flush,
# но close/open не поддерживается, поэтому просто возвращаем True
return True
def close(self) -> bool:
"""
Закрыть файл и обновить заголовок
Returns:
bool: True при успехе
"""
if not self._is_open:
return True
result = False
if self._handle and self._handle != 0:
result = self._lib.blf_close(self._handle) == 0
if result:
print(f"DEBUG: Successfully closed {self.filename} and updated header")
else:
print(f"ERROR: Failed to close {self.filename} properly")
self._handle = None
self._is_open = False
# Отменяем финализатор, так как уже закрыли
if self._finalizer is not None:
self._finalizer.detach()
return result
def __enter__(self):
"""Контекстный менеджер - гарантирует закрытие"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Закрытие при выходе из контекста"""
self.close()
def __del__(self):
"""Деструктор - пытаемся закрыть файл если он еще открыт"""
if self._is_open:
# Не вызываем close напрямую, чтобы избежать проблем с порядком удаления
if self._handle and self._handle != 0:
try:
self._lib.blf_close(self._handle)
print(f"DEBUG: Auto-closed {self.filename} in destructor")
except:
pass
self._handle = None
self._is_open = False
def is_open(self) -> bool:
"""Проверить, открыт ли файл"""
return self._is_open
# Вспомогательные функции
def can_flags(direction: int, rtr: bool = False) -> int:
"""
Сформировать флаги для CAN сообщения
Args:
direction: направление (CAN_DIR_TX или CAN_DIR_RX)
rtr: флаг RTR (Remote Transmission Request)
Returns:
int: флаги
"""
return ((1 if rtr else 0) << 7) | (direction & 0x0F)
def timestamp_ms_to_ns(timestamp_ms: float) -> int:
"""
Преобразовать миллисекунды в наносекунды
Args:
timestamp_ms: время в миллисекундах
Returns:
int: время в наносекундах
"""
return int(timestamp_ms * 1_000_000)
def timestamp_sec_to_ns(timestamp_sec: float) -> int:
"""
Преобразовать секунды в наносекунды
Args:
timestamp_sec: время в секундах
Returns:
int: время в наносекундах
"""
return int(timestamp_sec * 1_000_000_000)
# Регистрируем глобальное закрытие при завершении программы
atexit.register(BLFWriter.close_all)

50
example.py Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""
Пример использования BLF библиотеки из Python с гарантированным закрытием файлов
"""
import time
from blf_wrapper import BLFWriter, can_flags, timestamp_ms_to_ns
def example_with_container():
"""Пример записи CAN сообщений с использованием контейнера"""
print("\n=== CAN example with container ===")
# Используем контекстный менеджер для автоматического закрытия
with BLFWriter("example_container.blf") as writer:
# Начинаем контейнер
print("Starting container...")
writer.start_container(timestamp_ms_to_ns(1000))
# Добавляем сообщения в контейнер
for i in range(5):
writer.add_can_message(
channel=1,
can_id=0x200 + i,
dlc=4,
data=bytes([i, i*2, i*3, i*4]),
flags=can_flags(1), # CAN_DIR_TX = 1
timestamp_ns=timestamp_ms_to_ns(i * 50)
)
print(f" Added message {i} to container")
# Заканчиваем контейнер
print("Ending container...")
writer.end_container()
print("Container example completed, closing file...")
print("File closed successfully, header updated")
if __name__ == "__main__":
print("=" * 60)
print("BLF Library Example - Container Usage")
print("=" * 60)
example_with_container()
print("\n" + "=" * 60)
print("Example completed successfully!")
print("=" * 60)

14
modular.json Normal file
View File

@ -0,0 +1,14 @@
{
"dep": [
{
"type": "local",
"dir": "APP"
}
],
"cmake": {
"inc_dirs": [
],
"srcs": [
]
}
}