Overview

What's more satisfying that an obscure puzzle lock? How about one that involves chess pieces and a remotely actuated cash drawer that opens as if by magic? That's awesome, let's build it!!

How It Works

This project uses an RFM69HCW Packet Radio Feather M0 paired with the PN532 NFC/RFID breakout board secreted under a chess board to read RFID tags hidden in the bases of the pieces. (These micro NFC/RFID NTAG tags are seriously tiny!)

When the correct pair of pieces are placed on the designated board spaces, the Feather board hidden inside the chess board sends an unlock message to the receiving Feather inside the cash drawer case that is paired with a Motor FeatherWing to drive a linear actuator, thus opening the drawer to reveal more clues. Amazing!

Here's the stream of John Park's Workshop LIVE if you'd like to see a more in depth tour of the build and operation.

Parts, Tools, & Materials

You'll need the parts listed in the section below, plus:

  • Chess board with storage space underneath to hide parts, such as a folding board, or one with internal piece drawers
  • Chess pieces with felt stickers on the bottoms (or get some felt stickers to add)
  • Two 1.5K ohm through hole resistors
  • Hook up wire
  • Trunk or drawer to open
  • Linear actuator, such as this one
  • Mounting hardware and fasteners, including clevis brackets for the actuator, or you can 3D print your own
  • Wire strippers
  • Soldering iron, solder
1 x RFID / NFC SMART RING - NTAG213
Optional operator's secret reset ring, choose your size
2 x UFL SMT ANTENNA CONNECTOR
Antenna connectors
1 x 12V 5A SWITCHING POWER SUPPLY
12V motor power supply
2 x 5V 2.4A SWITCHING POWER SUPPLY WITH 20AWG MICROUSB CABLE
Optional Feather power supplies/battery chargers

Build the Reader Circuit

The reader circuit is made up of the RFM69 Feather M0, the NFC/RFID breakout board, a couple of 1.5K ohm resistors, battery power, and a Terminal Block Breakout FeatherWing for connections and its handy on/off switch.

The two resistors are necessary when connecting the PN532 board in I2C mode to the Feather

Below is an example of the circuit wiring as done on a breadboard for testing.

Here's the same circuit and wiring transferred to the Terminal Block FeatherWing, before and after attaching the Feather.

First, solder on the male header pins to the Feather as shown here. You can use the terminal breakout FeatherWing as a jig to keep things square.

Next, you'll use the prototyping area of the FeatherWing to add the two resistors in-line with 3V power and the SCL (serial clock) & SDA (serial data) pins.

Clip off the excess leads from the SCL and SDA ends of the resistors, but leave the other ends long for now so they can be connected to the 3V wires in a moment.

Now, run some hookup wire from the free ends of the resistors to the 3V rail.

Note how the 3V wires will connect to the resistors.

Wrap the free resistor leads to the wire ends, then solder in place.

Solder the UFL antenna connector to the underside of the Feather.

It's helpful to pre-tin the pads, then rest the connector in place and reheat.

Snap on the antenna adapter cable, and screw on the antenna to the other end.

Now, you can solder wires to the PN532 board. The other ends of each wire will be screwed into the terminal breakout FeatherWing.

Here a more in depth guide about setting up the board, however, we'll be using it in I2C mode, not SPI mode. 

Strip the wires on one end, then tin the wires. Insert them into the appropriate pin holes and solder.

Once soldered, trim off any excess from the bottom side, so the board will lay as flat as possible on the underside of the chess board.

Be sure to solder the mode jumpers on the PN532 breakout board as shown here to use I2C mode. Note the reference silkscreen on the board.

You can now strip the other ends of the wires and screw them into the terminals as shown.

Now, you're ready to code and test the reader!

Code the Chess Piece Reader

The chess board will contain the reader and transmitter for the locking system. The software needs to do just a couple of things: read NFC/RFID tags to determine if the right combination of pieces has been scanned, and send a command to the receiver telling it to open or close the drawer.

