#include "rfid.hpp" #include RfidDB rfidDB; RfidDB::RfidDB(const String& filename) : filename_(filename), tmpFilename_(filename + ".tmp"), initialized_(false) {} RfidDB::~RfidDB() {} void printDatabaseContents() { LOG_DEBUG("--- RFID Database Contents (Stored Values) ---"); rfidDB.iterate([](uint32_t stored_id) { // 'stored_id' ist der Wert, wie er in der Datei steht (Byte-geswappt) Serial.printf("Stored HEX: 0x%08X\n", stored_id); }); LOG_DEBUG("-------------------------------------------"); } bool RfidDB::begin() { if (initialized_) { return true; } if (!LittleFS.begin()) { LOG_ERROR("RfidDB: LittleFS mount failed"); return false; } // Ensure the file exists, creating it if necessary. if (!LittleFS.exists(filename_)) { File f = LittleFS.open(filename_, "w"); if (!f) { return false; // Could not create the file } f.close(); } initialized_ = true; printDatabaseContents(); return true; } uint32_t RfidDB::count() { if (!initialized_) return 0; File f = LittleFS.open(filename_, "r"); if (!f) return 0; uint32_t n = fileEntryCount(f); f.close(); return n; } bool RfidDB::contains(uint32_t raw_id) { if (!initialized_) { LOG_DEBUG("RfidDB: contains not initialized"); return false; } File f = LittleFS.open(filename_, "r"); if (!f) { LOG_DEBUG("RfidDB: contains failed to open file"); return false; } uint32_t id = byteSwap(raw_id); uint32_t idx; bool found = false; bool ok = binarySearch(f, idx, id, found); f.close(); LOG_DEBUG("RfidDB: contains id=%08X, ok=%d, found=%d", raw_id, ok, found); return ok && found; } void RfidDB::iterate(std::function callback) { if (!initialized_ || !callback) return; File f = LittleFS.open(filename_, "r"); if (!f) return; uint32_t n = fileEntryCount(f); for (uint32_t i = 0; i < n; ++i) { uint32_t v; if (readEntryAt(f, i, v)) { callback(v); } } f.close(); } uint32_t RfidDB::fileEntryCount(File &f) { return f.size() / ENTRY_SIZE; } // 🚀 More efficient read/write methods bool RfidDB::readEntryAt(File &f, uint32_t index, uint32_t &out) { if (!f.seek(index * ENTRY_SIZE, SeekSet)) return false; return f.read(reinterpret_cast(&out), ENTRY_SIZE) == ENTRY_SIZE; } bool RfidDB::writeEntryAt(File &f, uint32_t index, uint32_t value) { if (!f.seek(index * ENTRY_SIZE, SeekSet)) return false; return f.write(reinterpret_cast(&value), ENTRY_SIZE) == ENTRY_SIZE; } bool RfidDB::binarySearch(File &f, uint32_t &outIndex, uint32_t key, bool &found) { // Get the number of entries in the file (each entry is 4 bytes) uint32_t n = fileEntryCount(f); // If the file is empty, return with outIndex = 0 and found = false if (n == 0) { outIndex = 0; found = false; return true; } // Initialize search boundaries for binary search uint32_t left = 0; uint32_t right = n - 1; // Perform binary search on the sorted database // Note: The database must contain values in ascending order of their *swapped* (Little-Endian) representation // to ensure correct search results. The 'key' parameter is the byte-swapped (Little-Endian) value of the // raw RFID ID (e.g., raw ID 0x635C426D is swapped to 0x6D425C63 for comparison). while (left <= right) { // Calculate the middle index uint32_t mid = left + (right - left) / 2; uint32_t v; // Read the entry at index 'mid' from the file // The file stores IDs as Big-Endian (e.g., bytes 63 5C 42 6D for original ID 0x635C426D). // On this Little-Endian platform (e.g., ESP32), reading 4 bytes into 'v' interprets them as // Little-Endian, so bytes 63 5C 42 6D become v = 0x6D425C63 (swapped). if (!readEntryAt(f, mid, v)) { return false; // Failed to read entry } // Compare the read value 'v' (Little-Endian, swapped) with the search key (also Little-Endian, swapped) if (v == key) { outIndex = mid; found = true; return true; // Found the ID at index 'mid' } // Since the database is sorted by the swapped (Little-Endian) values, // adjust the search boundaries based on the comparison if (v < key) { left = mid + 1; // Search in the right half } else { if (mid == 0) break; // Prevent underflow when right = mid - 1 right = mid - 1; // Search in the left half } } // If not found, set outIndex to the insertion point where the key would be added // to maintain the sorted order of swapped (Little-Endian) values outIndex = left; found = false; return true; } uint32_t RfidDB::byteSwap(uint32_t x) const { return ((x & 0xFF000000) >> 24) | ((x & 0x00FF0000) >> 8) | ((x & 0x0000FF00) << 8) | ((x & 0x000000FF) << 24); } bool RfidDB::add(uint32_t raw_id) { if (!initialized_) return false; File src = LittleFS.open(filename_, "r"); if (!src) return false; uint32_t id = byteSwap(raw_id); uint32_t idx; bool found; if (!binarySearch(src, idx, id, found)) { src.close(); return false; } if (found) { src.close(); return true; // Already present, we consider this a success } File dst = LittleFS.open(tmpFilename_, "w"); if (!dst) { src.close(); return false; } bool success = true; uint32_t n = fileEntryCount(src); uint32_t v; // Copy entries before the insertion point for (uint32_t i = 0; i < idx; ++i) { if (!readEntryAt(src, i, v) || !writeEntryAt(dst, i, v)) { success = false; break; } } // Insert the new entry if (success && !writeEntryAt(dst, idx, id)) { success = false; } // Copy the remaining entries if (success) { for (uint32_t i = idx; i < n; ++i) { if (!readEntryAt(src, i, v) || !writeEntryAt(dst, i + 1, v)) { success = false; break; } } } src.close(); dst.close(); if (!success) { LittleFS.remove(tmpFilename_); return false; } // Atomic replace if (!LittleFS.rename(tmpFilename_, filename_)) { // Fallback in case rename fails LittleFS.remove(tmpFilename_); return false; } return true; } bool RfidDB::remove(uint32_t raw_id) { if (!initialized_) return false; File src = LittleFS.open(filename_, "r"); if (!src) return false; uint32_t id = byteSwap(raw_id); uint32_t idx; bool found; if (!binarySearch(src, idx, id, found) || !found) { src.close(); return false; // Not found, so nothing to remove } File dst = LittleFS.open(tmpFilename_, "w"); if (!dst) { src.close(); return false; } bool success = true; uint32_t n = fileEntryCount(src); uint32_t written = 0; uint32_t v; for (uint32_t i = 0; i < n; ++i) { if (i == idx) continue; // Skip the entry to be deleted if (!readEntryAt(src, i, v) || !writeEntryAt(dst, written, v)) { success = false; break; } written++; } src.close(); dst.close(); if (!success) { LittleFS.remove(tmpFilename_); return false; } if (!LittleFS.rename(tmpFilename_, filename_)) { LittleFS.remove(tmpFilename_); return false; } return true; }