Initial commit: project docs and ignore rules
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
23
README.md
Normal file
23
README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# common (shared NFC helpers)
|
||||||
|
|
||||||
|
Shared C++ sources for the **dual PN532 ESP-NOW** system described in the [parent README](../README.md): protocol definitions, error handling, memory helpers, and other code consumed by `listener_firmware/` and `emulator_firmware/`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Firmware sketches include headers via paths such as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "../../common/nfc_protocol.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep this folder versioned **together** with:
|
||||||
|
|
||||||
|
- `listener_firmware/`
|
||||||
|
- `emulator_firmware/`
|
||||||
|
- Parent `platformio.ini` (workspace root)
|
||||||
|
|
||||||
|
Splitting into a separate remote is easiest if these three become submodules of a single meta-repo, or you refactor include paths.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Unit tests that exercise parts of this tree live in [`tests/`](../tests/).
|
||||||
302
error_handler.cpp
Normal file
302
error_handler.cpp
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#include "error_handler.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#define SERIAL_PRINT(x) Serial.print(x)
|
||||||
|
#define SERIAL_PRINTLN(x) Serial.println(x)
|
||||||
|
#define GET_MILLIS() millis()
|
||||||
|
#define DELAY_MS(x) delay(x)
|
||||||
|
#else
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
#define SERIAL_PRINT(x) printf("%s", (x))
|
||||||
|
#define SERIAL_PRINTLN(x) printf("%s\n", (x))
|
||||||
|
#define GET_MILLIS() (static_cast<unsigned long>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()))
|
||||||
|
#define DELAY_MS(x) std::this_thread::sleep_for(std::chrono::milliseconds(x))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Global error handler instance
|
||||||
|
ErrorHandler errorHandler;
|
||||||
|
|
||||||
|
ErrorHandler::ErrorHandler() : error_index(0), logging_enabled(true) {
|
||||||
|
memset(&stats, 0, sizeof(stats));
|
||||||
|
memset(error_history, 0, sizeof(error_history));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::reportError(ErrorCode code, ErrorSeverity severity, const char* description) {
|
||||||
|
// Update statistics
|
||||||
|
stats.total_errors++;
|
||||||
|
stats.last_error_time = GET_MILLIS();
|
||||||
|
stats.last_error_code = code;
|
||||||
|
|
||||||
|
if (severity == SEVERITY_CRITICAL) {
|
||||||
|
stats.critical_errors++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categorize errors
|
||||||
|
switch (code) {
|
||||||
|
case ERR_NFC_INIT_FAILED:
|
||||||
|
case ERR_NFC_READ_FAILED:
|
||||||
|
stats.nfc_errors++;
|
||||||
|
break;
|
||||||
|
case ERR_COMMUNICATION_TIMEOUT:
|
||||||
|
case ERR_PAIRING_FAILED:
|
||||||
|
case ERR_INVALID_MESSAGE:
|
||||||
|
stats.communication_errors++;
|
||||||
|
break;
|
||||||
|
case ERR_SD_INIT_FAILED:
|
||||||
|
case ERR_SD_WRITE_FAILED:
|
||||||
|
stats.sd_errors++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store in history
|
||||||
|
ErrorEntry& entry = error_history[error_index];
|
||||||
|
entry.code = code;
|
||||||
|
entry.severity = severity;
|
||||||
|
entry.timestamp = GET_MILLIS();
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
strncpy(entry.description, description, sizeof(entry.description) - 1);
|
||||||
|
entry.description[sizeof(entry.description) - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
entry.description[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
error_index = (error_index + 1) % MAX_ERROR_HISTORY;
|
||||||
|
|
||||||
|
// Log the error
|
||||||
|
if (logging_enabled) {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.print("[ERROR] Code: ");
|
||||||
|
Serial.print(static_cast<int>(code));
|
||||||
|
Serial.print(", Severity: ");
|
||||||
|
Serial.print(static_cast<int>(severity));
|
||||||
|
Serial.print(", Time: ");
|
||||||
|
Serial.print(entry.timestamp);
|
||||||
|
if (description) {
|
||||||
|
Serial.print(", Desc: ");
|
||||||
|
Serial.print(description);
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
#else
|
||||||
|
printf("[ERROR] Code: %d, Severity: %d, Time: %lu",
|
||||||
|
static_cast<int>(code), static_cast<int>(severity), entry.timestamp);
|
||||||
|
if (description) {
|
||||||
|
printf(", Desc: %s", description);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt recovery for non-critical errors
|
||||||
|
if (severity < SEVERITY_CRITICAL) {
|
||||||
|
handleError(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::reportNFCError(const char* description) {
|
||||||
|
reportError(ERR_NFC_READ_FAILED, SEVERITY_ERROR, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::reportCommunicationError(const char* description) {
|
||||||
|
reportError(ERR_COMMUNICATION_TIMEOUT, SEVERITY_WARNING, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::reportSDError(const char* description) {
|
||||||
|
reportError(ERR_SD_WRITE_FAILED, SEVERITY_WARNING, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::handleError(ErrorCode code) {
|
||||||
|
switch (code) {
|
||||||
|
case ERR_NFC_READ_FAILED:
|
||||||
|
return attemptNFCRecovery();
|
||||||
|
case ERR_COMMUNICATION_TIMEOUT:
|
||||||
|
case ERR_PAIRING_FAILED:
|
||||||
|
return attemptCommunicationRecovery();
|
||||||
|
case ERR_SD_WRITE_FAILED:
|
||||||
|
return attemptSDRecovery();
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::attemptNFCRecovery() {
|
||||||
|
if (logging_enabled) {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("[RECOVERY] Attempting NFC recovery...");
|
||||||
|
#else
|
||||||
|
printf("[RECOVERY] Attempting NFC recovery...\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DELAY_MS(100);
|
||||||
|
|
||||||
|
// TODO: Implement actual NFC recovery logic
|
||||||
|
return true; // Assume success for now
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::attemptCommunicationRecovery() {
|
||||||
|
if (logging_enabled) {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("[RECOVERY] Attempting communication recovery...");
|
||||||
|
#else
|
||||||
|
printf("[RECOVERY] Attempting communication recovery...\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DELAY_MS(500);
|
||||||
|
|
||||||
|
return true; // Assume success for now
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::attemptSDRecovery() {
|
||||||
|
if (logging_enabled) {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("[RECOVERY] Attempting SD card recovery...");
|
||||||
|
#else
|
||||||
|
printf("[RECOVERY] Attempting SD card recovery...\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DELAY_MS(200);
|
||||||
|
|
||||||
|
return true; // Assume success for now
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::clearErrors() {
|
||||||
|
memset(&stats, 0, sizeof(stats));
|
||||||
|
memset(error_history, 0, sizeof(error_history));
|
||||||
|
error_index = 0;
|
||||||
|
|
||||||
|
if (logging_enabled) {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("[INFO] Error history cleared");
|
||||||
|
#else
|
||||||
|
printf("[INFO] Error history cleared\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorStats ErrorHandler::getStats() const {
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::hasRecentErrors(unsigned long time_window_ms) const {
|
||||||
|
unsigned long current_time = GET_MILLIS();
|
||||||
|
return (current_time - stats.last_error_time) < time_window_ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::hasCriticalErrors() const {
|
||||||
|
return stats.critical_errors > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::printErrorHistory() const {
|
||||||
|
if (!logging_enabled) return;
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("=== Error History ===");
|
||||||
|
for (int i = 0; i < MAX_ERROR_HISTORY; i++) {
|
||||||
|
int idx = (error_index + i) % MAX_ERROR_HISTORY;
|
||||||
|
const ErrorEntry& entry = error_history[idx];
|
||||||
|
|
||||||
|
if (entry.timestamp == 0) continue; // Empty entry
|
||||||
|
|
||||||
|
Serial.print("[");
|
||||||
|
Serial.print(i + 1);
|
||||||
|
Serial.print("] Code: ");
|
||||||
|
Serial.print(static_cast<int>(entry.code));
|
||||||
|
Serial.print(", Severity: ");
|
||||||
|
Serial.print(static_cast<int>(entry.severity));
|
||||||
|
Serial.print(", Time: ");
|
||||||
|
Serial.print(entry.timestamp);
|
||||||
|
if (entry.description[0] != '\0') {
|
||||||
|
Serial.print(", Desc: ");
|
||||||
|
Serial.print(entry.description);
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
Serial.println("===================");
|
||||||
|
#else
|
||||||
|
printf("=== Error History ===\n");
|
||||||
|
for (int i = 0; i < MAX_ERROR_HISTORY; i++) {
|
||||||
|
int idx = (error_index + i) % MAX_ERROR_HISTORY;
|
||||||
|
const ErrorEntry& entry = error_history[idx];
|
||||||
|
|
||||||
|
if (entry.timestamp == 0) continue; // Empty entry
|
||||||
|
|
||||||
|
printf("[%d] Code: %d, Severity: %d, Time: %lu",
|
||||||
|
i + 1, static_cast<int>(entry.code), static_cast<int>(entry.severity), entry.timestamp);
|
||||||
|
if (entry.description[0] != '\0') {
|
||||||
|
printf(", Desc: %s", entry.description);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("===================\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::printStats() const {
|
||||||
|
if (!logging_enabled) return;
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("=== Error Statistics ===");
|
||||||
|
Serial.print("Total Errors: ");
|
||||||
|
Serial.println(stats.total_errors);
|
||||||
|
Serial.print("Critical Errors: ");
|
||||||
|
Serial.println(stats.critical_errors);
|
||||||
|
Serial.print("NFC Errors: ");
|
||||||
|
Serial.println(stats.nfc_errors);
|
||||||
|
Serial.print("Communication Errors: ");
|
||||||
|
Serial.println(stats.communication_errors);
|
||||||
|
Serial.print("SD Errors: ");
|
||||||
|
Serial.println(stats.sd_errors);
|
||||||
|
Serial.print("Last Error Time: ");
|
||||||
|
Serial.println(stats.last_error_time);
|
||||||
|
Serial.print("Last Error Code: ");
|
||||||
|
Serial.println(static_cast<int>(stats.last_error_code));
|
||||||
|
Serial.println("========================");
|
||||||
|
#else
|
||||||
|
printf("=== Error Statistics ===\n");
|
||||||
|
printf("Total Errors: %u\n", stats.total_errors);
|
||||||
|
printf("Critical Errors: %u\n", stats.critical_errors);
|
||||||
|
printf("NFC Errors: %u\n", stats.nfc_errors);
|
||||||
|
printf("Communication Errors: %u\n", stats.communication_errors);
|
||||||
|
printf("SD Errors: %u\n", stats.sd_errors);
|
||||||
|
printf("Last Error Time: %u\n", stats.last_error_time);
|
||||||
|
printf("Last Error Code: %d\n", static_cast<int>(stats.last_error_code));
|
||||||
|
printf("========================\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::enableLogging(bool enable) {
|
||||||
|
logging_enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ErrorHandler::feedWatchdog() {
|
||||||
|
// TODO: Implement watchdog feeding logic
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ErrorHandler::isSystemHealthy() const {
|
||||||
|
// Check for critical errors
|
||||||
|
if (hasCriticalErrors()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for recent errors (within 30 seconds)
|
||||||
|
if (hasRecentErrors(30000)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check total error count threshold
|
||||||
|
if (stats.total_errors > 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
104
error_handler.h
Normal file
104
error_handler.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#ifndef ERROR_HANDLER_H
|
||||||
|
#define ERROR_HANDLER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#else
|
||||||
|
#include <cstdio>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Error codes
|
||||||
|
enum ErrorCode {
|
||||||
|
ERR_NONE = 0,
|
||||||
|
ERR_NFC_INIT_FAILED = 1,
|
||||||
|
ERR_ESPNOW_INIT_FAILED = 2,
|
||||||
|
ERR_SD_INIT_FAILED = 3,
|
||||||
|
ERR_COMMUNICATION_TIMEOUT = 4,
|
||||||
|
ERR_INVALID_MESSAGE = 5,
|
||||||
|
ERR_PAIRING_FAILED = 6,
|
||||||
|
ERR_NFC_READ_FAILED = 7,
|
||||||
|
ERR_SD_WRITE_FAILED = 8,
|
||||||
|
ERR_MEMORY_ALLOCATION = 9,
|
||||||
|
ERR_HARDWARE_FAULT = 10
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error severity levels
|
||||||
|
enum ErrorSeverity {
|
||||||
|
SEVERITY_INFO = 0,
|
||||||
|
SEVERITY_WARNING = 1,
|
||||||
|
SEVERITY_ERROR = 2,
|
||||||
|
SEVERITY_CRITICAL = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error entry structure
|
||||||
|
struct ErrorEntry {
|
||||||
|
ErrorCode code;
|
||||||
|
ErrorSeverity severity;
|
||||||
|
unsigned long timestamp;
|
||||||
|
char description[64];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error statistics
|
||||||
|
struct ErrorStats {
|
||||||
|
uint32_t total_errors;
|
||||||
|
uint32_t critical_errors;
|
||||||
|
uint32_t last_error_time;
|
||||||
|
ErrorCode last_error_code;
|
||||||
|
uint32_t nfc_errors;
|
||||||
|
uint32_t communication_errors;
|
||||||
|
uint32_t sd_errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ErrorHandler {
|
||||||
|
private:
|
||||||
|
static const int MAX_ERROR_HISTORY = 10;
|
||||||
|
ErrorEntry error_history[MAX_ERROR_HISTORY];
|
||||||
|
int error_index;
|
||||||
|
ErrorStats stats;
|
||||||
|
bool logging_enabled;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ErrorHandler();
|
||||||
|
|
||||||
|
// Error reporting
|
||||||
|
void reportError(ErrorCode code, ErrorSeverity severity, const char* description = nullptr);
|
||||||
|
void reportNFCError(const char* description = nullptr);
|
||||||
|
void reportCommunicationError(const char* description = nullptr);
|
||||||
|
void reportSDError(const char* description = nullptr);
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
bool handleError(ErrorCode code);
|
||||||
|
void clearErrors();
|
||||||
|
|
||||||
|
// Recovery mechanisms
|
||||||
|
bool attemptNFCRecovery();
|
||||||
|
bool attemptCommunicationRecovery();
|
||||||
|
bool attemptSDRecovery();
|
||||||
|
|
||||||
|
// Status and statistics
|
||||||
|
ErrorStats getStats() const;
|
||||||
|
bool hasRecentErrors(unsigned long time_window_ms = 5000) const;
|
||||||
|
bool hasCriticalErrors() const;
|
||||||
|
void printErrorHistory() const;
|
||||||
|
void printStats() const;
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
void enableLogging(bool enable);
|
||||||
|
void setMaxRetries(int retries);
|
||||||
|
|
||||||
|
// Watchdog functionality
|
||||||
|
void feedWatchdog();
|
||||||
|
bool isSystemHealthy() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global error handler instance
|
||||||
|
extern ErrorHandler errorHandler;
|
||||||
|
|
||||||
|
// Convenience macros
|
||||||
|
#define REPORT_ERROR(code, severity, desc) errorHandler.reportError(code, severity, desc)
|
||||||
|
#define REPORT_NFC_ERROR(desc) errorHandler.reportNFCError(desc)
|
||||||
|
#define REPORT_COMM_ERROR(desc) errorHandler.reportCommunicationError(desc)
|
||||||
|
#define REPORT_SD_ERROR(desc) errorHandler.reportSDError(desc)
|
||||||
|
|
||||||
|
#endif // ERROR_HANDLER_H
|
||||||
327
memory_manager.cpp
Normal file
327
memory_manager.cpp
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
#include "memory_manager.h"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#define DEBUG_PRINT(x) Serial.print(x)
|
||||||
|
#define DEBUG_PRINTLN(x) Serial.println(x)
|
||||||
|
#else
|
||||||
|
#include <cstdio>
|
||||||
|
#define DEBUG_PRINT(x) printf("%s", (x))
|
||||||
|
#define DEBUG_PRINTLN(x) printf("%s\n", (x))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Global memory pool instance
|
||||||
|
MemoryPool globalMemoryPool;
|
||||||
|
|
||||||
|
MemoryPool::MemoryPool() : next_free_offset(0), allocation_count(0), deallocation_count(0) {
|
||||||
|
// Initialize all blocks as unused
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
blocks[i].ptr = nullptr;
|
||||||
|
blocks[i].size = 0;
|
||||||
|
blocks[i].in_use = false;
|
||||||
|
blocks[i].magic = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the memory pool
|
||||||
|
memset(pool_memory, 0, POOL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryPool::~MemoryPool() {
|
||||||
|
// Check for memory leaks
|
||||||
|
uint32_t active_blocks = 0;
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if (blocks[i].in_use) {
|
||||||
|
active_blocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_blocks > 0) {
|
||||||
|
#ifdef DEBUG_MEMORY
|
||||||
|
DEBUG_PRINT("WARNING: Memory pool destroyed with ");
|
||||||
|
DEBUG_PRINT(active_blocks);
|
||||||
|
DEBUG_PRINTLN(" active blocks!");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* MemoryPool::allocate(size_t size) {
|
||||||
|
if (size == 0 || size > POOL_SIZE) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Align size to 4-byte boundary for better performance
|
||||||
|
size = (size + 3) & ~3;
|
||||||
|
|
||||||
|
// Check if we have enough space
|
||||||
|
if (next_free_offset + size > POOL_SIZE) {
|
||||||
|
// Try to find a free block that was previously deallocated
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if (!blocks[i].in_use && blocks[i].ptr != nullptr && blocks[i].size >= size) {
|
||||||
|
blocks[i].in_use = true;
|
||||||
|
blocks[i].magic = MAGIC_NUMBER;
|
||||||
|
allocation_count++;
|
||||||
|
return blocks[i].ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr; // Out of memory
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a free block descriptor
|
||||||
|
size_t block_index = MAX_BLOCKS;
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if (!blocks[i].in_use && blocks[i].ptr == nullptr) {
|
||||||
|
block_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block_index == MAX_BLOCKS) {
|
||||||
|
return nullptr; // No free block descriptors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate from the pool
|
||||||
|
void* ptr = &pool_memory[next_free_offset];
|
||||||
|
|
||||||
|
// Set up the block descriptor
|
||||||
|
blocks[block_index].ptr = ptr;
|
||||||
|
blocks[block_index].size = size;
|
||||||
|
blocks[block_index].in_use = true;
|
||||||
|
blocks[block_index].magic = MAGIC_NUMBER;
|
||||||
|
|
||||||
|
next_free_offset += size;
|
||||||
|
allocation_count++;
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryPool::deallocate(void* ptr) {
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the block
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if (blocks[i].ptr == ptr && blocks[i].in_use) {
|
||||||
|
// Check magic number for corruption
|
||||||
|
if (blocks[i].magic != MAGIC_NUMBER) {
|
||||||
|
#ifdef DEBUG_MEMORY
|
||||||
|
DEBUG_PRINTLN("ERROR: Memory corruption detected during deallocation!");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks[i].in_use = false;
|
||||||
|
blocks[i].magic = 0;
|
||||||
|
deallocation_count++;
|
||||||
|
|
||||||
|
// Clear the memory for security
|
||||||
|
memset(ptr, 0, blocks[i].size);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Pointer not found
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryPool::MemoryStats MemoryPool::getStats() const {
|
||||||
|
MemoryStats stats;
|
||||||
|
stats.total_size = POOL_SIZE;
|
||||||
|
stats.allocations = allocation_count;
|
||||||
|
stats.deallocations = deallocation_count;
|
||||||
|
stats.active_blocks = 0;
|
||||||
|
stats.used_size = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if (blocks[i].in_use) {
|
||||||
|
stats.active_blocks++;
|
||||||
|
stats.used_size += blocks[i].size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.free_size = POOL_SIZE - stats.used_size;
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryPool::checkIntegrity() const {
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
if (blocks[i].in_use && blocks[i].magic != MAGIC_NUMBER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPool::reset() {
|
||||||
|
// Clear all blocks
|
||||||
|
for (size_t i = 0; i < MAX_BLOCKS; ++i) {
|
||||||
|
blocks[i].ptr = nullptr;
|
||||||
|
blocks[i].size = 0;
|
||||||
|
blocks[i].in_use = false;
|
||||||
|
blocks[i].magic = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
next_free_offset = 0;
|
||||||
|
allocation_count = 0;
|
||||||
|
deallocation_count = 0;
|
||||||
|
|
||||||
|
// Clear the memory pool
|
||||||
|
memset(pool_memory, 0, POOL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPool::printStats() const {
|
||||||
|
MemoryStats stats = getStats();
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
Serial.println("=== Memory Pool Statistics ===");
|
||||||
|
Serial.print("Total Size: ");
|
||||||
|
Serial.print(stats.total_size);
|
||||||
|
Serial.println(" bytes");
|
||||||
|
Serial.print("Used Size: ");
|
||||||
|
Serial.print(stats.used_size);
|
||||||
|
Serial.println(" bytes");
|
||||||
|
Serial.print("Free Size: ");
|
||||||
|
Serial.print(stats.free_size);
|
||||||
|
Serial.println(" bytes");
|
||||||
|
Serial.print("Active Blocks: ");
|
||||||
|
Serial.println(stats.active_blocks);
|
||||||
|
Serial.print("Total Allocations: ");
|
||||||
|
Serial.println(stats.allocations);
|
||||||
|
Serial.print("Total Deallocations: ");
|
||||||
|
Serial.println(stats.deallocations);
|
||||||
|
Serial.print("Memory Integrity: ");
|
||||||
|
Serial.println(checkIntegrity() ? "OK" : "CORRUPTED");
|
||||||
|
Serial.println("==============================");
|
||||||
|
#else
|
||||||
|
printf("=== Memory Pool Statistics ===\n");
|
||||||
|
printf("Total Size: %zu bytes\n", stats.total_size);
|
||||||
|
printf("Used Size: %zu bytes\n", stats.used_size);
|
||||||
|
printf("Free Size: %zu bytes\n", stats.free_size);
|
||||||
|
printf("Active Blocks: %u\n", stats.active_blocks);
|
||||||
|
printf("Total Allocations: %u\n", stats.allocations);
|
||||||
|
printf("Total Deallocations: %u\n", stats.deallocations);
|
||||||
|
printf("Memory Integrity: %s\n", checkIntegrity() ? "OK" : "CORRUPTED");
|
||||||
|
printf("==============================\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeBuffer implementation
|
||||||
|
SafeBuffer::SafeBuffer(size_t capacity, MemoryPool* pool)
|
||||||
|
: data_(nullptr), size_(0), capacity_(capacity), pool_(pool), owns_memory_(false) {
|
||||||
|
|
||||||
|
if (capacity > 0) {
|
||||||
|
if (pool_) {
|
||||||
|
data_ = static_cast<uint8_t*>(pool_->allocate(capacity));
|
||||||
|
} else {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
data_ = static_cast<uint8_t*>(malloc(capacity));
|
||||||
|
#else
|
||||||
|
data_ = new uint8_t[capacity];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_) {
|
||||||
|
owns_memory_ = true;
|
||||||
|
memset(data_, 0, capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeBuffer::~SafeBuffer() {
|
||||||
|
if (data_ && owns_memory_) {
|
||||||
|
if (pool_) {
|
||||||
|
pool_->deallocate(data_);
|
||||||
|
} else {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
free(data_);
|
||||||
|
#else
|
||||||
|
delete[] data_;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeBuffer::SafeBuffer(SafeBuffer&& other) noexcept
|
||||||
|
: data_(other.data_), size_(other.size_), capacity_(other.capacity_),
|
||||||
|
pool_(other.pool_), owns_memory_(other.owns_memory_) {
|
||||||
|
other.data_ = nullptr;
|
||||||
|
other.size_ = 0;
|
||||||
|
other.capacity_ = 0;
|
||||||
|
other.owns_memory_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SafeBuffer& SafeBuffer::operator=(SafeBuffer&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
// Clean up current resources
|
||||||
|
if (data_ && owns_memory_) {
|
||||||
|
if (pool_) {
|
||||||
|
pool_->deallocate(data_);
|
||||||
|
} else {
|
||||||
|
#ifdef ARDUINO
|
||||||
|
free(data_);
|
||||||
|
#else
|
||||||
|
delete[] data_;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move from other
|
||||||
|
data_ = other.data_;
|
||||||
|
size_ = other.size_;
|
||||||
|
capacity_ = other.capacity_;
|
||||||
|
pool_ = other.pool_;
|
||||||
|
owns_memory_ = other.owns_memory_;
|
||||||
|
|
||||||
|
// Reset other
|
||||||
|
other.data_ = nullptr;
|
||||||
|
other.size_ = 0;
|
||||||
|
other.capacity_ = 0;
|
||||||
|
other.owns_memory_ = false;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SafeBuffer::write(const void* data, size_t size, size_t offset) {
|
||||||
|
if (!data_ || !data || size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + size > capacity_) {
|
||||||
|
return false; // Would exceed buffer capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data_ + offset, data, size);
|
||||||
|
|
||||||
|
// Update size if we wrote beyond current size
|
||||||
|
if (offset + size > size_) {
|
||||||
|
size_ = offset + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SafeBuffer::read(void* data, size_t size, size_t offset) const {
|
||||||
|
if (!data_ || !data || size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + size > size_) {
|
||||||
|
return false; // Would read beyond valid data
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(data, data_ + offset, size);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SafeBuffer::append(const void* data, size_t size) {
|
||||||
|
return write(data, size, size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SafeBuffer::clear() {
|
||||||
|
if (data_) {
|
||||||
|
memset(data_, 0, capacity_);
|
||||||
|
size_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
282
memory_manager.h
Normal file
282
memory_manager.h
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
#ifndef MEMORY_MANAGER_H
|
||||||
|
#define MEMORY_MANAGER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#else
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <memory>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Memory pool for embedded systems with limited heap
|
||||||
|
*
|
||||||
|
* This class provides a simple memory pool implementation to avoid
|
||||||
|
* heap fragmentation and provide predictable memory allocation.
|
||||||
|
*/
|
||||||
|
class MemoryPool {
|
||||||
|
private:
|
||||||
|
static const size_t POOL_SIZE = 4096; // 4KB pool
|
||||||
|
static const size_t MAX_BLOCKS = 32;
|
||||||
|
|
||||||
|
struct Block {
|
||||||
|
void* ptr;
|
||||||
|
size_t size;
|
||||||
|
bool in_use;
|
||||||
|
uint32_t magic; // For corruption detection
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t pool_memory[POOL_SIZE];
|
||||||
|
Block blocks[MAX_BLOCKS];
|
||||||
|
size_t next_free_offset;
|
||||||
|
uint32_t allocation_count;
|
||||||
|
uint32_t deallocation_count;
|
||||||
|
|
||||||
|
static const uint32_t MAGIC_NUMBER = 0xDEADBEEF;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MemoryPool();
|
||||||
|
~MemoryPool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate memory from the pool
|
||||||
|
* @param size Size in bytes to allocate
|
||||||
|
* @return Pointer to allocated memory or nullptr if failed
|
||||||
|
*/
|
||||||
|
void* allocate(size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deallocate memory back to the pool
|
||||||
|
* @param ptr Pointer to memory to deallocate
|
||||||
|
* @return true if successful, false if invalid pointer
|
||||||
|
*/
|
||||||
|
bool deallocate(void* ptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get memory usage statistics
|
||||||
|
*/
|
||||||
|
struct MemoryStats {
|
||||||
|
size_t total_size;
|
||||||
|
size_t used_size;
|
||||||
|
size_t free_size;
|
||||||
|
uint32_t allocations;
|
||||||
|
uint32_t deallocations;
|
||||||
|
uint32_t active_blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryStats getStats() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check for memory corruption
|
||||||
|
* @return true if memory is intact, false if corruption detected
|
||||||
|
*/
|
||||||
|
bool checkIntegrity() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the entire pool (use with caution!)
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print memory usage information
|
||||||
|
*/
|
||||||
|
void printStats() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RAII wrapper for automatic memory management
|
||||||
|
*
|
||||||
|
* This template class provides automatic memory management
|
||||||
|
* using RAII principles for embedded systems.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
class SmartPtr {
|
||||||
|
private:
|
||||||
|
T* ptr_;
|
||||||
|
MemoryPool* pool_;
|
||||||
|
bool owns_memory_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor for pool-allocated memory
|
||||||
|
*/
|
||||||
|
explicit SmartPtr(MemoryPool* pool = nullptr)
|
||||||
|
: ptr_(nullptr), pool_(pool), owns_memory_(false) {
|
||||||
|
if (pool_) {
|
||||||
|
ptr_ = static_cast<T*>(pool_->allocate(sizeof(T)));
|
||||||
|
if (ptr_) {
|
||||||
|
new(ptr_) T(); // Placement new
|
||||||
|
owns_memory_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor taking ownership of existing pointer
|
||||||
|
*/
|
||||||
|
SmartPtr(T* ptr, MemoryPool* pool, bool owns = true)
|
||||||
|
: ptr_(ptr), pool_(pool), owns_memory_(owns) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move constructor
|
||||||
|
*/
|
||||||
|
SmartPtr(SmartPtr&& other) noexcept
|
||||||
|
: ptr_(other.ptr_), pool_(other.pool_), owns_memory_(other.owns_memory_) {
|
||||||
|
other.ptr_ = nullptr;
|
||||||
|
other.owns_memory_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Move assignment operator
|
||||||
|
*/
|
||||||
|
SmartPtr& operator=(SmartPtr&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
reset();
|
||||||
|
ptr_ = other.ptr_;
|
||||||
|
pool_ = other.pool_;
|
||||||
|
owns_memory_ = other.owns_memory_;
|
||||||
|
other.ptr_ = nullptr;
|
||||||
|
other.owns_memory_ = false;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destructor - automatically cleans up
|
||||||
|
*/
|
||||||
|
~SmartPtr() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete copy constructor and assignment (no copying)
|
||||||
|
*/
|
||||||
|
SmartPtr(const SmartPtr&) = delete;
|
||||||
|
SmartPtr& operator=(const SmartPtr&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Access the managed object
|
||||||
|
*/
|
||||||
|
T* get() const { return ptr_; }
|
||||||
|
T& operator*() const { return *ptr_; }
|
||||||
|
T* operator->() const { return ptr_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the pointer is valid
|
||||||
|
*/
|
||||||
|
bool isValid() const { return ptr_ != nullptr; }
|
||||||
|
explicit operator bool() const { return isValid(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Release ownership without destroying
|
||||||
|
*/
|
||||||
|
T* release() {
|
||||||
|
T* temp = ptr_;
|
||||||
|
ptr_ = nullptr;
|
||||||
|
owns_memory_ = false;
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the pointer, destroying current object if owned
|
||||||
|
*/
|
||||||
|
void reset() {
|
||||||
|
if (ptr_ && owns_memory_) {
|
||||||
|
ptr_->~T(); // Explicit destructor call
|
||||||
|
if (pool_) {
|
||||||
|
pool_->deallocate(ptr_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr_ = nullptr;
|
||||||
|
owns_memory_ = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Buffer management class with bounds checking
|
||||||
|
*/
|
||||||
|
class SafeBuffer {
|
||||||
|
private:
|
||||||
|
uint8_t* data_;
|
||||||
|
size_t size_;
|
||||||
|
size_t capacity_;
|
||||||
|
MemoryPool* pool_;
|
||||||
|
bool owns_memory_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SafeBuffer(size_t capacity, MemoryPool* pool = nullptr);
|
||||||
|
~SafeBuffer();
|
||||||
|
|
||||||
|
// Delete copy constructor and assignment
|
||||||
|
SafeBuffer(const SafeBuffer&) = delete;
|
||||||
|
SafeBuffer& operator=(const SafeBuffer&) = delete;
|
||||||
|
|
||||||
|
// Move constructor and assignment
|
||||||
|
SafeBuffer(SafeBuffer&& other) noexcept;
|
||||||
|
SafeBuffer& operator=(SafeBuffer&& other) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write data to buffer with bounds checking
|
||||||
|
*/
|
||||||
|
bool write(const void* data, size_t size, size_t offset = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read data from buffer with bounds checking
|
||||||
|
*/
|
||||||
|
bool read(void* data, size_t size, size_t offset = 0) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append data to buffer
|
||||||
|
*/
|
||||||
|
bool append(const void* data, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear buffer contents
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get buffer information
|
||||||
|
*/
|
||||||
|
uint8_t* data() { return data_; }
|
||||||
|
const uint8_t* data() const { return data_; }
|
||||||
|
size_t size() const { return size_; }
|
||||||
|
size_t capacity() const { return capacity_; }
|
||||||
|
size_t available() const { return capacity_ - size_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if buffer is valid
|
||||||
|
*/
|
||||||
|
bool isValid() const { return data_ != nullptr; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global memory pool instance
|
||||||
|
extern MemoryPool globalMemoryPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convenience function to create smart pointers
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
SmartPtr<T> makeSmartPtr(MemoryPool* pool = &globalMemoryPool) {
|
||||||
|
return SmartPtr<T>(pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Memory debugging macros (only active in debug builds)
|
||||||
|
*/
|
||||||
|
#ifdef DEBUG_MEMORY
|
||||||
|
#define MEM_ALLOC(size) globalMemoryPool.allocate(size)
|
||||||
|
#define MEM_FREE(ptr) globalMemoryPool.deallocate(ptr)
|
||||||
|
#define MEM_CHECK() globalMemoryPool.checkIntegrity()
|
||||||
|
#define MEM_STATS() globalMemoryPool.printStats()
|
||||||
|
#else
|
||||||
|
#define MEM_ALLOC(size) globalMemoryPool.allocate(size)
|
||||||
|
#define MEM_FREE(ptr) globalMemoryPool.deallocate(ptr)
|
||||||
|
#define MEM_CHECK() true
|
||||||
|
#define MEM_STATS() do {} while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // MEMORY_MANAGER_H
|
||||||
69
nfc_protocol.cpp
Normal file
69
nfc_protocol.cpp
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include "nfc_protocol.h"
|
||||||
|
|
||||||
|
uint16_t calculateChecksum(const uint8_t* data, size_t length) {
|
||||||
|
uint16_t checksum = 0;
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
checksum += data[i];
|
||||||
|
}
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateMessage(const CommMessage* msg) {
|
||||||
|
if (msg == nullptr) return false;
|
||||||
|
|
||||||
|
// Calculate checksum excluding the checksum field itself
|
||||||
|
size_t data_size = sizeof(CommMessage) - sizeof(msg->checksum);
|
||||||
|
uint16_t calculated_checksum = calculateChecksum((const uint8_t*)msg, data_size);
|
||||||
|
|
||||||
|
return calculated_checksum == msg->checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printNFCData(const NFCData* data) {
|
||||||
|
if (data == nullptr) return;
|
||||||
|
|
||||||
|
Serial.println("=== NFC Data ===");
|
||||||
|
Serial.print("UID: ");
|
||||||
|
for (int i = 0; i < data->uid_length; i++) {
|
||||||
|
if (data->uid[i] < 0x10) Serial.print("0");
|
||||||
|
Serial.print(data->uid[i], HEX);
|
||||||
|
if (i < data->uid_length - 1) Serial.print(" ");
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Serial.print("UID Length: ");
|
||||||
|
Serial.println(data->uid_length);
|
||||||
|
|
||||||
|
Serial.print("SAK: 0x");
|
||||||
|
if (data->sak < 0x10) Serial.print("0");
|
||||||
|
Serial.println(data->sak, HEX);
|
||||||
|
|
||||||
|
Serial.print("ATQA: 0x");
|
||||||
|
if (data->atqa[0] < 0x10) Serial.print("0");
|
||||||
|
Serial.print(data->atqa[0], HEX);
|
||||||
|
Serial.print(" 0x");
|
||||||
|
if (data->atqa[1] < 0x10) Serial.print("0");
|
||||||
|
Serial.println(data->atqa[1], HEX);
|
||||||
|
|
||||||
|
Serial.print("Tag Type: ");
|
||||||
|
Serial.println(data->tag_type);
|
||||||
|
|
||||||
|
Serial.print("Data Length: ");
|
||||||
|
Serial.println(data->data_length);
|
||||||
|
|
||||||
|
Serial.print("Timestamp: ");
|
||||||
|
Serial.println(data->timestamp);
|
||||||
|
|
||||||
|
if (data->data_length > 0) {
|
||||||
|
Serial.print("Raw Data: ");
|
||||||
|
for (int i = 0; i < data->data_length && i < 32; i++) {
|
||||||
|
if (data->raw_data[i] < 0x10) Serial.print("0");
|
||||||
|
Serial.print(data->raw_data[i], HEX);
|
||||||
|
Serial.print(" ");
|
||||||
|
}
|
||||||
|
if (data->data_length > 32) {
|
||||||
|
Serial.print("... (truncated)");
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
Serial.println("================");
|
||||||
|
}
|
||||||
67
nfc_protocol.h
Normal file
67
nfc_protocol.h
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef NFC_PROTOCOL_H
|
||||||
|
#define NFC_PROTOCOL_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
// Communication protocol definitions
|
||||||
|
#define MAX_NFC_DATA_SIZE 256
|
||||||
|
#define ESPNOW_CHANNEL 1
|
||||||
|
#define PAIRING_TIMEOUT_MS 30000
|
||||||
|
#define HEARTBEAT_INTERVAL_MS 1000
|
||||||
|
#define MAX_RETRY_ATTEMPTS 3
|
||||||
|
|
||||||
|
// Message types
|
||||||
|
enum MessageType {
|
||||||
|
MSG_PAIRING_REQUEST = 0x01,
|
||||||
|
MSG_PAIRING_RESPONSE = 0x02,
|
||||||
|
MSG_NFC_DATA = 0x03,
|
||||||
|
MSG_HEARTBEAT = 0x04,
|
||||||
|
MSG_ERROR = 0x05,
|
||||||
|
MSG_ACK = 0x06
|
||||||
|
};
|
||||||
|
|
||||||
|
// NFC data structure
|
||||||
|
struct NFCData {
|
||||||
|
uint8_t uid[10]; // NFC UID (max 10 bytes)
|
||||||
|
uint8_t uid_length; // Actual UID length
|
||||||
|
uint8_t sak; // Select Acknowledge
|
||||||
|
uint8_t atqa[2]; // Answer to Request Type A
|
||||||
|
uint8_t raw_data[MAX_NFC_DATA_SIZE]; // Raw NFC data
|
||||||
|
uint16_t data_length; // Actual data length
|
||||||
|
uint32_t timestamp; // Capture timestamp
|
||||||
|
uint8_t tag_type; // NFC tag type
|
||||||
|
};
|
||||||
|
|
||||||
|
// Communication message structure
|
||||||
|
struct CommMessage {
|
||||||
|
uint8_t type; // Message type
|
||||||
|
uint8_t sequence; // Sequence number
|
||||||
|
uint16_t payload_size; // Payload size
|
||||||
|
union {
|
||||||
|
NFCData nfc_data;
|
||||||
|
uint8_t raw_payload[sizeof(NFCData)];
|
||||||
|
};
|
||||||
|
uint16_t checksum; // Message integrity check
|
||||||
|
};
|
||||||
|
|
||||||
|
// Device roles
|
||||||
|
enum DeviceRole {
|
||||||
|
ROLE_LISTENER = 1,
|
||||||
|
ROLE_EMULATOR = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// Status codes
|
||||||
|
enum StatusCode {
|
||||||
|
STATUS_OK = 0,
|
||||||
|
STATUS_ERROR = 1,
|
||||||
|
STATUS_TIMEOUT = 2,
|
||||||
|
STATUS_INVALID_DATA = 3,
|
||||||
|
STATUS_NOT_PAIRED = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function prototypes
|
||||||
|
uint16_t calculateChecksum(const uint8_t* data, size_t length);
|
||||||
|
bool validateMessage(const CommMessage* msg);
|
||||||
|
void printNFCData(const NFCData* data);
|
||||||
|
|
||||||
|
#endif // NFC_PROTOCOL_H
|
||||||
Reference in New Issue
Block a user