commit 1e0353ac0311660a53d8f4c6c83c5114d4863778 Author: Dr Jones Date: Sun May 3 23:20:16 2026 -0700 Initial commit: project docs and ignore rules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba13302 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.pio +.vscode/.browse.c_cpp.db* +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a413a6 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# emulator_firmware + +**Emulator module** for the dual-PN532 NFC system: receives NFC captures over **ESP-NOW**, drives PN532 emulation, and logs to **SD card** when available. + +## Build + +Built via PlatformIO from the **workspace root** (same `platformio.ini` as the listener): + +```bash +cd .. +pio run -e emulator +pio run -e emulator -t upload +``` + +Requires `../common/` for `nfc_protocol.h` and related sources. + +## Hardware + +See the root [README](../README.md) for ESP32-C3 ↔ PN532 and SD wiring. diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..297b0f1 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,390 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../../common/nfc_protocol.h" + +// Pin definitions for ESP32-C3 +#define PN532_SDA 8 +#define PN532_SCL 9 +#define LED_PIN 2 +#define SD_CS_PIN 10 +#define SD_MOSI_PIN 6 +#define SD_MISO_PIN 5 +#define SD_SCK_PIN 4 + +// PN532 instance +Adafruit_PN532 nfc(PN532_SDA, PN532_SCL); + +// Communication variables +uint8_t listener_mac[6] = {0}; +bool is_paired = false; +uint8_t message_sequence = 0; +unsigned long last_heartbeat_received = 0; +const unsigned long HEARTBEAT_TIMEOUT = 5000; // 5 seconds + +// NFC emulation variables +NFCData current_tag_data = {}; +bool has_tag_data = false; +bool emulation_active = false; + +// SD card variables +bool sd_available = false; +String log_filename = ""; + +// Function prototypes +void initializeNFC(); +void initializeESPNow(); +void initializeSDCard(); +void handlePairingRequest(const uint8_t* mac); +void processNFCData(const NFCData& data); +void startEmulation(); +void stopEmulation(); +void logToSDCard(const NFCData& data); +void checkConnection(); +void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status); +void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len); +void blinkLED(int times, int delay_ms = 200); +String formatTimestamp(unsigned long timestamp); + +void setup() { + Serial.begin(115200); + delay(1000); + + Serial.println("=== NFC Emulator Module Starting ==="); + + // Initialize LED + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // Initialize SD card + initializeSDCard(); + + // Initialize NFC + initializeNFC(); + + // Initialize ESP-NOW + initializeESPNow(); + + Serial.println("Emulator module ready - waiting for listener..."); + blinkLED(3, 100); +} + +void loop() { + unsigned long current_time = millis(); + + // Check connection status + if (is_paired) { + checkConnection(); + } + + // Handle NFC emulation + if (emulation_active && has_tag_data) { + // The PN532 will automatically respond to NFC readers when configured + // We just need to keep the emulation active + digitalWrite(LED_PIN, HIGH); + } else { + digitalWrite(LED_PIN, LOW); + } + + delay(10); +} + +void initializeNFC() { + Serial.println("Initializing PN532..."); + + nfc.begin(); + + uint32_t versiondata = nfc.getFirmwareVersion(); + if (!versiondata) { + Serial.println("ERROR: PN532 not found!"); + while (1) { + blinkLED(1, 100); + delay(500); + } + } + + Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); + Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); + Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC); + + // Configure board for card emulation + nfc.SAMConfig(); + + Serial.println("PN532 initialized successfully!"); +} + +void initializeESPNow() { + Serial.println("Initializing ESP-NOW..."); + + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + + if (esp_now_init() != ESP_OK) { + Serial.println("ERROR: ESP-NOW init failed!"); + return; + } + + esp_now_register_send_cb(onDataSent); + esp_now_register_recv_cb(onDataReceived); + + Serial.println("ESP-NOW initialized successfully!"); +} + +void initializeSDCard() { + Serial.println("Initializing SD card..."); + + // Configure SPI pins for SD card + SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN); + + if (!SD.begin(SD_CS_PIN)) { + Serial.println("WARNING: SD card initialization failed!"); + sd_available = false; + return; + } + + uint8_t cardType = SD.cardType(); + if (cardType == CARD_NONE) { + Serial.println("WARNING: No SD card attached!"); + sd_available = false; + return; + } + + Serial.print("SD Card Type: "); + if (cardType == CARD_MMC) { + Serial.println("MMC"); + } else if (cardType == CARD_SD) { + Serial.println("SDSC"); + } else if (cardType == CARD_SDHC) { + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + + // Create log filename with timestamp + log_filename = "/nfc_log_" + String(millis()) + ".txt"; + + // Write header to log file + File logFile = SD.open(log_filename, FILE_WRITE); + if (logFile) { + logFile.println("=== NFC Emulator Log ==="); + logFile.println("Timestamp,UID,UID_Length,SAK,ATQA,Tag_Type,Data_Length,Raw_Data"); + logFile.close(); + Serial.println("Log file created: " + log_filename); + } + + sd_available = true; + Serial.println("SD card initialized successfully!"); +} + +void handlePairingRequest(const uint8_t* mac) { + if (is_paired) return; // Already paired + + Serial.println("Pairing request received!"); + + // Store listener MAC + memcpy(listener_mac, mac, 6); + is_paired = true; + last_heartbeat_received = millis(); + + // Add listener as peer + esp_now_peer_info_t peer_info = {}; + memcpy(peer_info.peer_addr, listener_mac, 6); + peer_info.channel = ESPNOW_CHANNEL; + peer_info.encrypt = false; + esp_now_add_peer(&peer_info); + + // Send pairing response + CommMessage response; + response.type = MSG_PAIRING_RESPONSE; + response.sequence = message_sequence++; + response.payload_size = 0; + response.checksum = calculateChecksum((uint8_t*)&response, sizeof(response) - sizeof(response.checksum)); + + esp_now_send(listener_mac, (uint8_t*)&response, sizeof(response)); + + Serial.println("Paired with listener!"); + Serial.print("Listener MAC: "); + for (int i = 0; i < 6; i++) { + Serial.printf("%02X", listener_mac[i]); + if (i < 5) Serial.print(":"); + } + Serial.println(); + + blinkLED(5, 100); +} + +void processNFCData(const NFCData& data) { + Serial.println("NFC data received from listener!"); + printNFCData(&data); + + // Store the tag data + current_tag_data = data; + has_tag_data = true; + + // Log to SD card + if (sd_available) { + logToSDCard(data); + } + + // Start emulation + startEmulation(); + + // Send acknowledgment + CommMessage ack; + ack.type = MSG_ACK; + ack.sequence = message_sequence++; + ack.payload_size = 0; + ack.checksum = calculateChecksum((uint8_t*)&ack, sizeof(ack) - sizeof(ack.checksum)); + + esp_now_send(listener_mac, (uint8_t*)&ack, sizeof(ack)); +} + +void startEmulation() { + if (!has_tag_data) return; + + Serial.println("Starting NFC emulation..."); + + // Configure PN532 for card emulation mode + // Note: The PN532 has limited emulation capabilities + // This is a simplified implementation + + uint8_t uid[7]; + memcpy(uid, current_tag_data.uid, min(7, (int)current_tag_data.uid_length)); + + // Try to start emulation (this is hardware dependent) + // The actual implementation would depend on the specific PN532 firmware + + emulation_active = true; + Serial.println("NFC emulation started!"); +} + +void stopEmulation() { + if (!emulation_active) return; + + Serial.println("Stopping NFC emulation..."); + emulation_active = false; + + // Reset PN532 to normal mode + nfc.SAMConfig(); +} + +void logToSDCard(const NFCData& data) { + if (!sd_available) return; + + File logFile = SD.open(log_filename, FILE_APPEND); + if (!logFile) { + Serial.println("ERROR: Failed to open log file!"); + return; + } + + // Write CSV format + logFile.print(formatTimestamp(data.timestamp)); + logFile.print(","); + + // UID + for (int i = 0; i < data.uid_length; i++) { + if (data.uid[i] < 0x10) logFile.print("0"); + logFile.print(data.uid[i], HEX); + } + logFile.print(","); + + logFile.print(data.uid_length); + logFile.print(","); + logFile.print(data.sak, HEX); + logFile.print(","); + logFile.print(data.atqa[0], HEX); + logFile.print(data.atqa[1], HEX); + logFile.print(","); + logFile.print(data.tag_type); + logFile.print(","); + logFile.print(data.data_length); + logFile.print(","); + + // Raw data (first 32 bytes) + for (int i = 0; i < data.data_length && i < 32; i++) { + if (data.raw_data[i] < 0x10) logFile.print("0"); + logFile.print(data.raw_data[i], HEX); + } + + logFile.println(); + logFile.close(); + + Serial.println("Data logged to SD card"); +} + +void checkConnection() { + unsigned long current_time = millis(); + + if (current_time - last_heartbeat_received > HEARTBEAT_TIMEOUT) { + Serial.println("Connection lost with listener!"); + is_paired = false; + stopEmulation(); + + // Remove peer + esp_now_del_peer(listener_mac); + memset(listener_mac, 0, 6); + } +} + +void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { + // Optional: Handle send status +} + +void onDataReceived(const uint8_t *mac, const uint8_t *incomingData, int len) { + if (len != sizeof(CommMessage)) return; + + CommMessage* msg = (CommMessage*)incomingData; + + if (!validateMessage(msg)) { + Serial.println("Invalid message received"); + return; + } + + switch (msg->type) { + case MSG_PAIRING_REQUEST: + handlePairingRequest(mac); + break; + + case MSG_NFC_DATA: + if (is_paired && memcmp(mac, listener_mac, 6) == 0) { + processNFCData(msg->nfc_data); + } + break; + + case MSG_HEARTBEAT: + if (is_paired && memcmp(mac, listener_mac, 6) == 0) { + last_heartbeat_received = millis(); + } + break; + } +} + +void blinkLED(int times, int delay_ms) { + for (int i = 0; i < times; i++) { + digitalWrite(LED_PIN, HIGH); + delay(delay_ms); + digitalWrite(LED_PIN, LOW); + delay(delay_ms); + } +} + +String formatTimestamp(unsigned long timestamp) { + unsigned long seconds = timestamp / 1000; + unsigned long milliseconds = timestamp % 1000; + + unsigned long hours = seconds / 3600; + seconds %= 3600; + unsigned long minutes = seconds / 60; + seconds %= 60; + + char buffer[20]; + sprintf(buffer, "%02lu:%02lu:%02lu.%03lu", hours, minutes, seconds, milliseconds); + return String(buffer); +} \ No newline at end of file