Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

The 'Lampost'

5.00/5 (3 votes)
23 Jun 2018CPOL7 min read 9.2K   117  
The RFID reader idea that never made it...

Introduction

My brother-in-law had an idea of how to manage luggage in airports, using simple and cheap Radio-frequency identification (RFID) stickers. In this article, I will show how I created a prototype for the idea.

Because of the shape of the prototype, we decided to call it 'Lampost'.

Background

Since 2010, there has been a drastic drop in the number of lost luggage items at airports, largely due to the introduction of new technology for luggage handling. It has been as low as 5%, but if you have to switch flights the chance of your luggage being misdirected can be as high as 40%, or more. Last year IATA started investigating the possible use of RFID technology to improve the situation, According to their research, the cost is $0.10 per passenger with overall savings of at least $0.20.

The reason is that RFID is very cheap, flexible and needs no maintenance by the customer. A passive RFID is just a simple sticker than can be stuck, stitched or implanted on the luggage item. It requires no battery or other power source to keep it active.

So the general idea is:

  • All luggage items will be tagged with RFID stickers on check-in.
  • There will be fixed RFID readers anywhere that luggage is transported. There will also be mobile readers for locations where a fixed reader cannot be attached.
  • Each reader will report every item that passes it back to the central server application, which will keep track of the items' locations.
  • This will include luggage being unloaded from an aircraft and transported to the terminal building, and being loaded onto a carousel.

The Hardware

The brains of the project was a RedBoard, which is an Arduino Uno, with mini USB. I paired it with a WiFi shield, powered by an ESP8266, and paired with a 2.4 GHz antenna. To spare me some soldering for the prototype, I used some stackable headers to plug them together.

Image 1 Image 2 Image 3
The RedBoard, the WiFi shield and then stacked together. Images are courtesy of SparkFun Electronics, photographed by Juan Peña.

The next level of the solution is the RFID reader module. I chose one of the Micro modules made by ThingMagicm which met our requirements. This module comes on its own board, which is not compatible with the Arduino layout so (and to spare me some soldering) I got an Arduino Proto Shield, and asked a local professional to solder the RFID reader module to it (it has open edges - means edge soldering, so I preferred it that way). I then added that as 3rd level using some more stackable headers. The last piece was to attach the antenna to the RFID module.

Image 4

The RFID reader module. Image is courtesy of atlasRFIDstore.

The Setup

The WiFi shield can communicate with the Arduino board using UART via pins 0/1 (hardware) or 8/9 (software). It can be configured using an on-board switch. I picked the software port to leave the 0/1 pair unused in case I need more hardware that has no such flexibility. The RFID module can work in a number of ways (including a direct hook to the PC and step-by-step programming). For this project, I configured it to work in autonomous mode, for which I hooked it to the PC and used utility software that came with the device. In this mode, the module starts scanning for RFID tags when powered on and makes the data available via the UART whenever a tag is identified.

When soldering, I had the TX/RX lines of the RFID module connected to pins 10/11 of the Arduino, to leave 0/1 free for the same reason as described above.

At this point, all I needed was to power up the Arduino and the hardware part is complete.

The Software

The software solution has three parts. The Arduino is responsible for configuring the 'Lampost' unit, to listen to the RFID module, and to call the web service when a tag is identified. The web service is there to receive the tag's data from the Arduino and do all the related checks and notifications. There is also a simple desktop application that can be used to communicate with the 'Lampost' and configure it.

For the Arduino part, I used the Arduino IDE , and for the web service and configuration tool I used Visual Studio.

Arduino - The Configuration

This part of the Arduino program deals with the incoming commands from the desktop application that is used to configure the unit itself. This configuration is there to set the network information and identification of the 'Lampost'. The information is stored in the EEPROM memory of the Arduino, so even after a power failure or other restart, the module can connect to the network.

C++
#include <EEPROM.h>

static char _network[17];   // 16 + \0
static char _password[17];  // 16 + \0
static char _server[16];    // 15 + \0 -> 000.000.000.000

