/*
* Initial Author: ryand1011 (https://github.com/ryand1011)
*
* Reads data written by a program such as "rfid_write_personal_data.ino"
*
* See: https://github.com/miguelbalboa/rfid/tree/master/examples/rfid_write_personal_data
*
* Uses MIFARE RFID card using RFID-RC522 reader
* Uses MFRC522 - Library
* -----------------------------------------------------------------------------------------
* MFRC522 Arduino Arduino Arduino Arduino Arduino
* Reader/PCD Uno/101 Mega Nano v3 Leonardo/Micro Pro Micro
* Signal Pin Pin Pin Pin Pin Pin
* -----------------------------------------------------------------------------------------
* RST/Reset RST 9 5 D9 RESET/ICSP-5 RST
* SPI SS SDA(SS) 10 53 D10 10 10
* SPI MOSI MOSI 11 / ICSP-4 51 D11 ICSP-4 16
* SPI MISO MISO 12 / ICSP-1 50 D12 ICSP-1 14
* SPI SCK SCK 13 / ICSP-3 52 D13 ICSP-3 15
*
* More pin layouts for other boards can be found here: https://github.com/miguelbalboa/rfid#pin-layout
*/
#include <SPI.h>
#include <Wire.h>
#include <Keypad.h>
#include <avr/wdt.h>
#include <MFRC522.h>
#include <EEPROM.h>
#include <LiquidCrystal.h>
// Memory Manager
#include "mem.h"
// Software version string
#define SW_VERS "KeyMGR v26.01.08"
#define SW_COPY "(C)2026 Dakotath"
// Memory Layout
#define EEPROM_MAP_INTEGRITY 0
#define EEPROM_MAP_BEGIN 4
#define EEPROM_MAP_SETTINGS 100
#define EEPROM_MAP_RELAYS 1000
// Config
EEPROMStream eeprom(0);
// EEPROM Settings Layout
typedef struct {
bool LCDBacklight;
bool EnableBlip;
} EEPROMSettings_t;
// EEPROM Memory layout
typedef struct {
uint32_t entries;
uint32_t version;
} EEPROMLayout_t;
// Entry layout
typedef struct {
unsigned char name[32];
uint16_t type;
uint32_t value;
} EEPROMEntry_t;
// Keypad config
const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
char keys[ROWS][COLS] = {
{'1','2','3', 'U'}, // Up
{'4','5','6', 'D'}, // Down
{'7','8','9', 'E'}, // Enter
{'*','0','#', 'B'} // Back
};
const char *multiTapMap[10] = {
" 0", // 0
"ABC1", // 1
"DEF2", // 2
"GHI3", // 3
"JKL4", // 4
"MNO5", // 5
"PQRS6", // 6
"TUV7", // 7
"WXYZ8", // 8
".,?9" // 9 (optional symbols)
};
// Keypad connection
byte rowPins[ROWS] = {32, 33, 34, 35}; //connect to the col pinouts of the keypad
byte colPins[COLS] = {31, 30, 29, 28}; //connect to the row pinouts of the keypad
#define RST_PIN 5 // Configurable, see typical pin layout above
#define SS_PIN 53 // Configurable, see typical pin layout above
#define AU_PIN 49
#define NFC_START_BLOCK 4 // Name and Info block
#define NFC_RELAY_BLOCK 6 // Relay setup block
#define NFC_MAX_LEN 32 // 2 Blocks
// LCD Pin defs
#define LCD_REGSELECT 22
#define LCD_ENABLE 23
#define LCD_DATA4 24
#define LCD_DATA5 25
#define LCD_DATA6 26
#define LCD_DATA7 27
#define LCD_BACKLIGHT 36
// Relay pins
#define RELAY0 37
#define RELAY1 38
#define RELAY2 39
#define RELAY3 40
#define RELAY4 41
#define RELAY5 42
#define RELAY6 43
#define RELAY7 44
// Relay array
int relays[8] = {
RELAY0, RELAY1, RELAY2, RELAY3, RELAY4, RELAY5, RELAY6, RELAY7
};
// Real time clock
//DS1307 RTC(0x68,6,7,&Wire);
// Keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
// NFC Reader/Writer
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
// LCD Display
LiquidCrystal lcd(
LCD_REGSELECT,
LCD_ENABLE,
LCD_DATA4, LCD_DATA5, LCD_DATA6, LCD_DATA7
);
// Menu Entries
typedef struct {
const char *name;
void (*func)(void);
} menuentry_t;
unsigned long djb2_hash_int(int value) {
// Treat the integer as a sequence of bytes
unsigned char* ptr = (unsigned char*)&value;
unsigned long hash = 5381;
size_t i;
// Iterate over the bytes of the integer
for (i = 0; i < sizeof(int); i++) {
hash = ((hash << 5) + hash) + ptr[i];
}
return hash;
}
// Beeper
void blip(int time) {
// Change to wokwi compat
tone(AU_PIN, 500, time);
delay(time*2);
}
// Display LCD Error
void DispayLCDError(const char* line1, const char* line2) {
// LCD Error
lcd.clear();
lcd.noAutoscroll();
lcd.home();
lcd.print(line1);
lcd.setCursor(0, 1);
lcd.print(line2);
delay(2000);
}
// Reverify EEPROM Integrity
void RecalculateIntegrity() {
// Get HASH
int hash = 0;
eeprom.seek(0);
eeprom.read(&hash, sizeof(int));
// LCD Print
lcd.clear();
lcd.print("Recalculating");
lcd.setCursor(0, 1);
lcd.print("EEPROM Integrity");
// Verify Integrity
int hash_verify = 0;
int block_in = 0;
for(int i=0; i<eeprom.len - sizeof(int); i++) {
eeprom.read(&block_in, sizeof(unsigned char));
hash_verify = djb2_hash_int(block_in + hash_verify);
}
// Set hash
eeprom.seek(0);
eeprom.write(&hash_verify, sizeof(hash_verify));
lcd.clear();
// Check
if(hash != hash_verify) {
lcd.print("IntegRecalc OK!");
lcd.setCursor(0,1);
// Create the integ stat
char* integStr = (char*)malloc(16);
sprintf(integStr, ">%04x - %04x.");
} else {
lcd.print("IntegRecalc FAIL");
lcd.setCursor(0,1);
lcd.print("No changes made.");
}
delay(2000);
}
// Verify EEPROM integrity
void VerifyEEPROMIntegrity() {
// Get HASH
int hash = 0;
eeprom.seek(0);
eeprom.read(&hash, sizeof(int));
// LCD Print
lcd.clear();
lcd.print("Verify EEPROM...");
lcd.setCursor(0, 1);
// Verify Integrity
int hash_verify = 0;
int block_in = 0;
for(int i=0; i<eeprom.len - sizeof(int); i++) {
eeprom.read(&block_in, sizeof(unsigned char));
hash_verify = djb2_hash_int(block_in + hash_verify);
}
// Set hash
eeprom.seek(0);
eeprom.write(&hash_verify, sizeof(hash_verify));
lcd.clear();
// Check
if(hash == hash_verify) {
lcd.print("EEPROM OK!");
} else {
lcd.print("EEPROM Corrupted");
lcd.setCursor(0,1);
lcd.print("Reset EEPROM...");
for(int i=0; i<3; i++) {
blip(400);
}
// Reset EEPROM from beginning
// 255 to write to all blocks
uint8_t blankByte = 0xff;
for(int i=0; i<eeprom.len-EEPROM_MAP_BEGIN; i++) {
eeprom.write(&blankByte, sizeof(blankByte));
}
eeprom.seek(EEPROM_MAP_BEGIN);
// Recalculate integrity of EEPROM.
RecalculateIntegrity();
}
delay(1000);
}
bool WaitForTag(uint16_t timeoutMs = 0) {
uint32_t start = millis();
while (true) {
if (mfrc522.PICC_IsNewCardPresent() &&
mfrc522.PICC_ReadCardSerial()) {
return true;
}
if (timeoutMs && millis() - start > timeoutMs) {
return false;
}
}
}
static bool AuthenticateBlock(uint8_t block) {
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++)
key.keyByte[i] = 0xFF;
uint8_t trailer = (block / 4) * 4 + 3;
return mfrc522.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A,
trailer,
&key,
&(mfrc522.uid)
) == MFRC522::STATUS_OK;
}
// Relay config for NFC Tag
bool WriteRelayCountToTag(uint8_t count) {
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF;
}
if (!WaitForTag())
return false;
uint8_t block = NFC_RELAY_BLOCK;
// Determine trailer block for authentication
uint8_t trailer = (block / 4) * 4 + 3;
// Authenticate sector
if (mfrc522.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A,
trailer,
&key,
&(mfrc522.uid)) != MFRC522::STATUS_OK) {
return false;
}
// Prepare 16-byte data block
byte dataBlock[16] = {0};
dataBlock[0] = count; // store relay count in first byte
// Write block
if (mfrc522.MIFARE_Write(block, dataBlock, 16) != MFRC522::STATUS_OK) {
return false;
}
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return true;
}
bool GetRelayCountFromTag(uint8_t &count) {
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++) {
key.keyByte[i] = 0xFF;
}
if (!WaitForTag())
return false;
uint8_t block = NFC_RELAY_BLOCK;
// Calculate sector trailer block
uint8_t trailer = (block / 4) * 4 + 3;
// Authenticate sector
if (mfrc522.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A,
trailer,
&key,
&(mfrc522.uid)) != MFRC522::STATUS_OK) {
return false;
}
byte buffer[18]; // MFRC522 requires 18 bytes
byte size = sizeof(buffer);
// Read data block
if (mfrc522.MIFARE_Read(block, buffer, &size) != MFRC522::STATUS_OK) {
return false;
}
// Relay count stored in first byte
count = buffer[0];
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return true;
}
bool WriteRelaySettings2Card(const uint8_t* relays, uint16_t count) {
if (!relays || count > 256)
return false;
if (!WaitForTag())
return false;
uint8_t block = NFC_RELAY_BLOCK;
uint16_t offset = 0;
// Write count first
uint8_t totalBytes = count + 1;
while (offset < totalBytes) {
if ((block % 4) == 3) { // skip trailer blocks
block++;
continue;
}
if (!AuthenticateBlock(block))
return false;
byte dataBlock[16] = {0};
for (uint8_t i = 0; i < 16 && offset < totalBytes; i++) {
if (offset == 0)
dataBlock[i] = count;
else
dataBlock[i] = relays[offset - 1];
offset++;
}
if (mfrc522.MIFARE_Write(block, dataBlock, 16) != MFRC522::STATUS_OK)
return false;
block++;
}
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return true;
}
bool ReadRelaySettingsFromCard(uint8_t* relays, uint16_t& count) {
if (!relays)
return false;
if (!WaitForTag())
return false;
uint8_t block = NFC_RELAY_BLOCK;
uint16_t offset = 0;
bool countRead = false;
while (true) {
if ((block % 4) == 3) {
block++;
continue;
}
if (!AuthenticateBlock(block))
return false;
byte buffer[18];
byte size = sizeof(buffer);
if (mfrc522.MIFARE_Read(block, buffer, &size) != MFRC522::STATUS_OK)
return false;
for (uint8_t i = 0; i < 16; i++) {
if (!countRead) {
count = buffer[i];
if (count > 256)
return false;
countRead = true;
offset = 0;
} else {
if (offset < count) {
relays[offset++] = buffer[i];
} else {
goto done;
}
}
}
block++;
}
done:
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return true;
}
bool WriteTextToTag(const char *text, uint8_t len) {
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
if (!WaitForTag()) return false;
uint8_t block = NFC_START_BLOCK;
uint8_t offset = 0;
while (offset < len) {
byte dataBlock[16] = {0};
for (uint8_t i = 0; i < 16 && offset < len; i++) {
dataBlock[i] = text[offset++];
}
byte trailer = (block / 4) * 4 + 3;
if (mfrc522.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A,
trailer,
&key,
&(mfrc522.uid)) != MFRC522::STATUS_OK) {
return false;
}
if (mfrc522.MIFARE_Write(block, dataBlock, 16) != MFRC522::STATUS_OK) {
return false;
}
block++;
}
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return true;
}
bool ReadTextFromTag(char *buffer, uint8_t maxLen) {
MFRC522::MIFARE_Key key;
for (byte i = 0; i < 6; i++) key.keyByte[i] = 0xFF;
if (!WaitForTag()) return false;
uint8_t block = NFC_START_BLOCK;
uint8_t offset = 0;
while (offset < maxLen - 1) {
byte dataBlock[18];
byte size = sizeof(dataBlock);
byte trailer = (block / 4) * 4 + 3;
if (mfrc522.PCD_Authenticate(
MFRC522::PICC_CMD_MF_AUTH_KEY_A,
trailer,
&key,
&(mfrc522.uid)) != MFRC522::STATUS_OK) {
return false;
}
if (mfrc522.MIFARE_Read(block, dataBlock, &size) != MFRC522::STATUS_OK) {
return false;
}
for (uint8_t i = 0; i < 16 && offset < maxLen - 1; i++) {
if (dataBlock[i] == 0x00) {
buffer[offset] = '\0';
goto done;
}
buffer[offset++] = dataBlock[i];
}
block++;
}
done:
buffer[offset] = '\0';
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
return true;
}
// Run menu loops
void RunMenuArrayLoop(menuentry_t *menu, uint8_t menuSize) {
int selected = 0; // currently selected entry
int top = 0; // first visible entry
bool needsRedraw = true;
while (true) {
// --- Draw menu ONLY when needed ---
if (needsRedraw) {
lcd.clear();
for (int i = 0; i < 2; i++) {
int idx = top + i;
if (idx >= menuSize) break;
lcd.setCursor(0, i);
lcd.print(idx == selected ? ">" : " ");
lcd.print(menu[idx].name);
}
needsRedraw = false;
}
// --- Handle keypad ---
char key = keypad.getKey();
if (!key) continue;
blip(30);
switch (key) {
case 'U': // UP
selected--;
if (selected < 0) selected = menuSize - 1;
if (selected < top) top = selected;
needsRedraw = true;
break;
case 'D': // DOWN
selected++;
if (selected >= menuSize) selected = 0;
if (selected >= top + 2) top = selected - 1;
needsRedraw = true;
break;
case 'E': // ENTER
lcd.clear();
menu[selected].func();
needsRedraw = true; // redraw menu after returning
break;
case 'B': // BACK
lcd.clear();
return;
}
delay(150); // debounce / smooth scrolling
}
}
// Main menu functions
void Menu1() {
char buffer[33];
lcd.clear();
lcd.print("Tap NFC Tag");
if (ReadTextFromTag(buffer, sizeof(buffer))) {
lcd.clear();
lcd.print(buffer);
} else {
lcd.clear();
lcd.print("Read Failed");
}
delay(3000);
}
void Menu2() {
char buffer[33] = {0};
uint8_t len = 0;
char lastKey = 0;
uint8_t tapIndex = 0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Enter Text:");
while (true) {
char key = keypad.getKey();
if (!key) continue;
// -------- COMMIT CHARACTER --------
if (key == '#') {
if (lastKey && len < sizeof(buffer) - 1) {
buffer[len++] = multiTapMap[lastKey - '0'][tapIndex];
buffer[len] = '\0';
}
lastKey = 0;
tapIndex = 0;
}
// -------- DELETE --------
else if (key == '*') {
if (len > 0) {
len--;
buffer[len] = '\0';
}
lastKey = 0;
tapIndex = 0;
}
// -------- WRITE TO NFC (ENTER) --------
else if (key == 'E') {
lcd.clear();
lcd.print("Tap NFC Tag");
if (WriteTextToTag(buffer, len)) {
lcd.setCursor(0, 1);
lcd.print("Write OK");
} else {
lcd.setCursor(0, 1);
lcd.print("Write Failed");
}
delay(2000);
return;
}
// -------- EXIT --------
else if (key == 'B') {
return;
}
// -------- MULTI-TAP INPUT --------
else if (key >= '0' && key <= '9') {
if (key == lastKey) {
tapIndex++;
if (tapIndex >= strlen(multiTapMap[key - '0'])) {
tapIndex = 0;
}
} else {
lastKey = key;
tapIndex = 0;
}
}
// -------- DISPLAY --------
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(buffer);
lcd.setCursor(0, 1);
if (lastKey) {
lcd.print(">");
lcd.print(multiTapMap[lastKey - '0'][tapIndex]);
}
delay(200);
}
}
void Menu3() {
uint8_t relayList[] = {1, 3, 5, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
uint16_t relayCount = sizeof(relayList);
WriteRelaySettings2Card(relayList, relayCount);
uint8_t readRelays[256];
uint16_t readCount;
if (ReadRelaySettingsFromCard(readRelays, readCount)) {
Serial.print("Relays supported: ");
for (uint16_t i = 0; i < readCount; i++) {
lcd.clear();
lcd.print("Key Card Support");
lcd.setCursor(0,1);
lcd.print(readRelays[i]);
delay(500);
}
}
}
void Menu4() {
DispayLCDError(SW_VERS, SW_COPY);
DispayLCDError("This software is", "Developed by");
DispayLCDError("Dakotath (DCT)", "Summerside, PEI");
DispayLCDError("(C)2025-2026", "Dakota Thorpe");
}
// Hard-Reset via code
void hardReset() {
wdt_enable(WDTO_15MS); // shortest timeout
while (true) {} // wait for watchdog to bite
}
void Menu5() {
// Increment value without changing hash
int realayAssign = 0;
eeprom.seek(EEPROM_MAP_RELAYS);
eeprom.read(&realayAssign, sizeof(realayAssign));
eeprom.seek(EEPROM_MAP_RELAYS);
realayAssign+=1;
eeprom.write(&realayAssign, sizeof(realayAssign));
// Message then reset
for(int i=4; i>=0; i--) {
lcd.clear();
lcd.print("Corrupt EEPROM..");
lcd.setCursor(0, 1);
lcd.print("Reset in ");
lcd.print(i);
blip(500);
}
hardReset();
}
menuentry_t MainMenu[] = {
{ "Read NFC Chip", Menu1 },
{ "Write NFC Chip", Menu2 },
{ "Corrupt EEPROM", Menu5},
{ "Relay Config", Menu3 },
{ "About", Menu4 },
};
// Write settings
void WriteSettings() {
// Create settings structure
EEPROMSettings_t setting;
}
void SpectaSync(bool strobesOnly, int count) {
// AU Pin is internal audio pin, relay[0] is spectralert control relay, LOW is Relay ON
for(int i=0; i<count; i++) {
// 1. Maintain power for the majority of the second (Active High)
digitalWrite(relays[0], LOW);
if(strobesOnly) delay(1000-(15*4)); // 985ms of steady power
else delay(1000-(15*2));
//digitalWrite(AU_PIN, LOW);
//delay(985/2); // 985ms of steady power
//digitalWrite(AU_PIN, HIGH);
// 2. The Sync Pulse: A brief interruption of power
// This tells the strobe/horn to reset its internal cycle to 0
digitalWrite(relays[0], HIGH);
digitalWrite(AU_PIN, HIGH);
// Extra 30ms of time delay to shut the fucking horns up
if(strobesOnly) {
delay(15); // 15ms "dark" window (Standard for System Sensor)
delay(15); // 15ms "dark" window (Standard for System Sensor)
}
// Regular 15ms delay (sync pulse)
delay(15);
digitalWrite(AU_PIN, LOW); // Shut the fucking buzzer up
// Total cycle is 1000ms (1Hz)
}
// Ensure power stays ON after the loop ends
digitalWrite(AU_PIN, LOW);
digitalWrite(relays[0], HIGH);
}
//*****************************************************************************************//
void setup() {
Serial.begin(9600); // Initialize serial communications with the PC
// Setup blipper and LCD
pinMode(AU_PIN, OUTPUT);
lcd.begin(16,2);
// Test
VerifyEEPROMIntegrity();
// Initialize Relays
for(int i=0; i<8; i++) {
pinMode(relays[i], OUTPUT);
}
delay(500);
for(int j=0; j<8; j++) {
digitalWrite(relays[j], HIGH);
}
// Sync
SpectaSync(true, 10); // Strobes only
SpectaSync(false, 10); // Horns and strobes
// Setup LCD Backlight
pinMode(LCD_BACKLIGHT, OUTPUT);
digitalWrite(LCD_BACKLIGHT, HIGH);
// Print message and blip init
for(int i=0; i<10; i++) {
digitalWrite(AU_PIN, HIGH);
delay(50);
digitalWrite(AU_PIN, LOW);
delay(50);
}
DispayLCDError(
"Please Wait...",
"Initializing..."
);
// Init RTC
//RTC.begin();
// Check for RTC
if(false == false)
{
Serial.println("RTC Not Enabled!");
DispayLCDError("Internal Error", "RTC COM Disable");
blip(200);
blip(200);
blip(200);
blip(200);
blip(200);
}
// RFID Init
SPI.begin(); // Init SPI bus
mfrc522.PCD_Init(); // Init MFRC522 card
Serial.println(F("Read personal data on a MIFARE PICC:")); //shows in serial that it is ready to read
// Notify user it's ready
lcd.clear();
RunMenuArrayLoop(MainMenu, sizeof(MainMenu) / sizeof(MainMenu[0]));
}
//*****************************************************************************************//
void loop() {
delay(100);
}
//*****************************************************************************************//