ESP32使用spiffs实现数据环形存储

ESP32提供spiffs文件管理系统,我使用的ESP32-wrover-e有16MB的rom空间,去掉系统所占的空间,完全可以拿剩下的10MB空间来存储日志,数据等消息。下面是我用C语言实现的,环形存储,供大家参考借鉴。

#define FILE_DAY_COUNT 7  //定义7天
#define MAX_ENTRIES_PER_FILE 1750 // 一个文件的最大条数,超过将跳转到下一个文件
#define STORAGE_THRESHOLD 6000  // 当存储空间小于此字节数时,将擦除最旧的文件

typedef struct {
    size_t start; // 指向最旧数据的索引
    size_t end; // 指向最新数据的索引
    size_t entryCount; // 当前文件中的条目计数
    bool nextFileTriggered; // 表示是否需要跳转到下一个文件
} RingBufferIndex;


typedef struct {
    size_t length; // 数据长度
    char data[]; // 数据内容
} DataEntry;


void init_ring_buffer();
bool write_ring_bufferdata(const uint8_t* data, size_t length);
uint8_t* read_ring_bufferdata(size_t* dataLength);
bool trigger_next_file();//跳转到下一个文件
void wipe_oldest_file();// 擦除最旧文件
void print_spiffs_info(const char* partition_label);
void ringbufftest();//单元测试
// 创建一个数组来存储每个文件的RingBufferIndex
RingBufferIndex rbi[FILE_DAY_COUNT];
size_t oldestFileIndex = 0;

void init_ring_buffer() {
    FILE* f = fopen(PROJ_LIB_ROOT "/ringbufindex.txt", "r");
    if (f == NULL) {
        // 文件不存在,创建并用默认值初始化
        memset(&rbi, 0, sizeof(rbi));
        oldestFileIndex = 0;
        for(size_t i = 0; i < FILE_DAY_COUNT; i++) {
            rbi[i].nextFileTriggered = false;
        }
        f = fopen(PROJ_LIB_ROOT "/ringbufindex.txt", "w");
        if (f != NULL) {
            fwrite(&rbi, sizeof(RingBufferIndex), FILE_DAY_COUNT, f);
            fwrite(&oldestFileIndex, sizeof(size_t), 1, f);
            fclose(f);
            OLOGI("Created ringbufindex.txt");
        } else {
            OLOGE("Failed to create ringbufindex.txt");
            return;
        }
    } else {
        // 从文件中读取 rbi 数组和 oldestFileIndex 变量
        fread(&rbi, sizeof(RingBufferIndex), FILE_DAY_COUNT, f);
        fread(&oldestFileIndex, sizeof(size_t), 1, f);
        fclose(f);
        // 打印读取的信息
        OLOGI("Loaded ringbufindex.txt:");
        for (size_t i = 0; i < FILE_DAY_COUNT; i++) {
        OLOGI("rbi[%zu]: start=%zu, end=%zu, entryCount=%zu, nextFileTriggered=%d",
          i, rbi[i].start, rbi[i].end, rbi[i].entryCount, rbi[i].nextFileTriggered);
         }
         OLOGI("oldestFileIndex: %zu", oldestFileIndex);
    }
    // 初始化环形缓冲区文件
    for (size_t i = 0; i < FILE_DAY_COUNT; i++) {
        char filename[30];
        snprintf(filename, sizeof(filename), PROJ_LIB_ROOT "/ringbuffdata_%zu.txt", i);
        f = fopen(filename, "r");
        if (f == NULL) {
            // 数据文件不存在,创建新的
            f = fopen(filename, "w");
            if (f == NULL) {
                OLOGE("Failed to create %s", filename);
                return;
            }
            OLOGI("Created %s", filename);
        }
        fclose(f);
    }
}

//跳转到下一个文件
bool trigger_next_file() {
    // 查找正在写入的文件
    size_t fileIndex;
    for (fileIndex = 0; fileIndex < FILE_DAY_COUNT; fileIndex++) {
        if (!rbi[fileIndex].nextFileTriggered) {
            break;
        }
    }
    // 如果找到,设置nextFileTriggered为true并返回真
    if (fileIndex != FILE_DAY_COUNT) {
        rbi[fileIndex].nextFileTriggered = true;
        return true;
    }
    // 没有找到,记录警告信息并返回假
    OLOGW("Failed to trigger next file, all files are marked as nextFileTriggered");
    return false;
}
// 擦除最旧文件
void wipe_oldest_file() {
    // 获取当前最旧文件的文件名
    char filename[30];
    snprintf(filename, sizeof(filename), PROJ_LIB_ROOT "/ringbuffdata_%zu.txt", oldestFileIndex);

    // 打开文件,并使用 "w" 模式以清空文件
    FILE* f = fopen(filename, "w");
    if (f != NULL) {
        OLOGI("Cleared %s successfully", filename);
        fclose(f);
    } else {
        OLOGW("Failed to clear %s", filename);
        return;
    }

    // 重置对应的 RingBufferIndex
    rbi[oldestFileIndex] = (RingBufferIndex){0, 0, 0, false};

    // 更新 oldestFileIndex
    oldestFileIndex = (oldestFileIndex + 1) % FILE_DAY_COUNT;

    // 将更新后的 oldestFileIndex 和 rbi[] 写回 ringbufindex.txt
    f = fopen(PROJ_LIB_ROOT "/ringbufindex.txt", "w");
    if (f == NULL) {
        OLOGW("Failed to open ringbufindex.txt for writing");
        return;
    }
    fwrite(&rbi, sizeof(RingBufferIndex), FILE_DAY_COUNT, f);
    fwrite(&oldestFileIndex, sizeof(size_t), 1, f);
    fclose(f);
}


