Interface Arduino with RFID and MySQL (via PHP)

Hey Everyone, this is my first post here, but I have been reading for a while. Got my first Arduino a couple months ago and my experimenting has been very low key. Now I am working on a much more ambitious project that I need help with.

I am trying to build an (almost) identical system to the one shown here: TechShop RFID | This is a prototype RFID internet controlled… | Flickr Built by Garrett Mace of Macetech http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?action=viewprofile;username=macegr, this system reads an RFID tag, sends the tag to a server for verification, and activates a trigger based on the response.

My system is the following: an Arduino Duemilanove, ID-12/20 reader, official Ethernet shield, and an installation of Xampp running Apache, MySQL, and PHP. This system will arm/disarm the alarm system at remote radio tower sites. I want to scan an RFID tag, push it to the server for authentication, read the response from the server and act accordingly.

So far, I have the supported web stuff sorted out - I can manually send a card ID and site variables in the URL to the server and get back a 1 or 0 depending on if it's valid or not. I have used various examples and notes from all over this forum and other sources (examples) to send the URL from the Arduino (WebClient.pde) and get back the proper response. I have also used example code for the RFID portion to confirm reading and printing to serial console.

My issue comes when putting it all together. I've been bashing my head around trying to understand and put things together one step at a time, but my brain is starting to hurt. :cry:

I have tried to adapt the RFID code from here http://www.instructables.com/id/Arduino-RFID-Door-Lock/ (very cool project btw). The code below is where I am at, obviously messy and I need to fix up the commenting left behind but it helps me figure out where I am. Or I'm just WAY lost... :o

#include <Ethernet.h>
#define powerPin    10
#define doorPin     11

byte mac[] = { 0x00, 0x00, 0x5E, 0x00, 0x01, 0xAA };
byte ip[] = { 192, 168, 2, 9 };
byte gateway[] = { 192, 168, 2, 254 };
byte subnet[] = { 255, 255, 255, 0 };
byte server[] = { 192, 168, 2, 148 }; 

Client client(server, 80);

int DO_RESET_ETH_SHIELD = 8;  // A delayed reset for a hardware issue

boolean match = false;

byte readCard[6];    // Stores an ID read from the RFID reader
byte checksum = 0;   // Stores the checksum to verify the ID 

void setup() 
{
  pinMode(powerPin, OUTPUT);      // Connected to Blue on tri-color LED to indicate reader is ready
  pinMode(doorPin, OUTPUT);       // Connected to relay to activate the door lock
  Serial.begin(9600);             // Connect to the serial port
  init_ethernet();
  Ethernet.begin(mac, ip);
}

void loop () 
{
  byte val = 0;       // Temp variable to hold the current byte

  normalModeOn();
  
    if(Serial.available() > 0)          // If the serial port is available and sending data...
    {
      if((val = Serial.read()) == 2)    // First Byte should be 2, STX byte 
      {                  
        getID();                        // Get the ID, sets readCard = to the read ID
        if (client.connect())
        {
            Serial.println("Connected!");
            client.print("GET /Test/test1.php?site=berland&active=1&card=");
            client.print(readCard[5]);
            client.println();
        }
        else
        {
            Serial.println("Connection failed");
        }
        Serial.println();        
      }
    }
  }

// If the serial port is ready and we received the STX BYTE (2) then this function is called 
// to get the 4 BYTE ID + 1 BYTE checksum. The ID+checksum is stored in readCard[6]
// Bytes 0-4 are the 5 ID bytes, byte 5 is the checksum
void getID()
{
  byte bytesread = 0;
  byte i = 0;
  byte val = 0;
  byte tempbyte = 0;
  
  // 5 HEX Byte code is actually 10 ASCII Bytes. 
  while ( bytesread < 12 ) // Read 10 digit code + 2 digit checksum
  {                        
    if( Serial.available() > 0)   // Check to make sure data is coming on the serial line
    { 
      val = Serial.read();        // Store the current ASCII byte in val
                                  
      if((val == 0x0D)||(val == 0x0A)||(val == 0x03)||(val == 0x02)) 
      {                           // If header or stop bytes before the 10 digit reading
        break;                    // Stop reading                                 
      }
      
      if ( (val >= '0' ) && ( val <= '9' ) )    // Do Ascii/Hex conversion
      {
        val = val - '0';
      } 
      else if ( ( val >= 'A' ) && ( val <= 'F' ) ) 
      {
        val = 10 + val - 'A';
      }

      if ( bytesread & 1 == 1 )      // Every two ASCII charactors = 1 BYTE in HEX format
      {
                                     // Make some space for this hex-digit by
                                     // shifting the previous hex-digit with 4 bits to the left:
        readCard[bytesread >> 1] = (val | (tempbyte << 4));
   
        if ( bytesread >> 1 != 5 )                // If we're at the checksum byte,
        {
          checksum ^= readCard[bytesread >> 1];   // Calculate the checksum using XOR
        };
      } 
      else                                        // If it is the first HEX charactor
      {
        tempbyte = val;                           // Store the HEX in a temp variable
      };
      bytesread++;                                // Increment the counter to keep track
    } 
  } 
  bytesread = 0;
}