Make sure you have your RFM69 Feather M0 up and running, using the instructions here, here, and here first. Then, you can use the code shown below by copying and pasting it into a new document in the Arduino IDE and saving the file as chessBoardPuzzleLockTX.ino. Then, upload it to your Feather.

Download: file
//Chess Board Puzzle Lock TX
// by John Park
// Using PN532 NFC Breakout Board on Feather M0 RFM69HCW
// based upon readMifareClassic by Adafruit Industries
//and RadioHead69_addrDemo_TX by AirSpayce

//watches for certain pairs of tags, sends a message

/*************************** Libraries ******************************/
#include <Wire.h>
#include <RH_RF69.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
#include <RHReliableDatagram.h>

/*************************** NFC Defines ******************************/
// If using the breakout or shield with I2C, define just the pins connected
// to the IRQ and reset lines.  Use the values below (2, 3) for the shield!
#define PN532_IRQ   (5)
#define PN532_RESET (6)  // Not connected by default on the NFC Shield

// Or use this line for a breakout or shield with an I2C connection:
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

/*************************** Radio Defines ******************************/
// Change to 434.0 or other frequency, must match RX's freq!
#define RF69_FREQ 915.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4
#define LED           13

// Where to send packets to!
#define DEST_ADDRESS   1
// change addresses for each client board, any number :)
#define MY_ADDRESS     2

// Singleton instance of the radio driver
RH_RF69 rf69(RFM69_CS, RFM69_INT);

// Class to manage message delivery and receipt, using the driver declared above
RHReliableDatagram rf69_manager(rf69, MY_ADDRESS);

int16_t packetnum = 0;  // packet counter, we increment per xmission

/*************************** NFC/RFID Card IDs  ******************************/

char* CARDS[] = {
  "Pawn",
  "Black Rook",
  "Black Knight",
  "Ring North",
  "Ring South"
  } ;

long CIDS[]={
  3125135392,
  3125205536,
  3124812064,
  3561521440,
  3561521184
} ;

//char cardTX[] = { //only used if sending individal card reads
//  'a','b','c','d','e'
//} ;

int cardCount = 1;

long lastCID;

uint32_t firstPiece;  //to check which piece is there
uint32_t secondPiece; //to check which piece is there

/***************************  Setup ******************************/

void setup() {

  Serial.begin(115200);
  //while (!Serial); // wait for serial console to open. remove if not tethered to computer
  
  pinMode(LED, OUTPUT);     
  pinMode (PN532_IRQ, INPUT);
  digitalWrite(PN532_IRQ, HIGH);
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, LOW);

  Serial.println("Chess Puzzle Card Reader");

  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // halt
  }
  // Got ok data, print it out!
  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 to read RFID tags
  nfc.SAMConfig();
  
  Serial.println("Waiting for an ISO14443A Card ...");



  Serial.println("\nFeather RFM69 TX...");
  Serial.println();

  // manual reset
  digitalWrite(RFM69_RST, HIGH);
  delay(10);
  digitalWrite(RFM69_RST, LOW);
  delay(10);


  if (!rf69_manager.init()) {
    Serial.println("RFM69 radio init failed");
    while (1);
  }
  Serial.println("RFM69 radio init OK!");
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module)
  if (!rf69.setFrequency(RF69_FREQ)) {
    Serial.println("setFrequency failed");
  }
 
  // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the
  // ishighpowermodule flag set like this:
  rf69.setTxPower(20, true);  // range from 14-20 for power, 2nd arg must be true for 69HCW
 
  // The encryption key has to be the same as the one in the server
  uint8_t key[] = { 0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};

  rf69.setEncryptionKey(key);

  Serial.print("RFM69 radio @");  
  Serial.print((int)RF69_FREQ);  
  Serial.println(" MHz");

}


// Dont put this on the stack:
uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];
uint8_t data[] = "  OK";


/*************************** Loop ******************************/