bool write_ring_bufferdata(const uint8_t* data, size_t length) {
    // 每次写入之前,都先检查剩余的存储空间
    size_t total_storage = 0;
    size_t used_storage = 0;
    esp_err_t ret = esp_spiffs_info("lib", &total_storage, &used_storage);
    if (ret != ESP_OK) {
        OLOGW("Failed to get partition information");
    } else {
        size_t remaining_storage = total_storage - used_storage;
        if (remaining_storage < STORAGE_THRESHOLD) {
            wipe_oldest_file();
        }
    }
    // 查找有空间的文件
    char filename[30];
    size_t fileIndex;
    
    for (fileIndex = 0; fileIndex < FILE_DAY_COUNT; fileIndex++) {
        if (!rbi[fileIndex].nextFileTriggered && rbi[fileIndex].entryCount < MAX_ENTRIES_PER_FILE) {
            break;
        }
    }
    // 打开文件进行写入
    FILE* f;
    DataEntry entry;
    // 如果所有文件都满了,就覆盖最旧的文件
    if (fileIndex == FILE_DAY_COUNT || rbi[fileIndex].nextFileTriggered)  {
        fileIndex = oldestFileIndex;
        oldestFileIndex = (oldestFileIndex + 1) % FILE_DAY_COUNT;  // 更新最旧文件索引
        rbi[fileIndex] = (RingBufferIndex){0, 0, 0, false};  // 重置该文件的 RingBufferIndex
        snprintf(filename, sizeof(filename), PROJ_LIB_ROOT "/ringbuffdata_%zu.txt", fileIndex);
        f = fopen(filename, "w");  // "w"模式会创建新文件
    } else {
        snprintf(filename, sizeof(filename), PROJ_LIB_ROOT "/ringbuffdata_%zu.txt", fileIndex);
        f = fopen(filename, "a");  // "a"模式会追加数据到文件尾部
    }
    if (f == NULL) {
        OLOGW("Failed to open %s for writing\n", filename);
        return false;
    }
    OLOGI("Writing fileIndex: %zu\n", fileIndex);  // 打印当前的索引
    // 写入数据
    entry.length = length;
    // OLOGI("Writing data: Length: %zu\n", entry.length);  // 打印写入的数据长度
    fwrite(&entry.length, sizeof(entry.length), 1, f);
    fwrite(data, length, 1, f);
    fclose(f);
    // 更新索引
    rbi[fileIndex].end = rbi[fileIndex].end + sizeof(DataEntry) + entry.length;
    rbi[fileIndex].entryCount++;
    // 写入更新后的索引
    f = fopen(PROJ_LIB_ROOT"/ringbufindex.txt", "w");
    if (f == NULL) {
        OLOGW("Failed to open ringbufindex.txt for writing\n");
        return false;
    }
    fwrite(&rbi, sizeof(RingBufferIndex), FILE_DAY_COUNT, f);
    fwrite(&oldestFileIndex, sizeof(size_t), 1, f);
    fclose(f);
    //查看剩余容量
    // print_spiffs_info("lib");
    return true;
}

