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