// Opens door and turns on the green LED for setDelay seconds
void openDoor( int setDelay )
{
  setDelay *= 1000; // Sets delay in seconds
  
  digitalWrite(doorPin, HIGH);  // Unlock door!
  
  delay(setDelay); // Hold door lock open for 5 seconds
  
  digitalWrite(doorPin, LOW); // Relock door
}

// Controls LED's for Normal mode, Blue on, all others off
void normalModeOn()
{
  digitalWrite(powerPin, HIGH);    // Power pin ON and ready to read card
  digitalWrite(doorPin, LOW);      // Make sure Door is Locked 
}
void init_ethernet()
{
 pinMode(DO_RESET_ETH_SHIELD, OUTPUT);      // sets the digital pin as output
 digitalWrite(DO_RESET_ETH_SHIELD, LOW);
 delay(1000);  //for ethernet chip to reset
 digitalWrite(DO_RESET_ETH_SHIELD, HIGH);
 delay(1000);  //for ethernet chip to reset
 pinMode(DO_RESET_ETH_SHIELD, INPUT);      // sets the digital pin input
 delay(1000);  //for ethernet chip to reset
 Ethernet.begin(mac,ip,gateway,subnet);
 delay(1000);  //for ethernet chip to reset
 Ethernet.begin(mac,ip,gateway,subnet);
 delay(1000);  //for ethernet chip to reset
}

At this point I get the output Connected! at the serial interface when I read a card, but can't tell what it gets back. Also, after 4 reads it always fails to connect (or says it does, but I never see any more TX data). Also, my pin 11 is high all the time. :-?

Again, I am new to this, so any help is appreciated. Thanks in advance to anyone reading this.

Aaron

Also, after 4 reads it always fails to connect

After reading a RFID tag, you invoke client.connect(). The ethernet card supports up to 4 connections at a time. Since you never close the client connections, the failure to open another connection after the first 4 is understandable.

At this point I get the output Connected! at the serial interface when I read a card, but can't tell what it gets back.

The client.print statements send the data to the server (the GET request). There should be a response. Use client.available() in a while loop to wait for the response, then use client.read() to read the response.

Also, my pin 11 is high all the time.

Maybe it needs to lay off the dope for a while.

Since you never call openDoor to change the state, the state of the pin should never change. In setup(), make sure the doorPin is set to LOW:

digitalWrite(doorPin, LOW);

Hey Paul, thank you very much for the input. I will be working on this again today...

Paul, so far you've been a huge help - your comments above noting the while statement with client.available() and closing the client connection were great!! I tore everything apart today, and started over from the very basics of getting the card read, putting the ID in an array, printing that to serial, and then adding it to the web URL. A few more tweaks and the code runs reliably (as reliably as the Ethernet shield).

#include <Ethernet.h>

byte mac[] = { 0x00, 0x00, 0x5E, 0x00, 0x01, 0xAA };
byte ip[] = { 192, 168, 2, 9 };
byte gateway[] = { 192, 168, 2, 254 };  // Gateway only for management pinging
byte subnet[] = { 255, 255, 255, 0 };
byte server[] = { 192, 168, 2, 148 }; // Baker3

Client client(server, 80);

int DO_RESET_ETH_SHIELD = 8;  // Output pin to reset Ethernet shield
int readyLED =  10;
int validPin = 9;

byte readCard[6];    // Sotres an ID read from the RFID reader
byte checksum = 0;   // Stores the checksum to verify the ID 



void setup() 
{
  Serial.begin(9600);             // Connect to the serial port
  init_ethernet();   // Delayed reset of Ethernet shield
  pinMode(readyLED, OUTPUT);
  pinMode(validPin, OUTPUT);
  digitalWrite(readyLED, HIGH);
  digitalWrite(validPin, LOW);
}