void loop() {

  uint8_t success; //store a sucessful card read
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;                        // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
    
  // Wait for an ISO14443A type cards (Mifare, etc.).  When one is found
  // 'uid' will be populated with the UID, and uidLength will indicate
  // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  
  if (success) { //any card has been read
    Blink(LED, 50, 1); //blink LED 1 times, 50ms between blinks

    
    // Display some basic information about the card
    //display these values in Serial monitor to generate list of known cards
    Serial.println("\n ------------------------------");
    Serial.println("\n Found an ISO14443A card");
    Serial.print("  UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
    Serial.print("  UID Value: ");
    nfc.PrintHex(uid, uidLength);
    Serial.println("\n ------------------------------");
    

    if(uidLength == 7){ //we've read a card w 7 bytes

        //the next lines take the "uid[]"" array and populate the integer "cardid" value 
        uint32_t cardid = uid[0]; //build the cardid variable from the uid array
        cardid <<= 8;
        cardid |= uid[1];
        cardid <<= 8;
        cardid |= uid[2];  
        cardid <<= 8;
        cardid |= uid[3]; 
        cardid <<= 8;
        cardid |= uid[4];
        cardid <<= 8;
        cardid |= uid[5];
        cardid <<= 8;
        cardid |= uid[6];
        cardid <<= 8;
        cardid |= uid[7];
        
        Serial.print(" cardid: ");
        Serial.println(cardid); 
        digitalWrite(13,HIGH);



        if(cardid!=lastCID){ // enable to prevent mult reads of same object from showing up
          for(int i=0; i<32; i++){ //check against the big list
            if(CIDS[i]==cardid){
              Serial.print("\nPiece ");
              Serial.print(cardCount);
              Serial. print(" is: ");
              Serial.println(CARDS[i]);
              Serial.println("\n ------------------------------");
            }
          }
          firstPiece=lastCID;
          Serial.print("firstPiece: ");
          Serial.println(firstPiece);

          secondPiece=cardid;
          Serial.print("secondPiece: ");
          Serial.println(secondPiece);
          
        }//end it isn't the same card as previous one 

        Serial.println("\n  same piece\n");
        lastCID=cardid;
        delay(500);
        digitalWrite(13,LOW);
        cardCount ++;
    } // end if UID length is 7

      //this is for checking 4 byte card cardid #s, integrate as above if using those cards instead of 7 byte cards
      //if(uidLength == 4){
        //uint32_t cardid = uid[0];
        //cardid <<= 8;
        //cardid |= uid[1];
        //cardid <<= 8;
        //cardid |= uid[2];  
        //cardid <<= 8;
        //cardid |= uid[3]; 
        //the next two lines give us the integer value of the card id to use in the cardid array
        //copy these to make your list
        //Serial.print("cardid: ");
        //Serial.println(cardid); 
        //digitalWrite(13,HIGH);
    //}//end 4 byte card read

        //test the pieces read against each unlock pairing, send the unlock message
        //Pawn and Rook
        if((firstPiece==3125205536 && secondPiece==3125135392)||(firstPiece==3125135392 && secondPiece==3125205536)){ //rook, pawn
          Serial.println("...................SOLVED");
          Serial.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>U N L O C K  I T");
          //send unlock message to the receiver
          //send the  unlock code 'A'
          char radiopacket[20];
          radiopacket[0]='A';
          radiopacket[1]='\0';
          //itoa(packetnum++, radiopacket+13, 10);
          Serial.print("-Sending "); 
          Serial.println(radiopacket[0]);
     
          // Send a message to the DESTINATION!  
          if (rf69_manager.sendtoWait((uint8_t *)radiopacket, strlen(radiopacket), DEST_ADDRESS)) {
          // Now wait for a reply from the server
            uint8_t len = sizeof(buf);
            uint8_t from;   
            if (rf69_manager.recvfromAckTimeout(buf, &len, 2000, &from)) {
              buf[len] = 0; // zero out remaining string
              
              Serial.print("\n\tGot reply from #"); Serial.print(from);
              Serial.print(" [RSSI :");
              Serial.print(rf69.lastRssi());
              Serial.print("] : ");
              Serial.println((char*)buf);     
              Blink(LED, 40, 3); //blink LED 3 times, 40ms between blinks
              delay(100);
            } 

            else {
              Serial.println("No reply, is anyone listening?");
            }
          } 
        
          else {
            Serial.println("Sending failed (no ack)");
          }
        }//end unlock pair
      

      //test the pieces read against each lock pairing, send the LOCK message
      //Reset Ring N/S
      if((firstPiece==3561521440 && secondPiece==3561521184)||(firstPiece==3561521184 && secondPiece==3561521440)){ //rook, pawn
        Serial.println("...................RESET RING");
        Serial.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> L O C K  I T");
        //send unlock message to the receiver
        //send the  unlock code 'A'
        char radiopacket[20];
        radiopacket[0]='B';
        radiopacket[1]='\0';
        //itoa(packetnum++, radiopacket+13, 10);
        Serial.print("-Sending "); 
        Serial.println(radiopacket[0]);
   
        // Send a message to the DESTINATION!  
        if (rf69_manager.sendtoWait((uint8_t *)radiopacket, strlen(radiopacket), DEST_ADDRESS)) {
        // Now wait for a reply from the server
          uint8_t len = sizeof(buf);
          uint8_t from;   
          if (rf69_manager.recvfromAckTimeout(buf, &len, 2000, &from)) {
            buf[len] = 0; // zero out remaining string
            
            Serial.print("\n\tGot reply from #"); Serial.print(from);
            Serial.print(" [RSSI :");
            Serial.print(rf69.lastRssi());
            Serial.print("] : ");
            Serial.println((char*)buf);     
            Blink(LED, 40, 3); //blink LED 3 times, 40ms between blinks
            delay(100);
          } 

          else {
            Serial.println("No reply, is anyone listening?");
          }
        } 
      
        else {
          Serial.println("Sending failed (no ack)");
        }
      }//end lock pair
  }//end successful read 
}//end loop()

void Blink(byte PIN, byte DELAY_MS, byte loops) {
  for (byte i=0; i<loops; i++)  {
    digitalWrite(PIN,HIGH);
    delay(DELAY_MS);
    digitalWrite(PIN,LOW);
    delay(DELAY_MS);
  }
}

After uploading the code, let's test it out. While keeping the Feather connected to your computer, open the Serial Monitor in the Arduino IDE to the current port being used. Set one of your NFC/RFID tags on the reader (near the rectangular antenna coil side of the board) and you'll see some information displayed about the tag.

You'll need to copy and paste the UID Value: # displayed into the code, overwriting the existing values associated with my tags in favor of your own. 

To use a pair of UID Values to unlock the puzzle, copy both values into the firstPiece and secondPiece values in the conditional statement on line 249. Be sure to copy into both sides of the || condition, so that either order of piece placement is accepted.

For example, scan a tag, copy it's UID Value from the Serial Monitor, then update line 57 of the code where the CIDS[] list is with this ID as the first entry, in my case, 3125135392. This will correlate it to "Pawn" as seen in the code on line 49. If you want to put that tag in the white bishop instead, change line 49 to "White Bishop".

For each tag you scan and enter, you may want to mark the tag with a piece of tape, or write on the bag in which they each came with a marker so you can distinguish them later without needing to re-scan them. Once you've inserted the tags into the bases of the chess pieces this won't be a problem!

It's helpful to have a pair of tags for resetting the puzzle and shutting the drawer. The NFC Smart Ring is perfect for this -- just copy the two UID Values from the ring into the CIDS array and drawer lock condition on line 290.

Build the Drawer Actuator Circuit

The actuator circuit consists of the RFM69 Feather M0 with antenna, the motor controller FeatherWing, the linear actuator, 12V 5A DC power supply for the motor, and LiPo battery or 5V DC power supply for the Feather.

Here's what the circuit looks like before the FeatherWing is placed on top of the Feather. Note, the battery will be plugged into the battery extension cable ON/OFF switch, represented here by the in-line slide switch.

This is the circuit after the FeatherWing has been placed on top of the Feather.

Prep the Feather M0 by soldering on the female socket headers, as seen here.

Assemble the Motor FeatherWing as shown here.

Get out your jeweler's loupe and solder on the antenna connector to the back of the Feather board! This link will show you how. Then, screw on the antenna to the cable, and connect it to the uFL jack.

The Motor FeatherWing needs a separate 12V power supply to run the motor, vs. the small LiPo or 5V supply that will power the Feather. We'll clip the end off of the 12V supply (make sure it isn't plugged in first!) and screw it's wires into the FeatherWing power terminals.

