ESP_WifiTest/logs.cpp

371 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 1;
}
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();
}