uint8_t* read_ring_bufferdata(size_t* dataLength) {
    // 查找有数据的文件
    size_t fileIndex = oldestFileIndex;
    if (rbi[fileIndex].entryCount == 0) {
        //考虑未知情况下数据存放不连续,遍历所有文件,mqtt文件有时间戳,上传先后无影响。
        bool allFilesEmpty = true;
        for (size_t i = 0; i < FILE_DAY_COUNT; ++i) {
            if (rbi[i].entryCount != 0) {
                allFilesEmpty = false;
                fileIndex = i;
                oldestFileIndex = i;
                break;
            }
        }
        if (allFilesEmpty) {
            // OLOGI("All files are empty, no data to read");
            return NULL;  // 所有文件都是空的,返回 NULL
        }
    }
    // 获取当前文件名
    char filename[30];
    snprintf(filename, sizeof(filename), PROJ_LIB_ROOT"/ringbuffdata_%zu.txt", fileIndex);
    FILE* f = fopen(filename, "r");
    if (f == NULL) {
        OLOGW("Failed to open %s for reading\n", filename);
        return NULL;
    }
    uint8_t* data = NULL;
    // OLOGI("Reading fileIndex: %zu\n", fileIndex);  // 打印当前的索引
    size_t currentIndex = rbi[fileIndex].start;
    fseek(f, rbi[fileIndex].start, SEEK_SET);
    // while(currentIndex <= rbi[fileIndex].start) {
        // 读取数据长度
        size_t length;
        size_t readItems = fread(&length, sizeof(length), 1, f);
        if(readItems != 1) {
            if(data != NULL) {
                free(data);
            }
            fclose(f);
            OLOGW("Failed to read data length\n");
            return NULL;
        }
        
        if(length > 20480) { // 预期最大数据长度
            if(data != NULL) {
                free(data);
            }
            fclose(f);
            OLOGW("Data length is too big: %zu\n", length);
            return NULL;
        }

        // 重新分配内存并读取数据
        data = (uint8_t*)realloc(data, length);
        *dataLength = length;
        if (data == NULL) {
            fclose(f);
            OLOGW("Failed to allocate memory for data\n");
            return NULL;
        }
        readItems = fread(data, length, 1, f);
        if(readItems != 1) {
            free(data);
            fclose(f);
            OLOGW("Failed to read data\n");
            return NULL;
        }
      
        currentIndex += sizeof(length) + length;
        
    // }
    // OLOGI("read data: Length: %zu\n", *dataLength);  // 打印写入的数据长度
    fclose(f);

    // 更新索引
    rbi[fileIndex].start = rbi[fileIndex].start + sizeof(size_t) + *dataLength;
    // 如果读取了最旧文件的所有数据,就更新最旧文件索引
    if ( currentIndex >=rbi[fileIndex].end ) {
        oldestFileIndex = (oldestFileIndex + 1) % FILE_DAY_COUNT;
        rbi[fileIndex] = (RingBufferIndex){0, 0, 0, false};  // 重置索引
        // 打开并清空文件
        FILE* f = fopen(filename, "w");
        if (f != NULL) {
            OLOGI("Cleared %s", filename);
            fclose(f);
        } else {
            OLOGW("Failed to clear %s", filename);
        }
        // 添加的判断,如果每个文件的条数都为0,则把oldestFileIndex归0,说明数据被全读完了
        bool allFilesEmpty = true;
        for(size_t i = 0; i < FILE_DAY_COUNT; ++i) {
            if(rbi[i].entryCount != 0) {
                allFilesEmpty = false;
                break;
            }
        }
        if(allFilesEmpty) {
            oldestFileIndex = 0;
            // OLOGI("All files are empty, reset oldestFileIndex to 0");
        }
    }
    // 写入更新后的索引
    f = fopen(PROJ_LIB_ROOT"/ringbufindex.txt", "w");
    if (f == NULL) {
        free(data);
        OLOGW("Failed to open ringbufindex.txt for writing\n");
        return NULL;
    }
    fwrite(&rbi, sizeof(RingBufferIndex), FILE_DAY_COUNT, f);
    fwrite(&oldestFileIndex, sizeof(size_t), 1, f);
    fclose(f);
    return data;
}

void print_spiffs_info(const char* partition_label) {
    size_t total_storage = 0;
    size_t used_storage = 0;

    esp_err_t ret = esp_spiffs_info(partition_label, &total_storage, &used_storage);
    
    if (ret != ESP_OK) {
        OLOGW("Failed to get partition information for %s", partition_label);
    } else {
        OLOGI("[ /%s ] : total: %zu, used: %zu", partition_label, total_storage, used_storage);
    }
}

void ringbufftest()
{
    char data[51];
    for (int i = 0; i < 10; i++) {
    sprintf(data, "waring:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 10; i < 20; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 20; i < 30; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 30; i < 40; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 40; i < 50; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 50; i < 60; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 60; i < 70; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    // trigger_next_file();
    trigger_next_file();
    for (int i = 70; i < 80; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 80; i < 90; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
     trigger_next_file();
    for (int i = 90; i < 100; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 100; i < 110; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }
    trigger_next_file();
    for (int i = 110; i < 120; i++) {
    sprintf(data, "error:monitor——data_%d", i);
     if (!write_ring_bufferdata((const uint8_t*) data, strlen(data))) {
        OLOGE("Failed to write data: %s", data);
        fon_sleep(10);
        break;
    }
    }

    fon_sleep(1000);
    size_t dataLength;
    for (int i = 0; i < 400; i++) {
    uint8_t* read_data = read_ring_bufferdata(&dataLength);
    if (read_data != NULL) {
        // 分配一个新的内存区域来存储字符串,预留一个额外的位置给空字符
        char* temp = (char*) malloc((dataLength + 1) * sizeof(char));
        // 复制字符串并添加空字符
        strncpy(temp, (char*) read_data, dataLength);
        temp[dataLength] = '\0';

        OLOGI("Read from ring buffer: %s", temp);
        free(read_data);  // 释放从 read_ring_bufferdata 分配的内存
        free(temp);  // 释放临时字符串的内存
    } else {
        OLOGI("No more data in ring buffer");
        break;
    }
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值