371 lines
12 KiB
C++
371 lines
12 KiB
C++
#include "logs.h"
|
||
#include <esp_partition.h>
|
||
#include <esp_attr.h>
|
||
|
||
// CRC8 для проверки целостности
|
||
uint8_t LogModule::calculateCRC(const LogEntry &entry) {
|
||
uint8_t crc = 0;
|
||
const uint8_t* data = (const uint8_t*)&entry;
|
||
|
||
// Считаем CRC по всем байтам структуры КРОМЕ поля crc
|
||
for (size_t i = 0; i < offsetof(LogEntry, crc); i++) {
|
||
crc ^= data[i];
|
||
for (int j = 0; j < 8; j++) {
|
||
crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
|
||
}
|
||
}
|
||
|
||
// Пропускаем поле crc и считаем остальные
|
||
for (size_t i = offsetof(LogEntry, crc) + 1; i < ENTRY_SIZE; i++) {
|
||
crc ^= data[i];
|
||
for (int j = 0; j < 8; j++) {
|
||
crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
|
||
}
|
||
}
|
||
|
||
return crc;
|
||
}
|
||
|
||
bool LogModule::verifyEntry(const LogEntry &entry) {
|
||
uint8_t calculated_crc = calculateCRC(entry);
|
||
bool valid = (entry.crc == calculated_crc);
|
||
|
||
if (!valid) {
|
||
Serial.printf("CRC mismatch: stored=0x%02X, calculated=0x%02X\n",
|
||
entry.crc, calculated_crc);
|
||
}
|
||
|
||
return valid;
|
||
}
|
||
|
||
LogModule::LogModule() : partition(nullptr), write_pos(0), total_entries(0), partition_size(0) {}
|
||
|
||
void LogModule::recoverLog() {
|
||
Serial.println("Starting log recovery...");
|
||
|
||
// Ищем последнюю валидную запись
|
||
uint32_t last_valid = findLastValidEntry();
|
||
|
||
if (last_valid == UINT32_MAX) {
|
||
// Не нашли ни одной валидной записи
|
||
Serial.println("No valid entries found, clearing log");
|
||
write_pos = 0;
|
||
total_entries = 0;
|
||
} else {
|
||
// Восстанавливаем метаданные
|
||
write_pos = (last_valid + 1) % ((partition_size - 8) / ENTRY_SIZE);
|
||
total_entries = last_valid + 1;
|
||
Serial.printf("Recovered %u valid entries\n", total_entries);
|
||
}
|
||
|
||
saveMetadata();
|
||
}
|
||
|
||
uint32_t LogModule::findLastValidEntry() {
|
||
uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
|
||
uint32_t last_valid = UINT32_MAX;
|
||
|
||
for (uint32_t i = 0; i < max_entries; i++) {
|
||
uint32_t offset = 8 + (i * ENTRY_SIZE); // ← ИСПРАВЛЕНО!
|
||
|
||
LogEntry entry;
|
||
if (readRaw(offset, (uint8_t*)&entry, ENTRY_SIZE)) {
|
||
// Проверяем не пустая ли запись
|
||
bool is_erased = true;
|
||
for (size_t j = 0; j < ENTRY_SIZE; j++) {
|
||
if (((uint8_t*)&entry)[j] != 0xFF && ((uint8_t*)&entry)[j] != 0x00) {
|
||
is_erased = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (is_erased) {
|
||
Serial.printf("Empty entry at position %u, stopping scan\n", i);
|
||
break;
|
||
}
|
||
|
||
if (verifyEntry(entry)) {
|
||
last_valid = i;
|
||
Serial.printf("Valid entry at position %u: SEQ:%u\n", i, entry.seq);
|
||
} else {
|
||
Serial.printf("Corrupted entry at position %u\n", i);
|
||
break;
|
||
}
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
return last_valid;
|
||
}
|
||
|
||
bool LogModule::validateAllEntries() {
|
||
if (total_entries == 0) return true;
|
||
|
||
uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
|
||
|
||
for (uint32_t i = 0; i < total_entries; i++) {
|
||
uint32_t index = (write_pos - total_entries + i + max_entries) % max_entries;
|
||
uint32_t offset = 8 + (index * ENTRY_SIZE); // ← ИСПРАВЛЕНО!
|
||
|
||
LogEntry entry;
|
||
if (!readRaw(offset, (uint8_t*)&entry, ENTRY_SIZE)) {
|
||
Serial.printf("Read failed at index %u\n", index);
|
||
return false;
|
||
}
|
||
|
||
// Проверяем не пустая ли запись (все 0xFF или 0x00)
|
||
bool is_erased = true;
|
||
for (size_t j = 0; j < ENTRY_SIZE; j++) {
|
||
uint8_t byte = ((uint8_t*)&entry)[j];
|
||
if (byte != 0xFF && byte != 0x00) {
|
||
is_erased = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (is_erased) {
|
||
Serial.printf("Erased entry at index %u\n", index);
|
||
return false;
|
||
}
|
||
|
||
if (!verifyEntry(entry)) {
|
||
Serial.printf("CRC mismatch at index %u\n", index);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
bool LogModule::begin() {
|
||
partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA,
|
||
ESP_PARTITION_SUBTYPE_ANY,
|
||
"log_storage");
|
||
|
||
if (!partition) {
|
||
Serial.println("Log partition not found!");
|
||
return false;
|
||
}
|
||
|
||
partition_size = partition->size;
|
||
|
||
// Проверяем что партиция имеет правильный размер (2 МБ)
|
||
if (partition_size != LOG_PARTITION_SIZE) {
|
||
Serial.printf("ERROR: Partition size mismatch! Expected: %u bytes, Got: %u bytes\n",
|
||
LOG_PARTITION_SIZE, partition_size);
|
||
Serial.println("Check partitions.csv file");
|
||
return false;
|
||
}
|
||
|
||
Serial.printf("Log partition: %u bytes (2 MB)\n", partition_size);
|
||
|
||
// Инициализируем Preferences для метаданных
|
||
if (!prefs.begin("log_storage", false)) {
|
||
Serial.println("Failed to initialize Preferences");
|
||
return false;
|
||
}
|
||
|
||
// Пытаемся загрузить метаданные
|
||
if (loadMetadata()) {
|
||
|
||
// Проверяем целостность
|
||
if (!validateAllEntries()) {
|
||
Serial.println("Log corruption detected, reformatting...");
|
||
esp_partition_erase_range(partition, 0, partition_size);
|
||
write_pos = 0;
|
||
total_entries = 0;
|
||
saveMetadata();
|
||
}
|
||
} else {
|
||
// Первый запуск или поврежденные метаданные
|
||
Serial.println("Initializing new log storage...");
|
||
esp_partition_erase_range(partition, 0, partition_size);
|
||
write_pos = 0;
|
||
total_entries = 0;
|
||
saveMetadata();
|
||
}
|
||
|
||
Serial.printf("Log system ready. Max entries: %u\n", (partition_size - 8) / ENTRY_SIZE);
|
||
return true;
|
||
}
|
||
|
||
|
||
bool LogModule::writeRaw(uint32_t offset, const uint8_t* data, size_t size) {
|
||
// Проверяем выравнивание
|
||
if (size % 4 != 0 || offset % 4 != 0) {
|
||
Serial.printf("FATAL: Alignment error: offset=0x%X, size=%u\n", offset, size);
|
||
return false;
|
||
}
|
||
|
||
esp_err_t err = esp_partition_write(partition, offset, data, size);
|
||
if (err != ESP_OK) {
|
||
Serial.printf("Write failed: err=0x%X\n", err);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool LogModule::readRaw(uint32_t offset, uint8_t* data, size_t size) {
|
||
if (size % 4 != 0 || offset % 4 != 0) {
|
||
#ifdef FLASH_PRINT
|
||
Serial.printf("FATAL: Alignment error: offset=0x%X, size=%u\n", offset, size);
|
||
#endif
|
||
return false;
|
||
}
|
||
|
||
esp_err_t err = esp_partition_read(partition, offset, data, size);
|
||
return err == ESP_OK;
|
||
}
|
||
|
||
void LogModule::saveMetadata() {
|
||
prefs.putUInt("write_pos", write_pos);
|
||
prefs.putUInt("total_entries", total_entries);
|
||
#ifdef META_PRINT
|
||
Serial.printf("Metadata saved: write_pos=%u, total_entries=%u\n", write_pos, total_entries);
|
||
#endif
|
||
}
|
||
|
||
bool LogModule::loadMetadata() {
|
||
write_pos = prefs.getUInt("write_pos", 0);
|
||
total_entries = prefs.getUInt("total_entries", 0);
|
||
|
||
// Валидация загруженных метаданных
|
||
uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
|
||
if (write_pos >= max_entries || total_entries > max_entries) {
|
||
Serial.printf("Invalid metadata, resetting: write_pos=%u, total_entries=%u\n",
|
||
write_pos, total_entries);
|
||
write_pos = 0;
|
||
total_entries = 0;
|
||
saveMetadata();
|
||
return false;
|
||
}
|
||
|
||
#ifdef META_PRINT
|
||
Serial.printf("Metadata loaded: write_pos=%u, total_entries=%u\n", write_pos, total_entries);
|
||
#endif
|
||
return true;
|
||
}
|
||
|
||
bool LogModule::writeLog(const LogEntry &entry) {
|
||
if (!partition) return false;
|
||
|
||
// Подготавливаем запись с CRC
|
||
LogEntry safe_entry = entry;
|
||
safe_entry.crc = calculateCRC(safe_entry);
|
||
memset(safe_entry.reserved, 0, sizeof(safe_entry.reserved)); // Обнуляем reserved
|
||
|
||
// Вычисляем позицию
|
||
uint32_t entry_offset = 8 + (write_pos * ENTRY_SIZE); // 8 байт метаданных
|
||
|
||
#ifdef FLASH_PRINT
|
||
Serial.printf("Writing to offset 0x%X: SEQ:%u Size:%u\n",
|
||
entry_offset, safe_entry.seq, ENTRY_SIZE);
|
||
#endif
|
||
|
||
// Записываем запись
|
||
if (writeRaw(entry_offset, (uint8_t*)&safe_entry, ENTRY_SIZE)) {
|
||
write_pos = (write_pos + 1) % ((partition_size - 8) / ENTRY_SIZE);
|
||
total_entries++;
|
||
|
||
// Сохраняем метаlанные
|
||
saveMetadata();
|
||
|
||
#ifdef LOGGED_PRINT
|
||
String tsStr = String(entry.ts);
|
||
Serial.println("Logged SEQ:" + String(entry.seq) +
|
||
" TS:" + tsStr +
|
||
" EVT:" + (entry.event_type == 0 ? "RECV" : "SEND") +
|
||
" PAYLOAD:" + String(entry.payload) +
|
||
" CRC:0x" + String(safe_entry.crc, HEX));
|
||
#endif
|
||
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
|
||
|
||
void LogModule::dumpLogs() {
|
||
if (!partition) return;
|
||
|
||
// БЛОКИРУЕМ ВЫПОЛНЕНИЕ ПОКА ВСЕ НЕ ОТПРАВИМ
|
||
Serial.println("\n=== LOG DUMP START ===");
|
||
Serial.flush(); // Ждем отправки
|
||
|
||
Serial.printf("Total entries: %u, Write pos: %u\n", total_entries, write_pos);
|
||
Serial.flush();
|
||
|
||
uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
|
||
uint32_t entries_to_read = min(total_entries, max_entries);
|
||
|
||
// ПРАВИЛЬНАЯ логика для кольцевого буфера
|
||
uint32_t start_index = 0;
|
||
if (total_entries > max_entries) {
|
||
// Буфер переполнен - начинаем с позиции, где находятся самые старые данные
|
||
start_index = write_pos;
|
||
}
|
||
|
||
Serial.printf("Reading %u entries, start index: %u\n", entries_to_read, start_index);
|
||
Serial.flush();
|
||
|
||
for (uint32_t i = 0; i < entries_to_read; i++) {
|
||
uint32_t index = (start_index + i) % max_entries;
|
||
uint32_t offset = 8 + (index * ENTRY_SIZE);
|
||
|
||
LogEntry entry;
|
||
if (readRaw(offset, (uint8_t*)&entry, ENTRY_SIZE)) {
|
||
if (verifyEntry(entry)) {
|
||
Serial.printf("Entry %u: SEQ:%u TS:%llu EVT:%s PAYLOAD:%s\n",
|
||
i, entry.seq, entry.ts,
|
||
entry.event_type == 0 ? "RECV" : "SEND",
|
||
entry.payload);
|
||
} else {
|
||
Serial.printf("Entry %u: CORRUPTED - SEQ:%u CRC:0x%02X\n",
|
||
i, entry.seq, entry.crc);
|
||
}
|
||
|
||
// ПРИНУДИТЕЛЬНО ЖДЕМ ОТПРАВКИ КАЖДОЙ ЗАПИСИ
|
||
Serial.flush();
|
||
|
||
} else {
|
||
Serial.printf("Read failed at index %u, offset 0x%X\n", i, offset);
|
||
Serial.flush();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// ФИНАЛЬНОЕ ОЖИДАНИЕ
|
||
Serial.println("=== LOG DUMP END ===");
|
||
Serial.flush();
|
||
}
|
||
|
||
|
||
void LogModule::clearLogs() {
|
||
Serial.println("\n=== LOG ERASING ===");
|
||
|
||
// Очищаем данные в партиции
|
||
esp_partition_erase_range(partition, 0, partition_size);
|
||
|
||
// Очищаем метаданные в Preferences
|
||
prefs.clear();
|
||
|
||
// Сбрасываем переменные
|
||
write_pos = 0;
|
||
total_entries = 0;
|
||
|
||
// Сохраняем начальные метаданные
|
||
saveMetadata();
|
||
|
||
Serial.println("=== LOG CLEARED ===");
|
||
}
|
||
|
||
|
||
void LogModule::handleUART(char cmd, char dumpCmd, char clearCmd) {
|
||
if (cmd == dumpCmd) dumpLogs();
|
||
else if (cmd == clearCmd) clearLogs();
|
||
} |