Optionally, you can use a power jack adapter if you'd like to keep your plug intact! https://www.adafruit.com/product/368

Clip the plug off, then carefully remove some outer insulation to expose the two wires within. One will be uninsulated, so twist it together. The other has insulation, remove a bit of that, then twist this end, too.

This is very important! Use a multimeter to determine the polarity of the two wires. Plug in the supply, and set your voltmeter to DC. Touch the probes to the wires and watch for a positive voltage of around 12V. If this is negative, switch the probes and try again. Once you've determined which is positive (in the case shown here, the wire with the white insulation is positive) note this down. This will be the wire you screw into the + terminal on the Motor FeatherWing.

I added some heat shrink tubing, color coded red for + and black for GND.

Double check the polarity one last time, then insert the wires into the Motor FeatherWing's power terminals -- positive to positive, negative to negative -- and screw them down tightly.

Next, you can insert the two wires of the linear actuator into one of the motor port pairs on the FeatherWing. You can place either wire into either terminal of any port (M1 in this case) as the motor is bi-directional, and can be reversed in software or in the real world later if you like.

This circuit is ready for coding!

Code the Drawer Actuator

On the receiving side, our RFM69 Feather M0 and Motor FeatherWing combo has two primary functions: listen for commands transmitted by the chess board system, and then driving the linear actuator's motor either forward or in reverse to open or close the drawer.