void loop () 
{
  byte val = 0;       // Temp variable to hold the current byte
  
  if(Serial.available() > 0)          // If the serial port is available and sending data...
  {
    if((val = Serial.read()) == 2)    // First Byte should be 2, STX byte 
    {                  
      getID();                        // Get the ID, sets readCard = to the read ID
      byte bytesread = 0;
      Serial.println(client.connect());
      if (client.connect())
      {
        client.print("GET /Test/test1.php?site=berland&active=1&card=");
        for ( int i = 0; i < 5; i++ )         // Loop 5 times
        {
          if  ( readCard[i] < 16 )              // Print out 0 if < 16 to prepend output
          client.print("0");
          client.print(readCard[i], HEX);     // Print out the hex value read in
        }
        client.println();
    
        while(client.connected())
        {
          char c = client.read();
          Serial.print(c);
        }
      } 
      else
      {
        //Serial.println("connection failed");  // uncomment for debugging
      }    
      client.flush();
      client.stop();    
      Serial.println();

      //digitalWrite(validPin, HIGH);  // send valid pulse to SCADA Pack     
      init_ethernet();     // reset Ethernet shield after every read for reliability
      //digitalWrite(validPin, LOW);  // Reset valid pin 
    }
  }
}



// The following function copied from pcmofo of Instructables
// If the serial port is ready and we received the STX BYTE (2) then this function is called 
// to get the 5 BYTE ID + 1 BYTE checksum. The ID+checksum is stored in readCard[6]
// Bytes 0-4 are the 5 ID bytes, byte 5 is the checksum
void getID()
{
  byte bytesread = 0;
  byte i = 0;
  byte val = 0;
  byte tempbyte = 0;
  
  // 5 HEX Byte code is actually 10 ASCII Bytes. 
  while ( bytesread < 12 ) // Read 10 digit code + 2 digit checksum
  {                        
    if( Serial.available() > 0)   // Check to make sure data is coming on the serial line
    { 
      val = Serial.read();        // Store the current ASCII byte in val
                                  
      if((val == 0x0D)||(val == 0x0A)||(val == 0x03)||(val == 0x02)) 
      {                           // If header or stop bytes before the 10 digit reading
        break;                    // Stop reading                                 
      }
      
      if ( (val >= '0' ) && ( val <= '9' ) )    // Do Ascii/Hex conversion
      {
        val = val - '0';
      } 
      else if ( ( val >= 'A' ) && ( val <= 'F' ) ) 
      {
        val = 10 + val - 'A';
      }

      if ( bytesread & 1 == 1 )      // Every two ASCII charactors = 1 BYTE in HEX format
      {
                                     // Make some space for this hex-digit by
                                     // shifting the previous hex-digit with 4 bits to the left:
        readCard[bytesread >> 1] = (val | (tempbyte << 4));
   
        if ( bytesread >> 1 != 5 )                // If we're at the checksum byte,
        {
          checksum ^= readCard[bytesread >> 1];   // Calculate the checksum using XOR
        };
      } 
      else                                        // If it is the first HEX charactor
      {
        tempbyte = val;                           // Store the HEX in a temp variable
      };
      bytesread++;                                // Increment the counter to keep track
    } 
  } 
  bytesread = 0;
}



void init_ethernet()   // Delayed reset and startup for Ethernet shield
{
  pinMode(DO_RESET_ETH_SHIELD, OUTPUT);      // sets the digital pin as output
  digitalWrite(DO_RESET_ETH_SHIELD, LOW);
  digitalWrite(readyLED, LOW);                         // turn off Ready LED
  delay(1000);  //for ethernet chip to reset
  digitalWrite(DO_RESET_ETH_SHIELD, HIGH);
  delay(1000);  //for ethernet chip to reset
  pinMode(DO_RESET_ETH_SHIELD, INPUT);      // sets the digital pin input
  delay(1000);  //for ethernet chip to reset
  Ethernet.begin(mac,ip,gateway,subnet);
  delay(1000);  //for ethernet chip to reset
  digitalWrite(readyLED,HIGH);                        // wait for everything to be up and then turn on Ready LED
}

The last step I'm having trouble finding info on is how to handle the info that comes back from the web server. Currently I am using the while statement with the client.available() and client.read() commands to print the text to serial, but I'm not sure how to use it to trigger any outputs (only need 1). The text I get back is:

