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;
}
}
}