Here's the code you'll use below. Again, to use it, make a new document in the Arduino IDE, copy and paste the code, save it as chessBoardPuzzleLockRX.ino and then upload it to the Feather M0.

Download: file
//Chess Board Puzzle Lock RX
// by John Park
// Using Motor FeatherWing on Feather M0 RFM69HCW
// to drive linear actuator based on remote commands


/*************************** Libraries ******************************/
#include <RH_RF69.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <RHReliableDatagram.h>
#include <Wire.h>
#include <Adafruit_MotorShield.h>

/*************************** Motor Defines ******************************/
// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
// Select which 'port' M1, M2, M3 or M4. In this case, M1
Adafruit_DCMotor *myMotor = AFMS.getMotor(1);

/*************************** Radio Defines ******************************/
// Change to 434.0 or other frequency, must match RX's freq!
#define RF69_FREQ 915.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4

// Where to send packets to!
#define DEST_ADDRESS   2
// who am i? (server address)
#define MY_ADDRESS     1

// Singleton instance of the radio driver
RH_RF69 rf69(RFM69_CS, RFM69_INT);

// Class to manage message delivery and receipt, using the driver declared above
RHReliableDatagram rf69_manager(rf69, MY_ADDRESS);

int16_t packetnum = 0;  // packet counter, we increment per xmission


#define LED_PIN 13 //use as a power ON indicator
int drawerClosed = 1; //to keep track of state

/*************************** Setup ******************************/

void setup() {
	Serial.begin(115200);
    //while (!Serial) { delay(1); } // wait for serial console to open. remove if not tethered to computer
	pinMode(LED_PIN, OUTPUT);
	digitalWrite(LED_PIN,HIGH);

	//AFMS.begin();  // create with the default frequency 1.6KHz
	AFMS.begin(1000);  // OR with a different frequency, say 1KHz
	// Set the speed to start, from 0 (off) to 255 (max speed)
	myMotor->setSpeed(255);
	// turn on motor
	//open and close once on startup
	myMotor->run(FORWARD);
	delay(6000);
	myMotor->run(BACKWARD);
	delay(6000);
	myMotor->run(RELEASE);

	/////////////////////// radio ///////////////////////
	
	pinMode(RFM69_RST, OUTPUT);
	digitalWrite(RFM69_RST, LOW);

	Serial.println("Feather RFM69 RX Test!");
	Serial.println();

	// manual reset
	digitalWrite(RFM69_RST, HIGH);
	delay(10);
	digitalWrite(RFM69_RST, LOW);
	delay(10);

	if (!rf69_manager.init()) {
	Serial.println("RFM69 radio init failed");
	while (1);
	}
	Serial.println("RFM69 radio init OK!");

	// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module)
	if (!rf69.setFrequency(RF69_FREQ)) {
	Serial.println("setFrequency failed");
	}

	// If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the
	// ishighpowermodule flag set like this:
	rf69.setTxPower(20, true);  // range from 14-20 for power, 2nd arg must be true for 69HCW

	// The encryption key has to be the same as the one in the server
	uint8_t key[] = { 0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
	                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
	
	rf69.setEncryptionKey(key);

  	Serial.print("RFM69 radio @");  
  	Serial.print((int)RF69_FREQ);  
  	Serial.println(" MHz");

}


// Dont put this on the stack:
uint8_t data[] = "Message recieved, thanks";
uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];


/*************************** Loop ******************************/

void loop(void) {
	  uint8_t i;//for motor speed
	if (rf69_manager.available()) {

		// Wait for a message addressed to us from the client
		uint8_t len = sizeof(buf);
		uint8_t from;
		if (rf69_manager.recvfromAck(buf, &len, &from)) {
			
			buf[len] = 0; // zero out remaining string
		  
			Serial.print("\nGot packet from #"); Serial.print(from);
			Serial.print(" [RSSI :");
			Serial.print(rf69.lastRssi());
			Serial.print("] : ");
			Serial.println((char*)buf);

			char radiopacket[2];

			if ( buf[0]=='A'){ //this is the unlock code
				Serial.println("Unlocked");

				//turn the motor forward
				///////////////
				if(drawerClosed==1){
					Serial.println("Opening drawer");
					  myMotor->run(FORWARD);
					  //ramp up the speed
					  for (i=0; i<255; i++) {
					    myMotor->setSpeed(i);  
					    delay(10);
					}
					  //how long to run at top speed
					delay(2500); //this will vary depending on the throw of your actuator
					//ramp down the speed
					for (i=255; i!=0; i--) {
					    myMotor->setSpeed(i);  
					    delay(10);
					}
					myMotor->run(RELEASE);
					drawerClosed=0;//prevents multiple open signals from being sent
				}


				// Send a reply back to the originator client
				if (!rf69_manager.sendtoWait(data, sizeof(data), from))
	    			Serial.println("Sending failed (no ack)");
			}

			else if ( buf[0]=='B'){//this is the lock code
				Serial.println("Locked");

				//turn the motor backwards
				///////////////
				if(drawerClosed==0){
					Serial.println("Closing drawer");
					 myMotor->run(BACKWARD);
					 //ramp up the speed
					 for (i=0; i<255; i++) {
					    myMotor->setSpeed(i);  
					    delay(10);
					 }
					//how long to run at top speed
					delay(2500); //this will vary depending on the throw of your actuator
					//ramp down the speed
					for (i=255; i!=0; i--) {
						myMotor->setSpeed(i);  
					    delay(10);
					}
					myMotor->run(RELEASE);
					drawerClosed=1;//prevents multiple open signals from being sent
				}

				// Send a reply back to the originator client
				if (!rf69_manager.sendtoWait(data, sizeof(data), from))
	    		Serial.println("Sending failed (no ack)");
			}	
		}
	}// if radio available
	delay(100); //prevent a flood
}//end loop()

You can see in the code that the first thing it does upon power up or reset is to open and close the drawer once. This resets things to the ready position for the puzzle.

Then, rather than worry about the particular combinations of pieces on the chess board, it only receives two different commands. A letter 'A' means open the drawer, and a letter 'B' means close it! 