ÿÿÿÿÿÿ

PHP Test 1

Where the body is either a 1 or 0, but I'm not sure how to use just that part of the text. I've read a lot about "ignoring the first X lines and using the data after" but I have no idea how to do that. Any ideas?

Thanks again.

You could use the string library, and append each character to a string.

Then, use indexOf() to locate each <. Extract the rest of the string as a new string, overwriting the old string. Use startsWith() to see if the string starts with "body".

When it does, extract a substring starting at 5 (right after the >). Then locate the next < (in . The substring of interest then starts at 0 and ends just before the <.

Use itoa to convert the extracted string to an integer that will be either 0 or 1, after leading and trailing spaces are ignored.

Alternatively, you could change the PHP script so that instead of writing 0 or 1, it wrote "Entre vous" or "Stay the f**k out" (or whatever strings seem appropriate to you), and then use contains() to see if either of those strings are contained in the body of the message.

We have done this before with striping the php script to just return the answer we need with out all the html.

print "var1=1234"

so then we do a strncmp for the var1 and we have our data we need

we make our variables var1 var2 var3 all the same size, so we can get the answer easy

hope this helps

Just another reflexion (that have happen to me :slight_smile: ).

I don't know how your application timing is, but you should expect about 600ms to the tcp session to establish. In my case, that was unacceptable, but I didn't want to loose the apache/php part of the aplication, so I decided to write an Udp2http proxy, so the arduino sends the GET /xx.php request to the server over an UDP datagram, in the server I wrote a small piece of C code to take the UDP message, extract the HTTP command and establish a local TCP connection to the apache, get the response and, whatever is in an special html comment ( <!--- ARD: do_whatever --!> ) is sent back to the arduino over UDP as well.

With this, you can use the same php pages to send data to the aruidno in an easy way to process, and have a fancy webpage for the user (or for testing) .

Of course, if the Internet is in between your arduino and your server, using UDP could be quite dangerous!! (UDP is no guaranteed)

Maybe this comment is useless for you, but I've lost a lot of time trying to reduce does 600ms and, with the arduino ethershield, there is no way to reduce it.

regards
Chano

I was trying to figure out a way to post the code without giving too much information about the door locks at TechShop, but it's not really possible. I will say the code you already have is pretty close in concept. We keep reading lines until the headers are gone, then look for special keywords to find our data.

Thanks for all the help and ideas everyone. It's been a busy week and I haven't had any time in the last few days to work on this, hopefully over the weekend or early next week. I will keep you updated with my progress and post my final code when everything is working. Maybe even write an Instructable to help others.

Aaron

This one is gorgeous! Right now i'm planning to start the do exactly the same...

Are there any News for your work in Progress to See? :-/

Hey George, sorry haven't had much time to mess with this lately - except today. :wink: And hopefully I can finish this up soon and share with others.

At this point I'm having an issue figuring out how to use the response from the web server. I can get all of the http response into a string, and I can use the string.contains() function to look for the body of the text, but I'm not sure how to deal with 2 or more responses. Specifically, I the web page response contains keywords for the authorization level - ie. corporate, guest, denied.

I tried to use a switch and cases, but clearly not correctly. Anyone have any ideas? What I tried was this:

switch (result.contains())
      {
        case 'Staff':
          Serial.println("Staff Allowed");
          digitalWrite(StaffPin, HIGH);
          delay(1000);
          digitalWrite(StaffPin, LOW);
          break;
        case 'Guest':
          Serial.println("Guest Allowed");
          digitalWrite(GuestPin, HIGH);
          delay(1000);
          digitalWrite(GuestPin, LOW);
          break;
        case 'denied':
          Serial.println("Access Denied");
          break;
      }

I get the error "no matching function for call to 'String::contains()'. So, I'm pretty sure I can't use the switch this way. :-?

You need to use a series of if tests.

if(result.contains("Staff"))
{
   // Do the Staff stuff
}
else if(result.contains("Guest"))
{
  // Do the Guest stuff
}
else if(result.contains("denied"))
{
  // Do the "Keep out" stuff
}

Yah, that looks better. I had tried nested if statements, but it looked ugly and didn't work. haha I will try this right away.
Thanks again Paul.

Hey Paul, thanks for the tip, the else if statements work great!! A few tweaks to do, some commenting and I will post my final code. ;D