// Store data in EEPROM
void EEPROMwrite(char* buffer, int length, int start, int max)
{
    for (int i = 0; i < max; i++)
    {
        EEPROM.write(i + start, (i < length) ? buffer[i] : 0);
    }
}

// Validate input string
bool valid(char* input, int cmd_len, int max)
{
    strcpy(input, &input[cmd_len]);

    if (strlen(input) > max)
    {
        return(false);
    }

    return(true);
}

// Return string with current info about this module
//      INFO;NETWORK;SERVER
void getInfo()
{
    Serial.print(F("INFO;"));
    Serial.print(_network); Serial.print(F(";"));
    Serial.print(_server); Serial.print(F(";"));
    Serial.print("\n");
}

void handleCommands()
{
    if (Serial.available() > 0)
    {
        // Read input from serial port
        char inputLine[65];
        memset(inputLine, 0, 65);

        Serial.readBytesUntil('\n', inputLine, 64);

        if (strncmp(inputLine, "SSID:", 5) == 0)
        {
            if (valid(inputLine, 5, 16))
            {
                EEPROMwrite(inputLine, strlen(inputLine), 0, 17);
                EEPROMRead(_network, 17, 0);

                esp8266.disconnect();
            }
        }

        if (strncmp(inputLine, "PSW:", 4) == 0)
        {
            if (valid(inputLine, 4, 16))
            {
                EEPROMwrite(inputLine, strlen(inputLine), 17, 17);
                EEPROMRead(_password, 17, 17);

                esp8266.disconnect();
            }
        }

        if (strncmp(inputLine, "SRV:", 4) == 0)
        {
            if (valid(inputLine, 4, 15))
            {
                EEPROMwrite(inputLine, strlen(inputLine), 34, 16);
                EEPROMRead(_server, 15, 34);
            }
        }

        if (strncmp(inputLine, "INFO", 4) == 0)
        {
            getInfo();
        }
    }
}

void setup() {
    // init serial port
    Serial.begin(9600);
    while (!Serial);
}

void loop() {
    handleCommands();
}

Arduino - The WiFi Shield

Now we have all the information the module needs to identify itself and communicate with the web service, so we can actually start the communication. But first, we need to load the proper library for the WiFi shield to use. The library (in zip format) can be obtained here: SparkFun_ESP8266_AT_Arduino_Library, while the documentation to install it into the Arduino IDE is here: Arduino Libraries.

The new parts are in bold (and some of the old were omitted to keep it simple). As you can see, I put the WiFi shield into station-mode, as I only want to send, and not listen to incoming traffic. At this point, no data is sent anywhere, but if you check your network, you can see this module is up and running.

C++
#include <SoftwareSerial.h>
#include <SparkFunESP8266WiFi.h>
#include <EEPROM.h>

static char _network[17];   // 16 + \0
static char _password[17];  // 16 + \0
static char _server[16];    // 15 + \0 -> 000.000.000.000
static char _mac[18];       // 17 + \0 -> 00-00-00-00-00-00

// Return string with current info about this module
//      INFO;NETWORK;SERVER;MAC
void getInfo()
{
    Serial.print(F("INFO;"));
    Serial.print(_network); Serial.print(F(";"));
    Serial.print(_server); Serial.print(F(";"));
    Serial.print(_mac);
    Serial.print("\n");
}

void setup() {
    // init serial port
    Serial.begin(9600);
    while (!Serial);
    
    // load data from EEPROM
    EEPROMRead(_network, 16, 0);
    EEPROMRead(_password, 16, 17);
    EEPROMRead(_server, 15, 34);

    // Initialize WiFi shield
    while (!esp8266.begin(9600))
    {
        esp8266.reset();
    }

    // turn it into station mode
    while (esp8266.setMode(ESP8266_MODE_STA) < 0);

    // load mac address
    esp8266.localMAC(_mac);

    // connect
    esp8266.disconnect();
    esp8266.connect(_network, _password);
}

