Init
This commit is contained in:
commit
a5cc146c1c
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
|
||||
"cmake": {
|
||||
"inc_dirs": [
|
||||
"./"
|
||||
],
|
||||
"srcs": [
|
||||
"./ert_main.c",
|
||||
"./*.c"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"dep": [
|
||||
{
|
||||
"type": "local",
|
||||
"dir": "APP"
|
||||
}
|
||||
],
|
||||
"cmake": {
|
||||
"inc_dirs": [
|
||||
],
|
||||
"srcs": [
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue