How can I add alternative RFID tag numbers to the current ones in this code?

Hi everyone.

I have been given some code that I have permission to use. It is essentially an object placement puzzle using RFID tags, place the right tag on the right reader, open a lock. Basic stuff.

My issue. Despite my best efforts to stop it, customers keep putting the tags in their pocket and walking away with them and if they don't come back in time, I have to unscrew the cupboard, hook up the arduino and manually type a new tag number into the code to get it solvable again. Fine if I have time, game-breaking if I am absent or short of time.

I'd imagine in the future, I could create something like a "scan and update" program for new tags, but I'm self taught and I'm not quite there yet.

So I had hoped to come to this forum seeking some guidance for adding a "pool" of tag numbers that can be added in advance (as little as two or three per reader), so if a tag does go missing, I can grab a spare and pop it back in the room with little delay.

I tried my best to take the arrays out of the picture and make the code more piecemeal but every time I end up breaking my test setup, as you can see the code is quite complex for a noob like me so there's clearly something fundamental about the way this tech works that I'm misunderstanding. It's quite possible a kind forum user will just say "add these two lines of code" and my solution is there haha :pray:

Here's the code, it works great currently for the single disparate tag numbers.

/**
* "Object Placement" Puzzle
*
* This puzzle requires the player to place one or more items in the
* correct location, at which point an RFID tag embedded in the object
* is detected by a sensor.
* When all sensors read the tag of the correct object, the puzzle is solved.
*
* Demonstrates:
* - SPI interface shared between several devices
* - RFID library
*/

// DEFINES
// Provides debugging information over serial connection if defined
#define DEBUG

// LIBRARIES
// Standard SPI library comes bundled with the Arduino IDE
#include <SPI.h>
// Download and install from https://github.com/miguelbalboa/rfid
#include <MFRC522.h>

// CONSTANTS
// The number of RFID readers
const byte numReaders = 4;
// Each reader has a unique Slave Select pin
const byte ssPins[] = {2, 3, 4, 5};
// They'll share the same reset pin
const byte resetPin = 8;
// This pin will be driven LOW to release a lock when puzzle is solved
const byte lockPin = A0;
// The sequence of NFC tag IDs required to solve the puzzle
const String correctIDs[] = {"example1", "example2", "example3", "example4"};

// GLOBALS
// Initialise an array of MFRC522 instances representing each reader
MFRC522 mfrc522[numReaders];
// The tag IDs currently detected by each reader
String currentIDs[numReaders];


/**
 * Initialisation
 */
void setup() {

  #ifdef DEBUG
  // Initialise serial communications channel with the PC
  Serial.begin(9600);
  Serial.println(F("Serial communication started"));
  #endif
  
  // Set the lock pin as output and secure the lock
  pinMode(lockPin, OUTPUT);
  digitalWrite(lockPin, HIGH);
  
	// We set each reader's select pin as HIGH (i.e. disabled), so 
	// that they don't cause interference on the SPI bus when
	// first initialised
	for (uint8_t i=0; i<numReaders; i++) {
		pinMode(ssPins[i], OUTPUT);
		digitalWrite(ssPins[i], HIGH);
	}
	
  // Initialise the SPI bus
  SPI.begin();

  for (uint8_t i=0; i<numReaders; i++) {
  
    // Initialise the reader
    // Note that SPI pins on the reader must always be connected to certain
    // Arduino pins (on an Uno, MOSI=> pin11, MISO=> pin12, SCK=>pin13)
    // The Slave Select (SS) pin and reset pin can be assigned to any pin
    mfrc522[i].PCD_Init(ssPins[i], resetPin);
    
    // Set the gain to max - not sure this makes any difference...
    // mfrc522[i].PCD_SetAntennaGain(MFRC522::PCD_RxGain::RxGain_max);
    
	#ifdef DEBUG
    // Dump some debug information to the serial monitor
    Serial.print(F("Reader #"));
    Serial.print(i);
    Serial.print(F(" initialised on pin "));
    Serial.print(String(ssPins[i]));
    Serial.print(F(". Antenna strength: "));
    Serial.print(mfrc522[i].PCD_GetAntennaGain());
    Serial.print(F(". Version : "));
    mfrc522[i].PCD_DumpVersionToSerial();
	#endif
    
    // Slight delay before activating next reader
    delay(100);
  }
  
  #ifdef DEBUG
  Serial.println(F("--- END SETUP ---"));
  #endif
}

/**
 * Main loop
 */