void loop() {
    // maintain connection
    if (esp8266.status() <= 0)
    {
        esp8266.localMAC(_mac);

        esp8266.disconnect();
        esp8266.connect(_network, _password);
    }

    handleCommands();
}

Arduino - The RFID Module

The RFID module is in autonomous mode, which means that it will not wait for me to ask it to scan for tags, but will do automatically, and collect the information in its internal buffer. So all I need is to open another serial port and read that internal buffer. As I decided to use a certain kind of tag (standard 96 bit information) for my prototype, all I need is to read in the 12 bytes of information and send them on to the web server.

In this section of code, there is a lot of working with the RFID module that is based on the documentation and code-samples that comes with it. I will not go into details of why - if you are intending to work with such a module, you will have to learn it for yourself.

C++
// this line is the skeleton for the HTTP POST command sent to the web server
static char _http[] = 
"POST http://000.000.000.000/LocalAPI/api/RFID HTTP/1.1\r\
nContent-Type:application/x-www-form-urlencoded\r\nHost:000.000.000.000\r\
nContent-Length:27\r\n\r\nid=000000000000";

static SoftwareSerial _RFIDSerial(10, 11);
static byte _msg[16];
static byte _response[256]; // RFID sends data in chuncks of 256 bytes
static byte _op = 0x22;
static unsigned long _tag_count = 0;

static const unsigned int _crc[] PROGMEM = {
  0x0000, 0x1021, 0x2042, 0x3063,
  0x4084, 0x50a5, 0x60c6, 0x70e7,
  0x8108, 0x9129, 0xa14a, 0xb16b,
  0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
};

static char _hex[] = 
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

// Compute CRC for RFID module commands
unsigned int crc(byte* buf, byte len) {
  unsigned int crc = 0xffff;

  for (int i = 0; i < len; i++) {
    crc = ((crc << 4) | (buf[i] >> 4)) ^ _crc[crc >> 12];
    crc = ((crc << 4) | (buf[i] & 0xf)) ^ _crc[crc >> 12];
  }

  return (crc);
}

// send message to RFID module / and receive answer
int rfid_msg(byte opcode, byte* data, byte len) {
  int response_len = 0;
  unsigned int msg_crc;
  unsigned int response_crc;

  // send the message
  _msg[0] = 0xff;
  _msg[1] = len;
  _msg[2] = opcode;

  if (len > 0) {
    memcpy(&_msg[3], data, len);
  }

  msg_crc = crc(&_msg[1], len + 2);

  _msg[len + 3] = msg_crc >> 8;
  _msg[len + 4] = msg_crc & 0xff;

  _RFIDSerial.listen(); // set this active
  _RFIDSerial.write(_msg, len + 5);

  // read back the response
  response_len = _RFIDSerial.readBytes(_response, 256);

  if (response_len > 0) {
    if (response_len == (_response[1] + 7)) {
      response_crc = crc(_response, response_len - 2);
    }

    if ((response_len == 5) || ((response_crc >> 8 == _response[response_len - 2]) && 
        (response_crc & 0xff == _response[response_len - 1]))) {
      if (_response[2] == _msg[2]) {
        if ((_response[3] == 0) && (_response[4] == 0)) {
          return ((response_len == 5) ? 0 : response_len - 7);
        }
        else {
          return(-1 * (_response[3] | _response[4] << 8)); // status error
        }
      }
      else {
        return(-1); // opcode error
      }
    }
    else {
      return(-2); // crc error
    }
  }
  else {
    return(-3); // response error
  }

  _client.print(_http);
}

// message 22 gets the number of tags awaiting in the internal RFID buffer
void rfid_22() {
  if (rfid_msg(0x22, new byte[5]{ 0x00, 0x00, 0x13, 0x01, 0xf4 }, 5) == 7) {
    _tag_count = _response[8] << 24 | _response[9] << 16 | _response[10] << 8 | 
                 _response[11];

    if (_tag_count > 0) {
      _op = 0x29;
    }
  }
}