You could add other behaviors if you like, including driving different motors, lights, sounds, and so on.

The communication between the two RFM69 Feather boards is encrypted and sent to specific channels, so there's very little chance of an errant message flying through the airwaves and spoiling the fun!

Assemble the Chess Puzzle Lock

For the puzzle to work, we'll need to hide the micro NFC/RFID tags in the bases of the pieces you'd like to use in your puzzle. The most straightforward way is to use felt-bottomed pieces. You can carefully peel off the felt partway, insert the tag and close it back up.

You can use a little bit of super glue to seal the felt back onto the base.

Here are three finished pieces, as well as an NFC ring. The ring contains two tags in it, on in the "front" and one in the "back" (relative to a small jewel embedded in it). This can be worn by a magician or escape room operator and is used as a secret tag to discreetly reset the drawer when needed. You could also designate other pieces or artifacts for this purpose. 

Mounting the reader and electronics under the chess board is pretty straightforward. You can use very small screws or even double-sided foam tape to mount the reader flush to the board. 

Place the reader's antenna coil under the two board spaces you'll use in your puzzle, in this case I'm using g3 and h3.

Depending on how you'll be presenting your board for the puzzle, you may need to cover the bottom to avoid spoiling the gag. I used a piece of baltic birch plywood stained on one side. I glued in corner braces to fit and hold the bottom in place.

You can place felt protective feet on the corners for a nicer finish and to protect the table beneath. If you haven't got any on hand, you can improvise with some felt, a hole punch, and some glue!

Who would suspect anything is out of the ordinary?!

Mount the Drawer Actuator

You can open all sorts of different things using your linear actuator. Doors, secret bookcases, chest and trunk lids, drawers, or even vertically actuated hidden compartments recessed into  pedestals or table tops!

Your connection methods will vary depending on what you're opening. Here I'll show how to open a drawer.

You'll need to find enough space to mount the actuator -- in the case of a chest of drawers or cash drawer, the easiest way is to use the space of the drawer below the one you'd like to actuate. You can build the mount the actuator in the drawer below and fix it shut, or use the drawer front as a facade and remove the drawer entirely, as I did here.

I mounted a small wooden bracket to the facade so I could fix it in place with industrial Velcro for removal later.

Next, I tested the position of the actuator to determine the mounting point for the actuator connected to the bottom side of the drawer I want to open. This needs to be a point that allows the drawer to fully close when the actuator is retracted. Some trial and error using double stick foam tape while the actuator is opening and closing on a loop is a good way to determine this. Since I wanted to mount my actuator on its side, I used a single wood screw to connect it to a piece of scrap wood I mounted to the case bottom -- using a small enough diameter screw to allow movement of the actuator. I marked the top clevis position with a pencil and then removed the drawer from the case

I then predrilled some pilot holes, using the actuator clevis bracket as a guide. Due to the construction of my case (there was a piece in the way), I needed to put the drawer back in before screwing the clevis to its underside. I've also put the cash drawer dividers back in place.

I used a small screw starter to grip the screws and allow me to turn them into the pilot holes, then switched to a short screwdriver to finish the job.

The actuator rod's end hole is held in the clevis with a clevis pin, or in this case, an M6 bolt.

This system doesn't have active braking or obstruction sensing, so be very careful to keep fingers out of the drawer when it's closing! I didn't mount the wood base permanently to the case, so it can lift and supply relief if there is an obstruction.

Use It

It's time to test the whole system!

First, make sure the 12V supply for the actuator is plugged in.

Power up the two Feathers using their respective on/off switches. You'll see the red lights stay on when powered. The drawer will got through it's boot sequence of opening and closing.

Now, you can place the two designated pieces -- here a rook and pawn -- onto the board spaces -- here g3 and h3. After a moment, the drawer will mysteriously open!

When you are ready to reset it, you can either power the drawer Feather off and on, or simply use the ring's two tags to close it. You're ready for your next puzzle solver!

This guide was first published on Aug 25, 2017. It was last updated on Aug 25, 2017.