void loop() {

  // Assume that the puzzle has been solved
  boolean puzzleSolved = true;

  // Assume that the tags have not changed since last reading
  boolean changedValue = false;

  // Loop through each reader
  for (uint8_t i=0; i<numReaders; i++) {

    // Initialise the sensor
    mfrc522[i].PCD_Init();
    
    // String to hold the ID detected by each sensor
    String readRFID = "";
    
    // If the sensor detects a tag and is able to read it
    if(mfrc522[i].PICC_IsNewCardPresent() && mfrc522[i].PICC_ReadCardSerial()) {
      // Extract the ID from the tag
      readRFID = dump_byte_array(mfrc522[i].uid.uidByte, mfrc522[i].uid.size);
    }
    
    // If the current reading is different from the last known reading
    if(readRFID != currentIDs[i]){
      // Set the flag to show that the puzzle state has changed
      changedValue = true;
      // Update the stored value for this sensor
      currentIDs[i] = readRFID;
    }
    
    // If the reading fails to match the correct ID for this sensor 
    if(currentIDs[i] != correctIDs[i]) {
      // The puzzle has not been solved
      puzzleSolved = false;
    }

    // Halt PICC
    mfrc522[i].PICC_HaltA();
    // Stop encryption on PCD
    mfrc522[i].PCD_StopCrypto1(); 
  }

  #ifdef DEBUG
  // If the changedValue flag has been set, at least one sensor has changed
  if(changedValue){
    // Dump to serial the current state of all sensors
    for (uint8_t i=0; i<numReaders; i++) {
      Serial.print(F("Reader #"));
      Serial.print(String(i));
      Serial.print(F(" on Pin #"));
      Serial.print(String((ssPins[i])));
      Serial.print(F(" detected tag: "));
      Serial.println(currentIDs[i]);
    }
    Serial.println(F("---"));
  }
  #endif

  // If the puzzleSolved flag is set, all sensors detected the correct ID
  if(puzzleSolved){
    onSolve();
  }
 
  // Add a short delay before next polling sensors
  //delay(100); 
}

/**
 * Called when correct puzzle solution has been entered
 */
void onSolve(){

  #ifdef DEBUG
  // Print debugging message
  Serial.println(F("Puzzle Solved!"));
  #endif
  
  // Release the lock
  digitalWrite(lockPin, LOW);

  while(true) {
    delay(1000);
  }
  
}

/**
 * Helper function to return a string ID from byte array
 */
String dump_byte_array(byte *buffer, byte bufferSize) {
  String read_rfid = "";
  for (byte i=0; i<bufferSize; i++) {
    read_rfid = read_rfid + String(buffer[i], HEX);
  }
  return read_rfid;
}

And a schematic

Hopefully that's enough info for everyone but please let me know if I need to provide anything else.

Thank you!

so the magic happens in these lines

    // If the reading fails to match the correct ID for this sensor
    if (currentIDs[i] != correctIDs[i]) {
      // The puzzle has not been solved
      puzzleSolved = false;
    }

where the currentIDs acquired for reader #i is compared with the correct ID for reader #I

instead of having only one option for the correct answer, you could have multiple, say 1 key ID and 3 spares for each.

const String correctIDs[][4] = { 
  {"id0", "spareID0_1", "spareID0_2", "spareID0_3"},
  {"id1", "spareID1_1", "spareID1_2", "spareID1_3"},
  {"id2", "spareID2_1", "spareID2_2", "spareID2_3"},
  {"id3", "spareID2_1", "spareID3_2", "spareID3_3"}, 
};

and now to check if the currentIDs acquired for reader #i is correct, you need to loop and check against the 4 entries

something like

boolean matchFound = false;

// check the 4 entries to see if there is a match
for (int j = 0; j < 4; j++) {
    if (currentIDs[i] == correctIDs[i][j]) {
        matchFound = true;
        break;  // Exit the loop once a match is found
    }
}

// Update puzzleSolved based on whether a match was found
if (!matchFound) {
    puzzleSolved = false; 
}
1 Like

Thank you J-M-L, this is a really in depth response and it is much appreciated.

You responded to another one of my previous posts and I may have been a bit negative with you, I'm sorry about that, your advice is very useful.

I will incorporate what you've said and come back with the response and mark it as a solution if it works for me.

p.s. I also read your post that you linked in the previous post. It taught me a lot and I'm aware of some of the mistakes I make with my code, so thank you for that

I think @J-M-L 's idea will work, but it doesn't solve your problem forever. Even with 4 tags per reader, eventually, these will go astray and new ones, with unknown IDs, will need to be added, and as you said, you might not be there, or too busy to do it.

Would it not be better to build-in a "learn" mode, so that your colleagues can simply fix the problem without you in attendance?

A master tag, kept locked away from customers, could be presented to any rader to let the Arduino to know if it should be in learn mode or normal mode.

In normal mode, it would behave as it does now. But on entering learn mode, all previous tag IDs (except the master tag) would be forgotten. Each valid tag could then be presented at the correct reader and the Arduino would learn each tag's association with that reader. This information could be stored in the Arduino's EEPROM memory, instead of being hard-coded, at the moment of exiting learning mode.

1 Like

Yes you're right, but, a pool of let's say, 4 cards that do the same thing, there's enough redundancy in that for me to always have a workable tag at short notice, and if one or two get's lost, theres enough buffer there for me to unscrew the enclosure and update the code with some new tags