// message 29 reads the internal RFID buffer
void rfid_29() {
  if (rfid_msg(0x29, new byte[3]{ 0x00, 0x00, 0x00 }, 3) > 0) {
    byte count = _response[8];

    for (int i = 0; i < count; i++) {
      int offset = 9 + 18 * i + 4;

      for (int p = 0; p < 12; p++) {
        _http[160 + p] = _hex[_response[offset + p]];
      }

      if(_client.connected()) {
        // send to the web server
        _client.print(_http);
  
        // clean WiFi buffer
        while (_client.available())
        {
          _client.read();
        }
      }
    }

    _tag_count -= count;
  }
  else {
    _tag_count = 0;
  }

  if (_tag_count == 0) {
    rfid_msg(0x2a, new byte[0], 0); // clear the internal buffer
    _op = 0x22;
  }
}

void handleRFDI() {
  if (_op == 0x22) {
    rfid_22();
  }

  if (_op == 0x29) {
    rfid_29();
  }
}

// insert server IP
void insertIP() {
  int len = strlen(_server);
  int empty = 15 - len;

  // for POST address
  memset(&_http[5], ' ', 22);

  memcpy(&_http[5 + empty], "http://", 7);
  memcpy(&_http[5 + empty + 7], _server, len);

  // for Host header
  memset(&_http[113], ' ', 15);
  memcpy(&_http[113], _server, len);
}

void setup() {
  // init serial port
  Serial.begin(9600);
  while (!Serial);

  // load data from EEPROM
  EEPROMRead(_network, 16, 0);
  EEPROMRead(_password, 16, 17);
  EEPROMRead(_server, 15, 34);

  // Initialize WiFi shield
  while (!esp8266.begin(9600)) {
    esp8266.reset();
  }

  // turn it into station mode
  while (esp8266.setMode(ESP8266_MODE_STA) < 0);

  // load mac address
  esp8266.localMAC(_mac);

  // connect
  esp8266.disconnect();
  esp8266.connect(_network, _password);

  // set web server's IP address on the HTTP command
  insertIP();

  // init RFID connection
  _RFIDSerial.begin(9600);

  rfid_msg(0x06, new byte[4]{ 0x00, 0x00, 0x25, 0x80 }, 4); // set BAUD rate 
                                                            // on RFID module too
}

void loop() {
  // maintain connection
  if (esp8266.status() <= 0) {
    esp8266.localMAC(_mac);

    esp8266.disconnect();
    esp8266.connect(_network, _password);
  }

  handleCommands();

  handleRFDI();
}

Desktop Configuration Tool

Now that we have the 'Lampost' up and running, we should configure it, so the WiFi connection should really send data to the web server (the RFID-Arduino part will work on its own, but the Arduino-WiFi part needs information).

It is a fairly simple application, all it does is scan the COM ports for a connected device. If one is found, it will display the current information on it and let the user set new values.

Image 5

The Missing Parts

To enable holding information on the several 'Lampost's, one may have, it would be a good idea to send the information from this application to a central database (indexed by MAC address maybe) so one may create a physical map of the 'Lampost's. That would be essential to keep passengers informed of the whereabout of their luggage.

In a more sophisticated solution, the WiFi connected to the Arduino would work both as server and as client, which would enable configuring it via the network.

Web Service

This is where I cannot show most of the code as much of it is very implementation specific (and definitely not related to the Arduino). What I can show you is a C# skeleton of how the RFID data is received.

C#
public class RFIDController : ApiController
{
    public void Post ( string ID )
    {
        if ( !string.IsNullOrEmpty( ID ) )
        {
            // Log data into database
            // Identify user based on ID
            // Send a SMS message
        }
    }
}

Summary

As I mentioned above, this is not a live project, but a proof of concept prototype. The actual prototype demonstrates how the Arduino can be used to create some fairly complicated hardware that can receive and transmit data from/to different sources. Such hardware - paired with the proper back-office software (and not the one I wrote) - can be used to improve services in many areas; all you need is the right idea...

History

  • 20th June, 2018: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)