#include "OFFLINE_STORAGE.h" #include "FLASH_SPIFS.h" #include "cJSON.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include #include #include #include #define TAG "OFFLINE_STORAGE" #define DATA_DIR "/flash/data" #define INDEX_FILE "/flash/index.json" // 最大文件数(防止文件过多) #define MAX_FILES 10000 // 数据类型目录名 static const char* DATA_TYPE_DIRS[] = { "", // UNKNOWN "modbus", // MODBUS "status" // DEVICE_STATUS }; // 索引结构 static cJSON *index_json = NULL; /** * @brief 初始化数据目录 */ static esp_err_t init_data_directories(void) { struct stat st; // 检查主目录是否存在 if (stat(DATA_DIR, &st) != 0) { mkdir(DATA_DIR, 0777); ESP_LOGI(TAG, "创建数据目录: %s", DATA_DIR); } // 创建各类型子目录 for (int i = 1; i < 3; i++) { char dir_path[64]; snprintf(dir_path, sizeof(dir_path), "%s/%s", DATA_DIR, DATA_TYPE_DIRS[i]); if (stat(dir_path, &st) != 0) { mkdir(dir_path, 0777); ESP_LOGI(TAG, "创建子目录: %s", dir_path); } } return ESP_OK; } /** * @brief 加载索引文件 */ static esp_err_t load_index(void) { FILE *f = fopen(INDEX_FILE, "r"); if (f == NULL) { ESP_LOGI(TAG, "索引文件不存在,创建新索引"); index_json = cJSON_CreateObject(); if (index_json == NULL) { return ESP_ERR_NO_MEM; } // 创建索引数组 cJSON *index_array = cJSON_CreateArray(); if (index_array == NULL) { cJSON_Delete(index_json); return ESP_ERR_NO_MEM; } cJSON_AddItemToObject(index_json, "files", index_array); return ESP_OK; } // 读取文件内容 fseek(f, 0, SEEK_END); long file_size = ftell(f); fseek(f, 0, SEEK_SET); char *buffer = malloc(file_size + 1); if (buffer == NULL) { fclose(f); return ESP_ERR_NO_MEM; } fread(buffer, 1, file_size, f); buffer[file_size] = '\0'; fclose(f); // 解析JSON index_json = cJSON_Parse(buffer); free(buffer); if (index_json == NULL) { ESP_LOGE(TAG, "索引文件解析失败"); return ESP_FAIL; } return ESP_OK; } /** * @brief 保存索引文件 */ static esp_err_t save_index(void) { if (index_json == NULL) { return ESP_FAIL; } char *json_str = cJSON_PrintUnformatted(index_json); if (json_str == NULL) { return ESP_ERR_NO_MEM; } FILE *f = fopen(INDEX_FILE, "w"); if (f == NULL) { free(json_str); return ESP_FAIL; } size_t json_len = strlen(json_str); size_t written = fwrite(json_str, 1, json_len, f); fclose(f); free(json_str); if (written != json_len) { return ESP_FAIL; } return ESP_OK; } /** * @brief 生成新文件名 */ static esp_err_t generate_filename(char *filename, size_t max_len, offline_data_type_t data_type) { if (data_type <= OFFLINE_DATA_TYPE_UNKNOWN || data_type > OFFLINE_DATA_TYPE_DEVICE_STATUS) { return ESP_ERR_INVALID_ARG; } // 使用时间戳作为文件名 uint32_t timestamp = (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS); snprintf(filename, max_len, "%s/%s/%010lu.json", DATA_DIR, DATA_TYPE_DIRS[data_type], (unsigned long)timestamp); return ESP_OK; } esp_err_t offline_storage_init(void) { ESP_LOGI(TAG, "初始化离线存储模块..."); // 检查SPIFFS是否挂载 if (!flash_spiffs_is_mounted()) { ESP_LOGE(TAG, "SPIFFS未挂载"); return ESP_ERR_NOT_FOUND; } // 初始化目录结构 ESP_ERROR_CHECK(init_data_directories()); // 加载索引 ESP_ERROR_CHECK(load_index()); ESP_LOGI(TAG, "离线存储模块初始化完成"); return ESP_OK; } esp_err_t offline_storage_store(const char *data, size_t length, offline_data_type_t data_type) { if (data == NULL || length == 0) { return ESP_ERR_INVALID_ARG; } if (data_type <= OFFLINE_DATA_TYPE_UNKNOWN || data_type > OFFLINE_DATA_TYPE_DEVICE_STATUS) { return ESP_ERR_INVALID_ARG; } // 生成文件名 char filename[64]; esp_err_t ret = generate_filename(filename, sizeof(filename), data_type); if (ret != ESP_OK) { return ret; } // 写入文件 FILE *f = fopen(filename, "w"); if (f == NULL) { ESP_LOGE(TAG, "无法创建文件: %s", filename); return ESP_FAIL; } size_t written = fwrite(data, 1, length, f); fclose(f); if (written != length) { ESP_LOGE(TAG, "写入不完整: %zu/%zu", written, length); remove(filename); return ESP_FAIL; } ESP_LOGD(TAG, "存储数据: %s, 大小: %zu 字节", filename, length); // 更新索引 cJSON *files_array = cJSON_GetObjectItemCaseSensitive(index_json, "files"); if (files_array != NULL) { cJSON *file_info = cJSON_CreateObject(); if (file_info != NULL) { cJSON_AddStringToObject(file_info, "filename", filename); cJSON_AddNumberToObject(file_info, "type", data_type); cJSON_AddNumberToObject(file_info, "size", length); cJSON_AddItemToArray(files_array, file_info); save_index(); } } // 检查文件数量,如果超过限制则删除最旧的 uint32_t count = offline_storage_get_count(); if (count > MAX_FILES) { ESP_LOGW(TAG, "文件数量过多 (%u),删除最旧的", count); offline_storage_delete_oldest(); } return ESP_OK; } esp_err_t offline_storage_read_oldest(char *buffer, size_t max_len, offline_data_type_t *out_data_type) { if (buffer == NULL) { return ESP_ERR_INVALID_ARG; } cJSON *files_array = cJSON_GetObjectItemCaseSensitive(index_json, "files"); if (files_array == NULL || !cJSON_IsArray(files_array)) { return ESP_ERR_NOT_FOUND; } if (cJSON_GetArraySize(files_array) == 0) { return ESP_ERR_NOT_FOUND; } // 获取第一个元素(最旧的) cJSON *first_file = cJSON_GetArrayItem(files_array, 0); if (first_file == NULL) { return ESP_ERR_NOT_FOUND; } cJSON *filename_item = cJSON_GetObjectItemCaseSensitive(first_file, "filename"); cJSON *type_item = cJSON_GetObjectItemCaseSensitive(first_file, "type"); if (filename_item == NULL || !cJSON_IsString(filename_item)) { return ESP_FAIL; } const char *filename = cJSON_GetStringValue(filename_item); if (filename == NULL) { return ESP_FAIL; } // 读取文件 FILE *f = fopen(filename, "r"); if (f == NULL) { ESP_LOGE(TAG, "无法打开文件: %s (索引记录与实际文件不匹配)", filename); ESP_LOGW(TAG, "删除无效的索引记录: %s", filename); // 文件不存在,从索引中删除这个无效记录 offline_storage_delete_oldest(); return ESP_ERR_NOT_FOUND; } fseek(f, 0, SEEK_END); long file_size = ftell(f); fseek(f, 0, SEEK_SET); if (file_size > (long)max_len) { ESP_LOGE(TAG, "文件过大: %s (%ld bytes)", filename, file_size); fclose(f); // 删除过大的文件 offline_storage_delete_oldest(); return ESP_ERR_NO_MEM; } size_t read = fread(buffer, 1, file_size, f); fclose(f); buffer[read] = '\0'; ESP_LOGD(TAG, "读取数据: %s, 大小: %zu 字节", filename, read); // 返回数据类型 if (out_data_type != NULL && type_item != NULL && cJSON_IsNumber(type_item)) { *out_data_type = (offline_data_type_t)cJSON_GetNumberValue(type_item); } return ESP_OK; } esp_err_t offline_storage_delete_oldest(void) { cJSON *files_array = cJSON_GetObjectItemCaseSensitive(index_json, "files"); if (files_array == NULL || !cJSON_IsArray(files_array)) { return ESP_ERR_NOT_FOUND; } if (cJSON_GetArraySize(files_array) == 0) { return ESP_ERR_NOT_FOUND; } // 删除第一个元素 cJSON *first_file = cJSON_DetachItemFromArray(files_array, 0); if (first_file == NULL) { return ESP_FAIL; } cJSON *filename_item = cJSON_GetObjectItemCaseSensitive(first_file, "filename"); const char *filename = NULL; if (filename_item != NULL && cJSON_IsString(filename_item)) { filename = cJSON_GetStringValue(filename_item); if (filename != NULL) { remove(filename); ESP_LOGD(TAG, "删除文件: %s", filename); } } cJSON_Delete(first_file); // 检查并删除所有重复的索引记录(如果存在) if (filename != NULL) { int duplicate_count = 0; int array_size = cJSON_GetArraySize(files_array); for (int i = array_size - 1; i >= 0; i--) { cJSON *item = cJSON_GetArrayItem(files_array, i); if (item == NULL) { continue; } cJSON *item_filename = cJSON_GetObjectItemCaseSensitive(item, "filename"); if (item_filename != NULL && cJSON_IsString(item_filename)) { const char *item_name = cJSON_GetStringValue(item_filename); if (item_name != NULL && strcmp(filename, item_name) == 0) { // 找到重复记录,删除它 cJSON_DeleteItemFromArray(files_array, i); duplicate_count++; ESP_LOGW(TAG, "删除重复的索引记录: %s", item_name); } } } if (duplicate_count > 0) { ESP_LOGW(TAG, "删除了 %d 个重复的索引记录", duplicate_count); } } save_index(); return ESP_OK; } uint32_t offline_storage_get_count(void) { cJSON *files_array = cJSON_GetObjectItemCaseSensitive(index_json, "files"); if (files_array == NULL || !cJSON_IsArray(files_array)) { return 0; } return (uint32_t)cJSON_GetArraySize(files_array); } bool offline_storage_has_data(void) { return offline_storage_get_count() > 0; } esp_err_t offline_storage_clear_all(void) { ESP_LOGW(TAG, "清空所有离线数据..."); cJSON *files_array = cJSON_GetObjectItemCaseSensitive(index_json, "files"); if (files_array == NULL || !cJSON_IsArray(files_array)) { return ESP_OK; } // 删除所有文件 cJSON *item = NULL; cJSON_ArrayForEach(item, files_array) { cJSON *filename_item = cJSON_GetObjectItemCaseSensitive(item, "filename"); if (filename_item != NULL && cJSON_IsString(filename_item)) { const char *filename = cJSON_GetStringValue(filename_item); if (filename != NULL) { remove(filename); } } } // 清空索引 cJSON_DeleteItemFromObject(index_json, "files"); cJSON *new_array = cJSON_CreateArray(); if (new_array != NULL) { cJSON_AddItemToObject(index_json, "files", new_array); } save_index(); ESP_LOGI(TAG, "所有离线数据已清空"); return ESP_OK; } esp_err_t offline_storage_get_usage(size_t *used, size_t *total) { if (used == NULL || total == NULL) { return ESP_ERR_INVALID_ARG; } *total = flash_spiffs_get_total_size(); *used = flash_spiffs_get_used_size(); return ESP_OK; }