#include "logs.h" #include #include // 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(); }