From a5cc146c1c6729a87773616f5537e562eec84255 Mon Sep 17 00:00:00 2001 From: cfif Date: Wed, 25 Mar 2026 14:01:07 +0300 Subject: [PATCH] Init --- APP/blf.c | 648 +++++++++++++++++++++++++++++++++++++++++++++++ APP/blf.h | 185 ++++++++++++++ APP/main.c | 52 ++++ APP/modular.json | 12 + CMakeLists.txt | 37 +++ blf_wrapper.py | 483 +++++++++++++++++++++++++++++++++++ example.py | 50 ++++ modular.json | 14 + 8 files changed, 1481 insertions(+) create mode 100644 APP/blf.c create mode 100644 APP/blf.h create mode 100644 APP/main.c create mode 100644 APP/modular.json create mode 100644 CMakeLists.txt create mode 100644 blf_wrapper.py create mode 100644 example.py create mode 100644 modular.json diff --git a/APP/blf.c b/APP/blf.c new file mode 100644 index 0000000..79a3f1e --- /dev/null +++ b/APP/blf.c @@ -0,0 +1,648 @@ +/** + * @file blf.c + * @brief Реализация функций для создания BLF-файлов (разделяемая библиотека). + */ + +#include "blf.h" +#include +#include +#include +#include + +/* ------------------------------------------------------------------------- + * Упакованные структуры 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; +} \ No newline at end of file diff --git a/APP/blf.h b/APP/blf.h new file mode 100644 index 0000000..949c610 --- /dev/null +++ b/APP/blf.h @@ -0,0 +1,185 @@ +/** + * @file blf.h + * @brief Заголовочный файл библиотеки для создания BLF-файлов (экспортная версия). + */ + +#ifndef BLF_H +#define BLF_H + +#include +#include + +#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 */ \ No newline at end of file diff --git a/APP/main.c b/APP/main.c new file mode 100644 index 0000000..20a3814 --- /dev/null +++ b/APP/main.c @@ -0,0 +1,52 @@ +/** + * @file main.c + * @brief Пример использования библиотеки blf (C). + */ + +#include "blf.h" +#include + +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; +} \ No newline at end of file diff --git a/APP/modular.json b/APP/modular.json new file mode 100644 index 0000000..e097b40 --- /dev/null +++ b/APP/modular.json @@ -0,0 +1,12 @@ +{ + + "cmake": { + "inc_dirs": [ + "./" + ], + "srcs": [ + "./ert_main.c", + "./*.c" + ] + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..33ee99a --- /dev/null +++ b/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/blf_wrapper.py b/blf_wrapper.py new file mode 100644 index 0000000..c94e05e --- /dev/null +++ b/blf_wrapper.py @@ -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) \ No newline at end of file diff --git a/example.py b/example.py new file mode 100644 index 0000000..8ba780c --- /dev/null +++ b/example.py @@ -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) \ No newline at end of file diff --git a/modular.json b/modular.json new file mode 100644 index 0000000..6034ef1 --- /dev/null +++ b/modular.json @@ -0,0 +1,14 @@ +{ + "dep": [ + { + "type": "local", + "dir": "APP" + } + ], + "cmake": { + "inc_dirs": [ + ], + "srcs": [ + ] + } +} \ No newline at end of file