In other words, they don't get taken THAT often

I like the challenge of building a "learn mode" though. Imagine you place all the tags, press a button and it updates the code with the new information. That'd be very useful.

1 Like

PS. That's not a schematic!

Is that Fritzing?

What you posted, I think, is a breadboard wiring diagram. A schematic is a different thing. Fritzing has both "views". What's the difference? Compare the London underground "tube" map to a road map covering the same stations. Your breadboard wiring diagram is like that road map.

Actually, I'd argue the breadboard diagram is more akin to the Tube map than to the actual London Road Map.

The Tube map was designed to be easy to read and understand for anyone with little knowledge of London, at the loss of being geographically "correct" or "complete", as is so often the criticism of Fritzing diagrams in contrast to proper schematics. The distances in the Tube map, for example, may not be to scale but the colors make it easier to follow. In fact, Harry Beck's map design went on to define how other subways in the world would draw their maps, including New York City and Toronto.

Yes, I just recently watched Thoughty2's video on the London Underground.

The example sketches in the MFRC522 library seem to have an example to change the UID of a given card to make duplicates of some other card. Could always spend an afternoon making duplicates if this works the way the code implies it does. Never tried it myself though. Obviously, this isn't my code, it's one of the examples.

/*
 * --------------------------------------------------------------------------------------------------------------------
 * Example to change UID of changeable MIFARE card.
 * --------------------------------------------------------------------------------------------------------------------
 * This is a MFRC522 library example; for further details and other examples see: https://github.com/miguelbalboa/rfid
 * 
 * This sample shows how to set the UID on a UID changeable MIFARE card.
 * 
 * @author Tom Clement
 * @license Released into the public domain.
 *
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             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 <MFRC522.h>

#define RST_PIN   9     // Configurable, see typical pin layout above
#define SS_PIN    10    // Configurable, see typical pin layout above

MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance

/* Set your new UID here! */
#define NEW_UID {0xDE, 0xAD, 0xBE, 0xEF}

MFRC522::MIFARE_Key key;

void setup() {
  Serial.begin(9600);  // Initialize serial communications with the PC
  while (!Serial);     // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
  SPI.begin();         // Init SPI bus
  mfrc522.PCD_Init();  // Init MFRC522 card
  Serial.println(F("Warning: this example overwrites the UID of your UID changeable card, use with care!"));
  
  // Prepare key - all keys are set to FFFFFFFFFFFFh at chip delivery from the factory.
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

// Setting the UID can be as simple as this:
//void loop() {
//  byte newUid[] = NEW_UID;
//  if ( mfrc522.MIFARE_SetUid(newUid, (byte)4, true) ) {
//    Serial.println("Wrote new UID to card.");
//  }
//  delay(1000);
//}

// But of course this is a more proper approach
void loop() {
  
  // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle. And if present, select one.
  if ( ! mfrc522.PICC_IsNewCardPresent() || ! mfrc522.PICC_ReadCardSerial() ) {
    delay(50);
    return;
  }
  
  // Now a card is selected. The UID and SAK is in mfrc522.uid.
  
  // Dump UID
  Serial.print(F("Card UID:"));
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
    Serial.print(mfrc522.uid.uidByte[i], HEX);
  } 
  Serial.println();

  // Dump PICC type
//  MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
//  Serial.print(F("PICC type: "));
//  Serial.print(mfrc522.PICC_GetTypeName(piccType));
//  Serial.print(F(" (SAK "));
//  Serial.print(mfrc522.uid.sak);
//  Serial.print(")\r\n");
//  if (  piccType != MFRC522::PICC_TYPE_MIFARE_MINI 
//    &&  piccType != MFRC522::PICC_TYPE_MIFARE_1K
//    &&  piccType != MFRC522::PICC_TYPE_MIFARE_4K) {
//    Serial.println(F("This sample only works with MIFARE Classic cards."));
//    return;
//  }
  
  // Set new UID
  byte newUid[] = NEW_UID;
  if ( mfrc522.MIFARE_SetUid(newUid, (byte)4, true) ) {
    Serial.println(F("Wrote new UID to card."));
  }
  
  // Halt PICC and re-select it so DumpToSerial doesn't get confused
  mfrc522.PICC_HaltA();
  if ( ! mfrc522.PICC_IsNewCardPresent() || ! mfrc522.PICC_ReadCardSerial() ) {
    return;
  }
  
  // Dump the new memory contents
  Serial.println(F("New UID and contents:"));
  mfrc522.PICC_DumpToSerial(&(mfrc522.uid));
  
  delay(2000);
}

this only works with specific MIFARE cards where the UID is unprotected but indeed that could be an easy solution to keep the original code going and never worry again about missing tags.

And a schematic is designed to be easy to read and understand at the loss of being a physically accurate representation of the circuit, hence my analogy. :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.